(opts.table).get(id);
+ if (!raw) return null;
+ if (opts.decrypt) {
+ // clone before decrypt so the IDB-cached row stays ciphertext
+ return (await decryptRecord(opts.table, { ...raw })) as T;
+ }
+ return raw;
+ });
+ const sub = obs.subscribe((val) => {
+ entity = val ?? null;
+ loading = false;
+ if (val && !focused) {
+ opts.onLoad?.(val);
+ }
+ });
+ unsubscribe = () => sub.unsubscribe();
+ });
+
+ onDestroy(() => {
+ if (unsubscribe) unsubscribe();
+ });
+
+ return {
+ get entity() {
+ return entity;
+ },
+ get loading() {
+ return loading;
+ },
+ get focused() {
+ return focused;
+ },
+ get confirmDelete() {
+ return confirmDelete;
+ },
+ focus: () => {
+ focused = true;
+ },
+ blur: () => {
+ focused = false;
+ },
+ askDelete: () => {
+ confirmDelete = true;
+ },
+ cancelDelete: () => {
+ confirmDelete = false;
+ },
+ async deleteWithUndo({ label, delete: doDelete, goBack }: DeleteWithUndoOptions) {
+ const id = opts.id();
+ if (!id) return;
+ await doDelete();
+ goBack();
+ if (opts.table) {
+ toastStore.undo(label, () => {
+ db.table(opts.table!).update(id, {
+ deletedAt: undefined,
+ updatedAt: new Date().toISOString(),
+ });
+ });
+ } else {
+ // Custom loader: consumer must provide its own undo via the toast directly.
+ toastStore.undo(label, () => {});
+ }
+ },
+ };
+}
diff --git a/apps/mana/apps/web/src/lib/modules/calendar/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/calendar/views/DetailView.svelte
index 6605110cb..c625beacc 100644
--- a/apps/mana/apps/web/src/lib/modules/calendar/views/DetailView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/calendar/views/DetailView.svelte
@@ -3,11 +3,12 @@
All fields are always editable. Changes auto-save on blur.
-->
-
- {#if !event}
-
Termin nicht gefunden
- {:else}
-
+
+ {#snippet body(event)}
(focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
placeholder="Titel..."
/>
-
@@ -134,7 +131,7 @@
type="date"
class="prop-input"
bind:value={editDate}
- onfocus={() => (focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
/>
{#if !editAllDay}
@@ -143,7 +140,7 @@
type="time"
class="prop-input"
bind:value={editStartTime}
- onfocus={() => (focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
/>
—
@@ -151,7 +148,7 @@
type="time"
class="prop-input"
bind:value={editEndTime}
- onfocus={() => (focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
/>
@@ -168,21 +165,20 @@
(focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
placeholder="Ort hinzufügen..."
/>
- {#if timeBlock?.recurrenceRule}
+ {#if event._block?.recurrenceRule}
🔁
- {timeBlock.recurrenceRule}
+ {event._block.recurrenceRule}
{/if}
-
{#if eventTags.length > 0}
Tags
@@ -202,23 +198,20 @@
{/if}
-
-
Beschreibung
-
{#if event.createdAt}
Erstellt: {new Date(event.createdAt).toLocaleDateString('de')}
@@ -227,118 +220,27 @@
Bearbeitet: {new Date(event.updatedAt).toLocaleDateString('de')}
{/if}
-
-
-
- {#if confirmDelete}
-
Termin wirklich löschen?
-
-
-
-
- {:else}
-
- {/if}
-
- {/if}
-
+ {/snippet}
+
diff --git a/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte
index eb8f0ade7..3362202cc 100644
--- a/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte
@@ -5,64 +5,50 @@
-
- {#if !deck}
-
Deck nicht gefunden
- {:else}
-
+
+ detail.deleteWithUndo({
+ label: 'Deck gelöscht',
+ delete: () => deckStore.deleteDeck(deckId),
+ goBack,
+ })}
+>
+ {#snippet body(deck)}
(focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
placeholder="Deck-Name..."
/>
-
Farbe
@@ -109,7 +96,7 @@
type="color"
class="color-input"
bind:value={editColor}
- onfocus={() => (focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
/>
@@ -134,259 +121,23 @@
{/if}
-
Beschreibung
-
Erstellt: {new Date(deck.createdAt ?? '').toLocaleDateString('de')}
{#if deck.updatedAt}
Bearbeitet: {new Date(deck.updatedAt).toLocaleDateString('de')}
{/if}
-
-
-
- {#if confirmDelete}
-
Deck wirklich löschen?
-
-
-
-
- {:else}
-
- {/if}
-
- {/if}
-
-
-
+ {/snippet}
+
diff --git a/apps/mana/apps/web/src/lib/modules/citycorners/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/citycorners/views/DetailView.svelte
index 49da46b70..c94c2be94 100644
--- a/apps/mana/apps/web/src/lib/modules/citycorners/views/DetailView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/citycorners/views/DetailView.svelte
@@ -5,49 +5,34 @@
-
- {#if !location}
-
Ort nicht gefunden
- {:else}
-
+
+ detail.deleteWithUndo({
+ label: 'Ort gelöscht',
+ delete: deleteLocation,
+ goBack,
+ })}
+>
+ {#snippet body(location)}
(focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
placeholder="Name..."
/>
-
-
-
-
Beschreibung
-
Erstellt: {new Date(location.createdAt ?? '').toLocaleDateString('de')}
{#if location.updatedAt}
Bearbeitet: {new Date(location.updatedAt).toLocaleDateString('de')}
{/if}
-
-
-
- {#if confirmDelete}
-
Ort wirklich löschen?
-
- Löschen
- (confirmDelete = false)}>Abbrechen
-
- {:else}
-
(confirmDelete = true)}>
- Löschen
-
- {/if}
-
- {/if}
-
+ {/snippet}
+
diff --git a/apps/mana/apps/web/src/lib/modules/contacts/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/contacts/views/DetailView.svelte
index 6e6e400e6..86b9338c3 100644
--- a/apps/mana/apps/web/src/lib/modules/contacts/views/DetailView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/contacts/views/DetailView.svelte
@@ -3,32 +3,19 @@
All fields are always editable. Changes auto-save on blur.
-->
-
- {#if !contact}
-
Kontakt nicht gefunden
- {:else}
-
+
+ detail.deleteWithUndo({
+ label: 'Kontakt gelöscht',
+ delete: () => contactsStore.deleteContact(contactId),
+ goBack,
+ })}
+>
+ {#snippet body(contact)}
+ {/snippet}
+
diff --git a/apps/mana/apps/web/src/lib/modules/inventar/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/inventar/views/DetailView.svelte
index 0b83e3e21..7e1330384 100644
--- a/apps/mana/apps/web/src/lib/modules/inventar/views/DetailView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/inventar/views/DetailView.svelte
@@ -5,90 +5,73 @@
-
- {#if !collection}
-
Sammlung nicht gefunden
- {:else}
-
+
+ detail.deleteWithUndo({
+ label: 'Sammlung gelöscht',
+ delete: () => collectionsStore.delete(collectionId),
+ goBack,
+ })}
+>
+ {#snippet body(collection)}
{#if collection.icon}
{collection.icon}
@@ -96,22 +79,21 @@
(focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
placeholder="Name..."
/>
-
- Gegenstaende
+ Gegenstände
{itemCount}
-
Beschreibung
-
Erstellt: {new Date(collection.createdAt ?? '').toLocaleDateString('de')}
{#if collection.updatedAt}
Bearbeitet: {new Date(collection.updatedAt).toLocaleDateString('de')}
{/if}
-
-
-
- {#if confirmDelete}
-
Sammlung wirklich loeschen?
-
- Loeschen
- (confirmDelete = false)}>Abbrechen
-
- {:else}
-
(confirmDelete = true)}>
- Loeschen
-
- {/if}
-
- {/if}
-
-
-
+ {/snippet}
+
diff --git a/apps/mana/apps/web/src/lib/modules/memoro/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/memoro/views/DetailView.svelte
index 4f5867471..2ebaef15f 100644
--- a/apps/mana/apps/web/src/lib/modules/memoro/views/DetailView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/memoro/views/DetailView.svelte
@@ -3,47 +3,32 @@
Memo details with transcript, pin toggle, auto-save on blur.
-->
-
- {#if !memo}
-
Memo nicht gefunden
- {:else}
-
+
+ detail.deleteWithUndo({
+ label: 'Memo gelöscht',
+ delete: () => memosStore.delete(memoId),
+ goBack,
+ })}
+>
+ {#snippet body(memo)}
(focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
placeholder="Titel..."
/>
@@ -110,11 +98,10 @@
-
Status
-
+
{statusLabels[memo.processingStatus]}
@@ -129,27 +116,25 @@
(focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
placeholder="z.B. de"
/>
-
Zusammenfassung
-
{#if memo.transcript}
Transkript
@@ -157,78 +142,26 @@
{/if}
-
Erstellt: {new Date(memo.createdAt ?? '').toLocaleDateString('de')}
{#if memo.updatedAt}
Bearbeitet: {new Date(memo.updatedAt).toLocaleDateString('de')}
{/if}
-
-
-
- {#if confirmDelete}
-
Memo wirklich loeschen?
-
- Loeschen
- (confirmDelete = false)}>Abbrechen
-
- {:else}
-
(confirmDelete = true)}>
- Loeschen
-
- {/if}
-
- {/if}
-
+ {/snippet}
+
diff --git a/apps/mana/apps/web/src/lib/modules/music/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/music/views/DetailView.svelte
index fa56910b5..6f60f6ae8 100644
--- a/apps/mana/apps/web/src/lib/modules/music/views/DetailView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/music/views/DetailView.svelte
@@ -3,22 +3,16 @@
All fields are always editable. Changes auto-save on blur.
-->
-
- {#if !song}
-
Song nicht gefunden
- {:else}
-
+
+ detail.deleteWithUndo({
+ label: 'Song gelöscht',
+ delete: () => libraryStore.delete(songId),
+ goBack,
+ })}
+>
+ {#snippet body(song)}
(focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
placeholder="Titel..."
/>
-
+
-
@@ -146,9 +127,9 @@
type="number"
class="prop-input"
bind:value={editYear}
- onfocus={() => (focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
- placeholder="--"
+ placeholder="—"
/>
@@ -158,9 +139,9 @@
type="number"
class="prop-input"
bind:value={editBpm}
- onfocus={() => (focused = true)}
+ onfocus={detail.focus}
onblur={saveField}
- placeholder="--"
+ placeholder="—"
/>
@@ -175,7 +156,6 @@
-
Erstellt: {new Date(song.createdAt ?? '').toLocaleDateString('de')}
{#if song.updatedAt}
@@ -185,205 +165,5 @@
Zuletzt gehört: {new Date(song.lastPlayedAt).toLocaleDateString('de')}
{/if}
-
-
-
- {#if confirmDelete}
-
Song wirklich löschen?
-
- Löschen
- (confirmDelete = false)}>Abbrechen
-
- {:else}
-
(confirmDelete = true)}>
- Löschen
-
- {/if}
-
- {/if}
-
-
-
+ {/snippet}
+
diff --git a/apps/mana/apps/web/src/lib/modules/places/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/places/views/DetailView.svelte
index 885430ca8..a808e68d7 100644
--- a/apps/mana/apps/web/src/lib/modules/places/views/DetailView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/places/views/DetailView.svelte
@@ -3,23 +3,20 @@
All fields are always editable. Changes auto-save on blur.
-->
-
- {#if !place}
-
Ort nicht gefunden
- {:else}
-
+
+ {#snippet body(place)}