forked from akai/readest
160 lines
4.5 KiB
TypeScript
160 lines
4.5 KiB
TypeScript
import { ConvertChineseVariant } from '@/types/book';
|
|
import { SectionItem, TOCItem, CFI, BookDoc } from '@/libs/document';
|
|
import { initSimpleCC, runSimpleCC } from '@/utils/simplecc';
|
|
import { SIZE_PER_LOC } from '@/services/constants';
|
|
|
|
export const findParentPath = (toc: TOCItem[], href: string): TOCItem[] => {
|
|
for (const item of toc) {
|
|
if (item.href === href) {
|
|
return [item];
|
|
}
|
|
if (item.subitems) {
|
|
const path = findParentPath(item.subitems, href);
|
|
if (path.length) {
|
|
return [item, ...path];
|
|
}
|
|
}
|
|
}
|
|
return [];
|
|
};
|
|
|
|
export const findTocItemBS = (toc: TOCItem[], cfi: string): TOCItem | null => {
|
|
let left = 0;
|
|
let right = toc.length - 1;
|
|
let result: TOCItem | null = null;
|
|
|
|
while (left <= right) {
|
|
const mid = Math.floor((left + right) / 2);
|
|
const item = toc[mid]!;
|
|
const currentCfi = toc[mid]!.cfi || '';
|
|
const comparison = CFI.compare(currentCfi, cfi);
|
|
if (comparison === 0) {
|
|
return findInSubitems(item, cfi) ?? item;
|
|
} else if (comparison < 0) {
|
|
result = findInSubitems(item, cfi) ?? item;
|
|
left = mid + 1;
|
|
} else {
|
|
right = mid - 1;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
const findInSubitems = (item: TOCItem, cfi: string): TOCItem | null => {
|
|
if (!item.subitems?.length) return null;
|
|
return findTocItemBS(item.subitems, cfi);
|
|
};
|
|
|
|
export const updateToc = async (
|
|
bookDoc: BookDoc,
|
|
sortedTOC: boolean,
|
|
convertChineseVariant: ConvertChineseVariant,
|
|
) => {
|
|
const items = bookDoc?.toc || [];
|
|
if (!items.length) return;
|
|
|
|
if (convertChineseVariant && convertChineseVariant !== 'none') {
|
|
await initSimpleCC();
|
|
convertTocLabels(items, convertChineseVariant);
|
|
}
|
|
|
|
const sections = bookDoc?.sections || [];
|
|
if (!sections.length) return;
|
|
|
|
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 totalLocations = Math.floor(totalSize / SIZE_PER_LOC);
|
|
sections.forEach((section, index) => {
|
|
section.location = {
|
|
current: Math.floor(cumulativeSizes[index]! / SIZE_PER_LOC),
|
|
next: Math.floor((cumulativeSizes[index]! + sizes[index]!) / SIZE_PER_LOC),
|
|
total: totalLocations,
|
|
};
|
|
const subitems = section.subitems || [];
|
|
subitems.forEach((subitem, subitemIndex) => {
|
|
subitem.location = {
|
|
current: Math.floor(
|
|
(cumulativeSizes[index]! +
|
|
subitems.slice(0, subitemIndex).reduce((sum, t) => sum + (t.size || 0), 0)) /
|
|
SIZE_PER_LOC,
|
|
),
|
|
next: Math.floor(
|
|
(cumulativeSizes[index]! +
|
|
subitems.slice(0, subitemIndex + 1).reduce((sum, t) => sum + (t.size || 0), 0)) /
|
|
SIZE_PER_LOC,
|
|
),
|
|
total: totalLocations,
|
|
};
|
|
});
|
|
});
|
|
|
|
const sectionsMap = sections.reduce((map: Record<string, SectionItem>, section) => {
|
|
map[section.id] = section;
|
|
section.subitems?.forEach((subitem) => {
|
|
if (subitem.href) {
|
|
map[subitem.href] = subitem;
|
|
}
|
|
});
|
|
return map;
|
|
}, {});
|
|
|
|
updateTocData(bookDoc, items, sections, sectionsMap);
|
|
|
|
if (sortedTOC) {
|
|
sortTocItems(items);
|
|
}
|
|
};
|
|
|
|
const convertTocLabels = (items: TOCItem[], convertChineseVariant: ConvertChineseVariant) => {
|
|
items.forEach((item) => {
|
|
if (item.label) {
|
|
item.label = runSimpleCC(item.label, convertChineseVariant);
|
|
}
|
|
if (item.subitems) {
|
|
convertTocLabels(item.subitems, convertChineseVariant);
|
|
}
|
|
});
|
|
};
|
|
|
|
const updateTocData = (
|
|
bookDoc: BookDoc,
|
|
items: TOCItem[],
|
|
sections: SectionItem[],
|
|
sectionsMap: { [id: string]: SectionItem },
|
|
index = 0,
|
|
): number => {
|
|
items.forEach((item) => {
|
|
item.id ??= index++;
|
|
if (item.href) {
|
|
const id = bookDoc.splitTOCHref(item.href)[0]!;
|
|
const section = sectionsMap[item.href] || sectionsMap[id];
|
|
if (section) {
|
|
item.cfi = section.cfi;
|
|
if (id === item.href || items.length <= sections.length || item.href === section.href) {
|
|
item.location = section.location;
|
|
}
|
|
}
|
|
}
|
|
if (item.subitems) {
|
|
index = updateTocData(bookDoc, item.subitems, sections, sectionsMap, index);
|
|
}
|
|
});
|
|
return index;
|
|
};
|
|
|
|
const sortTocItems = (items: TOCItem[]): void => {
|
|
items.sort((a, b) => {
|
|
if (a.location && b.location) {
|
|
return a.location.current - b.location.current;
|
|
}
|
|
return 0;
|
|
});
|
|
};
|