Feat: Dual-Sync Tracking (#52)

This commit is contained in:
Youwes09
2026-04-26 13:36:30 -05:00
parent c0efbba4df
commit 50c5131477
6 changed files with 451 additions and 175 deletions
+22 -33
View File
@@ -1,6 +1,5 @@
import type { Tracker, TrackRecord } from "@types/index";
import type { Chapter } from "@types/index";
import { FETCH_TRACK } from "@api/mutations/tracking";
import { MARK_CHAPTERS_READ } from "@api/mutations/chapters";
import { buildChapterList, type ChapterDisplayPrefs } from "@features/series/lib/chapterList";
@@ -115,7 +114,6 @@ export function removeRecord(
}
export interface SyncBackOptions {
threshold: number | null;
respectScanlatorFilter: boolean;
chapterPrefs: ChapterDisplayPrefs;
}
@@ -125,45 +123,36 @@ export async function syncBackFromTracker(
chapters: Chapter[],
opts: SyncBackOptions,
gqlFn: (query: string, vars: Record<string, unknown>) => Promise<unknown>,
): Promise<number[]> {
const eligible = opts.respectScanlatorFilter
? buildChapterList(chapters, opts.chapterPrefs)
: chapters;
): Promise<{ markedRead: number[]; markedUnread: number[] }> {
const eligible = buildChapterList(
opts.respectScanlatorFilter ? buildChapterList(chapters, opts.chapterPrefs) : chapters,
{ ...opts.chapterPrefs, sortDir: "asc" },
);
const toMark: number[] = [];
const toMarkRead: number[] = [];
const toMarkUnread: number[] = [];
for (const record of records) {
let fresh: TrackRecord;
try {
const res = await gqlFn(FETCH_TRACK, { recordId: record.id }) as { fetchTrack: { trackRecord: TrackRecord } };
fresh = res.fetchTrack.trackRecord;
} catch {
continue;
}
const remote = fresh.lastChapterRead;
const remote = record.lastChapterRead;
if (!remote || remote <= 0) continue;
let ceiling: number;
const position = Math.round(remote);
const below = eligible.slice(0, position);
const above = eligible.slice(position);
if (opts.threshold === null) {
ceiling = remote;
} else {
const match = eligible
.filter(c => Math.abs(c.chapterNumber - remote) <= opts.threshold!)
.sort((a, b) => Math.abs(a.chapterNumber - remote) - Math.abs(b.chapterNumber - remote))[0];
if (!match) continue;
ceiling = match.chapterNumber;
}
const unread = eligible.filter(c => !c.isRead && c.chapterNumber <= ceiling);
toMark.push(...unread.map(c => c.id));
toMarkRead.push(...below.filter(c => !c.isRead).map(c => c.id));
toMarkUnread.push(...above.filter(c => c.isRead).map(c => c.id));
}
const ids = [...new Set(toMark)];
if (ids.length > 0) {
await gqlFn(MARK_CHAPTERS_READ, { ids, isRead: true });
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 });
}
return ids;
return { markedRead: readIds, markedUnread: unreadIds };
}