From c0bf2d40dd7cf0e2d50f14f604b137c05d3eafe3 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Fri, 19 Sep 2025 00:38:09 +0800 Subject: [PATCH] fix: jump to current item in virtualized TOC list, closes #2055 (#2066) --- .../app/reader/components/sidebar/Content.tsx | 2 +- .../app/reader/components/sidebar/SideBar.tsx | 2 +- .../app/reader/components/sidebar/TOCView.tsx | 13 +++---- apps/readest-app/src/store/readerStore.ts | 4 ++- apps/readest-app/src/utils/book.ts | 34 ++++++++++++------- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/apps/readest-app/src/app/reader/components/sidebar/Content.tsx b/apps/readest-app/src/app/reader/components/sidebar/Content.tsx index 4fc086d4..8923551b 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/Content.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/Content.tsx @@ -73,7 +73,7 @@ const SidebarContent: React.FC<{ })} > {targetTab === 'toc' && bookDoc.toc && ( - + )} {targetTab === 'annotations' && ( diff --git a/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx b/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx index 3f36d30c..db3bbcac 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx @@ -293,7 +293,7 @@ const SideBar: React.FC<{ )}
) => { const TOCView: React.FC<{ bookKey: string; toc: TOCItem[]; -}> = ({ bookKey, toc }) => { + sections?: SectionItem[]; +}> = ({ bookKey, toc, sections }) => { const { appService } = useEnv(); const { getView, getProgress, getViewState, getViewSettings } = useReaderStore(); const { sideBarBookKey, isSideBarVisible } = useSidebarStore(); @@ -183,7 +184,7 @@ const TOCView: React.FC<{ } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeHref]); + }, [flatItems, activeHref]); const virtualItemSize = useMemo(() => { return window.innerWidth >= 640 && !viewSettings?.translationEnabled ? 37 : 57; @@ -214,12 +215,12 @@ const TOCView: React.FC<{ useEffect(() => { if (flatItems.length > 0) { - setTimeout(scrollToActiveItem, appService?.isAndroidApp ? 300 : 0); + setTimeout(scrollToActiveItem, appService?.isAndroidApp ? 300 : 100); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [scrollToActiveItem]); + }, [progress]); - return flatItems.length > 256 ? ( + return sections && sections.length > 256 ? (
((set, get) => ({ const primaryLanguage = getPrimaryLanguage(bookDoc.metadata.language); book.primaryLanguage = book.primaryLanguage ?? primaryLanguage; book.metadata = book.metadata ?? bookDoc.metadata; - book.metaHash = book.metaHash ?? getMetadataHash(bookDoc.metadata); + // TODO: uncomment this when we can ensure metaHash is correctly generated for all books + // book.metaHash = book.metaHash ?? getMetadataHash(bookDoc.metadata); + book.metaHash = getMetadataHash(bookDoc.metadata); const isFixedLayout = FIXED_LAYOUT_FORMATS.has(book.format); useBookDataStore.setState((state) => ({ diff --git a/apps/readest-app/src/utils/book.ts b/apps/readest-app/src/utils/book.ts index c86c0d58..525e6562 100644 --- a/apps/readest-app/src/utils/book.ts +++ b/apps/readest-app/src/utils/book.ts @@ -220,9 +220,13 @@ const getTitleForHash = (title: string | LanguageMap) => { const getAuthorsList = (contributors: string | string[] | Contributor | Contributor[]) => { if (!contributors) return []; return Array.isArray(contributors) - ? contributors.map((contributor) => - typeof contributor === 'string' ? contributor : formatLanguageMap(contributor?.name, true), - ) + ? contributors + .map((contributor) => + typeof contributor === 'string' + ? contributor + : formatLanguageMap(contributor?.name, true), + ) + .filter(Boolean) : [ typeof contributors === 'string' ? contributors @@ -231,12 +235,16 @@ const getAuthorsList = (contributors: string | string[] | Contributor | Contribu }; const normalizeIdentifier = (identifier: string) => { - if (identifier.includes('urn:')) { - // Slice after the last ':' - return identifier.match(/[^:]+$/)![0]; - } else if (identifier.includes(':')) { - // Slice after the first ':' - return identifier.match(/^[^:]+:(.+)$/)![1]; + try { + if (identifier.includes('urn:')) { + // Slice after the last ':' + return identifier.match(/[^:]+$/)?.[0] || ''; + } else if (identifier.includes(':')) { + // Slice after the first ':' + return identifier.match(/^[^:]+:(.+)$/)?.[1] || ''; + } + } catch { + return identifier; } return identifier; }; @@ -246,9 +254,11 @@ const getIdentifiersList = ( ) => { if (!identifiers) return []; return Array.isArray(identifiers) - ? identifiers.map((identifier) => - typeof identifier === 'string' ? normalizeIdentifier(identifier) : identifier.value, - ) + ? identifiers + .map((identifier) => + typeof identifier === 'string' ? normalizeIdentifier(identifier) : identifier.value, + ) + .filter(Boolean) : typeof identifiers === 'string' ? [normalizeIdentifier(identifiers)] : [identifiers.value];