Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5c2270007 | ||
|
|
94a4facc6c | ||
|
|
11617c1860 |
@@ -29,6 +29,7 @@ require (
|
|||||||
github.com/yuin/goldmark v1.8.2
|
github.com/yuin/goldmark v1.8.2
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
|
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
|
||||||
golang.org/x/crypto v0.50.0
|
golang.org/x/crypto v0.50.0
|
||||||
|
golang.org/x/net v0.53.0
|
||||||
golang.org/x/text v0.36.0
|
golang.org/x/text v0.36.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,7 +58,6 @@ require (
|
|||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/xyproto/randomstring v1.2.0 // indirect
|
github.com/xyproto/randomstring v1.2.0 // indirect
|
||||||
golang.org/x/image v0.39.0 // indirect
|
golang.org/x/image v0.39.0 // indirect
|
||||||
golang.org/x/net v0.53.0 // indirect
|
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.43.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
golang.org/x/time v0.15.0 // indirect
|
golang.org/x/time v0.15.0 // indirect
|
||||||
|
|||||||
+21
-19
@@ -36,7 +36,7 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
|
||||||
<title>{ metadata.Title } - Bookhoard Reader</title>
|
<title>{ metadata.Title } - Bookhoard Reader</title>
|
||||||
<link rel="manifest" href="/static/manifest.json"/>
|
<link rel="manifest" href="/static/manifest.json"/>
|
||||||
<link href="/static/reader-fonts.css" rel="stylesheet"/>
|
<link href="/static/reader-fonts.css" rel="stylesheet"/>
|
||||||
@@ -71,7 +71,7 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="reader-viewport" class="absolute inset-x-0 top-[52px] bottom-[52px]">
|
<div id="reader-viewport" class="absolute inset-x-0" style="top: 56px; bottom: 56px;">
|
||||||
<foliate-view id="reader-view" class="block w-full h-full"></foliate-view>
|
<foliate-view id="reader-view" class="block w-full h-full"></foliate-view>
|
||||||
</div>
|
</div>
|
||||||
@DictionaryPopup()
|
@DictionaryPopup()
|
||||||
@@ -82,25 +82,25 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm
|
|||||||
templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress) {
|
templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress) {
|
||||||
<div id="reader-chrome" class="transition-opacity duration-300">
|
<div id="reader-chrome" class="transition-opacity duration-300">
|
||||||
<!-- Top bar -->
|
<!-- Top bar -->
|
||||||
<div class="fixed top-0 left-0 right-0 bg-opacity-95 backdrop-blur border-b z-40" style="background-color: var(--bg-primary);">
|
<div id="reader-topbar" class="fixed top-0 left-0 right-0 bg-opacity-95 backdrop-blur border-b z-40 pt-[env(safe-area-inset-top)]" style="background-color: var(--bg-primary);">
|
||||||
<div class="flex items-center justify-between px-4 py-3">
|
<div class="flex items-center justify-between px-3 py-2 sm:px-4 sm:py-3">
|
||||||
<a href={ "/media/" + metadata.MediaItemID } class="text-lg hover:underline">
|
<a href={ "/media/" + metadata.MediaItemID } class="text-base sm:text-lg hover:underline">
|
||||||
← Back
|
← Back
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-lg font-semibold">{ metadata.Title }</h1>
|
<h1 class="text-base sm:text-lg font-semibold hidden sm:block sm:truncate">{ metadata.Title }</h1>
|
||||||
<button
|
<button
|
||||||
@click="toggleSettings()"
|
@click="toggleSettings()"
|
||||||
class="p-2 rounded-lg hover:bg-gray-700"
|
class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700"
|
||||||
title="Settings"
|
title="Settings"
|
||||||
>
|
>
|
||||||
⚙️
|
⚙️
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- Bottom bar -->
|
</div> <!-- Bottom bar -->
|
||||||
<div class="fixed bottom-0 left-0 right-0 bg-opacity-95 backdrop-blur border-t z-40" style="background-color: var(--bg-primary);">
|
<div id="reader-bottombar" class="fixed bottom-0 left-0 right-0 bg-opacity-95 backdrop-blur border-t z-40 pb-[env(safe-area-inset-bottom)]" style="background-color: var(--bg-primary);">
|
||||||
<div class="flex items-center px-2 py-2 gap-1">
|
<div class="flex items-center px-1.5 py-1.5 gap-0.5 sm:px-2 sm:py-2 sm:gap-1">
|
||||||
<!-- Left navigation -->
|
<!-- Left navigation -->
|
||||||
<button @click="goLeft()" class="p-2 rounded-lg hover:bg-gray-700" title="Go Left" aria-label="Go left">
|
<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">
|
<svg class="reader-icon" width="24" height="24" aria-hidden="true">
|
||||||
<path d="M 15 6 L 9 12 L 15 18"></path>
|
<path d="M 15 6 L 9 12 L 15 18"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -118,7 +118,7 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
/>
|
/>
|
||||||
<datalist id="tick-marks"></datalist>
|
<datalist id="tick-marks"></datalist>
|
||||||
<!-- Right navigation -->
|
<!-- Right navigation -->
|
||||||
<button @click="goRight()" class="p-2 rounded-lg hover:bg-gray-700" title="Go Right" aria-label="Go right">
|
<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">
|
<svg class="reader-icon" width="24" height="24" aria-hidden="true">
|
||||||
<path d="M 9 6 L 15 12 L 9 18"></path>
|
<path d="M 9 6 L 15 12 L 9 18"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -129,7 +129,7 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
<template x-if="isFixedLayout">
|
<template x-if="isFixedLayout">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<!-- Zoom out -->
|
<!-- Zoom out -->
|
||||||
<button @click="zoomOut()" class="p-2 rounded-lg hover:bg-gray-700" title="Zoom Out" aria-label="Zoom out">
|
<button @click="zoomOut()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Zoom Out" aria-label="Zoom out">
|
||||||
<svg class="reader-icon" width="20" height="20" aria-hidden="true">
|
<svg class="reader-icon" width="20" height="20" aria-hidden="true">
|
||||||
<path d="M 5 10 L 15 10"></path>
|
<path d="M 5 10 L 15 10"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -139,20 +139,20 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
<span x-text="zoomPercent + '%'">100%</span>
|
<span x-text="zoomPercent + '%'">100%</span>
|
||||||
</button>
|
</button>
|
||||||
<!-- Zoom in -->
|
<!-- Zoom in -->
|
||||||
<button @click="zoomIn()" class="p-2 rounded-lg hover:bg-gray-700" title="Zoom In" aria-label="Zoom in">
|
<button @click="zoomIn()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Zoom In" aria-label="Zoom in">
|
||||||
<svg class="reader-icon" width="20" height="20" aria-hidden="true">
|
<svg class="reader-icon" width="20" height="20" aria-hidden="true">
|
||||||
<path d="M 10 5 L 10 15 M 5 10 L 15 10"></path>
|
<path d="M 10 5 L 10 15 M 5 10 L 15 10"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<!-- Magnifier -->
|
<!-- Magnifier -->
|
||||||
<button @click="toggleMagnifier()" class="p-2 rounded-lg hover:bg-gray-700" title="Magnifier" aria-label="Toggle magnifier">
|
<button @click="toggleMagnifier()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Magnifier" aria-label="Toggle magnifier">
|
||||||
<svg class="reader-icon" width="20" height="20" aria-hidden="true">
|
<svg class="reader-icon" width="20" height="20" aria-hidden="true">
|
||||||
<circle cx="9" cy="9" r="5"></circle>
|
<circle cx="9" cy="9" r="5"></circle>
|
||||||
<path d="M 13 13 L 18 18"></path>
|
<path d="M 13 13 L 18 18"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<!-- Pan/Select mode (PDF only) -->
|
<!-- Pan/Select mode (PDF only) -->
|
||||||
<button x-show="isPDF" @click="toggleInteractionMode()" class="p-2 rounded-lg hover:bg-gray-700" title="Pan/Select Mode" aria-label="Toggle pan/select mode">
|
<button x-show="isPDF" @click="toggleInteractionMode()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Pan/Select Mode" aria-label="Toggle pan/select mode">
|
||||||
<svg class="reader-icon" width="20" height="20" aria-hidden="true">
|
<svg class="reader-icon" width="20" height="20" aria-hidden="true">
|
||||||
<path d="M 5 3 v 12 M 5 15 l -2 2 M 5 15 l 2 2 M 5 3 l 3 3"></path>
|
<path d="M 5 3 v 12 M 5 15 l -2 2 M 5 15 l 2 2 M 5 3 l 3 3"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -162,7 +162,8 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
<!-- Separator -->
|
<!-- Separator -->
|
||||||
<div class="w-px h-6 bg-gray-600 mx-1"></div>
|
<div class="w-px h-6 bg-gray-600 mx-1"></div>
|
||||||
<!-- Progress display -->
|
<!-- Progress display -->
|
||||||
<div id="progress-display" x-text="progressText" @click="cycleProgressMode()" :title="progressTooltip()" class="text-sm min-w-[4rem] text-center cursor-pointer">
|
<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 progress.FormatGroup == "reflowable" {
|
if progress.FormatGroup == "reflowable" {
|
||||||
if metadata.EstimatedPages > 0 {
|
if metadata.EstimatedPages > 0 {
|
||||||
{ fmt.Sprintf("%.0f%% · Page %d/%d", progress.Percentage, progress.CurrentPage, metadata.EstimatedPages) }
|
{ fmt.Sprintf("%.0f%% · Page %d/%d", progress.Percentage, progress.CurrentPage, metadata.EstimatedPages) }
|
||||||
@@ -172,14 +173,15 @@ templ ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
} else {
|
} else {
|
||||||
{ fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages) }
|
{ fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages) }
|
||||||
}
|
}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- Separator -->
|
<!-- Separator -->
|
||||||
<div class="w-px h-6 bg-gray-600 mx-1"></div>
|
<div class="w-px h-6 bg-gray-600 mx-1"></div>
|
||||||
<!-- Action buttons -->
|
<!-- Action buttons -->
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<button @click="toggleTOC()" class="p-2 rounded-lg hover:bg-gray-700" title="Table of Contents">📖</button>
|
<button @click="toggleTOC()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Table of Contents">📖</button>
|
||||||
<button @click="addBookmark()" class="p-2 rounded-lg hover:bg-gray-700" title="Bookmark">🏷️</button>
|
<button @click="addBookmark()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Bookmark">🏷️</button>
|
||||||
<button @click="toggleBookmarks()" class="p-2 rounded-lg hover:bg-gray-700" title="Notes">📝</button>
|
<button @click="toggleBookmarks()" class="p-1.5 sm:p-2 rounded-lg hover:bg-gray-700" title="Notes">📝</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+13
-13
@@ -60,7 +60,7 @@ func Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookma
|
|||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, viewport-fit=cover\"><title>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ func Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookma
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div></div><div id=\"reader-viewport\" class=\"absolute inset-x-0 top-[52px] bottom-[52px]\"><foliate-view id=\"reader-view\" class=\"block w-full h-full\"></foliate-view></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div></div><div id=\"reader-viewport\" class=\"absolute inset-x-0\" style=\"top: 56px; bottom: 56px;\"><foliate-view id=\"reader-view\" class=\"block w-full h-full\"></foliate-view></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
templ_7745c5c3_Var4 = templ.NopComponent
|
templ_7745c5c3_Var4 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div id=\"reader-chrome\" class=\"transition-opacity duration-300\"><!-- Top bar --><div class=\"fixed top-0 left-0 right-0 bg-opacity-95 backdrop-blur border-b z-40\" style=\"background-color: var(--bg-primary);\"><div class=\"flex items-center justify-between px-4 py-3\"><a href=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div id=\"reader-chrome\" class=\"transition-opacity duration-300\"><!-- Top bar --><div id=\"reader-topbar\" class=\"fixed top-0 left-0 right-0 bg-opacity-95 backdrop-blur border-b z-40 pt-[env(safe-area-inset-top)]\" style=\"background-color: var(--bg-primary);\"><div class=\"flex items-center justify-between px-3 py-2 sm:px-4 sm:py-3\"><a href=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -176,20 +176,20 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" class=\"text-lg hover:underline\">← Back</a><h1 class=\"text-lg font-semibold\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" class=\"text-base sm:text-lg hover:underline\">← Back</a><h1 class=\"text-base sm:text-lg font-semibold hidden sm:block sm:truncate\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var6 string
|
var templ_7745c5c3_Var6 string
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(metadata.Title)
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(metadata.Title)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 90, Col: 54}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 90, Col: 95}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</h1><button @click=\"toggleSettings()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Settings\">⚙️</button></div></div><!-- Bottom bar --><div class=\"fixed bottom-0 left-0 right-0 bg-opacity-95 backdrop-blur border-t z-40\" style=\"background-color: var(--bg-primary);\"><div class=\"flex items-center px-2 py-2 gap-1\"><!-- Left navigation --><button @click=\"goLeft()\" class=\"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><!-- Progress slider --><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><!-- Right navigation --><button @click=\"goRight()\" class=\"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><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Zoom controls (fixed-layout only) --><template x-if=\"isFixedLayout\"><div class=\"flex items-center gap-1\"><!-- Zoom out --><button @click=\"zoomOut()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Zoom Out\" aria-label=\"Zoom out\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 5 10 L 15 10\"></path></svg></button><!-- Zoom percentage --><button @click=\"resetZoom()\" class=\"text-xs px-1 rounded-lg hover:bg-gray-700 min-w-[3rem]\" title=\"Reset Zoom\" aria-label=\"Reset zoom\"><span x-text=\"zoomPercent + '%'\">100%</span></button><!-- Zoom in --><button @click=\"zoomIn()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Zoom In\" aria-label=\"Zoom in\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 10 5 L 10 15 M 5 10 L 15 10\"></path></svg></button><!-- Magnifier --><button @click=\"toggleMagnifier()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Magnifier\" aria-label=\"Toggle magnifier\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><circle cx=\"9\" cy=\"9\" r=\"5\"></circle> <path d=\"M 13 13 L 18 18\"></path></svg></button><!-- Pan/Select mode (PDF only) --><button x-show=\"isPDF\" @click=\"toggleInteractionMode()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Pan/Select Mode\" aria-label=\"Toggle pan/select mode\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 5 3 v 12 M 5 15 l -2 2 M 5 15 l 2 2 M 5 3 l 3 3\"></path></svg></button></div></template><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Progress display --><div id=\"progress-display\" x-text=\"progressText\" @click=\"cycleProgressMode()\" :title=\"progressTooltip()\" class=\"text-sm min-w-[4rem] text-center cursor-pointer\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</h1><button @click=\"toggleSettings()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Settings\">⚙️</button></div></div><!-- Bottom bar --><div id=\"reader-bottombar\" class=\"fixed bottom-0 left-0 right-0 bg-opacity-95 backdrop-blur border-t z-40 pb-[env(safe-area-inset-bottom)]\" style=\"background-color: var(--bg-primary);\"><div class=\"flex items-center px-1.5 py-1.5 gap-0.5 sm:px-2 sm:py-2 sm:gap-1\"><!-- Left navigation --><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><!-- Progress slider --><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><!-- Right navigation --><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><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Zoom controls (fixed-layout only) --><template x-if=\"isFixedLayout\"><div class=\"flex items-center gap-1\"><!-- Zoom out --><button @click=\"zoomOut()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Zoom Out\" aria-label=\"Zoom out\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 5 10 L 15 10\"></path></svg></button><!-- Zoom percentage --><button @click=\"resetZoom()\" class=\"text-xs px-1 rounded-lg hover:bg-gray-700 min-w-[3rem]\" title=\"Reset Zoom\" aria-label=\"Reset zoom\"><span x-text=\"zoomPercent + '%'\">100%</span></button><!-- Zoom in --><button @click=\"zoomIn()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Zoom In\" aria-label=\"Zoom in\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 10 5 L 10 15 M 5 10 L 15 10\"></path></svg></button><!-- Magnifier --><button @click=\"toggleMagnifier()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Magnifier\" aria-label=\"Toggle magnifier\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><circle cx=\"9\" cy=\"9\" r=\"5\"></circle> <path d=\"M 13 13 L 18 18\"></path></svg></button><!-- Pan/Select mode (PDF only) --><button x-show=\"isPDF\" @click=\"toggleInteractionMode()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Pan/Select Mode\" aria-label=\"Toggle pan/select mode\"><svg class=\"reader-icon\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M 5 3 v 12 M 5 15 l -2 2 M 5 15 l 2 2 M 5 3 l 3 3\"></path></svg></button></div></template><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Progress display --><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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
var templ_7745c5c3_Var7 string
|
var templ_7745c5c3_Var7 string
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.0f%% · Page %d/%d", progress.Percentage, progress.CurrentPage, metadata.EstimatedPages))
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.0f%% · Page %d/%d", progress.Percentage, progress.CurrentPage, metadata.EstimatedPages))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 168, Col: 112}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 169, Col: 113}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -208,7 +208,7 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
var templ_7745c5c3_Var8 string
|
var templ_7745c5c3_Var8 string
|
||||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.0f%%", progress.Percentage))
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.0f%%", progress.Percentage))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 170, Col: 51}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 171, Col: 52}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -219,14 +219,14 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
|
|||||||
var templ_7745c5c3_Var9 string
|
var templ_7745c5c3_Var9 string
|
||||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages))
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 173, Col: 71}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 174, Col: 72}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Action buttons --><div class=\"flex items-center gap-1\"><button @click=\"toggleTOC()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Table of Contents\">📖</button> <button @click=\"addBookmark()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Bookmark\">🏷️</button> <button @click=\"toggleBookmarks()\" class=\"p-2 rounded-lg hover:bg-gray-700\" title=\"Notes\">📝</button></div></div></div></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span></div><!-- Separator --><div class=\"w-px h-6 bg-gray-600 mx-1\"></div><!-- Action buttons --><div class=\"flex items-center gap-1\"><button @click=\"toggleTOC()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Table of Contents\">📖</button> <button @click=\"addBookmark()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Bookmark\">🏷️</button> <button @click=\"toggleBookmarks()\" class=\"p-1.5 sm:p-2 rounded-lg hover:bg-gray-700\" title=\"Notes\">📝</button></div></div></div></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -359,7 +359,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
|
|||||||
var templ_7745c5c3_Var14 string
|
var templ_7745c5c3_Var14 string
|
||||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(bookmark.CfiPosition)
|
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(bookmark.CfiPosition)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 390, Col: 38}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 392, Col: 38}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -372,7 +372,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
|
|||||||
var templ_7745c5c3_Var15 string
|
var templ_7745c5c3_Var15 string
|
||||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Title)
|
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Title)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 393, Col: 49}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 395, Col: 49}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -385,7 +385,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
|
|||||||
var templ_7745c5c3_Var16 string
|
var templ_7745c5c3_Var16 string
|
||||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Position)
|
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Position)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 395, Col: 27}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 397, Col: 27}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|||||||
+83
-41
@@ -372,6 +372,8 @@ document.addEventListener("alpine:init", () => {
|
|||||||
interactionMode: "select" as string,
|
interactionMode: "select" as string,
|
||||||
magnifierEnabled: false,
|
magnifierEnabled: false,
|
||||||
progressText: "",
|
progressText: "",
|
||||||
|
progressLabel: "",
|
||||||
|
progressMain: "",
|
||||||
sliderValue: 0,
|
sliderValue: 0,
|
||||||
settings: null as ReaderSettings | null,
|
settings: null as ReaderSettings | null,
|
||||||
justify: true,
|
justify: true,
|
||||||
@@ -489,21 +491,20 @@ document.addEventListener("alpine:init", () => {
|
|||||||
tocItem,
|
tocItem,
|
||||||
section,
|
section,
|
||||||
};
|
};
|
||||||
const progressStr = this.formatProgress(
|
const progressParts = this.formatProgressParts(
|
||||||
fraction,
|
fraction,
|
||||||
location,
|
location,
|
||||||
pageItem,
|
pageItem,
|
||||||
tocItem,
|
tocItem,
|
||||||
section,
|
section,
|
||||||
);
|
);
|
||||||
this.progressText = progressStr;
|
this.setProgress(progressParts);
|
||||||
this.sliderValue = fraction;
|
this.sliderValue = fraction;
|
||||||
const slider = document.getElementById(
|
const slider = document.getElementById(
|
||||||
"progress-slider",
|
"progress-slider",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
if (slider) {
|
if (slider) {
|
||||||
slider.value = fraction;
|
slider.value = fraction;
|
||||||
slider.title = progressStr;
|
|
||||||
}
|
}
|
||||||
const range = e.detail.range as Range | undefined;
|
const range = e.detail.range as Range | undefined;
|
||||||
if (range) {
|
if (range) {
|
||||||
@@ -547,6 +548,34 @@ document.addEventListener("alpine:init", () => {
|
|||||||
}
|
}
|
||||||
this.initTime = Date.now();
|
this.initTime = Date.now();
|
||||||
this.fetchReadingSpeed();
|
this.fetchReadingSpeed();
|
||||||
|
this.setupViewportInsets();
|
||||||
|
},
|
||||||
|
updateViewportInsets() {
|
||||||
|
const top = document.getElementById("reader-topbar");
|
||||||
|
const bot = document.getElementById("reader-bottombar");
|
||||||
|
const vp = document.getElementById("reader-viewport");
|
||||||
|
if (!top || !bot || !vp) return;
|
||||||
|
const margin = 6;
|
||||||
|
vp.style.setProperty("top", `${top.offsetHeight + margin}px`);
|
||||||
|
vp.style.setProperty("bottom", `${bot.offsetHeight + margin}px`);
|
||||||
|
},
|
||||||
|
setupViewportInsets() {
|
||||||
|
const top = document.getElementById("reader-topbar");
|
||||||
|
const bot = document.getElementById("reader-bottombar");
|
||||||
|
this.updateViewportInsets();
|
||||||
|
window.addEventListener("resize", () => this.updateViewportInsets());
|
||||||
|
window.addEventListener("orientationchange", () =>
|
||||||
|
setTimeout(() => this.updateViewportInsets(), 250),
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
typeof ResizeObserver !== "undefined" &&
|
||||||
|
top &&
|
||||||
|
bot
|
||||||
|
) {
|
||||||
|
const ro = new ResizeObserver(() => this.updateViewportInsets());
|
||||||
|
ro.observe(top);
|
||||||
|
ro.observe(bot);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
debouncedSaveProgress(fraction: number, location: any, cfi: string) {
|
debouncedSaveProgress(fraction: number, location: any, cfi: string) {
|
||||||
if (Date.now() - this.initTime < 5000) return;
|
if (Date.now() - this.initTime < 5000) return;
|
||||||
@@ -774,19 +803,20 @@ document.addEventListener("alpine:init", () => {
|
|||||||
/* ignore bookmark errors for now */
|
/* ignore bookmark errors for now */
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
formatProgress(
|
formatProgressParts(
|
||||||
fraction: number,
|
fraction: number,
|
||||||
location: { current: number; next: number; total: number },
|
location: { current: number; next: number; total: number },
|
||||||
pageItem: { id: number; label: string; href: string },
|
pageItem: { id: number; label: string; href: string },
|
||||||
tocItem: FoliateTocItem | null,
|
tocItem: FoliateTocItem | null,
|
||||||
section: { current: number; total: number },
|
section: { current: number; total: number },
|
||||||
): string {
|
): { label: string; main: string } {
|
||||||
const percent = new Intl.NumberFormat("en", { style: "percent" }).format(
|
const percent = new Intl.NumberFormat("en", { style: "percent" }).format(
|
||||||
fraction,
|
fraction,
|
||||||
);
|
);
|
||||||
|
const chapterLabel = tocItem?.label ? `${tocItem.label} · ` : "";
|
||||||
switch (this.progressMode) {
|
switch (this.progressMode) {
|
||||||
case "percentage":
|
case "percentage":
|
||||||
return percent;
|
return { label: "", main: percent };
|
||||||
case "chapter": {
|
case "chapter": {
|
||||||
if (this.isFixedLayout && tocItem && this.chapterBoundaries.length > 0) {
|
if (this.isFixedLayout && tocItem && this.chapterBoundaries.length > 0) {
|
||||||
const totalSections = this.book?.sections?.filter(
|
const totalSections = this.book?.sections?.filter(
|
||||||
@@ -809,10 +839,10 @@ document.addEventListener("alpine:init", () => {
|
|||||||
1,
|
1,
|
||||||
Math.min(currentPage - chapterStart + 1, chapterPages),
|
Math.min(currentPage - chapterStart + 1, chapterPages),
|
||||||
);
|
);
|
||||||
const label = tocItem.label
|
return {
|
||||||
? `${tocItem.label} · `
|
label: chapterLabel,
|
||||||
: "";
|
main: `${currentInChapter} / ${chapterPages}`,
|
||||||
return `${label}${currentInChapter} / ${chapterPages}`;
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tocItem && this.chapterBoundaries.length > 0) {
|
if (tocItem && this.chapterBoundaries.length > 0) {
|
||||||
@@ -836,10 +866,10 @@ document.addEventListener("alpine:init", () => {
|
|||||||
1,
|
1,
|
||||||
Math.min(currentPage - chapterStart + 1, chapterPages),
|
Math.min(currentPage - chapterStart + 1, chapterPages),
|
||||||
);
|
);
|
||||||
const label = tocItem.label
|
return {
|
||||||
? `${tocItem.label} · `
|
label: chapterLabel,
|
||||||
: "";
|
main: `${currentInChapter} / ${chapterPages}`,
|
||||||
return `${label}${currentInChapter} / ${chapterPages}`;
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (section && this.sectionFractionsArr.length > 1) {
|
if (section && this.sectionFractionsArr.length > 1) {
|
||||||
@@ -859,16 +889,19 @@ document.addEventListener("alpine:init", () => {
|
|||||||
Math.round((fraction - startFrac) * pageBase),
|
Math.round((fraction - startFrac) * pageBase),
|
||||||
);
|
);
|
||||||
const clamped = Math.min(currentInSec, totalInSec);
|
const clamped = Math.min(currentInSec, totalInSec);
|
||||||
const label = tocItem?.label
|
return {
|
||||||
? `${tocItem.label} · `
|
label: chapterLabel,
|
||||||
: "";
|
main: `${clamped} / ${totalInSec}`,
|
||||||
return `${label}${clamped} / ${totalInSec}`;
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (location.total > 0) {
|
if (location.total > 0) {
|
||||||
return `${percent} · ${location.current} / ${location.total}`;
|
return {
|
||||||
|
label: "",
|
||||||
|
main: `${percent} · ${location.current} / ${location.total}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return percent;
|
return { label: "", main: percent };
|
||||||
}
|
}
|
||||||
case "time-left": {
|
case "time-left": {
|
||||||
if (this.readingSpeedPpm > 0 && location.total > 0) {
|
if (this.readingSpeedPpm > 0 && location.total > 0) {
|
||||||
@@ -877,30 +910,47 @@ document.addEventListener("alpine:init", () => {
|
|||||||
if (mins >= 60) {
|
if (mins >= 60) {
|
||||||
const hrs = Math.floor(mins / 60);
|
const hrs = Math.floor(mins / 60);
|
||||||
const m = mins % 60;
|
const m = mins % 60;
|
||||||
return `${percent} · ~${hrs}h ${m}m left`;
|
return { label: "", main: `${percent} · ~${hrs}h ${m}m left` };
|
||||||
}
|
}
|
||||||
return `${percent} · ~${mins} min left`;
|
return { label: "", main: `${percent} · ~${mins} min left` };
|
||||||
}
|
}
|
||||||
return `${percent} · ~-- min left`;
|
return { label: "", main: `${percent} · ~-- min left` };
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
if (this.isFixedLayout) {
|
if (this.isFixedLayout) {
|
||||||
const pageInfo = this.getRenderedPageInfo();
|
const pageInfo = this.getRenderedPageInfo();
|
||||||
if (pageInfo) {
|
if (pageInfo) {
|
||||||
return `${pageInfo.current} / ${pageInfo.total}`;
|
return { label: "", main: `${pageInfo.current} / ${pageInfo.total}` };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pageItem) {
|
if (pageItem) {
|
||||||
return `${percent} · Page ${pageItem.label}`;
|
return { label: "", main: `${percent} · Page ${pageItem.label}` };
|
||||||
}
|
}
|
||||||
const pageInfoReflow = this.getRenderedPageInfo();
|
const pageInfoReflow = this.getRenderedPageInfo();
|
||||||
if (pageInfoReflow) {
|
if (pageInfoReflow) {
|
||||||
return `${percent} · ${pageInfoReflow.current + 1} / ${pageInfoReflow.total}`;
|
return {
|
||||||
|
label: "",
|
||||||
|
main: `${percent} · ${pageInfoReflow.current + 1} / ${pageInfoReflow.total}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return `${percent} · ${location.current} / ${location.total}`;
|
return {
|
||||||
|
label: "",
|
||||||
|
main: `${percent} · ${location.current} / ${location.total}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setProgress(parts: { label: string; main: string }, sliderTitle?: string) {
|
||||||
|
this.progressLabel = parts.label;
|
||||||
|
this.progressMain = parts.main;
|
||||||
|
this.progressText = parts.label + parts.main;
|
||||||
|
const slider = document.getElementById(
|
||||||
|
"progress-slider",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
if (slider) {
|
||||||
|
slider.title = sliderTitle ?? this.progressText;
|
||||||
|
}
|
||||||
|
},
|
||||||
cycleProgressMode() {
|
cycleProgressMode() {
|
||||||
const modes = ["pages", "chapter", "percentage", "time-left"];
|
const modes = ["pages", "chapter", "percentage", "time-left"];
|
||||||
const idx = modes.indexOf(this.progressMode);
|
const idx = modes.indexOf(this.progressMode);
|
||||||
@@ -909,19 +959,15 @@ document.addEventListener("alpine:init", () => {
|
|||||||
if (this.lastRelocateDetail) {
|
if (this.lastRelocateDetail) {
|
||||||
const { fraction, location, pageItem, tocItem, section } =
|
const { fraction, location, pageItem, tocItem, section } =
|
||||||
this.lastRelocateDetail;
|
this.lastRelocateDetail;
|
||||||
this.progressText = this.formatProgress(
|
this.setProgress(
|
||||||
|
this.formatProgressParts(
|
||||||
fraction,
|
fraction,
|
||||||
location,
|
location,
|
||||||
pageItem,
|
pageItem,
|
||||||
tocItem,
|
tocItem,
|
||||||
section,
|
section,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const slider = document.getElementById(
|
|
||||||
"progress-slider",
|
|
||||||
) as HTMLInputElement;
|
|
||||||
if (slider) {
|
|
||||||
slider.title = this.progressText;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async fetchReadingSpeed() {
|
async fetchReadingSpeed() {
|
||||||
@@ -944,19 +990,15 @@ document.addEventListener("alpine:init", () => {
|
|||||||
if (this.lastRelocateDetail) {
|
if (this.lastRelocateDetail) {
|
||||||
const { fraction, location, pageItem, tocItem, section } =
|
const { fraction, location, pageItem, tocItem, section } =
|
||||||
this.lastRelocateDetail;
|
this.lastRelocateDetail;
|
||||||
this.progressText = this.formatProgress(
|
this.setProgress(
|
||||||
|
this.formatProgressParts(
|
||||||
fraction,
|
fraction,
|
||||||
location,
|
location,
|
||||||
pageItem,
|
pageItem,
|
||||||
tocItem,
|
tocItem,
|
||||||
section,
|
section,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const slider = document.getElementById(
|
|
||||||
"progress-slider",
|
|
||||||
) as HTMLInputElement;
|
|
||||||
if (slider) {
|
|
||||||
slider.title = this.progressText;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computeFixedLayoutChapterBoundaries() {
|
computeFixedLayoutChapterBoundaries() {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user