Files
williamandClaude Sonnet 5 4db1269348 Audit complet de mise en forme — Liste à puces/numérotée (4e élément)
Implémente toutes les options manquantes identifiées pour les listes :
typographie complète, style/position/couleur/taille de puce (validés
selon le kind), puce personnalisée en SVG pour les listes à puces,
padding uniforme par élément (nouveau, il n'y en avait aucun), espacement
entre éléments réglable, et tous les attributs de boîte partagés sur la
liste entière. Bordure/fond/padding par élément individuel et sous-listes
imbriquées volontairement différés (portée actée avec l'utilisateur
avant implémentation : transformeraient le stockage des éléments en
objets structurés, chantier bien plus lourd).

Trois ajouts transversaux bénéficiant à plusieurs éléments : sections
"Contenu"/"Conteneur" dans tous les panneaux de propriétés, alignement
vertical du contenu dans son bloc (Titre/Paragraphe/Liste/Image
légendée), et une option pour retirer un thème appliqué ("Aucun modèle"
dans la modale, avec une nouvelle fonction db.remove_document_theme).

Quatre bugs réels trouvés et corrigés en chaîne pendant la validation
avec le thème "Sécurité incendie" : un badge de thème s'affichait
au-dessus du texte au lieu d'à côté ; le correctif a d'abord fait
disparaître les puces/numéros natifs de TOUTES les listes (bug plus
grave que celui corrigé) ; puis un marqueur natif redondant apparaissait
à côté du badge du thème ; puis une règle CSS site-large de spécificité
supérieure empêchait silencieusement ce dernier correctif. Chaque étape
vérifiée par navigateur automatisé sur un support jetable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-26 15:13:14 +02:00

134 lines
5.6 KiB
Python

"""Attributs de mise en forme de "boîte" PARTAGÉS par plusieurs kinds de
contenu (padding/margin/background_color/border_radius/border/align_self)
— un seul et même jeu d'attributs et une seule fonction de rendu pour ne
jamais dupliquer cette logique entre `_render_text`/`_render_image`/
`_render_button`/etc. (voir retour utilisateur du 26/09/2026 : audit
complet des réglages manquants, à ajouter élément par élément en
réutilisant CE module à chaque fois plutôt que de le réécrire)."""
import html as html_lib
from typing import Any
BORDER_SIDES = ("top", "right", "bottom", "left")
_DEFAULT_BORDER_SIDE = {"style": "none", "width": "1px", "color": "var(--doc-border)"}
def default_border() -> dict[str, dict[str, str]]:
"""Nouveau dict à chaque appel (jamais un littéral partagé/muté par
référence entre plusieurs éléments, même précaution que
DEFAULT_QUIZ_CONFIG côté labels)."""
return {side: dict(_DEFAULT_BORDER_SIDE) for side in BORDER_SIDES}
BOX_DEFAULTS = {
"padding": "",
"margin": "",
"background_color": "",
"border_radius": "",
"align_self": "stretch",
"width": "",
"max_width": "",
"height": "",
"min_height": "",
"max_height": "",
"min_width": "",
"box_shadow": "",
"opacity": "",
"content_align": "top",
}
_CONTENT_ALIGN_TO_JUSTIFY_CONTENT = {"center": "center", "bottom": "flex-end"}
# (clé d'attribut, propriété CSS) — chaque paire suit exactement le même
# patron (lire/nettoyer/ajouter si non vide) ; une simple table de
# correspondance ici évite un enchaînement de blocs `if` identiques
# (complexité cognitive réduite, voir _render_simple_properties).
_SIMPLE_PROPERTIES = (
("padding", "padding"),
("margin", "margin"),
("background_color", "background-color"),
("border_radius", "border-radius"),
("width", "width"),
("max_width", "max-width"),
("height", "height"),
("min_height", "min-height"),
("max_height", "max-height"),
("min_width", "min-width"),
("box_shadow", "box-shadow"),
("opacity", "opacity"),
)
def _render_simple_properties(a: dict[str, Any]) -> list[str]:
parts = []
for attr_key, css_prop in _SIMPLE_PROPERTIES:
value = str(a.get(attr_key, "")).strip()
if value:
parts.append(f"{css_prop}:{html_lib.escape(value)};")
return parts
def _render_border(a: dict[str, Any]) -> list[str]:
"""Un côté à `style="none"` (ou absent) ne produit aucune déclaration
pour ce côté, jamais un `border-top:none` explicite."""
parts = []
border = a.get("border") or {}
for side in BORDER_SIDES:
side_border = border.get(side) or {}
style = str(side_border.get("style", "none"))
if style and style != "none":
width = html_lib.escape(str(side_border.get("width", "1px")))
color = html_lib.escape(str(side_border.get("color", "var(--doc-border)")))
parts.append(f"border-{side}:{width} {html_lib.escape(style)} {color};")
return parts
def render_box_style(a: dict[str, Any]) -> str:
"""Construit les déclarations CSS inline communes à plusieurs kinds à
partir des attributs listés dans `_SIMPLE_PROPERTIES` + `border`/
`align_self` de `a` — chaîne vide pour tout attribut absent ou à sa
valeur par défaut (aucun style ajouté, comportement historique
inchangé). `border` est un dict à 4 clés (`BORDER_SIDES`), chacune
`{"style", "width", "color"}`.
- **Retour** : les déclarations CSS (`"propriete:valeur; ..."`),
jamais vide ni `None`.
- **Exceptions** : aucune."""
parts = _render_simple_properties(a) + _render_border(a)
# align-self ne fait quoi que ce soit d'utile QUE si l'élément a par
# ailleurs une taille bornée (max_width/width) — voir la note dans
# element_kind_labels.md — mais reste toujours sûr à poser seul
# ("stretch" est déjà le comportement par défaut d'un enfant flex en
# colonne, donc jamais ajouté explicitement pour ne rien changer).
align_self = str(a.get("align_self", "stretch"))
if align_self and align_self != "stretch":
parts.append(f"align-self:{html_lib.escape(align_self)};")
return " ".join(parts)
def render_content_align(a: dict[str, Any]) -> str:
"""Alignement vertical du CONTENU à l'intérieur de son propre bloc —
utile UNIQUEMENT une fois qu'une hauteur fixe/minimale dépasse la
hauteur naturelle du contenu (retour utilisateur du 26/09/2026 : "je
peux augmenter la hauteur d'un conteneur mais pas l'alignement
vertical à l'intérieur"). Jamais fusionné dans `render_box_style` :
contrairement à `align_self` (position du BLOC dans SON parent, la
même logique convient à tout consommateur), l'alignement du CONTENU
dépend de l'axe interne du conteneur — correct en `justify-content`
pour un conteneur en colonne (texte, liste), mais un bouton
(rangée : icône + texte) gère déjà cet axe autrement (`align-items`,
voir static/document/document-editor.css, .docButton) : chaque
renderer qui veut ce comportement l'appelle donc explicitement lui-
même (voir _render_text/_render_list/_render_image), jamais
automatiquement pour tous les kinds.
- **Retour** : `""` si `content_align` est absent ou `"top"` (défaut,
comportement historique inchangé), sinon la déclaration
`justify-content:...;`.
- **Exceptions** : aucune."""
content_align = str(a.get("content_align", "top"))
justify_content = _CONTENT_ALIGN_TO_JUSTIFY_CONTENT.get(content_align)
return f"justify-content:{justify_content};" if justify_content else ""