feat(web): note editing at the reader slider and book banner; Deleted rename

Objective 8a (web half). Reader notes-drawer rows gain an inline editor
(mirroring the bookmark-rename pattern) that PUTs the new content — the
stored position rides along because the server's note PUT hard-sets the
position column (a probe note proved an omitted position NULLs the
anchor). Book-detail banner rows gain Edit/Delete for location notes
and Edit-note/Add-note for the note ATTACHED to a highlight (full-replace
highlight PUT; empty content clears the note; the highlight itself is
never edited from the banner). The edit dialog is centered with the
markdown caption.

The book-detail 'Notes' button becomes 'Deleted' (badge =
deleted-annotation count) and the modal title becomes 'Deleted
annotations' — that modal only ever showed removed annotations.

Verified live in Brave: drawer inline edit round-trips content and
position; banner note edit, highlight-note edit, and delete (tombstone
appears in the Deleted modal); rename renders with the count badge.
This commit is contained in:
John O'Keefe committed 2026-10-05 11:36:14 -04:00
1 parent 0b83db8fce
commit 5275f2ba38
11 files changed
+257 -30

No files matched your search

+54 -1
View File
@@ -78,15 +78,33 @@ templ AnnotationsBanner(book handlers.MediaDetail) {
<span class="block text-xs mt-0.5" style="color: var(--text-secondary);" x-text="row.label"></span>
</span>
</button>
<!-- Jump opens the reader at the annotation's location -->
<!-- Jump · edit (notes + attached highlight notes) · delete
(location notes) — objective 8a -->
<div class="flex items-center justify-between px-4 pb-3 pt-1">
<a
x-show="row.loc"
:href="'/readers/' + bannerMediaId + '?loc=' + encodeURIComponent(row.loc)"
class="text-xs px-2 py-1 rounded-lg border hover:bg-surface-hover transition-colors"
style="border-color: var(--border); color: var(--accent);"
>
Open reader here ↗
</a>
<div class="flex items-center gap-1">
<button
type="button"
@click="startBannerEdit(row)"
class="text-xs px-2 py-1 rounded-lg hover:bg-surface-hover"
style="color: var(--text-secondary);"
x-text="row.kind === 'highlight' ? (row.hasNote ? 'Edit note' : 'Add note') : 'Edit'"
></button>
<button
type="button"
x-show="row.kind === 'note'"
@click="deleteBannerNote(row)"
class="text-xs px-2 py-1 rounded-lg hover:bg-surface-hover"
style="color: var(--status-danger);"
>✕ Delete</button>
</div>
</div>
<!-- Expanded rich body (markdown pipeline) -->
<div x-show="row.expanded" class="px-4 pb-4" x-html="row.rich"></div>
@@ -96,6 +114,41 @@ templ AnnotationsBanner(book handlers.MediaDetail) {
</div>
</div>
<!-- Note edit dialog (objective 8a): raw markdown source; for
highlight rows the dialog edits the note ATTACHED to the
highlight (never the highlight itself) -->
<div
id="banner-note-dialog"
x-cloak
x-show="bannerEditOpen"
class="fixed inset-0 z-[60] flex items-center justify-center p-4"
style="background: rgba(0,0,0,.6);"
>
<div class="max-w-md w-full card p-6">
<h4 class="text-lg font-semibold mb-1" style="color: var(--text-primary);" x-text="bannerEditRow && bannerEditRow.kind === 'highlight' ? (bannerEditRow.hasNote ? 'Edit highlight note' : 'Add note to highlight') : 'Edit note'"></h4>
<p class="text-xs mb-3" style="color: var(--text-secondary);">Markdown supported</p>
<textarea
x-model="bannerEditText"
rows="6"
class="input w-full text-sm"
></textarea>
<div class="flex gap-2 mt-4">
<button
type="button"
@click="saveBannerNote()"
:disabled="!String(bannerEditText || '').trim()"
class="flex-1 py-2 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
>Save</button>
<button
type="button"
@click="bannerEditOpen = false"
class="flex-1 py-2 text-sm border rounded hover:bg-surface-hover"
style="border-color: var(--border); color: var(--text-secondary);"
>Cancel</button>
</div>
</div>
</div>
<!-- Export format dialog -->
<div
id="annotations-export-dialog"