fix(ebook-reader): load SVG images and fix progress update ID

- Add SVG image support: process <image xlink:href=...> elements
  in addition to HTML <img> tags for cover pages and embedded images
- Fix progress update: use 'id' from API response instead of
  'media_item_id' which the backend never returns for this endpoint
- Update reader context interfaces to include currentPage field
- Add debug logging for all resource keys to help troubleshoot
  image loading issues in future epubs
This commit is contained in:
2026-04-06 17:01:05 -04:00
parent b12040c63f
commit 6805d0b66d
4 changed files with 29 additions and 15 deletions
+5 -1
View File
@@ -28,7 +28,11 @@ interface MangaReader {
readingDirection: "rtl" | "vertical"; readingDirection: "rtl" | "vertical";
} }
type CurrentReader = UniversalReader | PDFReader | ComicReader | MangaReader; export type CurrentReader =
| UniversalReader
| PDFReader
| ComicReader
| MangaReader;
export interface ReaderContext { export interface ReaderContext {
getState: () => { getState: () => {
+22 -12
View File
@@ -279,6 +279,7 @@ export async function renderSpineItem() {
if (!container || !spineItem) return; if (!container || !spineItem) return;
// Get the actual content from resources using the href // Get the actual content from resources using the href
const resources = state.currentReader.cif.resources; const resources = state.currentReader.cif.resources;
console.log("ALL resource keys:", Array.from(resources.keys()));
const contentBlob = resources?.get(spineItem.content); const contentBlob = resources?.get(spineItem.content);
if (!contentBlob) { if (!contentBlob) {
// Fallback: try to fetch directly if not in resources // Fallback: try to fetch directly if not in resources
@@ -326,16 +327,11 @@ export async function renderSpineItem() {
// Rewrite image src paths to use blob URLs from resources // Rewrite image src paths to use blob URLs from resources
const modifiedContent = rewriteImageUrls(contentText, resources); const modifiedContent = rewriteImageUrls(contentText, resources);
const images = modifiedContent.match(/<img[^>]+>/g); const htmlImages = modifiedContent.match(/<img[^>]+>/g);
console.log("Found images in content:", images); const svgImages =
// Test resource lookup modifiedContent.match(/<image[^>]+xlink:href="([^"]+)"[^>]*>/gi) || [];
console.log("Trying to find image:", { const allImages = [...htmlImages, ...svgImages];
direct: resources.get("image/1.png"), console.log("Found images in content:", allImages);
withoutFolder: resources.get("1.png"),
allImageKeys: Array.from(resources.keys()).filter(
(k: string) => k.includes("image") || k.includes("png"),
),
});
// Parse HTML and process images BEFORE setting innerHTML // Parse HTML and process images BEFORE setting innerHTML
const parser = new DOMParser(); const parser = new DOMParser();
@@ -354,6 +350,20 @@ export async function renderSpineItem() {
img.setAttribute("src", blobUrl); img.setAttribute("src", blobUrl);
} }
} }
// Process SVG <image> elements with xlink:href
const svgImgElements = Array.from(doc.querySelectorAll("image"));
for (const img of svgImgElements) {
const src = img.getAttribute("xlink:href");
if (!src) continue;
let blob = findImageInResources(resources, src);
if (blob) {
const blobUrl = URL.createObjectURL(blob);
img.setAttribute("xlink:href", blobUrl);
}
}
container.innerHTML = doc.body.innerHTML; container.innerHTML = doc.body.innerHTML;
// Apply reader styling // Apply reader styling
applyReaderTheme(); applyReaderTheme();
@@ -498,7 +508,7 @@ function sendProgressUpdate() {
if (!state.currentReader || !state.readerMetadata) return; if (!state.currentReader || !state.readerMetadata) return;
const mediaItemId = const mediaItemId =
state.readerMetadata.media_item_id || state.readerMetadata.id ||
document.body.dataset.mediaItemId || document.body.dataset.mediaItemId ||
window.location.pathname.split("/").pop(); window.location.pathname.split("/").pop();
@@ -541,7 +551,7 @@ function sendProgressUpdate() {
const percentage = totalPages > 0 ? (currentPage / totalPages) * 100 : 0; const percentage = totalPages > 0 ? (currentPage / totalPages) * 100 : 0;
updateReadingProgress( updateReadingProgress(
state.readerMetadata.media_item_id, state.readerMetadata.id,
{ {
current_page: currentPage, current_page: currentPage,
total_pages: totalPages, total_pages: totalPages,
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ReaderMetadata, ReaderState } from "./reader-context"; import type { ReaderState } from "./reader-context";
type CurrentReader = import("./reader-context").CurrentReader; type CurrentReader = import("./reader-context").CurrentReader;
+1 -1
View File
@@ -72,7 +72,7 @@ interface ParserCapabilities {
// ============================================================ // ============================================================
interface ReaderMetadata { interface ReaderMetadata {
media_item_id: string; id: string;
title: string; title: string;
author: string; author: string;
cover_image_path: string; cover_image_path: string;