diff --git a/public/assets/js/ui-editor.js b/public/assets/js/ui-editor.js index e7144a0..0027dc4 100644 --- a/public/assets/js/ui-editor.js +++ b/public/assets/js/ui-editor.js @@ -38,6 +38,7 @@ export function initEditor() { let senderLoadPromise = null; let currentEditorType = 'grapesjs'; let versionItems = []; + let savedSnapshot = '';   const ok  = (m) => toast(m, true);   const err = (m) => toast(m, false); @@ -87,6 +88,83 @@ export function initEditor() { setVersionUiVisible(false); + function normalizeSnapshotValue(value) { + return value === null || value === undefined ? '' : String(value); + } + + function buildSnapshot({ editorType, html, json, craftJson }) { + return JSON.stringify({ + editorType: normalizeSnapshotValue(editorType), + html: normalizeSnapshotValue(html), + json: normalizeSnapshotValue(json), + craftJson: normalizeSnapshotValue(craftJson), + }); + } + + function extractContentFields(payload = {}) { + const base = payload || {}; + const item = base.item || {}; + return { + html: base.html ?? base.html_content ?? item.html ?? item.html_content ?? '', + json: base.content ?? base.json_content ?? item.content ?? item.json_content ?? '', + craftJson: base.craft_json ?? item.craft_json ?? '', + editorType: String(base.editor_type ?? item.editor_type ?? currentEditorType ?? 'grapesjs').toLowerCase(), + }; + } + + async function buildCurrentSnapshot() { + if (currentEditorType === 'craftjs') { + return buildSnapshot({ + editorType: 'craftjs', + html: craftEditor ? craftEditor.getContent() : '', + json: '', + craftJson: craftEditor && craftEditor.getCraftJson ? craftEditor.getCraftJson() : '', + }); + } + try { + const editor = await waitForEditor(2000); + const win = iframe?.contentWindow; + const fontCss = win?.BridgeParts?.RTE_FONT_FACE_CSS || ''; + const cssPayload = (fontCss ? fontCss + '\n' : '') + (editor.getCss() || ''); + const htmlContent = (editor.getHtml() || '') + ''; + let jsonRaw = ''; + try { + jsonRaw = JSON.stringify(editor.getProjectData()); + } catch {} + return buildSnapshot({ + editorType: 'grapesjs', + html: htmlContent, + json: jsonRaw, + craftJson: '', + }); + } catch { + return buildSnapshot({ editorType: currentEditorType, html: '', json: '', craftJson: '' }); + } + } + + function setSavedSnapshotFromData(payload) { + const fields = extractContentFields(payload); + savedSnapshot = buildSnapshot(fields); + } + + async function hasUnsavedChanges() { + if (!savedSnapshot) return false; + const currentSnapshot = await buildCurrentSnapshot(); + return currentSnapshot !== savedSnapshot; + } + + async function confirmUnsavedChanges() { + const dirty = await hasUnsavedChanges(); + if (!dirty) return true; + const shouldSave = window.confirm('Ungespeicherte Änderungen gefunden. Jetzt speichern?'); + if (shouldSave) { + const okSave = await save(); + if (!okSave) return false; + return true; + } + return true; + } + function renderVersionOptions(items) { versionItems = items || []; if (!versionSelect) return; @@ -125,6 +203,33 @@ export function initEditor() { } } + async function applyVersionPayload(payload) { + const data = extractContentFields(payload); + const targetType = data.editorType === 'craftjs' ? 'craftjs' : 'grapesjs'; + setEditorType(targetType); + if (targetType === 'craftjs') { + craftEditor?.setContent(data.html || '', data.craftJson || ''); + return; + } + const editor = await waitForEditor(3000); + const jsonRaw = normalizeSnapshotValue(data.json).trim(); + if (jsonRaw) { + try { + const project = JSON.parse(jsonRaw); + editor.loadProjectData(project); + return; + } catch {} + } + const html = normalizeSnapshotValue(data.html); + editor.setComponents(html); + } + + async function loadLatestContentFromServer() { + const res = await apiAction('content.get', { method: 'GET', data: { id: current.id, section_id: current.section.id } }); + await applyVersionPayload(res || {}); + setSavedSnapshotFromData(res || {}); + } +   function writeHtmlToFrame(html) {     iframe.srcdoc = `       @@ -404,6 +509,7 @@ export function initEditor() {     ]); editorType = editorType === 'craftjs' ? 'craftjs' : 'grapesjs'; + setSavedSnapshotFromData({ html: fresh, content: jsonState, editor_type: editorType, craft_json: craftJson }); setEditorType(editorType); if (editorType === 'craftjs') { const craftHtml = extractCraftHtml(craftJson, fresh); @@ -512,7 +618,15 @@ export function initEditor() { } const okSave = await delegateCommand('save-data'); - if (okSave) setTimeout(loadVersionsForCurrent, 800); + if (okSave) { + setTimeout(async () => { + await loadVersionsForCurrent(); + try { + const res = await apiAction('content.get', { method: 'GET', data: { id: current.id, section_id: current.section.id } }); + setSavedSnapshotFromData(res || {}); + } catch {} + }, 800); + } return okSave; } @@ -584,9 +698,11 @@ export function initEditor() {   }   function closePreview(){ prevDlg?.close?.(); } - function close() { -    // nächstes Öffnen invalidiert laufende asyncs -    reqToken++; + async function close() { + const proceed = await confirmUnsavedChanges(); + if (!proceed) return; + // nächstes Öffnen invalidiert laufende asyncs + reqToken++;     try { iframe.contentWindow?.postMessage({source:'admin',type:'reset'}, '*'); } catch {}     if (bridgeListener) window.removeEventListener('message', bridgeListener); @@ -662,6 +778,26 @@ export function initEditor() { btnCancelSend&& (btnCancelSend.onclick= closeSend); sendForm && (sendForm.onsubmit = doSend); editorSelect && (editorSelect.onchange = () => switchEditor(editorSelect.value)); + versionSelect && (versionSelect.onchange = async () => { + if (!current?.id) return; + const proceed = await confirmUnsavedChanges(); + if (!proceed) { + versionSelect.value = ''; + return; + } + const versionId = Number(versionSelect.value || 0); + if (!versionId) { + await loadLatestContentFromServer(); + return; + } + try { + const res = await apiAction('content_versions.get', { method: 'GET', data: { id: versionId, content_id: current.id } }); + if (!res?.ok) throw new Error(res?.error || 'Version konnte nicht geladen werden'); + await applyVersionPayload(res?.item || res); + } catch (e) { + err(e.message || 'Version konnte nicht geladen werden'); + } + }); btnRestoreVersion && (btnRestoreVersion.onclick = async () => { if (!current?.id) return; const versionId = Number(versionSelect?.value || 0);