116 lines
4.3 KiB
JavaScript
116 lines
4.3 KiB
JavaScript
|
|
const maxResults = 3, titleWeight = 10, urlWeight = 5, keywordWeight = 3, descriptionWeight = 1
|
|
let searchVal = "", pages = []
|
|
|
|
function handleKeyNav(/* KeyboardEvent */ e) {
|
|
let row = _(".autocompleteResult.selected")
|
|
switch (e.key) {
|
|
case "ArrowUp":
|
|
if (row && row.previousElementSibling) {
|
|
row.classList.remove("selected")
|
|
row = row.previousElementSibling
|
|
row.classList.add("selected")
|
|
}
|
|
break;
|
|
case "ArrowDown":
|
|
if (!row) {
|
|
// pick the first one
|
|
row = _(".autocompleteResult")
|
|
} else if (row.nextElementSibling) {
|
|
row.classList.remove("selected")
|
|
row = row.nextElementSibling
|
|
}
|
|
if (row) {
|
|
row.classList.add("selected")
|
|
}
|
|
break;
|
|
case "Enter":
|
|
e.preventDefault();
|
|
if (!row || row.id === "autocompleteShowAll") {
|
|
// "see all" is selected or no autocomplete result selected
|
|
window.location.href = "/search/?q=" + e.target.value;
|
|
} else {
|
|
// an autocomplete result is selected
|
|
row.click()
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function matches(input, search) {
|
|
return String(input).toUpperCase().split(search.toUpperCase()).length - 1;
|
|
}
|
|
|
|
function handleSearch(/* KeyboardEvent */ e) {
|
|
if (e.target.value === searchVal) {
|
|
// no new search
|
|
return
|
|
}
|
|
searchVal = e.target.value
|
|
let results = [];
|
|
if (searchVal.length > 2) {
|
|
for (let i = 0; i < pages.length; i++) {
|
|
// search url, description, title, and keywords for search input
|
|
const p = pages[i];
|
|
if (!p.title) {
|
|
continue
|
|
}
|
|
let score = (matches(p.title, searchVal) * titleWeight)
|
|
if (p.description != null) {
|
|
score += (matches(p.description, searchVal) * descriptionWeight)
|
|
}
|
|
if (p.url != null) {
|
|
score += (matches(p.url, searchVal) * urlWeight)
|
|
}
|
|
if (p.keywords != null) {
|
|
score += (matches(p.keywords, searchVal) * keywordWeight)
|
|
}
|
|
if (score > 0) {
|
|
results.push({ "topic": i, "score": score });
|
|
}
|
|
}
|
|
}
|
|
let rows = []
|
|
if (results.length > 0) {
|
|
results.sort((a, b) => b.score - a.score);
|
|
const match = new RegExp(`(${searchVal})`, "gi");
|
|
const highlight = function (/* String */ content) {
|
|
return content.replace(match, "<span>$1</span>")
|
|
}
|
|
for (let i = 0; i < maxResults && i < results.length; i++) {
|
|
const p = pages[results[i].topic];
|
|
rows.push(`<div class='autocompleteResult' onclick='window.location.href = "${p.url}"'><ul>`);
|
|
rows.push(`<li><a class='title' href='${p.url}'>${ highlight(p.title) }</a></li>`);
|
|
if (p.description && p.description !== p.title) {
|
|
// Omit description if it's the same as the title
|
|
rows.push(`<li>${ highlight(p.description) }</li>`);
|
|
}
|
|
if (p.keywords) {
|
|
rows.push(`<li class='keywords'><span class='glyphicon glyphicon-tags'></span><i>${ highlight(p.keywords) }</i></li>`);
|
|
}
|
|
rows.push("</ul></div>")
|
|
}
|
|
let shown = Math.min(results.length, maxResults)
|
|
rows.push(`<div class='autocompleteResult' id='autocompleteShowAll'><ul><li>Showing ${shown} of ${results.length} ${ (shown > 1) ? "results" : "result" }. <a href='/search/?q=${searchVal}'>See all results...</a></li></ul></div>`)
|
|
}
|
|
|
|
const out = _("#autocompleteResults")
|
|
if (out) {
|
|
out.innerHTML = rows.join("");
|
|
let shown = Math.min(results.length, maxResults)
|
|
out.style.display = shown === 0 ? "none" : "block"
|
|
}
|
|
}
|
|
|
|
ready(() => {
|
|
getJSON( "/js/metadata.json", function(data) {
|
|
pages = data
|
|
const input = _("#st-search-input")
|
|
if (/* HTMLInputElement */ input) {
|
|
input.form.addEventListener('submit', (e) => e.preventDefault());
|
|
input.addEventListener('keyup', handleKeyNav, {capture: true })
|
|
input.addEventListener('keyup', debounce(handleSearch, 100), {capture: true })
|
|
}
|
|
});
|
|
})
|