aboutsummaryrefslogtreecommitdiff
path: root/static/js/fixedsearch.ts
diff options
context:
space:
mode:
Diffstat (limited to 'static/js/fixedsearch.ts')
-rw-r--r--static/js/fixedsearch.ts241
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;
}
});
+ });
})();