Référence des templates
Un thème Chinaski est un répertoire plat de templates Template Toolkit 2 (.tt). Le processus de build rend vos templates en HTML statique pour chaque page et post, une fois par langue configurée. Cette page couvre les fichiers de thème, les variables qu'ils reçoivent, et les helpers et règles qui les accompagnent.
Où vivent les thèmes
- Le thème par défaut intégré est livré avec l'application et sert de fallback.
- Les thèmes personnalisés vivent dans
builder/themes/<your-theme-slug>/. Quand un thème avec le slug du thème actif existe là, le builder l'utilise ; sinon il retombe sur les templates intégrés. - Le thème actif se choisit dans Settings.
Fichiers de template requis et optionnels
| Fichier | Rôle | Requis ? |
|---|---|---|
page.tt | Template par défaut de chaque page publiée | Oui |
post.tt | Template de chaque post de blog publié | Oui |
blog_index.tt | Liste de blog paginée et pages de catégories | Oui |
404.tt | Page 404 par langue | Oui |
_head.tt | Partial <head> : CSS, balises meta, imports de polices | Convention |
_header.tt | En-tête du site : logo, nav, tagline | Convention |
_footer.tt | Pied de page du site : copyright, nav secondaire | Convention |
_cookie_notice.tt | Bannière GDPR optionnelle | Non |
_sidebar.tt | Partial de sidebar optionnel | Non |
search.tt | Page de résultats de recherche optionnelle | Non |
_password_gate.tt | UI de porte à mot de passe côté client pour les pages protégées | Requis si page.tt/post.tt supporte password_hash |
<custom>.tt | N'importe quelle page qui le sélectionne via le réglage template | Non |
Les fichiers commençant par _ sont des partials et ne sont jamais rendus comme pages autonomes.
Variables de template
Les templates reçoivent un ensemble plat de variables ; il n'y a pas d'objets page. ou site.. Utilisez site_name, title, lang, etc. directement.
page.tt et 404.tt
| Variable | Type | Notes |
|---|---|---|
title | string | Titre de la page |
body | chaîne HTML | Pré-rendu : ne l'échappez pas avec | html |
slug | string | Slug d'URL |
lang / language | string | Code à 2 lettres, ex. en |
meta_title | string? | Outrepasse <title> |
meta_description | string? | Description SEO |
og_title | string? | Titre OpenGraph |
og_description | string? | Description OG |
og_image_url | string? | Image OG |
canonical_url | string | Généré automatiquement |
featured_image_url | string? | Image héro |
featured_image_alt | string? | Texte alt |
updated_at | string | Datetime SQLite |
site_name | string | Depuis les réglages |
site_tagline | string | Depuis les réglages |
footer_copyright | string | Depuis les réglages |
year | integer | Année de build |
url_base | string | Préfixe d'URL : peut être vide ; préfixez TOUS les liens internes |
cookie_notice_active | 0/1 | Rendre ou non _cookie_notice.tt |
noindex | 0/1 | Émettre <meta name="robots" content="noindex"> |
nav_items | array | {label, url, slot, sort_order} : slot vaut header ou footer |
hreflang_links | array | {hreflang, href} pour les traductions |
breadcrumbs | array | {name, url, is_current} |
json_ld | chaîne HTML | JSON-LD pré-sérialisé : émettez brut |
ga4_id | string? | ID Google Analytics |
gtm_id | string? | ID de conteneur GTM |
twitter_site | string? | Handle Twitter avec @ |
password_hash | string? | Si défini, vous DEVEZ envelopper le contenu dans _password_gate.tt (voir ci-dessous) |
post.tt : tout ce qui vient de page.tt, plus
| Variable | Type | Notes |
|---|---|---|
excerpt | string | Extrait fourni par l'auteur ; peut être vide |
category | string? | Nom de la catégorie unique |
tags | string | Séparés par des virgules ; découpez avec tags.split(',') |
author | string | Nom affiché |
is_sticky | 0/1 | Épinglé en tête de l'index du blog |
published_at | string? | Datetime SQLite ; moment de la première publication |
date | string | Tranche YYYY-MM-DD de published_at |
feed_url | string | URL absolue du feed RSS |
og_type | string | Toujours 'article' |
blog_index.tt
| Variable | Type | Notes |
|---|---|---|
posts | array | Hash de posts triés avec les clés p.title, p.slug, p.date, p.body, p.category |
pagination | hash? | {current_page, total_pages, prev_url, next_url, abs_prev_url, abs_next_url} : undef quand une seule page |
title | string | « Blog » localisé ou nom de la catégorie |
category_name | string? | Défini lors du filtrage par catégorie |
search.tt
| Variable | Type | Notes |
|---|---|---|
query | string | Terme de recherche |
results | array | {title, url, excerpt} |
result_count | integer | Nombre total de correspondances |
Blocks
Les auteurs placent des blocks en écrivant {{block:my-cta}} dans un body de page ou de post. Au moment du build, le builder trouve le shortcode, recherche le block publié avec ce nom et cette langue, le rend avec son template, et substitue le HTML rendu dans le body.
Les templates de blocks sont partagés entre les thèmes et vivent hors de votre répertoire de thème ; votre thème est responsable du CSS qui style leur sortie.
Classes CSS que votre thème doit styler
Chaque type de block émet une classe wrapper prévisible et des classes internes. Votre _head.tt doit styler celles-ci :
| Type | Wrapper | Classes internes |
|---|---|---|
cta | .cta-block | .cta-inner .cta-heading .cta-subheading .cta-button |
image | .image-block | .image-caption |
quote | .quote-block | .quote-text .quote-attribution |
hero | .hero-block | .hero-media .hero-inner .hero-heading .hero-subheading .hero-button |
testimonial | .testimonial-block | .testimonial-label .testimonial-body .testimonial-avatar .testimonial-text .testimonial-attribution |
stat | .stat-block | .stat-number .stat-label |
promo | .promo-block | .promo-media .promo-content .promo-heading .promo-body .promo-button |
embed | .embed-block | .embed-caption .embed-content |
video | .video-block | .video-heading .video-wrapper .video-caption |
form | .form-block | .form-heading .form-subheading .block-form .block-form__field |
newsletter | .newsletter-block | .newsletter-inner .newsletter-heading .newsletter-subheading .newsletter-form .newsletter-form__row |
accordion | .accordion-block | .accordion-heading .accordion .accordion__item .accordion__summary .accordion__body |
table | .table-block | .table-heading .table-wrapper .block-table |
columns | .columns-block | .columns-heading .columns .columns--2 .columns--3 .columns--4 .column .column__image .column__heading .column__body |
gallery | .gallery-block | .gallery-heading .gallery .gallery__item |
Style minimum requis pour les classes utilitaires :
.video-wrapper:position: relative; padding-bottom: 56.25%;avec l'iframeposition: absolute; inset: 0; width: 100%; height: 100%;(vidéo responsive 16:9).columns--2/.columns--3/.columns--4:display: gridavec lesgrid-template-columnsappropriées.table-wrapper:overflow-x: autopour que les tables défilent sur mobile.gallery: grille responsive avec gap.accordion__item/.accordion__summary: reset basiquedetails/summary
Pages multi-langues
- Chaque page est rendue une fois par langue configurée.
- Les liens internes doivent inclure le préfixe de langue : utilisez
[% url_base %]/[% lang %]/contact/ou similaire. La seule exception estnav_items[].url, qui contient déjà l'URL complète correcte ; utilisez-la telle quelle. - L'élément
<html>doit porter la langue :<html lang="[% lang | html %]">.
Règles de sécurité
Les directives suivantes sont interdites dans le contenu des thèmes et des blocks et sont rejetées :
[% PERL %]/[% RAWPERL %][% INSERT '/absolute/path' %][% USE ... %]de modules comme File/IO/Socket/Net/LWP/WWW/HTTP/System/Cmd/Process/Pipe/Expect
Pages protégées par mot de passe
La protection par mot de passe est une porte tournée vers le lecteur : le contenu de la page est toujours présent dans le HTML publié, et _password_gate.tt le cache derrière une vérification JavaScript jusqu'à ce que le visiteur entre le bon mot de passe. page.tt et post.tt doivent l'implémenter comme ceci :
[% IF password_hash %]
[% WRAPPER '_password_gate.tt' %][% PROCESS page_content %][% END %]
[% ELSE %]
[% PROCESS page_content %]
[% END %] (Enveloppez quel que soit le bloc détenant votre contenu principal ; le nom exact du bloc n'importe pas, seulement qu'il soit défini et traité à l'intérieur du wrapper.) Si votre template omet entièrement la branche password_hash, un administrateur peut tout de même définir un mot de passe sur une page utilisant votre thème, et la page se rendra publiquement sans aucun avertissement que la porte est absente.
Anti-sèche
[% variable %]: sortie brute[% variable | html %]: échappé en HTML (à utiliser pour TOUT texte contrôlé par l'utilisateur)[% variable | uri %]: encodage URI (pour les segments de chemin d'URL)[% IF / ELSE / END %],[% UNLESS ... %]: conditionnelles[% FOREACH item IN items %]: boucles ;loop.index/loop.countdisponibles[% INCLUDE '_partial.tt' %]: inclure un partial, en partageant les variables courantes ; ajoutezx = 42pour passer ou masquer des valeurs[% PROCESS '_macros.tt' %]: comme INCLUDE mais sans introduire de nouvelle portée[%- ... -%]: rogne les blancs environnants
Pièges clés
- Stash plat : pas d'objets imbriqués.
site.title,page.title,post.titlesont tous faux ; utilisezsite_nameettitle. - Les champs des nav items sont
label,url,slot: pasname,href,active. - Les dates : n'appelez jamais le formatage de date sur
published_at; utilisez la variabledatepré-calculée, ou prenez les 10 premiers caractères depublished_at. - Le body est du HTML pré-rendu : sortez
[% body %]sans échappement. - Les partials sont préfixés par
_. - Tous les liens internes ont besoin de
url_baseetlang.