diff --git a/web/src/reader/core/reader-context.ts b/web/src/reader/core/reader-context.ts index cda67f6..5cb2f89 100644 --- a/web/src/reader/core/reader-context.ts +++ b/web/src/reader/core/reader-context.ts @@ -1,17 +1,10 @@ -import { PageCalculationResult } from "../formats/reflowable/page-calculator"; - -interface UniversalReader { - type: "ebook"; - cif: any; - currentSpineIndex: number; - currentPage?: number; - pageCalculationResult?: PageCalculationResult; - currentScrollPosition?: number; -} +import { readerEvents, ReaderEventType } from "./reader-events"; +import { UniversalReader } from "../reader-shell"; +import { PDFDocumentProxy } from "pdfjs-dist"; interface PDFReader { type: "pdf"; - doc: any; + doc: PDFDocumentProxy; currentPage: number; } @@ -87,5 +80,3 @@ export function createReaderContext( }, }; } - -import { readerEvents, ReaderEventType } from "./reader-events"; diff --git a/web/src/reader/core/reader-navigation.ts b/web/src/reader/core/reader-navigation.ts index 573a1e3..462efe9 100644 --- a/web/src/reader/core/reader-navigation.ts +++ b/web/src/reader/core/reader-navigation.ts @@ -2,11 +2,11 @@ import { getDefaultSettings } from "../settings-manager"; import { getState, setState } from "./reader-state"; import { readerEvents } from "./reader-events"; import { updateReadingProgress } from "./reader-services"; -import { UniversalReader } from "../reader-shell"; +import { ComicReader, MangaReader, UniversalReader } from "../reader-shell"; import * as reflowableNav from "../formats/reflowable/navigation"; import * as progressTracker from "../formats/reflowable/progress-tracker"; import * as contentRenderer from "../formats/reflowable/content-renderer"; -import type { ReflowableBook } from "../formats/reflowable/types"; +import { CurrentReader } from "./reader-context"; export function createNavigationAPI() { return { @@ -17,7 +17,7 @@ export function createNavigationAPI() { if (state.currentReader.type === "ebook") { // NEW: Use reflowable navigation - const book = state.currentReader as ReflowableBook; + const book = state.currentReader as UniversalReader; if (!reflowableNav.canGoNext(book)) { return; // Already at last page @@ -37,10 +37,7 @@ export function createNavigationAPI() { setState({ currentReader: updatedBook }); // Render content - const pageData = - updatedBook.pagination?.pageMap.get(position.currentPage - 1) || - null; - contentRenderer.renderPage(container, content, pageData); + contentRenderer.renderPage(container, content); // Update UI updatePageIndicator(updatedBook); @@ -77,7 +74,7 @@ export function createNavigationAPI() { if (state.currentReader.type === "ebook") { // NEW: Use reflowable navigation - const book = state.currentReader as ReflowableBook; + const book = state.currentReader as UniversalReader; if (!reflowableNav.canGoPrevious(book)) { return; // Already at first page @@ -97,10 +94,7 @@ export function createNavigationAPI() { setState({ currentReader: updatedBook }); // Render content - const pageData = - updatedBook.pagination?.pageMap.get(position.currentPage - 1) || - null; - contentRenderer.renderPage(container, content, pageData); + contentRenderer.renderPage(container, content); // Update UI updatePageIndicator(updatedBook); @@ -132,7 +126,7 @@ export function createNavigationAPI() { if (state.currentReader.type === "ebook") { // NEW: Use reflowable navigation - const book = state.currentReader as ReflowableBook; + const book = state.currentReader as UniversalReader; const { success, position, content } = reflowableNav.goToPage( book, @@ -151,10 +145,7 @@ export function createNavigationAPI() { setState({ currentReader: updatedBook }); // Render content - const pageData = - updatedBook.pagination?.pageMap.get(position.currentPage - 1) || - null; - contentRenderer.renderPage(container, content, pageData); + contentRenderer.renderPage(container, content); // Update UI updatePageIndicator(updatedBook); @@ -167,10 +158,10 @@ export function createNavigationAPI() { sendProgressUpdate(); } } else if ( - state.currentReader.type === "comic" || - state.currentReader.type === "manga" + state.currentReader && + isComicOrMangaReader(state.currentReader) ) { - if (page >= 0 && page < (state.currentReader as any).images.length) { + if (page >= 0 && page < state.currentReader.images.length) { state.currentReader.currentPage = page; renderComicPage(); sendProgressUpdate(); @@ -187,8 +178,8 @@ export function createNavigationAPI() { const chapters = state.readerMetadata.chapter_metadata.chapters; if (chapterIndex < 0 || chapterIndex >= chapters.length) return; const chapter = chapters[chapterIndex]; - if ((chapter as any).spine_index !== undefined) { - state.currentReader.currentSpineIndex = (chapter as any).spine_index; + if (chapter.spine_index !== undefined) { + state.currentReader.currentSpineIndex = chapter.spine_index; setState({ currentReader: state.currentReader }); renderSpineItem(); sendProgressUpdate(); @@ -201,8 +192,14 @@ export function createNavigationAPI() { }; } +function isComicOrMangaReader( + reader: CurrentReader, +): reader is ComicReader | MangaReader { + return reader.type === "comic" || reader.type === "manga"; +} + // Helper function to update page indicator -function updatePageIndicator(book: ReflowableBook): void { +function updatePageIndicator(book: UniversalReader): void { const { currentPage, totalPages, percentage } = progressTracker.calculateProgress(book); @@ -450,62 +447,71 @@ function getFontStack(font: string): string { function sendProgressUpdate(): void { const state = getState(); if (!state.currentReader || !state.readerMetadata) return; - const mediaItemId = - state.readerMetadata.id || - document.body.dataset.mediaItemId || - window.location.pathname.split("/").pop(); + const mediaItemId = state.readerMetadata.id; if (!mediaItemId) { console.warn("No mediaItemId available for progress update"); return; } - const container = document.getElementById("reader-content"); let currentPage = 1; let totalPages = 1; let percentage = 0; - let character = 0; if (state.currentReader.type === "ebook") { - const book = state.currentReader as ReflowableBook; + const book = state.currentReader as UniversalReader; const posData = progressTracker.getPositionForSave(book); - updateReadingProgress({ - book_id: state.currentReader.id, - page: posData.page, - cfi: posData.cfi, - progress: posData.progress, - }); + currentPage = posData.page; + totalPages = book.pagination?.totalPages || 1; + percentage = posData.progress; + + // Correct: 3 separate parameters + updateReadingProgress( + mediaItemId, // 1st param: mediaItemId + { + // 2nd param: ReadingProgress object + current_page: posData.page, + total_pages: totalPages, + cfi: posData.cfi, + percentage, + last_read_at: new Date().toISOString(), + }, + { + // 3rd param (optional): ExtendedProgress + percentage, + }, + ); } else if (state.currentReader.type === "pdf") { - totalPages = state.readerMetadata.total_pages || 0; + totalPages = state.readerMetadata.total_pages || 1; currentPage = state.currentReader.currentPage; + percentage = currentPage / totalPages; + + updateReadingProgress( + mediaItemId, + { + current_page: currentPage, + total_pages: totalPages, + last_read_at: new Date().toISOString(), + percentage, + }, + { percentage }, + ); } else if ( state.currentReader.type === "comic" || state.currentReader.type === "manga" ) { totalPages = state.currentReader.images.length; currentPage = state.currentReader.currentPage; + percentage = currentPage / totalPages; + + updateReadingProgress( + mediaItemId, + { + current_page: currentPage, + total_pages: totalPages, + last_read_at: new Date().toISOString(), + percentage, + }, + { percentage }, + ); } - const reader = state.currentReader as UniversalReader; - updateReadingProgress({ - book_id: state.currentReader.id, - page: state.currentReader.currentPage, - progress: - state.currentReader.currentPage / - (state.readerMetadata?.total_pages || 1), - }); readerEvents.emit("progressUpdated", { currentPage, totalPages, percentage }); } - -function getCharacterOffset(): number { - const container = document.getElementById("reader-content"); - if (!container) return 0; - const viewportHeight = window.innerHeight - 120; - const scrollTop = container.scrollTop; - const viewportIndex = Math.floor(scrollTop / viewportHeight); - const allText = container.textContent || ""; - const totalChars = allText.length; - const viewportCount = Math.max( - 1, - Math.ceil(container.scrollHeight / viewportHeight), - ); - const charsPerViewport = totalChars / viewportCount; - return Math.floor(viewportIndex * charsPerViewport); -} diff --git a/web/src/reader/core/reader-services.ts b/web/src/reader/core/reader-services.ts index afa2844..e702dfe 100644 --- a/web/src/reader/core/reader-services.ts +++ b/web/src/reader/core/reader-services.ts @@ -16,24 +16,26 @@ export async function updateReadingProgress( return; } - const payload: any = { + const payload: ReadingProgress = { current_page: progress.current_page, total_pages: progress.total_pages, - epubcfi: progress.epubcfi || "", // ADD THIS - percentage: extended?.percentage || 0, // ADD THIS + cfi: progress.cfi || "", + percentage: extended?.percentage || 0, + last_read_at: new Date().toISOString(), }; const response = await apiPut( `/media-items/${mediaItemId}/progress`, payload, ); if (!response.ok) { - const errorText = await response.text(); - console.warn("Progress update failed:", response.status, errorText); + const clonedResponse = response.clone(); + const errorText = await clonedResponse.text(); + console.warn("Progress update failed:", clonedResponse.status, errorText); return; } } -export function getChapterNavigation(chapters: any[]) { +export function getChapterNavigation(chapters: Chapter[]) { return { getNextChapter: (currentPage: number) => { for (let i = 0; i < chapters.length - 1; i++) { diff --git a/web/src/reader/formats/reflowable/ebook/search.ts b/web/src/reader/formats/reflowable/ebook/search.ts index fe1077e..d06c270 100644 --- a/web/src/reader/formats/reflowable/ebook/search.ts +++ b/web/src/reader/formats/reflowable/ebook/search.ts @@ -1,7 +1,7 @@ // Search within ebook content // Feature Registration Pattern implementation -import type { ReaderContext } from "../core/reader-context"; +import type { ReaderContext } from "../../../core/reader-context"; export function init(context: ReaderContext): void { let ebookData: any = null; @@ -139,4 +139,5 @@ function extractSnippet(text: string, offset: number, length: number): string { if (end < text.length) snippet = snippet + "..."; return snippet; -} \ No newline at end of file +} + diff --git a/web/src/reader/formats/reflowable/navigation.ts b/web/src/reader/formats/reflowable/navigation.ts index a808d16..e15ee5c 100644 --- a/web/src/reader/formats/reflowable/navigation.ts +++ b/web/src/reader/formats/reflowable/navigation.ts @@ -1,14 +1,15 @@ // Import types -import type { ReadingPosition, ReflowableBook } from "./types"; +import type { ReadingPosition } from "./types"; import { getPageContent, findPageByCFI, createPositionFromPage, } from "./page-calculator"; +import { UniversalReader } from "../../reader-shell"; // Navigate to specific page export function goToPage( - book: ReflowableBook, + book: UniversalReader, targetPage: number, ): { success: boolean; @@ -30,7 +31,7 @@ export function goToPage( } // Navigate to next page -export function nextPage(book: ReflowableBook): { +export function nextPage(book: UniversalReader): { success: boolean; position: ReadingPosition; content: string; @@ -40,7 +41,7 @@ export function nextPage(book: ReflowableBook): { } // Navigate to previous page -export function previousPage(book: ReflowableBook): { +export function previousPage(book: UniversalReader): { success: boolean; position: ReadingPosition; content: string; @@ -51,7 +52,7 @@ export function previousPage(book: ReflowableBook): { // Jump to specific CFI export function goToCFI( - book: ReflowableBook, + book: UniversalReader, cfi: string, ): { success: boolean; @@ -78,23 +79,23 @@ function createDefaultPosition(): ReadingPosition { } // Check if navigation is possible -export function canGoNext(book: ReflowableBook): boolean { +export function canGoNext(book: UniversalReader): boolean { return book.position.currentPage < (book.pagination?.totalPages || 1); } // Check if previous navigation is possible -export function canGoPrevious(book: ReflowableBook): boolean { +export function canGoPrevious(book: UniversalReader): boolean { return book.position.currentPage > 1; } // Get progress percentage -export function getProgressPercentage(book: ReflowableBook): number { +export function getProgressPercentage(book: UniversalReader): number { return Math.round(book.position.progress * 100); } // Update book position (after resize/recalculation) export function updatePosition( - book: ReflowableBook, + book: UniversalReader, newCFI?: string, ): ReadingPosition { if (newCFI && book.pagination) { diff --git a/web/src/reader/formats/reflowable/page-calculator.ts b/web/src/reader/formats/reflowable/page-calculator.ts index ce69ab4..687751d 100644 --- a/web/src/reader/formats/reflowable/page-calculator.ts +++ b/web/src/reader/formats/reflowable/page-calculator.ts @@ -1,4 +1,5 @@ // Import types +import { UniversalReader } from "../../reader-shell"; import type { SpineItem, SpineInfo, @@ -6,7 +7,6 @@ import type { PaginationData, PaginationSettings, ReadingPosition, - ReflowableBook, } from "./types"; // Constants for word count estimation (from Kavita) @@ -452,7 +452,7 @@ export function shouldRecalculate( // Create position object from page number export function createPositionFromPage( - book: ReflowableBook, + book: UniversalReader, page: number, ): ReadingPosition { if (!book.pagination) { diff --git a/web/src/reader/formats/reflowable/parser.ts b/web/src/reader/formats/reflowable/parser.ts index 86a63a3..4a3cba7 100644 --- a/web/src/reader/formats/reflowable/parser.ts +++ b/web/src/reader/formats/reflowable/parser.ts @@ -1,51 +1,113 @@ // Import types and existing parsers -import type { ReflowableBook, SpineItem, TOCItem } from "./types"; +import type { TOCItem } from "./types"; import { parseEPUB } from "../../parsers/epub-parsers"; import { parseFB2 } from "../../parsers/fb2-parser"; import { parseTXT } from "../../parsers/txt-parser"; import { parseHTML } from "../../parsers/html-parser"; +import { UniversalReader } from "../../reader-shell"; // Parse any reflowable format export async function parseReflowable( file: File, format: "epub" | "fb2" | "txt" | "html", -): Promise { +): Promise { switch (format) { - case "epub": - return await parseEPUB(file); - case "fb2": - return await parseFB2(file); - case "txt": - return await parseTXT(file); - case "html": - return await parseHTML(file); + case "epub": { + const cif = await parseEPUB(file); // Get EbookCIF + return { + // Wrap in UniversalReader structure + type: "ebook", + cif, // ← The EbookCIF goes here + currentSpineIndex: 0, + currentPage: 1, + pagination: null, + position: { + currentPage: 1, + spineIndex: 0, + localPageIndex: 0, + cfi: "", + progress: 0, + }, + }; + } + case "fb2": { + const cif = await parseFB2(file); + return { + type: "ebook", + cif, + currentSpineIndex: 0, + currentPage: 1, + pagination: null, + position: { + currentPage: 1, + spineIndex: 0, + localPageIndex: 0, + cfi: "", + progress: 0, + }, + }; + } + case "txt": { + const cif = await parseTXT(file); + return { + type: "ebook", + cif, + currentSpineIndex: 0, + currentPage: 1, + pagination: null, + position: { + currentPage: 1, + spineIndex: 0, + localPageIndex: 0, + cfi: "", + progress: 0, + }, + }; + } + case "html": { + const cif = await parseHTML(file); + return { + type: "ebook", + cif, + currentSpineIndex: 0, + currentPage: 1, + pagination: null, + position: { + currentPage: 1, + spineIndex: 0, + localPageIndex: 0, + cfi: "", + progress: 0, + }, + }; + } default: throw new Error(`Unsupported reflowable format: ${format}`); } } // Validate parsed book data -export function validateBook(book: ReflowableBook): boolean { - return book.spine.length > 0 && book.metadata.title !== ""; +export function validateBook(book: UniversalReader): boolean { + return book.cif.spine.length > 0 && book.cif.metadata.title !== ""; } // Get book title -export function getBookTitle(book: ReflowableBook): string { - return book.metadata.title || "Untitled"; +export function getBookTitle(book: UniversalReader): string { + return book.cif.metadata.title || "Untitled"; } // Get book author -export function getBookAuthor(book: ReflowableBook): string { - return book.metadata.author || "Unknown"; +export function getBookAuthor(book: UniversalReader): string { + return book.cif.metadata.author || "Unknown"; } // Get total spine count -export function getSpineCount(book: ReflowableBook): number { - return book.spine.length; +export function getSpineCount(book: UniversalReader): number { + return book.cif.spine.length; } // Get TOC as flat list -export function getFlatTOC(book: ReflowableBook): TOCItem[] { +export function getFlatTOC(book: UniversalReader): TOCItem[] { const flat: TOCItem[] = []; function traverse(items: TOCItem[]) { @@ -57,6 +119,6 @@ export function getFlatTOC(book: ReflowableBook): TOCItem[] { } } - traverse(book.toc); + traverse(book.cif.toc); return flat; } diff --git a/web/src/reader/formats/reflowable/progress-tracker.ts b/web/src/reader/formats/reflowable/progress-tracker.ts index 9a44f5f..af3dbf4 100644 --- a/web/src/reader/formats/reflowable/progress-tracker.ts +++ b/web/src/reader/formats/reflowable/progress-tracker.ts @@ -1,12 +1,13 @@ // Import types -import type { ReflowableBook, ReadingPosition } from "./types"; +import type { ReadingPosition } from "./types"; import { findPageByCFI, createPositionFromPage } from "./page-calculator"; +import { UniversalReader } from "../../reader-shell"; // Update current position export function updateCurrentPosition( - book: ReflowableBook, + book: UniversalReader, position: ReadingPosition, -): ReflowableBook { +): UniversalReader { return { ...book, position, @@ -14,12 +15,12 @@ export function updateCurrentPosition( } // Extract CFI from position -export function getCurrentCFI(book: ReflowableBook): string { +export function getCurrentCFI(book: UniversalReader): string { return book.position.cfi; } // Calculate progress for display -export function calculateProgress(book: ReflowableBook): { +export function calculateProgress(book: UniversalReader): { currentPage: number; totalPages: number; percentage: number; @@ -33,7 +34,7 @@ export function calculateProgress(book: ReflowableBook): { } // Get position for saving to database -export function getPositionForSave(book: ReflowableBook): { +export function getPositionForSave(book: UniversalReader): { cfi: string; progress: number; page: number; @@ -47,7 +48,7 @@ export function getPositionForSave(book: ReflowableBook): { // Restore position from database export function restorePosition( - book: ReflowableBook, + book: UniversalReader, savedCFI: string, savedPage?: number, ): ReadingPosition { diff --git a/web/src/reader/reader-shell.ts b/web/src/reader/reader-shell.ts index 2872e8d..c3f2d4c 100644 --- a/web/src/reader/reader-shell.ts +++ b/web/src/reader/reader-shell.ts @@ -19,30 +19,31 @@ import type { ReadingPosition, } from "./formats/reflowable/types"; import { updatePageDisplay, updateProgressBar } from "./ui/page-display"; +import { PDFDocumentProxy } from "pdfjs-dist"; export interface UniversalReader { type: "ebook"; - cif: any; + cif: EbookCIF; currentSpineIndex: number; + currentScrollPosition?: number; currentPage: number; - pageCalculationResult?: any; pagination?: PaginationData; position?: ReadingPosition; } -interface PDFReader { +export interface PDFReader { type: "pdf"; - doc: any; + doc: PDFDocumentProxy; currentPage: number; } -interface ComicReader { +export interface ComicReader { type: "comic"; images: Blob[]; currentPage: number; } -interface MangaReader { +export interface MangaReader { type: "manga"; images: Blob[]; currentPage: number; @@ -182,7 +183,9 @@ async function initializeReader(): Promise { } } -async function initializeEbookReader(metadata: any): Promise { +async function initializeEbookReader( + metadata: ReaderMetadata, +): Promise { const { parseEbook, requiresServerParsing } = await import("./core/parser-manager"); const needsServer = requiresServerParsing( @@ -191,7 +194,7 @@ async function initializeEbookReader(metadata: any): Promise { ); let ebookFile: Blob; if (needsServer) { - const response = await fetch(`/readers/${metadata.media_item_id}/parse`, { + const response = await fetch(`/readers/${metadata.id}/parse`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -214,15 +217,15 @@ async function initializeEbookReader(metadata: any): Promise { ); console.log("EPUB Parse Result:", { spineLength: cif.spine?.length, - spineItems: cif.spine?.map((s: any) => s.href), + spineItems: cif.spine?.map((s: SpineItem) => s.href), tocLength: cif.toc?.length, keys: Object.keys(cif), }); // ============================================================ // Fetch saved progress from backend // ============================================================ - const savedProgress = await getReadingProgress(metadata.media_item_id); - const savedCFI = savedProgress?.epubcfi; + const savedProgress = await getReadingProgress(metadata.id); + const savedCFI = savedProgress?.cfi; const savedPage = savedProgress?.current_page; console.log("Saved progress:", { savedCFI, savedPage }); // ============================================================ @@ -245,11 +248,10 @@ async function initializeEbookReader(metadata: any): Promise { lineHeight: userSettings.line_height, marginWidth: userSettings.margin_width, }); - const spineItems = cif.spine.map((s: any) => ({ - id: s.id || s.href, + const spineItems = cif.spine.map((s: SpineItem) => ({ + id: s.id, type: "html" as const, - content: s.href, - href: s.href, + content: s.content, })); const pagination = await calculatePagination( spineItems, @@ -265,15 +267,27 @@ async function initializeEbookReader(metadata: any): Promise { // ============================================================ const position: ReadingPosition = restorePosition( { - type: "epub", - spine: spineItems, - resources: cif.resources, - toc: cif.toc || [], - metadata: { - title: cif.metadata.title || metadata.title, - author: cif.metadata.author || metadata.author, - identifier: metadata.id, + type: "ebook", + cif: { + spine: spineItems, + resources: cif.resources, + toc: cif.toc || [], + metadata: { + title: cif.metadata.title || metadata.title, + author: cif.metadata.author || metadata.author, + identifier: metadata.id, + }, + pagination, + position: { + currentPage: 1, + spineIndex: 0, + localPageIndex: 0, + cfi: "", + progress: 0, + }, }, + currentSpineIndex: 0, + currentPage: 1, pagination, position: { currentPage: 1, @@ -300,15 +314,26 @@ async function initializeEbookReader(metadata: any): Promise { const reflowableNav = await import("./formats/reflowable/navigation"); const { success, content: pageContent } = reflowableNav.goToPage( { - type: "epub", - spine: spineItems, - resources: cif.resources, - toc: cif.toc || [], - metadata: { - title: cif.metadata.title || metadata.title, - author: cif.metadata.author || metadata.author, - identifier: metadata.id, + type: "ebook", + cif: { + spine: spineItems, + resources: cif.resources, + toc: cif.toc || [], + metadata: { + title: cif.metadata.title || metadata.title, + author: cif.metadata.author || metadata.author, + language: cif.metadata.language || "en", + publisher: cif.metadata.publisher, + isbn: cif.metadata.isbn, + coverImage: cif.metadata.coverImage, + }, + locations: cif.locations || { + totalCharacters: 0, + estimatedPages: 0, + }, }, + currentSpineIndex: position.spineIndex, + currentPage: position.currentPage, pagination, position, }, @@ -332,18 +357,24 @@ async function initializeEbookReader(metadata: any): Promise { }; } -async function initializePDFReader(metadata: any): Promise { +async function initializePDFReader( + metadata: ReaderMetadata, +): Promise { const { initializePDFReader } = await import("./formats/pdf/pdfjs-wrapper"); return initializePDFReader(metadata); } -async function initializeComicReader(metadata: any): Promise { +async function initializeComicReader( + metadata: ReaderMetadata, +): Promise { const { initializeComicReader } = await import("./formats/comic/image-parser"); return initializeComicReader(metadata); } -async function initializeMangaReader(metadata: any): Promise { +async function initializeMangaReader( + metadata: ReaderMetadata, +): Promise { const { initializeMangaReader } = await import("./formats/comic/image-parser"); return initializeMangaReader(metadata); @@ -460,14 +491,10 @@ function handleResize(): void { updatedBook.position.currentPage, ); if (success) { - const pageData = - newPagination.pageMap.get( - updatedBook.position.currentPage - 1, - ) || null; const { renderPage, } = require("./formats/reflowable/content-renderer"); - renderPage(container, pageContent, pageData); + renderPage(container, pageContent); updatePageDisplay( container, updatedBook.position.currentPage, diff --git a/web/src/types/reader.d.ts b/web/src/types/reader.d.ts index 01cdf4f..b9f7444 100644 --- a/web/src/types/reader.d.ts +++ b/web/src/types/reader.d.ts @@ -79,7 +79,6 @@ interface ReaderMetadata { format_group: "reflowable" | "fixed_layout" | "comic_archive" | "unkown"; library_type_name: "ebooks" | "comics" | "manga" | "pdf"; mime_type: string; - mime_type: string; manga_type: "unknown" | "no" | "yes" | "yes_and_right_to_left"; file_path: string; chapter_metadata?: ChapterMetadata; @@ -104,6 +103,7 @@ interface Chapter { title: string; start_page: number; page_count: number; + spine_index?: number; } interface PanelData {