Move study editor state into StudyPage and hooks
- StudyPage owns layout/tabs/collapsed sections, tags, DOCX script import, chunk navigation, keyboard shortcuts, and the selected-chunk values derived from the project. - useStudyResources: commentary + interlinear for the selected chunk's chapter. These now load only on the study page (they previously also fetched while on Setup). - useCrossRefs: typed refs, dataset suggestions, verse text. - useWordStudy: word entries, Strong's lookups, suggestion picker. - Fix: study-page messages (word suggestions, DOCX import, lookups) set a status that only SetupPage displayed, so they were never seen. They now show as a toast on the study page. - copyVerse moves into ReaderPage. - Tests: toast, arrow-key navigation, cross-refs, tags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4b14b3fbe6
commit
bf02e071fa
8 files changed
+749
-771
No files matched your search
@@ -0,0 +1,76 @@
|
||||
// Commentary and interlinear text for the chapter of the selected chunk.
|
||||
// Each loads only while its section is expanded; results are cached for the visit.
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const commentaryCache = {}; // "commentaryId/BOOK/chapter" -> data
|
||||
const interlinearCache = {}; // BOOK -> whole-book interlinear data
|
||||
|
||||
export function useStudyResources({ project, selectedChunk, selectedChunkChapterIndex, collapsedSections }) {
|
||||
const [commentarySource, setCommentarySource] = useState('matthew-henry');
|
||||
const [commentaryData, setCommentaryData] = useState(null); // { content: [...] } for current chapter
|
||||
const [commentaryLoading, setCommentaryLoading] = useState(false);
|
||||
const [commentaryError, setCommentaryError] = useState('');
|
||||
const [interlinearData, setInterlinearData] = useState(null);
|
||||
const [interlinearLoading, setInterlinearLoading] = useState(false);
|
||||
const [interlinearError, setInterlinearError] = useState('');
|
||||
|
||||
const loadInterlinearChapter = async (bookAbbrev, chapterNumber) => {
|
||||
if (interlinearCache[bookAbbrev]) return interlinearCache[bookAbbrev];
|
||||
const res = await fetch(`/interlinear/${bookAbbrev}.json`);
|
||||
if (!res.ok) throw new Error('No interlinear data for this book.');
|
||||
const data = await res.json();
|
||||
interlinearCache[bookAbbrev] = data;
|
||||
return data;
|
||||
};
|
||||
|
||||
const loadCommentaryChapter = async (commentaryId, bookAbbrev, chapterNumber) => {
|
||||
const cacheKey = `${commentaryId}/${bookAbbrev}/${chapterNumber}`;
|
||||
if (commentaryCache[cacheKey]) return commentaryCache[cacheKey];
|
||||
const res = await fetch(`https://bible.helloao.org/api/c/${commentaryId}/${bookAbbrev}/${chapterNumber}.json`);
|
||||
const contentType = res.headers.get('content-type') || '';
|
||||
if (!res.ok || !contentType.includes('json')) throw new Error(`No ${commentaryId} commentary for this chapter.`);
|
||||
const data = await res.json();
|
||||
commentaryCache[cacheKey] = data;
|
||||
return data;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (collapsedSections.commentary) return;
|
||||
if (selectedChunkChapterIndex < 0) return;
|
||||
const chapter = project.chapters[selectedChunkChapterIndex];
|
||||
setCommentaryLoading(true);
|
||||
setCommentaryError('');
|
||||
loadCommentaryChapter(commentarySource, chapter.bookAbbrev, chapter.chapter)
|
||||
.then((data) => setCommentaryData(data))
|
||||
.catch((err) => {
|
||||
setCommentaryData(null);
|
||||
setCommentaryError(err.message);
|
||||
})
|
||||
.finally(() => setCommentaryLoading(false));
|
||||
}, [commentarySource, selectedChunk?.id, selectedChunkChapterIndex, collapsedSections.commentary]);
|
||||
|
||||
useEffect(() => {
|
||||
if (collapsedSections.interlinear) return;
|
||||
if (selectedChunkChapterIndex < 0) return;
|
||||
const chapter = project.chapters[selectedChunkChapterIndex];
|
||||
setInterlinearLoading(true);
|
||||
setInterlinearError('');
|
||||
loadInterlinearChapter(chapter.bookAbbrev, chapter.chapter)
|
||||
.then((data) => setInterlinearData(data?.[String(chapter.chapter)] ?? null))
|
||||
.catch((err) => {
|
||||
setInterlinearData(null);
|
||||
setInterlinearError(err.message);
|
||||
})
|
||||
.finally(() => setInterlinearLoading(false));
|
||||
}, [selectedChunk?.id, selectedChunkChapterIndex, collapsedSections.interlinear]);
|
||||
|
||||
return {
|
||||
commentarySource, setCommentarySource,
|
||||
commentaryData,
|
||||
commentaryLoading,
|
||||
commentaryError,
|
||||
interlinearData,
|
||||
interlinearLoading,
|
||||
interlinearError,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user