Hover tooltips, clickable cross-references, easier highlighting

- Tooltips on hover and keyboard focus across the app: icon-only buttons show
  their label, existing titles show in the app's style without the browser's
  delay, and the reader's main buttons show shortcut hints ("Search (/)").
  Touch screens are unaffected.
- Reader: cross-references (under verses and in the Cross-refs tab) open the
  passage's text in the side panel without leaving the chapter; "Open chapter"
  goes there when wanted
- Reader: the verse panel now has a Highlight row of colours for one-tap
  whole-verse highlighting (tap again to remove, Clear for all, with Undo);
  selecting words still highlights just those
- Fix: a cross-reference preview was cleared when React ran the panel's
  effect twice (development builds)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-01 16:31:27 -04:00
1 parent bb80623c45
commit 7f0256be06
9 files changed
+394 -28

No files matched your search

+18
View File
@@ -106,6 +106,7 @@ export default function ReaderPage() {
const [chromeVisible, setChromeVisible] = useState(true);
const [readerDrawMode, setReaderDrawMode] = useState(false);
const [selectionPicker, setSelectionPicker] = useState(null);
const [xrefRequest, setXrefRequest] = useState(null); // a cross-reference tapped in the text: { verse, ref, n }
const swipeTouchRef = useRef(null);
const readerPageInkStrokes = readerInkByPage?.[`${readerBookAbbrev}_${readerChapter}`] ?? NO_STROKES;
@@ -298,6 +299,18 @@ export default function ReaderPage() {
removeTextHighlight(id);
if (gone) toast('Highlight removed', { action: { label: 'Undo', onClick: () => restoreTextHighlight(gone) } });
};
// Highlighting a whole verse from the verse panel: tapping the same colour again takes it off.
const highlightVerse = (color) => {
if (!selectedVerse) return;
const existing = verseHighlights.find((h) => h.color === color && h.startOffset === 0 && h.endOffset >= selectedVerse.text.length);
if (existing) { removeHighlight(existing.id); return; }
addTextHighlight({ book: readerBookAbbrev, chapter: readerChapter, verse: selectedVerse.number, startOffset: 0, endOffset: selectedVerse.text.length, color, text: selectedVerse.text });
};
const clearVerseHighlights = () => {
const gone = verseHighlights;
gone.forEach((h) => removeTextHighlight(h.id));
if (gone.length) toast(`Removed ${gone.length} highlight${gone.length === 1 ? '' : 's'}`, { action: { label: 'Undo', onClick: () => gone.forEach((h) => restoreTextHighlight(h)) } });
};
const removeBookmark = (key, color) => {
toggleReaderBookmark(key);
toast('Bookmark removed', { action: { label: 'Undo', onClick: () => restoreReaderBookmark?.(key, color) } });
@@ -387,6 +400,7 @@ export default function ReaderPage() {
}).sort((a, b) => a.bookIndex - b.bookIndex || a.chapter - b.chapter || a.verse - b.verse || a.startOffset - b.startOffset);
const selectedVerse = readerSelectedVerse != null ? readerVerses.find((v) => v.number === readerSelectedVerse) : null;
const verseHighlights = selectedVerse ? (readerTextHighlights || []).filter((h) => h.book === readerBookAbbrev && h.chapter === readerChapter && h.verse === selectedVerse.number) : [];
const selectedKey = selectedVerse ? `${readerBookAbbrev}-${readerChapter}-${selectedVerse.number}` : null;
const title = `${readerBook?.name ?? ''} ${readerChapter} · ${readerTranslation}`;
const audio = {
@@ -459,6 +473,7 @@ export default function ReaderPage() {
searchLower={chapterSearch}
crossRefs={readerCrossRefs}
showCrossRefs={readerShowCrossRefs}
onOpenCrossRef={(verse, ref) => { setReaderSelectedVerse(verse); setXrefRequest((r) => ({ verse, ref, n: (r?.n ?? 0) + 1 })); }}
onSelectionEnd={handleSelectionEnd}
footer={(
<PlanFooter
@@ -528,6 +543,9 @@ export default function ReaderPage() {
onCycleColor={() => cycleBookmarkColor(selectedKey)}
onJump={jump}
onClose={() => setReaderSelectedVerse(null)}
request={xrefRequest}
verseHighlights={verseHighlights} colorLabels={readerColorLabels ?? {}}
onHighlightVerse={highlightVerse} onClearVerseHighlights={clearVerseHighlights}
/>
)}