diff --git a/apps/readest-app/.env b/apps/readest-app/.env index 1e4b4c25..a1275fd2 100644 --- a/apps/readest-app/.env +++ b/apps/readest-app/.env @@ -3,7 +3,7 @@ PDFJS_FONTS_PATH=../../packages/foliate-js/node_modules/pdfjs-dist PDFJS_STYLE_PATH=../../packages/foliate-js/vendor/pdfjs NEXT_PUBLIC_DEFAULT_POSTHOG_URL_BASE64="aHR0cHM6Ly91cy5pLnBvc3Rob2cuY29t" -NEXT_PUBLIC_DEFAULT_POSTHOG_KEY_BASE64="cGhjX2ViNXowbVRxWm8yZm5YYnZGNmE3bFh5TThpTmRSNTNsR1A3VFM3VGh4S08=" +NEXT_PUBLIC_DEFAULT_POSTHOG_KEY_BASE64="cGhjX0x3ekhZRWtsZUVub3ZSc05ZQlRpTVRTV2MyS1NUOFdZMzBIWWFhN0ZPa1IK" NEXT_PUBLIC_DEFAULT_SUPABASE_URL_BASE64="aHR0cHM6Ly9yZWFkZXN0LnN1cGFiYXNlLmNv" NEXT_PUBLIC_DEFAULT_SUPABASE_KEY_BASE64="ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBjM01pT2lKemRYQmhZbUZ6WlNJc0luSmxaaUk2SW5aaWMzbDRablZ6YW1weFpIaHJhbkZzZVhOaklpd2ljbTlzWlNJNkltRnViMjRpTENKcFlYUWlPakUzTXpReE1qTTJOekVzSW1WNGNDSTZNakEwT1RZNU9UWTNNWDAuM1U1VXFhb3VfMVNnclZlMWVvOXJBcGMwdUtqcWhwUWRVWGh2d1VIbVVmZw==" diff --git a/apps/readest-app/src/__tests__/utils/cfi.test.ts b/apps/readest-app/src/__tests__/utils/cfi.test.ts index 3b9b6eea..69596114 100644 --- a/apps/readest-app/src/__tests__/utils/cfi.test.ts +++ b/apps/readest-app/src/__tests__/utils/cfi.test.ts @@ -18,6 +18,12 @@ describe('isCfiInLocation', () => { expect(isCfiInLocation('epubcfi(/6/6)', null)).toBe(false); expect(isCfiInLocation('epubcfi(/6/6)', undefined)).toBe(false); }); + + it('should return false for null/undefined/empty cfi', () => { + expect(isCfiInLocation(null as unknown as string, 'epubcfi(/6/6)')).toBe(false); + expect(isCfiInLocation(undefined as unknown as string, 'epubcfi(/6/6)')).toBe(false); + expect(isCfiInLocation('', 'epubcfi(/6/6)')).toBe(false); + }); }); describe('findNearestCfi', () => { diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 4aec6073..552693fd 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -264,7 +264,13 @@ const FoliateViewer: React.FC<{ item.style && getIndexFromCfi(item.cfi) === sectionIndex, ) - .forEach((annotation) => viewRef.current?.addAnnotation(annotation)); + .map((annotation) => { + try { + viewRef.current?.addAnnotation(annotation); + } catch (err) { + console.warn('Failed to add annotation', { annotation, error: err }); + } + }); }, 100); if (!detail.doc.isEventListenersAdded) { diff --git a/apps/readest-app/src/app/reader/components/ProgressBar.tsx b/apps/readest-app/src/app/reader/components/ProgressBar.tsx index 355d4e7b..6e35dbd3 100644 --- a/apps/readest-app/src/app/reader/components/ProgressBar.tsx +++ b/apps/readest-app/src/app/reader/components/ProgressBar.tsx @@ -54,10 +54,9 @@ const ProgressBar: React.FC = ({ const progressInfo = formatProgress(pageInfo?.current, pageInfo?.total, template, localize, lang); const { page: current = 0, pages: total = 0 } = view?.renderer || {}; - const pagesLeft = Math.min( - Math.max(total - current, 1), - pageInfo ? pageInfo.total - pageInfo.current : total, - ); + const pagesLeft = bookData?.isFixedLayout + ? 1 + : Math.min(Math.max(total - current, 1), pageInfo ? pageInfo.total - pageInfo.current : total); const showPagesLeft = total > 0 || bookData?.isFixedLayout; const timeLeftStr = showPagesLeft ? _('{{time}} min left in chapter', { diff --git a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx index d499d392..45dc5057 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -333,7 +333,11 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { getIndexFromCfi(booknote.cfi) === detail.index, ) .map((annotation) => { - view?.addAnnotation(annotation); + try { + view?.addAnnotation(annotation); + } catch (err) { + console.warn('Failed to add annotation', { annotation, error: err }); + } }); }; diff --git a/apps/readest-app/src/app/reader/hooks/useProgressAutoSave.ts b/apps/readest-app/src/app/reader/hooks/useProgressAutoSave.ts index 0676d8ab..86b90b7f 100644 --- a/apps/readest-app/src/app/reader/hooks/useProgressAutoSave.ts +++ b/apps/readest-app/src/app/reader/hooks/useProgressAutoSave.ts @@ -15,7 +15,8 @@ export const useProgressAutoSave = (bookKey: string) => { const saveBookConfig = useCallback( debounce(() => { setTimeout(async () => { - const config = getConfig(bookKey)!; + const config = getConfig(bookKey); + if (!config) return; const settings = useSettingsStore.getState().settings; await saveConfig(envConfig, bookKey, config, settings); }, 500); diff --git a/apps/readest-app/src/context/PHContext.tsx b/apps/readest-app/src/context/PHContext.tsx index 058500b2..5f05e3ed 100644 --- a/apps/readest-app/src/context/PHContext.tsx +++ b/apps/readest-app/src/context/PHContext.tsx @@ -23,6 +23,7 @@ if (typeof window !== 'undefined' && process.env['NODE_ENV'] === 'production' && posthog.init(posthogKey, { api_host: posthogUrl, person_profiles: 'always', + autocapture: false, }); } } diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 193c894c..24301591 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -87,7 +87,7 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial = { openLastBooks: false, lastOpenBooks: [], autoImportBooksOnOpen: false, - telemetryEnabled: false, + telemetryEnabled: true, discordRichPresenceEnabled: false, libraryViewMode: 'grid', librarySortBy: LibrarySortByType.Updated, diff --git a/apps/readest-app/src/services/tts/NativeTTSClient.ts b/apps/readest-app/src/services/tts/NativeTTSClient.ts index dcfb19df..9838770a 100644 --- a/apps/readest-app/src/services/tts/NativeTTSClient.ts +++ b/apps/readest-app/src/services/tts/NativeTTSClient.ts @@ -181,7 +181,7 @@ export class NativeTTSClient implements TTSClient { const { marks } = parseSSMLMarks(ssml, this.#primaryLang); for (const mark of marks) { - this.controller?.dispatchSpeakMark(mark); + if (!preload) this.controller?.dispatchSpeakMark(mark); for await (const ev of this.speakMark(mark, preload, signal)) { if (signal.aborted) { yield { code: 'error', message: 'Aborted' } as TTSMessageEvent; diff --git a/apps/readest-app/src/services/tts/TTSController.ts b/apps/readest-app/src/services/tts/TTSController.ts index 2a1099f6..c084684b 100644 --- a/apps/readest-app/src/services/tts/TTSController.ts +++ b/apps/readest-app/src/services/tts/TTSController.ts @@ -32,7 +32,7 @@ export class TTSController extends EventTarget { #nossmlCnt: number = 0; #currentSpeakAbortController: AbortController | null = null; #currentSpeakPromise: Promise | null = null; - #isPreloading: boolean = false; + #ttsSectionIndex: number = -1; state: TTSState = 'stopped'; @@ -261,7 +261,6 @@ export class TTSController extends EventTarget { const tts = this.view.tts; if (!tts) return; - this.#isPreloading = true; const ssmls: string[] = []; for (let i = 0; i < count; i++) { const ssml = await this.#preprocessSSML(tts.next()); @@ -271,7 +270,6 @@ export class TTSController extends EventTarget { for (let i = 0; i < ssmls.length; i++) { tts.prev(); } - this.#isPreloading = false; await Promise.all(ssmls.map((ssml) => this.preloadSSML(ssml, new AbortController().signal))); } @@ -533,15 +531,11 @@ export class TTSController extends EventTarget { dispatchSpeakMark(mark?: TTSMark) { this.dispatchEvent(new CustomEvent('tts-speak-mark', { detail: mark || { text: '' } })); if (mark && mark.name !== '-1') { - if (this.#isPreloading) { - setTimeout(() => this.dispatchSpeakMark(mark), 500); - } else { + try { const range = this.view.tts?.setMark(mark.name); - try { - const cfi = this.view.getCFI(this.#ttsSectionIndex, range); - this.dispatchEvent(new CustomEvent('tts-highlight-mark', { detail: { cfi } })); - } catch {} - } + const cfi = this.view.getCFI(this.#ttsSectionIndex, range); + this.dispatchEvent(new CustomEvent('tts-highlight-mark', { detail: { cfi } })); + } catch {} } } diff --git a/apps/readest-app/src/store/readerStore.ts b/apps/readest-app/src/store/readerStore.ts index 54071375..2baee01d 100644 --- a/apps/readest-app/src/store/readerStore.ts +++ b/apps/readest-app/src/store/readerStore.ts @@ -179,6 +179,8 @@ export const useReaderStore = create((set, get) => ({ } } } + // Filter out invalid booknotes + config.booknotes = config.booknotes?.filter((booknote) => booknote.cfi) ?? []; await updateToc( bookDoc, config.viewSettings?.sortedTOC ?? false, diff --git a/apps/readest-app/src/utils/cfi.ts b/apps/readest-app/src/utils/cfi.ts index 80459a5d..0681983a 100644 --- a/apps/readest-app/src/utils/cfi.ts +++ b/apps/readest-app/src/utils/cfi.ts @@ -6,14 +6,19 @@ const unwrapCfi = (cfi: string): string => { }; export function isCfiInLocation(cfi: string, location: string | null | undefined): boolean { - if (!location) return false; + if (!cfi || !location) return false; if (cfi === location) return true; if (cfi && unwrapCfi(cfi).startsWith(unwrapCfi(location))) return true; const start = CFI.collapse(location); const end = CFI.collapse(location, true); - return CFI.compare(cfi, start) >= 0 && CFI.compare(cfi, end) <= 0; + try { + return CFI.compare(cfi, start) >= 0 && CFI.compare(cfi, end) <= 0; + } catch (err) { + console.warn('Failed to compare CFIs', { cfi, location, error: err }); + return false; + } } /** diff --git a/apps/readest-app/src/utils/toc.ts b/apps/readest-app/src/utils/toc.ts index 3befb6fc..00886be5 100644 --- a/apps/readest-app/src/utils/toc.ts +++ b/apps/readest-app/src/utils/toc.ts @@ -19,6 +19,7 @@ export const findParentPath = (toc: TOCItem[], href: string): TOCItem[] => { }; export const findTocItemBS = (toc: TOCItem[], cfi: string): TOCItem | null => { + if (!cfi) return null; let left = 0; let right = toc.length - 1; let result: TOCItem | null = null; diff --git a/packages/foliate-js b/packages/foliate-js index 57e35351..ae94dce2 160000 --- a/packages/foliate-js +++ b/packages/foliate-js @@ -1 +1 @@ -Subproject commit 57e35351b18a74e7d8dcd49de5f66d9110a7536a +Subproject commit ae94dce26164f7894a60bd6cc76f8dcca2ad332a