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

- 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:
nmemmertandClaude Sonnet 5.5 committed 2026-10-06 10:45:32 -04:00
1 parent 54cb1c217d
commit 207bfcb919
45 files changed
+1669 -75

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
// What is different between a study as it is now and an earlier version of it, in words a person can read.
const FIELD_NAMES = {
observation: 'Observe', interpretation: 'Interpret', application: 'Apply', generalNotes: 'Background', finalScript: 'Script',
discussionQuestions: 'Questions', episodeNumber: 'Session number', episodeTitle: 'Session title',
};
const same = (a, b) => JSON.stringify(a ?? '') === JSON.stringify(b ?? '');
const chunksOf = (project) => {
const out = new Map();
(project?.chapters ?? []).forEach((chapter) => (chapter.chunks ?? []).forEach((chunk) => out.set(chunk.id, { chunk, ref: `${chapter.book} ${chapter.chapter}:${chunk.startVerse}–${chunk.endVerse}` })));
return out;
};
// current vs older -> { titleChanged, changed: [{ ref, fields }], removedSince: [ref], addedSince: [ref], identical }
// "removedSince" = chunks the older version has that the study doesn't now (restoring brings them back);
// "addedSince" = chunks written since (restoring takes them away, though the copy you replace is kept).
export function diffProjects(current, older) {
const now = chunksOf(current);
const then = chunksOf(older);
const changed = [];
const removedSince = [];
const addedSince = [];
for (const [id, { chunk, ref }] of then) {
const mine = now.get(id);
if (!mine) { removedSince.push(ref); continue; }
const fields = Object.keys(FIELD_NAMES).filter((f) => !same(mine.chunk[f], chunk[f])).map((f) => FIELD_NAMES[f]);
if (!same(mine.chunk.tags, chunk.tags)) fields.push('Tags');
if (!same(mine.chunk.crossReferences, chunk.crossReferences)) fields.push('Cross-references');
if (!same(mine.chunk.greekWords, chunk.greekWords)) fields.push('Words');
if (fields.length) changed.push({ ref, fields });
}
for (const [id, { ref }] of now) if (!then.has(id)) addedSince.push(ref);
const titleChanged = (current?.title ?? '') !== (older?.title ?? '');
const outlineChanged = !same(current?.outline, older?.outline);
return { titleChanged, outlineChanged, changed, removedSince, addedSince, identical: !titleChanged && !outlineChanged && !changed.length && !removedSince.length && !addedSince.length };
}
export function describeDiff(diff) {
if (diff.identical) return ['This version is the same as what you have now.'];
const lines = [];
if (diff.titleChanged) lines.push('The title is different.');
for (const c of diff.changed) lines.push(`${c.ref}: ${c.fields.join(', ')} differ${c.fields.length === 1 ? 's' : ''}.`);
if (diff.removedSince.length) lines.push(`Brings back ${diff.removedSince.length} chunk${diff.removedSince.length === 1 ? '' : 's'} deleted since: ${diff.removedSince.join('; ')}.`);
if (diff.addedSince.length) lines.push(`Takes away ${diff.addedSince.length} chunk${diff.addedSince.length === 1 ? '' : 's'} added since: ${diff.addedSince.join('; ')}.`);
if (diff.outlineChanged) lines.push('The whole-study outline is different.');
return lines;
}