Add Insights, Observe-first, compare, handout layouts, richer search, backup nudge
- Commentary and study aids wait for 15 words of your own Observe notes. - Insights page: this week's own work, themes across studies, words you keep studying, what slipped in recalls, open follow-ups. - Recall can record what slipped; session notes (teach it back, who came, group notes, follow-ups); "Pray about this" from Apply. - Compare any two passages side by side. - Handout layouts: standard, questions only, large print, note-taking. - Search understands "in <book>", "last week/month/year" and a year. - Home shows the last backup age and nudges after two weeks. - Phone: the stage bar scrolls instead of clipping Script and Export. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ddc4e1ff6
commit
d8e522dd06
24 files changed
+540
-28
No files matched your search
@@ -0,0 +1,61 @@
|
||||
// Compare two passages side by side — your notes and the verses — for parallels (the Gospels), repeated themes,
|
||||
// or the same chapter studied twice. Pick any chunk from any of your studies to set beside the one you're on.
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useApp } from '../../context/AppContext.js';
|
||||
import { loadProjectById } from '../../utils/projectStorage.js';
|
||||
import { formatChunkReference, getChunkVerseEntries } from '../../utils/bibleUtils.jsx';
|
||||
|
||||
const FIELDS = [['observation', 'Observe'], ['interpretation', 'Interpret'], ['application', 'Apply']];
|
||||
|
||||
// Every chunk in your studies, as a pick-list entry that remembers where it sits.
|
||||
function choices(projectIndex, currentProject) {
|
||||
const projects = projectIndex.map((e) => (e.id === currentProject?.id ? currentProject : loadProjectById(e.id))).filter(Boolean);
|
||||
return projects.flatMap((project) => project.chapters.flatMap((chapter, chapterIndex) => chapter.chunks.map((chunk) => ({
|
||||
key: `${project.id}:${chunk.id}`, project, chapterIndex, chunk,
|
||||
label: `${formatChunkReference(project, chapterIndex, chunk, '–')} · ${project.title}`,
|
||||
}))));
|
||||
}
|
||||
|
||||
function Column({ entry }) {
|
||||
if (!entry) return <p className="p-4 text-sm text-[var(--r-sub)]">Pick a passage to compare.</p>;
|
||||
const verses = getChunkVerseEntries(entry.project, entry.chapterIndex, entry.chunk);
|
||||
return (
|
||||
<div className="min-w-0 space-y-3 p-4">
|
||||
<h3 className="font-medium">{entry.label}</h3>
|
||||
<div className="max-h-48 space-y-1 overflow-y-auto rounded-xl bg-[var(--r-bar)] p-3 font-serif text-[13px] leading-relaxed">
|
||||
{verses.map((v) => <p key={v.number}><sup className="mr-1 text-[var(--r-accent)]">{v.number}</sup>{v.text}</p>)}
|
||||
</div>
|
||||
{FIELDS.map(([f, label]) => (
|
||||
<div key={f}>
|
||||
<p className="text-[11px] uppercase tracking-wide text-[var(--r-sub)]">{label}</p>
|
||||
<p className="whitespace-pre-line font-serif text-[14px] leading-relaxed">{entry.chunk[f]?.trim() || <span className="text-[var(--r-sub)]">Nothing written.</span>}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CompareSheet({ currentChunkId, onClose }) {
|
||||
const { projectIndex, project } = useApp();
|
||||
const all = useMemo(() => choices(projectIndex, project), [projectIndex, project]);
|
||||
const mine = all.find((c) => c.chunk.id === currentChunkId && c.project.id === project?.id);
|
||||
const [otherKey, setOtherKey] = useState('');
|
||||
const other = all.find((c) => c.key === otherKey);
|
||||
return (
|
||||
<div role="dialog" aria-modal="true" aria-label="Compare passages" className="fixed inset-0 z-[70] flex flex-col bg-[var(--r-bg)] text-[var(--r-text)]">
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-x-3 gap-y-2 border-b border-[var(--r-line)] bg-[var(--r-bar)] px-4 py-2.5">
|
||||
<h2 className="mr-auto text-sm font-medium sm:mr-0">Compare passages</h2>
|
||||
<select aria-label="Compare with" value={otherKey} onChange={(e) => setOtherKey(e.target.value)}
|
||||
className="order-last min-w-0 basis-full rounded-lg border border-[var(--r-line)] bg-[var(--r-bg)] px-2.5 py-1.5 text-sm sm:order-none sm:basis-0 sm:flex-1">
|
||||
<option value="">Choose a passage to set beside this one…</option>
|
||||
{all.filter((c) => c.key !== mine?.key).map((c) => <option key={c.key} value={c.key}>{c.label}</option>)}
|
||||
</select>
|
||||
<button type="button" onClick={onClose} className="rounded-lg border border-[var(--r-line)] px-3 py-1.5 text-sm hover:bg-black/5">Close</button>
|
||||
</div>
|
||||
<div className="grid min-h-0 flex-1 overflow-y-auto md:grid-cols-2 md:divide-x md:divide-[var(--r-line)]">
|
||||
<Column entry={mine} />
|
||||
<Column entry={other} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user