Textes projet éditables suivant Grav + écriture atomique du store
- Champ Présentation prérempli avec le texte Grav, suivi modifié/synchro (textOverride undefined = suit Grav, sinon figé ; bouton Réinitialiser) - Fix génération PDF : savePortfolio écrit en atomique (temp+rename) pour éviter la corruption de lecture par l'autosave concurrent du rendu print - Toast d'échec de génération explicite (message serveur) - Refonte architecture visuelle, fond unifié white/tint/full, dispositions couverture bandeau/colonne, instantané public complet, index 3 lignes
This commit is contained in:
@@ -15,5 +15,15 @@ export default defineEventHandler(async (event): Promise<{ portfolio: Portfolio;
|
||||
throw createError({ statusCode: 404, statusMessage: 'Portfolio introuvable' })
|
||||
}
|
||||
|
||||
return { portfolio, projects: portfolio.frozenContent.projects }
|
||||
// Vrai instantané : on rend la composition figée (pages + mise en forme), pas
|
||||
// la composition live. Repli sur le live pour les instantanés antérieurs.
|
||||
const frozen = portfolio.frozenContent
|
||||
const snapshot: Portfolio = {
|
||||
...portfolio,
|
||||
name: frozen.name ?? portfolio.name,
|
||||
cover: frozen.cover ?? portfolio.cover,
|
||||
pages: frozen.pages ?? portfolio.pages,
|
||||
}
|
||||
|
||||
return { portfolio: snapshot, projects: frozen.projects }
|
||||
})
|
||||
|
||||
@@ -68,7 +68,16 @@ export async function freezeContent(portfolio: Portfolio, fileName: string): Pro
|
||||
}
|
||||
}
|
||||
|
||||
return { generatedAt: new Date().toISOString(), fileName, projects }
|
||||
// Instantané de la composition (mise en forme incluse) : la vue publique
|
||||
// rendra CETTE version figée, indépendante des éditions ultérieures.
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
fileName,
|
||||
projects,
|
||||
name: portfolio.name,
|
||||
cover: structuredClone(portfolio.cover),
|
||||
pages: structuredClone(portfolio.pages),
|
||||
}
|
||||
}
|
||||
|
||||
export interface GenerateResult {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Bibliothèque de portfolios : un fichier JSON par portfolio (spec §13).
|
||||
import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { nanoid } from 'nanoid'
|
||||
import type { Portfolio, PortfolioSummary } from '~~/types'
|
||||
@@ -61,7 +61,16 @@ export async function savePortfolio(portfolio: Portfolio, touch = true): Promise
|
||||
// aligner updatedAt sur lastExport.at et marquer le PDF « à jour »).
|
||||
if (touch) portfolio.updatedAt = new Date().toISOString()
|
||||
await mkdir(portfoliosDir(), { recursive: true })
|
||||
await writeFile(pathFor(portfolio.id), JSON.stringify(portfolio, null, 2))
|
||||
// Écriture ATOMIQUE (fichier temporaire + rename) : l'autosave de l'éditeur
|
||||
// peut écrire pendant que la génération PDF relit le portfolio via /print.
|
||||
// Un writeFile en place tronque le fichier → lecture concurrente = JSON
|
||||
// partiel → getPortfolio renvoie null → rendu print en échec. Le rename est
|
||||
// atomique sur le même volume : les lecteurs voient toujours l'ancien OU le
|
||||
// nouveau fichier complet, jamais un état intermédiaire.
|
||||
const dest = pathFor(portfolio.id)
|
||||
const tmp = `${dest}.tmp-${nanoid(6)}`
|
||||
await writeFile(tmp, JSON.stringify(portfolio, null, 2))
|
||||
await rename(tmp, dest)
|
||||
return portfolio
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user