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:
nmemmertandClaude Opus 5.5 committed 2026-09-22 14:59:22 -04:00
1 parent 4b14b3fbe6
commit bf02e071fa
8 files changed
+749 -771

No files matched your search

+76
View File
@@ -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,
};
}