diff options
Diffstat (limited to 'static/js/fixedsearch.ts')
-rw-r--r-- | static/js/fixedsearch.ts | 241 |
1 files changed, 155 insertions, 86 deletions
diff --git a/static/js/fixedsearch.ts b/static/js/fixedsearch.ts index 2b0abda..04d318b 100644 --- a/static/js/fixedsearch.ts +++ b/static/js/fixedsearch.ts @@ -1,17 +1,71 @@ +/* + * Fixed Search Copyright (C) 2020 Heracles Papatheodorou + * Copyright (C) Craig Mod, Eddie Webb, and Matthew Daly + * https://gist.github.com/Arty2/8b0c43581013753438a3d35c15091a9f#file-license-md + * Licence: MIT | https://mit-license.org/ +*/ + (function () { - self.addEventListener("DOMContentLoaded", function () { - /* - Originally Based on fixedsearch, a super fast, client side search for Hugo.io with Fusejs.io - based on https://gist.github.com/cmod/5410eae147e4318164258742dd053993 - */ - - const form = document.getElementById("search-form"); // search form - const query = document.getElementById("search-input"); // input box for search - const submit = document.getElementById("search-submit"); // form submit button - const dropdown = document.getElementById("search-results"); // targets the <ul> - const container = document.getElementById("search-frame"); // targets the upper parent container + ["DOMContentLoaded", "URLChangedCustomEvent"].forEach(function (event) { + self.addEventListener(event, function () { + const container = document.querySelector("search-box"); + const dropdown = document.querySelector("search-box ul"); + const form = document.querySelector("search-box form"); + const query = document.querySelector("search-box input"); + const submit = document.querySelector("search-box button"); + + function first(element) { + if (element.firstChild.nextElementSibling) { + return element.firstChild.nextElementSibling.firstChild + .nextElementSibling; + } + } + + function last(element) { + return element.lastElementChild.firstChild.nextElementSibling; + } + + function next(element) { + return element.activeElement.parentElement.nextElementSibling.firstChild + .nextElementSibling.focus(); + } + + function previous(element) { + return element.activeElement.parentElement.previousElementSibling + .firstChild + .nextElementSibling.focus(); + } + + function press(keyname) { + document.dispatchEvent(new KeyboardEvent("keydown", { "key": keyname })); + } + + let selected; + + if (submit === null) return; + + submit.addEventListener("click", function (event) { + if (selected) { + event.preventDefault(); + selected.focus(); + return selected.click(); + } + first(dropdown).focus(); + press("ArrowDown"); + document.activeElement.click(); + }); + + ["keyup", "click"].forEach(function (event) { + form.addEventListener(event, function () { + if (document.activeElement.nodeName === "A") { + return selected = document.activeElement; + } + selected = undefined; + }); + }); form.addEventListener("focusin", function () { + container.setAttribute("data-focus", ""); initialize(); }); @@ -21,46 +75,40 @@ }); form.addEventListener("keydown", function (event) { - const head = - dropdown.firstChild.nextElementSibling.firstChild.nextElementSibling; - const tail = dropdown.lastElementChild.firstChild.nextElementSibling; - // ESC (27) - if (query.contains(event.target)) { + if (form.contains(event.target)) { if (event.keyCode == 27) { document.activeElement.blur(); dropdown.setAttribute("hidden", ""); + container.removeAttribute("data-focus"); } } - // DOWN (40) + // BACKSPACE (8) + if (event.keyCode == 8) { + if (document.activeElement != query) { + event.preventDefault(); + query.focus(); + } + } + + const head = first(dropdown); + const tail = last(dropdown); + + // DOWN (40) if (event.keyCode == 40) { event.preventDefault(); if (document.activeElement == query) head.focus(); else if (document.activeElement == tail) query.focus(); - else { - document.activeElement.parentElement.nextElementSibling.firstChild - .nextElementSibling.focus(); - } + else next(document); } - // UP (38) + // UP (38) if (event.keyCode == 38) { event.preventDefault(); if (document.activeElement == query) tail.focus(); else if (document.activeElement == head) query.focus(); - else { - document.activeElement.parentElement.previousElementSibling.firstChild - .nextElementSibling.focus(); - } - } - - // BACKSPACE (8) - if (event.keyCode == 8) { - if (document.activeElement != query) { - event.preventDefault(); - query.focus(); - } + else previous(document); } // ENTER (13) @@ -68,7 +116,7 @@ if (dropdown && document.activeElement == query) { event.preventDefault(); head.focus(); - self.window.location = document.activeElement.href; + head.click(); } } }); @@ -85,30 +133,31 @@ scrolls++; }); - document.addEventListener("click", function (event) { - if (!form.contains(event.target)) { + self.addEventListener("click", function (event) { + if ( + !form.contains(event.target) && + !(document.activeElement === query) && + !(document.activeElement === submit) + ) { dropdown.setAttribute("hidden", ""); container.removeAttribute("data-focus"); } }); - function fetch_JSON(path, callback) { - const httpRequest = new XMLHttpRequest(); - httpRequest.onreadystatechange = function () { - if (httpRequest.readyState === 4) { - if (httpRequest.status === 200) { - const data = JSON.parse(httpRequest.responseText); - if (callback) callback(data); - } + function fetch(url, callback) { + const http = new XMLHttpRequest(); + http.onreadystatechange = function () { + if (http.readyState === 4 && http.status === 200 && callback) { + callback(http); } }; - httpRequest.open("GET", path); - httpRequest.send(); + http.open("GET", url); + http.send(); } /* Load script based on https://stackoverflow.com/a/55451823 */ - function load_script(url) { + function script(url) { return new Promise(function (resolve, reject) { const script = document.createElement("script"); script.onerror = reject; @@ -118,41 +167,64 @@ script, document.currentScript, ); - } else { - document.head.appendChild(script); - } + } else document.head.appendChild(script); script.src = url; }); } - let first_run = true; // allow us to delay loading json data unless search activated + let data = {}; + let boot = true; + + const options = { key: ["title"] }; + + function isEmpty(obj) { return Object.keys(obj).length === 0; } + + function appendItemsTo(local, remote) { + const paginated = Object.keys(remote).includes("next_url"); + if (isEmpty(local)) { + local = remote; + } else { + local.items = local.items.concat(remote.items); + } + if (paginated) { + fetch(remote.next_url, function (request) { + appendItemsTo(local, JSON.parse(request.responseText)); + }); + } else search(query.value, data.items, options); + data = local; + } function initialize() { - if (first_run) { - load_script(window.location.origin + "/js/fuzzysort.js") - .then(() => { - first_run = false; - fetch_JSON("/index.json", function (data) { - const options = { - key: ["title"], - }; - - query.addEventListener("keyup", function () { - search(query.value, data.items, options); - }); + if (boot) { + script(window.location.origin + "/js/fuzzysort.js") + .then(function () { - query.addEventListener("focusin", function () { - search(query.value, data.items, options); - }); + fetch("/index.json", function (request) { + appendItemsTo({}, JSON.parse(request.responseText)); + search("", data.items, options); + boot = false; + }); - search(query.value, data.items, options); + + ["keyup", "focusin"].forEach(function (event) { + query.addEventListener(event, function () { + if (data.items) search(query.value, data.items, options); + else { boot = true; initialize(); } + }); }); - }).catch((error) => { - console.log("Error failed to load fuzzy sort: " + error); + + }).catch(function (error) { + console.error("ERROR: Failed to load fuzzy search", error); }); } } + function escape(text) { + const escaped = document.createElement("textarea"); + escaped.textContent = text; + return escaped.innerHTML; + } + function search(term, data, options) { const results = fuzzysort.go(term, data, options); let items = ""; @@ -160,36 +232,33 @@ if (results.length === 0 && term.length >= 0) { let separator = "—"; if (term.length === 0) separator = ""; - items = ` - <li> - <a href="javascript: void(0)" tabindex="0">${term} ${separator} No Results Found</a> - </li> - `; + items = '<li><a tabindex="0">'.concat( + escape(term), + " ", + ).concat(separator, " No Results Found</a></li>"); dropdown.removeAttribute("hidden"); container.setAttribute("data-focus", ""); } else { dropdown.removeAttribute("hidden"); - for (const string in results.slice(0, 10)) { + for (var string in results.slice(0, 10)) { + const title = results[string].obj.title; let highlight = fuzzysort.highlight( - fuzzysort.single(term, results[string].obj.title), + fuzzysort.single(escape(term), escape(title)), "<span>", "</span>", ); - if (highlight === null) { - highlight = results[string].obj.title; - } + if (highlight === null) highlight = title; items = items + - ` - <li> - <a href="${results[string].obj.url}" tabindex="0">${highlight}</a> - </li> - `; + '\n<li>\n<a href="'.concat( + results[string].obj.url, + '" tabindex="0">', + ).concat(highlight, "</a>\n</li>\n"); } } - dropdown.innerHTML = items; } }); + }); })(); |