Corrige la modale "Utiliser un modèle" : aucune sélection par défaut, aperçu plein espace, navigation entre pages
Trois retours distincts : - Plus de présélection du thème déjà appliqué à l'ouverture — un choix toujours explicite de l'utilisateur. - L'état vide (.docTemplatePreviewEmpty) restait visible EN MÊME TEMPS que l'iframe une fois un thème sélectionné : `display:flex` posé directement dessus battait le `display:none` natif de [hidden] (même bug déjà rencontré pour .docSidebarTabPanel[hidden] plus tôt dans le projet) — les deux se partageaient flex:1, coupant l'aperçu en deux au lieu de lui laisser tout l'espace. - L'aperçu ne montrait que la première page du modèle sans aucun moyen d'en voir les autres : la route /document/<slug>/theme/<id>/preview rend désormais TOUTES les pages, une barre Précédent/Suivant (entièrement côté client, aucun aller-retour serveur supplémentaire) permet de naviguer entre elles. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
bd8f1d4b1e
commit
7730b3688f
@@ -8,16 +8,23 @@ from core.flask_app import app
|
|||||||
@app.route("/document/<slug>/theme/<theme_id>/preview")
|
@app.route("/document/<slug>/theme/<theme_id>/preview")
|
||||||
def document_theme_preview(slug: str, theme_id: str) -> str:
|
def document_theme_preview(slug: str, theme_id: str) -> str:
|
||||||
"""Aperçu isolé (destiné à un <iframe>, voir la modale "Utiliser un
|
"""Aperçu isolé (destiné à un <iframe>, voir la modale "Utiliser un
|
||||||
modèle" dans static/document/js/document-editor.js) de la première
|
modèle" dans static/document/js/document-editor.js) de TOUTES les
|
||||||
page de démonstration d'un thème — RÉELLEMENT rendue par le moteur
|
pages de démonstration d'un thème — RÉELLEMENT rendues par le moteur
|
||||||
(document_engine.render_document), jamais une image statique ou une
|
(document_engine.render_document), jamais une image statique ou une
|
||||||
resucée manuelle du CSS : ce qui est prévisualisé est EXACTEMENT ce
|
resucée manuelle du CSS : ce qui est prévisualisé est EXACTEMENT ce
|
||||||
qui s'affichera une fois le thème appliqué. `slug` sert uniquement à
|
qui s'affichera une fois le thème appliqué. La navigation entre pages
|
||||||
la garde de propriété (core/auth_guard.py, générique sur toute route
|
(retour utilisateur du 24/09/2026 : "je dois pouvoir naviguer dans
|
||||||
<slug>) — l'aperçu ne dépend d'aucune donnée de CE support."""
|
l'aperçu pour voir toutes les pages") se fait entièrement côté
|
||||||
|
client dans le template, jamais par un nouvel aller-retour serveur —
|
||||||
|
toutes les pages sont déjà rendues ici en une fois. `slug` sert
|
||||||
|
uniquement à la garde de propriété (core/auth_guard.py, générique
|
||||||
|
sur toute route <slug>) — l'aperçu ne dépend d'aucune donnée de CE
|
||||||
|
support."""
|
||||||
theme = document_engine.get_document_theme_entry(theme_id)
|
theme = document_engine.get_document_theme_entry(theme_id)
|
||||||
if theme is None:
|
if theme is None:
|
||||||
raise NotFound
|
raise NotFound
|
||||||
elements = document_engine.seed_blocks_to_elements(theme["seed_pages"][0])
|
rendered_pages = [
|
||||||
rendered_page = document_engine.render_document(elements)
|
document_engine.render_document(document_engine.seed_blocks_to_elements(page_blocks))
|
||||||
return render_template("document/document_theme_preview.html", theme=theme, rendered_page=rendered_page)
|
for page_blocks in theme["seed_pages"]
|
||||||
|
]
|
||||||
|
return render_template("document/document_theme_preview.html", theme=theme, rendered_pages=rendered_pages)
|
||||||
|
|||||||
@@ -2382,6 +2382,17 @@ img.docImage {
|
|||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* `display: flex` ci-dessus est une règle AUTEUR, qui bat TOUJOURS le
|
||||||
|
`display: none` d'origine UA de [hidden] quelle que soit la
|
||||||
|
spécificité/l'ordre (même bug déjà rencontré pour
|
||||||
|
.docSidebarTabPanel[hidden]) — sans cet override explicite, l'état
|
||||||
|
vide et l'iframe restent visibles SIMULTANÉMENT, chacun prenant
|
||||||
|
flex:1, ce qui coupe l'aperçu en deux au lieu de lui laisser toute la
|
||||||
|
place (bug réel constaté le 24/09/2026, capture à l'appui). */
|
||||||
|
.docTemplatePreviewEmpty[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.docTemplatePreviewActions {
|
.docTemplatePreviewActions {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -2906,19 +2906,19 @@ function forgeDocApplyTemplate(mode) {
|
|||||||
|
|
||||||
function forgeDocOpenTemplateModal() {
|
function forgeDocOpenTemplateModal() {
|
||||||
document.getElementById('docTemplateModal').classList.add('is-open');
|
document.getElementById('docTemplateModal').classList.add('is-open');
|
||||||
forgeDocSelectedTemplateId = window.FORGE_DOCUMENT.activeThemeId || null;
|
// Toujours vide à l'ouverture (retour utilisateur du 24/09/2026 :
|
||||||
|
// "aucun template ne dois etre sélectionner de base") — même si un
|
||||||
|
// thème est déjà appliqué à ce support, le choisir ici reste une
|
||||||
|
// action explicite de l'utilisateur, jamais présélectionnée.
|
||||||
|
forgeDocSelectedTemplateId = null;
|
||||||
forgeDocRenderTemplateList();
|
forgeDocRenderTemplateList();
|
||||||
const frame = document.getElementById('docTemplatePreviewFrame');
|
const frame = document.getElementById('docTemplatePreviewFrame');
|
||||||
const empty = document.getElementById('docTemplatePreviewEmpty');
|
const empty = document.getElementById('docTemplatePreviewEmpty');
|
||||||
const actions = document.getElementById('docTemplatePreviewActions');
|
const actions = document.getElementById('docTemplatePreviewActions');
|
||||||
if (forgeDocSelectedTemplateId) {
|
|
||||||
forgeDocSelectTemplate(forgeDocSelectedTemplateId);
|
|
||||||
} else {
|
|
||||||
frame.classList.remove('is-visible');
|
frame.classList.remove('is-visible');
|
||||||
frame.src = 'about:blank';
|
frame.src = 'about:blank';
|
||||||
empty.hidden = false;
|
empty.hidden = false;
|
||||||
actions.hidden = true;
|
actions.hidden = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function forgeDocCloseTemplateModal() {
|
function forgeDocCloseTemplateModal() {
|
||||||
|
|||||||
@@ -12,18 +12,99 @@
|
|||||||
<style>
|
<style>
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 16px;
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
background: #efece4;
|
background: #efece4;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewPages {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewPages .docPage[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewNav {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #1a1b20;
|
||||||
|
color: #fff;
|
||||||
|
font: 600 13px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewNav button {
|
||||||
|
border: none;
|
||||||
|
background: #2a2c33;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewNav button:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="docPage">
|
<div class="previewPages">
|
||||||
|
{% for rendered_page in rendered_pages %}
|
||||||
|
<div class="docPage" data-page-index="{{ loop.index0 }}" {% if not loop.first %}hidden{% endif %}>
|
||||||
<div class="docPageContent">
|
<div class="docPageContent">
|
||||||
{{ rendered_page | safe }}
|
{{ rendered_page | safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if rendered_pages|length > 1 %}
|
||||||
|
<div class="previewNav">
|
||||||
|
<button type="button" id="previewPrevBtn">‹ Précédent</button>
|
||||||
|
<span id="previewPageLabel"></span>
|
||||||
|
<button type="button" id="previewNextBtn">Suivant ›</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<script>
|
||||||
|
(function previewPageNav() {
|
||||||
|
var pages = Array.prototype.slice.call(document.querySelectorAll('.previewPages .docPage'));
|
||||||
|
if (pages.length <= 1) return;
|
||||||
|
var current = 0;
|
||||||
|
var prevBtn = document.getElementById('previewPrevBtn');
|
||||||
|
var nextBtn = document.getElementById('previewNextBtn');
|
||||||
|
var label = document.getElementById('previewPageLabel');
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
pages.forEach(function pageAt(page, index) {
|
||||||
|
page.hidden = index !== current;
|
||||||
|
});
|
||||||
|
label.textContent = 'Page ' + (current + 1) + ' / ' + pages.length;
|
||||||
|
prevBtn.disabled = current === 0;
|
||||||
|
nextBtn.disabled = current === pages.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevBtn.addEventListener('click', function goPrev() {
|
||||||
|
if (current > 0) { current -= 1; render(); }
|
||||||
|
});
|
||||||
|
nextBtn.addEventListener('click', function goNext() {
|
||||||
|
if (current < pages.length - 1) { current += 1; render(); }
|
||||||
|
});
|
||||||
|
render();
|
||||||
|
}());
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -454,10 +454,19 @@ def test_document_theme_apply_template_content_replaces_everything(client: Flask
|
|||||||
assert len(pages) == len(theme["seed_pages"])
|
assert len(pages) == len(theme["seed_pages"])
|
||||||
|
|
||||||
|
|
||||||
def test_document_theme_preview_renders_the_first_seed_page(client: FlaskClient, support: str) -> None:
|
def test_document_theme_preview_renders_every_seed_page_with_navigation(client: FlaskClient, support: str) -> None:
|
||||||
resp = client.get(f"/document/{support}/theme/securite-incendie/preview")
|
resp = client.get(f"/document/{support}/theme/securite-incendie/preview")
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert "docPage" in resp.get_data(as_text=True)
|
html = resp.get_data(as_text=True)
|
||||||
|
theme = document_engine.get_document_theme_entry("securite-incendie")
|
||||||
|
assert theme is not None
|
||||||
|
page_count = len(theme["seed_pages"])
|
||||||
|
assert html.count('class="docPage"') == page_count
|
||||||
|
# Une seule page visible au chargement (les autres portent `hidden`,
|
||||||
|
# navigation gérée côté client — voir templates/document/
|
||||||
|
# document_theme_preview.html).
|
||||||
|
assert html.count("hidden>") == page_count - 1
|
||||||
|
assert "previewNav" in html
|
||||||
|
|
||||||
|
|
||||||
def test_document_theme_preview_404s_for_an_unknown_theme(client: FlaskClient, support: str) -> None:
|
def test_document_theme_preview_404s_for_an_unknown_theme(client: FlaskClient, support: str) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user