Connect reading plans to study/audio, cache offline audio, add journal export
Build and Push Docker Image / build-and-push (push) Successful in 4m33s

Reading plan days now offer "Study this" (seeds a new chunk-builder project
with the day's full passage and jumps straight into the Study editor) and
"Listen" (starts book-mode audio at the right chapter via a new
handlePlayBookAudioFrom, avoiding a stale-closure bug the reader-state
approach would hit). Fixed the "Listen to BSB Audio" player only rendering
when a study project existed, leaving Listen with nowhere to show up.

The service worker now caches audio.bible.helloao.org (the actual host
serving narrator audio, not just the text/cross-ref API) and no longer
tries to cache 206 Partial Content responses, which the Cache API rejects
and was throwing unhandled rejections for every range-requested audio file.

Added a reading journal export (Settings > My reading journal): gathers
bookmarks, text highlights, and completed plans, fetches their verse text,
and offers a .docx download or a print-to-PDF page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5 committed 2026-09-23 13:41:40 -04:00
1 parent 1dd6aef6a5
commit 787edcbe73
8 files changed
+604 -105

No files matched your search

+56 -1
View File
@@ -8,7 +8,9 @@ import { useReadingPlans } from './hooks/useReadingPlans.js';
import { useSharedView } from './hooks/useSharedView.js';
import HomePage from './pages/HomePage.jsx';
import { getRemoteReadingProgress, saveRemoteProject } from './syncService.js';
import { buildExportHtml, loadProjectIndex, switchStorageUser } from './utils/bibleUtils.jsx';
import {
bookOptions, buildExportHtml, loadProjectIndex, makeId, parseBibleChapter, switchStorageUser,
} from './utils/bibleUtils.jsx';
import { migrateOldStorageKeys, saveProjectToStorage } from './utils/projectStorage.js';
// Less-common pages are split into their own chunks so the first load only
@@ -35,6 +37,7 @@ const App = () => {
const [saveStatus, setSaveStatus] = useState('');
const saveTimerRef = useRef(null);
const [syncStatus, setSyncStatus] = useState(''); // '' | 'syncing' | 'synced' | 'error'
const [studyBuildBusy, setStudyBuildBusy] = useState(false);
const reader = useBibleReader(currentPage);
const audio = useAudio(reader.readerBookAbbrev, reader.readerChapter);
const readingPlans = useReadingPlans();
@@ -123,6 +126,55 @@ const App = () => {
setCurrentPage('reader');
};
// Starts book-mode audio playback at a specific plan day's chapter, then
// shows the Home audio player. Uses handlePlayBookAudioFrom (not the reader
// player) so this works even without opening the reader first.
const listenReadingPlanDay = (bookAbbrev, chapter) => {
audio.handlePlayBookAudioFrom(bookAbbrev, chapter);
goHome();
};
// Builds a new chunk-builder project seeded with a reading-plan day's
// passage — one whole-chapter chunk per chapter in the day — and jumps
// straight into the Study page, skipping the manual chapter/chunk setup.
const studyReadingPlanDay = async (dayRefs, title) => {
setStudyBuildBusy(true);
try {
const chapters = await Promise.all(dayRefs.map(async ({ book, chapter }) => {
const res = await fetch(`https://bible.helloao.org/api/BSB/${book}/${chapter}.json`);
if (!res.ok) throw new Error(`Failed to load ${book} ${chapter}`);
const data = await res.json();
const verses = parseBibleChapter(data);
const bookName = bookOptions.find((b) => b.abbrev === book)?.name ?? book;
const chunk = {
id: makeId(),
startVerse: verses[0]?.number ?? 1,
endVerse: verses.at(-1)?.number ?? 1,
spilloverEndVerse: null,
observation: '', interpretation: '', application: '',
crossReferences: [], greekWords: [], generalNotes: '',
episodeNumber: '', episodeTitle: '', finalScript: '', tags: [],
};
return { book: bookName, bookAbbrev: book, chapter: String(chapter), verses, chunks: [chunk] };
}));
const newProject = {
id: makeId(),
title: title || 'Reading Plan Study',
translation: 'BSB',
lastEdited: Date.now(),
selectedChunkId: chapters[0]?.chunks[0]?.id ?? null,
chapters,
};
setProject(newProject);
setCurrentPage('study');
} catch (err) {
console.error('studyReadingPlanDay error:', err);
window.alert("Couldn't load today's passage. Please try again.");
} finally {
setStudyBuildBusy(false);
}
};
const openNewProject = () => {
setProject(null);
setCurrentPage('setup');
@@ -172,6 +224,9 @@ const App = () => {
openBibleReader,
openReadingPlans,
readReadingPlanDay,
listenReadingPlanDay,
studyReadingPlanDay,
studyBuildBusy,
openImportProject,
openNewProject,
// Audio (book player on Home + chapter player in the Reader)