From e584200369586cc950576778484255b87d5f6410 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 7 Jun 2026 11:45:33 -0400 Subject: [PATCH] feat(reader): send context_text with progress for reflowable EPUBs The web reader now captures ~100 chars of visible text from the foliate relocate event's range and includes it as context_text in the progress PUT body for reflowable formats. This enables the server's reverseByTextSearch fallback in ConvertStandardToCRE, which is critical for single-file EPUBs (e.g. 1984) where foliate emits coarse or fake-section CFIs that CREngine cannot directly resolve. Previously only the KOReader plugin sent context_text; the web reader's omission left the fallback unusable, causing percentage-based position estimation that was off by ~1 page. Changes: - Add contextText state property (line 387) - Extract visible text from e.detail.range in relocate handler, normalize whitespace, and slice to 100 chars (lines 508-512) - Include context_text in saveProgress PUT body for reflowable EPUBs only, alongside epubcfi (line 575) The server-side chain was already wired: media.go accepts it, progress.go stores it, and koreader.go passes it to the CFI converter. No Go changes needed. --- web/src/reader/reader.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/src/reader/reader.ts b/web/src/reader/reader.ts index 0ff9d53..ad9882d 100644 --- a/web/src/reader/reader.ts +++ b/web/src/reader/reader.ts @@ -384,6 +384,7 @@ document.addEventListener("alpine:init", () => { mediaItemId: "" as string, saveTimeout: null as ReturnType | null, initTime: 0 as number, + contextText: "" as string, readingTheme: "light" as string, readingMode: "light" as string, readingFont: "literata" as string, @@ -504,6 +505,11 @@ document.addEventListener("alpine:init", () => { slider.value = fraction; slider.title = progressStr; } + const range = e.detail.range as Range | undefined; + if (range) { + const text = range.toString().replace(/\s+/g, " ").trim(); + this.contextText = text ? text.slice(0, 100) : ""; + } this.debouncedSaveProgress(fraction, location, cfi); }); const slider = document.getElementById( @@ -566,6 +572,7 @@ document.addEventListener("alpine:init", () => { }; if (!this.isFixedLayout) { body.epubcfi = cfi || ""; + body.context_text = this.contextText || ""; } const tocItem = this.lastRelocateDetail?.tocItem;