fix(web): banner highlight delete; retention wording states the days
The book-detail banner's highlight rows gain Delete (tombstoned server-side, restorable from the Deleted modal — same as the reader drawers already offered). The deleted-annotations modal now says the retention in plain language — 'kept for 30 days' from the annotation_tombstone_ttl_days tunable (threaded through BookDetail → NotesHighlightsModal; 1-day singular and retention-disabled variants handled) — instead of referencing a settings name a reader cannot see.
This commit is contained in:
1 parent
5275f2ba38
commit
f45adc6362
7 files changed
+502
-444
No files matched your search
@@ -1436,7 +1436,10 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
|
|
||||||
// Render template
|
// Render template
|
||||||
var buf bytes.Buffer
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,8 +99,7 @@ templ AnnotationsBanner(book handlers.MediaDetail) {
|
|||||||
></button>
|
></button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
x-show="row.kind === 'note'"
|
@click="row.kind === 'note' ? deleteBannerNote(row) : deleteBannerHighlight(row)"
|
||||||
@click="deleteBannerNote(row)"
|
|
||||||
class="text-xs px-2 py-1 rounded-lg hover:bg-surface-hover"
|
class="text-xs px-2 py-1 rounded-lg hover:bg-surface-hover"
|
||||||
style="color: var(--status-danger);"
|
style="color: var(--status-danger);"
|
||||||
>✕ Delete</button>
|
>✕ Delete</button>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) {
|
templ BookDetail(user User, book handlers.MediaDetail, errorMessage string, tombstoneDays int) {
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@@ -440,7 +440,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ProgressSyncModal(user, book)
|
@ProgressSyncModal(user, book)
|
||||||
@NotesHighlightsModal(user, book)
|
@NotesHighlightsModal(user, book, tombstoneDays)
|
||||||
@AnnotationsBanner(book)
|
@AnnotationsBanner(book)
|
||||||
if user.Role == "admin" {
|
if user.Role == "admin" {
|
||||||
@MetadataEditorModal(book)
|
@MetadataEditorModal(book)
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ templ ProgressSyncModal(user User, book handlers.MediaDetail) {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ NotesHighlightsModal(user User, book handlers.MediaDetail) {
|
templ NotesHighlightsModal(user User, book handlers.MediaDetail, tombstoneDays int) {
|
||||||
<div
|
<div
|
||||||
id="notes-modal"
|
id="notes-modal"
|
||||||
class="hidden fixed inset-0 z-50 flex items-center justify-center overflow-y-auto p-4"
|
class="hidden fixed inset-0 z-50 flex items-center justify-center overflow-y-auto p-4"
|
||||||
@@ -183,7 +183,13 @@ templ NotesHighlightsModal(user User, book handlers.MediaDetail) {
|
|||||||
<span class="badge" style="background-color: var(--accent-muted); color: var(--accent);">{ len(book.DeletedAnnotations) }</span>
|
<span class="badge" style="background-color: var(--accent-muted); color: var(--accent);">{ len(book.DeletedAnnotations) }</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs mb-4" style="color: var(--text-secondary);">
|
<p class="text-xs mb-4" style="color: var(--text-secondary);">
|
||||||
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.
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
for _, ann := range book.DeletedAnnotations {
|
for _, ann := range book.DeletedAnnotations {
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -17,7 +17,7 @@ import (
|
|||||||
"strings"
|
"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) {
|
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
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,6 +311,23 @@ Alpine.data("bookDetail", () => {
|
|||||||
showToast("Could not delete the note", "error");
|
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 {
|
showExportDialog(): void {
|
||||||
if (this.bannerRows.length === 0) return;
|
if (this.bannerRows.length === 0) return;
|
||||||
this.annotationsExportOpen = true;
|
this.annotationsExportOpen = true;
|
||||||
|
|||||||
Reference in new issue
Block a user