// Search this chapter, or the whole Bible in the translation being read. // BSB and NET are downloaded once and searched in the browser; ESV, NLT, CSB and // NASB are searched through the server (their licences don't allow a bulk copy). import { useMemo } from 'react'; import Sheet from './Sheet.jsx'; import { SearchIcon } from './icons.jsx'; import { bookOptions } from '../../utils/bibleUtils.jsx'; const nameFor = (abbrev) => bookOptions.find((b) => b.abbrev === abbrev)?.name ?? abbrev; const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Wraps each searched word (quotes ignored) in . function highlight(text, query) { const words = [...new Set(query.toLowerCase().replace(/["“”]/g, ' ').split(/\s+/).filter((w) => w.length > 1))]; if (!words.length) return text; const re = new RegExp(`(${words.map(escapeRegExp).join('|')})`, 'gi'); return text.split(re).map((part, i) => (i % 2 === 1 ? {part} : part)); } export default function SearchSheet({ open, onClose, query, onQuery, scope, onScope, verses, bookName, chapter, currentBook, translation, isLocal, indexStatus, onLoadIndex, bibleMatches, deferredBibleQuery, remote, onJump, }) { const q = query.trim().toLowerCase(); const chapterMatches = useMemo( () => (scope === 'chapter' && q ? verses.filter((v) => v.text.toLowerCase().includes(q)) : []), [scope, q, verses], ); const scopeBtn = (id, label, onClick) => ( ); const note = (text) =>

{text}

; const resultRow = (r) => ( ); return (
onQuery(e.target.value)} placeholder={scope === 'bible' ? `Search the whole Bible (${translation})…` : `Search ${bookName} ${chapter}…`} className="min-w-0 flex-1 bg-transparent text-sm text-[var(--r-text)] outline-none placeholder:text-[var(--r-sub)]" />
{scopeBtn('chapter', 'This chapter', () => onScope('chapter'))} {scopeBtn('bible', `Whole Bible (${translation})`, () => { onScope('bible'); if (isLocal && indexStatus === 'idle') onLoadIndex(); })}
{scope === 'chapter' && ( !q ? note('Type a word to find it in this chapter.') : chapterMatches.length === 0 ? note(`No verses match “${query}”.`) : chapterMatches.map((v) => ( )) )} {scope === 'bible' && isLocal && ( <> {indexStatus === 'loading' && note('Loading the full Bible for search. This happens once per visit (about 7 MB).')} {indexStatus === 'error' &&

Couldn’t load the full Bible.

} {indexStatus === 'ready' && (!deferredBibleQuery ? note('Type a word to search all 66 books.') : bibleMatches.length === 0 ? note(`No verses match “${query}”.`) : ( <>

{bibleMatches.length} match{bibleMatches.length === 1 ? '' : 'es'}{bibleMatches.length > 100 ? ' (showing first 100)' : ''}

{bibleMatches.slice(0, 100).map(resultRow)} ))} )} {scope === 'bible' && !isLocal && ( <> {remote.tooShort && note('Type at least two letters. Use quotes for an exact phrase, like “good shepherd”.')} {!remote.tooShort && remote.error &&

{remote.error}

} {!remote.tooShort && !remote.error && remote.loading && remote.results.length === 0 && note('Searching…')} {!remote.tooShort && !remote.error && !remote.loading && remote.total === 0 && remote.page > 0 && note(`No verses match “${query}”.`)} {remote.results.length > 0 && ( <>

{remote.total} match{remote.total === 1 ? '' : 'es'} in {translation}

{remote.results.map(resultRow)} {remote.hasMore && ( )} )} )}
); }