fix: jump to current item in virtualized TOC list, closes #2055 (#2066)

This commit is contained in:
Huang Xin
2025-09-19 00:38:09 +08:00
committed by GitHub
parent e30a39f9cb
commit c0bf2d40dd
5 changed files with 34 additions and 21 deletions
@@ -73,7 +73,7 @@ const SidebarContent: React.FC<{
})}
>
{targetTab === 'toc' && bookDoc.toc && (
<TOCView toc={bookDoc.toc} bookKey={sideBarBookKey} />
<TOCView toc={bookDoc.toc} sections={bookDoc.sections} bookKey={sideBarBookKey} />
)}
{targetTab === 'annotations' && (
<BooknoteView type='annotation' toc={bookDoc.toc ?? []} bookKey={sideBarBookKey} />
@@ -293,7 +293,7 @@ const SideBar: React.FC<{
)}
<div
className={clsx(
'drag-bar absolute -right-2 top-0 h-full w-0.5 cursor-col-resize bg-transparent p-2',
'drag-bar absolute -right-2 top-0 h-full w-0.5 cursor-col-resize bg-transparent p-1',
isMobile && 'hidden',
)}
role='slider'
@@ -3,7 +3,7 @@ import { FixedSizeList as VirtualList } from 'react-window';
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import 'overlayscrollbars/overlayscrollbars.css';
import { TOCItem } from '@/libs/document';
import { SectionItem, TOCItem } from '@/libs/document';
import { useEnv } from '@/context/EnvContext';
import { useReaderStore } from '@/store/readerStore';
import { useSidebarStore } from '@/store/sidebarStore';
@@ -39,7 +39,8 @@ const useFlattenedTOC = (toc: TOCItem[], expandedItems: Set<string>) => {
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 ? (
<div
className='virtual-list rounded pt-2'
data-overlayscrollbars-initialize=''
+3 -1
View File
@@ -160,7 +160,9 @@ export const useReaderStore = create<ReaderStore>((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) => ({
+22 -12
View File
@@ -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];