diff --git a/web/src/reader/core/parser-manager.ts b/web/src/reader/core/parser-manager.ts index 369eebe..4cc9d99 100644 --- a/web/src/reader/core/parser-manager.ts +++ b/web/src/reader/core/parser-manager.ts @@ -1,11 +1,6 @@ // Parser Manager - Routes files to appropriate parsers // Procedural style: Functions, not classes -import { parseEPUB } from "../parsers/epub-parsers"; -import { parseFB2 } from "../parsers/fb2-parser"; -import { parseTXT } from "../parsers/txt-parser"; -import { parseHTML } from "../parsers/html-parser"; - // ============================================================ // Parser Registry // ============================================================ @@ -125,14 +120,22 @@ async function parseEbookOnClient( format: string, ): Promise { switch (format) { - case "epub": + case "epub": { + const { parseEPUB } = await import("../parsers/epub-parsers"); return parseEPUB(file); - case "fb2": + } + case "fb2": { + const { parseFB2 } = await import("../parsers/fb2-parser"); return parseFB2(file); - case "txt": + } + case "txt": { + const { parseTXT } = await import("../parsers/txt-parser"); return parseTXT(file); - case "html": + } + case "html": { + const { parseHTML } = await import("../parsers/html-parser"); return parseHTML(file); + } default: throw new Error(`Client-side parser not implemented for: ${format}`); } diff --git a/web/src/reader/parsers/epub-parsers.ts b/web/src/reader/parsers/epub-parsers.ts index d9a4fad..1565434 100644 --- a/web/src/reader/parsers/epub-parsers.ts +++ b/web/src/reader/parsers/epub-parsers.ts @@ -1,13 +1,12 @@ // EPUB Parser - Converts EPUB 2/3 to Common Intermediate Format // Procedural style: Functions, not classes -import JSZip from 'jszip'; - // ============================================================ // Main Parse Function // ============================================================ export async function parseEPUB(epubBlob: Blob): Promise { + const JSZip = (await import('jszip')).default; const zip = await JSZip.loadAsync(epubBlob); // Parse container.xml to find OPF file @@ -18,11 +17,9 @@ export async function parseEPUB(epubBlob: Blob): Promise { throw new Error('Invalid EPUB: no OPF file found'); } - // Parse OPF file const opfXml = await getZipFileContent(zip, opfPath); const packageDoc = parseXML(opfXml); - // Extract all components const metadata = extractMetadata(packageDoc); const spine = parseSpine(packageDoc); const toc = await parseTOC(zip, packageDoc, opfPath); @@ -48,7 +45,7 @@ export async function parseEPUB(epubBlob: Blob): Promise { // Helper Functions // ============================================================ -async function getZipFileContent(zip: JSZip, path: string): Promise { +async function getZipFileContent(zip: any, path: string): Promise { const file = zip.file(path); if (!file) { throw new Error(`File not found: ${path}`); @@ -113,7 +110,7 @@ function parseSpine(packageDoc: XMLDocument): EbookCIF['spine'] { return result; } -async function parseTOC(zip: JSZip, packageDoc: XMLDocument, opfPath: string): Promise { +async function parseTOC(zip: any, packageDoc: XMLDocument, opfPath: string): Promise { // Try EPUB 3.0 navigation document first const navItem = packageDoc.querySelector('manifest item[properties~="nav"]'); if (navItem) { @@ -140,7 +137,7 @@ async function parseTOC(zip: JSZip, packageDoc: XMLDocument, opfPath: string): P return []; } -async function parseNavTOC(zip: JSZip, navPath: string): Promise { +async function parseNavTOC(zip: any, navPath: string): Promise { const navXml = await getZipFileContent(zip, navPath); const navDoc = parseXML(navXml); const nav = navDoc.querySelector('nav'); @@ -168,7 +165,7 @@ async function parseNavTOC(zip: JSZip, navPath: string): Promise { +async function parseNCXTOC(zip: any, ncxPath: string): Promise { const ncxXml = await getZipFileContent(zip, ncxPath); const ncxDoc = parseXML(ncxXml); const navMap = ncxDoc.querySelector('navMap'); @@ -198,7 +195,7 @@ function parseNCXNode(node: Element): EbookCIF['toc'] { return result; } -async function loadResources(zip: JSZip): Promise> { +async function loadResources(zip: any): Promise> { const resources = new Map(); const files = Object.keys(zip.files); @@ -213,7 +210,7 @@ async function loadResources(zip: JSZip): Promise> { return resources; } -async function extractCover(zip: JSZip, packageDoc: XMLDocument): Promise { +async function extractCover(zip: any, packageDoc: XMLDocument): Promise { // Try cover-id metadata const coverId = packageDoc.querySelector('meta[name="cover"]')?.getAttribute('content'); if (coverId) { diff --git a/web/src/reader/parsers/fb2-parser.ts b/web/src/reader/parsers/fb2-parser.ts index 7b5bf5b..d1090f1 100644 --- a/web/src/reader/parsers/fb2-parser.ts +++ b/web/src/reader/parsers/fb2-parser.ts @@ -2,8 +2,6 @@ // FB2 is XML-based, similar to EPUB structure // Procedural style: Functions, not classes -import JSZip from "jszip"; - // ============================================================ // Main Parse Function // ============================================================ @@ -16,6 +14,7 @@ export async function parseFB2(fb2Blob: Blob): Promise { fb2Blob.type === "application/zip" || fb2Blob.type === "application/x-zip-compressed" ) { + const JSZip = (await import("jszip")).default; const zip = await JSZip.loadAsync(fb2Blob); const files = Object.keys(zip.files); @@ -231,6 +230,7 @@ export async function getFB2Metadata( fb2Blob.type === "application/zip" || fb2Blob.type === "application/x-zip-compressed" ) { + const JSZip = (await import("jszip")).default; const zip = await JSZip.loadAsync(fb2Blob); const files = Object.keys(zip.files); diff --git a/web/src/reader/reader-shell.ts b/web/src/reader/reader-shell.ts index 53d4ff0..4c35235 100644 --- a/web/src/reader/reader-shell.ts +++ b/web/src/reader/reader-shell.ts @@ -1,12 +1,11 @@ import { Alpine } from "../alpine"; -import { getReaderMetadata, updateReadingProgress } from "../api"; +import { getReaderMetadata } from "../api"; +import "./comic/panel-editor"; import { createReaderContext, type ReaderContext } from "./core/reader-context"; import { getState, setState } from "./core/reader-state"; import { createNavigationAPI } from "./core/reader-navigation"; import { readerEvents } from "./core/reader-events"; -type ReaderType = "ebook" | "pdf" | "comic" | "manga"; - interface UniversalReader { type: "ebook"; cif: any; @@ -35,7 +34,7 @@ interface MangaReader { type CurrentReader = UniversalReader | PDFReader | ComicReader | MangaReader; let currentReader: CurrentReader | null = null; -let readerMetadata: import("../types/reader").ReaderMetadata | null = null; +let readerMetadata: ReaderMetadata | null = null; // ============================================================ // Feature Registry @@ -90,41 +89,62 @@ async function initializeFeatures(context: ReaderContext): Promise { async function initializeReader(): Promise { const mediaItemId = document.body.dataset.mediaItemId; - if (!mediaItemId) return; - - readerMetadata = await getReaderMetadata(mediaItemId); - - switch (readerMetadata.library_type) { - case "ebook": - currentReader = await initializeEbookReader(readerMetadata); - break; - case "pdf": - currentReader = await initializePDFReader(readerMetadata); - break; - case "comic": - currentReader = await initializeComicReader(readerMetadata); - break; - case "manga": - currentReader = await initializeMangaReader(readerMetadata); - break; + console.log("Initializing reader for:", mediaItemId); + if (!mediaItemId) { + console.error("No mediaItemId on body"); + return; } - if (!currentReader) return; + try { + readerMetadata = await getReaderMetadata(mediaItemId); + console.log("Got metadata:", readerMetadata); + console.log("Library type:", readerMetadata.library_type_name); + console.log("Mime type:", readerMetadata.mime_type); + switch (readerMetadata.format_group) { + case "reflowable": + if ( + readerMetadata.mime_type === "application/epub+zip" || + readerMetadata.file_path.endsWith(".epub") + ) { + currentReader = await initializeEbookReader(readerMetadata); + } + break; + case "fixed_layout": + currentReader = await initializePDFReader(readerMetadata); + break; + case "comic_archive": + // Check manga_type or library_type_name + if ( + readerMetadata.manga_type === "yes" || + readerMetadata.manga_type === "yes_and_right_to_left" || + readerMetadata.library_type_name === "manga" + ) { + currentReader = await initializeMangaReader(readerMetadata); + } else { + currentReader = await initializeComicReader(readerMetadata); + } + break; + } - setState({ currentReader, readerMetadata }); + if (!currentReader) return; - const context = createReaderContext( - getState, - setState, - createNavigationAPI(), - () => - (context.render = () => { - // Delegates to navigation module - }), - ); + setState({ currentReader, readerMetadata }); - await initializeFeatures(context); - readerEvents.emit("readerReady", currentReader); + const context = createReaderContext( + getState, + setState, + createNavigationAPI(), + () => + (context.render = () => { + // Delegates to navigation module + }), + ); + + await initializeFeatures(context); + readerEvents.emit("readerReady", currentReader); + } catch (error) { + console.error("Render initialization failed:", error); + } } async function initializeEbookReader(metadata: any): Promise { @@ -196,7 +216,7 @@ function getFileExtension(filepath: string): string { // ============================================================ Alpine.data("readerShell", () => ({ - init() { + initReader() { initializeReader(); },