From a34bcf4159c27a795d765cc60369414a67b73dca Mon Sep 17 00:00:00 2001 From: william Date: Thu, 24 Sep 2026 08:12:29 +0200 Subject: [PATCH] =?UTF-8?q?Ajoute=204=20m=C3=A9canismes=20moteur=20manquan?= =?UTF-8?q?ts=20pour=20le=20th=C3=A8me=20s=C3=A9curit=C3=A9=20incendie=20:?= =?UTF-8?q?=20=C3=A9tiquette,=20carte,=20image=20SVG=20inline,=20bouton=20?= =?UTF-8?q?avec=20pi=C3=A8ce=20jointe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contenu et mécanisme uniquement, aucun style ajouté (voir consigne du 24/09/2026) : deux nouveaux kinds de contenu (badge/carte, rendu en div brutes sans CSS), un mode SVG inline pour l'image (svg_markup, nettoyé par un nouveau sanitizer allow-list avant chaque rendu) et un fichier téléchargeable joignable à un bouton (upload/download routes, stockage sous db.support_dir). Le futur système de templates portera l'habillage visuel de ces éléments. Co-Authored-By: Claude Sonnet 5 --- db/__init__.py | 2 + document_engine/__init__.py | 2 + document_engine/labels/element_kind_labels.py | 25 +++- document_engine/labels/labels.md | 32 +++-- .../rendering/render_document_element.py | 45 +++++- document_engine/rendering/rendering.md | 40 +++++- .../rendering/sanitize_svg_markup.py | 129 ++++++++++++++++++ routes/document/__init__.py | 2 + .../document_element_download_attachment.py | 30 ++++ .../document_element_upload_attachment.py | 54 ++++++++ static/document/document-editor.css | 2 + static/document/js/document-editor.js | 74 ++++++++++ tests/document/test_document_elements.py | 122 +++++++++++++++++ tests/document/test_document_routes.py | 56 ++++++++ tests/document/test_sanitize_svg_markup.py | 71 ++++++++++ 15 files changed, 668 insertions(+), 18 deletions(-) create mode 100644 document_engine/rendering/sanitize_svg_markup.py create mode 100644 routes/document/document_element_download_attachment.py create mode 100644 routes/document/document_element_upload_attachment.py create mode 100644 tests/document/test_sanitize_svg_markup.py diff --git a/db/__init__.py b/db/__init__.py index 5ba194e4..5a4d31a6 100644 --- a/db/__init__.py +++ b/db/__init__.py @@ -79,6 +79,7 @@ from .slugify import slugify from .supports.create_support import create_support from .supports.delete_support import delete_support from .supports.list_supports import list_supports +from .supports.support_dir import support_dir from .supports.support_meta import support_meta from .table_name_for import table_name_for @@ -115,6 +116,7 @@ __all__ = [ "set_scorm_version", "create_support", "list_supports", + "support_dir", "support_meta", "delete_support", "ONBOARDING_TYPES", diff --git a/document_engine/__init__.py b/document_engine/__init__.py index e4521c2d..59dc1b74 100644 --- a/document_engine/__init__.py +++ b/document_engine/__init__.py @@ -77,6 +77,7 @@ from .pages.list_document_pages import list_document_pages from .pages.move_document_page import move_document_page from .pages.update_document_page import update_document_page from .rendering.render_document_element import render_document, render_document_element +from .rendering.sanitize_svg_markup import sanitize_svg_markup __all__ = [ "CARD_MODES", @@ -121,6 +122,7 @@ __all__ = [ "sanitize_mots_config", "sanitize_quiz_config", "sanitize_scenario_config", + "sanitize_svg_markup", "update_document_element_attributes", "update_document_page", ] diff --git a/document_engine/labels/element_kind_labels.py b/document_engine/labels/element_kind_labels.py index 2b4db007..cd615ed2 100644 --- a/document_engine/labels/element_kind_labels.py +++ b/document_engine/labels/element_kind_labels.py @@ -11,7 +11,7 @@ from .mots_config import DEFAULT_MOTS_CONFIG from .quiz_config import DEFAULT_QUIZ_CONFIG from .scenario_config import DEFAULT_SCENARIO_CONFIG -CONTENT_KINDS = ("titre", "paragraphe", "image", "bouton", "liste_puces", "liste_numerotee") +CONTENT_KINDS = ("titre", "paragraphe", "image", "bouton", "liste_puces", "liste_numerotee", "badge", "carte") MINIGAME_KINDS = ("quiz", "association", "memory", "mots", "scenario", "zones") # "row" n'apparaît jamais dans la bibliothèque (créé implicitement par le @@ -30,6 +30,8 @@ ELEMENT_KIND_LABELS: dict[str, str] = { "bouton": "Bouton", "liste_puces": "Liste à puces", "liste_numerotee": "Liste numérotée", + "badge": "Étiquette", + "carte": "Carte", "quiz": "Quiz", "association": "Association", "memory": "Memory", @@ -56,15 +58,32 @@ def element_default_attributes(kind: str) -> dict[str, Any]: if kind == "paragraphe": return {"content": "Nouveau paragraphe de texte.", "style": "paragraphe", **_TEXT_DEFAULTS} if kind == "image": - return {"src": "", "alt": ""} + # svg_markup (optionnel) prend le pas sur src au rendu (voir + # render_document_element._render_image) — un contenu vectoriel + # dessiné/collé directement plutôt qu'un fichier hébergé. + return {"src": "", "alt": "", "svg_markup": ""} if kind == "bouton": - return {"label": "Bouton", "target": ""} + # attachment_stored_name/attachment_filename (optionnels) : un + # fichier téléchargeable joint au bouton (voir routes/document/ + # document_element_upload_attachment.py), indépendant de `target` + # 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": ""} 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 # rendu (voir render_document_element._render_list), jamais un # attribut "ordered" redondant à tenir synchronisé avec le kind. return {"items": ["Premier élément", "Deuxième élément"]} + if kind == "badge": + return {"content": "Étiquette"} + if kind == "carte": + # Contenu pur (lettre/repère court + titre + description) — sans + # aucun choix de couleur/forme, laissé au futur système de + # templates (voir consigne du 24/09/2026 : moteur = contenu et + # mécanisme uniquement, jamais de style). + return {"label": "A", "title": "Titre de la carte", "description": "Description de la carte."} if kind == "row": return {"gap": 16, "align": "stretch", "justify": "flex-start"} if kind == "quiz": diff --git a/document_engine/labels/labels.md b/document_engine/labels/labels.md index 2ccd7bbe..09400db8 100644 --- a/document_engine/labels/labels.md +++ b/document_engine/labels/labels.md @@ -6,12 +6,17 @@ attributs par défaut posés à la création de chaque type. ## `CONTENT_KINDS: tuple[str, ...]` `("titre", "paragraphe", "image", "bouton", "liste_puces", -"liste_numerotee")` — éléments du flux, peuvent être top-niveau ou -enfants d'une rangée. `"liste_puces"`/`"liste_numerotee"` partagent la -même structure d'attributs (`items`) ; c'est le `kind` lui-même qui -décide `