diff --git a/internal/router/frontend.go b/internal/router/frontend.go
index 46eae09a..e7894b16 100644
--- a/internal/router/frontend.go
+++ b/internal/router/frontend.go
@@ -1436,7 +1436,10 @@ func registerFrontendRoutes(cfg *Config) {
// Render template
var buf bytes.Buffer
- err = templates.BookDetail(user, detail, "").Render(c.Request().Context(), &buf)
+ // The deleted-annotations modal states the retention in plain days
+ // (the tunable's user-facing value), never as a settings name.
+ tombstoneDays := cfg.Settings.TombstoneTTL() / (24 * time.Hour)
+ err = templates.BookDetail(user, detail, "", int(tombstoneDays)).Render(c.Request().Context(), &buf)
if err != nil {
return err
}
diff --git a/templates/annotations_banner.templ b/templates/annotations_banner.templ
index cedbc94d..e71d9b8a 100644
--- a/templates/annotations_banner.templ
+++ b/templates/annotations_banner.templ
@@ -99,8 +99,7 @@ templ AnnotationsBanner(book handlers.MediaDetail) {
>
✕ Delete
diff --git a/templates/book_detail.templ b/templates/book_detail.templ
index 20f9302e..a8a9abb6 100644
--- a/templates/book_detail.templ
+++ b/templates/book_detail.templ
@@ -9,7 +9,7 @@ import (
"strings"
)
-templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) {
+templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombstoneDays int) {
@@ -440,7 +440,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) {
}
@ProgressSyncModal(user, book)
- @NotesHighlightsModal(user, book)
+ @NotesHighlightsModal(user, book, tombstoneDays)
@AnnotationsBanner(book)
if user.Role == "admin" {
@MetadataEditorModal(book)
diff --git a/templates/book_detail_modals.templ b/templates/book_detail_modals.templ
index 8c3add39..78070c8b 100644
--- a/templates/book_detail_modals.templ
+++ b/templates/book_detail_modals.templ
@@ -128,7 +128,7 @@ templ ProgressSyncModal(user User, book handlers.MediaDetail) {
}
-templ NotesHighlightsModal(user User, book handlers.MediaDetail) {
+templ NotesHighlightsModal(user User, book handlers.MediaDetail, tombstoneDays int) {
{ len(book.DeletedAnnotations) }
- Deleted entries are kept for the sync retention window before being removed automatically. Restore returns them to every synced device; Delete permanently removes them immediately.
+ if tombstoneDays == 1 {
+ Deleted entries are kept for 1 day before being removed automatically. Restore returns them to every synced device; Delete permanently removes them immediately.
+ } else if tombstoneDays > 1 {
+ Deleted entries are kept for { tombstoneDays } days before being removed automatically. Restore returns them to every synced device; Delete permanently removes them immediately.
+ } else {
+ Deleted entries are kept until manually purged (retention disabled). Restore returns them to every synced device; Delete permanently removes them immediately.
+ }
for _, ann := range book.DeletedAnnotations {
diff --git a/templates/book_detail_modals_templ.go b/templates/book_detail_modals_templ.go
index 80d22260..6e521957 100644
--- a/templates/book_detail_modals_templ.go
+++ b/templates/book_detail_modals_templ.go
@@ -335,7 +335,7 @@ func ProgressSyncModal(user User, book handlers.MediaDetail) templ.Component {
})
}
-func NotesHighlightsModal(user User, book handlers.MediaDetail) templ.Component {
+func NotesHighlightsModal(user User, book handlers.MediaDetail, tombstoneDays int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -447,64 +447,97 @@ func NotesHighlightsModal(user User, book handlers.MediaDetail) templ.Component
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
Deleted entries are kept for the sync retention window before being removed automatically. Restore returns them to every synced device; Delete permanently removes them immediately.
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- for _, ann := range book.DeletedAnnotations {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
1 {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "Deleted entries are kept for ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
- templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(ann.ID)
+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(tombstoneDays)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 190, Col: 61}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 189, Col: 51}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\">
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " days before being removed automatically. Restore returns them to every synced device; Delete permanently removes them immediately.")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "Deleted entries are kept until manually purged (retention disabled). Restore returns them to every synced device; Delete permanently removes them immediately.")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, ann := range book.DeletedAnnotations {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
- templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(FormatInTimezone(ann.DeletedAt, user.Timezone))
+ templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(ann.AnnotationType)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 195, Col: 59}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 199, Col: 119}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var26 string
- templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue("restoreDeletedAnnotation('" + ann.AnnotationType + "', '" + ann.ID + "')")
+ templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(FormatInTimezone(ann.DeletedAt, user.Timezone))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 200, Col: 93}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 201, Col: 59}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "\" class=\"btn btn-secondary text-xs px-3 py-1.5\">")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -512,20 +545,20 @@ func NotesHighlightsModal(user User, book handlers.MediaDetail) templ.Component
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "Restore ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "\" class=\"btn btn-ghost text-xs px-3 py-1.5\" style=\"color: var(--status-error);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -533,64 +566,64 @@ func NotesHighlightsModal(user User, book handlers.MediaDetail) templ.Component
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "Delete permanently
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "Delete permanently ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if ann.DisplayText != "" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var28 string
- templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(ann.DisplayText)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 217, Col: 94}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- } else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "(no text)
")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- if ann.SecondaryText != "" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var29 string
- templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(ann.SecondaryText)
+ templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(ann.DisplayText)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 222, Col: 103}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 223, Col: 94}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "(no text)
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, " ")
+ if ann.SecondaryText != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var30 string
+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(ann.SecondaryText)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 228, Col: 103}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "Close
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "Close
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -614,12 +647,12 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var30 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var30 == nil {
- templ_7745c5c3_Var30 = templ.NopComponent
+ templ_7745c5c3_Var31 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var31 == nil {
+ templ_7745c5c3_Var31 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/templates/book_detail_templ.go b/templates/book_detail_templ.go
index 7eb3431d..fda4b32c 100644
--- a/templates/book_detail_templ.go
+++ b/templates/book_detail_templ.go
@@ -17,7 +17,7 @@ import (
"strings"
)
-func BookDetail(user User, book handlers.MediaDetail, errorMessage string) templ.Component {
+func BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombstoneDays int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -1396,7 +1396,7 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string) templ
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = NotesHighlightsModal(user, book).Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = NotesHighlightsModal(user, book, tombstoneDays).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/web/src/book-detail.ts b/web/src/book-detail.ts
index 009318e1..3dce2aeb 100644
--- a/web/src/book-detail.ts
+++ b/web/src/book-detail.ts
@@ -311,6 +311,23 @@ Alpine.data("bookDetail", () => {
showToast("Could not delete the note", "error");
}
},
+ async deleteBannerHighlight(row: AnnotationRow) {
+ if (row.kind !== "highlight") return;
+ const mediaId = getMediaId();
+ const hlId = row.key.slice("hl-".length);
+ try {
+ const resp = await fetch(`/api/media-items/${mediaId}/highlights/${hlId}`, {
+ method: "DELETE",
+ headers: { Authorization: getAuthHeader() },
+ });
+ if (!resp.ok) throw new Error(String(resp.status));
+ // Tombstoned server-side: restorable from the Deleted modal.
+ showToast("Highlight deleted — restorable from Deleted", "success");
+ await this.fetchBannerAnnotations();
+ } catch (_e) {
+ showToast("Could not delete the highlight", "error");
+ }
+ },
showExportDialog(): void {
if (this.bannerRows.length === 0) return;
this.annotationsExportOpen = true;