diff --git a/src/lib/components/library/Library.svelte b/src/lib/components/library/Library.svelte index ce6c6c1..87063fd 100644 --- a/src/lib/components/library/Library.svelte +++ b/src/lib/components/library/Library.svelte @@ -105,6 +105,15 @@ } async function doRemove(m: Manga) { + // Remove from every category first, then remove from library + const catIds = libraryState.categories + .filter(c => (libraryState.categoryMangaMap.get(c.id) ?? []).some(x => x.id === m.id)) + .map(c => c.id) + if (catIds.length) { + try { + await getAdapter().updateMangaCategories(String(m.id), [], catIds) + } catch (e) { console.error(e) } + } await getAdapter().removeFromLibrary(String(m.id)) libraryState.items = libraryState.items.filter(x => x.id !== m.id) await loadCategories() @@ -211,8 +220,17 @@ async function onBulkRemove() { bulkWorking = true try { + // For each selected manga, remove from all its categories first await Promise.allSettled( - [...libraryState.selected].map(id => getAdapter().removeFromLibrary(String(id))) + [...libraryState.selected].map(async (id) => { + const catIds = libraryState.categories + .filter(c => (libraryState.categoryMangaMap.get(c.id) ?? []).some(x => x.id === id)) + .map(c => c.id) + if (catIds.length) { + try { await getAdapter().updateMangaCategories(String(id), [], catIds) } catch {} + } + return getAdapter().removeFromLibrary(String(id)) + }) ) libraryState.items = libraryState.items.filter(m => !libraryState.selected.has(m.id)) libraryState.exitSelect() diff --git a/src/lib/components/recent/UpdatesTab.svelte b/src/lib/components/recent/UpdatesTab.svelte index 3295ad7..d0afaa5 100644 --- a/src/lib/components/recent/UpdatesTab.svelte +++ b/src/lib/components/recent/UpdatesTab.svelte @@ -1,8 +1,10 @@
@@ -124,50 +174,135 @@
- {#each items as item (item.id)} -
- - + + {:else} + + {/if} + {#if openingId === item.id} + + {:else} + + {/if} +
+ +
+ + {:else} + {@const bundle = row} + {@const expanded = expandedBundles[bundle.key] ?? false} + {@const first = bundle.items[0]} + {@const hasUnread = bundle.items.some(i => !i.isRead)} +
+ +
+ +
-
- {#if enqueueing.has(item.id)} - - {:else if item.isDownloaded} - - {:else} - - {/if} - {#if openingId === item.id} - - {:else} - - {/if} -
- -
+ + + {#if expanded} +
+ {#each bundle.items as item (item.id)} +
+ + {:else} + + {/if} + {#if openingId === item.id} + + {:else} + + {/if} +
+ +
+ {/each} + + {/if} + + {/if} + {/each} @@ -287,6 +422,48 @@ .dl-btn-delete { color: var(--color-error); } .dl-btn-delete:hover { background: var(--color-error-bg); } + /* ── Bundle styles ── */ + .bundle { + border-radius: var(--radius-md); + border: 1px solid var(--border-dim); + background: var(--bg-raised); + overflow: hidden; + transition: border-color var(--t-fast), background var(--t-fast); + } + .bundle.expanded { border-color: var(--border-strong); } + + .bundle-header { + display: flex; align-items: stretch; + transition: opacity var(--t-base); + } + .bundle-header.read { opacity: 0.5; } + .bundle-header:has(.bundle-summary:hover) { background: var(--bg-elevated); } + + .bundle-summary { + flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--sp-3); + padding: var(--sp-2) var(--sp-3); background: none; border: none; + cursor: pointer; text-align: left; + } + + .caret { color: var(--text-faint); display: flex; align-items: center; } + + .bundle-items { + border-top: 1px solid var(--border-dim); + display: flex; flex-direction: column; + } + + .bundle-child { + border-radius: 0; border: none; + border-bottom: 1px solid var(--border-dim); + background: var(--bg-overlay, var(--bg-elevated)); + padding-left: var(--sp-6); + } + .bundle-child:last-child { border-bottom: none; } + .bundle-child:has(.info-btn:hover:not(:disabled)) { + background: var(--bg-elevated); + border-color: transparent; + } + .empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-2); color: var(--text-faint);