Reader: whole-Bible search in every translation
Build and Push Docker Image / build-and-push (push) Failing after 20s
Build and Push Docker Image / build-and-push (push) Failing after 20s
BSB and NET are indexed in the browser; ESV, NLT, CSB and NASB are searched through a server proxy (keys stay server-side). Results page in 25 at a time and jump to the verse. Also fixes a stray trailing comma in CSB chapter text. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3279dddae5
commit
c11b6a60bf
8 files changed
+262
-55
No files matched your search
+26
-21
@@ -62,7 +62,6 @@ export function useBibleReader(currentPage) {
|
||||
const [readerSearch, setReaderSearch] = useState('');
|
||||
const [readerSearchActive, setReaderSearchActive] = useState(false);
|
||||
const [readerSearchScope, setReaderSearchScope] = useState('chapter'); // 'chapter' | 'bible'
|
||||
const [bibleIndexStatus, setBibleIndexStatus] = useState('idle'); // 'idle' | 'loading' | 'ready' | 'error'
|
||||
const _bibleIndexCacheRef = useRef({}); // translation -> flat verse array
|
||||
const [readerInkByPage, setReaderInkByPage] = useState(() => {
|
||||
try { return JSON.parse(localStorage.getItem('readerInkByPage') ?? '{}'); }
|
||||
@@ -77,27 +76,33 @@ export function useBibleReader(currentPage) {
|
||||
// Debounce timer ref so rapid stroke updates don't flood the server
|
||||
const inkSaveTimerRef = useRef({});
|
||||
|
||||
// The HelloAO API has no search endpoint, so whole-Bible search fetches the
|
||||
// entire translation once (~7MB) and searches an in-memory flat verse index
|
||||
// client-side. Cached per translation for the rest of the session (not persisted
|
||||
// across reloads, to avoid eating into localStorage/IndexedDB quota for a cache
|
||||
// that's cheap enough to rebuild once per visit).
|
||||
const loadBibleIndex = async () => {
|
||||
if (_bibleIndexCacheRef.current.BSB) {
|
||||
setBibleIndexStatus('ready');
|
||||
return;
|
||||
}
|
||||
setBibleIndexStatus('loading');
|
||||
// The HelloAO API has no search endpoint, so whole-Bible search for the free
|
||||
// translations (BSB, NET) fetches the entire translation once (~7MB) and searches
|
||||
// an in-memory flat verse index client-side. Cached per translation for the rest of
|
||||
// the visit, and in IndexedDB for a while. Licensed translations use the server
|
||||
// search proxy instead (see useBibleSearch).
|
||||
const [bibleIndexState, setBibleIndexState] = useState({ translation: null, status: 'idle' });
|
||||
const bibleIndexStatusFor = (translation) => {
|
||||
if (_bibleIndexCacheRef.current[translation]) return 'ready';
|
||||
return bibleIndexState.translation === translation ? bibleIndexState.status : 'idle';
|
||||
};
|
||||
|
||||
const loadBibleIndex = async (translation = 'BSB') => {
|
||||
const set = (status) => setBibleIndexState({ translation, status });
|
||||
if (_bibleIndexCacheRef.current[translation]) { set('ready'); return; }
|
||||
const source = READER_TRANSLATIONS.find((t) => t.id === translation);
|
||||
if (!source || source.licensed) { set('error'); return; }
|
||||
set('loading');
|
||||
try {
|
||||
// Check IndexedDB cache before hitting the network (~7 MB fetch)
|
||||
const cached = await getBibleIndexFromIDB('BSB');
|
||||
const cached = await getBibleIndexFromIDB(translation);
|
||||
if (cached?.flat && Date.now() - cached.cachedAt < BIBLE_IDB_TTL) {
|
||||
_bibleIndexCacheRef.current.BSB = cached.flat;
|
||||
setBibleIndexStatus('ready');
|
||||
_bibleIndexCacheRef.current[translation] = cached.flat;
|
||||
set('ready');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch('https://bible.helloao.org/api/BSB/complete.json');
|
||||
const res = await fetch(`https://bible.helloao.org/api/${source.helloao ?? source.id}/complete.json`);
|
||||
if (!res.ok) throw new Error('Unable to load the full Bible for search.');
|
||||
const data = await res.json();
|
||||
const flat = [];
|
||||
@@ -111,12 +116,12 @@ export function useBibleReader(currentPage) {
|
||||
}
|
||||
}
|
||||
}
|
||||
_bibleIndexCacheRef.current.BSB = flat;
|
||||
setBibleIndexStatus('ready');
|
||||
_bibleIndexCacheRef.current[translation] = flat;
|
||||
set('ready');
|
||||
// Persist for future visits — non-blocking
|
||||
saveBibleIndexToIDB('BSB', flat);
|
||||
saveBibleIndexToIDB(translation, flat);
|
||||
} catch {
|
||||
setBibleIndexStatus('error');
|
||||
set('error');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -381,7 +386,7 @@ export function useBibleReader(currentPage) {
|
||||
readerTextHighlights,
|
||||
addTextHighlight,
|
||||
removeTextHighlight,
|
||||
bibleIndexStatus,
|
||||
bibleIndexStatusFor,
|
||||
loadBibleIndex,
|
||||
_bibleIndexCacheRef,
|
||||
loadReaderChapter,
|
||||
|
||||
Reference in new issue
Block a user