From 2df3ecbf36a0e852b52093810529f8d9b32abd7d Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 12 Sep 2026 14:22:29 -0400 Subject: [PATCH] fix(ui): remove the broken Generate Cover button from the metadata editor Generate Cover has never worked: it fetched the book file using a URL scraped from the cover preview tag (so it downloaded either the existing cover JPEG or, when no cover existed, the detail page HTML), then handed it to foliate-js, which rejects both. Its fixed-layout path also called view.renderer.renderPage(), a method that does not exist in the pinned foliate fork. Every click ended in the same generic 'Cover generation failed' toast. The working alternative already exists server-side: the scanner's PDF/EPUB cover extraction plus the per-book Rescan button, now that the rasterizer renders the CropBox. Users who want a specific image can upload one. Delete web/src/cover-generator.ts, the modal buttons, and the dead generateCover()/coverGenerating/fileUrl plumbing in book-detail.ts. --- templates/book_detail_modals.templ | 41 ++--- templates/book_detail_modals_templ.go | 212 ++++++++++++-------------- web/src/book-detail.ts | 37 ----- web/src/cover-generator.ts | 82 ---------- 4 files changed, 109 insertions(+), 263 deletions(-) delete mode 100644 web/src/cover-generator.ts diff --git a/templates/book_detail_modals.templ b/templates/book_detail_modals.templ index ad1b0b3..1e4242e 100644 --- a/templates/book_detail_modals.templ +++ b/templates/book_detail_modals.templ @@ -290,25 +290,6 @@ templ MetadataEditorModal(book handlers.MediaDetail) {
- -
-
-
- - -
-
- - -
+
+
+ +
+
+ + +
+
diff --git a/templates/book_detail_modals_templ.go b/templates/book_detail_modals_templ.go index 21437c4..ef95d6d 100644 --- a/templates/book_detail_modals_templ.go +++ b/templates/book_detail_modals_templ.go @@ -689,23 +689,7 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "
Click to upload
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, tag := range book.Tags { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "\" class=\"hidden\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "
Vertical
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "Save") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/web/src/book-detail.ts b/web/src/book-detail.ts index faaeeeb..aae5cb1 100644 --- a/web/src/book-detail.ts +++ b/web/src/book-detail.ts @@ -1,6 +1,5 @@ import { Alpine } from "./alpine"; import { showToast } from "./toast"; -import { generateCoverBlob } from "./cover-generator"; import { searchTagSuggestions, type TagSuggestion } from "./tag-dropdown"; function getMediaId(): string { @@ -91,7 +90,6 @@ export { interface MetadataEditorState { openSections: Record; - coverGenerating: boolean; hasExistingCover: boolean; newCoverPreview: string; coverFile: Blob | null; @@ -108,7 +106,6 @@ interface MetadataEditorState { showMetadataEditor(): void; hideMetadataEditor(): void; handleCoverUpload(event: Event): void; - generateCover(): Promise; removeCover(): void; saveMetadata(): Promise; rescanBook(): Promise; @@ -130,12 +127,6 @@ Alpine.data("bookDetail", () => { coverImg?.src && !coverImg.src.includes("placeholder-book.svg"); - const coverPreviewEl = document.querySelector( - ".aspect-\\[2\\/3\\] img", - ) as HTMLImageElement | null; - const coverSrc = coverPreviewEl?.src || ""; - const fileUrl = coverSrc && !coverSrc.includes("placeholder") ? coverSrc : ""; - const initialTags: string[] = []; const tagBadges = document.querySelectorAll("#metadata-editor-modal [data-editor-tag]"); tagBadges.forEach((el) => { @@ -145,7 +136,6 @@ Alpine.data("bookDetail", () => { return { openSections: { basic: true } as Record, - coverGenerating: false, hasExistingCover: !!hasCover, newCoverPreview: "", coverFile: null as Blob | null, @@ -462,33 +452,6 @@ Alpine.data("bookDetail", () => { reader.readAsDataURL(file); }, - async generateCover() { - this.coverGenerating = true; - try { - const formatGroup = - document - .querySelector('[data-format-group]') - ?.getAttribute("data-format-group") || "reflowable"; - - const blob = await generateCoverBlob(fileUrl, formatGroup); - if (!blob) return; - - this.coverFile = blob; - this.coverAction = "upload"; - - const preview = document.getElementById( - "metadata-cover-preview", - ) as HTMLImageElement; - if (preview) { - preview.src = URL.createObjectURL(blob); - } - this.newCoverPreview = URL.createObjectURL(blob); - showToast("Cover generated successfully", "success"); - } finally { - this.coverGenerating = false; - } - }, - removeCover() { this.coverAction = "remove"; this.coverFile = null; diff --git a/web/src/cover-generator.ts b/web/src/cover-generator.ts deleted file mode 100644 index 4955680..0000000 --- a/web/src/cover-generator.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { showToast } from "./toast"; - -function getToken(): string { - return localStorage.getItem("token") || ""; -} - -export async function generateCoverBlob( - fileUrl: string, - formatGroup: string, -): Promise { - try { - const View = (await import("foliate-js/view.js")).default; - const view = new View(); - - const resp = await fetch(fileUrl, { - headers: { Authorization: `Bearer ${getToken()}` }, - }); - if (!resp.ok) { - showToast("Failed to fetch book file for cover generation", "error"); - return null; - } - - const blob = await resp.blob(); - const file = new File([blob], "book", { type: blob.type }); - - const pdfOptions = - formatGroup === "fixed_layout" - ? { - pdf: { - cMapUrl: "/static/vendor/pdfjs/cmaps/", - standardFontDataUrl: "/static/vendor/pdfjs/standard_fonts/", - }, - } - : {}; - - await view.open(file, pdfOptions); - - if (!view.book?.sections?.length) { - showToast("Could not read book sections", "error"); - return null; - } - - if (formatGroup === "fixed_layout") { - const canvas = document.createElement("canvas"); - await view.renderer?.renderPage(view.book.sections[0], canvas); - return new Promise((resolve) => { - canvas.toBlob( - (b) => resolve(b), - "image/jpeg", - 0.85, - ); - }); - } - - const coverHref = view.book.cover; - if (coverHref) { - const coverBlob = await coverHref.blob(); - if (coverBlob.type.startsWith("image/")) { - return coverBlob; - } - return new Promise((resolve) => { - const img = new Image(); - img.onload = () => { - const c = document.createElement("canvas"); - c.width = img.naturalWidth; - c.height = img.naturalHeight; - c.getContext("2d")?.drawImage(img, 0, 0); - c.toBlob((b) => resolve(b), "image/jpeg", 0.85); - }; - img.onerror = () => resolve(null); - img.src = URL.createObjectURL(coverBlob); - }); - } - - showToast("No cover found in book file", "error"); - return null; - } catch (e) { - console.error("Cover generation failed:", e); - showToast("Cover generation failed", "error"); - return null; - } -}