This commit is contained in:
2026-01-21 00:15:32 +01:00
parent 9e67b8e30f
commit 5e76717a93
2 changed files with 70 additions and 26 deletions

View File

@@ -15,7 +15,6 @@ export function initEditor() {
const btnClear = document.getElementById('btn-clear-main'); const btnClear = document.getElementById('btn-clear-main');
const editorSelect = document.getElementById('editorTypeSelect'); const editorSelect = document.getElementById('editorTypeSelect');
const versionSelect = document.getElementById('versionSelect'); const versionSelect = document.getElementById('versionSelect');
const btnRestoreVersion = document.getElementById('btn-restore-version');
const craftEditor = initCraftEditor(); const craftEditor = initCraftEditor();
  const prevDlg      = document.getElementById('previewDialog');   const prevDlg      = document.getElementById('previewDialog');
@@ -44,6 +43,9 @@ export function initEditor() {
let versionItems = []; let versionItems = [];
let savedSnapshot = ''; let savedSnapshot = '';
let lastVersionSelection = ''; let lastVersionSelection = '';
let isDirty = false;
let dirtyCleanup = null;
let dialogCancelBound = false;
  const ok  = (m) => toast(m, true);   const ok  = (m) => toast(m, true);
  const err = (m) => toast(m, false);   const err = (m) => toast(m, false);
@@ -88,7 +90,7 @@ export function initEditor() {
function setVersionUiVisible(show) { function setVersionUiVisible(show) {
if (versionSelect) versionSelect.classList.toggle('hidden', !show); if (versionSelect) versionSelect.classList.toggle('hidden', !show);
if (btnRestoreVersion) btnRestoreVersion.classList.toggle('hidden', !show); // restore button removed
} }
setVersionUiVisible(false); setVersionUiVisible(false);
@@ -117,6 +119,38 @@ export function initEditor() {
}; };
} }
function markDirty() {
isDirty = true;
}
function clearDirty() {
isDirty = false;
}
function attachGjsDirtyTracker(editor) {
if (!editor || typeof editor.on !== 'function') return () => {};
const onUpdate = () => markDirty();
editor.on('update', onUpdate);
editor.on('component:update', onUpdate);
return () => {
try {
editor.off('update', onUpdate);
editor.off('component:update', onUpdate);
} catch {}
};
}
function attachCraftDirtyTracker() {
const host = document.getElementById('craftEditor');
if (!host) return () => {};
const handler = () => markDirty();
const events = ['input', 'keydown', 'paste', 'drop'];
events.forEach(evt => host.addEventListener(evt, handler, true));
return () => {
events.forEach(evt => host.removeEventListener(evt, handler, true));
};
}
async function buildCurrentSnapshot() { async function buildCurrentSnapshot() {
if (currentEditorType === 'craftjs') { if (currentEditorType === 'craftjs') {
return buildSnapshot({ return buildSnapshot({
@@ -150,9 +184,11 @@ export function initEditor() {
function setSavedSnapshotFromData(payload) { function setSavedSnapshotFromData(payload) {
const fields = extractContentFields(payload); const fields = extractContentFields(payload);
savedSnapshot = buildSnapshot(fields); savedSnapshot = buildSnapshot(fields);
clearDirty();
} }
async function hasUnsavedChanges() { async function hasUnsavedChanges() {
if (isDirty) return true;
if (!savedSnapshot) return false; if (!savedSnapshot) return false;
const currentSnapshot = await buildCurrentSnapshot(); const currentSnapshot = await buildCurrentSnapshot();
return currentSnapshot !== savedSnapshot; return currentSnapshot !== savedSnapshot;
@@ -245,6 +281,8 @@ export function initEditor() {
if (targetType === 'craftjs') { if (targetType === 'craftjs') {
craftEditor?.setContent(data.html || '', data.craftJson || ''); craftEditor?.setContent(data.html || '', data.craftJson || '');
setSavedSnapshotFromData(payload); setSavedSnapshotFromData(payload);
if (dirtyCleanup) dirtyCleanup();
dirtyCleanup = attachCraftDirtyTracker();
return; return;
} }
const editor = await waitForEditor(3000); const editor = await waitForEditor(3000);
@@ -254,12 +292,16 @@ export function initEditor() {
const project = JSON.parse(jsonRaw); const project = JSON.parse(jsonRaw);
editor.loadProjectData(project); editor.loadProjectData(project);
setSavedSnapshotFromData(payload); setSavedSnapshotFromData(payload);
if (dirtyCleanup) dirtyCleanup();
dirtyCleanup = attachGjsDirtyTracker(editor);
return; return;
} catch {} } catch {}
} }
const html = normalizeSnapshotValue(data.html); const html = normalizeSnapshotValue(data.html);
editor.setComponents(html); editor.setComponents(html);
setSavedSnapshotFromData(payload); setSavedSnapshotFromData(payload);
if (dirtyCleanup) dirtyCleanup();
dirtyCleanup = attachGjsDirtyTracker(editor);
} }
async function loadLatestContentFromServer() { async function loadLatestContentFromServer() {
@@ -553,6 +595,8 @@ export function initEditor() {
if (editorType === 'craftjs') { if (editorType === 'craftjs') {
const craftHtml = extractCraftHtml(craftJson, fresh); const craftHtml = extractCraftHtml(craftJson, fresh);
craftEditor?.setContent(craftHtml, craftJson); craftEditor?.setContent(craftHtml, craftJson);
if (dirtyCleanup) dirtyCleanup();
dirtyCleanup = attachCraftDirtyTracker();
hideVeil(); hideVeil();
if (dlg && typeof dlg.showModal === 'function') dlg.showModal(); if (dlg && typeof dlg.showModal === 'function') dlg.showModal();
if (!looksCraftSerialized(craftJson) && craftEditor?.serializeFromHtml) { if (!looksCraftSerialized(craftJson) && craftEditor?.serializeFromHtml) {
@@ -633,10 +677,24 @@ export function initEditor() {
}; };
    // Jetzt den Editor-Core laden (erst NACH about:blank)     // Jetzt den Editor-Core laden (erst NACH about:blank)
    iframe.src = `editor/editor-core.php?mode=${encodeURIComponent(current.section.slug)}&id=${current.id}&section_id=${current.section.id}&t=${Date.now()}`; iframe.src = `editor/editor-core.php?mode=${encodeURIComponent(current.section.slug)}&id=${current.id}&section_id=${current.section.id}&t=${Date.now()}`;
    dlg?.showModal?.(); dlg?.showModal?.();
  } if (dlg && !dialogCancelBound) {
dlg.addEventListener('cancel', async (ev) => {
ev.preventDefault();
await close();
});
dialogCancelBound = true;
}
waitForEditor(6000)
.then((ed) => {
if (dirtyCleanup) dirtyCleanup();
dirtyCleanup = attachGjsDirtyTracker(ed);
})
.catch(() => {});
}
// ---------- Speichern (DELEGIERT) ---------- // ---------- Speichern (DELEGIERT) ----------
// 🚨 KORRIGIERT: Delegiert Speichern an den iFrame, der die JSON-Daten holt! // 🚨 KORRIGIERT: Delegiert Speichern an den iFrame, der die JSON-Daten holt!
@@ -740,6 +798,8 @@ export function initEditor() {
async function close() { async function close() {
const decision = await confirmUnsavedChanges(); const decision = await confirmUnsavedChanges();
if (decision === 'cancel') return; if (decision === 'cancel') return;
if (dirtyCleanup) dirtyCleanup();
dirtyCleanup = null;
// nächstes Öffnen invalidiert laufende asyncs // nächstes Öffnen invalidiert laufende asyncs
reqToken++; reqToken++;
@@ -754,9 +814,10 @@ export function initEditor() {
    iframe.src = 'about:blank#' + Date.now();     iframe.src = 'about:blank#' + Date.now();
    // Kontext leeren     // Kontext leeren
    current = null; current = null;
    window.__currentItemId = undefined; window.__currentItemId = undefined;
    window.__currentEditorCtx = undefined; window.__currentEditorCtx = undefined;
clearDirty();
} }
async function switchEditor(nextType) { async function switchEditor(nextType) {
@@ -841,23 +902,7 @@ export function initEditor() {
versionSelect.value = previousSelection; versionSelect.value = previousSelection;
} }
}); });
btnRestoreVersion && (btnRestoreVersion.onclick = async () => { // restore button removed
if (!current?.id) return;
const versionId = Number(versionSelect?.value || 0);
if (!versionId) {
err('Bitte eine Version auswählen');
return;
}
if (!confirm('Version wiederherstellen? Der aktuelle Stand wird überschrieben.')) return;
try {
const res = await apiAction('content_versions.restore', { method: 'POST', data: { id: versionId, content_id: current.id } });
if (!res?.ok) throw new Error(res?.error || 'Wiederherstellen fehlgeschlagen');
ok('Version wiederhergestellt');
await open({ id: current.id, name: current.name, section: current.section }, null, current.section);
} catch (e) {
err(e.message || 'Wiederherstellen fehlgeschlagen');
}
});
window.AdminTestSend = window.AdminTestSend || {}; window.AdminTestSend = window.AdminTestSend || {};
window.AdminTestSend.open = (opts = {}) => { window.AdminTestSend.open = (opts = {}) => {

View File

@@ -62,7 +62,6 @@ require __DIR__ . '/../partials/structure/layout_start.php';
<select id="versionSelect" class="input h-8 py-0 text-sm min-w-[200px]"> <select id="versionSelect" class="input h-8 py-0 text-sm min-w-[200px]">
<option value="">Letzte Versionen</option> <option value="">Letzte Versionen</option>
</select> </select>
<button id="btn-restore-version" type="button" class="btn">Wiederherstellen</button>
<button id="btn-clear-main" type="button" class="btn" title="Leeren">🧹</button> <button id="btn-clear-main" type="button" class="btn" title="Leeren">🧹</button>
<button id="btn-preview" type="button" class="btn">Vorschau</button> <button id="btn-preview" type="button" class="btn">Vorschau</button>
<button id="btn-test" type="button" class="btn">Testversand</button> <button id="btn-test" type="button" class="btn">Testversand</button>