fix(reader): drop "Chapter N:" prefix from chapter title display (0.51.2)
The chapter list on the manga detail page, the reader's chapter-select
dropdown, the continuous-mode chapter bar, the browser tab title, and
the profile upload-history entries all prepended "Chapter {number}:"
in front of the crawled site title. Source titles already include
"Ch.N" themselves and the manga page renders chapters inside an <ol>,
so the prefix duplicated information the user could already see.
A small chapterLabel(c) helper in $lib/api/chapters returns the site
title as-is, falling back to "Chapter {number}" only when the
crawler captured an empty title (link/option stays non-empty). The
five render sites now call it. The previous-/next-chapter nav
buttons still read "Previous chapter (Ch. N)" / "Next chapter (Ch. N)"
since those are wayfinding labels, not title display.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@@ -1470,7 +1470,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.51.1"
|
version = "0.51.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.51.1"
|
version = "0.51.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "mangalord"
|
default-run = "mangalord"
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ test('manga overview shows title, cover, and a chapter list', async ({ page }) =
|
|||||||
await expect(page.getByTestId('manga-title')).toHaveText('Berserk');
|
await expect(page.getByTestId('manga-title')).toHaveText('Berserk');
|
||||||
await expect(page.getByTestId('manga-author')).toContainText('Kentaro Miura');
|
await expect(page.getByTestId('manga-author')).toContainText('Kentaro Miura');
|
||||||
await expect(page.getByTestId('manga-cover')).toBeVisible();
|
await expect(page.getByTestId('manga-cover')).toBeVisible();
|
||||||
await expect(page.getByTestId('chapter-list')).toContainText('Chapter 1');
|
await expect(page.getByTestId('chapter-list')).toContainText('The Brand');
|
||||||
await expect(page.getByTestId('bookmark-signin')).toBeVisible();
|
await expect(page.getByTestId('bookmark-signin')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mangalord-frontend",
|
"name": "mangalord-frontend",
|
||||||
"version": "0.51.1",
|
"version": "0.51.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import {
|
|||||||
listChapters,
|
listChapters,
|
||||||
getChapter,
|
getChapter,
|
||||||
getChapterPages,
|
getChapterPages,
|
||||||
createChapter
|
createChapter,
|
||||||
|
chapterLabel
|
||||||
} from './chapters';
|
} from './chapters';
|
||||||
|
|
||||||
function ok(body: unknown): Response {
|
function ok(body: unknown): Response {
|
||||||
@@ -129,6 +130,18 @@ describe('chapters api client', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('chapterLabel', () => {
|
||||||
|
it('returns the site title verbatim when present', () => {
|
||||||
|
expect(chapterLabel({ number: 7, title: 'Ch.7 : Official' })).toBe(
|
||||||
|
'Ch.7 : Official'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to "Chapter {number}" when title is null', () => {
|
||||||
|
expect(chapterLabel({ number: 3, title: null })).toBe('Chapter 3');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('getChapterPages unwraps the {pages} envelope into the array', async () => {
|
it('getChapterPages unwraps the {pages} envelope into the array', async () => {
|
||||||
fetchSpy.mockResolvedValueOnce(
|
fetchSpy.mockResolvedValueOnce(
|
||||||
ok({
|
ok({
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ export type ChaptersPage = {
|
|||||||
page: Page;
|
page: Page;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function chapterLabel(c: Pick<Chapter, 'number' | 'title'>): string {
|
||||||
|
return c.title ?? `Chapter ${c.number}`;
|
||||||
|
}
|
||||||
|
|
||||||
export type ListOptions = {
|
export type ListOptions = {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
type TagRef
|
type TagRef
|
||||||
} from '$lib/api/mangas';
|
} from '$lib/api/mangas';
|
||||||
import { resyncManga } from '$lib/api/admin';
|
import { resyncManga } from '$lib/api/admin';
|
||||||
|
import { chapterLabel } from '$lib/api/chapters';
|
||||||
import { listTags, type Tag } from '$lib/api/tags';
|
import { listTags, type Tag } from '$lib/api/tags';
|
||||||
import { session } from '$lib/session.svelte';
|
import { session } from '$lib/session.svelte';
|
||||||
import Chip from '$lib/components/Chip.svelte';
|
import Chip from '$lib/components/Chip.svelte';
|
||||||
@@ -45,6 +46,11 @@
|
|||||||
continueChapter?.number ?? readProgress?.chapter_number ?? null
|
continueChapter?.number ?? readProgress?.chapter_number ?? null
|
||||||
);
|
);
|
||||||
const continueChapterTitle = $derived(continueChapter?.title ?? null);
|
const continueChapterTitle = $derived(continueChapter?.title ?? null);
|
||||||
|
const continueLabel = $derived(
|
||||||
|
continueChapterNumber != null
|
||||||
|
? chapterLabel({ number: continueChapterNumber, title: continueChapterTitle })
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
const authors = $derived<AuthorRef[]>(manga.authors);
|
const authors = $derived<AuthorRef[]>(manga.authors);
|
||||||
const genres = $derived<GenreRef[]>(manga.genres);
|
const genres = $derived<GenreRef[]>(manga.genres);
|
||||||
@@ -431,7 +437,7 @@
|
|||||||
>
|
>
|
||||||
<span class="continue-label">Continue reading</span>
|
<span class="continue-label">Continue reading</span>
|
||||||
<span class="continue-target">
|
<span class="continue-target">
|
||||||
Chapter {continueChapterNumber}{#if continueChapterTitle}: {continueChapterTitle}{/if}
|
{continueLabel}
|
||||||
{#if readProgress && readProgress.page > 1}
|
{#if readProgress && readProgress.page > 1}
|
||||||
— page {readProgress.page}
|
— page {readProgress.page}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -445,7 +451,7 @@
|
|||||||
{#each chapters as c (c.id)}
|
{#each chapters as c (c.id)}
|
||||||
<li>
|
<li>
|
||||||
<a href="/manga/{manga.id}/chapter/{c.id}">
|
<a href="/manga/{manga.id}/chapter/{c.id}">
|
||||||
Chapter {c.number}{#if c.title}: {c.title}{/if}
|
{chapterLabel(c)}
|
||||||
</a>
|
</a>
|
||||||
<span class="pages">({c.page_count} pages)</span>
|
<span class="pages">({c.page_count} pages)</span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { GAP_PX, type ReaderPageGap } from '$lib/api/preferences';
|
import { GAP_PX, type ReaderPageGap } from '$lib/api/preferences';
|
||||||
import { preferences } from '$lib/preferences.svelte';
|
import { preferences } from '$lib/preferences.svelte';
|
||||||
import { updateReadProgress } from '$lib/api/read_progress';
|
import { updateReadProgress } from '$lib/api/read_progress';
|
||||||
|
import { chapterLabel } from '$lib/api/chapters';
|
||||||
import { resyncChapter } from '$lib/api/admin';
|
import { resyncChapter } from '$lib/api/admin';
|
||||||
import { readerFullscreen } from '$lib/reader-fullscreen.svelte';
|
import { readerFullscreen } from '$lib/reader-fullscreen.svelte';
|
||||||
import { session } from '$lib/session.svelte';
|
import { session } from '$lib/session.svelte';
|
||||||
@@ -28,9 +29,7 @@
|
|||||||
const gapPx = $derived(GAP_PX[preferences.readerPageGap]);
|
const gapPx = $derived(GAP_PX[preferences.readerPageGap]);
|
||||||
|
|
||||||
const pageTitle = $derived(
|
const pageTitle = $derived(
|
||||||
chapter.title
|
`Mangalord | ${manga.title} · ${chapterLabel(chapter)}`
|
||||||
? `Mangalord | ${manga.title} · Ch. ${chapter.number}: ${chapter.title}`
|
|
||||||
: `Mangalord | ${manga.title} · Ch. ${chapter.number}`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prev/next chapter computed from the chapter list. listChapters
|
// Prev/next chapter computed from the chapter list. listChapters
|
||||||
@@ -474,7 +473,7 @@
|
|||||||
>
|
>
|
||||||
{#each sortedChapters as c (c.id)}
|
{#each sortedChapters as c (c.id)}
|
||||||
<option value={c.id}>
|
<option value={c.id}>
|
||||||
Ch. {c.number}{c.title ? ` — ${c.title}` : ''}
|
{chapterLabel(c)}
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
@@ -685,7 +684,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<span class="chapter-bar-current" aria-hidden="true">
|
<span class="chapter-bar-current" aria-hidden="true">
|
||||||
Ch. {chapter.number}{#if chapter.title} — {chapter.title}{/if}
|
{chapterLabel(chapter)}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fileUrl } from '$lib/api/client';
|
import { fileUrl } from '$lib/api/client';
|
||||||
|
import { chapterLabel } from '$lib/api/chapters';
|
||||||
import { clearReadProgress, type ReadProgressSummary } from '$lib/api/read_progress';
|
import { clearReadProgress, type ReadProgressSummary } from '$lib/api/read_progress';
|
||||||
import BookImage from '@lucide/svelte/icons/book-image';
|
import BookImage from '@lucide/svelte/icons/book-image';
|
||||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||||
@@ -186,7 +187,7 @@
|
|||||||
<a href="/manga/{u.manga_id}" class="title">{u.manga_title}</a>
|
<a href="/manga/{u.manga_id}" class="title">{u.manga_title}</a>
|
||||||
<span class="target">
|
<span class="target">
|
||||||
<a href="/manga/{u.manga_id}/chapter/{u.chapter.id}">
|
<a href="/manga/{u.manga_id}/chapter/{u.chapter.id}">
|
||||||
Chapter {u.chapter.number}{#if u.chapter.title}: {u.chapter.title}{/if}
|
{chapterLabel(u.chapter)}
|
||||||
</a>
|
</a>
|
||||||
<span class="muted">({u.chapter.page_count} pages)</span>
|
<span class="muted">({u.chapter.page_count} pages)</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user