diff --git a/web/src/rendering-allowlist.txt b/web/src/rendering-allowlist.txt index 1c2d6406..6ddda901 100644 --- a/web/src/rendering-allowlist.txt +++ b/web/src/rendering-allowlist.txt @@ -20,7 +20,6 @@ web/src/dom.ts:1 web/src/password_validation.ts:1 web/src/queue.ts:3 web/src/search.ts:4 -web/src/series.ts:1 web/src/setup.ts:1 web/src/stats.ts:9 web/src/toast.ts:2 diff --git a/web/src/series.ts b/web/src/series.ts index 3b26c50d..1ebbf206 100644 --- a/web/src/series.ts +++ b/web/src/series.ts @@ -1,128 +1,22 @@ import { Alpine } from "./alpine"; -import { showToast } from "./toast"; -import { initLibrarySwitcher, switchWithTransition } from "./library-switcher"; +import { initLibrarySwitcher } from "./library-switcher"; -interface SeriesItem { - name: string; - library_id: string; - book_count: number; - total_in_series: number; - cover_paths: string[]; - last_entry_at: string; -} - -function renderSeriesCard(series: SeriesItem): string { - const href = `/series/detail?name=${encodeURIComponent(series.name)}&library_id=${encodeURIComponent(series.library_id ?? "")}`; - const coverCount = series.cover_paths.length; - const coverClass = `cover-count-${coverCount}`; - - let coversHtml = ""; - for (let i = 0; i < coverCount; i++) { - coversHtml += `${series.name}`; - } - - if (coverCount === 0) { - coversHtml = `
📚
`; - } - - return ` - -
-
- ${coversHtml} -
-
-

${series.name}

-

- ${series.book_count} of ${series.total_in_series} books -

-
-
-
`; -} - -function renderSeriesContent( - seriesList: SeriesItem[], - libraryId: string, - totalPages: number, - currentPage: number, -): string { - if (seriesList.length === 0) { - return ` -
-

Series

-
-
📚
-

No Series Found

-

Books with series metadata will appear here

-
-
`; - } - - const cardsHtml = seriesList.map((s) => renderSeriesCard(s)).join(""); - - let paginationHtml = ""; - if (totalPages > 1) { - const libParam = libraryId ? `library_id=${libraryId}&` : ""; - const prevLink = - currentPage > 1 - ? `← Previous` - : ""; - const nextLink = - currentPage < totalPages - ? `Next →` - : ""; - paginationHtml = ` -
- ${prevLink} - Page ${currentPage} of ${totalPages} - ${nextLink} -
`; - } - - return ` -
-

Series

-
- ${cardsHtml} -
- ${paginationHtml} -
`; -} - -async function switchLibrary(libraryId: string): Promise { - await switchWithTransition("series-container", async () => { - const param = libraryId ? `library_id=${libraryId}&` : ""; - const response = await fetch( - `/api/series?${param}limit=24&offset=0`, - { - headers: { - Authorization: `Bearer ${localStorage.getItem("token")}`, - "Content-Type": "application/json", - }, - }, - ); - - if (!response.ok) { - throw new Error("Failed to load series"); - } - - const data = await response.json(); - const seriesList: SeriesItem[] = data.series || []; - const total: number = data.total || 0; - const totalPages = Math.max(1, Math.ceil(total / 24)); - - const container = document.getElementById("series-container"); - if (container) { - container.innerHTML = renderSeriesContent(seriesList, libraryId, totalPages, 1); - } - }); +// Rendering ownership (AGENTS.md): the series grid and its paginator are +// rendered by templates/series.templ. This page intentionally has NO +// TypeScript renderer — the old one here duplicated the templ markup via +// innerHTML and re-fetched at hardcoded page 1, stomping SSR pagination +// whenever the header's library restore fired. A library switch is a plain +// SSR navigation so the URL stays the single source of truth for +// library_id and page state. +function navigateToSeries(libraryId: string): void { + const param = libraryId ? `library_id=${encodeURIComponent(libraryId)}&` : ""; + window.location.href = `/series?${param}page=1`; } Alpine.data("seriesPage", () => ({ initSeriesPage() { initLibrarySwitcher({ - onSwitch: switchLibrary, + onSwitch: navigateToSeries, }); }, }));