Redesign the study side: Outline, Study, Script and Export stages
Build and Push Docker Image / build-and-push (push) Successful in 4m15s

Studies now move through a stage bar in the reader's themes. Outline splits
chunks by clicking between verses (join merges notes) and suggests chunks from
section headings. Study is three panes: outline, passage with an Observe /
Interpret / Apply accordion, and commentary / word study / cross-refs / people
& places for the chunk; original words are added by clicking them in the
interlinear. Script drafts an outline from notes; Export holds files and the
share link. Home, Import and the Pronunciation guide are restyled. Study
projects are BSB only.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-09-29 10:13:46 -04:00
1 parent b1bc1c8ea4
commit 31abee0695
31 files changed
+1911 -2460

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
// Cross-references for the chunk: typed, suggested from the Open Cross Ref
// dataset, and a preview of each reference's verse text.
import { useState } from 'react';
function RefChip({ label, onRemove, loadVerseText }) {
const [text, setText] = useState(null);
const [open, setOpen] = useState(false);
const toggle = () => {
setOpen((o) => !o);
if (text == null) loadVerseText(label).then((t) => setText(t || 'Verse text unavailable.')).catch(() => setText('Verse text unavailable.'));
};
return (
<div className="rounded-xl border border-[var(--r-line)]">
<div className="flex items-center justify-between gap-2 px-3 py-1.5">
<button type="button" onClick={toggle} aria-expanded={open} className="flex-1 text-left text-sm">{label}</button>
<button type="button" onClick={onRemove} aria-label={`Remove ${label}`} className="text-[var(--r-sub)] hover:text-red-600">×</button>
</div>
{open && <p className="border-t border-[var(--r-line)] px-3 py-2 font-serif text-[13px] leading-relaxed text-[var(--r-sub)]">{text ?? 'Loading…'}</p>}
</div>
);
}
export default function CrossRefsTab({ crossRefs, chunk }) {
const { crossRefInput, setCrossRefInput, suggestingCrossRefs, crossRefSuggestions, addCrossRef, removeCrossRef,
suggestCrossRefsForChunk, addSuggestedCrossRef, loadVerseText } = crossRefs;
const refs = chunk.crossReferences ?? [];
return (
<div>
<div className="flex gap-2">
<input value={crossRefInput} onChange={(e) => setCrossRefInput(e.target.value)} placeholder="e.g. John 1:1 or Rom 3:23"
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addCrossRef(chunk.id); } }}
className="min-w-0 flex-1 rounded-lg border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-1.5 text-sm text-[var(--r-text)] focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]" />
<button type="button" onClick={() => addCrossRef(chunk.id)} className="rounded-lg bg-[var(--r-text)] px-3 py-1.5 text-sm text-[var(--r-bg)]">Add</button>
</div>
<button type="button" onClick={() => suggestCrossRefsForChunk(chunk.id)} disabled={suggestingCrossRefs}
className="mt-2 rounded-lg border border-[var(--r-line)] px-3 py-1.5 text-sm hover:bg-black/5 disabled:opacity-50">
{suggestingCrossRefs ? 'Suggesting…' : 'Suggest from passage'}
</button>
{crossRefSuggestions.length > 0 && (
<div className="mt-3 flex flex-wrap gap-1.5">
{crossRefSuggestions.map((ref) => (
<button key={ref} type="button" onClick={() => addSuggestedCrossRef(chunk.id, ref)}
className="rounded-full border border-[var(--r-accent)] px-2.5 py-0.5 text-xs text-[var(--r-accent)] hover:bg-black/5">+ {ref}</button>
))}
</div>
)}
<div className="mt-3 space-y-1.5">
{refs.map((ref) => <RefChip key={ref} label={ref} onRemove={() => removeCrossRef(chunk.id, ref)} loadVerseText={loadVerseText} />)}
{refs.length === 0 && <p className="text-sm text-[var(--r-sub)]">No cross-references yet.</p>}
</div>
</div>
);
}