Admin : déroulant sommaire PDF par chapitre, transitions pages, URL GrateWizard
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Ajout API /api/admin/pdf-outline (extraction sommaire PDF côté serveur via pdfjs-dist) - Déroulant <select> dans chaque ligne de chapitre admin avec les 61 titres/sous-titres du PDF - Sauvegarde des associations chapitre→page PDF via config YAML - Transition douce (fondu 1s/1.2s) pour le changement de pages dans le viewer PDF - Correction des numéros de pages réels dans chapterPages (extraits du sommaire PDF) - URL GrateWizard prod → gratewizard.axiom-team.fr Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ export default defineAppConfig({
|
||||
],
|
||||
},
|
||||
gratewizard: {
|
||||
url: import.meta.dev ? 'http://localhost:3001' : 'https://gratewizard.ml',
|
||||
url: import.meta.dev ? 'http://localhost:3001' : 'https://gratewizard.axiom-team.fr',
|
||||
popup: {
|
||||
width: 420,
|
||||
height: 720,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
placeholder="/pdf/une-economie-du-don.pdf"
|
||||
/>
|
||||
</div>
|
||||
<button class="save-pdf-btn" @click="savePdfPath" :disabled="savingPdf">
|
||||
<button class="save-pdf-btn" @click="savePdfConfig" :disabled="savingPdf">
|
||||
<div v-if="savingPdf" class="i-lucide-loader-2 h-3.5 w-3.5 animate-spin" />
|
||||
<div v-else-if="savedPdf" class="i-lucide-check h-3.5 w-3.5" />
|
||||
<div v-else class="i-lucide-save h-3.5 w-3.5" />
|
||||
@@ -59,6 +59,20 @@
|
||||
>{{ name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<select
|
||||
v-if="pdfOutline.length"
|
||||
class="page-select"
|
||||
:value="getChapterPage(chapter.slug) ?? ''"
|
||||
@change="setChapterPage(chapter.slug, ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="">— Page PDF —</option>
|
||||
<option
|
||||
v-for="entry in pdfOutline"
|
||||
:key="`${entry.page}-${entry.title}`"
|
||||
:value="entry.page"
|
||||
>{{ '\u00A0\u00A0'.repeat(entry.level) }}{{ entry.title }} (p.{{ entry.page }})</option>
|
||||
</select>
|
||||
<span v-else class="text-xs text-white/30">…</span>
|
||||
<button
|
||||
class="delete-btn"
|
||||
@click="removeChapter(chapter.slug)"
|
||||
@@ -115,25 +129,54 @@ const saved = ref(false)
|
||||
const newTitle = ref('')
|
||||
const newSlug = ref('')
|
||||
|
||||
// PDF path
|
||||
// PDF path + outline
|
||||
const pdfPath = ref(bookConfig.value?.book?.pdfFile ?? '/pdf/une-economie-du-don.pdf')
|
||||
const savingPdf = ref(false)
|
||||
const savedPdf = ref(false)
|
||||
|
||||
async function savePdfPath() {
|
||||
const pdfOutline = ref<Array<{ title: string; page: number; level: number }>>([])
|
||||
|
||||
|
||||
const chapterPageMap = ref<Record<string, number | undefined>>({})
|
||||
if (bookConfig.value?.chapterPages) {
|
||||
for (const cp of bookConfig.value.chapterPages) {
|
||||
chapterPageMap.value[cp.chapterSlug] = cp.page
|
||||
}
|
||||
}
|
||||
|
||||
function getChapterPage(slug: string): number | undefined {
|
||||
return chapterPageMap.value[slug]
|
||||
}
|
||||
|
||||
function setChapterPage(slug: string, val: string) {
|
||||
const num = parseInt(val, 10)
|
||||
if (num > 0) chapterPageMap.value[slug] = num
|
||||
else delete chapterPageMap.value[slug]
|
||||
}
|
||||
|
||||
// Charger le sommaire PDF côté client via l'API serveur
|
||||
if (import.meta.client) {
|
||||
$fetch<Array<{ title: string; page: number; level: number }>>('/api/admin/pdf-outline')
|
||||
.then((data) => { pdfOutline.value = data })
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
async function savePdfConfig() {
|
||||
if (!bookConfig.value) return
|
||||
savingPdf.value = true
|
||||
savedPdf.value = false
|
||||
try {
|
||||
bookConfig.value.book.pdfFile = pdfPath.value
|
||||
bookConfig.value.chapterPages = Object.entries(chapterPageMap.value)
|
||||
.filter(([, page]) => page != null)
|
||||
.map(([chapterSlug, page]) => ({ chapterSlug, page }))
|
||||
await $fetch('/api/admin/content/config', {
|
||||
method: 'PUT',
|
||||
body: bookConfig.value,
|
||||
})
|
||||
savedPdf.value = true
|
||||
setTimeout(() => { savedPdf.value = false }, 2000)
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
savingPdf.value = false
|
||||
}
|
||||
}
|
||||
@@ -310,6 +353,23 @@ async function removeChapter(slug: string) {
|
||||
border: 1px solid hsl(12 76% 48% / 0.2);
|
||||
}
|
||||
|
||||
.page-select {
|
||||
flex-shrink: 0;
|
||||
max-width: 14rem;
|
||||
padding: 0.25rem 0.4rem;
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid hsl(20 8% 18%);
|
||||
background: hsl(20 8% 6%);
|
||||
color: hsl(20 8% 65%);
|
||||
font-size: 0.7rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-select:focus {
|
||||
outline: none;
|
||||
border-color: hsl(12 76% 48% / 0.5);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 0.375rem;
|
||||
|
||||
Reference in New Issue
Block a user