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:
+11
-11
@@ -125,7 +125,7 @@ func Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookma
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><!-- Reading surface: edge-to-edge. Chrome overlays translucently;\n\t\t\t drawers and scrim float above everything. --><div id=\"reader-viewport\" class=\"fixed inset-0\"><foliate-view id=\"reader-view\" class=\"block w-full h-full\"></foliate-view></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><!-- Reading surface: edge-to-edge. Chrome overlays translucently;\n\t\t\t drawers and scrim float above everything. --><div id=\"reader-viewport\" class=\"fixed inset-0\"><foliate-view id=\"reader-view\" class=\"block w-full h-full\"></foliate-view><!-- Desktop edge page-turn zones: hover to reveal the arrow.\n\t\t\t\t Hidden on touch (tap zones cover that) and while a\n\t\t\t\t fixed-layout page is zoomed (edges belong to content then). --><div class=\"reader-edge-zone left\" :class=\"edgeZonesActive ? '' : 'pointer-events-none'\" @click=\"goLeft()\" aria-hidden=\"true\"><svg class=\"reader-icon\" width=\"28\" height=\"28\" aria-hidden=\"true\"><path d=\"M 18 6 L 10 14 L 18 22\"></path></svg></div><div class=\"reader-edge-zone right\" :class=\"edgeZonesActive ? '' : 'pointer-events-none'\" @click=\"goRight()\" aria-hidden=\"true\"><svg class=\"reader-icon\" width=\"28\" height=\"28\" aria-hidden=\"true\"><path d=\"M 10 6 L 18 14 L 10 22\"></path></svg></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -165,7 +165,7 @@ func Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookma
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div></body></html>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div><!-- Shortcuts & gestures help modal --><div x-show=\"helpOpen\" x-transition.opacity.duration.200ms class=\"reader-help-backdrop\" @click=\"helpOpen = false\" aria-hidden=\"true\"></div><div x-show=\"helpOpen\" x-transition:enter=\"transition ease-out duration-150\" x-transition:enter-start=\"opacity-0 scale-95\" x-transition:enter-end=\"opacity-100 scale-100\" x-transition:leave=\"transition ease-in duration-100\" x-transition:leave-start=\"opacity-100 scale-100\" x-transition:leave-end=\"opacity-0 scale-95\" class=\"reader-help\" role=\"dialog\" aria-label=\"Shortcuts and gestures\"><div class=\"reader-help-header\"><h3 class=\"font-semibold text-base\">Shortcuts & Gestures</h3><button @click=\"helpOpen = false\" class=\"p-1.5 rounded-lg hover:bg-gray-700\" title=\"Close (Esc)\" aria-label=\"Close\">✕</button></div><div class=\"reader-help-body\"><h4 class=\"reader-help-section\">Navigation</h4><div class=\"help-row\"><span>Previous / next page</span><span class=\"help-keys\"><kbd class=\"kbd\">←</kbd><kbd class=\"kbd\">→</kbd> or <kbd class=\"kbd\">h</kbd><kbd class=\"kbd\">l</kbd></span></div><div class=\"help-row\"><span>Return to previous location</span><span class=\"help-keys\"><kbd class=\"kbd\">Alt</kbd>+<kbd class=\"kbd\">←</kbd></span></div><div class=\"help-row\"><span>Page edge zones <span class=\"help-note\">(desktop)</span></span><span class=\"help-keys\">click page edges</span></div><div class=\"help-row\" x-show=\"!isFixedLayout || isPDF\"><span>Search in book</span><span class=\"help-keys\"><kbd class=\"kbd\">/</kbd></span></div><div class=\"help-row\"><span>Contents / thumbnails</span><span class=\"help-keys\"><kbd class=\"kbd\">t</kbd></span></div><div class=\"help-row\"><span>Annotations drawer</span><span class=\"help-keys\">📝 button</span></div><div class=\"help-row\"><span>Settings</span><span class=\"help-keys\"><kbd class=\"kbd\">s</kbd></span></div><div class=\"help-row\"><span>Add bookmark</span><span class=\"help-keys\"><kbd class=\"kbd\">b</kbd></span></div><div class=\"help-row\"><span>Close menus / toggle chrome</span><span class=\"help-keys\"><kbd class=\"kbd\">Esc</kbd></span></div><h4 class=\"reader-help-section\" x-show=\"isFixedLayout\">Zoom & pan <span class=\"help-note\">(comics, PDFs)</span></h4><div class=\"help-row\" x-show=\"isFixedLayout\"><span>Zoom in / out / reset</span><span class=\"help-keys\"><kbd class=\"kbd\">+</kbd><kbd class=\"kbd\">−</kbd><kbd class=\"kbd\">0</kbd> or wheel</span></div><div class=\"help-row\" x-show=\"isFixedLayout\"><span>Move the page</span><span class=\"help-keys\">drag with the mouse</span></div><div class=\"help-row\" x-show=\"isFixedLayout\"><span>Recenter <span class=\"help-note\">(keeps zoom)</span></span><span class=\"help-keys\">⊙ button</span></div><div class=\"help-row\" x-show=\"isPDF\"><span>Select text in a PDF</span><span class=\"help-keys\">Smart mode: drag over text · <kbd class=\"kbd\">Shift</kbd>+drag anywhere · Text mode</span></div><h4 class=\"reader-help-section\">Highlights</h4><div class=\"help-row\"><span>Create</span><span class=\"help-keys\">select text → pick a color</span></div><div class=\"help-row\"><span>Edit / delete</span><span class=\"help-keys\">click the highlight</span></div><h4 class=\"reader-help-section\">Touch</h4><div class=\"help-row\"><span>Page turn</span><span class=\"help-keys\">swipe, or tap outer edges</span></div><div class=\"help-row\"><span>Toggle chrome</span><span class=\"help-keys\">tap center</span></div><div class=\"help-row\" x-show=\"isFixedLayout\"><span>Zoom / move</span><span class=\"help-keys\">pinch · two-finger drag · double-tap</span></div></div></div></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -201,7 +201,7 @@ func ReaderChrome(metadata ReaderMetadata, progress ReadingProgress) templ.Compo
|
||||
var templ_7745c5c3_Var7 templ.SafeURL
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs("/media/" + metadata.MediaItemID)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 243, Col: 63}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 319, Col: 63}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -214,13 +214,13 @@ func ReaderChrome(metadata ReaderMetadata, progress ReadingProgress) templ.Compo
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(metadata.Title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 246, Col: 95}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 322, Col: 95}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</h1><div class=\"flex items-center gap-1\"><button @click=\"addBookmark()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Bookmark this position (b)\">🏷️</button> <button x-show=\"!isFixedLayout || isPDF\" @click=\"toggleSearch()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Search (/)\">🔍</button> <button @click=\"toggleBookmarks()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Annotations\">📝</button> <button @click=\"toggleSettings()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Settings (s)\">Aa</button></div></div></div><!-- Bottom bar --><div id=\"reader-bottombar\" class=\"fixed bottom-0 left-0 right-0 border-t z-40 pb-[env(safe-area-inset-bottom)] reader-glass\"><!-- Reflowable row --><div x-show=\"!isFixedLayout\" class=\"flex flex-wrap items-center gap-x-1 gap-y-1 px-1.5 py-1.5 sm:px-2 sm:py-2 sm:gap-1\"><div class=\"flex items-center gap-0.5 grow min-w-[190px]\"><button @click=\"goLeft()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Go Left\" aria-label=\"Go left\"><svg class=\"reader-icon\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path d=\"M 15 6 L 9 12 L 15 18\"></path></svg></button> <button x-show=\"backStack.length > 0\" @click=\"goBackToLocation()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Return to previous location (Alt+←)\" aria-label=\"Return to previous location\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 12 21 C 9 18 5 14.4 5 9.5 C 5 6.5 8 4 12 4 C 16 4 19 6.5 19 9.5 C 19 14.4 15 18 12 21 Z\"></path> <circle cx=\"12\" cy=\"9.5\" r=\"2\"></circle></svg></button> <input id=\"progress-slider\" type=\"range\" min=\"0\" max=\"1\" step=\"any\" list=\"tick-marks\" @input=\"goToFraction($event.target.value)\" class=\"grow\"> <datalist id=\"tick-marks\"></datalist> <button @click=\"goRight()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Go Right\" aria-label=\"Go right\"><svg class=\"reader-icon\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path d=\"M 9 6 L 15 12 L 9 18\"></path></svg></button></div><div class=\"flex items-center gap-1\"><div class=\"w-px h-6 reader-sep\"></div><div id=\"progress-display\" @click=\"cycleProgressMode()\" :title=\"progressTooltip()\" class=\"text-sm min-w-[4rem] max-w-[5rem] sm:max-w-none text-center cursor-pointer truncate whitespace-nowrap overflow-hidden\"><span class=\"hidden sm:inline\" x-text=\"progressLabel\"></span><span x-text=\"progressMain\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</h1><div class=\"flex items-center gap-1\"><button @click=\"addBookmark()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Bookmark this position (b)\">🏷️</button> <button x-show=\"!isFixedLayout || isPDF\" @click=\"toggleSearch()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Search (/)\">🔍</button> <button @click=\"toggleBookmarks()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Annotations\">📝</button> <button @click=\"toggleSettings()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Settings (s)\">Aa</button> <button @click=\"toggleHelp()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700 text-sm font-bold\" title=\"Shortcuts & gestures (?)\" aria-label=\"Shortcuts and gestures\">?</button></div></div></div><!-- Bottom bar --><div id=\"reader-bottombar\" class=\"fixed bottom-0 left-0 right-0 border-t z-40 pb-[env(safe-area-inset-bottom)] reader-glass\"><!-- Reflowable row --><div x-show=\"!isFixedLayout\" class=\"flex flex-wrap items-center gap-x-1 gap-y-1 px-1.5 py-1.5 sm:px-2 sm:py-2 sm:gap-1\"><div class=\"flex items-center gap-0.5 grow min-w-[190px]\"><button @click=\"goLeft()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Go Left\" aria-label=\"Go left\"><svg class=\"reader-icon\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path d=\"M 15 6 L 9 12 L 15 18\"></path></svg></button> <button x-show=\"backStack.length > 0\" @click=\"goBackToLocation()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Return to previous location (Alt+←)\" aria-label=\"Return to previous location\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 12 21 C 9 18 5 14.4 5 9.5 C 5 6.5 8 4 12 4 C 16 4 19 6.5 19 9.5 C 19 14.4 15 18 12 21 Z\"></path> <circle cx=\"12\" cy=\"9.5\" r=\"2\"></circle></svg></button> <input id=\"progress-slider\" type=\"range\" min=\"0\" max=\"1\" step=\"any\" list=\"tick-marks\" @input=\"goToFraction($event.target.value)\" class=\"grow\"> <datalist id=\"tick-marks\"></datalist> <button @click=\"goRight()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Go Right\" aria-label=\"Go right\"><svg class=\"reader-icon\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path d=\"M 9 6 L 15 12 L 9 18\"></path></svg></button></div><div class=\"flex items-center gap-1\"><div class=\"w-px h-6 reader-sep\"></div><div id=\"progress-display\" @click=\"cycleProgressMode()\" :title=\"progressTooltip()\" class=\"text-sm min-w-[4rem] max-w-[5rem] sm:max-w-none text-center cursor-pointer truncate whitespace-nowrap overflow-hidden\"><span class=\"hidden sm:inline\" x-text=\"progressLabel\"></span><span x-text=\"progressMain\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -229,7 +229,7 @@ func ReaderChrome(metadata ReaderMetadata, progress ReadingProgress) templ.Compo
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.0f%% · Page %d/%d", progress.Percentage, progress.CurrentPage, metadata.EstimatedPages))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 294, Col: 114}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 371, Col: 114}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -239,7 +239,7 @@ func ReaderChrome(metadata ReaderMetadata, progress ReadingProgress) templ.Compo
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.0f%%", progress.Percentage))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 296, Col: 53}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 373, Col: 53}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -250,7 +250,7 @@ func ReaderChrome(metadata ReaderMetadata, progress ReadingProgress) templ.Compo
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 299, Col: 73}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 376, Col: 73}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -265,7 +265,7 @@ func ReaderChrome(metadata ReaderMetadata, progress ReadingProgress) templ.Compo
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.0f%%", progress.Percentage))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 395, Col: 52}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 472, Col: 52}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -275,7 +275,7 @@ func ReaderChrome(metadata ReaderMetadata, progress ReadingProgress) templ.Compo
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 397, Col: 73}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 474, Col: 73}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -318,7 +318,7 @@ func drawerHeader(title string) templ.Component {
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 517, Col: 35}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 594, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
||||
Reference in New Issue
Block a user