feat(detail): mark read chapters and count new ones on the manga page

On the manga detail page, dim chapters at or before the reader's last-read
chapter (with an accent check + screen-reader "Read." label) and show a
"N new since last read" badge counting chapters that have landed since.
All client-side from the already-loaded chapter list and per-manga read
progress — no extra requests. The count reflects the loaded (paginated)
chapter list. Logic lives in a pure, unit-tested chapterProgress helper;
e2e covers the mid-series and never-opened cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-04 21:01:46 +02:00
parent 2267a83f6c
commit 01d18e7ba2
8 changed files with 211 additions and 7 deletions

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { isChapterRead, countNewChapters } from './chapterProgress';
describe('isChapterRead', () => {
it('marks chapters at or before the last-read number as read', () => {
expect(isChapterRead(1, 3)).toBe(true);
expect(isChapterRead(3, 3)).toBe(true);
});
it('leaves later chapters unread', () => {
expect(isChapterRead(4, 3)).toBe(false);
});
it('marks nothing when the last-read position is unknown', () => {
expect(isChapterRead(1, null)).toBe(false);
});
});
describe('countNewChapters', () => {
const chapters = [{ number: 1 }, { number: 2 }, { number: 3 }];
it('counts chapters past the last-read number', () => {
expect(countNewChapters(chapters, 1)).toBe(2);
});
it('is zero when caught up to the latest', () => {
expect(countNewChapters(chapters, 3)).toBe(0);
});
it('is zero when the last-read position is unknown', () => {
expect(countNewChapters(chapters, null)).toBe(0);
});
});

View File

@@ -0,0 +1,28 @@
// Pure helpers for reflecting a reader's personal progress on a manga's
// chapter list. Kept UI-free so the read/unread and "new since last read"
// logic is unit-testable without rendering the detail page.
export type NumberedChapter = { number: number };
/**
* A chapter counts as read once its number is at or before the reader's
* last-read chapter number. An unknown last-read position (guest, never
* opened, or a deleted chapter) marks nothing — we never guess.
*/
export function isChapterRead(chapterNumber: number, lastReadNumber: number | null): boolean {
return lastReadNumber != null && chapterNumber <= lastReadNumber;
}
/**
* How many of the given chapters sit past the reader's last-read chapter
* (by number) — the personal "new since you last read" count. `0` when the
* last-read position is unknown. Counts over the provided (loaded) list, so
* for a paginated chapter list it reflects what's currently shown.
*/
export function countNewChapters(
chapters: NumberedChapter[],
lastReadNumber: number | null
): number {
if (lastReadNumber == null) return 0;
return chapters.reduce((n, c) => (c.number > lastReadNumber ? n + 1 : n), 0);
}