Link Scripture references in notes and scripts to the verse (chips with a BSB preview)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-14 15:23:09 -04:00
1 parent f05d38ecde
commit 79d9b091f9
8 files changed
+220 -1

No files matched your search

+63
View File
@@ -0,0 +1,63 @@
// Under a note: each Scripture reference written in it (Rom 8:28, Psalm 23…) as a chip. Click one to read the verse
// (BSB) right there, or open it in the Bible reader. The text itself is never changed.
import { useEffect, useMemo, useState } from 'react';
import { useApp } from '../context/AppContext.js';
import { fetchChapter } from '../utils/bibleText.js';
import { labelFor, uniqueReferences } from '../utils/scriptureRefs.js';
const MAX_VERSES = 12;
const CHAPTER_PREVIEW = 6;
function Preview({ r }) {
const { jumpToReaderVerse, openBibleReader } = useApp();
const [state, setState] = useState({ loading: true });
useEffect(() => {
let live = true;
setState({ loading: true });
fetchChapter('BSB', r.book, r.chapter)
.then(({ verses }) => {
const from = r.verse ?? 1;
const to = r.verse ? (r.endVerse ?? r.verse) : from + CHAPTER_PREVIEW - 1;
const picked = verses.filter((v) => v.number >= from && v.number <= to).slice(0, MAX_VERSES);
if (live) setState({ verses: picked, more: !r.verse && verses.length > picked.length });
})
.catch((err) => { if (live) setState({ error: err.message || 'Could not load the verse.' }); });
return () => { live = false; };
}, [r.book, r.chapter, r.verse, r.endVerse]);
return (
<div role="region" aria-label={`${labelFor(r)} preview`} className="mt-1.5 rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] p-3 text-[13px]">
{state.loading && <p className="text-xs text-[var(--r-sub)]">Loading…</p>}
{state.error && <p className="text-xs text-[var(--r-sub)]">{state.error} You can still open it in the Bible.</p>}
{state.verses && (state.verses.length === 0
? <p className="text-xs text-[var(--r-sub)]">That verse isn’t in the BSB.</p>
: <div className="space-y-1 font-serif leading-relaxed">{state.verses.map((v) => <p key={v.number}><sup className="mr-1 font-sans text-[var(--r-accent)]">{v.number}</sup>{v.text}</p>)}{state.more && <p className="text-xs text-[var(--r-sub)]">…</p>}</div>)}
<div className="mt-2 flex items-center gap-3 font-sans text-xs">
<button type="button" onClick={() => { jumpToReaderVerse(r.book, r.chapter, r.verse ?? 1); openBibleReader(); }} className="text-[var(--r-accent)] hover:underline">Read in the Bible</button>
<span className="text-[var(--r-sub)]">Berean Standard Bible</span>
</div>
</div>
);
}
export default function ScriptureRefs({ text }) {
const refs = useMemo(() => uniqueReferences(text), [text]);
const [open, setOpen] = useState(null);
if (refs.length === 0) return null;
const openRef = refs.find((r) => labelFor(r) === open);
return (
<div className="mt-1.5" aria-label="References in this note">
<p className="flex flex-wrap items-center gap-1.5 text-xs">
<span className="text-[var(--r-sub)]">Verses mentioned:</span>
{refs.map((r) => {
const label = labelFor(r);
return (
<button key={label} type="button" aria-pressed={open === label} onClick={() => setOpen(open === label ? null : label)}
className={`rounded-full border px-2 py-0.5 ${open === label ? 'border-[var(--r-accent)] text-[var(--r-text)]' : 'border-[var(--r-line)] text-[var(--r-accent)] hover:bg-black/5'}`}>{label}</button>
);
})}
</p>
{openRef && <Preview r={openRef} />}
</div>
);
}