Feat: Livres blancs — nouveau template collection + livre blanc individuel

- Blueprints white-papers / white-paper (intro, cover, PDF, date)
- Templates PHP + JSON API (liste avec singleSlug, détail avec fileUrl)
- Route POST (:any)/(:any)/download pour le téléchargement gated
- Panel : entrée white-papers ajoutée au menu après blog
- collection.css : styles partagés extraits de Blog (collection-*)
- Blog.svelte : classes renommées blog-* → collection-*
- WhitePapers.svelte : vue liste, URLs dynamiques via data.uri
- WhitePaper.svelte : vue détail deux colonnes + formulaire de téléchargement
- i18n : clés white paper (label, form, consentement, statuts)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-03-18 17:37:22 +01:00
parent 42ee58c18d
commit d4f05d6157
17 changed files with 870 additions and 183 deletions

View file

@ -0,0 +1,61 @@
title: Livre blanc
icon: file
status:
draft:
label: Brouillon
text: Non visible
listed:
label: Publié
text: Visible publiquement
tabs:
content:
label: Contenu
icon: text
columns:
main:
width: 2/3
sections:
header:
type: fields
fields:
intro:
label: Description courte
type: writer
nodes: false
marks:
- bold
- italic
- link
maxlength: 200
sidebar:
width: 1/3
sections:
meta:
type: fields
fields:
published:
label: Date de publication
type: date
display: DD/MM/YYYY
required: true
default: today
translate: false
cover:
label: Image de couverture
type: files
layout: cards
max: 1
accept: image/*
translate: false
image:
cover: true
ratio: 3/4
uploads:
template: image
downloadFile:
label: Fichier PDF
type: files
max: 1
accept: application/pdf
translate: false
help: Fichier téléchargé après soumission du formulaire

View file

@ -0,0 +1,43 @@
title: Livres blancs
icon: book
status:
draft:
label: Brouillon
text: La page est accessible uniquement pour les éditeurs connectés
listed:
label: Public
text: La page est accessible par tout le monde
tabs:
content:
label: Contenu
icon: text
sections:
content:
type: fields
fields:
intro:
type: writer
marks:
- bold
- italic
- green
- pixel
- underline
- strike
- clear
- link
nodes:
- heading
headings:
- 1
help: Section de texte centrée (optionnelle).
items:
label: Livres blancs
type: pages
layout: cards
sortBy: published desc
template: white-paper
image:
ratio: 3/4
cover: true
info: "{{ page.published.toDate('d/m/Y') }}"

View file

@ -11,4 +11,27 @@ return [
],
'thumbs' => require __DIR__ . '/thumbs.php',
'routes' => [
[
'pattern' => ['(:any)/(:any)/download', 'en/(:any)/(:any)/download'],
'method' => 'POST',
'action' => function (string $parent, string $slug) {
$page = kirby()->page($parent . '/' . $slug);
if (!$page || $page->intendedTemplate()->name() !== 'white-paper') {
http_response_code(404);
header('Content-Type: application/json');
echo json_encode(['error' => 'Not found']);
exit;
}
// TODO: store/email form data ($kirby->request()->body())
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'fileUrl' => $page->downloadFile()->toFile()?->url(),
]);
exit;
}
]
],
];

View file

@ -29,7 +29,8 @@ return [
'portfolio' => menuItem('portfolio', 'Portfolio','images', 'pages/portfolio'),
'jouer' => menuItem('jouer', 'Jouer', 'play', 'pages/jouer'),
'a-propos' => menuItem('a-propos', 'À propos', 'users', 'pages/a-propos'),
'blog' => menuItem('blog', 'Blog', 'text', 'pages/blog'),
'blog' => menuItem('blog', 'Blog', 'text', 'pages/blog'),
'white-papers' => menuItem('livres-blancs', 'Livres blancs', 'book', 'pages/livres-blancs'),
'-',
'users',
'system',

View file

@ -0,0 +1,13 @@
<?php
$specificData = [
'published' => $page->published()->toDate('d/m/Y'),
'intro' => $page->intro()->value(),
'cover' => $page->cover()->toFile()?->url(),
'fileUrl' => $page->downloadFile()->toFile()?->url(),
];
$pageData = array_merge($genericData, $specificData);
header('Content-Type: application/json');
echo json_encode($pageData);

View file

@ -0,0 +1,2 @@
<?php snippet('header') ?>
<?php snippet('footer') ?>

View file

@ -0,0 +1,26 @@
<?php
$items = $page->children()->listed()->sortBy('published', 'desc');
$mapItem = function ($item) {
return [
'title' => $item->title()->value(),
'slug' => $item->slug(),
'published' => $item->published()->toDate('d/m/Y'),
'intro' => $item->intro()->value(),
'cover' => $item->cover()->toFile()?->url(),
];
};
$singleSlug = $items->count() === 1 ? $items->first()->slug() : null;
$specificData = [
'intro' => $page->intro()->value(),
'items' => $items->map($mapItem)->values(),
'singleSlug' => $singleSlug,
];
$pageData = array_merge($genericData, $specificData);
header('Content-Type: application/json');
echo json_encode($pageData);

View file

@ -0,0 +1,2 @@
<?php snippet('header') ?>
<?php snippet('footer') ?>