Files
Mangalord/frontend/src/routes/authors/[id]/+page.svelte
MechaCat02 5c22dfdb41 feat: paginate list views, fix stale page titles, tidy admin filter bar
Bundle of small UI/UX fixes plus a build hygiene tweak.

* List pagination — Home (`/`) and `/authors/[id]` silently capped at
  the backend default of 50 with no UI to advance. New reusable
  `Pager.svelte` (Prev/Next + numbered with ellipsis), URL-synced
  `?page=N`, and filter/search/sort reset to page 1 so users aren't
  stranded on an out-of-range page. Count label now shows a range
  ("Showing 51–100 of 237").

* Stale page title — Pages without a `<svelte:head><title>` left the
  document title at whatever the last manga / author / collection page
  set it to. Move static-route titles into a route-id → title map in
  the root layout and invert every dynamic title to brand-first
  (`Mangalord | {X}`) for consistency.

* Admin filter bar — `/admin/mangas` search input had `flex: 1` and
  ballooned across the row, shoving the sync-state select + Search
  button to the far right. Cap at 24rem, vertical-align the row, and
  promote the previously aria-only "Sync state" label to visible text.

* Build hygiene — `backend/target` had grown to 68 GiB. Cleaned and
  added `[profile.dev] debug = "line-tables-only"` (and `[profile.test]`
  too) to cut future dev builds by ~50–70% while keeping line numbers
  in backtraces.

Also: configure vitest to resolve Svelte's browser entry so
`@testing-library/svelte` can mount components in jsdom — needed for
the new `Pager.svelte.test.ts`.

Bump 0.48.0 -> 0.49.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 21:18:53 +02:00

121 lines
3.1 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import MangaCard from '$lib/components/MangaCard.svelte';
import Pager from '$lib/components/Pager.svelte';
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
let { data } = $props();
const author = $derived(data.author);
const mangas = $derived(data.mangas);
const total = $derived(data.total);
const currentPage = $derived(data.currentPage);
const pageSize = $derived(data.pageSize);
const totalPages = $derived(
total != null && total > 0 ? Math.ceil(total / pageSize) : 1
);
const rangeStart = $derived(mangas.length === 0 ? 0 : (currentPage - 1) * pageSize + 1);
const rangeEnd = $derived((currentPage - 1) * pageSize + mangas.length);
function goToPage(p: number) {
if (p === currentPage) return;
const url = new URL($page.url);
if (p === 1) url.searchParams.delete('page');
else url.searchParams.set('page', String(p));
goto(url.pathname + url.search, { noScroll: false });
}
</script>
<svelte:head>
<title>Mangalord | {author.name}</title>
</svelte:head>
<nav class="back">
<a href="/" class="back-link">
<ArrowLeft size={16} aria-hidden="true" />
<span>Back to search</span>
</a>
</nav>
<header class="overview">
<h1 data-testid="author-name">{author.name}</h1>
<p class="count" data-testid="author-manga-count">
{author.manga_count}
{author.manga_count === 1 ? 'work' : 'works'}
</p>
</header>
{#if mangas.length === 0}
<p class="status" data-testid="author-no-mangas">
No mangas attributed to this author.
</p>
{:else}
{#if total != null}
<p class="meta" data-testid="author-shown-of-total">
Showing {rangeStart}{rangeEnd} of {total}
</p>
{/if}
<ul class="manga-grid" data-testid="author-manga-list">
{#each mangas as m (m.id)}
<MangaCard manga={m} testid={`author-manga-${m.id}`} />
{/each}
</ul>
<Pager
page={currentPage}
{totalPages}
onChange={goToPage}
testid="author-pager"
/>
{/if}
<style>
.back {
margin-bottom: var(--space-3);
}
.back-link {
display: inline-flex;
align-items: center;
gap: var(--space-1);
color: var(--text-muted);
font-size: var(--font-sm);
}
.back-link:hover {
color: var(--primary);
text-decoration: none;
}
.overview {
margin-bottom: var(--space-5);
}
.overview h1 {
margin: 0 0 var(--space-1);
}
.count {
color: var(--text-muted);
margin: 0 0 var(--space-2);
}
.meta {
color: var(--text-muted);
font-size: var(--font-sm);
margin: 0 0 var(--space-3);
}
.status {
color: var(--text-muted);
}
.manga-grid {
list-style: none;
padding: 0;
margin: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: var(--space-4);
}
</style>