Build and deploy / test-python (push) Successful in 7m48s
Build and deploy / test-js (push) Successful in 52s
Build and deploy / lint-python (push) Successful in 5m44s
Build and deploy / lint-js (push) Failing after 1m52s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 5m5s
Retour utilisateur : "il faut implementer un systeme de page". Un support est desormais compose de PLUSIEURS pages (_document_pages), chacune un document independant affiche seul sur le canevas -- chaque element appartient a exactement une page via page_id (document_engine/ elements/*, routes/document/document_element_add.py/document_render.py revalident desormais un page_id explicite). Migration automatique et silencieuse pour les supports crees avant cette fonctionnalite (db/supports/ensure_document_pages_schema.py, meme convention que les ensure_X_schema.py existants) : leurs elements deviennent tous les enfants d'une "Page 1" creee a la volee, aucune perte de contenu. Nouveau paquet document_engine/pages/ (add/list/get/rename/move/delete) et 4 routes dediees (routes/document/document_page_*.py) -- supprimer la DERNIERE page restante est refuse (garde-fou pose a la route, meme decoupage que routes/game/screens/screen_delete.py cote jeu, jamais dans la fonction bas niveau). Cote editeur : une bande d'ONGLETS au-dessus du canevas (jamais un panneau lateral, choix explicite de l'utilisateur) -- clic pour changer de page, double-clic pour renommer (contenteditable), glisser pour reordonner, "+" pour ajouter, "x" pour supprimer. Changer de page vide la pile Annuler/Retablir (une commande empilee sur une autre page n'a plus de sens). Mode Apercu : navigation Page precedente/suivante avec indicateur "Page X / N" (choix explicite : page par page, pas de defilement continu), jamais affichee s'il n'y a qu'une seule page. Verifie : suite pytest complete (702 tests, dont 14 nouveaux pour les routes de pages), simulation DOM reelle (jsdom, 25 assertions couvrant tout le cycle de vie cote client -- creation/bascule/renommage/ reordonnancement/suppression de page, portee correcte des elements par page, pile Annuler/Retablir videe au changement de page, pilule de navigation en Apercu), et un test de fumee HTTP reel contre le serveur de dev en marche (creation/ajout d'element/rendu/renommage/suppression d'une page, refus de supprimer la derniere page, page inconnue -> 404). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
193 lines
7.9 KiB
HTML
193 lines
7.9 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="docPageTabs" id="docPageTabs">
|
||
{% for page in pages %}
|
||
<button type="button"
|
||
class="docPageTab {{ 'is-active' if page.id == active_page.id else '' }}"
|
||
data-page-id="{{ page.id }}"
|
||
draggable="true">
|
||
<span class="docPageTabTitle">{{ page.title }}</span>
|
||
<span class="docPageTabDelete" data-page-id="{{ page.id }}" title="Supprimer cette page">✕</span>
|
||
</button>
|
||
{% endfor %}
|
||
<button type="button" class="docPageTabAdd" id="docPageTabAdd" aria-label="Ajouter une page" title="Ajouter une page">+</button>
|
||
</div>
|
||
<div class="docBodyWrap">
|
||
<div class="docSidebarClose" id="docCloseLeft">← Retour au document</div>
|
||
<div class="docSidebar docSidebarLeft" id="docSidebarLeft">
|
||
{% for category_key, category in element_library.items() %}
|
||
<div>
|
||
<div class="docSectionLabel">{{ category.label }}</div>
|
||
{% if category_key == "mise_en_page" %}
|
||
<div class="docShapesGrid">
|
||
{% for kind in category.kinds %}
|
||
<div class="docLibItem docLibItem--shape"
|
||
draggable="true"
|
||
data-add="{{ kind }}">
|
||
<span class="docLibIcon docLibIcon--{{ kind }}"></span>
|
||
<span>{{ element_kind_labels[kind] }}</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<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>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</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 class="docSnapGridOverlay" aria-hidden="true"></div>
|
||
</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 %}
|