Files
study-bible-project/src/components/reader/ReaderText.jsx
T
nmemmertandClaude Sonnet 5.5 b1bc1c8ea4
Build and Push Docker Image / build-and-push (push) Failing after 19s
Reading plans: integrated into the Reader, redesigned, with per-plan translation
Reader: plan strip with a dot per chapter, plan sheet (day picker, chapter
ticks, translation, streak, other plans), end-of-day card that steps through
the day and rolls into the next, and chapter-level auto-marking.
Each plan remembers its own translation (BSB, ESV, NLT, CSB, NASB, NET) and the
Reader follows it while a plan is open. Plans home redesigned in the reader's
themes with a Continue card, in-progress plans, a filterable catalog, and
sheets for details and the custom-plan builder.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-29 09:47:51 -04:00

82 lines
3.3 KiB
React

// The page itself: chapter heading, translation notice, and verses set in a
// serif face. Tap a verse to open the verse sheet; select text to highlight.
import { formatCrossRef } from '../../utils/wordStudy.js';
export default function ReaderText({
title, translation, notice, verses, loading, error, layout, fontSize, wide,
bookmarks, bookKey, chapter, selectedVerse, onSelectVerse, renderVerseText, searchLower,
crossRefs, showCrossRefs, onSelectionEnd, footer,
}) {
const handleTap = (n) => {
const sel = window.getSelection();
if (sel && !sel.isCollapsed && sel.toString().trim()) return; // user is selecting text, not tapping
onSelectVerse(selectedVerse === n ? null : n);
};
const verseProps = (v) => {
const bm = bookmarks[`${bookKey}-${chapter}-${v.number}`];
const selected = selectedVerse === v.number;
return {
id: `reader-verse-${v.number}`,
'data-verse-num': v.number,
onClick: () => handleTap(v.number),
className: 'cursor-pointer rounded-sm transition-colors',
style: selected
? { backgroundColor: 'var(--r-hl)' }
: bm ? { backgroundColor: `${bm}66`, boxShadow: `0 0 0 2px ${bm}66` } : undefined,
};
};
const chips = (v) => showCrossRefs && crossRefs?.[v.number]?.length > 0 && (
<span className="ml-1 inline-flex flex-wrap gap-1 align-baseline font-sans">
{crossRefs[v.number].slice(0, 4).map((ref, i) => (
<span key={i} className="rounded-full border border-[var(--r-line)] px-1.5 py-px text-[10px] text-[var(--r-sub)]">{formatCrossRef(ref)}</span>
))}
</span>
);
const num = (n) => <sup className="mr-1 select-none font-sans text-[0.6em] text-[var(--r-sub)]">{n}</sup>;
return (
<article
className={`mx-auto px-5 pb-32 pt-6 text-[var(--r-text)] ${wide ? 'max-w-5xl' : 'max-w-2xl'}`}
onPointerUp={onSelectionEnd}
>
<header className="mb-5 text-center">
<h1 className="font-serif text-3xl font-normal tracking-tight">{title}</h1>
<p className="mt-1 font-sans text-xs text-[var(--r-sub)]">{translation}</p>
</header>
{loading && <p className="text-center font-sans text-sm text-[var(--r-sub)]">Loading…</p>}
{error && <p className="text-center font-sans text-sm text-red-600">{error}</p>}
{!loading && !error && (
<div
className={`font-serif ${wide ? 'md:columns-2 md:gap-x-12' : ''}`}
style={{ fontSize: `${1.125 * fontSize}rem`, lineHeight: 1.85 }}
>
{layout === 'flow' ? (
<p className="text-pretty">
{verses.map((v) => (
<span key={v.number} {...verseProps(v)}>
{num(v.number)}{renderVerseText(v.number, v.text, searchLower)}{chips(v)}{' '}
</span>
))}
</p>
) : (
verses.map((v) => (
<p key={v.number} className="mb-3 break-inside-avoid">
<span {...verseProps(v)}>{num(v.number)}{renderVerseText(v.number, v.text, searchLower)}{chips(v)}</span>
</p>
))
)}
</div>
)}
{!loading && !error && footer}
{notice && !loading && <p className="mt-10 border-t border-[var(--r-line)] pt-3 font-sans text-[11px] leading-snug text-[var(--r-sub)]">{notice}</p>}
</article>
);
}