diff --git a/document_engine/labels/element_kind_labels.py b/document_engine/labels/element_kind_labels.py index cd615ed2..2ef4e9c1 100644 --- a/document_engine/labels/element_kind_labels.py +++ b/document_engine/labels/element_kind_labels.py @@ -46,6 +46,10 @@ _TEXT_DEFAULTS = { "underline": False, "align": "left", "color": "var(--forge-text)", + # Vide par défaut = pleine largeur de .docPageContent (comportement + # historique) ; une valeur CSS libre (ex. "60ch", "480px") restreint + # la largeur du bloc — voir render_document_element._render_text. + "max_width": "", } diff --git a/document_engine/labels/labels.md b/document_engine/labels/labels.md index 09400db8..52d17227 100644 --- a/document_engine/labels/labels.md +++ b/document_engine/labels/labels.md @@ -39,7 +39,10 @@ l'affichage/debug hors bibliothèque). Attributs posés à la création d'un élément de ce type (voir `document_engine/elements/add_document_element.py`). - **Retour** : un dict d'attributs par défaut, dépendant du `kind` : - texte (`content/style` + `bold/italic/underline/align/color`), + texte (`content/style` + `bold/italic/underline/align/color/max_width` — + `max_width` vide par défaut = pleine largeur de `.docPageContent`, + une valeur CSS libre ex. `"60ch"`/`"480px"` restreint le bloc, voir + `rendering.md`), image (`src/alt/svg_markup` — `svg_markup` prend le pas sur `src` au rendu, voir rendering.md), bouton (`label/target/ attachment_stored_name/attachment_filename` — la pièce jointe est diff --git a/document_engine/rendering/render_document_element.py b/document_engine/rendering/render_document_element.py index 7f606516..c1409b76 100644 --- a/document_engine/rendering/render_document_element.py +++ b/document_engine/rendering/render_document_element.py @@ -59,6 +59,14 @@ def _render_text(el: dict[str, Any], _children_by_parent: dict[int | None, list[ f"font-size:{font_size}; font-weight:{weight}; line-height:{line_height}; " f"font-style:{font_style}; text-decoration:{text_decoration}; text-align:{align}; color:{color};" ) + # max_width optionnel (ex. "60ch", "480px") — vide par défaut (pleine + # largeur de .docPageContent, comportement inchangé). Retour + # utilisateur du 24/09/2026 : un paragraphe doit pouvoir rester plus + # étroit que la page, comme un sous-titre sous un grand titre, sans + # dépendre d'une rangée (qui partagerait la largeur avec un frère). + max_width = str(a.get("max_width", "")).strip() + if max_width: + style += f" max-width:{html_lib.escape(max_width)};" return f'
{content}
' diff --git a/document_engine/rendering/rendering.md b/document_engine/rendering/rendering.md index bd1f740f..32ba88bc 100644 --- a/document_engine/rendering/rendering.md +++ b/document_engine/rendering/rendering.md @@ -30,6 +30,11 @@ regroupement à chaque appel. récursivement. - **Texte** (`titre`/`paragraphe`) : `
` stylé selon `style` (préréglage taille/graisse/interligne) et `bold`/`italic`/`underline`/`align`/`color`. + `max_width` (optionnel, ex. `"60ch"`, `"480px"`) ajoute `max-width` au + style inline quand non vide — pleine largeur de `.docPageContent` par + défaut, retour utilisateur du 24/09/2026 (un paragraphe doit pouvoir + rester plus étroit que la page, sans dépendre d'une rangée qui en + partagerait la largeur avec un frère). - **Image** : ``, ou un bloc placeholder si `src` est vide — OU, si `attributes["svg_markup"]` est non vide (prioritaire sur `src`), un `
` portant directement ce fragment SVG nettoyé par diff --git a/document_engine/themes/securite_incendie_seed.py b/document_engine/themes/securite_incendie_seed.py index b977c79d..250607cc 100644 --- a/document_engine/themes/securite_incendie_seed.py +++ b/document_engine/themes/securite_incendie_seed.py @@ -22,7 +22,8 @@ SECURITE_INCENDIE_SEED_PAGES: list[dict[str, Any]] = [ "content": ( "Reconnaître les risques, réagir dans les 3 premières minutes, protéger les " "personnes autour de vous. Durée : 25 min · à renouveler tous les 24 mois." - ) + ), + "max_width": "60ch", }, }, ], diff --git a/static/document/js/document-editor.js b/static/document/js/document-editor.js index 0da1af14..cb0a5f0a 100644 --- a/static/document/js/document-editor.js +++ b/static/document/js/document-editor.js @@ -1720,12 +1720,14 @@ function forgeDocRenderTextProps(panel, el) { ${['var(--doc-text)', 'var(--doc-muted)', '#ff5f2e', '#ffb020'].map((c) => `
`).join('')}
+
${forgeDocDeleteButtonHtml()} `; function patch(partial) { forgeDocUpdateAttributes(el.id, { ...el.attributes, ...partial }); } document.getElementById('docContentInput').addEventListener('change', (e) => patch({ content: e.target.value })); + document.getElementById('docMaxWidthInput').addEventListener('change', (e) => patch({ max_width: e.target.value.trim() })); panel.querySelectorAll('#docStyleSeg .docSegBtn').forEach((btn) => { btn.addEventListener('click', () => patch({ style: btn.dataset.style })); }); diff --git a/tests/document/test_document_elements.py b/tests/document/test_document_elements.py index d169a79a..4d21b4d1 100644 --- a/tests/document/test_document_elements.py +++ b/tests/document/test_document_elements.py @@ -400,3 +400,36 @@ def test_bouton_with_attachment_filename_renders_data_attribute(tmp_support_slug html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id)) assert 'data-attachment-filename="fiche-consignes.pdf"' in html + + +def test_paragraphe_default_max_width_is_empty(tmp_support_slug_cleanup: Any) -> None: + slug, page_id = _new_support(tmp_support_slug_cleanup) + element_id = doc_engine.add_document_element(slug, "paragraphe", page_id=page_id) + element = doc_engine.get_document_element(slug, element_id) + assert element is not None + assert element["attributes"]["max_width"] == "" + + html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id)) + assert "max-width" not in html + + +def test_paragraphe_with_max_width_adds_it_to_the_inline_style(tmp_support_slug_cleanup: Any) -> None: + slug, page_id = _new_support(tmp_support_slug_cleanup) + element_id = doc_engine.add_document_element(slug, "paragraphe", page_id=page_id) + doc_engine.update_document_element_attributes( + slug, element_id, {"content": "Texte", "style": "paragraphe", "max_width": "60ch"} + ) + + html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id)) + assert "max-width:60ch;" in html + + +def test_paragraphe_max_width_is_escaped(tmp_support_slug_cleanup: Any) -> None: + slug, page_id = _new_support(tmp_support_slug_cleanup) + element_id = doc_engine.add_document_element(slug, "paragraphe", page_id=page_id) + doc_engine.update_document_element_attributes( + slug, element_id, {"content": "Texte", "style": "paragraphe", "max_width": '">'} + ) + + html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id)) + assert "