Window the search snippet on the densest term cluster
This commit is contained in:
parent
c9227ee8fb
commit
46176511f3
1 changed files with 32 additions and 8 deletions
|
|
@ -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) + "…";
|
}
|
||||||
const start = idx - 40;
|
if (occurrences.length === 0) return flat.slice(0, MAX) + "…";
|
||||||
|
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 ? "…" : "");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue