Générateur de portfolios PDF Figures Libres (app Nuxt autonome)

App Nuxt 3 + Nuxt UI qui lit les projets du CMS Grav et compose des portfolios
A3 paysage exportés en PDF (gabarit book_v3) : composeur WYSIWYG, templates
cover/toc/projet/grille/page libre, pipeline Playwright + Ghostscript, gel du
contenu à la génération, partage public opt-in.

Dépôt autonome : Dockerfile (base Playwright + Ghostscript + ffmpeg + rsync) inclus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 18:37:09 +02:00
co-authored by Claude Opus 4.8
commit 59dfbe4155
79 changed files with 18587 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
<script setup lang="ts">
// Route de rendu print : toutes les pages du portfolio empilées, imprimées
// par Playwright/Chromium (une page CSS = une page PDF, spec §10).
// Accès par jeton interne uniquement (?internal-token=…).
import type { Portfolio, Project } from '~~/types'
definePageMeta({ layout: false })
const route = useRoute()
const token = String(route.query['internal-token'] ?? '')
const headers = { 'x-internal-token': token }
const { data: portfolio } = await useFetch<Portfolio>(`/api/portfolios/${route.params.id}`, { headers })
if (!portfolio.value) {
throw createError({ statusCode: 404, statusMessage: 'Portfolio introuvable' })
}
// Contenu figé si présent (régénération à l'identique), live sinon
const frozen = portfolio.value.frozenContent
let projects: Record<string, Project>
if (frozen) {
projects = frozen.projects
} else {
const { data } = await useFetch<{ projects: Project[] }>('/api/projects', { headers })
projects = Object.fromEntries((data.value?.projects ?? []).map(p => [p.id, p]))
}
</script>
<template>
<div class="print-root">
<div v-for="(page, i) in portfolio!.pages" :id="`page-${i + 1}`" :key="page.id" class="print-page">
<PageRenderer
:portfolio="portfolio!"
:page="page"
:index="i"
:projects="projects"
render-mode="print"
:frozen="frozen ? portfolio!.id : undefined"
/>
</div>
</div>
</template>
<style>
/* Une page CSS = une page PDF — A3 paysage du gabarit :
1190,55 × 841,89 pt ≡ 1587,4 × 1122,5 px */
@page {
size: 1190.55pt 841.89pt;
margin: 0;
}
html,
body {
margin: 0;
padding: 0;
background: #ffffff;
}
.print-page {
width: 1587.4px;
height: 1122.5px;
overflow: hidden;
page-break-after: always;
break-after: page;
}
.print-page:last-child {
page-break-after: auto;
break-after: auto;
}
</style>