feat: integrate highlight management into reader app

- Added HighlightSyncManager and highlight state management to BibleReaderApp
- Implemented highlight handlers: add, update color, remove, and sync
- Connected highlight state from BibleReaderApp to VersDetailsPanel
- Updated VersDetailsPanel to pass highlight props to HighlightsTab
- Added auto-sync initialization with 30-second interval
- Prepared for Phase 2.1B API integration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-12 07:04:46 +00:00
parent ec62440b2d
commit ea2a848f73
2 changed files with 139 additions and 7 deletions

View File

@@ -1,13 +1,15 @@
'use client'
import { useState, useEffect } from 'react'
import { useState, useEffect, useRef } from 'react'
import { useLocale } from 'next-intl'
import { Box, Typography, Button } from '@mui/material'
import { BibleChapter, BibleVerse } from '@/types'
import { BibleChapter, BibleVerse, BibleHighlight, HighlightColor } from '@/types'
import { getCachedChapter, cacheChapter } from '@/lib/cache-manager'
import { SearchNavigator } from './search-navigator'
import { ReadingView } from './reading-view'
import { VersDetailsPanel } from './verse-details-panel'
import { ReadingSettings } from './reading-settings'
import { HighlightSyncManager } from '@/lib/highlight-sync-manager'
import { addHighlight, updateHighlight, getHighlightsByVerse, deleteHighlight, getAllHighlights } from '@/lib/highlight-manager'
interface BookInfo {
id: string // UUID
@@ -31,6 +33,8 @@ export function BibleReaderApp() {
const [versionId, setVersionId] = useState<string>('')
const [error, setError] = useState<string | null>(null)
const [booksLoading, setBooksLoading] = useState(true)
const [highlights, setHighlights] = useState<Map<string, BibleHighlight>>(new Map())
const syncManager = useRef<HighlightSyncManager | null>(null)
// Load books on mount or when locale changes
useEffect(() => {
@@ -44,6 +48,24 @@ export function BibleReaderApp() {
}
}, [bookId, chapter, booksLoading, books.length])
// Initialize sync manager on mount
useEffect(() => {
syncManager.current = new HighlightSyncManager()
syncManager.current.init()
syncManager.current.startAutoSync(30000, () => {
performSync()
})
return () => {
syncManager.current?.stopAutoSync()
}
}, [])
// Load all highlights on mount
useEffect(() => {
loadAllHighlights()
}, [])
async function loadBooks() {
setBooksLoading(true)
setError(null)
@@ -168,6 +190,95 @@ export function BibleReaderApp() {
console.log(`Note for verse ${selectedVerse.id}:`, note)
}
async function loadAllHighlights() {
try {
const highlightList = await getAllHighlights()
const map = new Map(highlightList.map(h => [h.verseId, h]))
setHighlights(map)
} catch (error) {
console.error('Failed to load highlights:', error)
}
}
async function handleHighlightVerse(color: HighlightColor = 'yellow') {
if (!selectedVerse) return
const highlight: BibleHighlight = {
id: `h-${selectedVerse.id}-${Date.now()}`,
verseId: selectedVerse.id,
color,
createdAt: Date.now(),
updatedAt: Date.now(),
syncStatus: 'pending'
}
try {
await addHighlight(highlight)
const newMap = new Map(highlights)
newMap.set(selectedVerse.id, highlight)
setHighlights(newMap)
} catch (error) {
console.error('Failed to highlight verse:', error)
}
}
async function handleChangeHighlightColor(color: HighlightColor) {
if (!selectedVerse) return
const existing = highlights.get(selectedVerse.id)
if (existing) {
const updated = {
...existing,
color,
updatedAt: Date.now(),
syncStatus: 'pending' as const
}
try {
await updateHighlight(updated)
const newMap = new Map(highlights)
newMap.set(selectedVerse.id, updated)
setHighlights(newMap)
} catch (error) {
console.error('Failed to update highlight color:', error)
}
}
}
async function handleRemoveHighlight() {
if (!selectedVerse) return
try {
// Find and delete all highlights for this verse
const existing = highlights.get(selectedVerse.id)
if (existing) {
await deleteHighlight(existing.id)
const newMap = new Map(highlights)
newMap.delete(selectedVerse.id)
setHighlights(newMap)
}
} catch (error) {
console.error('Failed to remove highlight:', error)
}
}
async function performSync() {
if (!syncManager.current) return
try {
const pending = await syncManager.current.getPendingSyncItems()
if (pending.length === 0) return
await syncManager.current.markSyncing(pending.map(h => h.id))
// TODO: POST to /api/highlights/bulk in Phase 2.1B
await syncManager.current.markSynced(pending.map(h => h.id))
} catch (error) {
console.error('Sync failed:', error)
// Mark items with error status
}
}
return (
<Box sx={{ display: 'flex', flexDirection: 'column', height: 'auto', overflow: 'hidden' }}>
{/* Header with search */}
@@ -238,6 +349,11 @@ export function BibleReaderApp() {
isBookmarked={selectedVerse ? bookmarks.has(selectedVerse.id) : false}
onToggleBookmark={handleToggleBookmark}
onAddNote={handleAddNote}
isHighlighted={highlights.has(selectedVerse?.id || '')}
currentHighlightColor={highlights.get(selectedVerse?.id || '')?.color}
onHighlightVerse={handleHighlightVerse}
onChangeHighlightColor={handleChangeHighlightColor}
onRemoveHighlight={handleRemoveHighlight}
/>
{/* Settings panel */}

View File

@@ -2,7 +2,7 @@
import { useState, useEffect } from 'react'
import { Box, Paper, Typography, Tabs, Tab, IconButton, useMediaQuery, useTheme, TextField, Button } from '@mui/material'
import { Close, Bookmark, BookmarkBorder } from '@mui/icons-material'
import { BibleVerse } from '@/types'
import { BibleVerse, HighlightColor } from '@/types'
import { HighlightsTab } from './highlights-tab'
interface VersDetailsPanelProps {
@@ -12,6 +12,11 @@ interface VersDetailsPanelProps {
isBookmarked: boolean
onToggleBookmark: () => void
onAddNote: (note: string) => void
isHighlighted?: boolean
currentHighlightColor?: HighlightColor | null
onHighlightVerse?: (color: HighlightColor) => void
onChangeHighlightColor?: (color: HighlightColor) => void
onRemoveHighlight?: () => void
}
export function VersDetailsPanel({
@@ -21,6 +26,11 @@ export function VersDetailsPanel({
isBookmarked,
onToggleBookmark,
onAddNote,
isHighlighted,
currentHighlightColor,
onHighlightVerse,
onChangeHighlightColor,
onRemoveHighlight,
}: VersDetailsPanelProps) {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
@@ -121,10 +131,16 @@ export function VersDetailsPanel({
{tabValue === 1 && (
<HighlightsTab
verse={verse}
isHighlighted={false} // TODO: get from state
currentColor={null} // TODO: get from state
onToggleHighlight={() => {}} // TODO: implement
onColorChange={() => {}} // TODO: implement
isHighlighted={isHighlighted || false}
currentColor={currentHighlightColor || null}
onToggleHighlight={() => {
if (isHighlighted) {
onRemoveHighlight?.()
} else {
onHighlightVerse?.('yellow')
}
}}
onColorChange={(color) => onChangeHighlightColor?.(color)}
/>
)}