diff --git a/apps/readest-app/src/app/reader/hooks/useKOSync.ts b/apps/readest-app/src/app/reader/hooks/useKOSync.ts index 65b0c7f3..150ce10b 100644 --- a/apps/readest-app/src/app/reader/hooks/useKOSync.ts +++ b/apps/readest-app/src/app/reader/hooks/useKOSync.ts @@ -9,7 +9,7 @@ import { Book, BookProgress, FIXED_LAYOUT_FORMATS } from '@/types/book'; import { BookDoc } from '@/libs/document'; import { debounce } from '@/utils/debounce'; import { eventDispatcher } from '@/utils/event'; -import { getCFIFromXPointer, XCFI } from '@/utils/xcfi'; +import { getCFIFromXPointer, normalizeProgressXPointer, XCFI } from '@/utils/xcfi'; type SyncState = 'idle' | 'checking' | 'conflict' | 'synced' | 'error'; @@ -72,7 +72,7 @@ export const useKOSync = (bookKey: string) => { const { doc, index: spineIndex } = content; const converter = new XCFI(doc, spineIndex || 0); const xpointerResult = converter.cfiToXPointer(cfi); - koProgress = xpointerResult.xpointer; + koProgress = normalizeProgressXPointer(xpointerResult.xpointer); } } catch (error) { console.error('Failed to convert CFI to XPointer', error); @@ -194,7 +194,7 @@ export const useKOSync = (bookKey: string) => { await kosyncClient.updateProgress(currentBook, progress.koProgress, progress.percentage); }, 5000), // eslint-disable-next-line react-hooks/exhaustive-deps - [bookKey, appService, kosyncClient, generateKOProgress], + [bookKey, appService, kosyncClient], ); const pullProgress = useCallback( @@ -239,10 +239,6 @@ export const useKOSync = (bookKey: string) => { ); useEffect(() => { - const handlePullProgress = (event: CustomEvent) => { - if (event.detail.bookKey !== bookKey) return; - pullProgress(); - }; const handlePushProgress = (event: CustomEvent) => { if (event.detail.bookKey !== bookKey) return; pushProgress(); @@ -251,16 +247,26 @@ export const useKOSync = (bookKey: string) => { if (event.detail.bookKey !== bookKey) return; pushProgress.flush(); }; - eventDispatcher.on('pull-kosync', handlePullProgress); eventDispatcher.on('push-kosync', handlePushProgress); eventDispatcher.on('flush-kosync', handleFlush); return () => { - eventDispatcher.off('pull-kosync', handlePullProgress); eventDispatcher.off('push-kosync', handlePushProgress); eventDispatcher.off('flush-kosync', handleFlush); pushProgress.flush(); + console.log('useKOSync cleanup done for', bookKey); }; - }, [bookKey, pushProgress, pullProgress]); + }, [bookKey, pushProgress]); + + useEffect(() => { + const handlePullProgress = (event: CustomEvent) => { + if (event.detail.bookKey !== bookKey) return; + pullProgress(); + }; + eventDispatcher.on('pull-kosync', handlePullProgress); + return () => { + eventDispatcher.off('pull-kosync', handlePullProgress); + }; + }, [bookKey, pullProgress]); // Pull: pull progress once when the book is opened useEffect(() => { diff --git a/apps/readest-app/src/app/reader/hooks/useProgressSync.ts b/apps/readest-app/src/app/reader/hooks/useProgressSync.ts index d56466e8..99388809 100644 --- a/apps/readest-app/src/app/reader/hooks/useProgressSync.ts +++ b/apps/readest-app/src/app/reader/hooks/useProgressSync.ts @@ -11,7 +11,7 @@ import { CFI } from '@/libs/document'; import { debounce } from '@/utils/debounce'; import { eventDispatcher } from '@/utils/event'; import { DEFAULT_BOOK_SEARCH_CONFIG, SYNC_PROGRESS_INTERVAL_SEC } from '@/services/constants'; -import { getCFIFromXPointer, getXPointerFromCFI } from '@/utils/xcfi'; +import { getCFIFromXPointer, getXPointerFromCFI, normalizeProgressXPointer } from '@/utils/xcfi'; export const useProgressSync = (bookKey: string) => { const _ = useTranslation(); @@ -56,7 +56,7 @@ export const useProgressSync = (bookKey: string) => { if (content && !FIXED_LAYOUT_FORMATS.has(book.format)) { const { doc, index } = content; const xpointerResult = await getXPointerFromCFI(config.location!, doc, index || 0); - config.xpointer = xpointerResult.xpointer; + config.xpointer = normalizeProgressXPointer(xpointerResult.xpointer); } } catch (error) { console.warn('Failed to convert CFI to XPointer', error); diff --git a/apps/readest-app/src/utils/xcfi.ts b/apps/readest-app/src/utils/xcfi.ts index 1888852a..68b5e04b 100644 --- a/apps/readest-app/src/utils/xcfi.ts +++ b/apps/readest-app/src/utils/xcfi.ts @@ -539,3 +539,15 @@ export const getXPointerFromCFI = async ( const xpointer = converter.cfiToXPointer(cfi); return xpointer; }; + +// Koreader sometimes cannot recognize totally valid XPointer. +// Workaround this by cleaning up any trailing /text().N segments. +// This has neglectable effect on position accuracy as the XPointer still point to the correct element +// while offset within the text node is usually ignored by pagination. +export const normalizeProgressXPointer = (xpointer: string): string => { + const tailingTextRange = /\/text\(\)\.\d+$/; + if (xpointer.match(tailingTextRange)) { + xpointer = xpointer.replace(tailingTextRange, ''); + } + return xpointer; +};