aboutsummaryrefslogtreecommitdiff
path: root/static/search.js
diff options
context:
space:
mode:
Diffstat (limited to 'static/search.js')
-rw-r--r--static/search.js337
1 files changed, 337 insertions, 0 deletions
diff --git a/static/search.js b/static/search.js
new file mode 100644
index 0000000..e653d2b
--- /dev/null
+++ b/static/search.js
@@ -0,0 +1,337 @@
+// in page results when press enter or click search icon from search box
+function closeSearchNow() {
+ const main = document.querySelector("main");
+ main.innerHTML = window.main;
+}
+function goSearchNow() {
+ const main = document.querySelector("main");
+ if (!window.main) {
+ window.main = main.innerHTML
+ };
+ var results = document.getElementById("suggestions");// suggestions div generated by search box
+
+ var ResultsClone = results.cloneNode(true);// make a clone of the results, so that we can alter it
+ ResultsClone.id = "results";// alter the id of our clone, so that we can apply different css style
+
+ var headerDiv = document.createElement("div");// create a div element
+
+ var headerContent = '<form name="closeSearch"><h2><button type="submit" title="Close Search"><i class="svgs x"></i></button> Results For: '.concat(document.getElementById("userinput").value, "</h2></form>");// header to use at top of results page
+
+ headerDiv.innerHTML = headerContent;// document element div (headerDiv), set the inner contents to our header html (headerContent)
+
+ ResultsClone.insertBefore(headerDiv, ResultsClone.firstChild);//insert our header div at the top of the page
+
+ main.innerHTML = ResultsClone.outerHTML;//display ResultsClone.outerHTML as the page
+ results.innerHTML = "";// clear the suggestions div popup
+ document.getElementById("userinput").value = "";// clear the search input box
+
+ return false
+}
+window.onload = function() {
+ document.body.contains(document.goSearch) && (document.goSearch.onsubmit = function() { return goSearchNow() }),
+ document.body.contains(document.closeSearch) && (document.closeSearch.onsubmit = function() { closeSearchNow() })
+};
+
+// Everything Below this line For Suggestsions as you type in search box
+var suggestions = document.getElementById("suggestions");
+var userinput = document.getElementById("userinput");
+
+function inputFocus(e) {
+
+ if (e.keyCode === 191
+ && document.activeElement.tagName !== "INPUT"
+ && document.activeElement.tagName !== "TEXTAREA") {
+ e.preventDefault();
+ userinput.focus();
+ }
+
+ if (e.keyCode === 27 ) {
+ userinput.blur();
+ suggestions.classList.add('d-none');
+ }
+
+}
+
+function suggestionFocus(e) {
+ const focusableSuggestions= suggestions.querySelectorAll('a');
+ if (suggestions.classList.contains('d-none')
+ || focusableSuggestions.length === 0) {
+ return;
+ }
+ const focusable= [...focusableSuggestions];
+ const index = focusable.indexOf(document.activeElement);
+
+ let nextIndex = 0;
+
+ if (e.keyCode === 38) {
+ e.preventDefault();
+ nextIndex= index > 0 ? index-1 : 0;
+ focusableSuggestions[nextIndex].focus();
+ }
+ else if (e.keyCode === 40) {
+ e.preventDefault();
+ nextIndex= index+1 < focusable.length ? index+1 : index;
+ focusableSuggestions[nextIndex].focus();
+ }
+
+}
+
+document.addEventListener("keydown", inputFocus);
+document.addEventListener("click", function(event) {suggestions.contains(event.target) || suggestions.classList.add("d-none")});
+document.addEventListener("keydown", suggestionFocus);
+
+// Get substring by bytes
+// If using JavaScript inline substring method, it will return error codes
+// Source: https://www.52pojie.cn/thread-1059814-1-1.html
+function substringByByte(str, maxLength) {
+ var result = "";
+ var flag = false;
+ var len = 0;
+ var length = 0;
+ var length2 = 0;
+ for (var i = 0; i < str.length; i++) {
+ var code = str.codePointAt(i).toString(16);
+ if (code.length > 4) {
+ i++;
+ if ((i + 1) < str.length) {
+ flag = str.codePointAt(i + 1).toString(16) == "200d";
+ }
+ }
+ if (flag) {
+ len += getByteByHex(code);
+ if (i == str.length - 1) {
+ length += len;
+ if (length <= maxLength) {
+ result += str.substr(length2, i - length2 + 1);
+ } else {
+ break
+ }
+ }
+ } else {
+ if (len != 0) {
+ length += len;
+ length += getByteByHex(code);
+ if (length <= maxLength) {
+ result += str.substr(length2, i - length2 + 1);
+ length2 = i + 1;
+ } else {
+ break
+ }
+ len = 0;
+ continue;
+ }
+ length += getByteByHex(code);
+ if (length <= maxLength) {
+ if (code.length <= 4) {
+ result += str[i]
+ } else {
+ result += str[i - 1] + str[i]
+ }
+ length2 = i + 1;
+ } else {
+ break
+ }
+ }
+ }
+ return result;
+}
+
+// Get the string bytes from binary
+function getByteByBinary(binaryCode) {
+ // Binary system, starts with `0b` in ES6
+ // Octal number system, starts with `0` in ES5 and starts with `0o` in ES6
+ // Hexadecimal, starts with `0x` in both ES5 and ES6
+ var byteLengthDatas = [0, 1, 2, 3, 4];
+ var len = byteLengthDatas[Math.ceil(binaryCode.length / 8)];
+ return len;
+}
+
+// Get the string bytes from hexadecimal
+function getByteByHex(hexCode) {
+ return getByteByBinary(parseInt(hexCode, 16).toString(2));
+}
+
+/*
+Source:
+ - https://github.com/nextapps-de/flexsearch#index-documents-field-search
+ - https://raw.githack.com/nextapps-de/flexsearch/master/demo/autocomplete.html
+ - http://elasticlunr.com/
+ - https://github.com/getzola/zola/blob/master/docs/static/search.js
+ - https://github.com/aaranxu/adidoks/blob/main/static/js/search.js
+*/
+(function(){
+ var index = elasticlunr.Index.load(window.searchIndex);
+ userinput.addEventListener('input', show_results, true);
+ suggestions.addEventListener('click', accept_suggestion, true);
+
+ function show_results(){
+ var value = this.value.trim();
+ var options = {
+ bool: "OR",
+ fields: {
+ title: {boost: 2},
+ body: {boost: 1},
+ }
+ };
+ var results = index.search(value, options);
+
+ var entry, childs = suggestions.childNodes;
+ var i = 0, len = results.length;
+ var items = value.split(/\s+/);
+ suggestions.classList.remove('d-none');
+
+ results.forEach(function(page) {
+ if (page.doc.body !== '') {
+ entry = document.createElement('div');
+
+ entry.innerHTML = '<a href><span></span><span></span></a>';
+
+ a = entry.querySelector('a'),
+ t = entry.querySelector('span:first-child'),
+ d = entry.querySelector('span:nth-child(2)');
+ a.href = page.ref;
+ t.textContent = page.doc.title;
+ d.innerHTML = makeTeaser(page.doc.body, items);
+
+ suggestions.appendChild(entry);
+ }
+ });
+
+ while(childs.length > len){
+ suggestions.removeChild(childs[i])
+ }
+
+ }
+
+ function accept_suggestion(){
+
+ while(suggestions.lastChild){
+
+ suggestions.removeChild(suggestions.lastChild);
+ }
+
+ return false;
+ }
+
+ // Taken from mdbook
+ // The strategy is as follows:
+ // First, assign a value to each word in the document:
+ // Words that correspond to search terms (stemmer aware): 40
+ // Normal words: 2
+ // First word in a sentence: 8
+ // Then use a sliding window with a constant number of words and count the
+ // sum of the values of the words within the window. Then use the window that got the
+ // maximum sum. If there are multiple maximas, then get the last one.
+ // Enclose the terms in <b>.
+ function makeTeaser(body, terms) {
+ var TERM_WEIGHT = 40;
+ var NORMAL_WORD_WEIGHT = 2;
+ var FIRST_WORD_WEIGHT = 8;
+ var TEASER_MAX_WORDS = 30;
+
+ var stemmedTerms = terms.map(function (w) {
+ return elasticlunr.stemmer(w.toLowerCase());
+ });
+ var termFound = false;
+ var index = 0;
+ var weighted = []; // contains elements of ["word", weight, index_in_document]
+
+ // split in sentences, then words
+ var sentences = body.toLowerCase().split(". ");
+ for (var i in sentences) {
+ var words = sentences[i].split(/[\s\n]/);
+ var value = FIRST_WORD_WEIGHT;
+ for (var j in words) {
+
+ var word = words[j];
+
+ if (word.length > 0) {
+ for (var k in stemmedTerms) {
+ if (elasticlunr.stemmer(word).startsWith(stemmedTerms[k])) {
+ value = TERM_WEIGHT;
+ termFound = true;
+ }
+ }
+ weighted.push([word, value, index]);
+ value = NORMAL_WORD_WEIGHT;
+ }
+
+ index += word.length;
+ index += 1; // ' ' or '.' if last word in sentence
+ }
+
+ index += 1; // because we split at a two-char boundary '. '
+ }
+
+ if (weighted.length === 0) {
+ if (body.length !== undefined && body.length > TEASER_MAX_WORDS * 10) {
+ return body.substring(0, TEASER_MAX_WORDS * 10) + '...';
+ } else {
+ return body;
+ }
+ }
+
+ var windowWeights = [];
+ var windowSize = Math.min(weighted.length, TEASER_MAX_WORDS);
+ // We add a window with all the weights first
+ var curSum = 0;
+ for (var i = 0; i < windowSize; i++) {
+ curSum += weighted[i][1];
+ }
+ windowWeights.push(curSum);
+
+ for (var i = 0; i < weighted.length - windowSize; i++) {
+ curSum -= weighted[i][1];
+ curSum += weighted[i + windowSize][1];
+ windowWeights.push(curSum);
+ }
+
+ // If we didn't find the term, just pick the first window
+ var maxSumIndex = 0;
+ if (termFound) {
+ var maxFound = 0;
+ // backwards
+ for (var i = windowWeights.length - 1; i >= 0; i--) {
+ if (windowWeights[i] > maxFound) {
+ maxFound = windowWeights[i];
+ maxSumIndex = i;
+ }
+ }
+ }
+
+ var teaser = [];
+ var startIndex = weighted[maxSumIndex][2];
+ for (var i = maxSumIndex; i < maxSumIndex + windowSize; i++) {
+ var word = weighted[i];
+ if (startIndex < word[2]) {
+ // missing text from index to start of `word`
+ teaser.push(body.substring(startIndex, word[2]));
+ startIndex = word[2];
+ }
+
+ // add <em/> around search terms
+ if (word[1] === TERM_WEIGHT) {
+ teaser.push("<b>");
+ }
+
+ startIndex = word[2] + word[0].length;
+ // Check the string is ascii characters or not
+ var re = /^[\x00-\xff]+$/
+ if (word[1] !== TERM_WEIGHT && word[0].length >= 12 && !re.test(word[0])) {
+ // If the string's length is too long, it maybe a Chinese/Japance/Korean article
+ // if using substring method directly, it may occur error codes on emoji chars
+ var strBefor = body.substring(word[2], startIndex);
+ var strAfter = substringByByte(strBefor, 12);
+ teaser.push(strAfter);
+ } else {
+ teaser.push(body.substring(word[2], startIndex));
+ }
+
+ if (word[1] === TERM_WEIGHT) {
+ teaser.push("</b>");
+ }
+ }
+ teaser.push("…");
+ return teaser.join("");
+ }
+}());