feat(series): add dedicated series detail page instead of bookshelf filter

Create a new /series/detail?name=X&library_id=Y SSR page that shows
all books in a specific series, replacing the broken approach of
linking to /bookshelf?series_filter=X (the bookshelf SSR handler
ignores all filter query params).

The series detail page features:
- Back link to /series browse page
- Library selector dropdown (full page navigation on change)
- Series name header with book count badge
- Book grid using the shared BookCard template
- Empty state for series with no books

Update all links to point to the new page:
- Series cards on /series browse page
- Series badge on book detail page
- JS-rendered cards in series.ts switchLibrary

Add seriesDetailPage Alpine component for the detail page's
library switcher (simple navigation, no AJAX needed).
This commit is contained in:
2026-05-08 20:50:59 -04:00
parent a57694b738
commit ddc14e3314
8 changed files with 375 additions and 7 deletions
+20 -1
View File
@@ -11,7 +11,7 @@ interface SeriesItem {
}
function renderSeriesCard(series: SeriesItem, libraryId: string): string {
const href = `/bookshelf?series_filter=${encodeURIComponent(series.name)}&sort=series&library_id=${libraryId}`;
const href = `/series/detail?name=${encodeURIComponent(series.name)}&library_id=${libraryId}`;
const coverCount = series.cover_paths.length;
const coverClass = `cover-count-${coverCount}`;
@@ -152,3 +152,22 @@ Alpine.data("seriesPage", () => ({
}
},
}));
Alpine.data("seriesDetailPage", () => ({
initSeriesDetailPage() {
const librarySelect = document.getElementById(
"library-select",
) as HTMLSelectElement;
if (librarySelect) {
librarySelect.addEventListener("change", () => {
if (librarySelect.value) {
const url = new URL(window.location.href);
const currentLib = url.searchParams.get("library_id");
if (currentLib === librarySelect.value) return;
url.searchParams.set("library_id", librarySelect.value);
window.location.href = url.toString();
}
});
}
},
}));