diff --git a/src/components/series/SeriesDetail.svelte b/src/components/series/SeriesDetail.svelte index 6d01adb..80767a3 100644 --- a/src/components/series/SeriesDetail.svelte +++ b/src/components/series/SeriesDetail.svelte @@ -6,7 +6,7 @@ import { GET_MANGA, GET_CHAPTERS, FETCH_CHAPTERS, ENQUEUE_DOWNLOAD, UPDATE_MANGA, MARK_CHAPTER_READ, MARK_CHAPTERS_READ, DELETE_DOWNLOADED_CHAPTERS, ENQUEUE_CHAPTERS_DOWNLOAD, GET_ALL_MANGA, GET_CATEGORIES, CREATE_CATEGORY, UPDATE_MANGA_CATEGORIES } from "../../lib/queries"; import { cache, CACHE_KEYS, recordSourceAccess } from "../../lib/cache"; import { dedupeMangaById, dedupeMangaByTitle } from "../../lib/util"; - import { store, addToast, updateSettings, openReader, setActiveManga, setGenreFilter, setNavPage, linkManga, unlinkManga, setPreviewManga, checkAndMarkCompleted as storeCheckAndMarkCompleted, clearMarkersForManga } from "../../store/state.svelte"; + import { store, addToast, updateSettings, openReader, setActiveManga, setGenreFilter, setNavPage, linkManga, unlinkManga, setPreviewManga, checkAndMarkCompleted as storeCheckAndMarkCompleted, clearMarkersForManga, addBookmark } from "../../store/state.svelte"; import type { MangaPrefs } from "../../store/state.svelte"; import { DEFAULT_MANGA_PREFS } from "../../store/state.svelte"; import type { Manga, Chapter, Category } from "../../lib/types"; @@ -148,13 +148,30 @@ const continueChapter = $derived((() => { if (!sortedChapters.length) return null; - const asc = [...sortedChapters].sort((a, b) => a.sourceOrder - b.sourceOrder); - const anyRead = asc.some(c => c.isRead); + const asc = [...sortedChapters].sort((a, b) => a.sourceOrder - b.sourceOrder); + const anyRead = asc.some(c => c.isRead); + + const bookmark = store.activeManga + ? store.bookmarks.find(b => b.mangaId === store.activeManga!.id) + : null; + if (bookmark) { + const ch = asc.find(c => c.id === bookmark.chapterId); + if (ch) { + const isLastChapter = asc[asc.length - 1]?.id === ch.id; + const allRead = asc.every(c => c.isRead); + // If bookmarked chapter is the last one and everything is read, + // treat as fully finished — fall through to "reread" + if (!(isLastChapter && allRead)) { + return { chapter: ch, type: "continue" as const, resumePage: bookmark.pageNumber }; + } + } + } + const inProgress = asc.find(c => !c.isRead && (c.lastPageRead ?? 0) > 0); - if (inProgress) return { chapter: inProgress, type: "continue" as const }; + if (inProgress) return { chapter: inProgress, type: "continue" as const, resumePage: inProgress.lastPageRead! }; const firstUnread = asc.find(c => !c.isRead); - if (firstUnread) return { chapter: firstUnread, type: (anyRead ? "continue" : "start") as const }; - return { chapter: asc[0], type: "reread" as const }; + if (firstUnread) return { chapter: firstUnread, type: (anyRead ? "continue" : "start") as const, resumePage: null }; + return { chapter: asc[0], type: "reread" as const, resumePage: null }; })()); const jumpChapter = $derived.by(() => { @@ -235,8 +252,11 @@ } async function checkAndMarkCompleted(mangaId: number, chaps: Chapter[]) { - await storeCheckAndMarkCompleted(mangaId, chaps, allCategories, gql, UPDATE_MANGA_CATEGORIES, UPDATE_MANGA); - if (chaps.length) { + const mangaStatus = manga?.status; + await storeCheckAndMarkCompleted(mangaId, chaps, allCategories, gql, UPDATE_MANGA_CATEGORIES, UPDATE_MANGA, mangaStatus); + // Never auto-move an ONGOING series into Completed — user must do that manually. + const isOngoing = mangaStatus === "ONGOING"; + if (chaps.length && !isOngoing) { const allRead = chaps.every(c => c.isRead); const completed = allCategories.find(c => c.name === "Completed"); if (completed) { @@ -529,7 +549,7 @@ } catch (e) { console.error(e); } } - function openReaderWithAhead(ch: Chapter, list: Chapter[]) { + function openReaderWithAhead(ch: Chapter, list: Chapter[], type?: "start" | "continue" | "reread", resumePage?: number | null) { const ahead = getPref("downloadAhead"); if (ahead > 0) { const idx = list.indexOf(ch); @@ -538,6 +558,19 @@ if (toQueue.length) enqueueMultiple(toQueue); } } + if (type === "continue" && resumePage && resumePage > 1) { + const existing = store.bookmarks.find(b => b.chapterId === ch.id); + if (!existing || existing.pageNumber < resumePage) { + addBookmark({ + mangaId: store.activeManga!.id, + mangaTitle: store.activeManga!.title, + thumbnailUrl: store.activeManga!.thumbnailUrl, + chapterId: ch.id, + chapterName: ch.name, + pageNumber: resumePage, + }); + } + } openReader(ch, list); } @@ -608,11 +641,11 @@
{#if continueChapter} - {/if}
@@ -888,7 +921,7 @@ {@const inProgress = !ch.isRead && (ch.lastPageRead ?? 0) > 0} {@const isGridSelected = selectedIds.has(ch.id)} {/if} {:else if !loadingDetail} diff --git a/src/store/state.svelte.ts b/src/store/state.svelte.ts index f151651..701f177 100644 --- a/src/store/state.svelte.ts +++ b/src/store/state.svelte.ts @@ -643,8 +643,11 @@ class Store { gqlFn: (query: string, vars: Record) => Promise, UPDATE_MANGA_CATEGORIES: string, UPDATE_MANGA?: string, + mangaStatus?: string, ): Promise { if (!chaps.length) return; + // Never auto-complete an ongoing series — user must set Completed manually. + if (mangaStatus === "ONGOING") return; const allRead = chaps.every(c => c.isRead); const completed = categories.find(c => c.name === "Completed"); if (!completed) return; @@ -722,6 +725,7 @@ export async function checkAndMarkCompleted( gqlFn: (query: string, vars: Record) => Promise, UPDATE_MANGA_CATEGORIES: string, UPDATE_MANGA?: string, + mangaStatus?: string, ): Promise { - return store.checkAndMarkCompleted(mangaId, chaps, categories, gqlFn, UPDATE_MANGA_CATEGORIES, UPDATE_MANGA); + return store.checkAndMarkCompleted(mangaId, chaps, categories, gqlFn, UPDATE_MANGA_CATEGORIES, UPDATE_MANGA, mangaStatus); }