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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user