Pages équipe + conclusion, colonnes de page libre, réglages couverture
- Page « équipe » : roster statique du collectif (composables/useTeam), portraits servis depuis public/portraits, grille ≤ 3 colonnes équilibrée (portrait/nom/rôle/présentation), sélection + surcharges par membre (repliables, réordonnables, réinitialisables) dans le composeur - Page « conclusion » : « Merci ! » centré + rappel des contacts - Page libre : 1 à 3 colonnes texte (markdown) ou images avec sous-titres, réordonnables ; migration des champs body/media legacy vers columns - Couverture : texte libre sous l'intro, adresse en bas à gauche (3 lignes), case « afficher les années » au niveau du document - Sommaire : items centrés (self-center), filet en points dégradés stable - Retrait de la dépendance morte pagedjs - Docs à jour (CLAUDE.md, README) ; spec marquée comme archive réalisée
This commit is contained in:
@@ -76,6 +76,7 @@ const projectWorkType = computed(() => {
|
||||
|
||||
const projectYear = computed(() => {
|
||||
if (props.page.kind !== 'project') return ''
|
||||
if (props.portfolio.showYears === false) return '' // réglage document : années masquées
|
||||
if (props.page.year?.trim()) return props.page.year.trim()
|
||||
const iso = project.value?.date
|
||||
const y = iso ? new Date(iso).getFullYear() : NaN
|
||||
@@ -132,6 +133,13 @@ const gridMedia = computed<ResolvedMedia[]>(() => {
|
||||
.filter((m): m is ResolvedMedia => m !== null)
|
||||
})
|
||||
|
||||
// Texte libre optionnel sous la phrase d'intro de la couverture (markdown).
|
||||
const coverBody = computed(() =>
|
||||
props.portfolio.cover.body?.trim()
|
||||
? stripTargetParam(marked.parse(props.portfolio.cover.body) as string)
|
||||
: '',
|
||||
)
|
||||
|
||||
// Filet de la couverture du dossier — définit aussi celui du sommaire.
|
||||
const coverBandColor = computed(() => {
|
||||
const cover = props.portfolio.pages.find(
|
||||
@@ -151,18 +159,61 @@ 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 stripTargetParam(marked.parse(props.page.body) as string)
|
||||
// Page libre : colonnes de texte (markdown) ou d'images. Repli sur les champs
|
||||
// legacy `body`/`media` pour les pages non encore migrées (lues depuis le disque).
|
||||
interface ResolvedColumn {
|
||||
id: string
|
||||
kind: 'text' | 'media'
|
||||
heading?: string
|
||||
html?: string
|
||||
media?: ResolvedMedia[]
|
||||
}
|
||||
|
||||
function resolveFreeMedia(refs: string[], colCount: number): ResolvedMedia[] {
|
||||
const width = Math.round(INNER_W / colCount)
|
||||
return refs.map((ref) => {
|
||||
const idx = ref.lastIndexOf('/')
|
||||
const projectId = ref.slice(0, idx)
|
||||
const media = resolve(projectId, ref.slice(idx + 1), width)
|
||||
media.caption = props.projects[projectId]?.title // titre du projet en légende
|
||||
return media
|
||||
})
|
||||
}
|
||||
|
||||
// Page équipe : membres cochés, résolus depuis le roster statique (nom +
|
||||
// portrait) avec rôle/présentation surchargés le cas échéant.
|
||||
const teamMembers = computed(() => {
|
||||
if (props.page.kind !== 'team') return []
|
||||
return props.page.members
|
||||
.filter(m => m.enabled)
|
||||
.map((m) => {
|
||||
const r = TEAM_ROSTER.find(x => x.id === m.id)
|
||||
return {
|
||||
id: m.id,
|
||||
name: r?.name ?? m.id,
|
||||
portraitUrl: portraitUrl(r?.portrait ?? ''),
|
||||
role: m.role ?? r?.role,
|
||||
bio: m.bio ?? r?.bio,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const freeMedia = computed<ResolvedMedia[]>(() => {
|
||||
if (props.page.kind !== 'free' || !props.page.media?.length) return []
|
||||
return props.page.media.map((ref) => {
|
||||
const idx = ref.lastIndexOf('/')
|
||||
return resolve(ref.slice(0, idx), ref.slice(idx + 1), 560)
|
||||
})
|
||||
const freeColumns = computed<ResolvedColumn[]>(() => {
|
||||
if (props.page.kind !== 'free') return []
|
||||
const page = props.page
|
||||
// Colonnes explicites, sinon synthèse depuis les champs legacy.
|
||||
const cols = page.columns?.length
|
||||
? page.columns
|
||||
: [
|
||||
...(page.body?.trim() ? [{ id: 'legacy-text', kind: 'text' as const, body: page.body }] : []),
|
||||
...(page.media?.length ? [{ id: 'legacy-media', kind: 'media' as const, media: page.media }] : []),
|
||||
]
|
||||
const list = cols.length ? cols : [{ id: 'empty', kind: 'text' as const, body: '' }]
|
||||
const count = list.length
|
||||
return list.map(col => col.kind === 'media'
|
||||
? { id: col.id, kind: 'media' as const, heading: col.heading, media: resolveFreeMedia(col.media ?? [], count) }
|
||||
: { id: col.id, kind: 'text' as const, heading: col.heading, html: col.body?.trim() ? stripTargetParam(marked.parse(col.body) as string) : '' },
|
||||
)
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -171,14 +222,37 @@ const freeMedia = computed<ResolvedMedia[]>(() => {
|
||||
<CoverTemplate
|
||||
v-if="page.kind === 'cover'"
|
||||
:title="portfolio.cover.title"
|
||||
:body="coverBody"
|
||||
:date="portfolio.cover.date"
|
||||
:site-url="portfolio.cover.siteUrl"
|
||||
:contact-email="portfolio.cover.contactEmail"
|
||||
:address="portfolio.cover.address"
|
||||
:background="page.background"
|
||||
:band-color="page.bandColor"
|
||||
:render-mode="renderMode"
|
||||
/>
|
||||
|
||||
<OutroTemplate
|
||||
v-else-if="page.kind === 'outro'"
|
||||
:title="page.title"
|
||||
:site-url="portfolio.cover.siteUrl"
|
||||
:contact-email="portfolio.cover.contactEmail"
|
||||
:address="portfolio.cover.address"
|
||||
:background="page.background"
|
||||
:band-color="page.bandColor ?? coverBandColor"
|
||||
:render-mode="renderMode"
|
||||
/>
|
||||
|
||||
<TeamTemplate
|
||||
v-else-if="page.kind === 'team'"
|
||||
:title="page.title"
|
||||
:members="teamMembers"
|
||||
:background="page.background"
|
||||
:band-color="page.bandColor ?? coverBandColor"
|
||||
:page="pageInfo"
|
||||
:render-mode="renderMode"
|
||||
/>
|
||||
|
||||
<TocTemplate
|
||||
v-else-if="page.kind === 'toc'"
|
||||
:title="page.title"
|
||||
@@ -193,8 +267,7 @@ const freeMedia = computed<ResolvedMedia[]>(() => {
|
||||
<FreeTextPage
|
||||
v-else-if="page.kind === 'free'"
|
||||
:title="page.title"
|
||||
:text="freeText"
|
||||
:media="freeMedia"
|
||||
:columns="freeColumns"
|
||||
:background="page.background"
|
||||
:band-color="page.bandColor ?? coverBandColor"
|
||||
:text-scale="page.textScale"
|
||||
|
||||
Reference in New Issue
Block a user