// Search across everything the reader has written or saved on this device: study notes, project // titles and passages, bookmarks and highlights. (Bible text itself is searched in the Reader.) import { bookOptions, formatChunkReference } from './bibleUtils.jsx'; const NOTE_FIELDS = [ ['observation', 'Observation'], ['interpretation', 'Interpretation'], ['application', 'Application'], ['generalNotes', 'Notes'], ['finalScript', 'Script'], ]; const snippetAround = (raw, at, len) => { const start = Math.max(0, at - 40); const end = at + len + 60; return (start > 0 ? '…' : '') + raw.slice(start, end).replace(/\s+/g, ' ') + (end < raw.length ? '…' : ''); }; // projects: full project objects. -> [{ projectId, projectTitle, chunkId, ref, field, snippet }], at most // `limit`, one hit per chunk (the first field that matches). export function searchNotes(projects, query, { limit = 20 } = {}) { const q = query.trim().toLowerCase(); if (q.length < 3) return []; const hits = []; for (const project of projects) { for (const [chapterIndex, chapter] of (project.chapters ?? []).entries()) { for (const chunk of chapter.chunks ?? []) { for (const [field, label] of NOTE_FIELDS) { const raw = chunk[field] ?? ''; const at = raw.toLowerCase().indexOf(q); if (at === -1) continue; hits.push({ projectId: project.id, projectTitle: project.title, chunkId: chunk.id, ref: formatChunkReference(project, chapterIndex, chunk, '–'), field: label, snippet: snippetAround(raw, at, q.length), }); break; } if (hits.length >= limit) return hits; } } } return hits; } // Projects whose title, passages or tags contain the text. export function searchProjects(index, query, { limit = 6 } = {}) { const q = query.trim().toLowerCase(); if (!q) return []; return index.filter((e) => [e.title, e.chapterSummary, ...(e.tags ?? [])].some((t) => (t ?? '').toLowerCase().includes(q))).slice(0, limit); } const bookName = (abbrev) => bookOptions.find((b) => b.abbrev === abbrev)?.name ?? abbrev; // Highlights (by their words or what the colour is named) and bookmarks (by reference). // -> [{ kind: 'highlight' | 'bookmark', book, bookName, chapter, verse, color, text?, label? }] export function searchMarks({ bookmarks = {}, highlights = [], labels = {} }, query, { limit = 10 } = {}) { const q = query.trim().toLowerCase(); if (q.length < 2) return []; const out = []; for (const h of highlights) { const label = labels[h.color] ?? ''; const ref = `${bookName(h.book)} ${h.chapter}:${h.verse}`.toLowerCase(); if ((h.text ?? '').toLowerCase().includes(q) || label.toLowerCase().includes(q) || ref.includes(q)) { out.push({ kind: 'highlight', book: h.book, bookName: bookName(h.book), chapter: h.chapter, verse: h.verse, color: h.color, text: h.text, label }); } } for (const [key, color] of Object.entries(bookmarks)) { const [book, chapter, verse] = key.split('-'); const ref = `${bookName(book)} ${chapter}:${verse}`; if (ref.toLowerCase().includes(q)) out.push({ kind: 'bookmark', book, bookName: bookName(book), chapter: Number(chapter), verse: Number(verse), color }); } return out.slice(0, limit); }