master @ 48 LINES
[ HISTORY ] [ UP ]
┌─ TS ───────────────────────────────────────────────────────────────────────┐
│ // Masthead search scorer. Replaces fuse.js - 9.5k of gzipped fuzzy │
│ // matching is absurd for an index this size, and a plain scorer stays │
│ // predictable as it grows. Runs in the browser. │
│ │
│ export interface SearchItem { │
│ note: string; │
│ title: string; │
│ category: string; │
│ tags: string[]; │
│ filed: string; │
│ url: string; │
│ } │
│ │
│ function fieldScore(value: string, term: string, weight: number): number { │
│ const v = value.toLowerCase(); │
│ if (v === term) return weight * 3; │
│ if (v.startsWith(term)) return weight * 2; │
│ if (v.includes(term)) return weight; │
│ return 0; │
│ } │
│ │
│ function itemScore(item: SearchItem, terms: string[]): number { │
│ let total = 0; │
│ for (const t of terms) { │
│ const s = Math.max( │
│ fieldScore(item.note, t, 3), │
│ fieldScore(item.title, t, 2), │
│ ...item.tags.map((tag) => fieldScore(tag, t, 1.5)), │
│ fieldScore(item.category, t, 1), │
│ fieldScore(item.filed, t, 1), │
│ ); │
│ if (s === 0) return 0; // every term must land somewhere │
│ total += s; │
│ } │
│ return total; │
│ } │
│ │
│ export function searchIndex(items: SearchItem[], query: string, limit = 8): Search │
│ Item[] { │
│ const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean); │
│ if (!terms.length) return []; │
│ return items │
│ .map((item) => ({ item, score: itemScore(item, terms) })) │
│ .filter((s) => s.score > 0) │
│ .sort((a, b) => b.score - a.score) │
│ .slice(0, limit) │
│ .map((s) => s.item); │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ TS ─────────────────────────────────┐
│ // Masthead search scorer. Replaces fuse.js │
│ - 9.5k of gzipped fuzzy │
│ // matching is absurd for an index this size │
│ , and a plain scorer stays │
│ // predictable as it grows. Runs in the brow │
│ ser. │
│ │
│ export interface SearchItem { │
│ note: string; │
│ title: string; │
│ category: string; │
│ tags: string[]; │
│ filed: string; │
│ url: string; │
│ } │
│ │
│ function fieldScore(value: string, term: str │
│ ing, weight: number): number { │
│ const v = value.toLowerCase(); │
│ if (v === term) return weight * 3; │
│ if (v.startsWith(term)) return weight * 2; │
│ if (v.includes(term)) return weight; │
│ return 0; │
│ } │
│ │
│ function itemScore(item: SearchItem, terms: │
│ string[]): number { │
│ let total = 0; │
│ for (const t of terms) { │
│ const s = Math.max( │
│ fieldScore(item.note, t, 3), │
│ fieldScore(item.title, t, 2), │
│ ...item.tags.map((tag) => fieldScore(t │
│ ag, t, 1.5)), │
│ fieldScore(item.category, t, 1), │
│ fieldScore(item.filed, t, 1), │
│ ); │
│ if (s === 0) return 0; // every term mus │
│ t land somewhere │
│ total += s; │
│ } │
│ return total; │
│ } │
│ │
│ export function searchIndex(items: SearchIte │
│ m[], query: string, limit = 8): SearchItem[] │
│ { │
│ const terms = query.trim().toLowerCase().s │
│ plit(/\s+/).filter(Boolean); │
│ if (!terms.length) return []; │
│ return items │
│ .map((item) => ({ item, score: itemScore │
│ (item, terms) })) │
│ .filter((s) => s.score > 0) │
│ .sort((a, b) => b.score - a.score) │
│ .slice(0, limit) │
│ .map((s) => s.item); │
│ } │
└──────────────────────────────────────────────┘
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET