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;