feat(chapter): preserve source-site order in chapter list (0.52.0)
The user-facing chapter list ordered by (number ASC, created_at ASC),
which broke the source site's order in two ways: non-numeric entries
("notice. : Officials") parsed to number=0 and clustered at the top,
even though the site placed them mid-list, and variants sharing a
number ("Ch.14 : PH" / "Ch.14 : Official") were torn apart by the
created_at tiebreak.
Capture each chapter's position in the source DOM as `source_index`
(0 = first = newest on this site) on every crawler sync, including the
UPDATE branch so a new chapter prepended on the source shifts every
existing row down by one on the next tick. The list query reverses
this with `ORDER BY source_index DESC NULLS LAST, number ASC,
created_at ASC` so the oldest chapter appears first, variants stay
adjacent in the order the site shows them, and non-numeric entries
land where the site placed them. User-uploaded chapters and pre-
migration rows keep their NULL source_index and fall through to the
prior number/created_at tiebreak via NULLS LAST.
The reader's client-side `[...chapters].sort((a,b) => a.number - b.number)`
is dropped; prev/next now walks the server-ordered array positionally
so it traverses variants and non-numeric entries in display order.
Existing data populates on the next cron tick or via admin force-resync.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,22 +33,21 @@
|
||||
);
|
||||
|
||||
// Prev/next chapter computed from the chapter list. listChapters
|
||||
// returns chapters in number ASC order; we still resolve via find
|
||||
// rather than index because the current chapter's position may
|
||||
// not be `chapter.number - 1` (sparse numbering / chapter 0.5 /
|
||||
// future skipped numbers).
|
||||
const sortedChapters = $derived(
|
||||
[...chapters].sort((a, b) => a.number - b.number)
|
||||
);
|
||||
// returns chapters in display order (reversed source-site order, so
|
||||
// oldest first — see backend repo::chapter::list_for_manga), and
|
||||
// prev/next walks that order positionally. Resolving the current
|
||||
// index via `find` rather than `chapter.number - 1` matters because
|
||||
// numbers aren't a reliable index: variants share numbers, non-
|
||||
// numeric entries pin to 0, and uploads can sparse-fill.
|
||||
const currentIdx = $derived(
|
||||
sortedChapters.findIndex((c) => c.id === chapter.id)
|
||||
chapters.findIndex((c) => c.id === chapter.id)
|
||||
);
|
||||
const prevChapter = $derived(
|
||||
currentIdx > 0 ? sortedChapters[currentIdx - 1] : null
|
||||
currentIdx > 0 ? chapters[currentIdx - 1] : null
|
||||
);
|
||||
const nextChapter = $derived(
|
||||
currentIdx >= 0 && currentIdx < sortedChapters.length - 1
|
||||
? sortedChapters[currentIdx + 1]
|
||||
currentIdx >= 0 && currentIdx < chapters.length - 1
|
||||
? chapters[currentIdx + 1]
|
||||
: null
|
||||
);
|
||||
|
||||
@@ -471,7 +470,7 @@
|
||||
}}
|
||||
data-testid="reader-chapter-select"
|
||||
>
|
||||
{#each sortedChapters as c (c.id)}
|
||||
{#each chapters as c (c.id)}
|
||||
<option value={c.id}>
|
||||
{chapterLabel(c)}
|
||||
</option>
|
||||
|
||||
Reference in New Issue
Block a user