From 9e262cc5f0a9e301a72c0e9957550a174c0fa237 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 7 Jun 2026 18:38:37 -0400 Subject: [PATCH] feat(series): Add-to-series control + Series browse view + nav (FC-6.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes FC-6.2 with the UI. - PostSeriesMenu: a "Series ▾" control on each post card — "New series from this post" (promote → navigates to manage) and "Add to existing series…" (dialog with a browsable picker loaded from GET /api/series, client-side filtered — avoids the empty-autocomplete #712 trap). - SeriesView (/series): a top-level Series browse grid — cover, name, artist, chapter/page counts, gap badge; sort recent|name|size; cards → manage/read. meta.title adds it to the nav automatically (peer of Posts). - seriesBrowse store for the list. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/components/posts/PostCard.vue | 2 + .../src/components/posts/PostSeriesMenu.vue | 123 +++++++++++++++ frontend/src/router.js | 5 +- frontend/src/stores/seriesBrowse.js | 31 ++++ frontend/src/views/SeriesView.vue | 145 ++++++++++++++++++ 5 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/posts/PostSeriesMenu.vue create mode 100644 frontend/src/stores/seriesBrowse.js create mode 100644 frontend/src/views/SeriesView.vue diff --git a/frontend/src/components/posts/PostCard.vue b/frontend/src/components/posts/PostCard.vue index cd1c2aa..c38c6da 100644 --- a/frontend/src/components/posts/PostCard.vue +++ b/frontend/src/components/posts/PostCard.vue @@ -15,6 +15,7 @@ · {{ totalImages }} image{{ totalImages === 1 ? '' : 's' }} + + + Series + + + + + New series from this post + + + + Add to existing series… + + + + + + + Add to series + +

+ Appends this post as the next chapter of the chosen series. +

+ +
+ + + Cancel + Add + +
+
+
+ + + diff --git a/frontend/src/router.js b/frontend/src/router.js index 9a6a05d..2073c84 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -4,6 +4,7 @@ import GalleryView from './views/GalleryView.vue' import ShowcaseView from './views/ShowcaseView.vue' import TagsView from './views/TagsView.vue' import ArtistView from './views/ArtistView.vue' +import SeriesView from './views/SeriesView.vue' import SeriesManageView from './views/SeriesManageView.vue' import SeriesReaderView from './views/SeriesReaderView.vue' import SubscriptionsView from './views/SubscriptionsView.vue' @@ -25,7 +26,9 @@ const routes = [ { path: '/tags', name: 'tags', component: TagsView, meta: { title: 'Tags' } }, // Artist detail — no meta.title (reached by clicking an artist, not nav). { path: '/artist/:slug', name: 'artist', component: ArtistView }, - // Series management — no meta.title (reached from a series tag card). + // Series browse — a nav entry (meta.title). Peer of Posts. + { path: '/series', name: 'series', component: SeriesView, meta: { title: 'Series' } }, + // Series management — no meta.title (reached from a series card/tag). { path: '/series/:tagId', name: 'series-manage', component: SeriesManageView }, // Series reader — immersive (no top nav, no meta.title). { path: '/series/:tagId/read', name: 'series-read', component: SeriesReaderView, meta: { immersive: true } }, diff --git a/frontend/src/stores/seriesBrowse.js b/frontend/src/stores/seriesBrowse.js new file mode 100644 index 0000000..adb796c --- /dev/null +++ b/frontend/src/stores/seriesBrowse.js @@ -0,0 +1,31 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { useApi } from '../composables/useApi.js' +import { useAsyncAction } from '../composables/useAsyncAction.js' + +// Backs the Series browse view (FC-6.2). list_series returns every series as a +// card; homelab scale is small enough to load the whole list and sort/filter +// server-side. +export const useSeriesBrowseStore = defineStore('seriesBrowse', () => { + const api = useApi() + const series = ref([]) + const sort = ref('recent') + const artistId = ref(null) + const { loading, error, run } = useAsyncAction({ errorAs: 'message' }) + + async function load() { + await run(async () => { + const params = { sort: sort.value } + if (artistId.value != null) params.artist_id = artistId.value + const body = await api.get('/api/series', { params }) + series.value = body.series || [] + }) + } + + function setSort(s) { + sort.value = s + return load() + } + + return { series, sort, artistId, loading, error, load, setSort } +}) diff --git a/frontend/src/views/SeriesView.vue b/frontend/src/views/SeriesView.vue new file mode 100644 index 0000000..c135848 --- /dev/null +++ b/frontend/src/views/SeriesView.vue @@ -0,0 +1,145 @@ + + + + +