Nouvelle version de Zola et petites corrections

This commit is contained in:
julien committed 2026-09-30 10:30:36 +02:00
1 parent 7e09d12345
commit 58bcc189ab
9 files changed
+92 -22

No files matched your search

+1
View File
@@ -0,0 +1 @@
FRONTEND_PORT=8002
+2
View File
@@ -1,3 +1,5 @@
.env
# Site généré par Zola # Site généré par Zola
src/public/ src/public/
+3
View File
@@ -1,4 +1,6 @@
lescarnetsdakkadien.netig.net { lescarnetsdakkadien.netig.net {
encode zstd gzip
@assets path /main.css /giallo.css /images/* /fonts/* @assets path /main.css /giallo.css /images/* /fonts/*
@documents { @documents {
not path /main.css /giallo.css /images/* /fonts/* not path /main.css /giallo.css /images/* /fonts/*
@@ -7,6 +9,7 @@ lescarnetsdakkadien.netig.net {
header { header {
Content-Security-Policy "default-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; object-src 'none'" Content-Security-Policy "default-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; object-src 'none'"
Referrer-Policy "strict-origin-when-cross-origin" Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "camera=(), geolocation=(), microphone=()"
Strict-Transport-Security "max-age=31536000" Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff" X-Content-Type-Options "nosniff"
X-Frame-Options "DENY" X-Frame-Options "DENY"
+6 -6
View File
@@ -4,13 +4,13 @@ COPY scripts/check-content.py scripts/_schema.py ./scripts/
COPY src ./src COPY src ./src
RUN python3 scripts/check-content.py RUN python3 scripts/check-content.py
FROM ghcr.io/getzola/zola:v0.23.1 AS builder FROM ghcr.io/getzola/zola:v0.23.6 AS build
WORKDIR /project WORKDIR /site
COPY --from=validator /project/src ./ COPY --from=validator /project/src /site
RUN ["/zola", "build"] RUN ["zola", "build", "--output-dir", "/public"]
FROM ghcr.io/static-web-server/static-web-server:2 FROM ghcr.io/static-web-server/static-web-server:2.44.0
COPY --from=build /public /public
ENV SERVER_ROOT=/public \ ENV SERVER_ROOT=/public \
SERVER_CACHE_CONTROL_HEADERS=false \ SERVER_CACHE_CONTROL_HEADERS=false \
SERVER_SECURITY_HEADERS=false SERVER_SECURITY_HEADERS=false
COPY --from=builder /project/public /public
+6 -5
View File
@@ -29,7 +29,7 @@ Les commandes publiques sont :
- `new-content.py` : création d’un brouillon ; - `new-content.py` : création d’un brouillon ;
- `update-content.py` : mise à jour de l’horodatage `updated` ; - `update-content.py` : mise à jour de l’horodatage `updated` ;
- `check-content.py` : validation structurelle des fiches. - `check-content.py` : validation des noms et de la structure des fiches.
`_schema.py` est leur module commun et ne se lance pas directement. `_schema.py` est leur module commun et ne se lance pas directement.
@@ -424,6 +424,9 @@ Zola génère `giallo.css`, tandis que le HTML rendu utilise les classes corresp
Pour chaque fiche, il vérifie : Pour chaque fiche, il vérifie :
- le nom de fichier en Unicode NFC ;
- les identifiants en minuscules avec lettres Unicode, chiffres et tirets ;
- la forme `mzl-NNN.md` pour les signes ;
- la lecture en UTF-8 ; - la lecture en UTF-8 ;
- la présence d’un front matter délimité par `+++` ; - la présence d’un front matter délimité par `+++` ;
- la validité du TOML ; - la validité du TOML ;
@@ -453,11 +456,9 @@ Le validateur accepte les chaînes et tableaux vides. Il ne contrôle pas :
- la qualité ou l’exactitude linguistique des valeurs ; - la qualité ou l’exactitude linguistique des valeurs ;
- les champs supplémentaires ; - les champs supplémentaires ;
- la forme des noms de fichiers ; - la correspondance entre le numéro MZL du front matter et le nom du fichier.
- la correspondance entre le numéro MZL et le nom du fichier ;
- la normalisation Unicode des fichiers existants.
Zola reste responsable des liens, taxonomies et templates. Le générateur reste responsable de produire des noms de fichiers valides et des squelettes complets. Zola reste responsable des liens, taxonomies et templates. Le générateur et le validateur appliquent les mêmes conventions de nommage ; le générateur reste responsable de produire des squelettes complets.
## Vérification avant publication ## Vérification avant publication
+23 -7
View File
@@ -4,7 +4,7 @@
Site public : <https://lescarnetsdakkadien.netig.net> Site public : <https://lescarnetsdakkadien.netig.net>
Le site est généré avec **Zola 0.23.1** et **Tera 2** à partir de fichiers Markdown. Il est entièrement statique : aucune base de données, aucune interface d’administration et aucune exécution côté serveur ne sont nécessaires. Le site est généré avec **Zola 0.23.6** et **Tera 2** à partir de fichiers Markdown. Il est entièrement statique : aucune base de données, aucune interface d’administration et aucun code applicatif côté serveur ne sont nécessaires.
## Contenu ## Contenu
@@ -21,7 +21,7 @@ La page d’accueil présente le dernier article et les trois fiches les plus r
Pour le développement local : Pour le développement local :
- Python 3.11 ou ultérieur ; - Python 3.11 ou ultérieur ;
- Zola 0.23.1 ; - Zola 0.23.6 ;
- Git, si le dépôt est versionné avec Git. - Git, si le dépôt est versionné avec Git.
Pour le déploiement décrit ici : Pour le déploiement décrit ici :
@@ -83,7 +83,7 @@ Les contenus sont stockés dans `src/content/`. Trois commandes accompagnent leu
- `new-content.py` crée un brouillon complet pour une section donnée ; - `new-content.py` crée un brouillon complet pour une section donnée ;
- `update-content.py` actualise son horodatage `updated` ; - `update-content.py` actualise son horodatage `updated` ;
- `check-content.py` vérifie la présence et le type des champs prévus. - `check-content.py` vérifie les noms de fichiers ainsi que la présence et le type des champs prévus.
Le module `_schema.py` centralise les modèles utilisés par le générateur et le validateur ; il n’est pas exécuté directement. Le module `_schema.py` centralise les modèles utilisés par le générateur et le validateur ; il n’est pas exécuté directement.
@@ -93,6 +93,7 @@ Les commandes, conventions et modèles de front matter sont détaillés dans le
```text ```text
. .
├── .env.example
├── Caddyfile.example ├── Caddyfile.example
├── Dockerfile ├── Dockerfile
├── compose.yml ├── compose.yml
@@ -128,8 +129,20 @@ Les commandes, conventions et modèles de front matter sont détaillés dans le
L’image est construite en trois étapes : L’image est construite en trois étapes :
1. validation des fiches avec Python ; 1. validation des fiches avec Python ;
2. génération du site avec Zola 0.23.1 ; 2. génération du site avec Zola 0.23.6 ;
3. copie des seuls fichiers publics dans un serveur HTTP statique. 3. copie des seuls fichiers publics dans static-web-server 2.44.0.
Créer la configuration locale à partir de l’exemple :
```sh
cp .env.example .env
```
Le seul réglage nécessaire est le port HTTP publié sur l’hôte :
```dotenv
FRONTEND_PORT=8002
```
Vérifier puis démarrer le service : Vérifier puis démarrer le service :
@@ -138,12 +151,14 @@ docker compose config
docker compose up -d --build docker compose up -d --build
``` ```
Le conteneur est exposé uniquement sur l’interface locale de l’hôte : Par défaut, le conteneur est exposé uniquement sur l’interface locale de l’hôte :
```text ```text
http://127.0.0.1:8002 http://127.0.0.1:8002
``` ```
Le port peut être changé dans `.env`. Si Caddy est utilisé, adapter alors aussi le port de `reverse_proxy` dans `Caddyfile.example` ou dans la configuration de l’hôte.
Pour mettre à jour le déploiement : Pour mettre à jour le déploiement :
```sh ```sh
@@ -163,10 +178,11 @@ Caddy prend en charge le domaine public, HTTPS, les en-têtes de sécurité et l
[`Caddyfile.example`](Caddyfile.example) fournit le bloc correspondant au port publié par `compose.yml`. Il applique : [`Caddyfile.example`](Caddyfile.example) fournit le bloc correspondant au port publié par `compose.yml`. Il applique :
- la compression `zstd` ou `gzip` selon le client ;
- `Cache-Control: no-cache` aux documents ; - `Cache-Control: no-cache` aux documents ;
- un cache navigateur d’une journée aux feuilles de style, images et polices ; - un cache navigateur d’une journée aux feuilles de style, images et polices ;
- une CSP compatible avec les ressources locales du site ; - une CSP compatible avec les ressources locales du site ;
- HSTS, `Referrer-Policy`, `X-Content-Type-Options` et `X-Frame-Options`. - HSTS, `Referrer-Policy`, `Permissions-Policy`, `X-Content-Type-Options` et `X-Frame-Options`.
Le fichier peut être importé au niveau principal du Caddyfile de l’hôte ou servir de modèle pour un bloc existant. Sa syntaxe peut être contrôlée avec : Le fichier peut être importé au niveau principal du Caddyfile de l’hôte ou servir de modèle pour un bloc existant. Sa syntaxe peut être contrôlée avec :
+2 -2
View File
@@ -3,6 +3,6 @@ name: les-carnets-d-akkadien
services: services:
zola: zola:
build: . build: .
ports:
- "127.0.0.1:8002:80"
restart: unless-stopped restart: unless-stopped
ports:
- "127.0.0.1:${FRONTEND_PORT:-8002}:80"
+46
View File
@@ -1,8 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Vérifie que chaque fiche contient les champs prévus pour son modèle.""" """Vérifie que chaque fiche contient les champs prévus pour son modèle."""
import re
import sys import sys
import tomllib import tomllib
import unicodedata
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -25,6 +27,8 @@ COMMON_FIELDS = {
"draft": bool, "draft": bool,
} }
MZL_FILENAME_RE = re.compile(r"mzl-([0-9]{3})")
class Validator: class Validator:
def __init__(self) -> None: def __init__(self) -> None:
@@ -68,6 +72,46 @@ def read_front_matter(
return None return None
def check_filename(
path: Path,
section: str,
validator: Validator,
) -> None:
stem = path.stem
normalized = unicodedata.normalize("NFC", stem)
if stem != normalized:
validator.error(path, "nom de fichier non normalisé en Unicode NFC")
if section == "signes":
match = MZL_FILENAME_RE.fullmatch(stem)
if match is None or not 1 <= int(match.group(1)) <= 999:
validator.error(path, "un signe doit être nommé mzl-NNN.md (001 à 999)")
return
allow_leading_hyphen = section == "vocabulaire"
body = stem[1:] if stem.startswith("-") else stem
if stem.startswith("-") and not allow_leading_hyphen:
validator.error(
path,
"seul le vocabulaire accepte un nom commençant par un tiret",
)
return
if (
not body
or stem in {".", "..", "_index"}
or stem != stem.lower()
or stem.endswith("-")
or "--" in stem
or not all(character.isalnum() or character == "-" for character in body)
):
validator.error(
path,
"nom de fichier invalide : utiliser des lettres Unicode minuscules, "
"des chiffres et des tirets",
)
def check_type( def check_type(
value: Any, value: Any,
expected_type: type, expected_type: type,
@@ -189,6 +233,8 @@ def main() -> int:
validator.error(path, f"section inconnue : {section}") validator.error(path, f"section inconnue : {section}")
continue continue
check_filename(path, section, validator)
result = read_front_matter(path, validator) result = read_front_matter(path, validator)
if result is None: if result is None:
continue continue
+3 -2
View File
@@ -1,7 +1,8 @@
{% extends "base.html" %} {% extends "base.html" %}
{% set term_label = term.name | replace(from="-", to=" ") if taxonomy.name != "lectures" else term.name %} {% block title %}
{% block title %}{% if taxonomy.name == "natures" %}Mots : {{ term_label }}{% elif taxonomy.name == "lectures" %}Lecture « {{ term_label }} »{% elif taxonomy.name == "genres" %}Textes : {{ term_label }}{% elif taxonomy.name == "themes" %}Grammaire : {{ term_label }}{% else %}{{ term_label }}{% endif %} · {{ config.title }}{% endblock title %} {% set term_label = term.name | replace(from="-", to=" ") if taxonomy.name != "lectures" else term.name %}{% if taxonomy.name == "natures" %}Mots : {{ term_label }}{% elif taxonomy.name == "lectures" %}Lecture « {{ term_label }} »{% elif taxonomy.name == "genres" %}Textes : {{ term_label }}{% elif taxonomy.name == "themes" %}Grammaire : {{ term_label }}{% else %}{{ term_label }}{% endif %} · {{ config.title }}{% endblock title %}
{% block content %} {% block content %}
{% set term_label = term.name | replace(from="-", to=" ") if taxonomy.name != "lectures" else term.name %}
{% set heading = term_label %}{% set description = "Entrées classées dans cet index." %}{% set back_url = get_url(path=taxonomy.name, trailing_slash=true) %}{% set back_label = "Retour à l’index" %} {% set heading = term_label %}{% set description = "Entrées classées dans cet index." %}{% set back_url = get_url(path=taxonomy.name, trailing_slash=true) %}{% set back_label = "Retour à l’index" %}
{% if taxonomy.name == "natures" %} {% if taxonomy.name == "natures" %}
{% set heading = "Mots : " ~ term_label %}{% set description = "Fiches de vocabulaire classées comme « " ~ term_label ~ " »." %}{% set back_label = "Toutes les natures grammaticales" %} {% set heading = "Mots : " ~ term_label %}{% set description = "Fiches de vocabulaire classées comme « " ~ term_label ~ " »." %}{% set back_label = "Toutes les natures grammaticales" %}