46 lines
2.0 KiB
JavaScript
46 lines
2.0 KiB
JavaScript
// Shared client-side news search over /search.json (no dependencies, no modules).
|
|
// Used by the homepage hero (live suggestions) and /search (full results).
|
|
(function () {
|
|
window.newsSearch = function (opts) {
|
|
const input = document.getElementById(opts.input);
|
|
const list = document.getElementById(opts.list);
|
|
const status = document.getElementById(opts.status);
|
|
let index = null;
|
|
let timer;
|
|
// First title match wrapped in <mark> (Starlight search pattern) — built
|
|
// from text nodes, never innerHTML.
|
|
function highlight(text, q) {
|
|
const i = text.toLowerCase().indexOf(q);
|
|
if (i < 0) return [text];
|
|
const mark = document.createElement('mark');
|
|
mark.textContent = text.slice(i, i + q.length);
|
|
return [text.slice(0, i), mark, text.slice(i + q.length)];
|
|
}
|
|
async function run() {
|
|
const q = input.value.trim().toLowerCase();
|
|
list.innerHTML = '';
|
|
if (q.length < 2) { status.textContent = opts.hint || ''; return; }
|
|
if (!index) index = await (await fetch('/search.json')).json();
|
|
const hits = index
|
|
.filter((p) => (p.title + ' ' + p.description + ' ' + p.category).toLowerCase().includes(q))
|
|
.slice(0, opts.limit);
|
|
const unit = opts.unit || 'result';
|
|
status.textContent = hits.length
|
|
? hits.length + ' ' + unit + (hits.length === 1 ? '' : 's') + ' for “' + input.value.trim() + '”'
|
|
: 'No results for “' + input.value.trim() + '” — try a shorter or different term.';
|
|
for (const p of hits) {
|
|
const li = document.createElement('li');
|
|
const a = document.createElement('a');
|
|
a.href = '/news/' + encodeURIComponent(p.slug);
|
|
a.append(...highlight(p.title, q));
|
|
const small = document.createElement('small');
|
|
small.textContent = p.date;
|
|
li.append(a, small);
|
|
list.append(li);
|
|
}
|
|
}
|
|
input.addEventListener('input', () => { clearTimeout(timer); timer = setTimeout(run, 150); });
|
|
return run; // callers may run() immediately (e.g. /search?q=…)
|
|
};
|
|
})();
|