64 lines
3.4 KiB
React
64 lines
3.4 KiB
React
// 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>
|
||
);
|
||
}
|