feat(series): chapter-aware manage view + reader — frontend (FC-6.1)
Completes FC-6.1: the series management UI now works in chapters. - SeriesManageView: chapters as cards (inline-rename, stated-page range inputs, move up/down, merge-into-previous, delete, pick-as-add-target), pages drag-reorder WITHIN a chapter, a "gap: N-M missing" badge between chapters with a stated-page hole, and Add chapter / Add placeholder. The picker adds the selection into the targeted chapter. - seriesManage store: chapter CRUD + reorderChapters/moveChapter/mergeChapter/ reorderPages actions; consumes chapters[]/gaps[]; addSelected targets a chapter. - Reader: page_number is now within-chapter, so anchors switched to a global `seq` (reading-order position) — fixes scroll/jump/active collisions across chapters — plus chapter-title dividers at each chapter boundary. - Updated seriesManage.spec to the chaptered store shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,9 +20,9 @@
|
||||
:value="currentPage" @change="jumpTo($event.target.value)"
|
||||
>
|
||||
<option
|
||||
v-for="p in store.pages" :key="p.page_number"
|
||||
:value="p.page_number"
|
||||
>Page {{ p.page_number }}</option>
|
||||
v-for="p in store.pages" :key="p.seq"
|
||||
:value="p.seq"
|
||||
>Page {{ p.seq }}</option>
|
||||
</select>
|
||||
<v-btn
|
||||
icon="mdi-menu" variant="text" size="small"
|
||||
@@ -43,23 +43,27 @@
|
||||
<div
|
||||
v-for="p in store.pages" :key="p.image_id"
|
||||
class="fc-reader__thumb"
|
||||
:class="{ active: p.page_number === currentPage }"
|
||||
@click="jumpTo(p.page_number, true)"
|
||||
:class="{ active: p.seq === currentPage }"
|
||||
@click="jumpTo(p.seq, true)"
|
||||
>
|
||||
<img :src="p.thumbnail_url" alt="" loading="lazy" />
|
||||
<span class="fc-reader__thumbnum">{{ p.page_number }}</span>
|
||||
<span class="fc-reader__thumbnum">{{ p.seq }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div ref="scrollEl" class="fc-reader__content" @scroll="onScroll">
|
||||
<div
|
||||
v-for="p in store.pages" :key="p.image_id"
|
||||
class="fc-reader__page" :id="'fc-page-' + p.page_number"
|
||||
:data-page="p.page_number"
|
||||
>
|
||||
<img :src="p.image_url" :alt="'Page ' + p.page_number" loading="lazy" />
|
||||
</div>
|
||||
<template v-for="p in store.pages" :key="p.image_id">
|
||||
<div v-if="p.isChapterStart" class="fc-reader__chdiv">
|
||||
{{ p.chapterLabel }}
|
||||
</div>
|
||||
<div
|
||||
class="fc-reader__page" :id="'fc-page-' + p.seq"
|
||||
:data-page="p.seq"
|
||||
>
|
||||
<img :src="p.image_url" :alt="'Page ' + p.seq" loading="lazy" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="store.error" class="fc-reader__empty">
|
||||
{{ store.error }}
|
||||
<v-btn variant="text" color="accent" @click="goBack">Back</v-btn>
|
||||
@@ -115,9 +119,9 @@ function pageMetrics() {
|
||||
const root = scrollEl.value
|
||||
if (!root) return []
|
||||
return store.pages.map(p => {
|
||||
const el = root.querySelector('#fc-page-' + p.page_number)
|
||||
const el = root.querySelector('#fc-page-' + p.seq)
|
||||
return {
|
||||
page_number: p.page_number,
|
||||
page_number: p.seq,
|
||||
top: el ? el.offsetTop : 0,
|
||||
height: el ? el.offsetHeight : 0
|
||||
}
|
||||
@@ -280,6 +284,13 @@ onUnmounted(() => {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
gap: 2px; padding: 0.25rem;
|
||||
}
|
||||
.fc-reader__chdiv {
|
||||
width: 100%; max-width: 1200px; margin: 0.5rem auto 0;
|
||||
padding: 0.4rem 0.75rem;
|
||||
font-family: 'Fraunces', Georgia, serif; font-size: 0.95rem;
|
||||
color: rgb(var(--v-theme-on-surface-variant));
|
||||
border-top: 1px solid rgb(var(--v-theme-surface-light));
|
||||
}
|
||||
.fc-reader__page { width: 100%; display: flex; justify-content: center; }
|
||||
.fc-reader__page img {
|
||||
max-width: min(100%, 1200px); height: auto; display: block;
|
||||
|
||||
Reference in New Issue
Block a user