diff --git a/src/components/ShortcutsHelp.jsx b/src/components/ShortcutsHelp.jsx index f315b2b..93e4c6f 100644 --- a/src/components/ShortcutsHelp.jsx +++ b/src/components/ShortcutsHelp.jsx @@ -15,6 +15,7 @@ const BY_PAGE = { ['G', 'Pick a book and chapter'], ['B', 'Bookmark the selected verse'], ['C', 'Compare translations'], + ['H', 'Your highlights'], [',', 'Reading settings'], ['F', 'Hide or show the controls'], ], diff --git a/src/components/reader/MarksSheet.jsx b/src/components/reader/MarksSheet.jsx index d609394..7ae4ba1 100644 --- a/src/components/reader/MarksSheet.jsx +++ b/src/components/reader/MarksSheet.jsx @@ -1,6 +1,6 @@ // Everything you have marked in the Bible: highlights (grouped and filtered by colour, each colour // named by you — "Promises", "Commands") and bookmarks. Opens from the bookmark button in the top bar. -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import Sheet from './Sheet.jsx'; import { CloseIcon } from './icons.jsx'; import { COLOR_NAMES, COLOR_SUGGESTIONS, HIGHLIGHT_COLORS, colorTitle } from '../../utils/highlightColors.js'; @@ -8,9 +8,11 @@ import { COLOR_NAMES, COLOR_SUGGESTIONS, HIGHLIGHT_COLORS, colorTitle } from '.. const tabBtn = (active) => `rounded-full border px-3 py-1 text-xs font-medium ${active ? 'border-[var(--r-text)] bg-[var(--r-text)] text-[var(--r-bg)]' : 'border-[var(--r-line)] text-[var(--r-sub)] hover:bg-black/5'}`; export default function MarksSheet({ - open, onClose, bookmarks, highlights, colorLabels, onSetLabel, onJump, onRemoveBookmark, onRemoveHighlight, + open, onClose, initialTab = 'highlights', bookmarks, highlights, colorLabels, onSetLabel, onJump, onRemoveBookmark, onRemoveHighlight, }) { - const [tab, setTab] = useState('highlights'); + const [tab, setTab] = useState(initialTab); + // Opening from the Highlights button shows highlights; from the Bookmarks button, bookmarks + useEffect(() => { if (open) setTab(initialTab); }, [open, initialTab]); const [filter, setFilter] = useState(null); // a highlight colour, or null for all const [editing, setEditing] = useState(false); diff --git a/src/components/reader/ReaderBars.jsx b/src/components/reader/ReaderBars.jsx index ec02543..f4741ed 100644 --- a/src/components/reader/ReaderBars.jsx +++ b/src/components/reader/ReaderBars.jsx @@ -1,11 +1,11 @@ // Top and bottom chrome for the reader. Everything stays one tap away; focus // mode hides both. -import { ChevronLeft, ChevronRight, ChevronDown, SearchIcon, LinkIcon, PenIcon, HeadphonesIcon, FocusIcon, CheckIcon, CompareIcon } from './icons.jsx'; +import { ChevronLeft, ChevronRight, ChevronDown, SearchIcon, LinkIcon, PenIcon, HeadphonesIcon, FocusIcon, CheckIcon, CompareIcon, HighlighterIcon } from './icons.jsx'; import { BookmarkIcon } from '../../utils/bibleUtils.jsx'; const iconBtn = 'flex h-9 w-9 items-center justify-center rounded-lg text-[var(--r-sub)] transition hover:bg-black/5 disabled:opacity-30 disabled:hover:bg-transparent'; -export function TopBar({ title, onBack, onOpenNavigator, onSearch, onBookmarks, bookmarkCount, onCompare, compareActive, onSettings, onDoneDrawing }) { +export function TopBar({ title, onBack, onOpenNavigator, onSearch, onBookmarks, bookmarkCount, onHighlights, highlightCount, onCompare, compareActive, onSettings, onDoneDrawing }) { return (
@@ -22,7 +22,10 @@ export function TopBar({ title, onBack, onOpenNavigator, onSearch, onBookmarks, ) : ( <> - + diff --git a/src/components/reader/icons.jsx b/src/components/reader/icons.jsx index 59523f5..aef8b02 100644 --- a/src/components/reader/icons.jsx +++ b/src/components/reader/icons.jsx @@ -12,3 +12,4 @@ export const HeadphonesIcon = () => ; export const CheckIcon = () => ; export const CompareIcon = () => ; +export const HighlighterIcon = () => ; diff --git a/src/pages/ReaderPage.jsx b/src/pages/ReaderPage.jsx index db8994c..db577cb 100644 --- a/src/pages/ReaderPage.jsx +++ b/src/pages/ReaderPage.jsx @@ -102,7 +102,7 @@ export default function ReaderPage() { const parallelChapter = useParallelChapter(parallelId, readerBookAbbrev, readerChapter); const parallelSource = READER_TRANSLATIONS.find((t) => t.id === parallelId); - const [sheet, setSheet] = useState(null); // null | 'nav' | 'settings' | 'search' | 'bookmarks' | 'audio' + const [sheet, setSheet] = useState(null); // null | 'nav' | 'settings' | 'search' | 'bookmarks' | 'highlights' | 'audio' const [chromeVisible, setChromeVisible] = useState(true); const [readerDrawMode, setReaderDrawMode] = useState(false); const [selectionPicker, setSelectionPicker] = useState(null); @@ -275,6 +275,7 @@ export default function ReaderPage() { else if (e.key === 'g' || e.key === 'G') { e.preventDefault(); openSheet('nav'); } else if (e.key === ',') { e.preventDefault(); openSheet('settings'); } else if (e.key === 'c' || e.key === 'C') { e.preventDefault(); openSheet('compare'); } + else if (e.key === 'h' || e.key === 'H') { e.preventDefault(); openSheet('highlights'); } else if (e.key === 'f' || e.key === 'F') setChromeVisible((v) => !v); else if ((e.key === 'b' || e.key === 'B') && selectedKey) { readerBookmarks[selectedKey] ? removeBookmark(selectedKey, readerBookmarks[selectedKey]) : toggleReaderBookmark(selectedKey); @@ -420,7 +421,9 @@ export default function ReaderPage() { onOpenNavigator={() => openSheet('nav')} onSearch={() => openSheet('search')} onBookmarks={() => openSheet('bookmarks')} - bookmarkCount={bookmarkEntries.length + highlightEntries.length} + bookmarkCount={bookmarkEntries.length} + onHighlights={() => openSheet('highlights')} + highlightCount={highlightEntries.length} onCompare={() => openSheet('compare')} compareActive={!!parallelId} onSettings={() => openSheet('settings')} onDoneDrawing={readerDrawMode ? () => setReaderDrawMode(false) : undefined} @@ -596,7 +599,8 @@ export default function ReaderPage() { onJump={jump} /> { + const saved = { ...ctx }; + Object.assign(ctx, { + readerTextHighlights: [{ id: 'h1', book: 'JHN', chapter: 3, verse: 2, startOffset: 0, endOffset: 4, color: '#bbf7d0', text: 'This' }], + readerBookmarks: { 'JHN-3-1': '#fef08a' }, + }); + render(); + const highlights = screen.getByLabelText('Highlights'); + expect(highlights.nextElementSibling).toBe(screen.getByLabelText('Bookmarks')); // beside the bookmarks button + fireEvent.click(highlights); + expect(screen.getByRole('tab', { name: /highlights \(1\)/i })).toHaveAttribute('aria-selected', 'true'); + expect(screen.getByText(/“This”/)).toBeTruthy(); + fireEvent.click(screen.getByLabelText('Close')); + fireEvent.click(screen.getByLabelText('Bookmarks')); + expect(screen.getByRole('tab', { name: /bookmarks \(1\)/i })).toHaveAttribute('aria-selected', 'true'); + expect(screen.getByText('John 3:1')).toBeTruthy(); + Object.assign(ctx, saved); +}); + +test('the H key opens the highlights', () => { + render(); + fireEvent.keyDown(window, { key: 'h' }); + expect(screen.getByRole('dialog', { name: /bookmarks & highlights/i })).toBeTruthy(); +}); + test('tapping a verse selects it', () => { render(); fireEvent.click(screen.getByText(/This man came to Jesus/));