diff --git a/public/assets/js/ui-editor.js b/public/assets/js/ui-editor.js
index 443b4c2..da9d8ff 100644
--- a/public/assets/js/ui-editor.js
+++ b/public/assets/js/ui-editor.js
@@ -15,7 +15,6 @@ export function initEditor() {
const btnClear = document.getElementById('btn-clear-main');
const editorSelect = document.getElementById('editorTypeSelect');
const versionSelect = document.getElementById('versionSelect');
- const btnRestoreVersion = document.getElementById('btn-restore-version');
const craftEditor = initCraftEditor();
const prevDlg = document.getElementById('previewDialog');
@@ -44,6 +43,9 @@ export function initEditor() {
let versionItems = [];
let savedSnapshot = '';
let lastVersionSelection = '';
+ let isDirty = false;
+ let dirtyCleanup = null;
+ let dialogCancelBound = false;
const ok = (m) => toast(m, true);
const err = (m) => toast(m, false);
@@ -88,7 +90,7 @@ export function initEditor() {
function setVersionUiVisible(show) {
if (versionSelect) versionSelect.classList.toggle('hidden', !show);
- if (btnRestoreVersion) btnRestoreVersion.classList.toggle('hidden', !show);
+ // restore button removed
}
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() {
if (currentEditorType === 'craftjs') {
return buildSnapshot({
@@ -150,9 +184,11 @@ export function initEditor() {
function setSavedSnapshotFromData(payload) {
const fields = extractContentFields(payload);
savedSnapshot = buildSnapshot(fields);
+ clearDirty();
}
async function hasUnsavedChanges() {
+ if (isDirty) return true;
if (!savedSnapshot) return false;
const currentSnapshot = await buildCurrentSnapshot();
return currentSnapshot !== savedSnapshot;
@@ -245,6 +281,8 @@ export function initEditor() {
if (targetType === 'craftjs') {
craftEditor?.setContent(data.html || '', data.craftJson || '');
setSavedSnapshotFromData(payload);
+ if (dirtyCleanup) dirtyCleanup();
+ dirtyCleanup = attachCraftDirtyTracker();
return;
}
const editor = await waitForEditor(3000);
@@ -254,12 +292,16 @@ export function initEditor() {
const project = JSON.parse(jsonRaw);
editor.loadProjectData(project);
setSavedSnapshotFromData(payload);
+ if (dirtyCleanup) dirtyCleanup();
+ dirtyCleanup = attachGjsDirtyTracker(editor);
return;
} catch {}
}
const html = normalizeSnapshotValue(data.html);
editor.setComponents(html);
setSavedSnapshotFromData(payload);
+ if (dirtyCleanup) dirtyCleanup();
+ dirtyCleanup = attachGjsDirtyTracker(editor);
}
async function loadLatestContentFromServer() {
@@ -553,6 +595,8 @@ export function initEditor() {
if (editorType === 'craftjs') {
const craftHtml = extractCraftHtml(craftJson, fresh);
craftEditor?.setContent(craftHtml, craftJson);
+ if (dirtyCleanup) dirtyCleanup();
+ dirtyCleanup = attachCraftDirtyTracker();
hideVeil();
if (dlg && typeof dlg.showModal === 'function') dlg.showModal();
if (!looksCraftSerialized(craftJson) && craftEditor?.serializeFromHtml) {
@@ -633,10 +677,24 @@ export function initEditor() {
};
// Jetzt den Editor-Core laden (erst NACH about:blank)
- iframe.src = `editor/editor-core.php?mode=${encodeURIComponent(current.section.slug)}&id=${current.id}§ion_id=${current.section.id}&t=${Date.now()}`;
+ iframe.src = `editor/editor-core.php?mode=${encodeURIComponent(current.section.slug)}&id=${current.id}§ion_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) ----------
// 🚨 KORRIGIERT: Delegiert Speichern an den iFrame, der die JSON-Daten holt!
@@ -740,6 +798,8 @@ export function initEditor() {
async function close() {
const decision = await confirmUnsavedChanges();
if (decision === 'cancel') return;
+ if (dirtyCleanup) dirtyCleanup();
+ dirtyCleanup = null;
// nächstes Öffnen invalidiert laufende asyncs
reqToken++;
@@ -754,9 +814,10 @@ export function initEditor() {
iframe.src = 'about:blank#' + Date.now();
// Kontext leeren
- current = null;
- window.__currentItemId = undefined;
- window.__currentEditorCtx = undefined;
+ current = null;
+ window.__currentItemId = undefined;
+ window.__currentEditorCtx = undefined;
+ clearDirty();
}
async function switchEditor(nextType) {
@@ -841,23 +902,7 @@ export function initEditor() {
versionSelect.value = previousSelection;
}
});
- btnRestoreVersion && (btnRestoreVersion.onclick = async () => {
- 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');
- }
- });
+ // restore button removed
window.AdminTestSend = window.AdminTestSend || {};
window.AdminTestSend.open = (opts = {}) => {
diff --git a/public/index.php b/public/index.php
index b3ca15b..83cb2d0 100644
--- a/public/index.php
+++ b/public/index.php
@@ -62,7 +62,6 @@ require __DIR__ . '/../partials/structure/layout_start.php';
-