Files
Forge-Engine/document_engine/rendering/rendering.md
T
williamandClaude Sonnet 5 e7c6ed7159 Ajoute les listes à puces et numérotées dans la bibliothèque de contenu
Retour utilisateur du 24/09/2026 : "dans la section contenu il manque
la possibilité d'utiliser une liste à puce ou ordonnée".

- Deux nouveaux kinds "liste_puces"/"liste_numerotee" dans
  CONTENT_KINDS, chacun sélectionnable directement dans la bibliothèque
  (comme titre/paragraphe/image/bouton) — partagent la même structure
  d'attributs (`items`, une liste de chaînes), c'est le kind lui-même
  qui décide <ul> ou <ol> au rendu (_render_list), pas un attribut
  "ordered" redondant à tenir synchronisé.
- Rendu : un <li> par item, échappé (html.escape) comme tout le
  contenu texte du document — une liste vide rend <ul>/<ol> sans
  enfant plutôt qu'un placeholder (état normal, pas une image sans
  fichier).
- Panneau Propriétés : même patron répéteur que l'Association/Memory
  (ajouter/renommer/supprimer une ligne, rechargé après chaque
  modification).
- Icônes de bibliothèque (☰/①) + style .docList (puces/numéros
  visibles, espacement entre items).

6 nouveaux tests Python (attributs par défaut, rendu <ul>/<ol>,
échappement, liste vide, route d'ajout) + vérifié par un test jsdom
dédié contre un vrai support (bibliothèque, panneau Propriétés :
ajout/modification/suppression d'item réellement fonctionnels).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 07:19:01 +02:00

9.6 KiB

document_engine/rendering/

Rendu HTML du document — point d'entrée unique utilisé à la fois par le canevas d'édition et par le Mode Aperçu (même fonction, voir docs/plan/PLAN.md — "Aperçu réel").

render_document(elements: list[dict[str, Any]]) -> str

Assemble le document entier à partir de la liste à plat renvoyée par document_engine.list_document_elements : regroupe les éléments par parent_id, puis rend récursivement les éléments top-niveau dans l'ordre (une rangée rend elle-même ses propres enfants côte à côte).

  • Retour : le HTML complet du document.
  • Exceptions : aucune.

render_document_element(el: dict[str, Any], children_by_parent: dict[int | None, list[dict[str, Any]]]) -> str

HTML d'un seul élément — dispatch par dict (table _RENDERERS) selon el["kind"], plutôt qu'un enchaînement de if (mirroir de l'esprit de game_engine/scenes/render_scene_object.py). children_by_parent est le regroupement précalculé par render_document, transmis pour que les rangées puissent rendre récursivement leurs enfants sans refaire le regroupement à chaque appel.

  • Retour : le HTML de cet élément (et de ses enfants s'il s'agit d'une rangée).
  • Exceptions : aucune ; un kind inconnu produit un bloc docUnknown visible plutôt qu'une levée d'exception.

Détail des rendus par catégorie (fonctions privées, table de dispatch)

  • Rangée (row) : conteneur flex (gap/align-items/ justify-content réels depuis attributes), enfants rendus récursivement.
  • Texte (titre/paragraphe) : <div> stylé selon style (préréglage taille/graisse/interligne) et bold/italic/underline/align/color.
  • Image : <img>, ou un bloc placeholder si src est vide.
  • Bouton : <button> avec son label et un data-target optionnel.
  • Liste à puces/numérotée (liste_puces/liste_numerotee) : <ul> ou <ol> selon le kind (fonction privée _render_list, partagée par les deux) — un <li> par entrée de attributes["items"]. Une liste vide rend <ul>/<ol> sans enfant plutôt qu'un placeholder : contrairement à une image sans fichier, ce n'est pas un état anormal.
  • Quiz : toujours une carte résumant la config réelle (nombre de questions, total des points via quiz_total_points, minuteur si activé) — sanitizée (sanitize_quiz_config) avant lecture, jamais un rendu direct d'attributes brut. Si au moins une question existe, s'y ajoute (fonction privée _render_quiz_player) le questionnaire RÉEL et interactif affiché en Mode Aperçu (voir docs/plan/maquettes/ document-formation-web.html — référence visuelle), masqué en édition par CSS (.docQuizPlayer, voir static/document/document-editor.css) : les questions sont embarquées en JSON dans un attribut data-quiz-config (jamais un <script> par élément), échappé pour l'HTML (html.escape(..., quote=True)) — static/document/js/document-editor.js lit cet attribut et gère tout le déroulé (réponse/score/question suivante/résultat) côté client, sans aucun aller-retour serveur.
  • Association : toujours une carte résumant la config réelle (nombre de paires) — sanitizée (sanitize_association_config) avant lecture. Si au moins une paire existe, s'y ajoute (fonction privée _render_association_player) le plateau de glisser-déposer RÉEL et interactif affiché en Mode Aperçu, masqué en édition par CSS (.docAssocPlayer) : les deux colonnes (termes/correspondances) sont mélangées INDÉPENDAMMENT (random.shuffle, mélange d'affichage — voir CODE_QUALITY.md) puis embarquées en JSON dans un attribut data-assoc-config, échappé pour l'HTML — même principe que le Quiz, aucun aller-retour serveur pendant qu'on joue. Contrairement au Quiz, le plateau reste affiché en permanence une fois la partie terminée : seul le bouton "Recommencer" (.docMinigameRestartBar, partagé avec Memory) apparaît, jamais d'écran de résultat séparé qui le remplacerait.
  • Memory : toujours une carte résumant la config réelle (nombre de cartes définies, mode paire/simple) — sanitizée (sanitize_memory_config) avant lecture. Si au moins une carte existe, s'y ajoute (fonction privée _render_memory_player) le plateau de retournement RÉEL et interactif : en mode "paire", chaque carte définie est DUPLIQUÉE en deux instances partageant le même card_index (l'appariement se fait dessus, classique Memory) ; en mode "single", une seule instance par carte (simple retournement, sans appariement). Les instances sont mélangées (random.shuffle, mélange d'affichage — voir CODE_QUALITY.md) puis embarquées en JSON dans un attribut data-memory-config, échappé pour l'HTML — même principe que le Quiz/l'Association, aucun aller-retour serveur pendant qu'on joue. Comme l'Association, le plateau reste affiché une fois toutes les paires trouvées (ou toutes les cartes révélées en mode "single") : seul le bouton "Recommencer" (.docMinigameRestartBar) apparaît.
  • Mots mêlés : toujours une carte résumant la config réelle (nombre de mots) — sanitizée (sanitize_mots_config) avant lecture. Si au moins un mot existe, s'y ajoute (fonction privée _render_mots_player) la grille RÉELLE et interactive. La grille ET la position exacte de chaque mot sont calculées ICI côté serveur (_build_mots_grid, jamais recalculées côté client) : chaque mot est placé horizontalement, verticalement, ou en diagonale (haut-gauche→bas-droite ou haut-droite→bas-gauche — jamais à l'envers), les lettres restantes tirées au hasard (random.choice/random.randint, tirage de jeu — voir CODE_QUALITY.md) ; si un mot ne trouve pas sa place, la grille entière est agrandie et le placement retenté depuis zéro, plutôt que d'abandonner silencieusement ce mot. Le tout (grille + coordonnées de chaque mot) est embarqué en JSON dans un attribut data-mots-config, échappé pour l'HTML — même principe que les autres mini-jeux, aucun aller-retour serveur pendant qu'on joue : static/document/js/ document-editor.js compare les coordonnées EXACTES sélectionnées par l'apprenant à celles de chaque mot (jamais une simple comparaison de texte, qui se tromperait sur des lettres partagées entre deux mots qui se croisent). Comme l'Association/Memory, la grille reste affichée une fois tous les mots trouvés : seul le bouton "Recommencer" (.docMinigameRestartBar) apparaît.
  • Scénario : toujours une carte résumant la config réelle (nombre de scénarios) — sanitizée (sanitize_scenario_config) avant lecture. Si au moins un scénario existe, s'y ajoute (fonction privée _render_scenario_player) la mise en situation RÉELLE et interactive : un scénario est un ARBRE DE DÉCISION (voir scenario_config.py pour la forme exacte — nodes[0] = situation initiale, chaque choix pointe vers un autre nœud via target_id, un nœud sans choix est une fin de branche), pas une simple question à une seule conséquence. Aucune notion de bonne/mauvaise réponse (retour utilisateur du 20/09/2026 : "il n'y a pas de notion vrai/faux, l'utilisateur observe les conséquences") : l'apprenant choisit une option, le texte du nœud visé REMPLACE l'affichage du nœud précédent (les boutons de choix disparaissent avec lui), et ainsi de suite jusqu'à une fin de branche — volontairement PAS le comportement du Quiz, où la question resterait affichée à côté d'un encart de feedback séparé. Une fois une fin de branche atteinte, passage au scénario (arbre) suivant. Les scénarios gardent l'ORDRE d'écriture du créateur (jamais mélangés, contrairement à Association/Memory/Mots mêlés — ce sont des mises en situation séquentielles, pas des éléments à faire correspondre/retrouver). Réutilise les classes visuelles du Quiz (.docQuizOptions/ .docQuizNextBar/.docQuizQuestionText) plutôt que de dupliquer ces règles. L'arbre complet (tous les nœuds/choix de tous les scénarios) est embarqué en JSON dans un attribut data-scenario-config, échappé pour l'HTML — même principe que les autres mini-jeux, aucun aller-retour serveur pendant qu'on joue : toute la navigation dans l'arbre se fait côté client. Comme l'Association/Memory/Mots mêlés (mais contrairement au Quiz), le dernier scénario reste affiché une fois une fin de branche atteinte : seul le bouton "Recommencer" (.docMinigameRestartBar) apparaît. L'arbre lui-même se construit dans une MODALE dédiée depuis le panneau Propriétés (voir forgeDocOpenScenarioTreeModal, static/document/js/document-editor.js) — trop de structure (nœuds + choix + destinations) pour la colonne étroite du panneau Propriétés, contrairement aux autres mini-jeux. La modale est un VRAI graphe visuel (retour utilisateur du 21/09/2026) : chaque nœud a une position x/y (voir scenario_config.py), affiché comme une carte déplaçable à la souris sur un canevas ; chaque choix relié à un target_id est dessiné comme une flèche SVG étiquetée par son texte, jamais un simple menu déroulant. Éditer le texte/les choix d'un nœud se fait dans l'inspecteur (panneau de droite) du nœud sélectionné ; relier un choix se fait en cliquant "Relier" puis le nœud cible sur le graphe (mode connexion, Échap annule).
  • Autres mini-jeux (zones) : carte placeholder portant le libellé du type (voir document_engine/labels/element_kind_labels.py) — emplacement réservé, formulaire de contenu dédié hors périmètre de cette passe.