114 lines
3.1 KiB
JavaScript
114 lines
3.1 KiB
JavaScript
(function () {
|
||
"use strict";
|
||
|
||
const input = document.getElementById("search-input");
|
||
const results = document.getElementById("search-results");
|
||
if (!input || !results) return;
|
||
|
||
let pagefind = null;
|
||
let timer = null;
|
||
let currentItems = [];
|
||
|
||
async function getPagefind() {
|
||
if (!pagefind) pagefind = await import("/pagefind/pagefind.js");
|
||
return pagefind;
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
const div = document.createElement("div");
|
||
div.textContent = str;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
function render(items) {
|
||
currentItems = items;
|
||
if (!items.length) {
|
||
results.innerHTML = '<p class="search-empty">Keine Treffer.</p>';
|
||
results.hidden = false;
|
||
return;
|
||
}
|
||
results.innerHTML = items
|
||
.map((it) => {
|
||
// excerpt enthält bereits <mark> von Pagefind – als HTML rendern
|
||
const excerpt = it.excerpt
|
||
? `<span class="search-excerpt">${it.excerpt}</span>`
|
||
: "";
|
||
const sub = (it.sub_results || [])
|
||
.slice(0, 3)
|
||
.map(
|
||
(s) =>
|
||
`<li><a href="${s.url}">${escapeHtml(s.title)}</a>` +
|
||
(s.excerpt ? `<span class="search-excerpt">${s.excerpt}</span>` : "") +
|
||
`</li>`
|
||
)
|
||
.join("");
|
||
return (
|
||
`<a class="search-item" href="${it.url}">` +
|
||
`<span class="search-title">${escapeHtml(it.title)}</span>${excerpt}</a>` +
|
||
(sub ? `<ul class="search-sub">${sub}</ul>` : "")
|
||
);
|
||
})
|
||
.join("");
|
||
results.hidden = false;
|
||
}
|
||
|
||
async function doSearch(query) {
|
||
query = query.trim();
|
||
if (query.length < 2) {
|
||
results.hidden = true;
|
||
results.innerHTML = "";
|
||
currentItems = [];
|
||
return;
|
||
}
|
||
try {
|
||
const pf = await getPagefind();
|
||
const search = await pf.search(query);
|
||
const items = [];
|
||
for (const res of search.results.slice(0, 8)) {
|
||
const data = await res.data();
|
||
items.push({
|
||
url: data.url,
|
||
title: (data.meta && data.meta.title) || data.url,
|
||
excerpt: data.excerpt,
|
||
sub_results: (data.sub_results || []).map((s) => ({
|
||
url: s.url,
|
||
title: s.title,
|
||
excerpt: s.excerpt,
|
||
})),
|
||
});
|
||
}
|
||
render(items);
|
||
} catch (e) {
|
||
results.innerHTML = '<p class="search-empty">Suche nicht verfügbar.</p>';
|
||
results.hidden = false;
|
||
}
|
||
}
|
||
|
||
input.addEventListener("input", () => {
|
||
clearTimeout(timer);
|
||
timer = setTimeout(() => doSearch(input.value), 150);
|
||
});
|
||
|
||
input.addEventListener("focus", () => {
|
||
if (input.value.trim().length >= 2) doSearch(input.value);
|
||
});
|
||
|
||
input.addEventListener("keydown", (e) => {
|
||
if (e.key === "Enter") {
|
||
e.preventDefault();
|
||
if (currentItems.length) window.location.href = currentItems[0].url;
|
||
}
|
||
});
|
||
|
||
document.addEventListener("click", (e) => {
|
||
if (!e.target.closest(".search")) results.hidden = true;
|
||
});
|
||
|
||
document.addEventListener("keydown", (e) => {
|
||
if (e.key === "Escape") {
|
||
results.hidden = true;
|
||
input.blur();
|
||
}
|
||
});
|
||
})();
|