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>
299 lines
14 KiB
HTML
299 lines
14 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Forge Engine — {{ support.name }}{% endblock %}
|
||
{% block body_class %}objectEditBody{% endblock %}
|
||
{% block content_class %}{% endblock %}
|
||
{% block extra_head %}
|
||
<link rel="stylesheet"
|
||
href="{{ url_for('static', filename='document/document-editor.css') }}">
|
||
{% if active_theme %}
|
||
{% if active_theme.font_url %}
|
||
<link rel="stylesheet" href="{{ active_theme.font_url }}">
|
||
{% endif %}
|
||
<link rel="stylesheet"
|
||
href="{{ url_for('static', filename=active_theme.css_path) }}">
|
||
{% endif %}
|
||
{% endblock %}
|
||
{% block content %}
|
||
<div class="docEditor3" id="docEditor3" data-slug="{{ support.slug }}">
|
||
<div class="docTopbar">
|
||
<div class="docCrumb">
|
||
<a href="{{ url_for('index') }}" class="docCrumbDim">Mes formations</a>
|
||
<svg width="14"
|
||
height="14"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="2"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<path d="M9 6l6 6-6 6" />
|
||
</svg>
|
||
<span class="docCrumbCur">{{ support.name }}</span>
|
||
</div>
|
||
<div class="docTopbarActions">
|
||
<button type="button"
|
||
class="docIconBtn"
|
||
id="docUndoBtn"
|
||
aria-label="Annuler"
|
||
disabled
|
||
title="Annuler (Ctrl+Z)">
|
||
<svg width="16"
|
||
height="16"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.8"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<path d="M8 6L4 10l4 4" />
|
||
<path d="M4 10h9a6 6 0 1 1-4.2 10.2" />
|
||
</svg>
|
||
</button>
|
||
<button type="button"
|
||
class="docIconBtn"
|
||
id="docRedoBtn"
|
||
aria-label="Rétablir"
|
||
disabled
|
||
title="Rétablir (Ctrl+Y)">
|
||
<svg width="16"
|
||
height="16"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.8"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<path d="M16 6l4 4-4 4" />
|
||
<path d="M20 10h-9a6 6 0 1 0 4.2 10.2" />
|
||
</svg>
|
||
</button>
|
||
<div class="docDividerV"></div>
|
||
<button type="button"
|
||
class="docBtnSecondary docBtnPrimary"
|
||
id="docPreviewBtn">
|
||
<svg width="16"
|
||
height="16"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.8"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z" />
|
||
<circle cx="12" cy="12" r="3" />
|
||
</svg><span class="docBtnLabel">Aperçu</span>
|
||
</button>
|
||
<button type="button"
|
||
class="docBtnSecondary"
|
||
id="docTemplateBtn">
|
||
<svg width="16"
|
||
height="16"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.8"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<rect x="3" y="3" width="7" height="7" rx="1" />
|
||
<rect x="14" y="3" width="7" height="7" rx="1" />
|
||
<rect x="3" y="14" width="7" height="7" rx="1" />
|
||
<rect x="14" y="14" width="7" height="7" rx="1" />
|
||
</svg><span class="docBtnLabel">Utiliser un modèle</span>
|
||
</button>
|
||
<div class="docDividerV"></div>
|
||
<div class="docWidthPicker"
|
||
id="docWidthPicker"
|
||
role="group"
|
||
aria-label="Largeur d'aperçu">
|
||
<button type="button" class="docWidthBtn is-active" data-width="">Bureau</button>
|
||
<button type="button" class="docWidthBtn" data-width="768">Tablette</button>
|
||
<button type="button" class="docWidthBtn" data-width="375">Mobile</button>
|
||
</div>
|
||
<div class="docDividerV"></div>
|
||
<button type="button"
|
||
class="docIconBtn docThemeToggle"
|
||
id="docThemeToggle"
|
||
aria-label="Changer de thème"></button>
|
||
</div>
|
||
</div>
|
||
<div class="docBodyWrap">
|
||
<div class="docSidebarClose" id="docCloseLeft">← Retour au document</div>
|
||
<div class="docSidebar docSidebarLeft" id="docSidebarLeft">
|
||
{# Panneau gauche à onglets (retour utilisateur du 23/09/2026 :
|
||
"il dois avoir deux onglets") : "Mise en page" (bibliothèque
|
||
d'éléments, contenu inchangé) actif par défaut — le panneau
|
||
"Pages" (gestion complète des pages : liste/réordonner/ajouter/
|
||
supprimer/renommer) est un espace dédié plein-écran plutôt que
|
||
de se partager la hauteur avec la bibliothèque comme les
|
||
itérations précédentes (bande de vignettes, puis carrousel). #}
|
||
<div class="docSidebarTabs" role="tablist">
|
||
<button type="button"
|
||
class="docSidebarTab"
|
||
id="docTabPages"
|
||
role="tab"
|
||
aria-selected="false"
|
||
aria-controls="docTabPanelPages"
|
||
data-tab="pages">Pages</button>
|
||
<button type="button"
|
||
class="docSidebarTab is-active"
|
||
id="docTabLayout"
|
||
role="tab"
|
||
aria-selected="true"
|
||
aria-controls="docTabPanelLayout"
|
||
data-tab="layout">Mise en page</button>
|
||
</div>
|
||
<div class="docSidebarTabPanel"
|
||
id="docTabPanelPages"
|
||
role="tabpanel"
|
||
aria-labelledby="docTabPages"
|
||
hidden>
|
||
{# Bouton d'ajout EN HAUT, toujours visible (retour utilisateur du
|
||
23/09/2026 : "le bouton pour ajouter une page dois être en haut
|
||
toujours visible" — avec beaucoup de pages, il disparaissait en
|
||
bas de la liste défilante). #}
|
||
<button type="button" class="docBtnSecondary docPageManagerAdd" id="docPageManagerAdd">+ Ajouter une page</button>
|
||
<button type="button"
|
||
class="docDeleteBtn docPageManagerDeleteAll"
|
||
id="docPageManagerDeleteAll"
|
||
{{ 'disabled' if not pages else '' }}>🗑 Supprimer toutes les pages</button>
|
||
<div class="docPageManagerList" id="docPageManagerList">
|
||
{% for page in pages %}
|
||
<div class="docPageRow {{ 'is-active' if page.id == active_page.id else '' }}"
|
||
role="button"
|
||
tabindex="0"
|
||
draggable="true"
|
||
data-page-id="{{ page.id }}">
|
||
{# //NOSONAR Web:S6819,Web:MouseEventWithoutKeyboardEquivalentCheck - div+role=button
|
||
volontaire : la rangée contient de vrais <button> d'action (renommer/supprimer),
|
||
qu'un <button> englobant ne pourrait pas contenir validement. L'équivalent clavier
|
||
(Entrée/Espace) est posé côté JS (forgeDocRenderPageManagerList), donc le finding
|
||
clavier de Sonar est un faux positif : l'analyseur statique ne voit pas les
|
||
addEventListener dynamiques. #}
|
||
<span class="docPageRowHandle" aria-hidden="true">⠿</span>
|
||
<span class="docPageRowNumber">{{ loop.index }}</span>
|
||
<span class="docPageRowTitle"><span class="docPageRowTitleText">{{ page.title }}</span></span>
|
||
<span class="docPageRowActions">
|
||
<button type="button"
|
||
class="docPageRowRename"
|
||
data-page-id="{{ page.id }}"
|
||
aria-label="Renommer cette page"
|
||
title="Renommer cette page">✎</button>
|
||
<button type="button"
|
||
class="docPageRowDelete"
|
||
data-page-id="{{ page.id }}"
|
||
aria-label="Supprimer cette page"
|
||
title="Supprimer cette page">✕</button>
|
||
</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<div class="docSidebarTabPanel" id="docTabPanelLayout" role="tabpanel" aria-labelledby="docTabLayout">
|
||
{% for category_key, category in element_library.items() %}
|
||
<div>
|
||
<div class="docSectionLabel">{{ category.label }}</div>
|
||
<div class="docLibList">
|
||
{% for kind in category.kinds %}
|
||
<div class="docLibItem docLibItem--row"
|
||
draggable="true"
|
||
data-add="{{ kind }}">
|
||
<span class="docLibIcon docLibIcon--{{ kind }}"></span>
|
||
<span>{{ element_kind_labels[kind] }}</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<div class="docCanvasArea" id="docCanvasArea">
|
||
<div class="docPage" id="docPage">
|
||
<div class="docPageContent"
|
||
id="docPageContent"
|
||
data-vertical-align="{{ active_page.vertical_align if active_page else 'top' }}">{% if active_page %}{{ rendered_document|safe }}{% else %}<div class="docEmptyState docEmptyPageState">Aucune page — cliquez sur « + Ajouter une page » pour commencer.</div>{% endif %}</div>
|
||
{# //NOSONAR S5247 - rendered_document vient de document_engine.render_document, qui échappe (html.escape) tout contenu utilisateur avant interpolation (voir document_engine/rendering/render_document_element.py) ; jamais de HTML brut non échappé ici. Le message "Aucune page" est un littéral, jamais un contenu utilisateur. #}
|
||
</div>
|
||
<button type="button"
|
||
class="docPreviewExitBtn"
|
||
id="docPreviewExitBtn"
|
||
aria-label="Retour à l'éditeur"
|
||
title="Retour à l'éditeur">✕ Retour à l'éditeur</button>
|
||
<div class="docZoomPill">
|
||
<button type="button" id="docZoomOut" aria-label="Zoom -">−</button>
|
||
<span id="docZoomVal">100%</span>
|
||
<button type="button" id="docZoomIn" aria-label="Zoom +">+</button>
|
||
</div>
|
||
<div class="docPageNavPill" id="docPageNavPill">
|
||
<button type="button" id="docPagePrevBtn" aria-label="Page précédente">‹</button>
|
||
<span id="docPageNavLabel"></span>
|
||
<button type="button" id="docPageNextBtn" aria-label="Page suivante">›</button>
|
||
</div>
|
||
</div>
|
||
<div class="docSidebarClose" id="docCloseRight">← Retour au document</div>
|
||
<div class="docSidebar docSidebarRight" id="docSidebarRight">
|
||
<div id="docPropsPanel">
|
||
<div class="docSectionLabel">Propriétés</div>
|
||
<div class="docEmptyState">Sélectionnez un élément du document pour voir ses propriétés.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="docBottomNav" id="docBottomNav">
|
||
<button type="button" data-target="left">Éléments</button>
|
||
<button type="button" data-target="doc" class="is-active">Document</button>
|
||
<button type="button" data-target="right">Propriétés</button>
|
||
</div>
|
||
</div>
|
||
<div class="docModalBackdrop" id="docScenarioTreeModal">
|
||
<div class="docModalDialog">
|
||
<div class="docModalHeader">
|
||
<span class="docModalTitle" id="docScenarioTreeModalTitle">Arbre du scénario</span>
|
||
<button type="button"
|
||
class="docModalCloseBtn"
|
||
id="docScenarioTreeModalClose"
|
||
aria-label="Fermer">✕</button>
|
||
</div>
|
||
<div class="docModalBody" id="docScenarioTreeModalBody"></div>
|
||
</div>
|
||
</div>
|
||
<div class="docModalBackdrop" id="docTemplateModal">
|
||
<div class="docModalDialog docModalDialog--wide">
|
||
<div class="docModalHeader">
|
||
<span class="docModalTitle">Utiliser un modèle</span>
|
||
<button type="button"
|
||
class="docModalCloseBtn"
|
||
id="docTemplateModalClose"
|
||
aria-label="Fermer">✕</button>
|
||
</div>
|
||
<div class="docModalBody docTemplateModalBody">
|
||
<div class="docTemplateList" id="docTemplateList"></div>
|
||
<div class="docTemplatePreviewPane">
|
||
<iframe class="docTemplatePreviewFrame"
|
||
id="docTemplatePreviewFrame"
|
||
title="Aperçu du modèle"></iframe>
|
||
<div class="docTemplatePreviewEmpty"
|
||
id="docTemplatePreviewEmpty">Sélectionnez un modèle pour le visualiser.</div>
|
||
<div class="docTemplatePreviewActions"
|
||
id="docTemplatePreviewActions"
|
||
hidden>
|
||
<button type="button" class="docBtnSecondary" id="docTemplateKeepContentBtn">Utiliser mon contenu actuel</button>
|
||
<button type="button" class="docBtnSecondary docBtnPrimary" id="docTemplateUseContentBtn">Utiliser le contenu du modèle</button>
|
||
<button type="button" class="docBtnSecondary docBtnPrimary" id="docTemplateRemoveBtn" hidden>Retirer le modèle</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
window.FORGE_DOCUMENT = {
|
||
slug: {{ support.slug|tojson }},
|
||
elements: {{ elements|tojson }},
|
||
elementKindLabels: {{ element_kind_labels|tojson }},
|
||
pages: {{ pages|tojson }},
|
||
activePageId: {{ (active_page.id if active_page else none)|tojson }},
|
||
themes: {{ document_themes|tojson }},
|
||
activeThemeId: {{ support.theme|tojson }}
|
||
};
|
||
</script>
|
||
<script src="{{ url_for('static', filename='document/js/document-editor.js') }}"></script>
|
||
{% endblock %}
|