@@ -11,7 +11,8 @@ import useTTS from '../hooks/useTTS'
1111import { useReaderStore } from '../store/useReaderStore'
1212import type { Script } from '../store/useReaderStore'
1313import { useAnnotationStore , loadAnnotationsForBook , saveAnnotationsForBook } from '../store/useAnnotationStore'
14- import { saveProgress , loadProgress , saveBookSettings , loadBookSettings } from '../hooks/useLibrary'
14+ import { saveProgress , loadProgress , saveBookSettings , loadBookSettings , loadBookmarks , saveBookmarks } from '../hooks/useLibrary'
15+ import type { Bookmark } from '../hooks/useLibrary'
1516
1617let _toSC : ( ( s : string ) => string ) | null = null
1718let _toTC : ( ( s : string ) => string ) | null = null
@@ -289,6 +290,9 @@ const Reader = ({ bookPath, bookId, bookRecord, getCoverDataUrl, onBack, darkMod
289290 const currentDocRef = useRef < Document | null > ( null )
290291 const lastIframeClickRef = useRef ( { x : 0 , y : 0 } ) // iframe 內最後一次點擊的主視窗座標
291292 const [ activePanel , setActivePanel ] = useState < 'notes' | 'chapters' | 'settings' | 'bookinfo' | null > ( null )
293+ const [ bookmarks , setBookmarks ] = useState < Bookmark [ ] > ( ( ) => loadBookmarks ( bookId ) )
294+ const [ currentCfi , setCurrentCfi ] = useState < string > ( '' )
295+ const isBookmarked = bookmarks . some ( ( b ) => b . cfi === currentCfi )
292296 const [ toc , setToc ] = useState < TocItem [ ] > ( [ ] )
293297 const [ currentHref , setCurrentHref ] = useState ( '' )
294298 const [ ready , setReady ] = useState ( false )
@@ -631,7 +635,10 @@ const Reader = ({ bookPath, bookId, bookRecord, getCoverDataUrl, onBack, darkMod
631635 // eslint-disable-next-line @typescript-eslint/no-explicit-any
632636 const l = loc as any
633637 setCurrentHref ( ( l ?. start ?. href ?? '' ) . split ( '#' ) [ 0 ] )
634- if ( l ?. start ?. cfi ) saveProgress ( bookId , l . start . cfi )
638+ if ( l ?. start ?. cfi ) {
639+ saveProgress ( bookId , l . start . cfi )
640+ setCurrentCfi ( l . start . cfi )
641+ }
635642 setAtStart ( l ?. atStart ?? false )
636643 setAtEnd ( l ?. atEnd ?? false )
637644
@@ -745,6 +752,8 @@ const Reader = ({ bookPath, bookId, bookRecord, getCoverDataUrl, onBack, darkMod
745752 saveBookSettings ( bookId , { fontSize : fs , fontFamily : ff , script : sc , lineHeight : lh , letterSpacing : ls , readingDirection : rd } )
746753 scanAbortRef . current . aborted = true
747754 if ( scanTimerRef . current ) clearTimeout ( scanTimerRef . current )
755+ setCurrentCfi ( '' )
756+ setBookmarks ( [ ] )
748757 setReady ( false )
749758 setPopup ( null )
750759 setToc ( [ ] )
@@ -984,6 +993,28 @@ const Reader = ({ bookPath, bookId, bookRecord, getCoverDataUrl, onBack, darkMod
984993 const togglePanel = ( panel : 'notes' | 'chapters' | 'settings' | 'bookinfo' ) =>
985994 setActivePanel ( ( cur ) => ( cur === panel ? null : panel ) )
986995
996+ const handleToggleBookmark = ( ) => {
997+ const cfi = currentCfi
998+ if ( ! cfi ) return
999+ setBookmarks ( ( prev ) => {
1000+ let next : Bookmark [ ]
1001+ if ( prev . some ( ( b ) => b . cfi === cfi ) ) {
1002+ next = prev . filter ( ( b ) => b . cfi !== cfi )
1003+ } else {
1004+ const chapterTitle = getChapterTitle ( )
1005+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1006+ const loc = ( renditionRef . current as any ) ?. currentLocation ?.( )
1007+ const pageNum = loc ?. start ?. displayed ?. page as number | undefined
1008+ const label = chapterTitle
1009+ ? `${ chapterTitle } ${ pageNum ? ` · 第${ pageNum } 頁` : '' } `
1010+ : pageNum ? `第${ pageNum } 頁` : '書籤'
1011+ next = [ ...prev , { id : crypto . randomUUID ( ) , cfi, label, addedAt : Date . now ( ) } ]
1012+ }
1013+ saveBookmarks ( bookId , next )
1014+ return next
1015+ } )
1016+ }
1017+
9871018 const speakCurrentPage = ( ) => {
9881019 if ( ! viewerRef . current ) return
9891020 const iframe = viewerRef . current . querySelector ( 'iframe' )
@@ -1108,6 +1139,8 @@ const Reader = ({ bookPath, bookId, bookRecord, getCoverDataUrl, onBack, darkMod
11081139 onToggleChapters = { ( ) => togglePanel ( 'chapters' ) }
11091140 onToggleSettings = { ( ) => togglePanel ( 'settings' ) }
11101141 activePanel = { activePanel }
1142+ isBookmarked = { isBookmarked }
1143+ onToggleBookmark = { handleToggleBookmark }
11111144 />
11121145 < div className = "flex flex-1 overflow-hidden" >
11131146 < div className = "flex-1 relative overflow-hidden" >
0 commit comments