Window the search snippet on the densest term cluster

This commit is contained in:
dtonon 2026-08-12 18:58:21 +01:00
parent c9227ee8fb
commit 46176511f3

View file

@ -12,20 +12,44 @@ export type SearchResult = {
createdAt: number; createdAt: number;
}; };
// Window the snippet around the first term match so the highlight is visible // Window the snippet around the best term cluster: the anchor occurrence
// even when the match sits deep in a long post. // whose window covers the most distinct query terms, so a multi-word query
// excerpts the passage matching the whole phrase, not the first lone word.
// Ties go to the earliest position.
function snippetOf(content: string, query: string): string { function snippetOf(content: string, query: string): string {
const flat = content.replace(/\s+/g, " ").trim(); const flat = content.replace(/\s+/g, " ").trim();
const MAX = 140; const MAX = 140;
if (flat.length <= MAX) return flat; if (flat.length <= MAX) return flat;
const lower = flat.toLowerCase(); const lower = flat.toLowerCase();
let idx = -1; const terms = [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))];
for (const t of query.toLowerCase().split(/\s+/).filter(Boolean)) {
const i = lower.indexOf(t); const occurrences: { i: number; term: string }[] = [];
if (i !== -1 && (idx === -1 || i < idx)) idx = i; for (const t of terms) {
let i = 0;
while ((i = lower.indexOf(t, i)) !== -1) {
occurrences.push({ i, term: t });
i += t.length;
}
} }
if (idx <= 40) return flat.slice(0, MAX) + "…"; if (occurrences.length === 0) return flat.slice(0, MAX) + "…";
const start = idx - 40; occurrences.sort((a, b) => a.i - b.i);
const span = MAX - 40; // Visible chars from the anchor to the window's end
let best = occurrences[0];
let bestScore = 0;
for (const o of occurrences) {
const seen = new Set<string>();
for (const p of occurrences) {
if (p.i >= o.i && p.i + p.term.length <= o.i + span) seen.add(p.term);
}
if (seen.size > bestScore) {
bestScore = seen.size;
best = o;
}
}
if (best.i <= 40) return flat.slice(0, MAX) + "…";
const start = best.i - 40;
const end = Math.min(flat.length, start + MAX); const end = Math.min(flat.length, start + MAX);
return "…" + flat.slice(start, end) + (end < flat.length ? "…" : ""); return "…" + flat.slice(start, end) + (end < flat.length ? "…" : "");
} }