Connect reading plans to study/audio, cache offline audio, add journal export
Build and Push Docker Image / build-and-push (push) Successful in 4m33s
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:
1 parent
1dd6aef6a5
commit
787edcbe73
8 files changed
+604
-105
No files matched your search
+56
-1
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user