Build and Push Docker Image / build-and-push (push) Failing after 19s
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>
82 lines
3.3 KiB
React
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>
|
|
);
|
|
}
|