Version history, notes on highlights and bookmarks, "you've studied this", prayer journal
Build and Push Docker Image / build-and-push (push) Successful in 5m13s
Build and Push Docker Image / build-and-push (push) Successful in 5m13s
- Earlier versions of a study: the server keeps compressed copies (about one per ten minutes of editing, the latest 30); Export shows what differs and puts one back (the replaced copy is kept) - Notes on highlights and bookmarks: synced, merged without loss, in exports and backups - Reader verse panel lists your studies and chunks that cover the verse, and opens them - Prayer journal: requests, who for, linked verses, days prayed, answers; synced, in backups and the account export; daily prayer reminder through ntfy - TODO.md lists the ideas not built yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
54cb1c217d
commit
207bfcb919
45 files changed
+1669
-75
No files matched your search
+39
-6
@@ -13,12 +13,13 @@ import { useAuth } from './hooks/useAuth.js';
|
||||
import { useBibleReader } from './hooks/useBibleReader.js';
|
||||
import { useProjectLibrary } from './hooks/useProjectLibrary.js';
|
||||
import { useHistoryNav } from './hooks/useHistoryNav.js';
|
||||
import { usePrayers } from './hooks/usePrayers.js';
|
||||
import { useReadingHistory } from './hooks/useReadingHistory.js';
|
||||
import { useReadingPlans } from './hooks/useReadingPlans.js';
|
||||
import { useSharedView } from './hooks/useSharedView.js';
|
||||
import HomePage from './pages/HomePage.jsx';
|
||||
import { themeVars } from './components/reader/readerThemes.js';
|
||||
import { joinStudy, loadRemoteProject, saveRemoteProject } from './syncService.js';
|
||||
import { joinStudy, loadRemoteProject, restoreStudyVersion, saveRemoteProject } from './syncService.js';
|
||||
import { getSyncBase, setSyncBase } from './utils/syncBase.js';
|
||||
import {
|
||||
bookOptions, buildExportHtml, loadProjectIndex, makeId, parseBibleChapter, switchStorageUser,
|
||||
@@ -42,6 +43,7 @@ const ScriptPage = lazy(() => import('./pages/ScriptPage.jsx'));
|
||||
const ExportPage = lazy(() => import('./pages/ExportPage.jsx'));
|
||||
const PronunciationPage = lazy(() => import('./pages/PronunciationPage.jsx'));
|
||||
const ReadingPlansPage = lazy(() => import('./pages/ReadingPlansPage.jsx'));
|
||||
const PrayerPage = lazy(() => import('./pages/PrayerPage.jsx'));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// App component
|
||||
@@ -59,7 +61,7 @@ function savedStartPage() {
|
||||
const AppInner = () => {
|
||||
const { toast, setTheme: setToastTheme } = useToast();
|
||||
const [project, setProject] = useState(null);
|
||||
// 'home' | 'setup' | 'study' | 'outline' | 'script' | 'export' | 'reader' | 'import' | 'pronunciation' | 'settings' | 'admin' | 'reading-plans'
|
||||
// 'home' | 'setup' | 'study' | 'outline' | 'script' | 'export' | 'reader' | 'import' | 'pronunciation' | 'settings' | 'admin' | 'reading-plans' | 'prayer'
|
||||
const [currentPage, setCurrentPage] = useState(savedStartPage);
|
||||
const currentPageRef = useRef(currentPage);
|
||||
currentPageRef.current = currentPage;
|
||||
@@ -75,6 +77,8 @@ const AppInner = () => {
|
||||
const [readerPlan, setReaderPlan] = useState(null);
|
||||
const readerPlanProgress = readerPlan ? readingPlans.planProgress.find((p) => p.id === readerPlan.progressId) ?? null : null;
|
||||
const reader = useBibleReader(currentPage, readerPlanProgress?.translation ?? null);
|
||||
const prayerJournal = usePrayers();
|
||||
const [prayerDraft, setPrayerDraft] = useState(null); // a verse to start a prayer with (from the Reader)
|
||||
const readingHistory = useReadingHistory({ active: currentPage === 'reader', book: reader.readerBookAbbrev, chapter: reader.readerChapter });
|
||||
const audio = useAudio(reader.readerBookAbbrev, reader.readerChapter, reader.readerTranslation);
|
||||
useEffect(() => { if (currentPage !== 'reader') setReaderPlan(null); }, [currentPage]);
|
||||
@@ -104,12 +108,14 @@ const AppInner = () => {
|
||||
reader.pullServerPrefs();
|
||||
// What you've read, merged with this device's.
|
||||
readingHistory.pullReadingHistory();
|
||||
prayerJournal.pullPrayers();
|
||||
},
|
||||
onSignOut: () => {
|
||||
reader.stopMarksSync();
|
||||
reader.stopPrefsSync();
|
||||
reader.stopInkSync();
|
||||
readingHistory.stopReadingHistory();
|
||||
prayerJournal.stopPrayers();
|
||||
readingPlans.stopReadingPlansSync();
|
||||
setProjectIndex(switchStorageUser(null));
|
||||
goStudies();
|
||||
@@ -161,7 +167,7 @@ const AppInner = () => {
|
||||
useEffect(() => {
|
||||
const names = {
|
||||
home: 'My Studies', setup: 'Chunks', study: 'Study', outline: 'Outline', script: 'Script', export: 'Export',
|
||||
import: 'Import', pronunciation: 'Pronunciation Guide', settings: 'Settings', admin: 'Admin', 'reading-plans': 'Reading Plans',
|
||||
import: 'Import', pronunciation: 'Pronunciation Guide', settings: 'Settings', admin: 'Admin', 'reading-plans': 'Reading Plans', prayer: 'Prayer journal',
|
||||
};
|
||||
let page = names[currentPage] ?? '';
|
||||
if (currentPage === 'reader') {
|
||||
@@ -251,11 +257,11 @@ const AppInner = () => {
|
||||
|
||||
const flushPendingSave = () => {
|
||||
const pending = pendingSaveRef.current;
|
||||
if (!pending) return;
|
||||
if (!pending) return Promise.resolve();
|
||||
window.clearTimeout(saveTimerRef.current);
|
||||
pendingSaveRef.current = null;
|
||||
savedSignatureRef.current = { id: pending.project.id, signature: pending.signature };
|
||||
commitSave(pending.project);
|
||||
return commitSave(pending.project);
|
||||
};
|
||||
const flushRef = useRef(flushPendingSave);
|
||||
flushRef.current = flushPendingSave;
|
||||
@@ -305,6 +311,27 @@ const AppInner = () => {
|
||||
toast('Switched to the version from your other device');
|
||||
};
|
||||
|
||||
// Puts an earlier version of the open study back. What you just typed is saved first (so it is in the list too),
|
||||
// then the server swaps the versions, and this device takes the restored one.
|
||||
const restoreEarlierVersion = async (versionId) => {
|
||||
if (!project) return false;
|
||||
await flushPendingSave();
|
||||
const result = await restoreStudyVersion(project.id, versionId);
|
||||
if (!result.ok) return false;
|
||||
window.clearTimeout(saveTimerRef.current);
|
||||
pendingSaveRef.current = null;
|
||||
const restored = result.data;
|
||||
try { saveProjectToStorage(restored); } catch { /* storage full: the account has it */ }
|
||||
unsyncedStudies.remove(restored.id);
|
||||
setSyncBase(restored.id, restored.lastEdited);
|
||||
savedSignatureRef.current = { id: restored.id, signature: signatureOf(restored) };
|
||||
setSyncConflict(null);
|
||||
setProject(restored);
|
||||
setProjectIndex(loadProjectIndex());
|
||||
toast('Earlier version put back. What you had before is still in the list.');
|
||||
return true;
|
||||
};
|
||||
|
||||
// Third way out of a conflict: combine both copies chunk by chunk, then save the result as the new version.
|
||||
const mergeBothVersions = async () => {
|
||||
if (!project || !syncConflict) return;
|
||||
@@ -342,6 +369,9 @@ const AppInner = () => {
|
||||
const openBibleReader = () => setCurrentPage('reader');
|
||||
|
||||
const openReadingPlans = () => setCurrentPage('reading-plans');
|
||||
const openPrayer = () => setCurrentPage('prayer');
|
||||
// "Pray about this verse": opens the journal with the verse filled in
|
||||
const startPrayerFor = (label) => { setPrayerDraft({ label }); setCurrentPage('prayer'); };
|
||||
|
||||
// Jumps straight into a specific chapter and opens the reader (no plan context).
|
||||
const readReadingPlanDay = (bookAbbrev, chapter) => {
|
||||
@@ -477,7 +507,7 @@ const AppInner = () => {
|
||||
saveStatus, setSaveStatus,
|
||||
syncStatus,
|
||||
retrySync,
|
||||
syncConflict, keepMyVersion, useServerVersion, mergeBothVersions,
|
||||
syncConflict, keepMyVersion, useServerVersion, mergeBothVersions, restoreEarlierVersion,
|
||||
// Home
|
||||
projectIndex,
|
||||
...library,
|
||||
@@ -502,6 +532,8 @@ const AppInner = () => {
|
||||
// Reader
|
||||
...reader,
|
||||
readingHistory: readingHistory.readingHistory,
|
||||
...prayerJournal,
|
||||
prayerDraft, clearPrayerDraft: () => setPrayerDraft(null), startPrayerFor, openPrayer,
|
||||
readerPlan, setReaderPlan,
|
||||
readerPlanActive: !!readerPlanProgress,
|
||||
openPlanReading,
|
||||
@@ -594,6 +626,7 @@ function AppRouter() {
|
||||
if (currentPage === 'admin') return <AdminPage />;
|
||||
if (currentPage === 'settings') return <SettingsPage />;
|
||||
if (currentPage === 'reading-plans') return <ReadingPlansPage />;
|
||||
if (currentPage === 'prayer') return <PrayerPage />;
|
||||
if (currentPage === 'reader') return <ReaderPage />;
|
||||
if (currentPage === 'import') return <ImportPage />;
|
||||
if (currentPage === 'study') return <StudyPage />;
|
||||
|
||||
Reference in new issue
Block a user