// The single home for per-verse tools: original words, cross-references,
// commentary, and people & places, plus bookmark / copy.
import { useEffect, useRef, useState } from 'react';
import Sheet from './Sheet.jsx';
import { CommentaryTab, PeoplePlacesTab } from '../StudyPanel.jsx';
import { BookmarkIcon, CopyIcon } from '../../utils/bibleUtils.jsx';
import { formatCrossRef, speakOriginalWord } from '../../utils/wordStudy.js';
import { READER_TRANSLATIONS, fetchChapter } from '../../utils/bibleText.js';
import { diffVerses, differenceLabel } from '../../utils/textDiff.js';
import { DiffText } from './DiffText.jsx';
import { HIGHLIGHT_COLORS, colorTitle } from '../../utils/highlightColors.js';
import { isWholeVerse } from '../../utils/highlightSpans.js';
import { useIqAvailable } from '../../hooks/useIq.js';
import MarkNote from './MarkNote.jsx';
import VerseShareImage from '../VerseShareImage.jsx';
import IqLexicon from '../iq/IqLexicon.jsx';
import IqStudyAids from '../iq/IqStudyAids.jsx';
const BASE_TABS = [['commentary', 'Commentary'], ['compare', 'Compare'], ['original', 'Original'], ['refs', 'Cross-refs'], ['people', 'People & places']];
function OriginalTab({ words }) {
const iq = useIqAvailable();
const [lookup, setLookup] = useState(null); // the Strong's number open in the lexicon panel
if (!words) return
);
}
const sameRef = (a, b) => a && b && a.book === b.book && a.chapter === b.chapter && a.verse === b.verse && (a.endVerse ?? a.verse) === (b.endVerse ?? b.verse);
function RefsTab({ refs, loading, active, onPick, preview }) {
if (loading && !refs) return
Loading…
;
if (!refs?.length) return
No cross-references for this verse.
;
return (
<>
{preview}
Tap one to read it here.
{refs.slice(0, 20).map((ref, i) => (
))}
>
);
}
// This verse in every translation, one under the other. Each Bible loads the chapter it is in
// (BSB and NET come from the free source; the rest through the server) and shows this verse.
function CompareTab({ book, chapter, verse, current }) {
const [rows, setRows] = useState({}); // id -> { text } | { error } | undefined while loading
useEffect(() => {
const controller = new AbortController();
setRows({});
READER_TRANSLATIONS.forEach((t) => {
fetchChapter(t.id, book, chapter, { signal: controller.signal })
.then(({ verses }) => {
const text = verses.find((v) => v.number === verse)?.text;
setRows((r) => ({ ...r, [t.id]: text ? { text } : { error: 'No verse here in this translation.' } }));
})
.catch((err) => { if (!controller.signal.aborted) setRows((r) => ({ ...r, [t.id]: { error: err.message || 'Unavailable.' } })); });
});
return () => controller.abort();
}, [book, chapter, verse]);
const mine = rows[current]?.text;
return (
{READER_TRANSLATIONS.map((t) => {
const row = rows[t.id];
// How this wording differs from the Bible being read, word by word
const d = row?.text && mine && t.id !== current ? diffVerses(mine, row.text) : null;
return (
{mine ? <>Marked words aren’t in the {current} wording. > : null}
Copyright notices for each translation appear at the foot of the chapter when you read it.
);
}
export default function VerseSheet({
verse, book, bookName, chapter, translation, words, crossRefs, crossRefsLoading, onLoadCrossRefs,
bookmarkColor, onToggleBookmark, onCycleColor, bookmarkNote = '', onSetBookmarkNote, onSetHighlightNote, studiesHere = [], onOpenStudy, onPray, onOpenMap, onJump, onClose, request,
verseHighlights = [], colorLabels = {}, onHighlightVerse, onClearVerseHighlights, panelRef, resizeHandle,
}) {
const iqOn = useIqAvailable();
const TABS = iqOn ? [...BASE_TABS, ['aids', 'Study aids']] : BASE_TABS;
const [tab, setTab] = useState('commentary');
const [copied, setCopied] = useState(false);
const [imageOpen, setImageOpen] = useState(false);
const [preview, setPreview] = useState(null); // the cross-reference being read in the panel
const appliedRequest = useRef(0);
// Leaving this verse closes the preview; a cross-reference tapped in the text opens it here. (Tracks the
// previous verse so React's double-run of effects in development can't clear a preview it just opened.)
const previousVerse = useRef(null);
useEffect(() => {
const verseChanged = previousVerse.current !== verse?.number;
previousVerse.current = verse?.number;
if (request && request.verse === verse?.number && request.n !== appliedRequest.current) {
appliedRequest.current = request.n;
setTab('refs');
setPreview(request.ref);
} else if (verseChanged) {
setPreview(null);
}
}, [verse?.number, request?.n]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => { if (verse && tab === 'refs') onLoadCrossRefs(); }, [verse?.number, tab]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => { setCopied(false); setImageOpen(false); }, [verse?.number]);
if (!verse) return null;
const copy = () => {
navigator.clipboard?.writeText(`${bookName} ${chapter}:${verse.number} ${translation} — ${verse.text}`);
setCopied(true);
window.setTimeout(() => setCopied(false), 1500);
};
const actionBtn = 'rounded-lg p-1.5 hover:bg-black/5';
return (
{bookmarkColor && (
)}
{onPray && (
)}
>
)}
>
{imageOpen && (
)}
{onHighlightVerse && (
// One-tap highlighting of the whole verse (to mark just some words, select them in the text)
Highlight
{HIGHLIGHT_COLORS.map((color) => {
const on = verseHighlights.some((h) => h.color === color && isWholeVerse(h, verse.text));
return (
);
})}
{verseHighlights.length > 0 && (
)}
Or select words in the text to highlight just those.