diff --git a/web/src/reader/core/reader-navigation.ts b/web/src/reader/core/reader-navigation.ts
index 9bd44a7..4f7f35c 100644
--- a/web/src/reader/core/reader-navigation.ts
+++ b/web/src/reader/core/reader-navigation.ts
@@ -1,11 +1,10 @@
import { getDefaultSettings } from "../settings-manager";
-import { getState, setState, getCurrentPage } from "./reader-state";
+import { getState, setState } from "./reader-state";
import { readerEvents } from "./reader-events";
import { updateReadingProgress } from "./reader-services";
import {
calculatePagesForEbook,
calculateProgressPercentage,
- getCurrentPageFromScroll,
type PageCalculationResult,
} from "../ebook/page-calculator";
import { UniversalReader } from "../reader-shell";
@@ -18,46 +17,45 @@ export function createNavigationAPI() {
nextPage: () => {
const state = getState();
if (!state.currentReader) return;
-
readerEvents.emit("beforePageChange", state.currentReader);
-
if (state.currentReader.type === "ebook") {
- const container = document.getElementById("reader-content");
- const viewportHeightAdjusted = window.innerHeight - 120;
-
if (pageCalculationResult) {
- const currentChapter = pageCalculationResult.chapterMap.get(
- state.currentReader.currentSpineIndex,
- );
+ const currentPage = state.currentReader.currentPage || 1;
- if (currentChapter && container) {
- const currentPageInChapter = Math.floor(
- container.scrollTop / viewportHeightAdjusted,
+ if (currentPage < pageCalculationResult.totalPages) {
+ const nextPage = currentPage + 1;
+ state.currentReader.currentPage = nextPage;
+
+ // Check if we need to move to next spine
+ const currentChapter = pageCalculationResult.chapterMap.get(
+ state.currentReader.currentSpineIndex,
);
- const pagesInCurrentChapter =
- currentChapter.endPage - currentChapter.startPage + 1;
- if (currentPageInChapter < pagesInCurrentChapter - 1) {
- // Still pages left in current chapter - just scroll down
- container.scrollTop =
- (currentPageInChapter + 1) * viewportHeightAdjusted;
- } else if (
- state.currentReader.currentSpineIndex <
- state.currentReader.cif.spine.length - 1
- ) {
- // At end of chapter, move to next spine
- state.currentReader.currentSpineIndex++;
- renderSpineItem();
+ if (currentChapter && nextPage > currentChapter.endPage) {
+ // Move to next spine
+ if (
+ state.currentReader.currentSpineIndex <
+ state.currentReader.cif.spine.length - 1
+ ) {
+ state.currentReader.currentSpineIndex++;
+ }
}
+
+ setState({ currentReader: state.currentReader });
+ renderSpineItem();
+ sendProgressUpdate();
+ readerEvents.emit("pageChanged", nextPage);
}
} else {
- // Fallback: just move to next spine
+ // Fallback: spine-based navigation
if (
state.currentReader.currentSpineIndex <
state.currentReader.cif.spine.length - 1
) {
state.currentReader.currentSpineIndex++;
+ setState({ currentReader: state.currentReader });
renderSpineItem();
+ sendProgressUpdate();
}
}
} else if (state.currentReader.type === "pdf") {
@@ -78,48 +76,54 @@ export function createNavigationAPI() {
renderComicPage();
}
}
-
setState({ currentReader: state.currentReader });
- sendProgressUpdate();
- readerEvents.emit("pageChanged", getCurrentPage());
readerEvents.emit("afterPageChange", state.currentReader);
},
previousPage: () => {
const state = getState();
if (!state.currentReader) return;
-
readerEvents.emit("beforePageChange", state.currentReader);
-
if (state.currentReader.type === "ebook") {
- const container = document.getElementById("reader-content");
- const viewportHeightAdjusted = window.innerHeight - 120;
-
if (pageCalculationResult) {
- const currentChapter = pageCalculationResult.chapterMap.get(
- state.currentReader.currentSpineIndex,
- );
+ const currentPage = state.currentReader.currentPage || 1;
- if (currentChapter && container) {
- const currentPageInChapter = Math.floor(
- container.scrollTop / viewportHeightAdjusted,
+ if (currentPage > 1) {
+ const prevPage = currentPage - 1;
+
+ // Check if we need to move to previous spine
+ const currentChapter = pageCalculationResult.chapterMap.get(
+ state.currentReader.currentSpineIndex,
);
- if (currentPageInChapter > 0) {
- // Not at start of chapter - just scroll up
- container.scrollTop =
- (currentPageInChapter - 1) * viewportHeightAdjusted;
- } else if (state.currentReader.currentSpineIndex > 0) {
- // At start of chapter, move to previous spine
- state.currentReader.currentSpineIndex--;
- renderSpineItem();
+ if (currentChapter && prevPage < currentChapter.startPage) {
+ // Move to previous spine
+ if (state.currentReader.currentSpineIndex > 0) {
+ state.currentReader.currentSpineIndex--;
+ const prevChapter = pageCalculationResult.chapterMap.get(
+ state.currentReader.currentSpineIndex,
+ );
+ state.currentReader.currentPage =
+ prevChapter?.endPage || prevPage;
+ } else {
+ state.currentReader.currentPage = 1;
+ }
+ } else {
+ state.currentReader.currentPage = prevPage;
}
+
+ setState({ currentReader: state.currentReader });
+ renderSpineItem();
+ sendProgressUpdate();
+ readerEvents.emit("pageChanged", state.currentReader.currentPage);
}
} else {
- // Fallback: just move to previous spine
+ // Fallback: spine-based navigation
if (state.currentReader.currentSpineIndex > 0) {
state.currentReader.currentSpineIndex--;
+ setState({ currentReader: state.currentReader });
renderSpineItem();
+ sendProgressUpdate();
}
}
} else if (state.currentReader.type === "pdf") {
@@ -136,23 +140,41 @@ export function createNavigationAPI() {
renderComicPage();
}
}
-
setState({ currentReader: state.currentReader });
- sendProgressUpdate();
- readerEvents.emit("pageChanged", getCurrentPage());
readerEvents.emit("afterPageChange", state.currentReader);
},
- goToPage: (page: number) => {
+ goToPage: async (page: number) => {
const state = getState();
if (!state.currentReader) return;
-
readerEvents.emit("beforePageChange", state.currentReader);
-
if (state.currentReader.type === "ebook") {
- if (page >= 0 && page < state.currentReader.cif.spine.length) {
- state.currentReader.currentSpineIndex = page;
- renderSpineItem();
+ if (pageCalculationResult) {
+ // Validate page number
+ if (page < 1 || page > pageCalculationResult.totalPages) {
+ return;
+ }
+
+ // Find which spine contains this page
+ for (const chapter of pageCalculationResult.chapters) {
+ if (page >= chapter.startPage && page <= chapter.endPage) {
+ state.currentReader.currentSpineIndex = chapter.spineIndex;
+ state.currentReader.currentPage = page;
+ break;
+ }
+ }
+
+ setState({ currentReader: state.currentReader });
+ await renderSpineItem();
+ sendProgressUpdate();
+ } else {
+ // Fallback: treat page number as spine index
+ if (page >= 0 && page < state.currentReader.cif.spine.length) {
+ state.currentReader.currentSpineIndex = page;
+ state.currentReader.currentPage = page + 1;
+ setState({ currentReader: state.currentReader });
+ await renderSpineItem();
+ }
}
} else if (state.currentReader.type === "pdf") {
if (page >= 1 && page <= (state.readerMetadata?.total_pages || 0)) {
@@ -163,14 +185,12 @@ export function createNavigationAPI() {
state.currentReader.type === "comic" ||
state.currentReader.type === "manga"
) {
- if (page >= 0 && page < state.currentReader.images.length) {
+ if (page >= 0 && page < (state.currentReader as any).images.length) {
state.currentReader.currentPage = page;
renderComicPage();
}
}
-
setState({ currentReader: state.currentReader });
- sendProgressUpdate();
readerEvents.emit("pageChanged", page);
readerEvents.emit("afterPageChange", state.currentReader);
},
@@ -195,9 +215,7 @@ export async function initializePageCalculation() {
readerEvents.on("settings:changed", async (settings: any) => {
const state = getState();
if (!state.currentReader || state.currentReader.type !== "ebook") return;
-
console.log("Recalculating pages due to settings change...");
-
const viewportWidth = window.innerWidth;
pageCalculationResult = await calculatePagesForEbook(
state.currentReader.cif,
@@ -208,9 +226,21 @@ export async function initializePageCalculation() {
marginWidth: settings.margin_width,
},
);
-
state.currentReader.pageCalculationResult = pageCalculationResult;
setState({ currentReader: state.currentReader });
+ // Re-render the current page with new settings
+ await renderSpineItem();
+
+ // Emit event so progress display updates
+ const currentPage = state.currentReader.currentPage || 1;
+ readerEvents.emit("progressUpdated", {
+ currentPage,
+ totalPages: pageCalculationResult.totalPages,
+ percentage: calculateProgressPercentage(
+ pageCalculationResult,
+ currentPage,
+ ),
+ });
});
const state = getState();
if (!state.currentReader || state.currentReader.type !== "ebook") return;
@@ -250,104 +280,67 @@ export async function initializePageCalculation() {
export async function renderSpineItem() {
const state = getState();
if (state.currentReader?.type !== "ebook") return;
+
+ if (!state.currentReader.currentPage) {
+ state.currentReader.currentPage = 1;
+ }
+
+ const container = document.getElementById("reader-content");
+ if (!container) return;
+
const spineItem =
state.currentReader.cif.spine[state.currentReader.currentSpineIndex];
- const container = document.getElementById("reader-content");
- if (pageCalculationResult) {
- const viewportHeight = window.innerHeight;
- const currentPage = getCurrentPageFromScroll(
- pageCalculationResult,
- state.currentReader.currentSpineIndex,
- container.scrollTop,
- viewportHeight,
- );
+ if (!spineItem) return;
+ // Get page calculation result
+ const chapter = pageCalculationResult?.chapterMap.get(
+ state.currentReader.currentSpineIndex,
+ );
- // Store computed page for UI display
- state.currentReader.currentPage = currentPage;
- setState({ currentReader: state.currentReader });
+ // Calculate which page within the chapter we're on
+ let currentPageContent = "";
+ let currentPageNumber = state.currentReader.currentPage || 1;
- const percentage = calculateProgressPercentage(
- pageCalculationResult,
- currentPage,
- );
- readerEvents.emit("progressUpdated", {
- currentPage,
- totalPages: pageCalculationResult.totalPages,
- percentage,
- });
+ if (chapter?.pages && chapter.pages.length > 0) {
+ // Calculate which page of this spine item to show
+ const globalPage = state.currentReader.currentPage || 1;
+ const pageWithinChapter = Math.max(0, globalPage - chapter.startPage);
+ const pageIndex = Math.min(pageWithinChapter, chapter.pages.length - 1);
+
+ currentPageContent = chapter.pages[pageIndex]?.html || chapter.content;
+ currentPageNumber = chapter.startPage + pageIndex;
+ } else {
+ // Fallback: load full content if pages not calculated yet
+ const resources = state.currentReader.cif.resources;
+ const contentBlob = resources?.get(spineItem.content);
+ if (contentBlob) {
+ currentPageContent = await contentBlob.text();
+ } else {
+ console.error(
+ "Spine item content not found in resources:",
+ spineItem.content,
+ );
+ container.innerHTML = `
Error: Could not load chapter content
`;
+ return;
+ }
}
- if (!container || !spineItem) return;
- // Get the actual content from resources using the href
- const resources = state.currentReader.cif.resources;
- console.log("ALL resource keys:", Array.from(resources.keys()));
- const contentBlob = resources?.get(spineItem.content);
- if (!contentBlob) {
- // Fallback: try to fetch directly if not in resources
- console.error(
- "Spine item content not found in resources:",
- spineItem.content,
- );
- container.innerHTML = `