diff --git a/templates/reader.templ b/templates/reader.templ index 5aa22294..07f38df0 100644 --- a/templates/reader.templ +++ b/templates/reader.templ @@ -135,13 +135,7 @@ templ Reader(user User, metadata ReaderMetadata) { :style="'background-color:' + c" :title="selectionPopover.mode === 'create' ? 'Highlight' : 'Set color'" :aria-label="'Highlight color ' + c" - @click="selectionPopover.noteOpen - ? (selectionPopover.mode === 'create' - ? (selectionPopover.color = c) - : (selectionPopover.color = c, saveHighlightChanges(true))) - : (selectionPopover.mode === 'create' - ? createHighlight(c) - : (selectionPopover.color = c, saveHighlightChanges()))" + @click="pickHighlightColor(c)" >
@@ -149,7 +143,7 @@ templ Reader(user User, metadata ReaderMetadata) { type="button" class="reader-popover-btn" title="Note" - @click="selectionPopover.noteOpen = true" + @click="openSelectionNote()" > diff --git a/templates/reader_templ.go b/templates/reader_templ.go index c3f17d77..d7187d56 100644 --- a/templates/reader_templ.go +++ b/templates/reader_templ.go @@ -123,7 +123,7 @@ func Reader(user User, metadata ReaderMetadata) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

Markdown supported — rich-text paste converts to markdown

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

Markdown supported — rich-text paste converts to markdown

0\" :style=\"noteOverLimit(selectionPopover.note) ? 'color: #f87171;' : 'color: var(--text-secondary);'\" x-text=\"noteCountLabel(selectionPopover.note)\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -175,7 +175,7 @@ func ReaderChrome(metadata ReaderMetadata) templ.Component { 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: 308, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 302, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -188,7 +188,7 @@ func ReaderChrome(metadata ReaderMetadata) templ.Component { 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: 311, Col: 95} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 305, Col: 95} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -230,7 +230,7 @@ func drawerHeader(title string) templ.Component { var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 583, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 577, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { diff --git a/web/src/reader/reader.ts b/web/src/reader/reader.ts index 8339b1e2..48abbf5c 100644 --- a/web/src/reader/reader.ts +++ b/web/src/reader/reader.ts @@ -1300,17 +1300,54 @@ document.addEventListener("alpine:init", () => { p.noteOpen = !!p.note && opts.mode === "edit"; p.pdfPage = opts.pdfPage ?? -1; p.pdfRects = opts.pdfRects ?? []; - // Clamp so the popover stays on screen (it anchors bottom-center; - // the popover is ~240px wide, so the fixed margins must cover half - // of it). NOTE: do not mutate the position after opening — touching - // the style mid-enter-transition leaves the popover invisible to - // compositor hit-testing (taps fall through to the content iframe). + // Clamp so the popover stays on screen (it anchors bottom-center and + // renders ABOVE the anchor: top = y - 10 - height). The size is + // estimated up front — the note area is already open when editing an + // existing note — and the position MUST be final before p.open = true + // (mutating mid-enter-transition breaks compositor hit-testing). const w = window.innerWidth; const h = window.innerHeight; - p.x = Math.min(Math.max(opts.x, 130), w - 130); - p.y = Math.min(Math.max(opts.y, 60), h - 60); + const estW = 240; + const estH = p.noteOpen ? 300 : 130; + p.x = Math.min(Math.max(opts.x, estW / 2 + 8), w - estW / 2 - 8); + p.y = Math.min(Math.max(opts.y, estH + 18), h - 60); p.open = true; }, + // Note area opens after the popover is visible; the textarea widens the + // popover, so measure the real box after render and shift it back inside + // the window (the popover's own enter transition has finished by the + // time a user clicks Note, so adjusting here is safe). + openSelectionNote() { + const p = this.selectionPopover; + p.noteOpen = true; + const w = window.innerWidth; + const h = window.innerHeight; + p.x = Math.min(Math.max(p.x, 148), w - 148); + p.y = Math.min(Math.max(p.y, 318), h - 60); + this.$nextTick(() => { + const el = document.querySelector(".reader-popover") as HTMLElement | null; + if (!el) return; + const r = el.getBoundingClientRect(); + if (r.x < 8) p.x += 8 - r.x; + if (r.right > w - 8) p.x -= r.right - (w - 8); + if (r.y < 8) p.y += 8 - r.y; + if (r.bottom > h - 8) p.y -= r.bottom - (h - 8); + }); + }, + // Highlight color picker (CSP-safe: single method call — the previous + // inline nested-ternary + comma-sequence expression is unparseable by + // the CSP build's evaluator). + pickHighlightColor(c: string) { + const p = this.selectionPopover; + p.color = c; + if (p.noteOpen) { + if (p.mode !== "create") this.saveHighlightChanges(true); + } else if (p.mode === "create") { + this.createHighlight(c); + } else { + this.saveHighlightChanges(); + } + }, hideSelectionPopover() { this.selectionPopover.open = false; this.selectionPopover.noteOpen = false;