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, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -818,7 +792,7 @@ func SidebarUserMenu(user User) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "Profile
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "Logout
") 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, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if SignupVisible() { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "Create an account") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "Create an account") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "
") 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
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/reader.templ b/templates/reader.templ index 81f68bcc..5dd3a5ea 100644 --- a/templates/reader.templ +++ b/templates/reader.templ @@ -735,7 +735,7 @@ templ ReaderAnnotationsDrawer() {
—
—
0/0
Zoom
Fit
Page position
Magnifier
Night mode
Pointer
Double page
Contents
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
—
—
0/0
Zoom
Fit
Page position
Magnifier
Night mode
Pointer
Double page
Contents
") 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, "

Markdown supported — rich-text paste converts to markdown

0\" :style=\"noteOverLimit(newNoteText) ? 'color: #f87171;' : 'color: var(--text-secondary);'\" x-text=\"noteCountLabel(newNoteText)\">
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

Markdown supported — rich-text paste converts to markdown

0\" :style=\"noteOverLimit(newNoteText) ? 'color: #f87171;' : 'color: var(--text-secondary);'\" x-text=\"noteCountLabel(newNoteText)\">
") 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, "

Behavior

Reading Theme

Typography

Layout & Display

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

Behavior

Reading Theme

Typography

Layout & Display

") 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",