fix(core): address white-box review feedback
- Fix unicode surrogate pairs and emoji slicing via Array.from in ORP calculator - Add slot smoothing constraint to pivot element to prevent horizontal jitter - Implement adaptive timestamp compensation and visibilitychange auto-pause - Slim storage layer by updating only STORE_META on progress changes with memory fallback - Align EPUB chapter indices to token array indices instead of raw character offsets - Support standalone emoji tokens via Extended_Pictographic regex - Code-split JSZip via dynamic import to optimize initial bundle size Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
import JSZip from 'jszip';
|
||||
import type { Chapter } from '../types';
|
||||
export interface EpubChapterItem {
|
||||
id: string;
|
||||
title: string;
|
||||
charOffset: number;
|
||||
}
|
||||
|
||||
export interface ParsedEpub {
|
||||
title: string;
|
||||
author: string;
|
||||
fullText: string;
|
||||
chapters: Chapter[];
|
||||
rawChapters: EpubChapterItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-side EPUB parser with dynamic JSZip import to reduce initial bundle size.
|
||||
*/
|
||||
export async function parseEpubFile(file: File | Blob): Promise<ParsedEpub> {
|
||||
// Dynamic import JSZip to split ~100KB off the initial load bundle
|
||||
const { default: JSZip } = await import('jszip');
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const zip = await JSZip.loadAsync(arrayBuffer);
|
||||
|
||||
@@ -39,8 +48,11 @@ export async function parseEpubFile(file: File | Blob): Promise<ParsedEpub> {
|
||||
const opfDoc = domParser.parseFromString(opfXml, 'application/xml');
|
||||
|
||||
// Metadata
|
||||
const title = opfDoc.querySelector('title, dc\\:title')?.textContent?.trim() || (file instanceof File ? file.name.replace(/\.epub$/i, '') : '未命名书籍');
|
||||
const author = opfDoc.querySelector('creator, dc\\:creator')?.textContent?.trim() || '未知作者';
|
||||
const title =
|
||||
opfDoc.querySelector('title, dc\\:title')?.textContent?.trim() ||
|
||||
(file instanceof File ? file.name.replace(/\.epub$/i, '') : '未命名书籍');
|
||||
const author =
|
||||
opfDoc.querySelector('creator, dc\\:creator')?.textContent?.trim() || '未知作者';
|
||||
|
||||
// Manifest: id -> href
|
||||
const manifestMap = new Map<string, string>();
|
||||
@@ -48,7 +60,6 @@ export async function parseEpubFile(file: File | Blob): Promise<ParsedEpub> {
|
||||
const id = item.getAttribute('id');
|
||||
const href = item.getAttribute('href');
|
||||
if (id && href) {
|
||||
// Decode URL encoding
|
||||
manifestMap.set(id, decodeURIComponent(href));
|
||||
}
|
||||
});
|
||||
@@ -62,7 +73,7 @@ export async function parseEpubFile(file: File | Blob): Promise<ParsedEpub> {
|
||||
}
|
||||
});
|
||||
|
||||
const chapters: Chapter[] = [];
|
||||
const rawChapters: EpubChapterItem[] = [];
|
||||
let fullText = '';
|
||||
let chapterIndex = 1;
|
||||
|
||||
@@ -70,7 +81,6 @@ export async function parseEpubFile(file: File | Blob): Promise<ParsedEpub> {
|
||||
const relativeHref = manifestMap.get(idref);
|
||||
if (!relativeHref) continue;
|
||||
|
||||
// Resolve full path inside zip
|
||||
const filePath = opfDir ? resolvePath(opfDir, relativeHref) : relativeHref;
|
||||
const chapterFile = zip.file(filePath);
|
||||
if (!chapterFile) continue;
|
||||
@@ -87,7 +97,6 @@ export async function parseEpubFile(file: File | Blob): Promise<ParsedEpub> {
|
||||
|
||||
// Extract text content cleanly
|
||||
const body = chapterDoc.body || chapterDoc.documentElement;
|
||||
// Remove scripts and styles
|
||||
body.querySelectorAll('script, style, svg').forEach((el) => el.remove());
|
||||
|
||||
const paragraphs: string[] = [];
|
||||
@@ -105,10 +114,11 @@ export async function parseEpubFile(file: File | Blob): Promise<ParsedEpub> {
|
||||
|
||||
const chapterText = paragraphs.join('\n\n');
|
||||
if (chapterText.trim().length > 0) {
|
||||
chapters.push({
|
||||
const startCharOffset = fullText.length;
|
||||
rawChapters.push({
|
||||
id: `chap-${chapterIndex}`,
|
||||
title: chapterTitle,
|
||||
startIndex: fullText.length,
|
||||
charOffset: startCharOffset,
|
||||
});
|
||||
|
||||
fullText += (fullText ? '\n\n' : '') + chapterText;
|
||||
@@ -124,7 +134,7 @@ export async function parseEpubFile(file: File | Blob): Promise<ParsedEpub> {
|
||||
title,
|
||||
author,
|
||||
fullText,
|
||||
chapters,
|
||||
rawChapters,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user