summaryrefslogtreecommitdiff
path: root/pieces
diff options
context:
space:
mode:
Diffstat (limited to 'pieces')
-rw-r--r--pieces/prepare.js128
-rw-r--r--pieces/retro-search.js304
-rw-r--r--pieces/suggest.js786
3 files changed, 0 insertions, 1218 deletions
diff --git a/pieces/prepare.js b/pieces/prepare.js
deleted file mode 100644
index 65ccee1..0000000
--- a/pieces/prepare.js
+++ /dev/null
@@ -1,128 +0,0 @@
-const fs = require('fs');
-var request = require('request');
-
-const kb = require('../utils/kb-util.js');
-
-
-// const https = require("https");
-
-
-/* TODO: ?? parallel read
- // var request = require('request-promise');
- var calls = [
- request({
- url: 'https://storage.googleapis.com/pythia-files/uploads/json/emarket-keywords.json',
- // headers: { ... }
- }),
- request({
- url: 'https://storage.googleapis.com/pythia-files/uploads/json/emarket-products.json',
- // headers: { ... }
- }),
- // + linked-terms and replaces
- // and more..
- ];
-
- Promise.all(calls).then(function(results) {
- // do something with results[0]
- // do something with results[1]
- // ...
- });
-*/
-
-
-/** create entity file from url
- * used for entities like 'linked-words', 'synonyms', etc.
- *
- * @param {string} entity: use the entity name
- * @param {string} url: prepared entity json-file in remote server
- */
-function create(entity, url) {
- request(url,
- function (error, response, body) {
- if (!error && response.statusCode == 200) {
- // body is a ready json-string; no need to parse and (re-)stringify
- try {
- fs.writeFileSync(`${__dirname}/../data/${entity}.json`, body, 'utf-8');
- // file written successfully
- } catch (err) {
- console.error(err);
- }
- }
- }
- );
-}
-
-function get_data_structure() {
- // data structure is an array of `{ url:.., expiration:.., path:.. }` objects
-}
-
-function set_data_structure(node) {
-
-}
-
-
-function load_products(url) {
- request(url,
- function (error, response, body) {
- if (!error && response.statusCode == 200) {
- // body is a ready json-string; no need to parse and (re-)stringify
- json = JSON.parse(body);
- newJson = [];
-
- // TODO:
- // + attach handle synonyms
- // + mark brand-names
- // + attach category-names and SAP-categories
- // + construct combo words
- // + remove non-important words
- // + normalize popularity
-
- json.forEach( p => {
- newJson.push({
- id: p.id,
- w: p.w,
- kb: kb.keyboardize(kb.clean(p.w))
- });
- });
-
- try {
-
- fs.writeFileSync(
- `${__dirname}/../data/products.json`,
- JSON.stringify(newJson),
- 'utf-8'
- );
- return true; // file written successfully
-
- } catch (err) {
- console.error(err);
- return false;
- }
- }
- }
- );
-}
-
-
-// create data
-// testing an implemenatatin of a custom local-fs-cache system
-function random() {
- var data = [];
- for( let i = 0; i < (Math.floor(Math.random() * 100) +15) ; i++) {
- data.push({ id: i, x5: i*5 });
- }
-
- try {
- fs.writeFileSync(__dirname + '/../data/random.json', JSON.stringify(data), 'utf-8');
- // file written successfully
- } catch (err) {
- console.error(err);
- }
-}
-
-
-module.exports = {
- create,
- random,
- load_products
-} \ No newline at end of file
diff --git a/pieces/retro-search.js b/pieces/retro-search.js
deleted file mode 100644
index ba4d937..0000000
--- a/pieces/retro-search.js
+++ /dev/null
@@ -1,304 +0,0 @@
-const kb = require('../utils/kb-util.js');
-const match = require('../utils/match-util.js');
-
-const products = require('../data/products.json');
-
-/** VARIABLES
- * may passed as module arguments
- * -----------------------------------------------------------------------------
- *//////////////////////////////////////////////////////////////////////////////
-
-
-var _allowFuzzy = true; // enable|disable fuzzy search
-var n = 2; // Ngram base
-var _fuzzyLimit = .5; // minimum bigram score for being considered a match
-var max_list = 24;
-var tolerance = 42;
-var STORE = { id: 904 };
-
-var _kwlinks; // keyword links (word-connections; imported via ajax-get)
-var _products = []; // all products (imported via ajax-get)
-
-// setup options
-var _maxResults = options.max_list; // limit suggestions
-var _blendProds = 4; // minimum final-produncts to blend with next-word suggestions
-var _Ngram_base = 2; // number of N in Ngram spliting algorithm
-var _isReady = false; // whether the searchbox is ready to be used
-
-// product keywords
-var keywordsURL = options.keywords_json;
-
-var cursor_on = { none: true }; // what product is highlighted; if not on product then { none: true }
-
-
-
-
-
-
-/** SUPPLEMENTARY FUNCTIONS
- * -----------------------------------------------------------------------------
- *//////////////////////////////////////////////////////////////////////////////
-
-
-
-// callback function for sorting resulrs per r (=rating) property
-function compare_rate(a,b) {
- return (a.r < b.r);
-}
-
-
-
-/** SEARCH ENGINE
- * -------------------------------------------------------------------------
- *//////////////////////////////////////////////////////////////////////////
-
-
-
-
-function matchWordInList(q, list = false) {
-
- if (list !== false && list.length == 0) return []; // no results
-
- var result = [];
- var firstPass = false;
-
- if (list === false) {
- firstPass = true; // on first pass
- list = products; // list is all products
- }
-
- for(let i = 0 ; i < list.length ; i++) {
-
- // check exact + rate
-
- // else check partial + rate
-
- // else check similarity + rate
-
- let found = false;
- let similarity = 0;
- products[i].kb.split(' ').forEach( w => {
- let sim = match.resemblance(q, w, 2);
- if (sim > 0.5) {
- found = true;
- similarity = (sim > similarity) ? sim : similarity;
- }
- });
- if (found) {
- products[i].similarity = similarity;
- products[i].rating = (firstPass)
- ? similarity * 2.0
- : products[i].rating + similarity * 2.0
- result.push(products[i]);
- }
- }
-
- return result
-}
-
-
-
-// suggestions engine //////////////////////////////////////////////////
-// ---
-function suggestions_engine(query) {
- var results = [];
- var pot = []; pot.length = 0;
-
- // clean and sanitize and mark links onto q(uery) string
- var q = kb.keyboardize( kb.sanitizeGR( kb.clean(query.trim()) ) ).trim();
-
- // TODO:
- // construct direct-linked words
- // = do unequivocally replaces
- // steps:
- // 1. replace accented vowels with non accented ones
- // 2. replace `/some pattern/gi , 'SOME-REPLACE-PATTERN'`
-
- var qAr = q.split(' '); // split to words
- /// if (space_ended) qAr.push(' '); // if space-end existed, push a space to query array
- ///
- /// if (qAr.slice(-1) == "") {
- /// qAr.pop();
- /// }
-
-
- pot = _products; // potential results // NOTE: CRITICAL: BY REFERENCE
-
- var wi = 0; // word index (from list)
- var wc = qAr.length;
-
- qAr.forEach( w => {
-
- let sf = []; // (matches) so far
- let mi; // position of match
- wi++;
-
- pot.forEach( it => {
- let matched = false;
- let tester = ' '+ it.kb + ' ';
-
- // reset previous history and ratings
- if (wi == 1) {
- it.r = 0;
- it.history = [];
- }
-
-
- // rate word-match > start-match > simple-match
- // ... up to 8 points
-
- if (tester.indexOf(' '+ w +' ') != -1) {
- it.r += 9;
- it.history.push({ w: w, rate: 9 });
- matched = true;
- }
- else if (tester.indexOf(' '+ w) != -1) {
- it.r += 5;
- it.history.push({ w: w, rate: 5 });
- matched = true;
- }
- else if (tester.indexOf(w) != -1) {
- it.r += 2;
- it.history.push({ w: w, rate: 2 });
- matched = true;
- }
-
- // rate `near-to-start` matching .. up to 7p
- // rate `earlyness` of word in query .. up to 7p
-
- if ((mi = tester.indexOf(' '+w)) != -1) {
- let fc1 = 100 - ((mi < 99) ? mi : 99); // near-to-start factor
- let fc2 = wc - wi + 1; // query earlyness factor
- let r1 = Math.floor(7*fc1/100);
- let r2 = Math.floor(7*fc2/wc);
-
- it.r += (r1 + r2);
- it.history.push({ w: w, left: [fc1, r1], early: [fc2, r2] });
- }
- if (matched) sf.push(it);
- });
-
- if ((sf.length > (_maxResults + Math.floor(_maxResults/2)))
- || (wi == 1) ) {
- // ..if pot has a fair amount (= max + 50%) of results
- // ..or these are results of '1st-query-word'
- // set sf as new source
- pot.lenght = 0; pot = [];
- pot = JSON.parse(JSON.stringify(sf)); // copy by value
-
- } else {
- // else.. keep the source list and increase of 'so-far rating'
- // console.log('found small list', sf, pot)
- pot.forEach( it => {
- sf.forEach( si => {
- if (it.id == si.id) {
- it.r += 10;
- it.history.push({ w: w, plus: '+10'});
- }
- });
- });
- }
- });
-
- // sort results, get max-list of best rated
- results = (pot.length > _maxResults)
- ? pot.sort(compare_rate).slice(0, _maxResults)
- : pot.sort(compare_rate)
-
- if (options.debug) console.log(results);
-
- return results;
-}
-
-
-// sub-module (start)
-////////////////////////////////////////////////////////////////////////////
-
-function update_common_search_results(q, results) {
- let queries = getSessionObj('sr');
- let newSRlist = [];
- let isnewQ = true;
- if (queries === null) {
- setSessionObj('sr', [{
- q: q,
- result: result,
- t: + new Date()
- }]);
- return true;
-
- } else {
-
- queries.forEach(it => {
- if (it.q == q) {
- newSRlist.push({
- q:q,
- result: result,
- t: + new Date()
- });
- isnewQ = false;
- } else { newSRlist.push(it); }
- });
-
- if (isnewQ) {
- newSRlist.push({
- q:q,
- result: result,
- t: + new Date()
- });
- }
-
- return true;
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////
-// sub-module (end)
-
-
-function common_search(query) {
- // clear ; sanitize ; split
- var qAr = keyboardize( sanitize_GR( clean_text(query) ) ).toLowerCase().split(' ');
-
- // if last item is empty, remove it
- if ((qAr.slice(-1) == ' ') || (qAr.slice(-1) == '')) qAr.pop()
-
- var results = _products;
-
- // for each key fitler results
- qAr.forEach( key => {
- results = key_sublist(key, results)
- });
-
- // echo products (and prepare list to POST)
- var list_ = [];
- results.forEach( item => {
- if (options.debug) console.log(item.id, ':', item.w);
- list_.push(item.id)
- })
-
- // *** TODO: keep results in local storage (or on session storage)
-
- // update_common_search_results(query, list_);
-
- let l = list_.join(',');
- var url = encodeURI(`${options.visualize_search_results_url}?search=${query}&eys_code=${l}`);
-
- console.log('common search: search query > location = search')
- window.location.href = encodeURI(`${options.visualize_search_results_url}?search=${query}`);
-
-}
-
-/** return from list only items that include 'key'
- */
-function key_sublist(key, list) {
- var result = [];
- list.forEach( item => {
- if (item.kb.includes(key)) {
- result.push(item);
- }
- });
-
- return result;
-}
diff --git a/pieces/suggest.js b/pieces/suggest.js
deleted file mode 100644
index c0a6e04..0000000
--- a/pieces/suggest.js
+++ /dev/null
@@ -1,786 +0,0 @@
-const kb = require('../utils/kb-util.js');
-const match = require('../utils/match-util.js');
-
-var _allowFuzzy = true; // enable|disable fuzzy search
-var n = 2; // Ngram base
-var _fuzzyLimit = .5; // minimum bigram score for being considered a match
-
-
-/** (Search) SUGGESTIONS ENGINE
- * ---------------------------------------------------------------------------
- *
- * Operates in dual mode;
- * -- suggestions engine (interactive)
- * -- classic-like mode (passive)
- *
- * TODO:
- * CRITICAL: (optimization)
- * Search initialization uses quite a lot of network sources;
- * thus it should be started in a later time;
- * lets say ... after `x` seconds
- * or... when document/core-ui-elements are ready
- *
- * @parametres (json) : options
- * ---
- * @var keywords_json (sring) : endpoint url of linked keywords structure
- * @var products_json (string) : endpoint od product descriptions
- * @var search_tag (string) : selector of field that shall act as typeahead-suggestions
- * @var visualize_search_results_url (str) : url that will visualize the sended "results-page"
- * @var debug (bool) : if true sends several debug console messages; if false mesagges are eliminated
- */
-
-suggestions_module({
- keywords_json: 'https://storage.googleapis.com/pythia-files/uploads/json/emarket-keywords.json',
- products_json: 'https://storage.googleapis.com/pythia-files/uploads/json/emarket-products.json',
- search_tag: '#tagsInput',
- visualize_search_results_url: '/product_list',
- debug: ((location.hostname == 'localhost') || (location.hostname == '127.0.0.1'))
-});
-
-function suggestions_module(options) {
-
- /** CONTENTS
- *
- * +1: Variables
- *
- * +2: Purify string functions
- * + keyboardize
- * + sanitize_GR
- * + clean
- * + mark_explicit_links
- *
- * +3: Supplementary function (vanilla js)
- * + ajax_get(url, callback)
- * + createNgram (fuzzy)
- * + checkSimilarity (fuzzy)
- * + check_match
- * + is_exact_match
- * + match_one
- *
- * +4: Actual data loading (async)
- *
- * +5: Suggestions Engine (jQuery)
- * + suggestions_engine
- *
- */
-
- console.log('executing suggestions...');
-
-
- /** 1. VARIABLES
- * -------------------------------------------------------------------------
- *//////////////////////////////////////////////////////////////////////////
-
- var _kwlinks; // keyword links (word-connections; imported via ajax-get)
- var _products = []; // all products (imported via ajax-get)
-
- // setup options
- var _maxResults = 24; // limit suggestions
- var _blendProds = 4; // minimum final-produncts to blend with next-word suggestions
- var _timeout_ms = 100; // time (in ms) for the search engine to find matches (before rendering)
-
- var _Ngram_base = 2; // number of N in Ngram spliting algorithm
- var _isReady = false; // whether the searchbox is ready to be used
-
- // product keywords
- var keywordsURL = options.keywords_json;
-
- var cursor_on = { none: true }; // what product is highlighted; if not on product then { none: true }
-
-
- /** replaces (correcting descriptions)
- * == construct unequivocally liked words //////////////////////////////////
- * -------------------------------------------------------------------------
- *
- * NOTE: TODO:
- * in future implementations multi-word keywords
- * may use the non-breaking space as conecting character (\u00A0) instead of dush (-)
- * (or maybe both of them)
- *
- * also TODO:
- * in future implementaions linked words may passed via some endpoint
- */
- replaces = [];
- replaceSource = [
- '3Α;3-ΑΛΦΑ',
- '3 ΑΛΦΑ;3-ΑΛΦΑ',
- 'HEAD & SHOULDERS;HEAD&SHOULDERS',
- 'HEAD N SHOULDERS;HEAD&SHOULDERS',
- 'W.K Kellogg; W-K-Kellogg',
- 'W.K Kellogg;',
- '7 DAYS;7-DAYS',
- '7 UP;7UP',
- '7-UP;7UP',
- 'ΜΠΑΡΜΠΑ ΣΤΑΘΗ;ΜΠΑΡΜΠΑ-ΣΤΑΘΗΣ',
- 'ΜΠΑΡΜΠΑ ΣΤΑΘΗΣ;ΜΠΑΡΜΠΑ-ΣΤΑΘΗΣ',
- 'COCA COLA;COCA-COLA',
- 'J P. CHENET; J.P.CHENET',
- 'J.P. CHENET; J.P.CHENET',
- 'COCACOLA;COCA-COLA',
- 'NES CAFE;NESCAFE',
- 'NES-CAFE;NESCAFE',
- 'LE PETIT MARSEILLAIS;LE-PETIT-MARSEILLAIS',
- 'PETIT MARSEILLAIS;PETIT-MARSEILLAIS',
- 'Το Μάννα;Το-Μάννα',
- 'Χωρίς Γλουτένη;Χωρίς-Γλουτένη',
- 'Χωρίς Ζάχαρη;Χωρίς-Ζάχαρη',
- 'Χωρίς Αλάτι;Χωρίς-Αλάτι',
- 'Χωρίς Λακτόζη;Χωρίς-Λακτόζη',
- 'Χωρίς Συντηρητικά;Χωρίς-Συντηρητικά',
- 'Χωρίς Αλκοόλ;Χωρίς-Αλκοόλ',
- 'Χωρίς Kαφεϊνη;Χωρίς-Kαφεϊνη',
- 'Χωρίς Kαφεΐνη;Χωρίς-Kαφεϊνη',
- 'Χωρίς Γλυκάνισο;Χωρίς-Γλυκάνισο',
- 'Χωρίς Ανθρακικό;Χωρίς-Ανθρακικό',
- 'Υψηλής Παστερίωσης;Υψηλής-Παστερίωσης',
- 'Ολικής Άλεσης;Ολικής-Άλεσης',
- 'Ολικής Aλέσεως;Ολικής-Aλέσεως',
- 'Χαρτί Υγείας;Χαρτί-Υγείας',
- 'ρολό υγείας;ρολό-υγείας',
- 'χαρτί τουαλέτας;χαρτί-τουαλέτας',
- 'Χαρτί Κουζίνας;Χαρτί-Κουζίνας',
- 'ρολό κουζίνας;ρολό-κουζίνας',
- 'Μπάρες Δημητριακών;Μπάρες-Δημητριακών',
- 'Ας Μαγειρέψουμε;Ας-Μαγειρέψουμε',
- 'ΚΡΙΣ ΚΡΙΣ;ΚΡΙΣ-ΚΡΙΣ',
- 'ΚΡΙΣΚΡΙΣ;ΚΡΙΣ-ΚΡΙΣ',
- 'ΚΡΙ ΚΡΙ;ΚΡΙ-ΚΡΙ',
- 'ΚΡΙΚΡΙ;ΚΡΙ-ΚΡΙ',
- 'ΕΛ ΓΚΡΕΚΟ;ΕΛ-ΓΚΡΕΚΟ',
- 'ΕΛΓΚΡΕΚΟ;ΕΛ-ΓΚΡΕΚΟ',
- 'FREE STEP;FREE-STEP',
- 'EL SABOR;EL-SABOR',
- 'ELSABOR;EL-SABOR',
- 'DOUWE EGBERTS;DOUWE-EGBERTS',
- 'DOUWEEGBERTS;DOUWE-EGBERTS',
- 'ΕΝ ΕΛΛΑΔΙ;ΕΝ-ΕΛΛΑΔΙ',
- 'ΕΝΕΛΛΑΔΙ;ΕΝ-ΕΛΛΑΔΙ',
- 'SPIN SPAN;SPIN-SPAN',
- 'SPINSPAN;SPIN-SPAN',
- 'CRETA-FARMS;CRETA-FARM',
- 'CRETA-FARM;CRETA-FARM',
- 'CRETAFARM;CRETA-FARM',
- 'Ολες-τις-Χρήσεις;Ολες-τις-Χρήσεις',
- 'Χωρίς προσθήκη ζάχαρης;Χωρίς-ζάχαρη',
- 'φρουι ζελε, φρουί-ζελε',
- 'DR BECKMANN, DR-BECKMANN'
- ];
- replaceSource.forEach( it => {
- st = sanitize_GR(
- it.toLowerCase()
- ).split(';');
- replaces.push({
- src: ' '+ st[0] +' ', // encolse between spaces
- trg: ' '+ st[1] +' ' // to separate from before/after words
- });
- });
-
- /** mark_explicit_links
- *
- * mark linked words shall be handled as one-(key)word
- * also, edit common mistakes with suggested replaces
- *
- * @param str
- * @return
- */
- function mark_explicit_links(str) {
- str = ' '+ str +' ';
- replaces.forEach( it => { str = str.replaceAll(it.src, it.trg); });
- return str.replaceAll(' ', ' ').trim();
- }
-
-
-
-
-
- /** 3. SUPPLEMENTARY FUNCTIONS
- * -------------------------------------------------------------------------
- *//////////////////////////////////////////////////////////////////////////
-
-
- // TODO:
- // exclude some generic non-critial words when proccessing user's query
- // ** example code to work with:
- // var ignoredKeys_kb = []; // keywords to ignore (in kb-format)
- // 'μας με σε για του της των από στο στον &'.split(' ').forEach(w => { ignoredKeys_kb.push(keyboardize(w)); });
-
- // pure JS ajax GET request; return data as JSON
- // no fancy things like UTF8; if needed use base64
- function ajax_get(url, callback) {
- var xmlhttp = new XMLHttpRequest();
- xmlhttp.onreadystatechange = function() {
- if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
- try {
- var data = JSON.parse(xmlhttp.responseText);
- } catch(err) {
- if (options.debug) console.log(err.message + " in " + xmlhttp.responseText);
- return;
- }
- callback(data);
- }
- };
- xmlhttp.open("GET", url, true);
- xmlhttp.send();
- }
-
-
-
-
- // Search Endine's match functions
- //////////////////////////////////////////////////////////////////////////////
-
- /** check_match
- * ---
- * check if a searching string -> query (string/latin in kb-format)
- * matches an item of the array of synonyms -> chkArr (array of utf-8/strings)
- *
- * + option to use fyzzy (bigram) match
- *
- * @return: matched string (utf-8) --or-- false (if not matched)
- */
- function check_match( query, chkArr, fuzzy = false ) {
- var result = '';
- var found = false;
-
- if (query == ' ') return chkArr[0];
-
- chkArr.forEach( chk => {
- if (!found) {
- chk_kb = keyboardize(chk);
- if ( (chk_kb.indexOf( query ) !== -1)
- || (fuzzy && (match.similarity(chk_kb, query, n) > _fuzzyLimit)) ) {
- found = true;
- result = chk;
- }
- }
- });
- return found ? result : false;
- }
-
- /** is_exact_match
- *
- * check if a searching string -> query (string/latin in kb-format)
- * matches exactly an item of the array of synonyms -> chkArr (array of utf-8/strings)
- *
- * @param query (string): searching string; string/latin in kb-format
- * @param chkArr (array): array of synonyms; (array of utf-8/strings)
- * @return (boolean): true|false
- */
- function is_exact_match( query, chkArr ) {
- found = false;
- chkArr.forEach( w => { if (keyboardize(w) == query) found = true });
- return found;
- }
-
- /** match_one
- *
- * check if at-least-ONE item from an array of query-words -> qArr (array of string/latin in kb-format)
- * matches any item of an array of synonyms -> chkArr (array of utf-8/strings)
- *
- * @return (boolean) true|false
- */
- function match_one( qArr, chkArr ) {
- found = false;
- qArr.forEach( query => {
- chkArr.forEach( w => { if (keyboardize(w) == query) found = true });
- });
- return found;
- }
-
-
-
-
- /** 4. ACTUAL DATA LOADING
- * -------------------------------------------------------------------------
- *//////////////////////////////////////////////////////////////////////////
-
-
-
- // TODO:
- // control completion of async loads via .then() rather by this custom structrure
- // Need to rewrite the folllowing code ............................. from here
- // ...........................................................................
- // ...........................................................................
-
-
- /** workline
- * -------------------------------------------------------------------------
- * custom object/data-structure
- * to track status/completion of async svents
- * (it does the job using a special set method)
- */
- var workline = {
-
- trackerJL : 0,
- set jsonLoaded(x) {
- this.trackerJL = x;
-
- // fire event on certain values
- if (x == 3) {
- if (options.debug) console.info('suggestion-engine requirements fulfilled');
- _isReady = true;
- // code to execute
- // ...
- }
- },
- get jsonLoaded() { return this.trackerJL; }
- };
-
-
- /** LOAD DATA (from endoints)
- * ---------------------------------------------------------------------------
- */
-
- ajax_get( keywordsURL, function(data) { // get _kwlinks
- _kwlinks = data;
- if (options.debug) console.log('...keywords loaded;');
- workline.jsonLoaded++;
- });
-
- function load_store_products(storeID) {
- ajax_get(options.products_json, function(data) { // get stor's _products
- _products = data;
- _products.forEach(p => {p.kb = keyboardize(p.w).toLowerCase()} );
- if (options.debug) console.log('...products loaded;');
- workline.jsonLoaded++;
- CURRENT_STOREs_CATALOG = storeID;
- });
- }
-
- var STORE = { id: 904 };
-
- if (STORE.id != 0) load_store_products(STORE.id);
-
- // ...........................................................................
- // ...........................................................................
- // ................................................................ up to here
-
-
-
-
- /** 5. SUGGESTIONS ENGINE
- * -------------------------------------------------------------------------
- *//////////////////////////////////////////////////////////////////////////
-
-
- workline.jsonLoaded++; // notify workline that jQuery is ready!!
-
-
- // suggestions engine //////////////////////////////////////////////////
- // ---
- function suggestions_engine(qOrig) {
- var results = []; // suggestions to respond
- var proList = []; // list of products (for all suggestions)
- var commonL = []; // list of common products (for multiple suggestions)
- var possibleNext = []; // list of possible next suggestions
-
- var root, last;
-
- var space_ended = (qOrig.slice(-1) == ' ') ? true : false;
-
- // clean and sanitize and mark links onto q(uery) string
- var q = mark_explicit_links( sanitize_GR( clean_text(qOrig) ) );
-
- // TODO:
- // construct direct-linked words
- // = do unequivocally replaces
- // steps:
- // 1. replace accented vowels with non accented ones
- // 2. replace `/some pattern/gi , 'SOME-REPLACE-PATTERN'`
- // --- -- -- - - -
- // test:
- // oneliner: var str = 'αλφα βητα ΑΛΦΑ Βητα world'; var src = 'αλφα βητα'; var reg = new RegExp(src, "gi"); var replacedOnce = str.toLowerCase().replace(reg, 'α-β'); console.log(replacedOnce);
-
- var qAr = q.split(' '); // split to words
- if (space_ended) qAr.push(' '); // if space-end existed, push a space to query array
-
- if (qAr.slice(-1) == "") {
- qAr.pop();
- }
-
- if (options.debug) console.log(qAr);
-
- if (qAr.length == 1) { // suggest 1st word //////////////////////
-
- var kbq = kb.keyboardize(q);
-
- // loop through root-words
- // ... to match all possible suggestions;
- _kwlinks.forEach( it => {
- chk = check_match(kbq, it.w, true);
-
- if ( chk != false ) {
- results.push({ w: chk });
-
- // ... also keep possible products in a list
- it.c.forEach( wo => { // for every Word-Link-Node
- if (proList.length < _maxResults + 2) { // NOTE: about +2 (bellow)
-
- // concatenate this-suggestion's product sub-list (wo.p)
- // to all-suggestions posiible products-list (proList)
- proList = proList.concat(wo.p);
- // keep unique products in the products-list
- proList = proList.filter((item, i, ar) => ar.indexOf(item) === i);
- }
- });
- }
-
- });
- }
-
- if (qAr.length == 2) { // suggest 2nd word /////////////////////////
- root = qAr[0];
- last = qAr[1];
- kbroot = kb.keyboardize(root);
- kblast = kb.keyboardize(last);
-
- // TODO:
- // change [_kwlinks.forEach] to for loop
- // ---
- _kwlinks.forEach( it => { // locate the ...
- if (match.exact(kbroot, kb.keyb_array(it.w))) { // exact match of root-word
-
- it.c.forEach ( wo => { // loop the word-links ...
- chk = check_match(kblast, wo.w, true);
- if ( chk !== false ) { // if a match is found
- results.push({ // keep suggestion
- w: root +' '+ chk,
- f: wo.f
- });
- if (proList.length < _maxResults + 2) { // plus...
- proList = proList.concat(wo.p); // keep products-list
- proList = proList.filter((item, i, ar) => ar.indexOf(item) === i);
- }
- }
- });
-
- }
- });
- }
-
- if (qAr.length > 2) { // suggest N-th word (N>2) ///////////////
- root = qAr.shift(); // isolate first item of qAr
- last = qAr.pop(); // isolate last item too
- // now qAr includes only the items after root and before last;
- // so qAr includes all already selected suggestions (but root)
-
- // prepare/cache kb-formated string for any key we're going to use
- var kb_qAr = []; // array of selected suggestions in kb-format
- qAr.forEach( w => { kb_qAr.push(keyboardize(w)); })
- kbroot = keyboardize(root);
- kblast = keyboardize(last);
-
- // TODO:
- // change [_kwlinks.forEach] to for loop
- // ---
- _kwlinks.forEach( it => {
- if (is_exact_match(kbroot, it.w)) { // locate root-word
-
- // calculate list of common items/products (commonL)
- // for selected suggestions
- is1stOcc = true; // 1st occurance flag
-
- it.c.forEach ( swo => {
- if (match_one(kb_qAr, swo.w)) {
- // swo is one of the already selected suggestions
- // so... update the common-(products)-L(ist)
- if (is1stOcc) {
- commonL = swo.p; // init list (on 1st occurance)
- is1stOcc = false;
- }
- else {
- // caclulate list of common products
- // = intersection of (so-far) commonL and swo.p
- // commonL = commonL.filter(value => swo.p.includes(value));
- commonL = commonL.filter(function(n) { return swo.p.indexOf(n) !== -1; });
- }
- }
- else { // if swo is not already selected
- // then This is a possible NEXT suggestion
- possibleNext.push(swo);
- }
- });
-
- // Now that we have all the possible next suggestions
- // we'll match them with the last word of the query
-
- possibleNext.forEach( poss => { // for tthe possible next suggestions
- // if last-word-of-query matches possible word(s)
- // and list of word's products has commons with commonL
- // then THiS is a Valid-Next-Suggestion
- chk = check_match(kblast, poss.w, true);
- if (chk !== false) {
- // check intersection of commonL and suggestion's product-lists
- tempL = commonL.filter(value => poss.p.includes(value));
- if (tempL.length > 0) {
- results.push({
- w: root +' '+ qAr.join(' ') +' '+ chk,
- f: poss.f
- });
- // update proList too
- proList = proList.concat(tempL);
- }
- }
- });
- }
-
- });
- }
-
- // NOTE: about +2 (vs +1)
- // after having calculated next suggestions and a banch of possible products
- // the proccedure is going to decide what data will return;
- // if pro(ducts)List includes less items than maximum suggestions
- // ... this will be the array to return. So
- // ... +2 ensures that this list will not become shorter than this limit
-
- // calculate unique products
- // (credit: https://stackoverflow.com/questions/11246758/)
- let unique = proList.filter((item, i, ar) => ar.indexOf(item) === i);
-
-
-
- if (unique.length < (_maxResults +1)) { // IF list is small ......
-
- // serve products instead of suggestions
- // ...
- results = [];
- notOnThisStore = []
- unique.forEach( pr => {
- for (i=0 ; i< _products.length-1 ; i++) { // for makes things faster
- pi = _products[i];
- if (pi.id == pr) {
- results.push(pi);
- break;
- }
- }
- });
-
- } else { // remove forced link character '-' from suggestions .....
- var dirty_results = results;
- results = [];
- for (i=0 ; i< dirty_results.length ; i++) {
- results.push({ w: dirty_results[i].w.replaceAll('-', ' ') })
- }
- }
-
- return results;
- }
-
- // simple, fast search products by numeric code property
- // ---
- function search_by_code( num ) {
- const str = num.toString();
- var results = [];
- var p;
- for (i=0 ; i< _products.length-1 ; i++) {
- p = _products[i];
- if (results.length > _maxResults) { break; }
- if ( (p.bp+'-'+p.bc).indexOf(str) !== -1 ) { results.push(p) }
- }
- return results;
- }
-
- // suggestions caller (router function)
- // arguments:
- // * qOrig : original query string
- // ** list : callback array structure to host results
- // ---
- var isuggest = function(qOrig, list) {
- var results;
-
- qSanit = sanitize_GR(qOrig); // sanitize greel accended chars
-
- // TODO:
- // check if all source-lists are ready
- // if not you need to wait ...
- // via async promishes or synced timouts
-
- // if query seems to be some king of 'code/id'
- if (qSanit.length>2 && qSanit.match(/^[0-9]+$/) != null) {
- results = search_by_code(qOrig);
-
- } else {
- // string match procedure with suggestions engine
- results = suggestions_engine(qOrig);
-
- }
- list(results);
- }
-
- /**
- jQuery(function() { // on document ready code //////////////////////////
- /**
-
- // UI-dependent code
- // uses reference to specific document element (passed via options)
- // -----------------------------------------------------------------------
- const searchBox = $(options.search_tag);
- searchBox.typeahead(
- {
- hint: true,
- highlight: true,
- minLength: 1
- },
- {
- limit: _maxResults,
- name: 'kwlinks',
- displayKey: 'w',
- source: isuggest,
- templates: {
- suggestion: function(data) {
- if (data.id)
- return '<div data-id="'+ data.id +'">'+ data.w +'</div>';
- return '<div>'+ data.w +'<span class="icon icon-arrowRight--b"></span></div>';
- },
- empty: '<div class="-empty-">Δεν υπάρχει στο κωδικολόγιο του καταστήματος</div>'
- }
- }
- )
- .bind("typeahead:selected", function(obj, datum, name) {
- if (datum.hasOwnProperty('id')) { // ** selected: PRODUCT
-
- // $('#js-add-product-to-order').attr('disabled', false);
- // fill_fields(datum.id, datum.sc, datum.x, datum.w, 1, datum.bc, datum.eu, datum.stk, datum.img);
- if (options.debug) console.log('selected: ', datum.id, datum.w);
-
- // TODO: GOTO product page
- window.location.href = 'product_list?product=id-' + datum.id;
-
- // $("#product-quantity").trigger('focus');
-
- }
- else { // selected: SUGGESTION
-
- $('.typeahead').typeahead('val','').trigger('blur');
- $('.typeahead').typeahead('val', datum.w +' ')
- .trigger("query");
- // give some time to the engine to calculate results
- // then fire focus again...
- setTimeout(() => { $('.typeahead').trigger('focus'); }, _timeout_ms);
- }
-
- })
- .bind("typeahead:cursorchange", function( event, obj) {
- // track cursor-chane to handle special keys after a final product is selected
-
- // TODO:
- // handle exception where obj is undefined;
- // this occures...
- // when cusror returns from suggestions list back to the search field
-
- if (typeof obj === 'undefined') cursor_on = { none: true };
- else if (obj.hasOwnProperty('id')) cursor_on = obj;
- else cursor_on = { none: true };
- });
-
- // TRACK user search attempt ///////////////////////////////////////////////
- $('.typeahead').on('keyup', function(e) {
- if (_isReady) {
- // console.log('on:', cursor_on);
- // console.log('key:', e.key);
- if ((e.key == ' ') && cursor_on.hasOwnProperty('id')) {
-
- // product is actually selected
- var datum = cursor_on;
-
- // fill_fields(datum.id, datum.sc, datum.x, datum.w, 1, datum.bc, datum.eu, datum.stk, datum.img);
- $("#product-quantity").trigger('focus');
- e.preventDefault();
-
- } else {
- if (e.key === "Enter") {
-
- // if curson is not on some option
- if ((typeof curson_on === 'undefined') || (curson_on.none == true)) {
-
- // do a common search
- if (options.debug) console.log('Do a Non-Suggestions search', searchBox.val());
-
- // DEPRICATED: var location = encodeURI('/product_list?productSearch=%'+ searchBox.val() +'%');
-
- search_results = common_search(searchBox.val());
-
-
- } else {
- // launch a product page
- var location = '/product_list?product=id-'+ cursor_on.id;
- if (options.debug) console.log('product', cursor_on.id, cursor_on.w);
-
- console.log('go to product', location)
- }
- // window.location.href = location;
- }
- }
- }
- });
-
- });
- */
-
-
- /*
- function common_search(query) {
- // clear ; sanitize ; split
- var qAr = keyboardize( sanitize_GR( clean_text(query) ) ).toLowerCase().split(' ');
-
- // if last item is empty, remove it
- if ((qAr.slice(-1) == ' ') || (qAr.slice(-1) == '')) qAr.pop()
-
- var results = _products;
-
- // for each key fitler results
- qAr.forEach( key => {
- results = key_sublist(key, results)
- });
-
- // echo products (and prepare list to POST)
- var list_ = [];
- results.forEach( item => {
- if (options.debug) console.log(item.id, ':', item.w);
- list_.push(item.id)
- })
-
- let l = list_.join(',');
- var url = encodeURI(`${options.visualize_search_results_url}?search=${query}&eys_code=${l}`);
-
- if ($('.js-categories.selected').length
- || $('.js-checkout.selected').length
- ) {
- url = encodeURI(`api/v1/products?eys_code=${l}&q=${query}`);
- console.log('common search : fetch results > history.push', url, query);
- fetchSearchProducts(url, query);
- window.history.pushState('search', null, '/product_list?search='+query);
- } else {
- console.log('common search: search query > location = search')
- window.location.href = encodeURI(`${options.visualize_search_results_url}?search=${query}`);
- }
-
- // Redirect with POST (template)
- // --- -- -- - - -
- // var form = $([
- // `<form action="${url}" method="post">`,
- // '<textarea name="products_list">',
- // list_.join(','),
- // '</textarea>',
- // '</form>'
- // ].join(''));
- // $('body').append(form);
- // form.submit();
-
- }
- */
-
- /** return from list only items that include 'key'
- */
- function key_sublist(key, list) {
- var result = [];
- list.forEach( item => {
- if (item.kb.includes(key)) {
- result.push(item);
- }
- });
-
- return result;
- }
-} \ No newline at end of file