feat(series): manage-view redesign — big pages, editable Part #, slide-over picker (FC-6.4)

Operator feedback: thumbnails too small to judge order, no obvious way to mark
'this installment is Part 2', and the permanent two-pane picker was busy and
competed with the ordering work.

- Full-width parts, each a card with a big page grid (150px, contain so whole
  pages are visible) and drag-to-reorder; positional page number as a badge.
- Editable Part # (hero field) backed by new series_chapter.stated_part —
  separate from the auto-managed chapter_number, mirroring the page_number vs
  stated_page split so reorder/delete renumbering can't wipe a hand-set part.
  Missing-Part hints when consecutive parts' stated_part jump >1.
- Each part labels its source post (derived from pages' primary_post_id) and
  shows the printed-page range with clear labels.
- Picker demoted to an on-demand right slide-over ('Add pages') with a target-
  part selector; part actions (move/merge/delete) collapsed into an overflow ⋮.

alembic 0042 adds series_chapter.stated_part (nullable int).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 20:29:10 -04:00
parent 7309d1d6d4
commit 978959bdc4
8 changed files with 523 additions and 184 deletions
+19 -6
View File
@@ -16,8 +16,9 @@ export const useSeriesManageStore = defineStore('seriesManage', () => {
const tagId = ref(null)
const series = ref(null)
const chapters = ref([]) // [{id, chapter_number, title, is_placeholder, stated_page_start/end, pages:[...]}]
const gaps = ref([]) // [{after_chapter_id, start, end}]
const chapters = ref([]) // [{id, chapter_number, stated_part, title, is_placeholder, stated_page_start/end, source_post, pages:[...]}]
const gaps = ref([]) // missing-page gaps: [{after_chapter_id, start, end}]
const partGaps = ref([]) // missing-Part gaps: [{after_chapter_id, start, end}]
const pageCount = ref(0)
const targetChapterId = ref(null) // which chapter the picker adds into
const picker = ref([]) // gallery scroll results
@@ -33,6 +34,7 @@ export const useSeriesManageStore = defineStore('seriesManage', () => {
series.value = body.series
chapters.value = body.chapters || []
gaps.value = body.gaps || []
partGaps.value = body.part_gaps || []
pageCount.value = (body.pages || []).length
// Keep a valid add-target: the selected chapter, else the first one.
const ids = chapters.value.map(c => c.id)
@@ -52,6 +54,10 @@ export const useSeriesManageStore = defineStore('seriesManage', () => {
return gaps.value.find(g => g.after_chapter_id === chapterId) || null
}
function partGapAfter(chapterId) {
return partGaps.value.find(g => g.after_chapter_id === chapterId) || null
}
// ---- chapters ----
async function createChapter({ title = null, isPlaceholder = false } = {}) {
await api.post(`/api/series/${tagId.value}/chapters`, {
@@ -67,6 +73,13 @@ export const useSeriesManageStore = defineStore('seriesManage', () => {
await refresh()
}
async function setChapterPart(chapterId, part) {
await api.patch(`/api/series/${tagId.value}/chapters/${chapterId}`, {
body: { stated_part: part }
})
await refresh()
}
async function setChapterStated(chapterId, start, end) {
await api.patch(`/api/series/${tagId.value}/chapters/${chapterId}`, {
body: { stated_page_start: start, stated_page_end: end }
@@ -151,11 +164,11 @@ export const useSeriesManageStore = defineStore('seriesManage', () => {
}
return {
tagId, series, chapters, gaps, pageCount, targetChapterId,
tagId, series, chapters, gaps, partGaps, pageCount, targetChapterId,
picker, pickerCursor, pickerSelection, loading,
load, refresh, gapAfter,
createChapter, renameChapter, setChapterStated, reorderChapters,
moveChapter, deleteChapter, mergeChapter, reorderPages,
load, refresh, gapAfter, partGapAfter,
createChapter, renameChapter, setChapterPart, setChapterStated,
reorderChapters, moveChapter, deleteChapter, mergeChapter, reorderPages,
loadPicker, togglePick, addSelected, remove, setCover
}
})