Better searching
All checks were successful
Publish Library / Build NPM Project (push) Successful in 10s
Publish Library / Tag Version (push) Successful in 8s

This commit is contained in:
2026-08-22 12:27:54 -04:00
parent c5207261fa
commit 86730f1de8
4 changed files with 46 additions and 9 deletions

View File

@@ -20,13 +20,42 @@ export function similarity(a, b) {
return 1 - levenshtein(a, b) / Math.max(a.length, b.length, 1);
}
/**
* Scores `text` against a single lowercased `term`. Substring containment always wins.
* Otherwise, only rescues genuine typos: requires the same leading letter and a tight
* absolute edit-distance cap, so unrelated words can't win purely on coincidental
* letter overlap (e.g. "Viennese" vs "diannes").
*/
function scoreAgainst(text, term) {
if (text.includes(term)) return 1 - (text.length - term.length) / text.length * 0.3;
if (!text.length || !term.length || text[0] !== term[0]) return 0;
const dist = levenshtein(text, term);
const maxAllowed = Math.max(1, Math.ceil(term.length * 0.34));
if (dist > maxAllowed) return 0;
return 1 - dist / Math.max(text.length, term.length);
}
/** Compares `target` against one or more search terms; returns avg/max/per-term similarity. */
export function fuzzyMatch(target, ...terms) {
if (!terms.length) throw new Error('Requires at least 1 term to compare');
const similarities = terms.map(t => similarity(target, t));
const lowerTarget = String(target).toLowerCase();
const words = lowerTarget.split(/\W+/).filter(Boolean);
const similarities = terms.map(term => {
const t = term.toLowerCase();
return Math.max(scoreAgainst(lowerTarget, t), ...words.map(w => scoreAgainst(w, t)));
});
return {
avg: similarities.reduce((acc, s) => acc + s, 0) / similarities.length,
max: Math.max(...similarities),
similarities,
};
}
/** Derives a readable pseudo-title from a URL's last path segment, e.g. ".../diannes-southwest-salad/" -> "Diannes Southwest Salad". */
export function titleFromUrl(url) {
const slug = String(url).replace(/\/$/, '').split('/').pop() || url;
const clean = slug.replace(/\.(zim|meta4|html?|md)$/i, '').replace(/[-_.]+/g, ' ');
return clean.replace(/\b\w/g, c => c.toUpperCase());
}