mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Feat: Shift from Stable to Preview (WIP)
This commit is contained in:
@@ -135,9 +135,9 @@
|
||||
|
||||
const f = store.settings.libraryTabFilters?.[tab] ?? {};
|
||||
if (f.unread) items = items.filter(m => (m.unreadCount ?? 0) > 0);
|
||||
if (f.started) items = items.filter(m => (m.unreadCount ?? 0) > 0 && (m.chapterCount ?? 0) > (m.unreadCount ?? 0));
|
||||
if (f.started) items = items.filter(m => (m.unreadCount ?? 0) > 0 && (m.chapters?.totalCount ?? 0) > (m.unreadCount ?? 0));
|
||||
if (f.downloaded) items = items.filter(m => (m.downloadCount ?? 0) > 0);
|
||||
if (f.bookmarked) items = items.filter(m => !!(m as any).hasBookmark);
|
||||
if (f.bookmarked) items = items.filter(m => (m.bookmarkCount ?? 0) > 0);
|
||||
|
||||
const recentlyReadMap = new Map<number, number>();
|
||||
if (tabSortMode === "recentlyRead") {
|
||||
@@ -247,7 +247,7 @@
|
||||
cache.get(CACHE_KEYS.LIBRARY, () => gql<{ mangas: { nodes: Manga[] } }>(GET_LIBRARY).then(d => d.mangas.nodes), DEFAULT_TTL_MS, CACHE_GROUPS.LIBRARY),
|
||||
reloadCategories(),
|
||||
]);
|
||||
const mapped = nodes.map((m: any) => ({ ...m, chapterCount: m.chapters?.totalCount ?? m.chapterCount ?? 0 }));
|
||||
const mapped = nodes.map((m: any) => ({ ...m }));
|
||||
allManga = dedupeMangaByTitle(dedupeMangaById(mapped), store.settings.mangaLinks);
|
||||
error = null;
|
||||
await migrateCategorizedToLibrary();
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
<div class="grid" style="--cols:{cols}">
|
||||
{#each visibleManga as m (m.id)}
|
||||
{@const isSelected = selectedIds.has(m.id)}
|
||||
{@const isCompleted = !m.unreadCount && (m.chapterCount ?? 0) > 0}
|
||||
{@const isCompleted = !m.unreadCount && (m.chapters?.totalCount ?? 0) > 0}
|
||||
<button
|
||||
class="card"
|
||||
class:card-selected={isSelected}
|
||||
|
||||
@@ -16,11 +16,11 @@ export const librarySorter = createSorter<Manga>({
|
||||
},
|
||||
{
|
||||
key: "totalChapters",
|
||||
comparator: (a, b) => (a.chapterCount ?? 0) - (b.chapterCount ?? 0),
|
||||
comparator: (a, b) => (a.chapters?.totalCount ?? 0) - (b.chapters?.totalCount ?? 0),
|
||||
},
|
||||
{
|
||||
key: "recentlyAdded",
|
||||
comparator: (a, b) => a.id - b.id,
|
||||
comparator: (a, b) => Number(a.inLibraryAt ?? 0) - Number(b.inLibraryAt ?? 0),
|
||||
},
|
||||
{
|
||||
key: "recentlyRead",
|
||||
@@ -33,11 +33,11 @@ export const librarySorter = createSorter<Manga>({
|
||||
},
|
||||
{
|
||||
key: "latestFetched",
|
||||
comparator: (a, b) => a.id - b.id,
|
||||
comparator: (a, b) => Number(a.latestFetchedChapter?.uploadDate ?? 0) - Number(b.latestFetchedChapter?.uploadDate ?? 0),
|
||||
},
|
||||
{
|
||||
key: "latestUploaded",
|
||||
comparator: (a, b) => a.id - b.id,
|
||||
comparator: (a, b) => Number(a.latestUploadedChapter?.uploadDate ?? 0) - Number(b.latestUploadedChapter?.uploadDate ?? 0),
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -49,4 +49,4 @@ export function sortLibrary(
|
||||
recentlyReadMap?: Map<number, number>,
|
||||
): Manga[] {
|
||||
return librarySorter.sort(items, mode, dir, recentlyReadMap ? { recentlyReadMap } : undefined);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
import { gql } from "@api/client";
|
||||
import { LIBRARY_UPDATE_STATUS } from "@api/queries/manga";
|
||||
import { UPDATE_LIBRARY } from "@api/mutations/manga";
|
||||
import { GET_RECENTLY_UPDATED } from "@api/queries/chapters";
|
||||
import { UPDATE_LIBRARY, FETCH_MANGA } from "@api/mutations/manga";
|
||||
import { GET_LIBRARY } from "@api/queries/manga";
|
||||
import type { LibraryUpdateEntry } from "@store/state.svelte";
|
||||
|
||||
const POLL_INTERVAL_MS = 3000;
|
||||
const POLL_INITIAL_MS = 2000;
|
||||
const POLL_INTERVAL_MS = 2000;
|
||||
const POLL_INITIAL_MS = 500;
|
||||
|
||||
export interface UpdateProgress {
|
||||
finished: number;
|
||||
total: number;
|
||||
finished: number;
|
||||
total: number;
|
||||
skippedManga: number;
|
||||
skippedCategories: number;
|
||||
}
|
||||
|
||||
export interface UpdateResult {
|
||||
@@ -21,89 +23,138 @@ export interface UpdateResult {
|
||||
export interface LibraryUpdaterCallbacks {
|
||||
onProgress: (p: UpdateProgress) => void;
|
||||
onDone: (r: UpdateResult) => void;
|
||||
onError: () => void;
|
||||
onError: (e?: unknown) => void;
|
||||
}
|
||||
|
||||
export async function refreshLibraryMetadata(
|
||||
onProgress?: (done: number, total: number) => void,
|
||||
): Promise<void> {
|
||||
const data = await gql<{ mangas: { nodes: { id: number }[] } }>(GET_LIBRARY, {});
|
||||
const ids = data.mangas.nodes.map(m => m.id);
|
||||
let done = 0;
|
||||
for (const id of ids) {
|
||||
try {
|
||||
await gql(FETCH_MANGA, { id });
|
||||
} catch {}
|
||||
onProgress?.(++done, ids.length);
|
||||
}
|
||||
}
|
||||
|
||||
export function startLibraryUpdate(callbacks: LibraryUpdaterCallbacks): () => void {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
let cancelled = false;
|
||||
const startedAt = Math.floor(Date.now() / 1000);
|
||||
|
||||
function cancel() {
|
||||
cancelled = true;
|
||||
if (timer) { clearTimeout(timer); timer = null; }
|
||||
}
|
||||
|
||||
function buildEntries(
|
||||
mangaUpdates: { status: string; manga: { id: number; title: string; thumbnailUrl: string; unreadCount: number } }[]
|
||||
): LibraryUpdateEntry[] {
|
||||
const byManga = new Map<number, LibraryUpdateEntry>();
|
||||
for (const u of mangaUpdates) {
|
||||
if (u.status !== "UPDATED") continue;
|
||||
const existing = byManga.get(u.manga.id);
|
||||
if (existing) {
|
||||
existing.newChapters++;
|
||||
} else {
|
||||
byManga.set(u.manga.id, {
|
||||
mangaId: u.manga.id,
|
||||
mangaTitle: u.manga.title,
|
||||
thumbnailUrl: u.manga.thumbnailUrl,
|
||||
newChapters: 1,
|
||||
checkedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
return [...byManga.values()];
|
||||
}
|
||||
|
||||
async function run() {
|
||||
let seenWork = false;
|
||||
let jobsStarted = false;
|
||||
|
||||
try {
|
||||
const res = await gql<{
|
||||
updateLibrary: { updateStatus: { jobsInfo: { isRunning: boolean; totalJobs: number } } }
|
||||
updateLibrary: {
|
||||
updateStatus: {
|
||||
jobsInfo: {
|
||||
isRunning: boolean;
|
||||
totalJobs: number;
|
||||
finishedJobs: number;
|
||||
skippedMangasCount: number;
|
||||
skippedCategoriesCount: number;
|
||||
}
|
||||
}
|
||||
}
|
||||
}>(UPDATE_LIBRARY, {});
|
||||
if (cancelled) return;
|
||||
seenWork = res.updateLibrary.updateStatus.jobsInfo.totalJobs > 0;
|
||||
} catch {
|
||||
if (!cancelled) callbacks.onError();
|
||||
|
||||
const { jobsInfo } = res.updateLibrary.updateStatus;
|
||||
jobsStarted = jobsInfo.totalJobs > 0;
|
||||
|
||||
callbacks.onProgress({
|
||||
finished: jobsInfo.finishedJobs,
|
||||
total: jobsInfo.totalJobs,
|
||||
skippedManga: jobsInfo.skippedMangasCount,
|
||||
skippedCategories: jobsInfo.skippedCategoriesCount,
|
||||
});
|
||||
|
||||
if (!jobsStarted) {
|
||||
callbacks.onDone({ entries: [], totalUpdated: 0, newChapters: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
if (jobsStarted && !jobsInfo.isRunning) {
|
||||
callbacks.onDone({ entries: [], totalUpdated: 0, newChapters: 0 });
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[libraryUpdater] failed to start update", e);
|
||||
if (!cancelled) callbacks.onError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
function poll() {
|
||||
gql<{
|
||||
libraryUpdateStatus: {
|
||||
jobsInfo: { isRunning: boolean; finishedJobs: number; totalJobs: number };
|
||||
mangaUpdates: { status: string; manga: { id: number } }[];
|
||||
jobsInfo: {
|
||||
isRunning: boolean;
|
||||
finishedJobs: number;
|
||||
totalJobs: number;
|
||||
skippedMangasCount: number;
|
||||
skippedCategoriesCount: number;
|
||||
};
|
||||
mangaUpdates: {
|
||||
status: string;
|
||||
manga: { id: number; title: string; thumbnailUrl: string; unreadCount: number };
|
||||
}[];
|
||||
}
|
||||
}>(LIBRARY_UPDATE_STATUS, {})
|
||||
.then(async d => {
|
||||
if (cancelled) return;
|
||||
const { jobsInfo } = d.libraryUpdateStatus;
|
||||
const { jobsInfo, mangaUpdates } = d.libraryUpdateStatus;
|
||||
|
||||
if (jobsInfo.totalJobs > 0) seenWork = true;
|
||||
callbacks.onProgress({ finished: jobsInfo.finishedJobs, total: jobsInfo.totalJobs });
|
||||
if (jobsInfo.totalJobs > 0) jobsStarted = true;
|
||||
callbacks.onProgress({
|
||||
finished: jobsInfo.finishedJobs,
|
||||
total: jobsInfo.totalJobs,
|
||||
skippedManga: jobsInfo.skippedMangasCount,
|
||||
skippedCategories: jobsInfo.skippedCategoriesCount,
|
||||
});
|
||||
|
||||
if (!jobsInfo.isRunning && seenWork) {
|
||||
const recent = await gql<{
|
||||
chapters: {
|
||||
nodes: {
|
||||
mangaId: number;
|
||||
fetchedAt: string;
|
||||
manga: { id: number; title: string; thumbnailUrl: string; inLibrary: boolean };
|
||||
}[]
|
||||
}
|
||||
}>(GET_RECENTLY_UPDATED, {}).catch(() => ({ chapters: { nodes: [] } }));
|
||||
|
||||
if (cancelled) return;
|
||||
|
||||
const byManga = new Map<number, LibraryUpdateEntry>();
|
||||
for (const ch of recent.chapters.nodes) {
|
||||
if (!ch.manga.inLibrary) continue;
|
||||
if (Number(ch.fetchedAt) < startedAt) continue;
|
||||
const existing = byManga.get(ch.mangaId);
|
||||
if (existing) {
|
||||
existing.newChapters++;
|
||||
} else {
|
||||
byManga.set(ch.mangaId, {
|
||||
mangaId: ch.mangaId,
|
||||
mangaTitle: ch.manga.title,
|
||||
thumbnailUrl: ch.manga.thumbnailUrl,
|
||||
newChapters: 1,
|
||||
checkedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const entries = [...byManga.values()];
|
||||
if (!jobsInfo.isRunning && jobsStarted) {
|
||||
const entries = buildEntries(mangaUpdates);
|
||||
const newChapters = entries.reduce((s, e) => s + e.newChapters, 0);
|
||||
|
||||
callbacks.onDone({ entries, totalUpdated: entries.length, newChapters });
|
||||
return;
|
||||
}
|
||||
|
||||
timer = setTimeout(poll, POLL_INTERVAL_MS);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) callbacks.onError();
|
||||
.catch((e) => {
|
||||
console.error("[libraryUpdater] poll error", e);
|
||||
if (!cancelled) callbacks.onError(e);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,16 @@
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { open as openUrl } from "@tauri-apps/plugin-shell";
|
||||
import { autoBackupAppData } from "@core/backup";
|
||||
import { gql } from "@api/client";
|
||||
import { GET_ABOUT_SERVER, GET_ABOUT_WEBUI } from "@api/queries/updater";
|
||||
|
||||
interface ReleaseInfo { tag_name: string; name: string; body: string; published_at: string; html_url: string; }
|
||||
type UpdatePhase = "idle" | "downloading" | "launching" | "ready" | "error";
|
||||
const IS_WINDOWS = navigator.userAgent.includes("Windows");
|
||||
|
||||
interface AboutServer { name: string; version: string; buildType: string; buildTime: number; github: string; discord: string; }
|
||||
interface AboutWebUI { channel: string; tag: string; updateTimestamp: number; }
|
||||
|
||||
let appVersion = $state("…");
|
||||
let releases = $state<ReleaseInfo[]>([]);
|
||||
let releasesLoading = $state(false);
|
||||
@@ -21,9 +26,13 @@
|
||||
let targetTag = $state<string | null>(null);
|
||||
let releasesLoaded = false;
|
||||
|
||||
let serverInfo = $state<AboutServer | null>(null);
|
||||
let webuiInfo = $state<AboutWebUI | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
getVersion().then(v => appVersion = v).catch(() => appVersion = "unknown");
|
||||
if (!releasesLoaded) { releasesLoaded = true; loadReleases(); }
|
||||
loadServerInfo();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
@@ -52,6 +61,17 @@
|
||||
} finally { releasesLoading = false; }
|
||||
}
|
||||
|
||||
async function loadServerInfo() {
|
||||
try {
|
||||
const [s, w] = await Promise.all([
|
||||
gql<{ aboutServer: AboutServer }>(GET_ABOUT_SERVER),
|
||||
gql<{ aboutWebUI: AboutWebUI }>(GET_ABOUT_WEBUI),
|
||||
]);
|
||||
serverInfo = s.aboutServer;
|
||||
webuiInfo = w.aboutWebUI;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function stripV(v: string) { return v.replace(/^v/, ""); }
|
||||
function isCurrentVersion(tag: string) { return stripV(tag) === appVersion; }
|
||||
function parseSemver(v: string) { return stripV(v).split(".").map(Number); }
|
||||
@@ -72,6 +92,11 @@
|
||||
return new Date(iso).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" });
|
||||
}
|
||||
|
||||
function fmtBuildTime(unix: number) {
|
||||
if (!unix) return "";
|
||||
return new Date(unix).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" });
|
||||
}
|
||||
|
||||
function fmtBytes(bytes: number) {
|
||||
if (bytes === 0) return "0 B";
|
||||
const units = ["B","KB","MB","GB"];
|
||||
@@ -164,6 +189,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if serverInfo}
|
||||
<div class="s-section">
|
||||
<p class="s-section-title">Server</p>
|
||||
<div class="s-section-body">
|
||||
<div class="s-row">
|
||||
<div class="s-row-info">
|
||||
<span class="s-label">Version</span>
|
||||
<span class="s-desc">
|
||||
{serverInfo.version}
|
||||
{#if serverInfo.buildType}
|
||||
<span class="s-release-badge">{serverInfo.buildType}</span>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{#if serverInfo.buildTime}
|
||||
<div class="s-row">
|
||||
<div class="s-row-info">
|
||||
<span class="s-label">Built</span>
|
||||
<span class="s-desc">{fmtBuildTime(serverInfo.buildTime)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if webuiInfo?.channel}
|
||||
<div class="s-row">
|
||||
<div class="s-row-info">
|
||||
<span class="s-label">Channel</span>
|
||||
<span class="s-desc">{webuiInfo.channel}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="s-section">
|
||||
<p class="s-section-title">Releases</p>
|
||||
<div class="s-section-body">
|
||||
@@ -223,6 +283,12 @@
|
||||
<div class="s-row" style="flex-direction:column;align-items:flex-start;gap:var(--sp-2)">
|
||||
<a href="https://github.com/moku-project/Moku" target="_blank" class="s-label" style="color:var(--accent-fg);text-decoration:none">GitHub →</a>
|
||||
<a href="https://discord.gg/Jq3pwuNqPp" target="_blank" class="s-label" style="color:var(--accent-fg);text-decoration:none">Discord →</a>
|
||||
{#if serverInfo?.github && serverInfo.github !== "https://github.com/moku-project/Moku"}
|
||||
<a href={serverInfo.github} target="_blank" class="s-label" style="color:var(--accent-fg);text-decoration:none">Suwayomi GitHub →</a>
|
||||
{/if}
|
||||
{#if serverInfo?.discord && serverInfo.discord !== "https://discord.gg/Jq3pwuNqPp"}
|
||||
<a href={serverInfo.discord} target="_blank" class="s-label" style="color:var(--accent-fg);text-decoration:none">Suwayomi Discord →</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
let flareTimeout = $state(store.settings.flareSolverrTimeout ?? 60);
|
||||
let flareSession = $state(store.settings.flareSolverrSessionName ?? "moku");
|
||||
let flareTtl = $state(store.settings.flareSolverrSessionTtl ?? 15);
|
||||
let flareFallback = $state(store.settings.flareSolverrFallback ?? false);
|
||||
let flareFallback = $state(store.settings.flareSolverrAsResponseFallback ?? false);
|
||||
|
||||
function showSaved(key: string) {
|
||||
secSaved = key; secError = null;
|
||||
@@ -74,7 +74,7 @@
|
||||
socksProxyVersion: socksVersion, socksProxyUsername: socksUsername,
|
||||
flareSolverrEnabled: flareEnabled, flareSolverrUrl: flareUrl,
|
||||
flareSolverrTimeout: flareTimeout, flareSolverrSessionName: flareSession,
|
||||
flareSolverrSessionTtl: flareTtl, flareSolverrFallback: flareFallback,
|
||||
flareSolverrSessionTtl: flareTtl, flareSolverrAsResponseFallback: flareFallback,
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
@@ -144,7 +144,7 @@
|
||||
updateSettings({
|
||||
flareSolverrEnabled: flareEnabled, flareSolverrUrl: flareUrl,
|
||||
flareSolverrTimeout: flareTimeout, flareSolverrSessionName: flareSession,
|
||||
flareSolverrSessionTtl: flareTtl, flareSolverrFallback: flareFallback,
|
||||
flareSolverrSessionTtl: flareTtl, flareSolverrAsResponseFallback: flareFallback,
|
||||
});
|
||||
showSaved("flare");
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -114,6 +114,7 @@ export function removeRecord(
|
||||
}
|
||||
|
||||
export interface SyncBackOptions {
|
||||
threshold: number | null;
|
||||
respectScanlatorFilter: boolean;
|
||||
chapterPrefs: ChapterDisplayPrefs;
|
||||
}
|
||||
@@ -123,36 +124,30 @@ export async function syncBackFromTracker(
|
||||
chapters: Chapter[],
|
||||
opts: SyncBackOptions,
|
||||
gqlFn: (query: string, vars: Record<string, unknown>) => Promise<unknown>,
|
||||
): Promise<{ markedRead: number[]; markedUnread: number[] }> {
|
||||
const eligible = buildChapterList(
|
||||
opts.respectScanlatorFilter ? buildChapterList(chapters, opts.chapterPrefs) : chapters,
|
||||
{ ...opts.chapterPrefs, sortDir: "asc" },
|
||||
);
|
||||
): Promise<number[]> {
|
||||
const base = opts.respectScanlatorFilter
|
||||
? buildChapterList(chapters, opts.chapterPrefs)
|
||||
: chapters;
|
||||
const eligible = buildChapterList(base, { ...opts.chapterPrefs, sortDir: "asc" });
|
||||
|
||||
const toMarkRead: number[] = [];
|
||||
const toMarkUnread: number[] = [];
|
||||
const toMarkRead: number[] = [];
|
||||
|
||||
for (const record of records) {
|
||||
const remote = record.lastChapterRead;
|
||||
if (!remote || remote <= 0) continue;
|
||||
|
||||
const position = Math.round(remote);
|
||||
const below = eligible.slice(0, position);
|
||||
const above = eligible.slice(position);
|
||||
|
||||
toMarkRead.push(...below.filter(c => !c.isRead).map(c => c.id));
|
||||
toMarkUnread.push(...above.filter(c => c.isRead).map(c => c.id));
|
||||
for (const chapter of eligible) {
|
||||
if (chapter.isRead) continue;
|
||||
const diff = Math.abs(chapter.chapterNumber - remote);
|
||||
if (opts.threshold !== null && diff > opts.threshold) continue;
|
||||
if (chapter.chapterNumber <= remote) toMarkRead.push(chapter.id);
|
||||
}
|
||||
}
|
||||
|
||||
const readIds = [...new Set(toMarkRead)];
|
||||
const unreadIds = [...new Set(toMarkUnread)];
|
||||
|
||||
if (readIds.length > 0) {
|
||||
await gqlFn(MARK_CHAPTERS_READ, { ids: readIds, isRead: true });
|
||||
}
|
||||
if (unreadIds.length > 0) {
|
||||
await gqlFn(MARK_CHAPTERS_READ, { ids: unreadIds, isRead: false });
|
||||
const ids = [...new Set(toMarkRead)];
|
||||
if (ids.length > 0) {
|
||||
await gqlFn(MARK_CHAPTERS_READ, { ids, isRead: true });
|
||||
}
|
||||
|
||||
return { markedRead: readIds, markedUnread: unreadIds };
|
||||
return ids;
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { gql } from "@api/client";
|
||||
import { GET_MANGA_TRACK_RECORDS, GET_ALL_TRACKER_RECORDS } from "@api/queries/tracking";
|
||||
import { GET_CHAPTERS } from "@api/queries/chapters";
|
||||
import { UPDATE_TRACK, FETCH_TRACK, UNBIND_TRACK } from "@api/mutations/tracking";
|
||||
import { MARK_CHAPTERS_READ } from "@api/mutations/chapters";
|
||||
import { buildChapterList, type ChapterDisplayPrefs } from "@features/series/lib/chapterList";
|
||||
import { syncBackFromTracker } from "@features/tracking/lib/trackingSync";
|
||||
import { store } from "@store/state.svelte";
|
||||
@@ -82,7 +81,7 @@ class TrackingState {
|
||||
for (const tracker of res.trackers.nodes.filter(t => t.isLoggedIn)) {
|
||||
for (const record of tracker.trackRecords.nodes) {
|
||||
if (!record.manga?.id) continue;
|
||||
const mangaId = record.manga.id;
|
||||
const mangaId = record.manga.id;
|
||||
const existing = this.byManga.get(mangaId) ?? [];
|
||||
const merged = [...existing.filter(r => r.id !== record.id), record];
|
||||
this.setFor(mangaId, merged);
|
||||
@@ -140,21 +139,22 @@ class TrackingState {
|
||||
const fresh = res.fetchTrack.trackRecord;
|
||||
this.patchFor(mangaId, fresh);
|
||||
|
||||
const { markedRead } = await this._applyRemoteProgress(fresh, chapters, prefs);
|
||||
return { fresh, markedIds: markedRead };
|
||||
const markedIds = await this._applyRemoteProgress(fresh, chapters, prefs);
|
||||
return { fresh, markedIds };
|
||||
}
|
||||
|
||||
private async _applyRemoteProgress(
|
||||
record: TrackRecord,
|
||||
chapters: Chapter[],
|
||||
prefs: ChapterDisplayPrefs,
|
||||
): Promise<{ markedRead: number[]; markedUnread: number[] }> {
|
||||
if (!store.settings.trackerSyncBack) return { markedRead: [], markedUnread: [] };
|
||||
): Promise<number[]> {
|
||||
if (!store.settings.trackerSyncBack) return [];
|
||||
|
||||
return syncBackFromTracker(
|
||||
[record],
|
||||
chapters,
|
||||
{
|
||||
threshold: store.settings.trackerSyncBackThreshold ?? null,
|
||||
respectScanlatorFilter: store.settings.trackerRespectScanlatorFilter ?? true,
|
||||
chapterPrefs: prefs,
|
||||
},
|
||||
@@ -290,6 +290,7 @@ class TrackingState {
|
||||
freshRecords,
|
||||
chapters,
|
||||
{
|
||||
threshold: store.settings.trackerSyncBackThreshold ?? null,
|
||||
respectScanlatorFilter: store.settings.trackerRespectScanlatorFilter ?? true,
|
||||
chapterPrefs: prefs,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user