feat(reader): shortcuts help modal + desktop edge page-turn zones
Help menu (the reader had a growing shortcut/gesture vocabulary with no discoverability): a ? topbar button, the '?' key, and F1 open a glass modal listing navigation, zoom/pan, highlight, and touch gesture reference — format-aware (fixed-layout/PDF rows appear only where they apply), Esc closes it first in the dismiss chain. Desktop edge zones: clickable page-turn strips on the left/right viewport edges (8% width, 44-72px), desktop only (hover+fine-pointer media query — touch devices use tap zones, avoiding double paging). Hovering reveals a chevron arrow and a subtle edge gradient. Zones disable (pointer-events pass-through) while a fixed-layout page is zoomed so edge clicks belong to content: panning, selection, highlight editing. fxZoomed tracks zoom state via the renderer zoom event, reset/fit actions, and init.
This commit is contained in:
@@ -395,6 +395,9 @@ document.addEventListener("alpine:init", () => {
|
||||
chromeBehavior: "auto-hide" as string,
|
||||
hideTimer: null as ReturnType<typeof setTimeout> | null,
|
||||
toolsOpen: false,
|
||||
helpOpen: false,
|
||||
pointerFine: true as boolean,
|
||||
fxZoomed: false as boolean,
|
||||
tapZonesEnabled: true as boolean,
|
||||
tapZoneSize: 30 as number,
|
||||
tapZoneTimer: null as ReturnType<typeof setTimeout> | null,
|
||||
@@ -511,6 +514,9 @@ document.addEventListener("alpine:init", () => {
|
||||
);
|
||||
},
|
||||
init() {
|
||||
this.pointerFine = window
|
||||
.matchMedia("(hover: hover) and (pointer: fine)")
|
||||
.matches;
|
||||
const link = document.getElementById("reader-back");
|
||||
if (!link) return;
|
||||
const storageKey = "reader_back";
|
||||
@@ -637,6 +643,7 @@ document.addEventListener("alpine:init", () => {
|
||||
);
|
||||
this.renderer.addEventListener("zoom", () => {
|
||||
this.zoomPercent = this.renderer.zoomPercent;
|
||||
this.fxZoomed = this.renderer.zoom != null;
|
||||
});
|
||||
this.computeFixedLayoutChapterBoundaries();
|
||||
this.applyDoublePageSpread();
|
||||
@@ -880,6 +887,7 @@ document.addEventListener("alpine:init", () => {
|
||||
if (this.isPDF) {
|
||||
this.renderer.setAttribute("interaction-mode", this.interactionMode);
|
||||
}
|
||||
this.fxZoomed = this.isFixedLayout && this.renderer?.zoom != null;
|
||||
this.initTime = Date.now();
|
||||
this.fetchReadingSpeed();
|
||||
this.refreshAnnotations();
|
||||
@@ -1425,6 +1433,7 @@ document.addEventListener("alpine:init", () => {
|
||||
this.renderer.resetZoom();
|
||||
this.renderer.dragOffset = { x: 0, y: 0 };
|
||||
this.zoomPercent = 100;
|
||||
this.fxZoomed = false;
|
||||
},
|
||||
toggleMagnifier() {
|
||||
if (!this.isFixedLayout) return;
|
||||
@@ -1449,6 +1458,7 @@ document.addEventListener("alpine:init", () => {
|
||||
if (mode === "fit-width" || mode === "fit-page") {
|
||||
this.renderer.setAttribute("zoom", mode);
|
||||
this.renderer.dragOffset = { x: 0, y: 0 };
|
||||
this.fxZoomed = false;
|
||||
}
|
||||
},
|
||||
toggleSpread() {
|
||||
@@ -1512,6 +1522,16 @@ document.addEventListener("alpine:init", () => {
|
||||
toggleTools() {
|
||||
this.toolsOpen = !this.toolsOpen;
|
||||
},
|
||||
toggleHelp() {
|
||||
this.helpOpen = !this.helpOpen;
|
||||
},
|
||||
// Desktop edge page-turn zones: fine-pointer devices only, and disabled
|
||||
// while a fixed-layout page is zoomed (edge clicks then belong to the
|
||||
// content: panning, text selection, highlight editing).
|
||||
get edgeZonesActive(): boolean {
|
||||
if (!this.pointerFine) return false;
|
||||
return !(this.isFixedLayout && this.fxZoomed);
|
||||
},
|
||||
anyDrawerOpen(): boolean {
|
||||
return this.tocOpen || this.settingsOpen || this.bookmarksOpen || this.searchOpen;
|
||||
},
|
||||
@@ -2297,7 +2317,9 @@ document.addEventListener("alpine:init", () => {
|
||||
else if (k === "-" || k === "_") this.zoomOut();
|
||||
else if (k === "0") this.resetZoom();
|
||||
else if (k === "Escape") {
|
||||
if (this.selectionPopover.open) {
|
||||
if (this.helpOpen) {
|
||||
this.helpOpen = false;
|
||||
} else if (this.selectionPopover.open) {
|
||||
this.hideSelectionPopover();
|
||||
} else if (this.toolsOpen) {
|
||||
this.toolsOpen = false;
|
||||
@@ -2310,10 +2332,14 @@ document.addEventListener("alpine:init", () => {
|
||||
this.chromeVisible = !this.chromeVisible;
|
||||
if (this.hideTimer) clearTimeout(this.hideTimer);
|
||||
}
|
||||
} else if (k === "F1") {
|
||||
event.preventDefault();
|
||||
this.toggleHelp();
|
||||
} else if (!typing) {
|
||||
if (k === "t") this.toggleTOC();
|
||||
else if (k === "s") this.toggleSettings();
|
||||
else if (k === "b") this.addBookmark();
|
||||
else if (k === "?") this.toggleHelp();
|
||||
else if (k === "/") {
|
||||
event.preventDefault();
|
||||
this.toggleSearch();
|
||||
|
||||
Reference in New Issue
Block a user