feat(reader): comic treatment for fixed-layout items in comics/manga libraries
PDFs shelved in comics or manga libraries (scanned manga, official manga PDFs with text layers) now get comic reader treatment: bookmarks only — no text-selection highlights, no annotations, no in-book search. Items in ebooks libraries are completely unaffected, and EPUBs keep their existing behavior everywhere. - handlers: new exported ShouldTreatAsComic(libraryType, formatGroup) — true for fixed_layout/comic_archive in manga/comics libraries; the reader JSON API also exposes it as treat_as_comic - router: the SSR reader page fetches the library type and passes TreatAsComic through ReaderMetadata into the init config - reader: when treatAsComic is set, the PDF textLayer selection listener (mouse and touch paths) never attaches so no highlight popover can open; pointer mode is forced to pan (Smart/Text segment hidden); search button and toggleSearch are disabled; previously-created PDF highlights stop rendering - bookmarks are unchanged (already page-index based for fixed layout), and device sync / OPDS / format classification are untouched since format_group stays fixed_layout
This commit is contained in:
@@ -398,6 +398,10 @@ document.addEventListener("alpine:init", () => {
|
||||
isFixedLayout: false,
|
||||
isPDF: false,
|
||||
isComic: false,
|
||||
// Fixed-layout item in a comics/manga library (e.g. a manga PDF):
|
||||
// comic reader treatment — bookmarks only, no text-selection
|
||||
// highlights, no in-book search.
|
||||
treatAsComic: false as boolean,
|
||||
comicFlow: "paged" as string,
|
||||
fxBrightness: 1 as number,
|
||||
fxContrast: 1 as number,
|
||||
@@ -588,8 +592,10 @@ document.addEventListener("alpine:init", () => {
|
||||
formatGroup: string;
|
||||
readingDirection: string;
|
||||
mangaType: string;
|
||||
treatAsComic: boolean;
|
||||
}) {
|
||||
this.mediaItemId = config.mediaItemId;
|
||||
this.treatAsComic = config.treatAsComic;
|
||||
// Reading state (position, bookmarks, annotations) is never baked
|
||||
// into the rendered page: the web reader is intrinsically tied to
|
||||
// the server, so it reads all of it from the APIs at open time —
|
||||
@@ -724,7 +730,11 @@ document.addEventListener("alpine:init", () => {
|
||||
// drags, so the popover's pointerup trigger never fires for
|
||||
// them; a selection that has been stable for a moment is the
|
||||
// settled gesture — open the popover for it.
|
||||
if (this.isFixedLayout && doc.querySelector(".textLayer"))
|
||||
if (
|
||||
this.isFixedLayout &&
|
||||
!this.treatAsComic &&
|
||||
doc.querySelector(".textLayer")
|
||||
)
|
||||
this.schedulePDFSelectionPopover(doc, index);
|
||||
else if (!this.isFixedLayout)
|
||||
this.scheduleSelectionPopover(doc, index);
|
||||
@@ -785,7 +795,12 @@ document.addEventListener("alpine:init", () => {
|
||||
// Detected structurally: renderer.isPDF isn't set until the first
|
||||
// spread renders (during view.init), which is after this listener
|
||||
// attaches — the stale copy here would always be falsy.
|
||||
if (this.isFixedLayout && doc.querySelector(".textLayer")) {
|
||||
// Comic-treated PDFs never attach it: no highlights, like comics.
|
||||
if (
|
||||
this.isFixedLayout &&
|
||||
!this.treatAsComic &&
|
||||
doc.querySelector(".textLayer")
|
||||
) {
|
||||
doc.addEventListener(
|
||||
"pointerup",
|
||||
(e: PointerEvent) =>
|
||||
@@ -939,6 +954,9 @@ document.addEventListener("alpine:init", () => {
|
||||
// Smart|Pan|Text control appear for PDFs.
|
||||
this.isPDF = !!this.renderer?.isPDF;
|
||||
if (this.isPDF) {
|
||||
// Comic-treated PDFs are paged through like comics: no text
|
||||
// selection, so pointer interactions stay in pan mode.
|
||||
if (this.treatAsComic) this.interactionMode = "pan";
|
||||
this.renderer.setAttribute("interaction-mode", this.interactionMode);
|
||||
}
|
||||
this.fxZoomed = this.isFixedLayout && this.renderer?.zoom != null;
|
||||
@@ -1607,7 +1625,12 @@ document.addEventListener("alpine:init", () => {
|
||||
const rows = await hlResp.json();
|
||||
this.highlightItems = (rows as any[])
|
||||
.map((r) => this.mapHighlightRow(r))
|
||||
.filter((hl: any) => hl.cfi || hl.pdfPage >= 0);
|
||||
// Comic-treated items show no highlights (bookmarks only),
|
||||
// and previously-created PDF highlights stop rendering.
|
||||
.filter(
|
||||
(hl: any) =>
|
||||
!this.treatAsComic && (hl.cfi || hl.pdfPage >= 0),
|
||||
);
|
||||
this.renderAllHighlights();
|
||||
}
|
||||
if (noteResp.ok) {
|
||||
@@ -2143,6 +2166,8 @@ document.addEventListener("alpine:init", () => {
|
||||
},
|
||||
setInteractionMode(mode: string) {
|
||||
if (!this.isFixedLayout || !this.isPDF) return;
|
||||
// Comic-treated PDFs have no text selection: pan only.
|
||||
if (this.treatAsComic) return;
|
||||
if (mode !== "select" && mode !== "pan" && mode !== "text") return;
|
||||
this.renderer.setAttribute("interaction-mode", mode);
|
||||
this.interactionMode = mode;
|
||||
@@ -2237,8 +2262,9 @@ document.addEventListener("alpine:init", () => {
|
||||
},
|
||||
toggleSearch() {
|
||||
// Reflowable books use foliate's DOM search; PDFs use extracted text.
|
||||
// Comics have no text at all.
|
||||
if (this.isFixedLayout && !this.isPDF) return;
|
||||
// Comics have no text at all — and comic-treated PDFs (manga scans)
|
||||
// opt out of search to match comic treatment: bookmarks only.
|
||||
if ((this.isFixedLayout && !this.isPDF) || this.treatAsComic) return;
|
||||
const opening = !this.searchOpen;
|
||||
this.closeDrawers();
|
||||
this.searchOpen = opening;
|
||||
@@ -2646,8 +2672,8 @@ document.addEventListener("alpine:init", () => {
|
||||
this.hyphenate = true;
|
||||
this.chromeBehavior = "auto-hide";
|
||||
this.applyChromeBehavior();
|
||||
this.interactionMode = "select";
|
||||
this.setInteractionMode("select");
|
||||
this.interactionMode = this.treatAsComic ? "pan" : "select";
|
||||
this.setInteractionMode(this.interactionMode);
|
||||
this.fxBrightness = 1;
|
||||
this.fxContrast = 1;
|
||||
this.fxInvert = false;
|
||||
|
||||
Reference in New Issue
Block a user