Audit complet de mise en forme — Bouton (3e élément du tableau)
Implémente toutes les options manquantes identifiées pour l'élément Bouton : typographie (gras/italique/transformation/police/taille/ espacement des lettres/couleur), icône SVG optionnelle avec position (avant/après le texte) et taille réglables — au-delà de l'Étiquette, qui n'a qu'une icône fixe —, tous les attributs de boîte partagés déjà créés pour Titre/Paragraphe/Image, et un effet visuel universel au survol/clic. Deux ajouts transversaux demandés pendant le test : un réglage "les 4 côtés de la bordure à la fois" dans le module de boîte partagé (bénéficie automatiquement à tous les éléments qui l'utilisent), et le glisser-déposer d'un élément du canevas vers une autre page via le panneau Pages (le mécanisme serveur existait déjà pour la pagination automatique, seule l'interaction manuelle manquait). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
9ad50c58b8
commit
8f879f6321
@@ -131,7 +131,35 @@ def element_default_attributes(kind: str) -> dict[str, Any]:
|
||||
# qui reste réservé à la navigation (URL/ancre). Jamais les deux
|
||||
# à la fois côté UI (voir document-editor.js), mais rien ne
|
||||
# l'empêche structurellement ici.
|
||||
return {"label": "Bouton", "target": "", "attachment_stored_name": "", "attachment_filename": ""}
|
||||
#
|
||||
# Audit du 26/09/2026 (réglages manquants — colonne "Bouton") :
|
||||
# bold/italic/text_transform/font_family/font_size/letter_spacing/
|
||||
# text_color (typo, jamais gérés par box_style.py) + svg_markup/
|
||||
# icon_position/icon_size (icône avant/après le texte, absente
|
||||
# même de l'étiquette qui n'a qu'un SVG fixe sans position ni
|
||||
# taille réglables) + les attributs de boîte partagés. bold=False
|
||||
# PAR DÉFAUT (le CSS de base garde son font-weight:700 tel quel
|
||||
# tant que "bold" n'est pas explicitement activé — voir
|
||||
# _render_button, qui ne pousse à 800 QUE si bold=True — aucune
|
||||
# régression visuelle sur les boutons déjà créés).
|
||||
return {
|
||||
"label": "Bouton",
|
||||
"target": "",
|
||||
"attachment_stored_name": "",
|
||||
"attachment_filename": "",
|
||||
"bold": False,
|
||||
"italic": False,
|
||||
"text_transform": "none",
|
||||
"font_family": "",
|
||||
"font_size": "",
|
||||
"letter_spacing": "",
|
||||
"text_color": "",
|
||||
"svg_markup": "",
|
||||
"icon_position": "before",
|
||||
"icon_size": "",
|
||||
**BOX_DEFAULTS,
|
||||
"border": default_border(),
|
||||
}
|
||||
if kind in ("liste_puces", "liste_numerotee"):
|
||||
# Une seule et même structure d'attributs pour les deux kinds —
|
||||
# "ordonnée ou non" se lit directement sur le kind au moment du
|
||||
|
||||
@@ -65,7 +65,11 @@ Attributs posés à la création d'un élément de ce type (voir
|
||||
`link_url` + `lazy_load` + `caption` : comportement/contenu, pas du
|
||||
style + les attributs de boîte partagés), bouton (`label/target/
|
||||
attachment_stored_name/attachment_filename` — la pièce jointe est
|
||||
indépendante de `target`, réservé à la navigation), liste à
|
||||
indépendante de `target`, réservé à la navigation — + `bold/italic/
|
||||
text_transform/font_family/font_size/letter_spacing/text_color`
|
||||
(typo, jamais gérés par box_style.py) + `svg_markup/icon_position
|
||||
("before"/"after")/icon_size` (icône optionnelle, voir rendering.md)
|
||||
+ les attributs de boîte partagés), liste à
|
||||
puces/numérotée (`items`, une liste de chaînes), badge
|
||||
(`content/svg_markup/width/border_radius/bold/uppercase` — tous vides
|
||||
ou `False` par défaut = comportement historique inchangé, voir
|
||||
|
||||
@@ -222,6 +222,51 @@ def _render_list(el: dict[str, Any], _children_by_parent: dict[int | None, list[
|
||||
return f'<{tag} class="docList" data-element-id="{el["id"]}" data-kind="{el["kind"]}">{items_html}</{tag}>'
|
||||
|
||||
|
||||
_BUTTON_TEXT_TRANSFORMS = ("uppercase", "lowercase", "capitalize")
|
||||
|
||||
|
||||
def _button_text_style(a: dict[str, Any]) -> list[str]:
|
||||
"""Déclarations de typographie propres au bouton (jamais dans
|
||||
box_style.py, partagé avec d'autres kinds qui n'ont pas tous une
|
||||
notion de texte)."""
|
||||
parts = []
|
||||
if a.get("bold"):
|
||||
# 800 (jamais 700, déjà le poids par défaut du CSS de base) :
|
||||
# "gras" ne fait que RENFORCER le poids existant, jamais
|
||||
# l'affaiblir — aucun bouton déjà créé ne change d'apparence tant
|
||||
# que cette case n'est pas cochée explicitement.
|
||||
parts.append("font-weight:800;")
|
||||
if a.get("italic"):
|
||||
parts.append("font-style:italic;")
|
||||
text_transform = str(a.get("text_transform", "none"))
|
||||
if text_transform in _BUTTON_TEXT_TRANSFORMS:
|
||||
parts.append(f"text-transform:{text_transform};")
|
||||
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)};")
|
||||
letter_spacing = str(a.get("letter_spacing", "")).strip()
|
||||
if letter_spacing:
|
||||
parts.append(f"letter-spacing:{html_lib.escape(letter_spacing)};")
|
||||
text_color = str(a.get("text_color", "")).strip()
|
||||
if text_color:
|
||||
parts.append(f"color:{html_lib.escape(text_color)};")
|
||||
return parts
|
||||
|
||||
|
||||
def _button_icon_html(a: dict[str, Any]) -> str:
|
||||
svg_markup = str(a.get("svg_markup", "")).strip()
|
||||
if not svg_markup:
|
||||
return ""
|
||||
icon_size = str(a.get("icon_size", "")).strip()
|
||||
size_style = (
|
||||
f' style="width:{html_lib.escape(icon_size)}; height:{html_lib.escape(icon_size)};"' if icon_size else ""
|
||||
)
|
||||
return f'<span class="docButtonIcon"{size_style}>{sanitize_svg_markup(svg_markup)}</span>'
|
||||
|
||||
|
||||
def _render_button(el: dict[str, Any], _children_by_parent: dict[int | None, list[dict[str, Any]]]) -> str:
|
||||
a = el["attributes"]
|
||||
label = html_lib.escape(str(a.get("label", "Bouton")))
|
||||
@@ -236,9 +281,19 @@ def _render_button(el: dict[str, Any], _children_by_parent: dict[int | None, lis
|
||||
# que le reste des appels AJAX de l'éditeur.
|
||||
attachment_filename = html_lib.escape(str(a.get("attachment_filename", "")))
|
||||
attachment_attr = f' data-attachment-filename="{attachment_filename}"' if attachment_filename else ""
|
||||
|
||||
style = " ".join(_button_text_style(a) + [render_box_style(a)]).strip()
|
||||
style_attr = f' style="{style}"' if style else ""
|
||||
|
||||
icon_html = _button_icon_html(a)
|
||||
label_span = f'<span class="docButtonLabel">{label}</span>'
|
||||
inner = (
|
||||
f"{label_span}{icon_html}" if str(a.get("icon_position", "before")) == "after" else f"{icon_html}{label_span}"
|
||||
)
|
||||
|
||||
return (
|
||||
f'<button type="button" class="docButton" data-element-id="{el["id"]}" '
|
||||
f'data-kind="bouton"{target_attr}{attachment_attr}>{label}</button>'
|
||||
f'data-kind="bouton"{target_attr}{attachment_attr}{style_attr}>{inner}</button>'
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -67,14 +67,31 @@ regroupement à chaque appel.
|
||||
forgeDocOpenImageLightbox), même principe que les mini-jeux et la
|
||||
pièce jointe d'un bouton. `caption` (non vide) enveloppe le tout dans
|
||||
un `<figure><figcaption>` échappée.
|
||||
- **Bouton** : `<button>` avec son `label`, un `data-target` optionnel
|
||||
- **Bouton** : `<button>` avec son `label` (enveloppé dans
|
||||
`<span class="docButtonLabel">`), un `data-target` optionnel
|
||||
(navigation) et un `data-attachment-filename` optionnel — marqueur
|
||||
mécanique posé quand un fichier a été joint (voir
|
||||
`routes/document/document_element_upload_attachment.py`), jamais
|
||||
l'URL de téléchargement elle-même (ce renderer ne connaît pas le slug
|
||||
du support ; `static/document/js/document-editor.js` l'assemble à
|
||||
partir de `data-element-id` + `FORGE_DOCUMENT.slug`, même principe que
|
||||
le reste des appels AJAX de l'éditeur).
|
||||
le reste des appels AJAX de l'éditeur). Style inline (audit du
|
||||
26/09/2026, réglages manquants — colonne "Bouton") : `bold` pousse
|
||||
`font-weight` à `800` (jamais en dessous du `700` déjà posé par le CSS
|
||||
de base — "gras" ne fait que renforcer, jamais affaiblir, aucune
|
||||
régression visuelle sur les boutons déjà créés), `italic`/
|
||||
`text_transform` (`"uppercase"`/`"lowercase"`/`"capitalize"`, jamais
|
||||
`"none"`)/`font_family`/`font_size`/`letter_spacing`/`text_color`
|
||||
(vides par défaut) + les attributs de boîte partagés
|
||||
(`render_box_style`, voir `box_style.py`). `svg_markup` (optionnel,
|
||||
nettoyé par `sanitize_svg_markup`) ajoute un `<span
|
||||
class="docButtonIcon">` avant OU après `.docButtonLabel` selon
|
||||
`icon_position` (`"before"` par défaut), dimensionné par `icon_size`
|
||||
(vide = `1em`, suit la taille du texte). États interactifs
|
||||
survol/actif : effet CSS universel (`filter`/`transform`, voir
|
||||
`static/document/document-editor.css`), jamais configurable par
|
||||
attribut — pas de notion d'état "désactivé" pour un bouton de contenu
|
||||
(ce n'est pas un vrai contrôle de formulaire).
|
||||
- **Étiquette** (`badge`) : `<div>` portant `attributes["content"]`
|
||||
échappé, précédé d'un `<span class="docBadgeIcon">` optionnel si
|
||||
`svg_markup` est non vide (nettoyé par `sanitize_svg_markup`, même
|
||||
|
||||
Reference in New Issue
Block a user