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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8f879f6321
commit
4db1269348
@@ -1,9 +1,10 @@
|
||||
import html as html_lib
|
||||
import json
|
||||
import random
|
||||
import urllib.parse
|
||||
from typing import Any
|
||||
|
||||
from .box_style import render_box_style
|
||||
from .box_style import render_box_style, render_content_align
|
||||
from .sanitize_svg_markup import sanitize_svg_markup
|
||||
|
||||
|
||||
@@ -102,6 +103,9 @@ def _render_text(el: dict[str, Any], _children_by_parent: dict[int | None, list[
|
||||
box_style = render_box_style(a)
|
||||
if box_style:
|
||||
style += f" {box_style}"
|
||||
content_align = render_content_align(a)
|
||||
if content_align:
|
||||
style += f" {content_align}"
|
||||
|
||||
return f'<div class="docText" data-element-id="{el["id"]}" data-kind="{el["kind"]}" style="{style}">{content}</div>'
|
||||
|
||||
@@ -199,7 +203,15 @@ def _render_image(el: dict[str, Any], _children_by_parent: dict[int | None, list
|
||||
media = f'<div class="docImageLightboxTrigger"{wrapper_style_attr}>{media}</div>'
|
||||
|
||||
if caption:
|
||||
figure_style_attr = f' style="{box_style}"' if box_style else ""
|
||||
# render_content_align (retour utilisateur du 26/09/2026 :
|
||||
# "je peux augmenter la hauteur d'un conteneur mais pas
|
||||
# l'alignement vertical à l'intérieur") n'a de sens ici QUE pour
|
||||
# la figure (conteneur flex-colonne à plusieurs enfants réels —
|
||||
# image + légende) : jamais sur l'<img> seul ni sur les
|
||||
# enveloppes lien/plein écran, qui ne sont pas des conteneurs
|
||||
# flex-colonne à plusieurs enfants.
|
||||
figure_style = " ".join(p for p in (box_style, render_content_align(a)) if p)
|
||||
figure_style_attr = f' style="{figure_style}"' if figure_style else ""
|
||||
media = (
|
||||
f'<figure class="docImageFigure"{figure_style_attr}>{media}'
|
||||
f'<figcaption class="docImageCaption">{html_lib.escape(caption)}</figcaption></figure>'
|
||||
@@ -208,6 +220,101 @@ def _render_image(el: dict[str, Any], _children_by_parent: dict[int | None, list
|
||||
return media
|
||||
|
||||
|
||||
_LIST_STYLE_TYPES = {
|
||||
"liste_puces": ("disc", "circle", "square", "none"),
|
||||
"liste_numerotee": (
|
||||
"decimal",
|
||||
"decimal-leading-zero",
|
||||
"lower-roman",
|
||||
"upper-roman",
|
||||
"lower-alpha",
|
||||
"upper-alpha",
|
||||
"none",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _list_text_style(a: dict[str, Any]) -> list[str]:
|
||||
"""Typographie de la liste ENTIÈRE (jamais par élément individuel,
|
||||
voir element_kind_labels.py — portée actée avec l'utilisateur)."""
|
||||
parts = []
|
||||
if a.get("bold"):
|
||||
parts.append("font-weight:700;")
|
||||
if a.get("italic"):
|
||||
parts.append("font-style:italic;")
|
||||
if a.get("underline"):
|
||||
parts.append("text-decoration:underline;")
|
||||
font_family = str(a.get("font_family", "")).strip()
|
||||
if font_family:
|
||||
parts.append(f"font-family:{html_lib.escape(font_family)};")
|
||||
font_size = str(a.get("font_size", "")).strip()
|
||||
if font_size:
|
||||
parts.append(f"font-size:{html_lib.escape(font_size)};")
|
||||
line_height = str(a.get("line_height", "")).strip()
|
||||
if line_height:
|
||||
parts.append(f"line-height:{html_lib.escape(line_height)};")
|
||||
text_color = str(a.get("text_color", "")).strip()
|
||||
if text_color:
|
||||
parts.append(f"color:{html_lib.escape(text_color)};")
|
||||
return parts
|
||||
|
||||
|
||||
def _list_marker_style(a: dict[str, Any], kind: str) -> list[str]:
|
||||
"""Puces/numéros — `marker_color`/`marker_size` passent par des
|
||||
PROPRIÉTÉS PERSONNALISÉES CSS (héritées jusqu'au pseudo-élément
|
||||
`::marker` de chaque <li>, voir .docList li::marker dans
|
||||
document-editor.css) : un style inline posé sur le <ul>/<ol> ne peut
|
||||
pas cibler directement le `::marker` de ses enfants autrement."""
|
||||
parts = []
|
||||
list_style_type = str(a.get("list_style_type", ""))
|
||||
if list_style_type in _LIST_STYLE_TYPES.get(kind, ()):
|
||||
parts.append(f"list-style-type:{list_style_type};")
|
||||
position_inside = str(a.get("list_style_position", "outside")) == "inside"
|
||||
if position_inside:
|
||||
parts.append("list-style-position:inside;")
|
||||
# Retour utilisateur du 26/09/2026 : "si j'enlève les puces ou que les
|
||||
# puces se mettent à l'intérieur, il reste un espace devant la liste,
|
||||
# cet espace doit être supprimé" — le padding-left:1.4em par défaut
|
||||
# (document-editor.css, .docList) réserve la place d'une puce
|
||||
# EXTÉRIEURE ; il n'a plus lieu d'être dès que la puce n'est plus là
|
||||
# ("none") ou qu'elle rejoint le flux du texte ("inside").
|
||||
if list_style_type == "none" or position_inside:
|
||||
parts.append("padding-left:0;")
|
||||
marker_color = str(a.get("marker_color", "")).strip()
|
||||
if marker_color:
|
||||
parts.append(f"--doc-marker-color:{html_lib.escape(marker_color)};")
|
||||
marker_size = str(a.get("marker_size", "")).strip()
|
||||
if marker_size:
|
||||
parts.append(f"--doc-marker-size:{html_lib.escape(marker_size)};")
|
||||
# Puce personnalisée (image SVG) : liste à puces UNIQUEMENT, une
|
||||
# puce imagée n'a pas de sens sur une liste numérotée. list-style-image
|
||||
# prime visuellement sur list-style-type dès qu'il est posé (aucun
|
||||
# conflit à gérer entre les deux).
|
||||
svg_markup = str(a.get("svg_markup", "")).strip() if kind == "liste_puces" else ""
|
||||
if svg_markup:
|
||||
sanitized = sanitize_svg_markup(svg_markup)
|
||||
encoded = urllib.parse.quote(sanitized)
|
||||
parts.append(f'list-style-image:url("data:image/svg+xml,{encoded}");')
|
||||
return parts
|
||||
|
||||
|
||||
def _list_item_style(a: dict[str, Any]) -> list[str]:
|
||||
"""`item_padding`/`item_spacing` s'appliquent à CHAQUE <li>, jamais au
|
||||
conteneur <ul>/<ol> lui-même — même mécanisme de propriété
|
||||
personnalisée CSS héritée que `_list_marker_style` ci-dessus (voir
|
||||
.docList li dans document-editor.css). Une valeur UNIFORME partagée
|
||||
par tous les éléments (retour utilisateur du 26/09/2026), jamais
|
||||
réglable par élément individuel."""
|
||||
parts = []
|
||||
item_padding = str(a.get("item_padding", "")).strip()
|
||||
if item_padding:
|
||||
parts.append(f"--doc-item-padding:{html_lib.escape(item_padding)};")
|
||||
item_spacing = str(a.get("item_spacing", "")).strip()
|
||||
if item_spacing:
|
||||
parts.append(f"--doc-item-spacing:{html_lib.escape(item_spacing)};")
|
||||
return parts
|
||||
|
||||
|
||||
def _render_list(el: dict[str, Any], _children_by_parent: dict[int | None, list[dict[str, Any]]]) -> str:
|
||||
"""Liste à puces (<ul>) ou numérotée (<ol>) — le kind lui-même décide
|
||||
la balise, pas un attribut "ordered" séparé (voir
|
||||
@@ -216,10 +323,22 @@ def _render_list(el: dict[str, Any], _children_by_parent: dict[int | None, list[
|
||||
image sans fichier, une liste sans élément n'a rien d'anormal à
|
||||
afficher (le créateur vient peut-être de tout supprimer avant d'en
|
||||
retaper un)."""
|
||||
items = el["attributes"].get("items", [])
|
||||
tag = "ol" if el["kind"] == "liste_numerotee" else "ul"
|
||||
a = el["attributes"]
|
||||
kind = el["kind"]
|
||||
items = a.get("items", [])
|
||||
tag = "ol" if kind == "liste_numerotee" else "ul"
|
||||
items_html = "".join(f"<li>{html_lib.escape(str(item))}</li>" for item in items)
|
||||
return f'<{tag} class="docList" data-element-id="{el["id"]}" data-kind="{el["kind"]}">{items_html}</{tag}>'
|
||||
|
||||
style = " ".join(
|
||||
_list_text_style(a)
|
||||
+ _list_marker_style(a, kind)
|
||||
+ _list_item_style(a)
|
||||
+ [render_box_style(a), render_content_align(a)]
|
||||
)
|
||||
style = style.strip()
|
||||
style_attr = f' style="{style}"' if style else ""
|
||||
|
||||
return f'<{tag} class="docList" data-element-id="{el["id"]}" data-kind="{kind}"{style_attr}>{items_html}</{tag}>'
|
||||
|
||||
|
||||
_BUTTON_TEXT_TRANSFORMS = ("uppercase", "lowercase", "capitalize")
|
||||
|
||||
Reference in New Issue
Block a user