diff --git a/internal/middleware/security_headers.go b/internal/middleware/security_headers.go
index 27447ead..b68dcefd 100644
--- a/internal/middleware/security_headers.go
+++ b/internal/middleware/security_headers.go
@@ -13,7 +13,11 @@ func SecurityHeadersMiddleware() echo.MiddlewareFunc {
c.Response().Header().Set("X-Frame-Options", "DENY")
c.Response().Header().Set("X-XSS-Protection", "1; mode=block")
c.Response().Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
- c.Response().Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:")
+ // script-src notes: 'unsafe-inline' is required by the pre-paint
+ // theme flash guard (inline ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -776,12 +750,12 @@ func SidebarUserMenu(user User) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var44 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var44 == nil {
- templ_7745c5c3_Var44 = templ.NopComponent
+ templ_7745c5c3_Var42 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var42 == nil {
+ templ_7745c5c3_Var42 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -852,12 +826,12 @@ func SidebarSignIn(currentPath string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var46 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var46 == nil {
- templ_7745c5c3_Var46 = templ.NopComponent
+ templ_7745c5c3_Var44 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var44 == nil {
+ templ_7745c5c3_Var44 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/templates/index_templ.go b/templates/index_templ.go
index a265dc8f..ef1c7191 100644
--- a/templates/index_templ.go
+++ b/templates/index_templ.go
@@ -37,7 +37,7 @@ func Index(loggedIn bool, registrationEnabled bool) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "Bookhoard - Home")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -269,7 +269,7 @@ func ReaderTOCDrawer() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -302,7 +302,7 @@ func ReaderAnnotationsDrawer() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
Select text in the book to highlight it
Markdown supported — rich-text paste converts to markdown
0\" :style=\"noteOverLimit(newNoteText) ? 'color: #f87171;' : 'color: var(--text-secondary);'\" x-text=\"noteCountLabel(newNoteText)\">
No bookmarks yet
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
Select text in the book to highlight it
Markdown supported — rich-text paste converts to markdown
0\" :style=\"noteOverLimit(newNoteText) ? 'color: #f87171;' : 'color: var(--text-secondary);'\" x-text=\"noteCountLabel(newNoteText)\">
No bookmarks yet
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -335,7 +335,7 @@ func ReaderSearchDrawer() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
Searching… 0 matches No matches Search failed
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
Searching… 0 matches No matches Search failed
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -368,7 +368,7 @@ func ReaderSettingsDrawer() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/web/src/audio.ts b/web/src/audio.ts
index 63516f7f..d1253dd7 100644
--- a/web/src/audio.ts
+++ b/web/src/audio.ts
@@ -699,15 +699,40 @@ Alpine.data("audioReader", () => ({
this.chooseSleep(choice);
this.sleepOpen = false;
},
+ applyDefaultSpeedFromSelect(e: Event) {
+ this.setDefaultSpeed(Number((e.target as HTMLSelectElement).value));
+ },
+ isDefaultSpeed(rate: number): boolean {
+ return this.defaultSpeed === rate;
+ },
+ onScrubInput(e: Event) {
+ this.scrubbing = true;
+ this.scrubValue = Number((e.target as HTMLInputElement).value);
+ },
+ setVolumeFromEvent(e: Event) {
+ this.setVolume(Number((e.target as HTMLInputElement).value));
+ },
+ volumeLabel(): string {
+ return `Volume ${Math.round(Number(this.volume) * 100)}%`;
+ },
+ railTickStyle(tick: { start: number }): string {
+ const span = Math.max(1, this.railTo - this.railFrom);
+ return `left:${((tick.start - this.railFrom) / span) * 100}%;background-color:var(--text-secondary);opacity:0.5;`;
+ },
+ isSkipActive(secs: number): boolean {
+ return Number(this.skipSeconds) === secs;
+ },
+ isSpeedActive(rate: number): boolean {
+ return Number(this.speed) === rate;
+ },
+ isBookmarkEditing(b: { id: unknown }): boolean {
+ return this.editingBookmarkId === String(b.id);
+ },
chooseSleepClockMode() {
this.chooseSleepClock();
this.sleepOpen = false;
},
- isDefaultSpeed(rate: number): boolean {
- return Number(this.defaultSpeed) === rate;
- },
-
setSkip(seconds: number) {
this.skipSeconds = seconds;
localStorage.setItem("audio_skip", String(seconds));
@@ -1455,4 +1480,5 @@ Alpine.data("audioReader", () => ({
this.pushProgress();
}, PROGRESS_INTERVAL_MS);
},
+ fmtTime,
}));
diff --git a/web/src/book-detail.ts b/web/src/book-detail.ts
index d3b62aa7..51645474 100644
--- a/web/src/book-detail.ts
+++ b/web/src/book-detail.ts
@@ -55,6 +55,10 @@ function hideMetadataEditor(): void {
if (modal) modal.classList.add("hidden");
}
+function triggerCoverUpload(): void {
+ document.getElementById("cover-upload-input")?.click();
+}
+
function collectFormData(editorTags: string[]): Record {
const modal = document.getElementById("metadata-editor-modal");
if (!modal) return {};
@@ -217,6 +221,10 @@ Alpine.data("bookDetail", () => {
showProgressSyncModal,
hideProgressSyncModal,
hideNotesModal,
+ triggerCoverUpload,
+ bannerEditHasText(): boolean {
+ return (this.bannerEditText || "").trim().length > 0;
+ },
async toggleAnnotationsBanner() {
this.annotationsBannerOpen = !this.annotationsBannerOpen;
diff --git a/web/src/reader/reader.ts b/web/src/reader/reader.ts
index e4cf0a2f..416c76e2 100644
--- a/web/src/reader/reader.ts
+++ b/web/src/reader/reader.ts
@@ -3608,6 +3608,9 @@ document.addEventListener("alpine:init", () => {
this.readingTheme = value;
this.applyTheme();
},
+ noteEditHasText(note: { editText?: string }): boolean {
+ return String(note.editText ?? "").trim().length > 0;
+ },
detectChromeDarkMode(): boolean {
const darkChromeThemes = [
"theme-tokyo-night",