Ajoute 4 mécanismes moteur manquants pour le thème sécurité incendie : étiquette, carte, image SVG inline, bouton avec pièce jointe
Contenu et mécanisme uniquement, aucun style ajouté (voir consigne du 24/09/2026) : deux nouveaux kinds de contenu (badge/carte, rendu en div brutes sans CSS), un mode SVG inline pour l'image (svg_markup, nettoyé par un nouveau sanitizer allow-list avant chaque rendu) et un fichier téléchargeable joignable à un bouton (upload/download routes, stockage sous db.support_dir). Le futur système de templates portera l'habillage visuel de ces éléments. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e7c6ed7159
commit
a34bcf4159
@@ -306,6 +306,8 @@ body.objectEditBody > main.content {
|
||||
.docLibIcon--bouton::before { content: "⬚"; }
|
||||
.docLibIcon--liste_puces::before { content: "☰"; }
|
||||
.docLibIcon--liste_numerotee::before { content: "①"; }
|
||||
.docLibIcon--badge::before { content: "◉"; }
|
||||
.docLibIcon--carte::before { content: "▭"; }
|
||||
|
||||
.docLibIcon--titre::before {
|
||||
content: "T";
|
||||
|
||||
@@ -98,6 +98,16 @@ function forgeDocApiMoveToPage(id, targetPageId) {
|
||||
}).then((r) => r.json());
|
||||
}
|
||||
|
||||
function forgeDocApiUploadAttachment(id, file) {
|
||||
// Multipart, jamais du JSON comme forgeDocApiUpdate ci-dessus — un
|
||||
// fichier ne se sérialise pas dans le corps JSON d'un PATCH classique
|
||||
// (voir routes/document/document_element_upload_attachment.py).
|
||||
const body = new FormData();
|
||||
body.set('file', file);
|
||||
return fetch(`/document/${window.forgeDocState.slug}/elements/${id}/upload-attachment`, { method: 'POST', body })
|
||||
.then((r) => r.json());
|
||||
}
|
||||
|
||||
function forgeDocApiRender() {
|
||||
const url = `/document/${window.forgeDocState.slug}/render?page_id=${window.forgeDocState.activePageId}`;
|
||||
return fetch(url).then((r) => r.json());
|
||||
@@ -778,6 +788,14 @@ function forgeDocBindCanvasInteractions() {
|
||||
const elementId = Number.parseInt(dom.dataset.elementId, 10);
|
||||
dom.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
// En Aperçu, un bouton portant un fichier joint déclenche son
|
||||
// téléchargement plutôt que la sélection (réservée à l'édition) —
|
||||
// même principe que les mini-jeux, jouables uniquement en Aperçu
|
||||
// (voir static/document/document-editor.css, .docEditor3--preview).
|
||||
if (window.forgeDocState.previewMode && dom.dataset.attachmentFilename) {
|
||||
window.location.href = `/document/${window.forgeDocState.slug}/elements/${elementId}/download-attachment`;
|
||||
return;
|
||||
}
|
||||
forgeDocSelectElement(elementId);
|
||||
});
|
||||
dom.setAttribute('draggable', 'true');
|
||||
@@ -1649,24 +1667,78 @@ function forgeDocRenderImageProps(panel, el) {
|
||||
${forgeDocPropsHeader('image')}
|
||||
<div class="docField"><label for="docImageSrc">URL de l'image</label><input type="text" id="docImageSrc" value="${a.src || ''}" placeholder="https://..."></div>
|
||||
<div class="docField"><label for="docImageAlt">Texte alternatif</label><input type="text" id="docImageAlt" value="${a.alt || ''}"></div>
|
||||
<div class="docField"><label for="docImageSvg">Code SVG (prioritaire sur l'URL si renseigné)</label><textarea id="docImageSvg" rows="4">${forgeDocEscapeHtml(a.svg_markup || '')}</textarea></div>
|
||||
${forgeDocDeleteButtonHtml()}
|
||||
`;
|
||||
function patch(partial) { forgeDocUpdateAttributes(el.id, { ...el.attributes, ...partial }); }
|
||||
document.getElementById('docImageSrc').addEventListener('change', (e) => patch({ src: e.target.value.trim() }));
|
||||
document.getElementById('docImageAlt').addEventListener('change', (e) => patch({ alt: e.target.value }));
|
||||
document.getElementById('docImageSvg').addEventListener('change', (e) => patch({ svg_markup: e.target.value.trim() }));
|
||||
}
|
||||
|
||||
function forgeDocRenderButtonProps(panel, el) {
|
||||
const a = el.attributes;
|
||||
const attachmentStatus = a.attachment_filename
|
||||
? `Fichier joint : ${forgeDocEscapeHtml(a.attachment_filename)}`
|
||||
: 'Aucun fichier joint';
|
||||
panel.innerHTML = `
|
||||
${forgeDocPropsHeader('bouton')}
|
||||
<div class="docField"><label for="docButtonLabel">Texte du bouton</label><input type="text" id="docButtonLabel" value="${a.label || ''}"></div>
|
||||
<div class="docField"><label for="docButtonTarget">Cible (URL ou ancre)</label><input type="text" id="docButtonTarget" value="${a.target || ''}"></div>
|
||||
<div class="docField">
|
||||
<span class="docFieldLabel">Fichier téléchargeable</span>
|
||||
<div id="docButtonAttachmentStatus">${attachmentStatus}</div>
|
||||
<input type="file" id="docButtonAttachmentInput">
|
||||
</div>
|
||||
${forgeDocDeleteButtonHtml()}
|
||||
`;
|
||||
function patch(partial) { forgeDocUpdateAttributes(el.id, { ...el.attributes, ...partial }); }
|
||||
document.getElementById('docButtonLabel').addEventListener('change', (e) => patch({ label: e.target.value }));
|
||||
document.getElementById('docButtonTarget').addEventListener('change', (e) => patch({ target: e.target.value.trim() }));
|
||||
document.getElementById('docButtonAttachmentInput').addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
forgeDocApiUploadAttachment(el.id, file).then((result) => {
|
||||
if (result.error) { alert(result.error); return; }
|
||||
el.attributes = result.attributes;
|
||||
forgeDocRenderProps(window.forgeDocState.elementsById[el.id]);
|
||||
const dom = document.querySelector(`[data-element-id="${el.id}"]`);
|
||||
if (dom && result.rendered_html) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = result.rendered_html;
|
||||
dom.replaceWith(wrapper.firstElementChild);
|
||||
forgeDocBindCanvasInteractions();
|
||||
forgeDocApplySelectionClass();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function forgeDocRenderBadgeProps(panel, el) {
|
||||
const a = el.attributes;
|
||||
panel.innerHTML = `
|
||||
${forgeDocPropsHeader('étiquette')}
|
||||
<div class="docField"><label for="docBadgeContent">Contenu</label><input type="text" id="docBadgeContent" value="${forgeDocEscapeHtml(a.content || '')}"></div>
|
||||
${forgeDocDeleteButtonHtml()}
|
||||
`;
|
||||
document.getElementById('docBadgeContent').addEventListener('change', (e) => {
|
||||
forgeDocUpdateAttributes(el.id, { ...el.attributes, content: e.target.value });
|
||||
});
|
||||
}
|
||||
|
||||
function forgeDocRenderCarteProps(panel, el) {
|
||||
const a = el.attributes;
|
||||
panel.innerHTML = `
|
||||
${forgeDocPropsHeader('carte')}
|
||||
<div class="docField"><label for="docCardLabel">Repère (lettre, chiffre...)</label><input type="text" id="docCardLabel" value="${forgeDocEscapeHtml(a.label || '')}"></div>
|
||||
<div class="docField"><label for="docCardTitle">Titre</label><input type="text" id="docCardTitle" value="${forgeDocEscapeHtml(a.title || '')}"></div>
|
||||
<div class="docField"><label for="docCardDescription">Description</label><textarea id="docCardDescription" rows="3">${forgeDocEscapeHtml(a.description || '')}</textarea></div>
|
||||
${forgeDocDeleteButtonHtml()}
|
||||
`;
|
||||
function patch(partial) { forgeDocUpdateAttributes(el.id, { ...el.attributes, ...partial }); }
|
||||
document.getElementById('docCardLabel').addEventListener('change', (e) => patch({ label: e.target.value }));
|
||||
document.getElementById('docCardTitle').addEventListener('change', (e) => patch({ title: e.target.value }));
|
||||
document.getElementById('docCardDescription').addEventListener('change', (e) => patch({ description: e.target.value }));
|
||||
}
|
||||
|
||||
function forgeDocRenderListItemHtml(item, index) {
|
||||
@@ -2777,6 +2849,8 @@ function forgeDocRenderProps(el) {
|
||||
else if (el.kind === 'image') forgeDocRenderImageProps(panel, el);
|
||||
else if (el.kind === 'bouton') forgeDocRenderButtonProps(panel, el);
|
||||
else if (el.kind === 'liste_puces' || el.kind === 'liste_numerotee') forgeDocRenderListProps(panel, el);
|
||||
else if (el.kind === 'badge') forgeDocRenderBadgeProps(panel, el);
|
||||
else if (el.kind === 'carte') forgeDocRenderCarteProps(panel, el);
|
||||
else if (el.kind === 'row') forgeDocRenderRowProps(panel, el);
|
||||
else if (el.kind === 'quiz') forgeDocRenderQuizProps(panel, el);
|
||||
else if (el.kind === 'association') forgeDocRenderAssociationProps(panel, el);
|
||||
|
||||
Reference in New Issue
Block a user