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

+57
View File
@@ -9,6 +9,7 @@ import {
updateProfile,
updateReminderSettings,
} from '../syncService.js';
import { exportJournalDocx, printJournalPdf } from '../utils/journalExport.js';
export default function SettingsPage() {
const {
@@ -38,6 +39,8 @@ export default function SettingsPage() {
const [reminderBusy, setReminderBusy] = useState(false);
const [reminderError, setReminderError] = useState('');
const [reminderSaved, setReminderSaved] = useState(false);
const [journalBusy, setJournalBusy] = useState(''); // '' | 'docx' | 'pdf'
const [journalError, setJournalError] = useState('');
const startSetup = async () => {
setMfaSetupError('');
@@ -132,6 +135,30 @@ export default function SettingsPage() {
window.setTimeout(() => setReminderSaved(false), 2500);
};
const downloadJournalDocx = async () => {
setJournalError('');
setJournalBusy('docx');
try {
await exportJournalDocx();
} catch {
setJournalError("Couldn't build the journal. Please try again.");
} finally {
setJournalBusy('');
}
};
const printJournal = async () => {
setJournalError('');
setJournalBusy('pdf');
try {
await printJournalPdf();
} catch {
setJournalError("Couldn't build the journal. Please try again.");
} finally {
setJournalBusy('');
}
};
return (
<div className="min-h-screen bg-slate-50 text-slate-900">
<header className="border-b border-slate-200 bg-slate-900 text-white shadow-sm">
@@ -242,6 +269,36 @@ export default function SettingsPage() {
{podcastNameSaved && <p className="text-sm text-emerald-600">Saved.</p>}
</section>
<section className="rounded-3xl border border-slate-200 bg-white p-8 shadow-panel space-y-4">
<div>
<h2 className="text-lg font-semibold text-slate-900">My reading journal</h2>
<p className="text-sm text-slate-500">
A document of your bookmarked verses, highlighted passages, and completed reading plans.
Bookmarks and highlights only live on this device (they're not synced), so this reflects
what's saved here.
</p>
</div>
<div className="flex flex-wrap items-center gap-3">
<button
type="button"
onClick={downloadJournalDocx}
disabled={journalBusy !== ''}
className="rounded-md bg-sky-500 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-sky-400 disabled:cursor-not-allowed disabled:bg-slate-300"
>
{journalBusy === 'docx' ? 'Building…' : 'Download as Word (.docx)'}
</button>
<button
type="button"
onClick={printJournal}
disabled={journalBusy !== ''}
className="rounded-md border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50"
>
{journalBusy === 'pdf' ? 'Building…' : 'Print / Save as PDF'}
</button>
</div>
{journalError && <p className="text-sm text-rose-600">{journalError}</p>}
</section>
<section className="rounded-3xl border border-slate-200 bg-white p-8 shadow-panel space-y-4">
<div>
<h2 className="text-lg font-semibold text-slate-900">Reading reminders</h2>