// Cross-references for the selected chunk: typed entry, suggestions from the // Open Cross Ref dataset, and loading a reference's verse text. import { useEffect, useState } from 'react'; import { parseBibleChapter } from '../utils/bibleUtils.jsx'; import { parseCrossRefString } from '../utils/importParsers.js'; import { formatCrossRef } from '../utils/wordStudy.js'; const crossRefDatasetCache = {}; // "BOOK/chapter" -> Open Cross Ref chapter data const verseChapterCache = {}; // "translation/BOOK/chapter" -> parsed verses // notify(message, ms) shows a short status message on the study page. export function useCrossRefs({ project, allChunks, selectedChunk, updateChunk, notify }) { const [crossRefInput, setCrossRefInput] = useState(''); const [suggestingCrossRefs, setSuggestingCrossRefs] = useState(false); const [crossRefSuggestions, setCrossRefSuggestions] = useState([]); useEffect(() => { setCrossRefSuggestions([]); }, [selectedChunk?.id]); useEffect(() => { setCrossRefInput(''); }, [project?.selectedChunkId]); const addCrossRef = (chunkId) => { const ref = crossRefInput.trim(); if (!ref) return; updateChunk(chunkId, { crossReferences: [...(selectedChunk?.crossReferences ?? []), ref], }); setCrossRefInput(''); }; const removeCrossRef = (chunkId, ref) => { updateChunk(chunkId, { crossReferences: (selectedChunk?.crossReferences ?? []).filter((r) => r !== ref), }); }; const suggestCrossRefsForChunk = async (chunkId) => { const chunk = allChunks.find((c) => c.id === chunkId); const chapterIndex = project?.chapters.findIndex((ch) => ch.chunks.some((c) => c.id === chunkId) ) ?? -1; const chapter = chapterIndex >= 0 ? project.chapters[chapterIndex] : null; if (!chunk || !chapter) return; setSuggestingCrossRefs(true); setCrossRefSuggestions([]); try { const cacheKey = `${chapter.bookAbbrev}/${chapter.chapter}`; let chapterData = crossRefDatasetCache[cacheKey]; if (!chapterData) { const res = await fetch(`https://bible.helloao.org/api/d/open-cross-ref/${chapter.bookAbbrev}/${chapter.chapter}.json`); if (!res.ok) throw new Error('No cross-reference data found for this chapter.'); chapterData = await res.json(); crossRefDatasetCache[cacheKey] = chapterData; } const verseEntries = (chapterData.chapter?.content ?? []).filter( (item) => item.verse >= chunk.startVerse && item.verse <= chunk.endVerse, ); const existing = new Set(chunk.crossReferences ?? []); const seen = new Set(); const refs = []; for (const v of verseEntries) { for (const ref of v.references ?? []) { const formatted = formatCrossRef(ref); if (existing.has(formatted) || seen.has(formatted)) continue; seen.add(formatted); refs.push({ formatted, score: ref.score ?? 0 }); } } refs.sort((a, b) => b.score - a.score); if (refs.length === 0) { notify('No new cross-reference suggestions found for this passage.', 2500); return; } setCrossRefSuggestions(refs.slice(0, 12).map((r) => r.formatted)); } catch (err) { notify(`Cross-reference suggest failed: ${err.message}`, 3000); } finally { setSuggestingCrossRefs(false); } }; const loadVerseText = async (refString) => { const parsed = parseCrossRefString(refString); if (!parsed) throw new Error('Could not parse reference.'); const translation = project?.translation ?? 'BSB'; const cacheKey = `${translation}/${parsed.bookAbbrev}/${parsed.chapter}`; let verses = verseChapterCache[cacheKey]; if (!verses) { const res = await fetch(`https://bible.helloao.org/api/${translation}/${parsed.bookAbbrev}/${parsed.chapter}.json`); if (!res.ok) throw new Error('Could not load verse text.'); const data = await res.json(); verses = parseBibleChapter(data); verseChapterCache[cacheKey] = verses; } const text = verses .filter((v) => v.number >= parsed.verse && v.number <= parsed.endVerse) .map((v) => v.text) .join(' '); return text; }; const addSuggestedCrossRef = (chunkId, ref) => { updateChunk(chunkId, { crossReferences: [...(selectedChunk?.crossReferences ?? []), ref], }); setCrossRefSuggestions((cur) => cur.filter((r) => r !== ref)); }; return { crossRefInput, setCrossRefInput, suggestingCrossRefs, crossRefSuggestions, addCrossRef, removeCrossRef, suggestCrossRefsForChunk, addSuggestedCrossRef, loadVerseText, }; }