Feat: Recent Tab (Unread State) + Bug Fixes

This commit is contained in:
Youwes09
2026-06-12 17:27:08 -05:00
parent 31a19687ce
commit 9dad1fb329
40 changed files with 668 additions and 668 deletions
+44 -28
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { Download, CheckCircle, Circle, CircleNotch, Trash } from 'phosphor-svelte'
import { Download, CheckSquare, Square, CircleNotch, Trash } from 'phosphor-svelte'
import ContextMenu from '$lib/components/shared/ui/ContextMenu.svelte'
import type { MenuEntry } from '$lib/components/shared/ui/ContextMenu.svelte'
import { longPress } from '$lib/core/ui/touchscreen'
@@ -14,27 +14,39 @@
enqueueing: Set<number>
chapterPage: number
totalPages: number
scrollEl?: HTMLDivElement | null
onOpen: (ch: Chapter, inProgress: boolean) => void
onToggleSelect: (id: number, e: MouseEvent | KeyboardEvent) => void
onEnqueue: (ch: Chapter, e: MouseEvent) => void
onDeleteDownload:(id: number) => void
onPageChange: (page: number) => void
onPageSizeChange:(n: number) => void
buildCtxItems: (ch: Chapter, idx: number) => MenuEntry[]
}
let {
pageChapters, sortedChapters, viewMode, loadingChapters,
selectedIds, enqueueing, chapterPage, totalPages,
scrollEl = $bindable(null),
onOpen, onToggleSelect, onEnqueue, onDeleteDownload,
onPageChange, buildCtxItems,
onPageChange, onPageSizeChange, buildCtxItems,
}: Props = $props()
let ctx: { x: number; y: number; chapter: Chapter; idx: number } | null = $state(null)
let listEl: HTMLDivElement | null = $state(null)
const hasSelection = $derived(selectedIds.size > 0)
$effect(() => {
if (!listEl || viewMode !== 'list') return
const ro = new ResizeObserver(([entry]) => {
const firstRow = listEl!.querySelector('.ch-row') as HTMLElement | null
const rowH = firstRow ? firstRow.offsetHeight : 37
const n = Math.max(1, Math.floor(entry.contentRect.height / rowH))
onPageSizeChange(n)
})
ro.observe(listEl)
return () => ro.disconnect()
})
function chapterLongPress(node: HTMLElement, param: [Chapter, number]) {
const [ch, idx] = param
return longPress(node, {
@@ -50,7 +62,7 @@
}
</script>
<div class={viewMode === 'grid' ? 'ch-grid' : 'ch-list'} bind:this={scrollEl}>
<div class={viewMode === 'grid' ? 'ch-grid' : 'ch-list'} bind:this={listEl}>
{#if loadingChapters && sortedChapters.length === 0}
{#if viewMode === 'grid'}
{#each Array(24) as _}<div class="grid-cell-skeleton skeleton"></div>{/each}
@@ -100,7 +112,7 @@
oncontextmenu={(e) => { e.preventDefault(); ctx = { x: e.clientX, y: e.clientY, chapter: ch, idx: idxInSorted } }}
>
<button class="ch-check" class:ch-check-visible={hasSelection} onclick={(e) => onToggleSelect(ch.id, e)} title="Select">
{#if isSelected}<CheckCircle size={15} weight="fill" />{:else}<Circle size={15} weight="light" />{/if}
{#if isSelected}<CheckSquare size={15} weight="fill" />{:else}<Square size={15} weight="light" />{/if}
</button>
<div class="ch-left">
<span class="ch-name">{ch.name}</span>
@@ -111,7 +123,7 @@
</div>
</div>
<div class="ch-right">
{#if ch.read}<CheckCircle size={14} weight="light" class="read-icon" />{/if}
{#if ch.read}<CheckSquare size={14} weight="light" class="read-icon" />{/if}
{#if ch.downloaded}
<div class="ch-dl-wrap">
<Download size={13} weight="fill" class="ch-dl-icon" />
@@ -145,38 +157,42 @@
{/if}
<style>
.ch-list { flex: 1; overflow-y: auto; }
.ch-grid { flex: 1; overflow-y: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); gap: 4px; padding: var(--sp-3); align-content: start; }
.ch-list { flex: 1; overflow: hidden; }
.ch-grid { flex: 1; overflow: hidden; display: grid; grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); gap: 4px; padding: var(--sp-3); align-content: start; }
.ch-row { display: flex; align-items: center; padding: 10px var(--sp-4); border-bottom: 1px solid var(--border-dim); cursor: pointer; transition: background var(--t-fast); gap: var(--sp-3); }
.ch-row { display: flex; align-items: center; padding: 8px var(--sp-4); border-bottom: 1px solid var(--border-dim); cursor: pointer; transition: background var(--t-fast); gap: var(--sp-3); }
.ch-row:hover { background: var(--bg-raised); }
.ch-row.read { opacity: 0.45; }
.ch-left { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.ch-row.read { opacity: 0.5; }
.ch-left { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ch-name { font-size: var(--text-sm); color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ch-meta { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.ch-meta { display: flex; align-items: center; gap: var(--sp-2); }
.ch-meta-item { font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); }
.ch-right { display: flex; align-items: center; gap: var(--sp-1); flex-shrink: 0; }
:global(.read-icon) { color: var(--text-faint); }
:global(.enqueue-icon) { color: var(--text-faint); }
.dl-btn { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: var(--radius-sm); color: var(--text-faint); transition: color var(--t-base), background var(--t-base); opacity: 0; }
.ch-row:hover .dl-btn { opacity: 1; }
.dl-btn { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: var(--radius-sm); color: var(--text-faint); transition: color var(--t-base), background var(--t-base); }
.dl-btn:hover { color: var(--text-muted); background: var(--bg-overlay); }
.dl-btn-delete { color: var(--color-error) !important; opacity: 0; }
.ch-row:hover .dl-btn-delete { opacity: 1; }
.dl-btn-delete { color: var(--color-error) !important; }
.dl-btn-delete:hover { background: var(--color-error-bg) !important; }
.ch-dl-wrap { position: relative; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; }
:global(.ch-dl-icon) { color: var(--text-faint); transition: opacity var(--t-fast); }
.ch-row:hover .ch-dl-wrap :global(.ch-dl-icon) { opacity: 0; }
.ch-dl-wrap .dl-btn-delete { position: absolute; inset: 0; opacity: 0; }
.ch-row:hover .ch-dl-wrap .dl-btn-delete { opacity: 1; }
.ch-dl-wrap { display: flex; align-items: center; gap: var(--sp-1); }
:global(.ch-dl-icon) { color: var(--text-faint); }
.ch-check { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; flex-shrink: 0; border-radius: var(--radius-sm); border: none; background: none; color: var(--text-faint); cursor: pointer; opacity: 0; transition: opacity var(--t-fast), color var(--t-fast); padding: 0; }
.ch-row:hover .ch-check { opacity: 1; }
.ch-check-visible { opacity: 1 !important; }
.ch-check {
display: flex; align-items: center; justify-content: center;
width: 20px; height: 20px; flex-shrink: 0;
border-radius: var(--radius-sm); border: none; background: none;
color: var(--text-faint); cursor: pointer; padding: 0;
opacity: 0;
transform: translateX(-6px);
transition: opacity var(--t-fast), transform var(--t-fast), color var(--t-fast);
margin-right: -20px;
}
.ch-row:hover .ch-check { opacity: 1; transform: translateX(0); margin-right: 0; }
.ch-check-visible { opacity: 1 !important; transform: translateX(0) !important; margin-right: 0 !important; }
.ch-selected .ch-check { color: var(--accent-fg); }
.ch-selected { background: color-mix(in srgb, var(--accent) 8%, transparent) !important; }
.ch-selected .ch-check { color: var(--accent-fg); opacity: 1; }
.row-skeleton { display: flex; flex-direction: column; gap: var(--sp-2); padding: 12px var(--sp-4); border-bottom: 1px solid var(--border-dim); }
@@ -184,8 +200,8 @@
.grid-cell:hover { background: var(--bg-overlay); border-color: var(--border-strong); }
.grid-cell.read { background: var(--color-read); color: var(--text-faint); border-color: transparent; }
.grid-cell-num { font-size: 10px; }
.grid-cell-dot { position: absolute; bottom: 3px; right: 3px; width: 4px; height: 4px; border-radius: 50%; background: var(--text-faint); }
.grid-cell-dl { position: absolute; top: 3px; left: 3px; width: 4px; height: 4px; border-radius: 50%; background: var(--accent-fg); }
.grid-cell-dot { position: absolute; bottom: 3px; right: 3px; width: 4px; height: 4px; border-radius: var(--radius-sm); background: var(--text-faint); }
.grid-cell-dl { position: absolute; top: 3px; left: 3px; width: 4px; height: 4px; border-radius: var(--radius-sm); background: var(--accent-fg); }
.grid-cell-spinner { position: absolute; top: 2px; right: 2px; }
.grid-cell-skeleton { aspect-ratio: 1; border-radius: var(--radius-sm); }
.grid-selected { background: var(--accent-muted) !important; border-color: var(--accent-dim) !important; }
+6 -24
View File
@@ -19,8 +19,6 @@
sortMode: ChapterSortMode
sortDir: ChapterSortDir
viewMode: 'list' | 'grid'
chapterPage: number
totalPages: number
downloadedCount: number
totalCount: number
deletingAll: boolean
@@ -57,7 +55,7 @@
let {
chapters, sortedChapters, sortMode, sortDir, viewMode,
chapterPage, totalPages, downloadedCount, totalCount, deletingAll,
downloadedCount, totalCount, deletingAll,
hasSelection, selectedCount, continueChapter,
availableScanlators, scanlatorFilter, scanlatorBlacklist, scanlatorForce,
allCategories, mangaCategories, catsLoading, refreshing,
@@ -277,9 +275,11 @@
<ArrowsClockwise size={14} weight="light" class={refreshing ? 'anim-spin' : ''} />
</button>
<button class="icon-btn" onclick={onOpenFolder} title="Open manga folder">
<FolderOpen size={14} weight="light" />
</button>
{#if downloadedCount > 0}
<button class="icon-btn" onclick={onOpenFolder} title="Open manga folder">
<FolderOpen size={14} weight="light" />
</button>
{/if}
<div class="fp-wrap" bind:this={folderPickerRef}>
<button class="icon-btn" class:active={hasFolders} onclick={() => folderPickerOpen = !folderPickerOpen}>
@@ -377,13 +377,6 @@
</div>
{/if}
{#if totalPages > 1}
<div class="pagination">
<button class="page-btn" onclick={() => onPageChange(Math.max(1, chapterPage - 1))} disabled={chapterPage === 1}></button>
<span class="page-num">{chapterPage} / {totalPages}</span>
<button class="page-btn" onclick={() => onPageChange(Math.min(totalPages, chapterPage + 1))} disabled={chapterPage === totalPages}></button>
</div>
{/if}
</div>
</div>
@@ -572,17 +565,6 @@
.dl-unified-btn.dl-has-count:hover { background: var(--accent-muted); border-color: var(--accent); opacity: 0.9; }
.dl-unified-btn.active { color: var(--accent-fg); border-color: var(--accent-dim); background: var(--accent-muted); }
.pagination { display: flex; align-items: center; gap: var(--sp-2); }
.page-btn {
font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide);
padding: 4px 10px; border-radius: var(--radius-sm); border: 1px solid var(--border-dim);
color: var(--text-faint); background: none; cursor: pointer;
transition: color var(--t-base), border-color var(--t-base);
}
.page-btn:hover:not(:disabled) { color: var(--text-muted); border-color: var(--border-strong); }
.page-btn:disabled { opacity: 0.3; cursor: default; }
.page-num { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); }
.sel-count {
font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-muted);
letter-spacing: var(--tracking-wide); padding: 0 var(--sp-1);
+33 -40
View File
@@ -8,13 +8,17 @@
CheckCircle, Circle, ArrowFatLinesUp, ArrowFatLinesDown,
ArrowFatLineUp, ArrowFatLineDown, Download, Trash, DownloadSimple, CheckSquare,
} from 'phosphor-svelte'
import type { MenuEntry } from '$lib/components/shared/ui/ContextMenu.svelte'
type MenuSeparator = { separator: true }
type MenuItem = { label: string; icon?: any; onClick: () => void; danger?: boolean; disabled?: boolean; separator?: never; children?: MenuEntry[] }
type MenuEntry = MenuItem | MenuSeparator
import { getManga, getMangaList } from '$lib/request-manager/manga'
import { markChapterRead, markChaptersRead, deleteDownloadedChapters, fetchChapters } from '$lib/request-manager/chapters'
import { downloadStore } from '$lib/state/downloads.svelte'
import { getCategories, updateMangaCategories, createCategory as createCategoryReq, updateManga } from '$lib/request-manager/manga'
import { saveScroll, getScroll } from '$lib/state/app.svelte'
import { seriesState, openReaderForChapter, acknowledgeUpdate, addBookmark, clearMarkersForManga } from '$lib/state/series.svelte'
import { updateSettings } from '$lib/state/settings.svelte'
import { DEFAULT_MANGA_PREFS } from '$lib/state/series.svelte'
import type { MangaPrefs } from '$lib/types/settings'
import { addToast } from '$lib/state/notifications.svelte'
@@ -33,8 +37,8 @@
interface Props { mangaId: number }
let { mangaId }: Props = $props()
const CHAPTERS_PER_PAGE = 25
const MANGA_TTL_MS = 5 * 60 * 1000
let chaptersPerPage: number = $state(25)
const MANGA_TTL_MS = 5 * 60 * 1000
const mangaCache: Map<number, { data: Manga; fetchedAt: number }> = new Map()
@@ -80,8 +84,8 @@
const scanlatorBlacklist = $derived(get('scanlatorBlacklist') as string[])
const scanlatorForce = $derived(get('scanlatorForce') as boolean)
const totalPages = $derived(Math.ceil(sortedChapters.length / CHAPTERS_PER_PAGE))
const pageChapters = $derived(sortedChapters.slice((chapterPage - 1) * CHAPTERS_PER_PAGE, chapterPage * CHAPTERS_PER_PAGE))
const totalPages = $derived(Math.ceil(sortedChapters.length / chaptersPerPage))
const pageChapters = $derived(sortedChapters.slice((chapterPage - 1) * chaptersPerPage, chapterPage * chaptersPerPage))
const readCount = $derived(sortedChapters.filter(c => c.read).length)
const totalCount = $derived(sortedChapters.length)
const progressPct = $derived(totalCount > 0 ? (readCount / totalCount) * 100 : 0)
@@ -94,11 +98,11 @@
const bookmark = seriesState.bookmarks.find(b => b.mangaId === mangaId)
const bookmarkedCh = bookmark ? asc.find(c => c.id === bookmark.chapterId) : null
if (bookmarkedCh && !bookmarkedCh.read)
return { chapter: bookmarkedCh, type: (anyRead ? 'continue' : 'start') as const, resumePage: bookmark!.pageNumber }
return { chapter: bookmarkedCh, type: (anyRead ? 'continue' : 'start') as 'continue' | 'start', resumePage: bookmark!.pageNumber }
const inProgress = asc.find(c => !c.read && (c.lastPageRead ?? 0) > 0)
const firstUnread = asc.find(c => !c.read)
const target = inProgress ?? firstUnread
if (target) return { chapter: target, type: (anyRead ? 'continue' : 'start') as const, resumePage: null }
if (target) return { chapter: target, type: (anyRead ? 'continue' : 'start') as 'continue' | 'start', resumePage: null }
return { chapter: asc[0], type: 'reread' as const, resumePage: null }
})())
@@ -140,8 +144,13 @@
const completed = allCategories.find(c => c.name === 'Completed')
if (!completed) return
const inCompleted = mangaCategories.some(c => c.id === completed.id)
if (allRead && !inCompleted) mangaCategories = [...mangaCategories, completed]
else if (!allRead && inCompleted) mangaCategories = mangaCategories.filter(c => c.id !== completed.id)
if (allRead && !inCompleted) {
await updateMangaCategories(String(id), [completed.id], []).catch(console.error)
mangaCategories = [...mangaCategories, completed]
} else if (!allRead && inCompleted) {
await updateMangaCategories(String(id), [], [completed.id]).catch(console.error)
mangaCategories = mangaCategories.filter(c => c.id !== completed.id)
}
}
function loadMangaData(id: number) {
@@ -154,7 +163,6 @@
loadingManga = false
seriesState.setActiveManga(cached.data)
if (Date.now() - cached.fetchedAt < MANGA_TTL_MS) return
// stale-while-revalidate: update cache + store in background
getManga(id, ctrl.signal)
.then(m => {
if (ctrl.signal.aborted) return
@@ -224,8 +232,8 @@
const records = trackingState.recordsFor(id)
if (!records.length) return
const prefs = {
sortMode: get('sortMode'),
sortDir: get('sortDir'),
sortMode: seriesState.settings.chapterSortMode,
sortDir: seriesState.settings.chapterSortDir,
preferredScanlator: get('preferredScanlator') as string,
scanlatorFilter: scanlatorFilter,
scanlatorBlacklist: scanlatorBlacklist,
@@ -278,7 +286,7 @@
checkAndMarkCompleted(mangaId, seriesState.chaptersFor(mangaId))
const ch = seriesState.chaptersFor(mangaId).find(c => c.id === chapterId)
const currentPrefs = {
sortMode: get('sortMode'), sortDir: get('sortDir'),
sortMode: seriesState.settings.chapterSortMode, sortDir: seriesState.settings.chapterSortDir,
preferredScanlator: get('preferredScanlator') as string,
scanlatorFilter, scanlatorBlacklist, scanlatorForce,
}
@@ -310,7 +318,7 @@
seriesState.patchChapters(mangaId, chaps => chaps.map(c => idSet.has(c.id) ? { ...c, read: isRead } : c))
checkAndMarkCompleted(mangaId, seriesState.chaptersFor(mangaId))
const currentPrefs = {
sortMode: get('sortMode'), sortDir: get('sortDir'),
sortMode: seriesState.settings.chapterSortMode, sortDir: seriesState.settings.chapterSortDir,
preferredScanlator: get('preferredScanlator') as string,
scanlatorFilter, scanlatorBlacklist, scanlatorForce,
}
@@ -431,21 +439,8 @@
openReaderForChapter(ch, manga)
}
function handleContinue(cc: typeof continueChapter) {
if (!cc) return
if (cc.type === 'continue' && cc.resumePage && cc.resumePage > 1) {
const existing = seriesState.bookmarks.find(b => b.chapterId === cc.chapter.id)
if (!existing || existing.pageNumber < cc.resumePage) {
addBookmark({
mangaId,
mangaTitle: manga!.title,
thumbnailUrl: manga!.thumbnailUrl,
chapterId: cc.chapter.id,
chapterName: cc.chapter.name,
pageNumber: cc.resumePage,
})
}
}
interface ContinueChapter { chapter: Chapter; type: 'start' | 'continue' | 'reread'; resumePage: number | null }
function handleContinue(cc: ContinueChapter) {
openReaderForChapter(cc.chapter, manga)
}
@@ -472,7 +467,7 @@
async function toggleCategory(cat: Category) {
const inCat = mangaCategories.some(c => c.id === cat.id)
try {
await updateMangaCategories(mangaId, inCat ? [] : [cat.id], inCat ? [cat.id] : [])
await updateMangaCategories(String(mangaId), inCat ? [] : [cat.id], inCat ? [cat.id] : [])
if (!inCat && !manga?.inLibrary) {
await updateManga(mangaId, { inLibrary: true }).catch(console.error)
if (manga) { manga = { ...manga, inLibrary: true }; seriesState.setActiveManga(manga) }
@@ -485,7 +480,7 @@
if (!name) return
try {
const cat = await createCategoryReq(name)
await updateMangaCategories(mangaId, [cat.id], [])
await updateMangaCategories(String(mangaId), [cat.id], [])
if (!manga?.inLibrary) {
await updateManga(mangaId, { inLibrary: true }).catch(console.error)
if (manga) { manga = { ...manga, inLibrary: true }; seriesState.setActiveManga(manga) }
@@ -514,7 +509,7 @@
{loadingLinkList}
{mangaCategories}
{togglingLibrary}
onRead={handleContinue}
onRead={(ch) => handleContinue(ch)}
onToggleLibrary={toggleLibrary}
onDeleteAll={deleteAllDownloads}
onMigrateOpen={() => migrateOpen = true}
@@ -526,15 +521,13 @@
onGenreClick={(genre) => goto(`/browse?genre=${encodeURIComponent(genre)}`)}
/>
<div class="list-wrap">
<div class="list-wrap" bind:this={chapterListEl}>
<SeriesActions
{chapters}
{sortedChapters}
sortMode={get('sortMode')}
sortDir={get('sortDir')}
sortMode={seriesState.settings.chapterSortMode}
sortDir={seriesState.settings.chapterSortDir}
{viewMode}
{chapterPage}
{totalPages}
{downloadedCount}
{totalCount}
{deletingAll}
@@ -564,8 +557,8 @@
onSetScanlatorFilter={(v) => set('scanlatorFilter', v)}
onSetScanlatorBlacklist={(v) => set('scanlatorBlacklist', v)}
onSetScanlatorForce={(v) => set('scanlatorForce', v)}
onSortModeChange={(v) => set('sortMode', v)}
onSortDirChange={(v) => set('sortDir', v)}
onSortModeChange={(v) => updateSettings({ chapterSortMode: v })}
onSortDirChange={(v) => updateSettings({ chapterSortDir: v })}
onOpenFolder={() => manga && openMangaFolder(manga)}
/>
@@ -578,12 +571,12 @@
{enqueueing}
{chapterPage}
{totalPages}
bind:scrollEl={chapterListEl}
onOpen={openReaderWithAhead}
onToggleSelect={toggleSelect}
onEnqueue={enqueue}
onDeleteDownload={deleteDownloaded}
onPageChange={(p) => chapterPage = p}
onPageSizeChange={(n) => { chaptersPerPage = n; chapterPage = Math.min(chapterPage, Math.ceil(sortedChapters.length / n) || 1) }}
{buildCtxItems}
/>
</div>
@@ -163,7 +163,7 @@
<Play size={12} weight="fill" />
{continueChapter.type === 'reread' ? 'Read again'
: continueChapter.type === 'start' ? 'Start reading'
: `Continue · Ch.${continueChapter.chapter.chapterNumber}${continueChapter.resumePage ? ` p.${continueChapter.resumePage}` : ''}`}
: `Continue · Ch.${continueChapter.chapter.chapterNumber}`}
</button>
{/if}
<div class="actions">
@@ -1,19 +0,0 @@
import { settingsState, updateSettings } from '$lib/state/settings.svelte'
import { DEFAULT_MANGA_PREFS } from '$lib/types/settings'
import type { MangaPrefs } from '$lib/types/settings'
export { DEFAULT_MANGA_PREFS } from '$lib/types/settings'
export function getPref<K extends keyof MangaPrefs>(mangaId: number, key: K): MangaPrefs[K] {
const prefs = settingsState.settings.mangaPrefs?.[mangaId] ?? {}
return (prefs[key] ?? DEFAULT_MANGA_PREFS[key]) as MangaPrefs[K]
}
export function setPref<K extends keyof MangaPrefs>(mangaId: number, key: K, value: MangaPrefs[K]) {
updateSettings({
mangaPrefs: {
...settingsState.settings.mangaPrefs,
[mangaId]: { ...(settingsState.settings.mangaPrefs?.[mangaId] ?? {}), [key]: value },
},
})
}
@@ -1,6 +1,6 @@
<script lang="ts">
import { X } from "phosphor-svelte";
import { getPref, setPref } from "$lib/components/series/lib/mangaPrefs";
import { getPref, setPref } from "$lib/state/series.svelte";
import { settingsState } from "$lib/state/settings.svelte";
import { libraryState } from "$lib/state/library.svelte";
import { resolvedCover } from "$lib/core/cover/coverResolver";
@@ -1,6 +1,6 @@
<script lang="ts">
import { X, CaretLeft, CaretRight, CircleNotch } from "phosphor-svelte";
import { setPref } from "$lib/components/series/lib/mangaPrefs";
import { setPref } from "$lib/state/series.svelte";
import { coverCandidatesSync, dedupeByImage } from "$lib/core/cover/coverResolver";
import Thumbnail from "$lib/components/shared/manga/Thumbnail.svelte";
import type { Manga } from "$lib/types";