OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
 
master @ 48 LINES
 
[ HISTORY ]  [ UP ]
 

// 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);
}

// 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