Build and deploy / test-python (push) Successful in 10m10s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / lint-python (push) Successful in 4m44s
Build and deploy / lint-js (push) Failing after 1m27s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m8s
Remplace le carrousel une-page-à-la-fois par un panneau dédié plein hauteur : liste verticale scrollable de toutes les pages, réordonnage par glisser OU boutons haut/bas (accessibilité clavier), renommer (crayon, édition en ligne), supprimer (protégé contre la suppression de la dernière page), ajouter. La bibliothèque d'éléments passe dans un second onglet "Mise en page", contenu inchangé. "Mise en page" actif par défaut au chargement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
245 lines
11 KiB
HTML
245 lines
11 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') }}">
|
||
{% 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>
|
||
<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>
|
||
<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 (réordonner/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="docPageRowMove"
|
||
data-dir="up"
|
||
data-page-id="{{ page.id }}"
|
||
aria-label="Monter"
|
||
title="Monter"
|
||
{{ 'disabled' if loop.first else '' }}>↑</button>
|
||
<button type="button"
|
||
class="docPageRowMove"
|
||
data-dir="down"
|
||
data-page-id="{{ page.id }}"
|
||
aria-label="Descendre"
|
||
title="Descendre"
|
||
{{ 'disabled' if loop.last else '' }}>↓</button>
|
||
<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"
|
||
{{ 'disabled' if pages|length <= 1 else '' }}>✕</button>
|
||
</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
<button type="button" class="docBtnSecondary docPageManagerAdd" id="docPageManagerAdd">+ Ajouter une page</button>
|
||
</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">{{ rendered_document|safe }}</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 #}
|
||
</div>
|
||
<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>
|
||
<script>
|
||
window.FORGE_DOCUMENT = {
|
||
slug: {{ support.slug|tojson }},
|
||
elements: {{ elements|tojson }},
|
||
elementKindLabels: {{ element_kind_labels|tojson }},
|
||
pages: {{ pages|tojson }},
|
||
activePageId: {{ active_page.id|tojson }}
|
||
};
|
||
</script>
|
||
<script src="{{ url_for('static', filename='document/js/document-editor.js') }}"></script>
|
||
{% endblock %}
|