2017-10-25 14:24:42 +03:00
|
|
|
function debouncer(interval, callback) {
|
|
|
|
let currentTimeout = null;
|
|
|
|
|
|
|
|
function trigger() {
|
|
|
|
currentTimeout = null;
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
|
|
|
|
return function () {
|
|
|
|
clearTimeout(currentTimeout);
|
|
|
|
currentTimeout = setTimeout(trigger, interval);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
const form = document.querySelector('form.search');
|
|
|
|
const input = form.querySelector('input');
|
|
|
|
const results = form.querySelector('.live-results');
|
|
|
|
const resultPrototype = document.getElementById('search-result-prototype').firstChild;
|
|
|
|
|
2017-10-25 17:34:21 +03:00
|
|
|
let ongoing = false;
|
2017-10-25 14:24:42 +03:00
|
|
|
function submit() {
|
|
|
|
if (input.value === "") {
|
|
|
|
results.classList.remove("show");
|
|
|
|
while (results.lastChild) results.removeChild(results.lastChild);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-10-25 17:34:21 +03:00
|
|
|
if (ongoing) return;
|
|
|
|
ongoing = true;
|
|
|
|
|
|
|
|
const query = input.value;
|
2017-10-25 14:24:42 +03:00
|
|
|
fetch(
|
2017-10-25 17:34:21 +03:00
|
|
|
"_search?snippet_size=4&limit=4&q=" + encodeURIComponent(query),
|
2017-10-25 14:24:42 +03:00
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Accept": "application/json",
|
|
|
|
},
|
|
|
|
credentials: "same-origin",
|
|
|
|
}
|
|
|
|
).then(response => {
|
|
|
|
if (!response.ok) throw new Error("Unexpected status code (" + response.status + ")");
|
|
|
|
|
|
|
|
return response.json();
|
|
|
|
}).then(result => {
|
|
|
|
while (results.lastChild) results.removeChild(results.lastChild);
|
|
|
|
|
2017-10-25 14:49:00 +03:00
|
|
|
result.hits.forEach((hit, index) => {
|
2017-10-25 14:24:42 +03:00
|
|
|
const item = resultPrototype.cloneNode(true);
|
|
|
|
item.querySelector('.link').href = hit.slug || ".";
|
2017-10-25 14:49:00 +03:00
|
|
|
item.querySelector('.link').setAttribute("data-focusindex", index + 1);
|
2017-10-25 14:24:42 +03:00
|
|
|
item.querySelector('.title').textContent = hit.title;
|
|
|
|
item.querySelector('.snippet').textContent = hit.snippet;
|
|
|
|
results.appendChild(item);
|
|
|
|
})
|
2017-10-25 17:34:21 +03:00
|
|
|
|
|
|
|
if (result.next) {
|
|
|
|
const item = resultPrototype.cloneNode(true);
|
|
|
|
item.querySelector('.link').href = "_search?q=" + encodeURIComponent(query);
|
|
|
|
item.querySelector('.link').setAttribute("data-focusindex", result.hits.length + 1);
|
|
|
|
item.querySelector('.link').innerHTML = "See more results\u2026";
|
|
|
|
results.appendChild(item);
|
|
|
|
}
|
|
|
|
|
2017-10-25 14:24:42 +03:00
|
|
|
results.classList.add("show");
|
2017-10-25 17:34:21 +03:00
|
|
|
|
|
|
|
if (input.value !== query) submitter();
|
|
|
|
|
|
|
|
ongoing = false;
|
2017-10-25 14:24:42 +03:00
|
|
|
}).catch(err => {
|
2017-10-25 17:34:21 +03:00
|
|
|
ongoing = false;
|
2017-10-25 14:24:42 +03:00
|
|
|
console.error(err);
|
2017-10-25 17:34:21 +03:00
|
|
|
alert(err); // TODO Better interactive error reporting
|
2017-10-25 14:24:42 +03:00
|
|
|
});
|
|
|
|
}
|
2017-10-25 17:34:21 +03:00
|
|
|
const submitter = debouncer(300, submit);
|
2017-10-25 14:24:42 +03:00
|
|
|
|
|
|
|
input.addEventListener('input', submitter);
|
|
|
|
|
|
|
|
form.addEventListener('focusin', () => form.classList.add("focus"));
|
|
|
|
form.addEventListener('focusout', function (ev) {
|
|
|
|
for (let ancestor = ev.relatedTarget; ancestor; ancestor = ancestor.parentElement) {
|
|
|
|
if (ancestor === form) return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We are now actually losing focus from the form:
|
|
|
|
form.classList.remove("focus");
|
|
|
|
});
|
2017-10-25 14:49:00 +03:00
|
|
|
|
2017-10-25 17:02:30 +03:00
|
|
|
function moveFocus(element, delta) {
|
2017-10-25 14:49:00 +03:00
|
|
|
const focusIndexText = document.activeElement.getAttribute("data-focusindex");
|
2017-10-25 17:02:30 +03:00
|
|
|
const nextIndex = focusIndexText ? parseInt(focusIndexText, 10) + delta : 0;
|
2017-10-25 14:49:00 +03:00
|
|
|
|
2017-10-25 17:02:30 +03:00
|
|
|
const candidate = element.querySelector("[data-focusindex=\"" + nextIndex + "\"]");
|
2017-10-25 14:49:00 +03:00
|
|
|
if (candidate) candidate.focus();
|
|
|
|
}
|
|
|
|
|
2017-10-25 17:02:30 +03:00
|
|
|
function focusControl(element, ev) {
|
2017-10-25 14:49:00 +03:00
|
|
|
if (ev.key === 'ArrowUp') {
|
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
2017-10-25 17:02:30 +03:00
|
|
|
moveFocus(element, -1);
|
2017-10-25 14:49:00 +03:00
|
|
|
} else if (ev.key === 'ArrowDown') {
|
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
2017-10-25 17:02:30 +03:00
|
|
|
moveFocus(element, 1);
|
2017-10-25 17:34:21 +03:00
|
|
|
} else if (ev.key === 'Escape') {
|
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
|
|
|
document.activeElement && document.activeElement.blur();
|
2017-10-25 14:49:00 +03:00
|
|
|
}
|
2017-10-25 17:02:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for (let element of document.querySelectorAll(".keyboard-focus-control")) {
|
|
|
|
const captureElement = element;
|
|
|
|
element.addEventListener('keydown', ev => focusControl(captureElement, ev));
|
|
|
|
}
|
|
|
|
|
|
|
|
const defaultKeyboardFocusControl = document.querySelector(".default-keyboard-focus-control");
|
|
|
|
if (defaultKeyboardFocusControl) {
|
|
|
|
document.addEventListener('keydown', ev => {
|
|
|
|
focusControl(defaultKeyboardFocusControl, ev);
|
|
|
|
});
|
|
|
|
}
|
2017-10-25 14:24:42 +03:00
|
|
|
})();
|