feat: render actual elements in markdown cheatsheet instead of labels

Replace text labels (h1, bold, italic) with rendered HTML in the Rendu column:
headings, strong, em, del, code, links, blockquote, lists, hr, sup, small
This commit is contained in:
Pontoporeia
2026-06-09 19:57:56 +02:00
parent 4a2b000fca
commit 38ef550397
16 changed files with 577 additions and 103 deletions

View File

@@ -0,0 +1,62 @@
/**
* Shared HTMX after-request handler for autosave forms.
*
* Reads the JSON response, updates the CSRF token, and surfaces
* parse errors instead of silently swallowing them (unlike the
* old autosave.js .catch(() => {}) pattern).
*/
function handleAutosaveResponse(event) {
const form = event.target.closest("form");
const status = form ? form.querySelector("[data-autosave-status]") : null;
if (!event.detail.successful) {
if (status) {
status.textContent = "Erreur !";
status.className = "autosave-status autosave-status--error";
}
return;
}
try {
const data = JSON.parse(event.detail.xhr.responseText);
// Rotate CSRF token in both the form and the meta tag
if (data.csrf_token) {
const csrfInput = form.querySelector('input[name="csrf_token"]');
if (csrfInput) csrfInput.value = data.csrf_token;
const meta = document.querySelector('meta[name="csrf-token"]');
if (meta) meta.content = data.csrf_token;
}
if (status) {
if (data.success) {
status.textContent = "Enregistré ✓";
status.className = "autosave-status autosave-status--saved";
} else {
status.textContent = "Erreur !";
status.className = "autosave-status autosave-status--error";
}
}
} catch {
// JSON parse failed (e.g. PHP warning in output) — surface it
if (status) {
status.textContent = "Erreur !";
status.className = "autosave-status autosave-status--error";
}
console.warn(
"Autosave: could not parse response",
event.detail.xhr.responseText,
);
}
}
// Show saving indicator while request is in flight
document.body.addEventListener("htmx:beforeRequest", (e) => {
const el = e.target;
if (!el) return;
const status = el.querySelector("[data-autosave-status]");
if (status) {
status.textContent = "Enregistrement…";
status.className = "autosave-status autosave-status--saving";
}
});

View File

@@ -1,76 +0,0 @@
/**
* Auto-save for admin content edit forms.
*
* Watches forms with [data-autosave] attribute. Debounces 1.5s after the
* last change, POSTs to the form's action, and shows a small status bar.
*
* The status indicator lives in a sibling element with [data-autosave-status].
* States: idle → saving… → saved ✓ / error !
*/
(() => {
const forms = document.querySelectorAll('form[data-autosave]');
if (!forms.length) return;
const DEBOUNCE_MS = 1500;
for (const form of forms) {
const statusEl = document.querySelector(form.dataset.autosaveStatus || '[data-autosave-status]');
let timer = null;
let dirty = false;
const setStatus = (text, cls) => {
if (!statusEl) return;
statusEl.textContent = text;
statusEl.className = 'autosave-status ' + (cls || '');
};
const doSave = () => {
if (!dirty) return;
dirty = false;
setStatus('Enregistrement…', 'autosave-status--saving');
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData,
headers: { 'Accept': 'application/json' },
})
.then((r) => {
if (!r.ok) throw new Error('HTTP ' + r.status);
setStatus('Enregistré ✓', 'autosave-status--saved');
// Refresh CSRF token from response if provided
r.json().then((data) => {
if (data && data.csrf_token) {
const csrfInput = form.querySelector('input[name="csrf_token"]');
if (csrfInput) csrfInput.value = data.csrf_token;
const meta = document.querySelector('meta[name="csrf-token"]');
if (meta) meta.setAttribute('content', data.csrf_token);
}
}).catch(() => {});
})
.catch(() => {
setStatus('Erreur !', 'autosave-status--error');
dirty = true;
});
};
const schedule = () => {
dirty = true;
setStatus('', '');
clearTimeout(timer);
timer = setTimeout(doSave, DEBOUNCE_MS);
};
// Watch all inputs inside the form
form.addEventListener('input', schedule);
form.addEventListener('change', schedule);
// Clear dirty on manual submit
form.addEventListener('submit', () => {
clearTimeout(timer);
dirty = false;
setStatus('', '');
});
}
})();