Audit complet de mise en forme — Bouton (3e élément du tableau)
Implémente toutes les options manquantes identifiées pour l'élément Bouton : typographie (gras/italique/transformation/police/taille/ espacement des lettres/couleur), icône SVG optionnelle avec position (avant/après le texte) et taille réglables — au-delà de l'Étiquette, qui n'a qu'une icône fixe —, tous les attributs de boîte partagés déjà créés pour Titre/Paragraphe/Image, et un effet visuel universel au survol/clic. Deux ajouts transversaux demandés pendant le test : un réglage "les 4 côtés de la bordure à la fois" dans le module de boîte partagé (bénéficie automatiquement à tous les éléments qui l'utilisent), et le glisser-déposer d'un élément du canevas vers une autre page via le panneau Pages (le mécanisme serveur existait déjà pour la pagination automatique, seule l'interaction manuelle manquait). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
9ad50c58b8
commit
8f879f6321
@@ -347,6 +347,37 @@ function forgeDocDeleteElement(id) {
|
||||
});
|
||||
}
|
||||
|
||||
function forgeDocMoveElementToPage(elementId, targetPageId) {
|
||||
// Retour utilisateur du 26/09/2026 : "j'aimerais pouvoir glisser
|
||||
// déposer un élément d'une page dans une autre page" — déposé depuis
|
||||
// le canevas sur une rangée du panneau Pages (voir
|
||||
// forgeDocRenderPageManagerList). move_document_element_to_page
|
||||
// (document_engine/elements/) redevient TOUJOURS top-niveau sur la
|
||||
// page cible (une rangée d'origine n'a aucun sens comme enfant d'une
|
||||
// rangée de la page cible) — Annuler restaure donc explicitement
|
||||
// l'ancien parent/position via forgeDocApiMove, une fois l'élément
|
||||
// revenu sur son ancienne page.
|
||||
const el = window.forgeDocState.elementsById[elementId];
|
||||
if (!el || targetPageId === window.forgeDocState.activePageId) return Promise.resolve();
|
||||
const state = {
|
||||
id: elementId,
|
||||
fromPageId: window.forgeDocState.activePageId,
|
||||
toPageId: targetPageId,
|
||||
parentId: el.parent_id,
|
||||
index: el.order_index,
|
||||
};
|
||||
function performForward() { return forgeDocApiMoveToPage(state.id, state.toPageId); }
|
||||
function performUndo() {
|
||||
return forgeDocApiMoveToPage(state.id, state.fromPageId)
|
||||
.then(() => forgeDocApiMove(state.id, state.parentId, state.index));
|
||||
}
|
||||
return performForward().then(() => {
|
||||
forgeDocPushCommand({ undo: performUndo, redo: performForward });
|
||||
window.forgeDocState.selectedId = null;
|
||||
return forgeDocRefreshCanvas();
|
||||
});
|
||||
}
|
||||
|
||||
function forgeDocUpdateAttributes(id, newAttributes) {
|
||||
const el = window.forgeDocState.elementsById[id];
|
||||
if (!el) return Promise.resolve();
|
||||
@@ -851,6 +882,30 @@ function forgeDocRenderPageManagerList() {
|
||||
e.preventDefault();
|
||||
forgeDocSwitchPage(Number(row.dataset.pageId));
|
||||
});
|
||||
// Glisser un élément du canevas jusqu'ici pour le déplacer vers CETTE
|
||||
// page (retour utilisateur du 26/09/2026 : "j'aimerais pouvoir
|
||||
// glisser déposer un élément d'une page dans une autre page") — même
|
||||
// charge utile ("text/forge-doc-move") que le réordonnancement d'un
|
||||
// élément AU SEIN d'une page (voir forgeDocBindCanvasDropZone), donc
|
||||
// aucune modification de la source de glisser côté canevas. Sans
|
||||
// rapport avec le glisser-déposer des PAGES elles-mêmes (délégué au
|
||||
// conteneur, voir forgeDocBindPageManagerDrag juste plus bas) : les
|
||||
// deux coexistent sur la même rangée sans conflit, chacun ignorant
|
||||
// le type de charge utile qui ne le concerne pas.
|
||||
row.addEventListener('dragover', (e) => {
|
||||
if (!e.dataTransfer.types.includes('text/forge-doc-move')) return;
|
||||
e.preventDefault();
|
||||
row.classList.add('docPageRow--dropTarget');
|
||||
});
|
||||
row.addEventListener('dragleave', () => row.classList.remove('docPageRow--dropTarget'));
|
||||
row.addEventListener('drop', (e) => {
|
||||
const moveId = e.dataTransfer.getData('text/forge-doc-move');
|
||||
row.classList.remove('docPageRow--dropTarget');
|
||||
if (!moveId) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
forgeDocMoveElementToPage(Number.parseInt(moveId, 10), Number(row.dataset.pageId));
|
||||
});
|
||||
});
|
||||
container.querySelectorAll('.docPageRowRename').forEach((btn) => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
@@ -2018,6 +2073,34 @@ function forgeDocRenderBoxFieldsHtml(a, idPrefix) {
|
||||
${FORGE_DOC_ALIGN_SELF_OPTIONS.map(([v, l]) => `<button type="button" class="docSegBtn ${alignSelf === v ? 'is-active' : ''}" data-align-self="${v}">${l}</button>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="docField">
|
||||
<span class="docFieldLabel">Bordure — les 4 côtés à la fois</span>
|
||||
<div class="docBorderSideRow" id="${idPrefix}BorderAllRow">
|
||||
${(() => {
|
||||
// Retour utilisateur du 26/09/2026 : "une option pour modifier les
|
||||
// 4 bordures en même temps" — un raccourci qui applique la MÊME
|
||||
// valeur aux 4 côtés d'un coup, en plus (jamais à la place) du
|
||||
// réglage par côté juste en dessous. Si les 4 côtés portent déjà
|
||||
// exactement la même valeur, cette rangée en repart (plutôt que
|
||||
// d'afficher un état neutre déconnecté de la réalité) ; sinon elle
|
||||
// repart d'un état neutre ("Aucune"/1px/couleur par défaut), un
|
||||
// signe que les côtés diffèrent actuellement.
|
||||
const sides = FORGE_DOC_BORDER_SIDES.map(([side]) => border[side] || {});
|
||||
const allSame = sides.every((s) => (
|
||||
s.style === sides[0].style && s.width === sides[0].width && s.color === sides[0].color
|
||||
));
|
||||
const common = allSame ? sides[0] : {};
|
||||
return `
|
||||
<span class="docBorderSideLabel">Tous</span>
|
||||
<select class="docBorderStyleSelect" id="${idPrefix}BorderAllStyle">
|
||||
${FORGE_DOC_BORDER_STYLES.map(([v, l]) => `<option value="${v}" ${(common.style || 'none') === v ? 'selected' : ''}>${l}</option>`).join('')}
|
||||
</select>
|
||||
<input type="range" class="docBorderWidthRange" id="${idPrefix}BorderAllWidth" min="1" max="10" step="1" value="${Number.parseFloat(common.width) || 1}" title="Épaisseur">
|
||||
<input type="color" class="docBorderColorInput" id="${idPrefix}BorderAllColor" value="${forgeDocToHexColor(common.color, '#c9c4b8')}" title="Couleur">
|
||||
`;
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="docField">
|
||||
<span class="docFieldLabel">Bordure (par côté)</span>
|
||||
<div class="docBorderEditor" id="${idPrefix}BorderEditor">
|
||||
@@ -2064,6 +2147,21 @@ function forgeDocBindBoxFields(panel, el, idPrefix, patch) {
|
||||
btn.addEventListener('click', () => patch({ align_self: btn.dataset.alignSelf }));
|
||||
});
|
||||
|
||||
function applyBorderToAllSides() {
|
||||
const style = document.getElementById(`${idPrefix}BorderAllStyle`).value;
|
||||
const width = `${document.getElementById(`${idPrefix}BorderAllWidth`).value}px`;
|
||||
const color = document.getElementById(`${idPrefix}BorderAllColor`).value;
|
||||
const sideValue = { style, width, color };
|
||||
patch({
|
||||
border: {
|
||||
top: sideValue, right: sideValue, bottom: sideValue, left: sideValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
document.getElementById(`${idPrefix}BorderAllStyle`).addEventListener('change', applyBorderToAllSides);
|
||||
document.getElementById(`${idPrefix}BorderAllWidth`).addEventListener('change', applyBorderToAllSides);
|
||||
document.getElementById(`${idPrefix}BorderAllColor`).addEventListener('change', applyBorderToAllSides);
|
||||
|
||||
function patchBorderSide(side, partial) {
|
||||
// Toujours relu depuis el.attributes AU MOMENT de l'appel (jamais un
|
||||
// instantané capturé au moment du binding) : el.attributes est
|
||||
@@ -2085,6 +2183,10 @@ function forgeDocBindBoxFields(panel, el, idPrefix, patch) {
|
||||
});
|
||||
}
|
||||
|
||||
const FORGE_DOC_TEXT_TRANSFORM_OPTIONS = [
|
||||
['none', 'Normal'], ['uppercase', 'MAJUSCULES'], ['lowercase', 'minuscules'], ['capitalize', 'Capitales'],
|
||||
];
|
||||
|
||||
function forgeDocRenderTextProps(panel, el) {
|
||||
const a = el.attributes;
|
||||
panel.innerHTML = `
|
||||
@@ -2116,7 +2218,7 @@ function forgeDocRenderTextProps(panel, el) {
|
||||
<div class="docField">
|
||||
<span class="docFieldLabel">Majuscules / minuscules</span>
|
||||
<div class="docSegmented" id="docTextTransformSeg">
|
||||
${[['none', 'Normal'], ['uppercase', 'MAJUSCULES'], ['lowercase', 'minuscules'], ['capitalize', 'Capitales']].map(([v, l]) => `<button type="button" class="docSegBtn ${(a.text_transform || 'none') === v ? 'is-active' : ''}" data-transform="${v}">${l}</button>`).join('')}
|
||||
${FORGE_DOC_TEXT_TRANSFORM_OPTIONS.map(([v, l]) => `<button type="button" class="docSegBtn ${(a.text_transform || 'none') === v ? 'is-active' : ''}" data-transform="${v}">${l}</button>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="docField">
|
||||
@@ -2282,6 +2384,10 @@ function forgeDocRenderImageProps(panel, el) {
|
||||
forgeDocBindBoxFields(panel, el, 'docImage', patch);
|
||||
}
|
||||
|
||||
const FORGE_DOC_ICON_POSITION_OPTIONS = [
|
||||
['before', 'Avant le texte'], ['after', 'Après le texte'],
|
||||
];
|
||||
|
||||
function forgeDocRenderButtonProps(panel, el) {
|
||||
const a = el.attributes;
|
||||
const attachmentStatus = a.attachment_filename
|
||||
@@ -2289,13 +2395,53 @@ function forgeDocRenderButtonProps(panel, el) {
|
||||
: '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"><label for="docButtonLabel">Texte du bouton</label><input type="text" id="docButtonLabel" value="${forgeDocEscapeHtml(a.label || '')}"></div>
|
||||
<div class="docField"><label for="docButtonTarget">Cible (URL ou ancre)</label><input type="text" id="docButtonTarget" value="${forgeDocEscapeHtml(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>
|
||||
<div class="docField"><label for="docButtonSvg">Icône SVG (optionnel)</label><textarea id="docButtonSvg" rows="3">${forgeDocEscapeHtml(a.svg_markup || '')}</textarea></div>
|
||||
<div class="docField">
|
||||
<span class="docFieldLabel">Position de l'icône</span>
|
||||
<div class="docSegmented" id="docButtonIconPosSeg">
|
||||
${FORGE_DOC_ICON_POSITION_OPTIONS.map(([v, l]) => `<button type="button" class="docSegBtn ${(a.icon_position || 'before') === v ? 'is-active' : ''}" data-pos="${v}">${l}</button>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
${forgeDocRenderToggleSliderHtml('docButtonIconSize', "Taille de l'icône", a.icon_size, {
|
||||
min: 10, max: 48, step: 1, unit: 'px', fallback: 16,
|
||||
})}
|
||||
<div class="docField">
|
||||
<span class="docFieldLabel">Mise en forme</span>
|
||||
<div class="docToolbarRow">
|
||||
<button type="button" class="docFmtBtn ${a.bold ? 'is-active' : ''}" id="docButtonBoldBtn" title="Gras">B</button>
|
||||
<button type="button" class="docFmtBtn docFmtBtn--italic ${a.italic ? 'is-active' : ''}" id="docButtonItalicBtn" title="Italique">I</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="docField">
|
||||
<span class="docFieldLabel">Majuscules / minuscules</span>
|
||||
<div class="docSegmented" id="docButtonTextTransformSeg">
|
||||
${FORGE_DOC_TEXT_TRANSFORM_OPTIONS.map(([v, l]) => `<button type="button" class="docSegBtn ${(a.text_transform || 'none') === v ? 'is-active' : ''}" data-transform="${v}">${l}</button>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="docField">
|
||||
<label for="docButtonColorInput">Couleur du texte</label>
|
||||
<input type="color" id="docButtonColorInput" value="${forgeDocToHexColor(a.text_color, '#14100c')}">
|
||||
</div>
|
||||
<div class="docField">
|
||||
<label for="docButtonFontFamilyInput">Police de caractère</label>
|
||||
<select id="docButtonFontFamilyInput">
|
||||
${FORGE_DOC_FONT_FAMILY_OPTIONS.map(([v, l]) => `<option value="${forgeDocEscapeHtml(v)}" ${(a.font_family || '') === v ? 'selected' : ''}>${l}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
${forgeDocRenderToggleSliderHtml('docButtonFontSize', 'Taille de police', a.font_size, {
|
||||
min: 10, max: 32, step: 1, unit: 'px', fallback: 14,
|
||||
})}
|
||||
${forgeDocRenderToggleSliderHtml('docButtonLetterSpacing', 'Espacement des lettres', a.letter_spacing, {
|
||||
min: -2, max: 10, step: 0.5, unit: 'px', fallback: 0,
|
||||
})}
|
||||
${forgeDocRenderBoxFieldsHtml(a, 'docButton')}
|
||||
${forgeDocDeleteButtonHtml()}
|
||||
`;
|
||||
function patch(partial) { forgeDocUpdateAttributes(el.id, { ...el.attributes, ...partial }); }
|
||||
@@ -2318,6 +2464,21 @@ function forgeDocRenderButtonProps(panel, el) {
|
||||
}
|
||||
});
|
||||
});
|
||||
document.getElementById('docButtonSvg').addEventListener('change', (e) => patch({ svg_markup: e.target.value.trim() }));
|
||||
panel.querySelectorAll('#docButtonIconPosSeg .docSegBtn').forEach((btn) => {
|
||||
btn.addEventListener('click', () => patch({ icon_position: btn.dataset.pos }));
|
||||
});
|
||||
forgeDocBindToggleSlider('docButtonIconSize', { unit: 'px' }, (v) => patch({ icon_size: v }));
|
||||
document.getElementById('docButtonBoldBtn').addEventListener('click', () => patch({ bold: !a.bold }));
|
||||
document.getElementById('docButtonItalicBtn').addEventListener('click', () => patch({ italic: !a.italic }));
|
||||
panel.querySelectorAll('#docButtonTextTransformSeg .docSegBtn').forEach((btn) => {
|
||||
btn.addEventListener('click', () => patch({ text_transform: btn.dataset.transform }));
|
||||
});
|
||||
document.getElementById('docButtonColorInput').addEventListener('change', (e) => patch({ text_color: e.target.value }));
|
||||
document.getElementById('docButtonFontFamilyInput').addEventListener('change', (e) => patch({ font_family: e.target.value }));
|
||||
forgeDocBindToggleSlider('docButtonFontSize', { unit: 'px' }, (v) => patch({ font_size: v }));
|
||||
forgeDocBindToggleSlider('docButtonLetterSpacing', { unit: 'px' }, (v) => patch({ letter_spacing: v }));
|
||||
forgeDocBindBoxFields(panel, el, 'docButton', patch);
|
||||
}
|
||||
|
||||
function forgeDocRenderBadgeProps(panel, el) {
|
||||
|
||||
Reference in New Issue
Block a user