feat: add location info for each entry in table of contents (#1016)
This commit is contained in:
@@ -131,7 +131,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
const progressValid = !!progressInfo;
|
||||
const progressFraction =
|
||||
progressValid && progressInfo?.total > 0
|
||||
? ((progressInfo!.next ?? progressInfo!.current) + 1) / progressInfo!.total || 0
|
||||
? (progressInfo!.current + 1) / progressInfo!.total || 0
|
||||
: 0;
|
||||
|
||||
return (
|
||||
|
||||
@@ -35,7 +35,7 @@ const PageInfoView: React.FC<PageInfoProps> = ({
|
||||
: ''
|
||||
: pageinfo
|
||||
? _(isVertical ? '{{currentPage}} · {{totalPage}}' : 'Loc. {{currentPage}} / {{totalPage}}', {
|
||||
currentPage: (pageinfo.next ?? pageinfo.current) + 1,
|
||||
currentPage: pageinfo.current + 1,
|
||||
totalPage: pageinfo.total,
|
||||
})
|
||||
: '';
|
||||
|
||||
@@ -58,7 +58,7 @@ const TOCItemView: React.FC<{
|
||||
}, [expandedItems, item.href]);
|
||||
|
||||
return (
|
||||
<li className='w-full' style={{ paddingTop: '1px' }}>
|
||||
<li className='border-base-300 w-full border-b sm:border-none sm:pt-[1px]'>
|
||||
<span
|
||||
role='treeitem'
|
||||
tabIndex={-1}
|
||||
@@ -67,8 +67,10 @@ const TOCItemView: React.FC<{
|
||||
aria-expanded={isExpanded ? 'true' : 'false'}
|
||||
aria-selected={isActive ? 'true' : 'false'}
|
||||
data-href={item.href ? getContentMd5(item.href) : undefined}
|
||||
className={`flex w-full cursor-pointer items-center rounded-md py-2 ${
|
||||
isActive ? 'bg-base-300/85 hover:bg-base-300' : 'sm:hover:bg-base-300/85'
|
||||
className={`flex w-full cursor-pointer items-center rounded-md py-4 sm:py-2 ${
|
||||
isActive
|
||||
? 'sm:bg-base-300/85 sm:hover:bg-base-300 sm:text-base-content text-blue-500'
|
||||
: 'sm:hover:bg-base-300/85'
|
||||
}`}
|
||||
>
|
||||
{item.subitems && (
|
||||
@@ -77,7 +79,7 @@ const TOCItemView: React.FC<{
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className='ml-2 truncate text-ellipsis'
|
||||
className='ms-2 truncate text-ellipsis'
|
||||
style={{
|
||||
maxWidth: 'calc(100% - 24px)',
|
||||
whiteSpace: 'nowrap',
|
||||
@@ -86,6 +88,11 @@ const TOCItemView: React.FC<{
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
{item.location && (
|
||||
<span className='text-base-content/50 ms-auto ps-1 text-xs sm:pe-1'>
|
||||
{item.location.current + 1}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{item.subitems && isExpanded && (
|
||||
<ol role='group'>
|
||||
@@ -161,7 +168,7 @@ const TOCView: React.FC<{
|
||||
|
||||
return (
|
||||
<div className='rounded pt-2'>
|
||||
<ul role='tree' ref={viewRef} className='px-2'>
|
||||
<ul role='tree' ref={viewRef} className='pe-4 ps-2 sm:pe-2'>
|
||||
{toc &&
|
||||
toc.map((item, index) => (
|
||||
<TOCItemView
|
||||
|
||||
@@ -36,11 +36,18 @@ export const CFI = epubcfi;
|
||||
|
||||
export type DocumentFile = File;
|
||||
|
||||
export type Location = {
|
||||
current: number;
|
||||
next: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export interface TOCItem {
|
||||
id: number;
|
||||
label: string;
|
||||
href: string;
|
||||
cfi?: string;
|
||||
location?: Location;
|
||||
subitems?: TOCItem[];
|
||||
}
|
||||
|
||||
@@ -48,6 +55,8 @@ export interface SectionItem {
|
||||
id: string;
|
||||
cfi: string;
|
||||
size: number;
|
||||
linear: string;
|
||||
location?: Location;
|
||||
}
|
||||
|
||||
export interface BookDoc {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { create } from 'zustand';
|
||||
import { BookContent, BookConfig, PageInfo, BookProgress, ViewSettings } from '@/types/book';
|
||||
import { EnvConfigType } from '@/services/environment';
|
||||
import { FoliateView } from '@/types/view';
|
||||
import { BookDoc, DocumentLoader, SectionItem, TOCItem } from '@/libs/document';
|
||||
import { updateTocCFI, updateTocID } from '@/utils/toc';
|
||||
import { BookDoc, DocumentLoader, TOCItem } from '@/libs/document';
|
||||
import { updateToc } from '@/utils/toc';
|
||||
import { useSettingsStore } from './settingsStore';
|
||||
import { useBookDataStore } from './bookDataStore';
|
||||
import { useLibraryStore } from './libraryStore';
|
||||
@@ -122,12 +122,7 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
|
||||
const { book: loadedBookDoc } = await new DocumentLoader(file).open();
|
||||
const bookDoc = loadedBookDoc as BookDoc;
|
||||
if (bookDoc.toc?.length && bookDoc.sections?.length) {
|
||||
updateTocID(bookDoc.toc);
|
||||
const sections = bookDoc.sections.reduce((map: Record<string, SectionItem>, section) => {
|
||||
map[section.id] = section;
|
||||
return map;
|
||||
}, {});
|
||||
updateTocCFI(bookDoc, bookDoc.toc, sections);
|
||||
updateToc(bookDoc, bookDoc.toc, bookDoc.sections);
|
||||
}
|
||||
// Set the book's language for formerly imported books, newly imported books have this field set
|
||||
book.primaryLanguage =
|
||||
|
||||
@@ -37,31 +37,50 @@ export const findTocItemBS = (toc: TOCItem[], cfi: string): TOCItem | null => {
|
||||
return result;
|
||||
};
|
||||
|
||||
export const updateTocID = (items: TOCItem[], index = 0): number => {
|
||||
items.forEach((item) => {
|
||||
item.id ??= index++;
|
||||
if (item.subitems) {
|
||||
index = updateTocID(item.subitems, index);
|
||||
}
|
||||
export const updateToc = (bookDoc: BookDoc, items: TOCItem[], sections: SectionItem[]): void => {
|
||||
const sizes = sections.map((s) => (s.linear != 'no' && s.size > 0 ? s.size : 0));
|
||||
let cumulativeSize = 0;
|
||||
const cumulativeSizes = sizes.reduce((acc: number[], size) => {
|
||||
acc.push(cumulativeSize);
|
||||
cumulativeSize += size;
|
||||
return acc;
|
||||
}, []);
|
||||
const totalSize = cumulativeSizes[cumulativeSizes.length - 1] || 0;
|
||||
const sizePerLoc = 1500;
|
||||
sections.forEach((section, index) => {
|
||||
section.location = {
|
||||
current: Math.floor(cumulativeSizes[index]! / sizePerLoc),
|
||||
next: Math.floor((cumulativeSizes[index]! + sizes[index]!) / sizePerLoc),
|
||||
total: Math.floor(totalSize / sizePerLoc),
|
||||
};
|
||||
});
|
||||
return index;
|
||||
|
||||
const sectionsMap = sections.reduce((map: Record<string, SectionItem>, section) => {
|
||||
map[section.id] = section;
|
||||
return map;
|
||||
}, {});
|
||||
updateTocData(bookDoc, items, sectionsMap);
|
||||
};
|
||||
|
||||
export const updateTocCFI = (
|
||||
const updateTocData = (
|
||||
bookDoc: BookDoc,
|
||||
items: TOCItem[],
|
||||
sections: { [id: string]: SectionItem },
|
||||
): void => {
|
||||
index = 0,
|
||||
): number => {
|
||||
items.forEach((item) => {
|
||||
item.id ??= index++;
|
||||
if (item.href) {
|
||||
const id = bookDoc.splitTOCHref(item.href)[0]!;
|
||||
const section = sections[id];
|
||||
if (section) {
|
||||
item.cfi = section.cfi;
|
||||
item.location = section.location;
|
||||
}
|
||||
}
|
||||
if (item.subitems) {
|
||||
updateTocCFI(bookDoc, item.subitems, sections);
|
||||
index = updateTocData(bookDoc, item.subitems, sections, index);
|
||||
}
|
||||
});
|
||||
return index;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user