summaryrefslogtreecommitdiff
path: root/html
diff options
context:
space:
mode:
Diffstat (limited to 'html')
-rw-r--r--html/inter.html61
-rw-r--r--html/map.html126
-rw-r--r--html/search-v2.html336
-rw-r--r--html/search-v3.html422
-rw-r--r--html/search.html403
-rw-r--r--html/search2.html642
6 files changed, 1123 insertions, 867 deletions
diff --git a/html/inter.html b/html/inter.html
new file mode 100644
index 0000000..fed1bd4
--- /dev/null
+++ b/html/inter.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <div id="result"></div>
+ </body>
+ <script>
+
+
+function intersect(arr1, arr2) {
+ var result = arr1.filter(function(n) { return arr2.indexOf(n) !== -1; });
+ return result;
+}
+
+var a = []
+var b = []
+var k = []
+var l = []
+var m = []
+
+var minitems = 75
+var maxitems = 150;
+var maxnumber = 500;
+
+/* ---
+var a = [2, 4, 6, 8, 10, 12, 14];
+var b = [3, 6, 9, 12, 15];
+console.log( intersect(a,b) ); --- */
+
+start = new Date();
+
+for(j=0 ; j < 10000; j++) {
+ a = [];
+ b = [];
+ for (i=0 ; i< (Math.floor(Math.random() * (maxitems-minitems)) + minitems) ; i++ ) {
+ x = Math.floor(Math.random() * maxnumber+1);
+ if (!a.includes(x))
+ a.push(x);
+ }
+
+ for (i=0 ; i< (Math.floor(Math.random() * (maxitems-minitems)) + minitems) ; i++ ) {
+ x = Math.floor(Math.random() * maxnumber+1);
+ if (!b.includes(x))
+ b.push(x);
+ }
+ // m = intersect(a, b);
+ m = a.filter(value => b.includes(value));
+}
+console.log(m);
+
+end = new Date();
+
+console.log(start, end, end-start);
+document.getElementById("result").innerHTML = (end-start) +"ms";
+
+
+
+ </script>
+</html> \ No newline at end of file
diff --git a/html/map.html b/html/map.html
new file mode 100644
index 0000000..6be59fe
--- /dev/null
+++ b/html/map.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <div id="result"></div>
+ </body>
+
+
+ <script>
+// arrays for kb-format (utf/EL-Gr to ascii translation)
+var ORiGiNal = 'ςερτυθιοπασδφγηξκλζχψωβνμΕΡΤΥΘΙΟΠΑΣΔΦΓΗΞΚΛΖΧΨΩΒΝΜάέήίόύώϊϋΆΈΉΊΌΎΏΪΫQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm0123456789-'.split('');
+var kbKeyZed = 'sertyuiopasdfghjklzxcvbnmertyuiopasdfghjklzxcvbnmaehioyviyaehioyviyqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm0123456789-'.split('');
+const map = new Map();
+for (i = 0 ; i < ORiGiNal.length; i++) map.set(ORiGiNal[i], kbKeyZed[i]);
+
+function kb_trans(str) {
+ str = str.replace('\'','');
+ var out = ''
+ for (var i=0 ; i< str.length; i++) { out += map.get(str[i]); };
+ // for (ch of str) { out += map.get(ch); };
+ return out;
+}
+
+/* ---
+function kb_trans(s) {
+ var charArr = s.split('')
+ var i
+ var out = ''
+ charArr.forEach( el => {
+ i = 0
+ exist = -1
+ ORiGiNal.forEach( ori => {
+ if (ori == el) {
+ exist = i;
+ }
+ i++;
+ })
+ out += (exist == -1) ? el : kbKeyZed[exist];
+ });
+ out = out.replace('\'','');
+ out = out.replace('-','');
+ return out;
+}
+--- */
+
+list = [ "καλαμπόκι-διαβητικών", "cocacola-zero", "γιουβαρλάκια", "WELCOME", "σφενδόνα", "σκλαβενίτης", 'bonora', 'kris-κρις-παπαδοπούλου', "τηλεφώνημα", "χωρίς-αλάτι" ];
+/* ---
+start = new Date();
+for (i=0 ; i<1000000 ; i++) {
+ tmp = kb_trans(list[i%10]);
+}
+end = new Date();
+console.log(start, end, end-start);
+document.getElementById("result").innerHTML = (end-start) +"ms";
+--- */
+
+
+start = new Date();
+for (i=0; i< 100000000 ; i++ ) { a = 0; }
+t0 = new Date();
+for (i=0; i< 100000000 ; i += 1 ) { a = 0; }
+end = new Date();
+console.log('++ :', t0-start);
+console.log('+= :', end-t0);
+
+
+someObj = [
+ { w: 'alfa', l: [ 1, 2, 3, 4, 5, 6, 7 ] },
+ { w: 'beta', l: [ 5, 4, 3, 2, 1] },
+ { w: 'pi', l: [ 3, 1, 4, 1, 5, 1, 9 ] }
+]
+
+function get_me(a, b) {
+ var _track = []
+ someObj.forEach( it => {
+ _track.push(it.w);
+
+ if (it.w == a) {
+
+ it.l.forEach( i => {
+ _track.push(i.toString());
+ if (i == b) {
+ _track.push('done!');
+ return true;
+ }
+
+
+ });
+ return true;
+ }
+
+ });
+ return _track;
+}
+
+
+function find_me(a, b) {
+ var _track = []
+ someObj.find( it => {
+ _track.push(it.w);
+
+ if (it.w == a) {
+
+ it.l.forEach( i => {
+ _track.push(i.toString());
+ if (i == b) {
+ _track.push('done!');
+ return true;
+ }
+
+
+ });
+ return true;
+ }
+
+ });
+ return _track;
+}
+get_me('beta',3);
+
+
+ </script>
+
+</html> \ No newline at end of file
diff --git a/html/search-v2.html b/html/search-v2.html
deleted file mode 100644
index 100565b..0000000
--- a/html/search-v2.html
+++ /dev/null
@@ -1,336 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="utf-8">
-
- <style>
-body { font-family: 'Cantarell', Helvetica, Arial, sans-serif; }
-
-.twitter-typeahead { width: 87% ;}
-.typeahead, .tt-query, .tt-hint {
- width: 100%; height: 30px;
- padding: 8px 12px; outline: none;
- font-size: 20px; line-height: 30px;
- border: 2px solid #ccc; border-radius: 8px;
- -webkit-border-radius: 8px;
- -moz-border-radius: 8px;
-}
-.tt-menu {
- width: 100%; margin: 12px 0; padding: 8px 0;
- background-color: #fff;
- border: 1px solid #ccc; border-radius: 8px;
- -webkit-border-radius: 8px;
- -moz-border-radius: 8px;
- -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
- -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
- box-shadow: 0 5px 10px rgba(0,0,0,.2);
-}
-.tt-suggestion { padding: 3px 20px; line-height: 24px; font-size: 18px; }
-.tt-suggestion span { padding-left: 16px; font-size: 14px; color: #777; float: right; }
-.tt-cursor { background: #ddd; }
-.tt-highlight { font-weight: normal; color: #777; }
-
-#selections { width: 87%; padding-top: 40px; }
-#selections div { padding: 4px 40px; line-height: 24px; font-size: 18px; color: #666; }
-#selections div span { padding-left: 16px; font-size: 14px; color: #999; float: right; }
- </style>
-
- <!-- js labraries -->
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/corejs-typeahead/1.2.1/bloodhound.min.js"></script>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/corejs-typeahead/1.2.1/typeahead.jquery.min.js"></script>
- </head>
- <body>
-
- <div id="the-basics">
- <input class="typeahead" id="tagsInput" type="text" placeholder="try me!">
- </div>
-
- <div id="selections">
- </div>
-
- </body>
- <script>
-
-// arrays for kb-format (utf/EL-Gr to ascii translation)
-var ORiGiN = 'ςερτυθιοπασδφγηξκλζχψωβνμΕΡΤΥΘΙΟΠΑΣΔΦΓΗΞΚΛΖΧΨΩΒΝΜάέήίόύώϊϋΆΈΉΊΌΎΏΪΫQWERTYUIOPASDFGHJKLZXCVBNM'.split('');
-var kbKeyZ = 'sertyuiopasdfghjklzxcvbnmertyuiopasdfghjklzxcvbnmaehioyviyaehioyviyqwertyuiopasdfghjklzxcvbnm'.split('');
-
-// convert string to kb-format
-function kb_trans(s) {
- var charArr = s.split('')
- var i
- var out = ''
- charArr.forEach( el => {
- i = 0
- exist = -1
- ORiGiN.forEach( ori => {
- if (ori == el) {
- exist = i;
- }
- i++;
- })
- out += (exist == -1) ? el : kbKeyZ[exist];
- });
- return out;
-}
-
-// public data objects
-var products;
-var everyProduct;
-
-function loadData() {
- const xhttp = new XMLHttpRequest();
- xhttp.onload = function() {
- products = JSON.parse(this.responseText);
- }
- xhttp.open("GET", "results/keywords-v3.json");
- xhttp.send();
-}
-loadData();
-
-function loadData2() {
- const xhttp = new XMLHttpRequest();
- xhttp.onload = function() {
- everyProduct = JSON.parse(this.responseText);
- }
- xhttp.open("GET", "results/products.json");
- xhttp.send();
-}
-loadData2();
-
-// bigram fuzzy match
-// --- credit: https://dirask.com/posts/JavaScript-check-words-similarity-fuzzy-compare-with-bigrams-paola1
-const createBigram = word => {
- const input = word.toLowerCase();
- const vector = [];
- for (let i = 0; i < input.length; ++i) {
- vector.push(input.slice(i, i + 2));
- }
- return vector;
-};
-const checkSimilarity = (a, b) => {
- if (a.length > 0 && b.length > 0) {
- const aBigram = createBigram(a);
- const bBigram = createBigram(b);
- let hits = 0;
- for (let x = 0; x < aBigram.length; ++x) {
- for (let y = 0; y < bBigram.length; ++y) {
- if (aBigram[x] === bBigram[y]) {
- hits += 1;
- }
- }
- }
- if (hits > 0) {
- const union = aBigram.length + bBigram.length;
- return (2.0 * hits) / union;
- }
- }
- return 0;
-};
-var bi_1st = .6; // bigram minimum match score for 1st word
-var bi_2nd = .8; // bigram minimum match score for 2nd word
-
-// on document ready code /////////////////////////////////////////////////////
-$(document).ready(function() {
-
- // 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;
-
- // clean q(uery) string from symbols and multiple spaces
- var q = qOrig.replace('+',' ').replace('.',' ')
- .replace(' ',' ')
- .replace(' ',' ');
-
- var qAr = q.split(' '); // split to words
-
- if (qAr.length == 1) { // suggest 1st word ////////////////////////
- var kbq = kb_trans(q)
- // regex match all possible suggestions; (in kb-format)
- substrRegex = new RegExp( kbq, 'i'); // match q anywhere
- products.forEach( it => {
- if ( (substrRegex.test(it.kb))
- || (checkSimilarity(it.kb, kbq) > bi_1st) ) {
- results.push(it);
- }
- });
- }
-
- if (qAr.length == 2) { // suggest 2nd word ///////////////////////////
- root = qAr[0].trim();
- kbroot = kb_trans(root);
-
- substrRegex = new RegExp( kb_trans(qAr[1]), 'i');
-
- products.forEach( it => { // loop through suggestions
- if (it.kb == kbroot ) { // match 1st suggestion
- it.c.forEach ( wo => { // regex match linked words
- if ( (substrRegex.test(wo.kb))
- || (checkSimilarity(wo.kb, kbroot) > bi_2nd) ) {
- results.push({
- w: root +' '+ wo.w,
- f: 100
- });
- proList = proList.concat(wo.p)
- }
- });
- }
- });
- }
-
- if (qAr.length > 2) {
- root = qAr.shift(); // get out the first item of qAr
- last = qAr.pop(); // get out the lase item of qAr
- // now qAr includes only the items after root and before last;
- // so qAr includes all already selected suggestions (but root)
-
- var kbqAr = []; // array of selected suggestions in kb-format
- qAr.forEach( w => { kbqAr.push(kb_trans(w)); })
-
- // kb-translate the root/last keys
- kbroot = kb_trans(root);
- kblast = kb_trans(last);
-
- substrRegex = new RegExp( kblast, 'i'); // construct regex for mathing
-
- products.forEach( it => {
- if (it.kb == kbroot ) { // find root
-
- // calculate list of common items/products (commonL)
- // for selected suggestions
- // ---
- is1stOcc = true; // 1st occurance flag
- it.c.forEach ( swo => {
- if (kbqAr.includes( swo.kb )) {
- // swo is one of the already selected suggestions
- // so...
- // update the commonL(ist)
- if (is1stOcc) {
- commonL = swo.p;
- is1stOcc = false;
- }
- else {
- // list ot common products
- // = intersection of (so-far) commonL and swo.p
- commonL = commonL.filter(value => swo.p.includes(value));
- }
- }
- else { // if swo is not already selected
- // then it is a possible next suggestion
- possibleNext.push(swo);
- }
- });
- // console.log('commonL:', commonL)
-
- possibleNext.forEach( poss => { // for tthe possible next suggestions
- // if word matches regex
- // and list of word's products has commons with commonL
- // then it is a valid next suggestion
- if (substrRegex.test(poss.kb)) {
- // check intersection of commonL and suggestion's product-lists
- tempL = commonL.filter(value => poss.p.includes(value));
- if (tempL.length) {
- results.push({
- w: root +' '+ qAr.join(' ') +' '+ poss.w,
- f: 100
- });
- // update proList too
- proList = proList.concat(tempL)
- }
- }
- });
- }
-
- });
- }
-
-
- if ((qAr.length != 1) && (proList.length < 13)) {
- // get unique product ids
- let unique = proList.filter((item, i, ar) => ar.indexOf(item) === i);
- // credit: https://stackoverflow.com/questions/11246758/
-
- results = [];
- unique.forEach( pr => {
- everyProduct.forEach( pi => {
- if (pi.id == pr)
- results.push(pi);
- })
- });
- }
- return results;
- }
-
- var isuggest = function(qOrig, list) {
- var results = suggestions_engine(qOrig);
- if (results.length == 0) {
- var qAr = qOrig.trim().split(' ');
- qAr.pop(); // remove last word
- results = suggestions_engine(qAr.join(' '));
- }
- list(results);
- }
-
- // setup suggestions search/input control
- // ---
- const $tagsInput = $('#tagsInput')
- $tagsInput.typeahead(
- {
- hint: true,
- highlight: true,
- minLength: 1
- },
- {
- limit: 12,
- name: 'products',
- displayKey: 'w',
- source: isuggest,
- templates: {
- suggestion: function(data) {
- // console.log(data.w);
- if (data.id)
- return '<div>'+ data.w + '<span>' + data.id + '</span></div>';
- return '<div>'+ data.w +'</div>';
- }
- }
- }
- )
- .bind("typeahead:selected", function(obj, datum, name) {
- console.log(datum);
- if (datum.hasOwnProperty('id')) {
- // final product selected; do whatever ...
- // ex. add to selection list
- $('#selections').append('<div>'+ datum.w + '<span>' + datum.id + '</span></div>');
-
- // then reset search control
- $('.typeahead').typeahead('val','').trigger('blur')
- .trigger("query");
- setTimeout(() => { $('.typeahead').focus(); }, 100);
- }
- else {
- $('.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').focus(); }, 100);
- }
- })
- .bind("typeahead:cursorchange", function(obj, data) {
- // console.log(obj, data);
- // var dt = new Date();
- // console.log('triggered cursorchange /'+dt);
- });
-
-
-
-});
- </script>
-</html>
diff --git a/html/search-v3.html b/html/search-v3.html
deleted file mode 100644
index 35e29ca..0000000
--- a/html/search-v3.html
+++ /dev/null
@@ -1,422 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="utf-8">
-
- <style>
-body { font-family: 'Cantarell', Helvetica, Arial, sans-serif; margin: 2em; }
-
-.twitter-typeahead { width: 87%; }
-.typeahead, .tt-query, .tt-hint {
- width: 100%; height: 30px;
- padding: 8px 12px; outline: none;
- font-size: 20px; line-height: 30px;
- border: 2px solid #ccc; border-radius: 8px;
- -webkit-border-radius: 8px;
- -moz-border-radius: 8px;
-}
-.tt-menu {
- width: 100%; margin: 12px 0; padding: 8px 0;
- background-color: #fff;
- border: 1px solid #ccc; border-radius: 8px;
- -webkit-border-radius: 8px;
- -moz-border-radius: 8px;
- -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
- -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
- box-shadow: 0 5px 10px rgba(0,0,0,.2);
-}
-.tt-suggestion { padding: 3px 20px; line-height: 24px; font-size: 18px; }
-.tt-suggestion:hover { cursor: pointer; }
-.tt-suggestion span { padding-left: 16px; font-size: 14px; color: #777; float: right; }
-.tt-cursor { background: #ddd; }
-.tt-highlight { font-weight: normal; color: #777; }
-.tt-hint { color: #9598; }
-
-#selections { width: 87%; padding-top: 40px; }
-#selections div { padding: 4px 40px; line-height: 24px; font-size: 18px; color: #666; }
-#selections div span { padding-left: 16px; font-size: 14px; color: #999; float: right; }
-.-info- { font-size: 12px !important; color: #959 !important; line-height: 14px !important; font-family: 'JetBrains Mono NL', Consolas, Monaco, monospace, fixed !important; }
-.-info- b { font-weight: 900;}
- </style>
-
- <!-- js labraries -->
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/corejs-typeahead/1.2.1/bloodhound.min.js"></script>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/corejs-typeahead/1.2.1/typeahead.jquery.min.js"></script>
- </head>
- <body>
-
- <div id="the-basics">
- <input class="typeahead" id="tagsInput" type="text" placeholder="try me!">
- </div>
-
- <div id="selections">
- </div>
-
- </body>
- <script>
-
-
-// PUBLIC VARIABLES ////////////////////////////////////////////////////////////
-
-var kwlinks; // keyword links (word-connections)
-var products; // all products
-
-var trackSearch = []; // searching analytics
-
-// setup options
-var sgLimit = 12; // limit suggestions
-var bi_1st = .65; // bigram minimum match score for 1st word
-var bi_2nd = .85; // bigram minimum match score for 2nd word
-
-
-// SUPLAMENARY FUNCTIONS ///////////////////////////////////////////////////////
-
-// arrays for kb-format (utf/EL-Gr to ascii translation)
-var ORiGiN = 'ςερτυθιοπασδφγηξκλζχψωβνμΕΡΤΥΘΙΟΠΑΣΔΦΓΗΞΚΛΖΧΨΩΒΝΜάέήίόύώϊϋΆΈΉΊΌΎΏΪΫQWERTYUIOPASDFGHJKLZXCVBNM'.split('');
-var kbKeyZ = 'sertyuiopasdfghjklzxcvbnmertyuiopasdfghjklzxcvbnmaehioyviyaehioyviyqwertyuiopasdfghjklzxcvbnm'.split('');
-
-// convert string to kb-format
-// ---
-function kb_trans(s) {
- var charArr = s.split('')
- var i
- var out = ''
- charArr.forEach( el => {
- i = 0
- exist = -1
- ORiGiN.forEach( ori => {
- if (ori == el) {
- exist = i;
- }
- i++;
- })
- out += (exist == -1) ? el : kbKeyZ[exist];
- });
- return out;
-}
-
-
-function loadData() {
- const xhttp = new XMLHttpRequest();
- xhttp.onload = function() {
- kwlinks = JSON.parse(this.responseText);
- }
- xhttp.open("GET", "results/keywords-v3.json");
- xhttp.send();
-}
-loadData();
-
-function loadData2() {
- const xhttp = new XMLHttpRequest();
- xhttp.onload = function() {
- products = JSON.parse(this.responseText);
- }
- xhttp.open("GET", "results/products.json");
- xhttp.send();
-}
-loadData2();
-
-// bigram fuzzy match
-// --- credit: https://dirask.com/posts/JavaScript-check-words-similarity-fuzzy-compare-with-bigrams-paola1
-const createBigram = word => {
- const input = word.toLowerCase();
- const vector = [];
- for (let i = 0; i < input.length; ++i) {
- vector.push(input.slice(i, i + 2));
- }
- return vector;
-};
-const checkSimilarity = (a, b) => {
- if (a.length > 0 && b.length > 0) {
- const aBigram = createBigram(a);
- const bBigram = createBigram(b);
- let hits = 0;
- for (let x = 0; x < aBigram.length; ++x) {
- for (let y = 0; y < bBigram.length; ++y) {
- if (aBigram[x] === bBigram[y]) {
- hits += 1;
- }
- }
- }
- if (hits > 0) {
- const union = aBigram.length + bBigram.length;
- return (2.0 * hits) / union;
- }
- }
- return 0;
-};
-
-
-
-function echo_tracking() {
- var actions = [];
- var c;
- var chs = 0; // number of characters pressed;
- var uis = 0; // number of UI actions used (arrows, enters etc.)
- var countingStarted = false; // flag
- trackSearch.forEach(e => {
- switch(e.v) {
- // use of ui actions
- case 'ArrowDown' : c = '↓'; uis++; break;
- case 'ArrowUp' : c = '↑'; uis++; break;
- case 'Enter' : c = '↲ '; uis++; break;
- case 'ArrowLeft' : c = '←'; uis++; break;
- case 'ArrowRight': c = '→'; uis++; break;
- case ' ' : c = '· '; uis++; break;
- // ignored keys
- case 'Alt' : c = 'Alt'; break;
- case 'Control' : c = 'Ctrl'; break;
- case 'Escape' : c = 'Esc'; break;
- case 'Shift' : c = 'Shft'; break;
- case 'Home' : c = 'Home'; break;
- case 'End' : c = 'End'; break;
- // backspace (user's typing errors)
- case 'Backspace' : c = 'BkSp'; break;
- case 'Delete' : c = 'Del'; break;
- // actual typed characters
- default:
- if (e.v.length == 1) {
- c = '<b><u>'+ e.v +'</u></b>';
- chs++;
- }
- else { // some non important key; no counter increased
- c = e.v; // just record the key
- }
- }
- actions.push(c);
- });
-
- return actions.join(',') +' (<u>'+ chs +' chs</u>, '+ uis +' uis)';
-}
-
-
-// on document ready code ///////////////////////////////////////////////////////
-$(document).ready(function() {
-
- // 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;
-
- // clean q(uery) string from symbols and multiple spaces
- var q = qOrig.replace('+',' ').replace('.',' ')
- .replace(' ',' ')
- .replace(' ',' ');
-
- var qAr = q.split(' '); // split to words
-
- if (qAr.length == 1) { // suggest 1st word ////////////////////////
- var kbq = kb_trans(q)
- // regex match all possible suggestions; (in kb-format)
- substrRegex = new RegExp( kbq, 'i'); // match q anywhere
- kwlinks.forEach( it => {
- if ( (substrRegex.test(it.kb))
- || (checkSimilarity(it.kb, kbq) > bi_1st) ) {
- results.push(it);
- }
- });
- }
-
- if (qAr.length == 2) { // suggest 2nd word ///////////////////////////
- root = qAr[0].trim();
- kbroot = kb_trans(root);
-
- substrRegex = new RegExp( kb_trans(qAr[1]), 'i');
-
- kwlinks.forEach( it => { // loop through suggestions
- if (it.kb == kbroot ) { // match 1st suggestion
- it.c.forEach ( wo => { // regex match linked words
- if ( (substrRegex.test(wo.kb))
- || (checkSimilarity(wo.kb, kbroot) > bi_2nd) ) {
- results.push({
- w: root +' '+ wo.w,
- f: 100
- });
- proList = proList.concat(wo.p)
- }
- });
- }
- });
- }
-
- if (qAr.length > 2) {
- root = qAr.shift(); // get out the first item of qAr
- last = qAr.pop(); // get out the lase item of qAr
- // now qAr includes only the items after root and before last;
- // so qAr includes all already selected suggestions (but root)
-
- var kbqAr = []; // array of selected suggestions in kb-format
- qAr.forEach( w => { kbqAr.push(kb_trans(w)); })
-
- // kb-translate the root/last keys
- kbroot = kb_trans(root);
- kblast = kb_trans(last);
-
- substrRegex = new RegExp( kblast, 'i'); // construct regex for mathing
-
- kwlinks.forEach( it => {
- if (it.kb == kbroot ) { // find root
-
- // calculate list of common items/products (commonL)
- // for selected suggestions
- // ---
- is1stOcc = true; // 1st occurance flag
- it.c.forEach ( swo => {
- if (kbqAr.includes( swo.kb )) {
- // swo is one of the already selected suggestions
- // so...
- // update the commonL(ist)
- if (is1stOcc) {
- commonL = swo.p;
- is1stOcc = false;
- }
- else {
- // list ot common products
- // = intersection of (so-far) commonL and swo.p
- commonL = commonL.filter(value => swo.p.includes(value));
- }
- }
- else { // if swo is not already selected
- // then it is a possible next suggestion
- possibleNext.push(swo);
- }
- });
- // console.log('commonL:', commonL)
-
- possibleNext.forEach( poss => { // for tthe possible next suggestions
- // if word matches regex
- // and list of word's products has commons with commonL
- // then it is a valid next suggestion
- if (substrRegex.test(poss.kb)) {
- // check intersection of commonL and suggestion's product-lists
- tempL = commonL.filter(value => poss.p.includes(value));
- if (tempL.length) {
- results.push({
- w: root +' '+ qAr.join(' ') +' '+ poss.w,
- f: 100
- });
- // update proList too
- proList = proList.concat(tempL)
- }
- }
- });
- }
-
- });
- }
-
-
- if ( (qAr.length != 1) && (proList.length < (sgLimit +1)) ) {
- // get unique product ids
- let unique = proList.filter((item, i, ar) => ar.indexOf(item) === i);
- // credit: https://stackoverflow.com/questions/11246758/
-
- results = [];
- unique.forEach( pr => {
- products.forEach( pi => {
- if (pi.id == pr)
- results.push(pi);
- })
- });
- }
- return results;
- }
-
- // request suggestions procedure
- // args...
- // qOrig: original query string
- // list: artay structure to host results
- // ---
- var isuggest = function(qOrig, list) {
- var results = suggestions_engine(qOrig);
-
- // if no results...
- // request again after removing last (key)word
- if (results.length == 0) {
- var qAr = qOrig.trim().split(' ');
- qAr.pop(); // remove last word
- results = suggestions_engine(qAr.join(' '));
- }
-
- list(results);
- }
-
- // setup suggestions search/input control
- // ---
- const $tagsInput = $('#tagsInput')
- $tagsInput.typeahead(
- {
- hint: true,
- highlight: true,
- minLength: 1
- },
- {
- limit: sgLimit,
- name: 'kwlinks',
- displayKey: 'w',
- source: isuggest,
- templates: {
- suggestion: function(data) {
- // console.log(data.w);
- if (data.id)
- return '<div>'+ data.w + '<span>' + data.id + '</span></div>';
- return '<div>'+ data.w +'<span>+</span></div>';
- }
- }
- }
- )
- .bind("typeahead:selected", function(obj, datum, name) {
- // console.log(datum);
- if (datum.hasOwnProperty('id')) {
- // final product selected; do whatever ...
- // ex. add to selection list
- $('#selections').append('<div>'+ datum.w + '<span>' + datum.id + '</span></div>');
-
- // then reset search control
- $('.typeahead').typeahead('val','').trigger('blur')
- .trigger("query");
- setTimeout(() => { $('.typeahead').focus(); }, 100);
-
- // finaly save tracking info;
- // $('#selections').append('<div class="-info-">'+ JSON.stringify(trackSearch) +'</div>');
- $('#selections').append('<div class="-info-">'+ echo_tracking() +'</div>');
- trackSearch.length = 0; // ... and reset info to be ready for nextsearch
- }
- else {
- $('.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').focus(); }, 100);
- }
- trackSearch.push({
- e: 'key',
- v: 'Enter',
- i: $('#tagsInput').val()
- });
- })
- .bind("typeahead:cursorchange", function(obj, data) {
- // console.log(obj, data);
- // var dt = new Date();
- // console.log('triggered cursorchange /'+dt);
- });
-
- // TRACK user search attempt ///////////////////////////////////////////////
- $('.typeahead').on('keyup', function(e) {
- trackSearch.push({
- e: 'key',
- v: e.key,
- i: $('#tagsInput').val()
- });
- });
-
-});
- </script>
-</html>
diff --git a/html/search.html b/html/search.html
index 2a12941..5c5e64b 100644
--- a/html/search.html
+++ b/html/search.html
@@ -4,9 +4,9 @@
<meta charset="utf-8">
<style>
-body { font-family: 'Cantarell', Helvetica, Arial, sans-serif; }
+body { font-family: 'Cantarell', Helvetica, Arial, sans-serif; margin: 2em; }
-.twitter-typeahead { width: 87% ;}
+.twitter-typeahead { width: 87%; }
.typeahead, .tt-query, .tt-hint {
width: 100%; height: 30px;
padding: 8px 12px; outline: none;
@@ -26,9 +26,20 @@ body { font-family: 'Cantarell', Helvetica, Arial, sans-serif; }
box-shadow: 0 5px 10px rgba(0,0,0,.2);
}
.tt-suggestion { padding: 3px 20px; line-height: 24px; font-size: 18px; }
+.tt-suggestion:hover { cursor: pointer; }
.tt-suggestion span { padding-left: 16px; font-size: 14px; color: #777; float: right; }
.tt-cursor { background: #ddd; }
.tt-highlight { font-weight: normal; color: #777; }
+.tt-hint { color: #9598; }
+
+#selections { width: 87%; padding-top: 40px; }
+#selections div { padding: 4px 40px; line-height: 24px; font-size: 18px; color: #666; }
+#selections div span { padding-left: 16px; font-size: 14px; color: #999; float: right; }
+.-info- { font-size: 12px !important; color: #959 !important;
+ font-family: 'JetBrains Mono NL', Consolas, Monaco, monospace, fixed !important;
+ line-height: 14px !important; border-bottom: 1px solid #ddda;
+}
+.-info- b { font-weight: 900; }
</style>
<!-- js labraries -->
@@ -42,55 +53,73 @@ body { font-family: 'Cantarell', Helvetica, Arial, sans-serif; }
<input class="typeahead" id="tagsInput" type="text" placeholder="try me!">
</div>
+ <div id="selections">
+ </div>
+
</body>
<script>
-// arrays for kb-format (utf/EL-Gr to ascii translation)
-var ORiGiN = 'ςερτυθιοπασδφγηξκλζχψωβνμΕΡΤΥΘΙΟΠΑΣΔΦΓΗΞΚΛΖΧΨΩΒΝΜάέήίόύώϊϋΆΈΉΊΌΎΏΪΫQWERTYUIOPASDFGHJKLZXCVBNM'.split('');
-var kbKeyZ = 'sertyuiopasdfghjklzxcvbnmertyuiopasdfghjklzxcvbnmaehioyviyaehioyviyqwertyuiopasdfghjklzxcvbnm'.split('');
-// convert string to kb-format
-function kb_trans(s) {
- var charArr = s.split('')
- var i
+// PUBLIC VARIABLES ////////////////////////////////////////////////////////////
+
+var _kwlinks; // keyword links (word-connections; imported via ajax-get)
+var _products; // all products (imported via ajax-get)
+
+var trackSearch = []; // searching analytics
+
+// setup options
+var maxResults = 24; // limit suggestions
+var blendProds = 4; // minimum final-produncts to blend with next-word suggestions
+var allowFuzzy = false; // enable|disable fuzzy search
+var bi1stScore = .65; // bigram minimum match score for 1st word
+var bi2ndScore = .85; // bigram minimum match score for 2nd word
+
+
+var ignoredKeys_kb = []; // keywords to ignore (in kb-format)
+['μας με σε για του της των από στο στον &'].split(' ').forEach(w => { ignoredKbs.push(kb_trans(w)); });
+
+
+// SUPLAMENARY FUNCTIONS ///////////////////////////////////////////////////////
+
+// arrays for kb-format (utf/EL-Gr to ascii translation)
+var ORiGiNal = 'ςερτυθιοπασδφγηξκλζχψωβνμΕΡΤΥΘΙΟΠΑΣΔΦΓΗΞΚΛΖΧΨΩΒΝΜάέήίόύώϊϋΆΈΉΊΌΎΏΪΫQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm0123456789-'.split('');
+var kbKeyZed = 'sertyuiopasdfghjklzxcvbnmertyuiopasdfghjklzxcvbnmaehioyviyaehioyviyqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm0123456789-'.split('');
+const map = new Map();
+for (var i=0; i<ORiGiNal.length; i++) map.set(ORiGiNal[i], kbKeyZed[i]);
+// function kb_trans
+// translates string to keyboard-latin keys;
+// [map]'s implementation is 40x faster than [for]'s
+function kb_trans(str) {
+ str = str.replace('\'','');
var out = ''
- charArr.forEach( el => {
- i = 0
- exist = -1
- ORiGiN.forEach( ori => {
- if (ori == el) {
- exist = i;
- }
- i++;
- })
- out += (exist == -1) ? el : kbKeyZ[exist];
- });
+ for (var i=0 ; i< str.length; i++) out += map.get(str[i]);
return out;
}
-// public data objects
-var products;
-var everyProduct;
-
-function loadData() {
- const xhttp = new XMLHttpRequest();
- xhttp.onload = function() {
- products = JSON.parse(this.responseText);
- }
- xhttp.open("GET", "results/keywords-v3.json");
- xhttp.send();
-}
-loadData();
-
-function loadData2() {
- const xhttp = new XMLHttpRequest();
- xhttp.onload = function() {
- everyProduct = JSON.parse(this.responseText);
- }
- xhttp.open("GET", "results/products.json");
- xhttp.send();
+
+
+// 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) {
+ console.log(err.message + " in " + xmlhttp.responseText);
+ return;
+ }
+ callback(data);
+ }
+ };
+
+ xmlhttp.open("GET", url, true);
+ xmlhttp.send();
}
-loadData2();
+
// bigram fuzzy match
// --- credit: https://dirask.com/posts/JavaScript-check-words-similarity-fuzzy-compare-with-bigrams-paola1
@@ -98,11 +127,13 @@ const createBigram = word => {
const input = word.toLowerCase();
const vector = [];
for (let i = 0; i < input.length; ++i) {
- vector.push(input.slice(i, i + 2));
+ vector.push(input.slice(i, i + 2));
}
return vector;
};
const checkSimilarity = (a, b) => {
+ if (!allowFuzzy) return false;
+
if (a.length > 0 && b.length > 0) {
const aBigram = createBigram(a);
const bBigram = createBigram(b);
@@ -112,27 +143,110 @@ const checkSimilarity = (a, b) => {
if (aBigram[x] === bBigram[y]) {
hits += 1;
}
- }
+ }
}
if (hits > 0) {
- const union = aBigram.length + bBigram.length;
- return (2.0 * hits) / union;
+ const union = aBigram.length + bBigram.length;
+ return (2.0 * hits) / union;
}
}
return 0;
};
-var biMMS = .6; // bigram minimum match score
-// on document ready code /////////////////////////////////////////////////////
+
+// compact print of search-tracking
+// ---
+function echo_tracking() {
+ var actions = [];
+ var c;
+ var chs = 0; // number of characters pressed;
+ var uis = 0; // number of UI actions used (arrows, enters etc.)
+ var countingStarted = false; // flag
+ trackSearch.forEach(e => {
+ switch(e.v) {
+ // use of ui actions
+ case 'ArrowDown' : c = '↓'; uis++; break;
+ case 'ArrowUp' : c = '↑'; uis++; break;
+ case 'Enter' : c = '↲ '; uis++; break;
+ case 'ArrowLeft' : c = '←'; uis++; break;
+ case 'ArrowRight': c = '→'; uis++; break;
+ case ' ' : c = '· '; uis++; break;
+ // ignored keys
+ case 'Alt' : c = 'Alt'; break;
+ case 'Control' : c = 'Ctrl'; break;
+ case 'Escape' : c = 'Esc'; break;
+ case 'Shift' : c = 'Shft'; break;
+ case 'Home' : c = 'Home'; break;
+ case 'End' : c = 'End'; break;
+ // backspace (user's typing errors)
+ case 'Backspace' : c = 'BkSp'; break;
+ case 'Delete' : c = 'Del'; break;
+ // actual typed characters
+ default:
+ if (e.v.length == 1) {
+ c = '<b><u>'+ e.v +'</u></b>';
+ chs++;
+ }
+ else { // some non important key; no counter increased
+ c = e.v; // just record the key
+ }
+ }
+ actions.push(c);
+ });
+
+ return actions.join(',') +' (<u>'+ chs +' chs</u>, '+ uis +' uis)';
+}
+
+
+// workline object to track status of async svents
+// ---
+var workline = {
+
+ trackerJL : 0,
+ set jsonLoaded(x) {
+ this.trackerJL = x;
+
+ // fire event on certain values
+ if (x == 2) {
+ console.log('All streams loaded');
+
+ // code to execute
+ // ...
+
+ }
+ },
+ get jsonLoaded() { return this.trackerJL; }
+
+};
+
+
+
+// LOAD DATA ///////////////////////////////////////////////////////////////////
+
+ajax_get('results/keywords-v3.json', function(data) {
+ _kwlinks = data;
+ workline.jsonLoaded++; console.log('keywords loaded');
+});
+
+ajax_get('results/products.json', function(data) {
+ _products = data;
+ workline.jsonLoaded++; console.log('products loaded')
+});
+
+
+
+
+
+// on document ready code ///////////////////////////////////////////////////////
$(document).ready(function() {
- // suggestions engine ////////////////////////////////////////////////////
+ // suggestions engine //////////////////////////////////////////////////////
// ---
- var isuggest = function(qOrig, list) {
- 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
+ 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;
@@ -140,34 +254,43 @@ $(document).ready(function() {
var q = qOrig.replace('+',' ').replace('.',' ')
.replace(' ',' ')
.replace(' ',' ');
- // split to words
- var qAr = q.split(' ');
- // console.log(qAr);
+
+ var qAr = q.split(' '); // split to words
if (qAr.length == 1) { // suggest 1st word ////////////////////////
- var kbq = kb_trans(q)
- // regex match all possible suggestions; (in kb-format)
- substrRegex = new RegExp( kbq, 'i'); // match q anywhere
- products.forEach( it => {
- if ( (substrRegex.test(it.kb))
- || (checkSimilarity(it.kb, kbq) > biMMS) ) {
- // console.log(it.kb, kbq, checkSimilarity(it.kb, kbq));
+ var kbq = kb_trans(q)
+
+ // ### (-) regex match all possible suggestions; (in kb-format)
+ // ### (-) substrRegex = new RegExp( kbq, 'i'); // match q anywhere
+ _kwlinks.forEach( it => {
+ // ### (-) if ( (substrRegex.test(it.kb))
+ if ( (it.kb.indexOf(kbq) !== -1)
+ || (checkSimilarity(it.kb, kbq) > bi1stScore) ) {
+ // a match found
results.push(it);
+ // also get possible products
+ // (but only if products are less max-results)
+ if (proList.length < maxResults + 2) {
+ it.c.forEach( wo => { proList = proList.concat(wo.p); });
+ // keep unique products in the list
+ proList = proList.filter((item, i, ar) => ar.indexOf(item) === i);
+ }
}
});
}
- if (qAr.length == 2) { // suggest 2nd word ///////////////////////////
+ if (qAr.length == 2) { // suggest 2nd word ///////////////////////////
root = qAr[0].trim();
kbroot = kb_trans(root);
- substrRegex = new RegExp( kb_trans(qAr[1]), 'i');
+ // ### (-) substrRegex = new RegExp( kb_trans(qAr[1]), 'i');
- products.forEach( it => { // loop through suggestions
- if (it.kb == kbroot ) { // match 1st suggestion
- it.c.forEach ( wo => { // regex match linked words
- if ( (substrRegex.test(wo.kb))
- || (checkSimilarity(wo.kb, kbroot) > biMMS) ) {
+ _kwlinks.forEach( it => { // loop through suggestions
+ if (it.kb == kbroot ) { // match 1st suggestion
+ it.c.forEach ( wo => { // regex match linked words
+ // ### (-) if ( (substrRegex.test(wo.kb))
+ if ( (wo.kb.indexOf(qAr[1]) !== -1)
+ || (checkSimilarity(wo.kb, kbroot) > bi2ndScore) ) {
results.push({
w: root +' '+ wo.w,
f: 100
@@ -180,21 +303,22 @@ $(document).ready(function() {
}
if (qAr.length > 2) {
- root = qAr.shift(); // get out the first item of qAr
+ root = qAr.shift(); // get out the first item of tempQAr
last = qAr.pop(); // get out the lase item of qAr
// now qAr includes only the items after root and before last;
// so qAr includes all already selected suggestions (but root)
+ // prepare/cache kb-formats for any key we may need
+ // ---
var kbqAr = []; // array of selected suggestions in kb-format
qAr.forEach( w => { kbqAr.push(kb_trans(w)); })
-
- // kb-translate the root/last keys
+ // kb-translate the root/last keys too
kbroot = kb_trans(root);
kblast = kb_trans(last);
- substrRegex = new RegExp( kblast, 'i'); // construct regex for mathing
+ // ### (-) substrRegex = new RegExp( kblast, 'i'); // construct regex for mathing
- products.forEach( it => {
+ _kwlinks.forEach( it => {
if (it.kb == kbroot ) { // find root
// calculate list of common items/products (commonL)
@@ -202,18 +326,19 @@ $(document).ready(function() {
// ---
is1stOcc = true; // 1st occurance flag
it.c.forEach ( swo => {
- if (kbqAr.includes( swo.kb )) {
+ if (kbqAr.indexOf( swo.kb ) !== -1) {
// swo is one of the already selected suggestions
// so...
// update the commonL(ist)
if (is1stOcc) {
- commonL = swo.p;
+ commonL = swo.p; // init list (on 1st occurance)
is1stOcc = false;
}
else {
- // list ot common products
+ // caclulate list of common products
// = intersection of (so-far) commonL and swo.p
- commonL = commonL.filter(value => swo.p.includes(value));
+ // 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
@@ -221,13 +346,12 @@ $(document).ready(function() {
possibleNext.push(swo);
}
});
- // console.log('commonL:', commonL)
possibleNext.forEach( poss => { // for tthe possible next suggestions
// if word matches regex
// and list of word's products has commons with commonL
// then it is a valid next suggestion
- if (substrRegex.test(poss.kb)) {
+ if (poss.kb.indexOf(kblast) !== -1 ) {
// check intersection of commonL and suggestion's product-lists
tempL = commonL.filter(value => poss.p.includes(value));
if (tempL.length) {
@@ -245,55 +369,97 @@ $(document).ready(function() {
});
}
+ // calculate unique products
+ let unique = proList.filter((item, i, ar) => ar.indexOf(item) === i);
+ // credit: https://stackoverflow.com/questions/11246758/
- if ((qAr.length != 1) && (proList.length < 13)) {
- // get unique product ids
- let unique = proList.filter((item, i, ar) => ar.indexOf(item) === i);
- // credit: https://stackoverflow.com/questions/11246758/
-
+ // console.log(q, qAr, qAr.length, proList.length);
+
+ if (unique.length < (maxResults +1)) {
results = [];
unique.forEach( pr => {
- everyProduct.forEach( pi => {
+ _products.forEach( pi => {
if (pi.id == pr)
results.push(pi);
})
});
}
+ return results;
+ }
+
+ // request suggestions procedure
+ // args...
+ // qOrig: original query string
+ // list: artay structure to host results
+ // ---
+ var isuggest = function(qOrig, list) {
+ var results = suggestions_engine(qOrig);
+
+ // if no results...
+ // request again after removing last (key)word
+ if (results.length == 0) {
+ var qAr = qOrig.trim().split(' ');
+ qAr.pop(); // remove last word
+ results = suggestions_engine(qAr.join(' '));
+ }
+
list(results);
}
- // setup suggestions search/input control
- // ---
+ // setup suggestions search/input control
+ // ---
const $tagsInput = $('#tagsInput')
$tagsInput.typeahead(
{
hint: true,
highlight: true,
- minLength: 0
+ minLength: 1
},
{
- limit: 12,
- name: 'products',
+ limit: maxResults,
+ name: 'kwlinks',
displayKey: 'w',
source: isuggest,
- templates: {
- suggestion: function(data) {
- console.log(data.w);
- if (data.id)
- return '<div>'+ data.w + '<span>' + data.id + '</span></div>';
- return '<div>'+ data.w +'</div>';
- }
- }
+ templates: {
+ suggestion: function(data) {
+ // console.log(data.w);
+ if (data.id)
+ return '<div>'+ data.w + '<span>' + data.id + '</span></div>';
+ return '<div>'+ data.w +'<span>+</span></div>';
+ }
+ }
}
)
.bind("typeahead:selected", function(obj, datum, name) {
- $('.typeahead').typeahead('val','').trigger('blur');
- $('.typeahead').typeahead('val', datum.w +' ')
+ // console.log(datum);
+ if (datum.hasOwnProperty('id')) {
+ // final product selected; do whatever ...
+ // ex. add to selection list
+ $('#selections').append('<div>'+ datum.w + '<span>' + datum.id + '</span></div>');
+
+ // then reset search control
+ $('.typeahead').typeahead('val','').trigger('blur')
.trigger("query");
- // give some time to the engine to calculate results
- // then fire focus again...
- setTimeout(() => { $('.typeahead').focus(); }, 100);
+ setTimeout(() => { $('.typeahead').focus(); }, 100);
+
+ // finaly save tracking info;
+ $('#selections').append('<div class="-info-">'+ echo_tracking() +'</div>');
+ trackSearch.length = 0; // ... and reset info to be ready for nextsearch
+ }
+ else {
+ $('.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').focus(); }, 100);
+ }
+ trackSearch.push({
+ e: 'key',
+ v: 'Enter',
+ i: $('#tagsInput').val()
+ });
})
.bind("typeahead:cursorchange", function(obj, data) {
// console.log(obj, data);
@@ -301,8 +467,27 @@ $(document).ready(function() {
// console.log('triggered cursorchange /'+dt);
});
+ // TRACK user search attempt ///////////////////////////////////////////////
+ $('.typeahead').on('keyup', function(e) {
+ trackSearch.push({
+ e: 'key',
+ v: e.key,
+ i: $('#tagsInput').val()
+ });
+ });
+
+});
+
+
+
+
+// also check (for suearching benchmarks)
+// ---
+// https://www.measurethat.net/Benchmarks/Show/13675/0/regextest-vs-stringincludes-vs-stringmatch
+// https://stackoverflow.com/questions/5296268/fastest-way-to-check-a-string-contain-another-substring-in-javascript
+// https://stackoverflow.com/questions/40387106/string-startwith-vs-regex
+// https://www.measurethat.net/Benchmarks/Show/4797/1/js-regex-vs-startswith-vs-indexof
-});
- </script>
-</html>
+ </script>
+</html> \ No newline at end of file
diff --git a/html/search2.html b/html/search2.html
new file mode 100644
index 0000000..bb88958
--- /dev/null
+++ b/html/search2.html
@@ -0,0 +1,642 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+
+ <style>
+body { font-family: 'Cantarell', Helvetica, Arial, sans-serif; margin: 2em; }
+
+.twitter-typeahead { width: 87%; }
+.typeahead, .tt-query, .tt-hint {
+ width: 100%; height: 30px;
+ padding: 8px 12px; outline: none;
+ font-size: 20px; line-height: 30px;
+ border: 2px solid #ccc; border-radius: 8px;
+ -webkit-border-radius: 8px;
+ -moz-border-radius: 8px;
+}
+.tt-menu {
+ width: 100%; margin: 12px 0; padding: 8px 0;
+ background-color: #fff;
+ border: 1px solid #ccc; border-radius: 8px;
+ -webkit-border-radius: 8px;
+ -moz-border-radius: 8px;
+ -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+ -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+ box-shadow: 0 5px 10px rgba(0,0,0,.2);
+}
+.tt-suggestion { padding: 3px 20px; line-height: 24px; font-size: 18px; }
+.tt-suggestion:hover { cursor: pointer; }
+.tt-suggestion span { padding-left: 16px; font-size: 14px; color: #777; float: right; }
+.tt-cursor { background: #ddd; }
+.tt-highlight { font-weight: normal; color: #777; }
+.tt-hint { color: #9598; }
+
+#selections { width: 87%; padding-top: 40px; }
+#selections div { padding: 4px 40px; line-height: 24px; font-size: 18px; color: #666; }
+#selections div span { padding-left: 16px; font-size: 14px; color: #999; float: right; }
+.-info- { font-size: 12px !important; color: #959 !important;
+ font-family: 'JetBrains Mono NL', Consolas, Monaco, monospace, fixed !important;
+ line-height: 14px !important; border-bottom: 1px solid #ddda;
+}
+.-info- b { font-weight: 900; }
+ </style>
+
+ <!-- js labraries -->
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/corejs-typeahead/1.2.1/bloodhound.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/corejs-typeahead/1.2.1/typeahead.jquery.min.js"></script>
+ </head>
+ <body>
+
+ <div id="the-basics">
+ <input class="typeahead" id="tagsInput" type="text" placeholder="try me!">
+ </div>
+
+ <div id="selections">
+ </div>
+
+ </body>
+ <script>
+
+
+// PUBLIC VARIABLES ////////////////////////////////////////////////////////////
+
+var _kwlinks; // keyword links (word-connections; imported via ajax-get)
+var _products; // all products (imported via ajax-get)
+
+var trackSearch = []; // searching analytics
+
+// 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 _allowFuzzy = true; // enable|disable fuzzy search
+var _fuzzyLimit = .5; // minimum bigram score for being considered a match
+var _Ngram_base = 2 // number of N in Ngram spliting algorithm
+
+
+
+// SUPPLEMENTARY FUNCTIONS ///////////////////////////////////////////////////////
+
+// arrays for kb-format (utf/EL-Gr to ascii translation)
+var ORiGiNal = 'ςερτυθιοπασδφγηξκλζχψωβνμΕΡΤΥΘΙΟΠΑΣΔΦΓΗΞΚΛΖΧΨΩΒΝΜάέήίόύώϊΐϋΆΈΉΊΌΎΏΪΫQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm0123456789- '.split('');
+var kbKeyZed = 'sertyuiopasdfghjklzxcvbnmertyuiopasdfghjklzxcvbnmaehioyviiyaehioyviyqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm0123456789- '.split('');
+const map = new Map();
+for (var i=0; i<ORiGiNal.length; i++) map.set(ORiGiNal[i], kbKeyZed[i]);
+// function kb_trans
+// translates string to keyboard-latin keys;
+// [map]'s implementation is 40x faster than [for]'s
+function kb_trans(str) {
+ str = str.replace('\'','');
+ var out = ''
+ for (var i=0 ; i< str.length; i++) out += map.get(str[i]);
+ return out;
+}
+
+var ignoredKeys_kb = []; // keywords to ignore (in kb-format)
+'μας με σε για του της των από στο στον &'.split(' ').forEach(w => { ignoredKeys_kb.push(kb_trans(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) {
+ console.log(err.message + " in " + xmlhttp.responseText);
+ return;
+ }
+ callback(data);
+ }
+ };
+
+ xmlhttp.open("GET", url, true);
+ xmlhttp.send();
+}
+
+
+// Ngram fuzzy match
+// ---
+const createNgram = (word, n) => { // Ngram creation
+ if (word.length <3) return word;
+ const vector = [];
+ for (let i = 0; i < word.length-n+1; ++i) {
+ vector.push(word.slice(i, i + n));
+ }
+ return vector;
+};
+const checkSimilarity = (a, b) => { // Ngram match score
+ if (!_allowFuzzy) return 0;
+
+ if (a.length > 0 && b.length > 0) {
+ const aNgram = createNgram(a, _Ngram_base);
+ const bNgram = createNgram(b, _Ngram_base);
+ let hits = 0;
+ for (let x = 0; x < aNgram.length; ++x) {
+ for (let y = 0; y < bNgram.length; ++y) {
+ if (aNgram[x] === bNgram[y]) {
+ hits += 1;
+ }
+ }
+ }
+ if (hits > 0) {
+ const union = aNgram.length + bNgram.length;
+ return (2.0 * hits) / union;
+ }
+ }
+ return 0;
+};
+
+
+// Search Endine's match functions ---------------------------------------------
+
+
+// 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;
+ chkArr.forEach( chk => {
+ if (!found) {
+ chk_kb = kb_trans(chk);
+ if ( (chk_kb.indexOf( query ) !== -1) || (fuzzy && (checkSimilarity(chk_kb, query) > _fuzzyLimit)) ) {
+ found = true;
+ result = chk;
+ }
+ }
+ });
+ return found ? result : false;
+}
+
+
+// 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)
+// *** return: true|false
+// ---
+function is_exact_match( query, chkArr ) {
+ found = false;
+ chkArr.forEach( w => { if (kb_trans(w) == query) found = true });
+ return found;
+}
+
+
+// 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: true|false
+// ---
+function match_one( qArr, chkArr ) {
+ found = false;
+ qArr.forEach( query => {
+ chkArr.forEach( w => { if (kb_trans(w) == query) found = true });
+ });
+ return found;
+}
+
+
+// compact print of search-tracking
+// ---
+function echo_tracking() {
+ var actions = [];
+ var c;
+ var chs = 0; // number of characters pressed;
+ var uis = 0; // number of UI actions used (arrows, enters etc.)
+ var countingStarted = false; // flag
+ trackSearch.forEach(e => {
+ switch(e.v) {
+ // use of ui actions
+ case 'ArrowDown' : c = '↓'; uis++; break;
+ case 'ArrowUp' : c = '↑'; uis++; break;
+ case 'Enter' : c = '↲ '; uis++; break;
+ case 'ArrowLeft' : c = '←'; uis++; break;
+ case 'ArrowRight': c = '→'; uis++; break;
+ case ' ' : c = '· '; uis++; break;
+ // ignored keys
+ case 'Alt' : c = 'Alt'; break;
+ case 'Control' : c = 'Ctrl'; break;
+ case 'Escape' : c = 'Esc'; break;
+ case 'Shift' : c = 'Shft'; break;
+ case 'Home' : c = 'Home'; break;
+ case 'End' : c = 'End'; break;
+ // backspace (user's typing errors)
+ case 'Backspace' : c = 'BkSp'; break;
+ case 'Delete' : c = 'Del'; break;
+ // actual typed characters
+ default:
+ if (e.v.length == 1) {
+ c = '<b><u>'+ e.v +'</u></b>';
+ chs++;
+ }
+ else { // some non important key; no counter increased
+ c = e.v; // just record the key
+ }
+ }
+ actions.push(c);
+ });
+
+ return actions.join(',') +' (<u>'+ chs +' chs</u>, '+ uis +' uis)';
+}
+
+
+// workline object to track status of async svents
+// ---
+var workline = {
+
+ trackerJL : 0,
+ set jsonLoaded(x) {
+ this.trackerJL = x;
+
+ // fire event on certain values
+ if (x == 2) {
+ console.log('All streams loaded');
+
+ // code to execute
+ // ...
+
+ }
+ },
+ get jsonLoaded() { return this.trackerJL; }
+
+};
+
+
+
+// LOAD DATA ///////////////////////////////////////////////////////////////////
+
+ajax_get('results/keywords.json', function(data) {
+ _kwlinks = data;
+ workline.jsonLoaded++; console.log('keywords loaded');
+});
+
+ajax_get('results/products.json', function(data) {
+ _products = data;
+ workline.jsonLoaded++; console.log('products loaded')
+});
+
+
+// on document ready code ///////////////////////////////////////////////////////
+$(document).ready(function() {
+
+ // 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;
+
+ // clean q(uery) string from symbols and multiple spaces
+ var q = qOrig.replace('+',' ').replace('.',' ')
+ .replace(' ',' ')
+ .replace(' ',' ');
+
+ var qAr = q.split(' '); // split to words
+
+ if (qAr.length == 1) { // suggest 1st word ////////////////////////
+ var kbq = kb_trans(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 + 1) {
+ // 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_trans(root);
+ kblast = kb_trans(last);
+
+ _kwlinks.forEach( it => { // locate the ...
+ if (is_exact_match(kbroot, 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 + 1) { // 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(kb_trans(w)); })
+ kbroot = kb_trans(root);
+ kblast = kb_trans(last);
+
+ _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 it 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: test before enable
+ // return false; // escape from forEach
+ }
+
+ });
+ }
+
+ // calculate unique products
+ let unique = proList.filter((item, i, ar) => ar.indexOf(item) === i);
+ // credit: https://stackoverflow.com/questions/11246758/
+
+ // console.log(q, qAr, qAr.length, proList.length);
+
+ if (unique.length < (_maxResults +1)) {
+ results = [];
+ unique.forEach( pr => {
+ _products.forEach( pi => {
+ if (pi.id == pr)
+ results.push(pi);
+ })
+ });
+ }
+
+ return results;
+ }
+
+ // simple, fast search products by numeric code property
+ // ---
+ function search_by_code( num ) {
+ const str = num.toString();
+ results = [];
+
+ _products.forEach(p => {
+ if (results.length > _maxResults) return false;
+
+ 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;
+
+ // if query seems to be some king of 'code/id'
+ if (qOrig.length>2 && qOrig.match(/^[0-9]+$/) != null) {
+ results = search_by_code(qOrig);
+ }
+ else {
+ // string match procedure with suggestions engine
+ results = suggestions_engine(qOrig);
+
+ // depricated:
+ // if no results request again after removing last (key)word
+ ////// if (results.length == 0) {
+ ////// console.log('no results found;', qOrig.split(' '));
+ ////// // var qAr = qOrig.trim().split(' ');
+ ////// // qAr.pop(); // remove last word
+ ////// // results = suggestions_engine(qAr.join(' '));
+ ////// }
+ }
+ list(results);
+ }
+
+ // setup suggestions search/input control
+ // ---
+ const $tagsInput = $('#tagsInput')
+ $tagsInput.typeahead(
+ {
+ hint: true,
+ highlight: true,
+ minLength: 1
+ },
+ {
+ limit: _maxResults,
+ name: 'kwlinks',
+ displayKey: 'w',
+ source: isuggest,
+ templates: {
+ suggestion: function(data) {
+ // console.log(data.w);
+ if (data.id)
+ return '<div>'+ data.w + '<span>' + data.bp +'-'+ data.bc + '</span></div>';
+ return '<div>'+ data.w +'<span>+</span></div>';
+ }
+ }
+ }
+ )
+ .bind("typeahead:selected", function(obj, datum, name) {
+ // console.log(datum);
+ if (datum.hasOwnProperty('id')) {
+ // final product selected; do whatever ...
+ // ex. add to selection list
+ $('#selections').append('<div>'+ datum.w + '<span>' + datum.id + '</span></div>');
+
+ // then reset search control
+ $('.typeahead').typeahead('val','').trigger('blur')
+ .trigger("query");
+ setTimeout(() => { $('.typeahead').focus(); }, _timeout_ms);
+
+ // finaly save tracking info;
+ $('#selections').append('<div class="-info-">'+ echo_tracking() +'</div>');
+ trackSearch.length = 0; // ... and reset info to be ready for nextsearch
+ }
+ else {
+ $('.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').focus(); }, _timeout_ms);
+ }
+ trackSearch.push({
+ e: 'key',
+ v: 'Enter',
+ i: $('#tagsInput').val()
+ });
+ })
+ .bind("typeahead:cursorchange", function(obj, data) {
+ // console.log(obj, data);
+ // var dt = new Date();
+ // console.log('triggered cursorchange /'+dt);
+ });
+
+ // TRACK user search attempt ///////////////////////////////////////////////
+ $('.typeahead').on('keyup', function(e) {
+ trackSearch.push({
+ e: 'key',
+ v: e.key,
+ i: $('#tagsInput').val()
+ });
+ });
+
+});
+
+
+// NOTE:
+// there is a strong chance that list-intersection and list-concatenation algos
+// can be optimized further (for CPU and RAM usage); check the following sources ...
+// ---
+// https://javascript.plainenglish.io/algorithms-101-includes-vs-indexof-in-javascript-7f1b4af04127
+// https://www.measurethat.net/Benchmarks/Show/8221/0/array-indexof-vs-includes-vs-some
+// https://www.measurethat.net/Benchmarks/Show/4223/0/array-concat-vs-spread-operator-vs-push
+// HINT:
+// ---
+// array.some() seems to be much faster than array.includes() and array.indexOf()
+// also array.push(list) seems to be an awesome alternative vs concat and Set
+
+
+ </script>
+</html>
+
+<!--
+ oneliner tests
+
+totl = 0
+_products.forEach(p => { if (kb_trans(p.w).indexOf(kb_trans("xvris")) !=-1) totl++; })
+
+totl = 0
+_products.forEach(p => { if (kb_trans(p.w).indexOf(kb_trans("eisagvghs")) !=-1) totl++; })
+
+_products.forEach(p => { if (kb_trans(p.w).indexOf(kb_trans("dvro")) !=-1) totl++; })
+_products.forEach(p => { if (kb_trans(p.w).indexOf(kb_trans("geysh")) !=-1) totl++; })
+_products.forEach(p => { if (kb_trans(p.w).indexOf(kb_trans("xvris prosuhkh zaxarhs")) !=-1) totl++; })
+
+
+χωρίς
+εισαγωγής
+δώρο
+γεύση
+γεύσεις
+φέτες
+Χωρίς-Γλουτένη
+Χωρίς-Ζάχαρη
+Χωρίς-Αλάτι
+Χωρίς-Λακτόζη
+Χωρίς-Συντηρητικά
+Χωρίς-Αλκοόλ
+Χωρίς-Kαφεϊνη
+Χωρίς-Γλυκάνισο
+Χωρίς-Ανθρακικό
+Υψηλής-Παστερίωσης
+Ολες-τις-Χρήσεις
+Ολικής-Άλεσης
+Ολικής-Aλέσεως
+Ολικής
+Γαϊδούρας
+Γαϊδάρου
+Ρούχων
+Πιάτων
+πλύσεις
+Πλυντηρίου
+Φύλλων
+Γάλακτος
+Χρήσης
+Τύπου
+Ολλανδίας
+Απορριμμάτων
+Medium
+Μαλλιά
+Μαλλιών
+Γενικής
+Plus
+Classic
+Έκπληξη
+Μάνης
+Ελάτου
+Άγριων
+Βοτάνων
+Λακωνίας
+
+-->
+
+<!--
+
+--> \ No newline at end of file