From 5caf66a6fc700c66996982cc9edc1f03f8fbea9c Mon Sep 17 00:00:00 2001 From: PJ Date: Mon, 22 Jun 2026 13:38:35 -0400 Subject: [PATCH] fix(import): import the cover image The EPUB cover was discarded and replaced with the default generated cover. Locate the cover via the manifest cover-image property or the meta name="cover" reference, resolve it to a data URI, and import it as an image cover. Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk --- src/import/epub.ts | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/import/epub.ts b/src/import/epub.ts index 8b29f6a..b4bdb94 100644 --- a/src/import/epub.ts +++ b/src/import/epub.ts @@ -1,7 +1,7 @@ import { invoke } from "@tauri-apps/api/core"; import { open } from "@tauri-apps/plugin-dialog"; import type { JSONContent } from "@tiptap/core"; -import { type Book, type BookMetadata, type Chapter, createCover } from "../model/book"; +import { type Book, type BookMetadata, type Chapter, type Cover, createCover } from "../model/book"; export interface RawFile { path: string; @@ -357,6 +357,11 @@ function readNavTitles( return titles; } +function fileToDataUri(file: RawFile, mime: string): string { + const data = file.encoding === "base64" ? file.data : btoa(unescape(encodeURIComponent(file.data))); + return `data:${mime};base64,${data}`; +} + function resolveImages( doc: Document, baseDir: string, @@ -375,12 +380,29 @@ function resolveImages( img.removeAttribute("src"); return; } - const mime = mimeByPath.get(resolved) || mimeFromExt(resolved); - const data = file.encoding === "base64" ? file.data : btoa(unescape(encodeURIComponent(file.data))); - img.setAttribute("src", `data:${mime};base64,${data}`); + img.setAttribute("src", fileToDataUri(file, mimeByPath.get(resolved) || mimeFromExt(resolved))); }); } +function findCover( + opf: Document, + manifest: Map, + byPath: Map, +): Cover { + let item = Array.from(manifest.values()).find((m) => m.properties.split(/\s+/).includes("cover-image")); + if (!item) { + const metaCover = Array.from(opf.getElementsByTagName("*")).find( + (el) => el.localName === "meta" && el.getAttribute("name") === "cover", + ); + const id = metaCover?.getAttribute("content"); + if (id) item = manifest.get(id); + } + if (!item) return createCover(); + const file = byPath.get(normalize(item.href)); + if (!file) return createCover(); + return { ...createCover(), kind: "image", image: fileToDataUri(file, item.mediaType || mimeFromExt(item.href)) }; +} + function buildChapter( file: RawFile, baseDir: string, @@ -455,7 +477,7 @@ export function filesToBook(files: RawFile[], fallbackName = "Imported book"): B metadata, theme: "quiet-press", settings: { trim: "6x9", bleed: true }, - cover: createCover(), + cover: findCover(opf, manifest, byPath), chapters, }; }