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:
@@ -57,30 +57,52 @@ const projectCoverImage = computed<ResolvedMedia | undefined>(() => {
|
||||
|
||||
const projectText = computed(() => {
|
||||
if (props.page.kind !== 'project' || !project.value) return ''
|
||||
if (props.page.textOverride?.trim()) return marked.parse(props.page.textOverride) as string
|
||||
return project.value.bodyHtml
|
||||
// `textOverride` undefined = suit Grav (bodyHtml) ; toute chaîne définie
|
||||
// (y compris vide) est un texte figé à l'édition et rendue telle quelle.
|
||||
const override = props.page.textOverride
|
||||
const html = override === undefined
|
||||
? project.value.bodyHtml
|
||||
: (marked.parse(override) as string)
|
||||
return stripTargetParam(html)
|
||||
})
|
||||
|
||||
const projectIntro = computed(() => {
|
||||
if (props.page.kind !== 'project' || !project.value) return ''
|
||||
return props.page.subtitleOverride?.trim() || project.value.categories.join(', ')
|
||||
// Ligne d'info de la couverture de projet : type de travail + année.
|
||||
// Type par défaut = taxonomy.category de Grav (« Site web », « Édition »…),
|
||||
// c.-à-d. Project.categories — surchargée par subtitleOverride si renseignée.
|
||||
const projectWorkType = computed(() => {
|
||||
if (props.page.kind !== 'project') return ''
|
||||
return props.page.subtitleOverride?.trim() || project.value?.categories.join(', ') || ''
|
||||
})
|
||||
|
||||
const projectYear = computed(() => {
|
||||
if (props.page.kind !== 'project') return ''
|
||||
if (props.page.year?.trim()) return props.page.year.trim()
|
||||
const iso = project.value?.date
|
||||
const y = iso ? new Date(iso).getFullYear() : NaN
|
||||
return Number.isNaN(y) ? '' : String(y)
|
||||
})
|
||||
|
||||
const projectLink = computed(() => {
|
||||
if (props.page.kind !== 'project') return ''
|
||||
return stripTargetParam(props.page.link?.trim() || project.value?.externalUrl || '')
|
||||
})
|
||||
|
||||
const projectBadge = computed(() => {
|
||||
const c = project.value?.category
|
||||
return c ? `Utilité ${CATEGORY_LABELS[c]?.toLowerCase() ?? c}` : undefined
|
||||
return c ? CATEGORY_BADGE[c] ?? c : undefined
|
||||
})
|
||||
|
||||
// ---- Page grille de projet ----
|
||||
// Le filet hérite de la couleur du bandeau de la page couverture du projet.
|
||||
const gridStripOverride = computed(() => {
|
||||
// Filet et mode clair hérités de la page couverture du même projet.
|
||||
const gridCover = computed(() => {
|
||||
if (props.page.kind !== 'project-grid') return undefined
|
||||
const projectId = props.page.projectId
|
||||
const cover = props.portfolio.pages.find(
|
||||
return props.portfolio.pages.find(
|
||||
(p): p is Extract<Page, { kind: 'project' }> => p.kind === 'project' && p.projectId === projectId,
|
||||
)
|
||||
return cover?.bandColor
|
||||
})
|
||||
// Le filet des grilles hérite de la couleur du bandeau de la couverture du projet.
|
||||
const gridStripOverride = computed(() => gridCover.value?.bandColor)
|
||||
|
||||
// Orientation dominante des images posées : la grille adaptative en dépend
|
||||
const gridOrientation = computed(() => {
|
||||
@@ -132,7 +154,7 @@ const tocEntries = computed(() =>
|
||||
// Page libre : texte markdown + médias "projectId/filename"
|
||||
const freeText = computed(() => {
|
||||
if (props.page.kind !== 'free' || !props.page.body?.trim()) return ''
|
||||
return marked.parse(props.page.body) as string
|
||||
return stripTargetParam(marked.parse(props.page.body) as string)
|
||||
})
|
||||
|
||||
const freeMedia = computed<ResolvedMedia[]>(() => {
|
||||
@@ -149,6 +171,9 @@ const freeMedia = computed<ResolvedMedia[]>(() => {
|
||||
<CoverTemplate
|
||||
v-if="page.kind === 'cover'"
|
||||
:title="portfolio.cover.title"
|
||||
:date="portfolio.cover.date"
|
||||
:site-url="portfolio.cover.siteUrl"
|
||||
:contact-email="portfolio.cover.contactEmail"
|
||||
:background="page.background"
|
||||
:band-color="page.bandColor"
|
||||
:render-mode="renderMode"
|
||||
@@ -171,7 +196,8 @@ const freeMedia = computed<ResolvedMedia[]>(() => {
|
||||
:text="freeText"
|
||||
:media="freeMedia"
|
||||
:background="page.background"
|
||||
:band-color="page.bandColor"
|
||||
:band-color="page.bandColor ?? coverBandColor"
|
||||
:text-scale="page.textScale"
|
||||
:page="pageInfo"
|
||||
:render-mode="renderMode"
|
||||
/>
|
||||
@@ -185,7 +211,10 @@ const freeMedia = computed<ResolvedMedia[]>(() => {
|
||||
:title="project.title"
|
||||
:category="project.category"
|
||||
:badge="projectBadge"
|
||||
:intro="projectIntro"
|
||||
:work-type="projectWorkType"
|
||||
:year="projectYear"
|
||||
:subtitle="page.subtitle"
|
||||
:link="projectLink"
|
||||
:text="projectText"
|
||||
:image="projectCoverImage"
|
||||
:image-fit="page.coverFit"
|
||||
@@ -194,6 +223,8 @@ const freeMedia = computed<ResolvedMedia[]>(() => {
|
||||
:band-height="page.bandHeight"
|
||||
:band-color="page.bandColor"
|
||||
:ink="page.ink"
|
||||
:background="page.background"
|
||||
:layout="page.layout"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -206,9 +237,8 @@ const freeMedia = computed<ResolvedMedia[]>(() => {
|
||||
:media="gridMedia"
|
||||
:grid="page.grid"
|
||||
:orientation="gridOrientation"
|
||||
:category="project.category"
|
||||
:band-color="gridStripOverride"
|
||||
:grey-bg="page.greyBg"
|
||||
:background="page.background"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user