inclide craft
This commit is contained in:
@@ -68,6 +68,11 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
|
|||||||
<button type="button" class="btn" data-rotate="external">Neu erstellen</button>
|
<button type="button" class="btn" data-rotate="external">Neu erstellen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<label class="block text-sm text-slate-600">Standard-Editor</label>
|
||||||
|
<select name="editor_default" class="input">
|
||||||
|
<option value="grapesjs">GrapesJS</option>
|
||||||
|
<option value="craftjs">Craft.js</option>
|
||||||
|
</select>
|
||||||
<div class="flex justify-between gap-2 flex-wrap pt-2">
|
<div class="flex justify-between gap-2 flex-wrap pt-2">
|
||||||
<div class="flex gap-2" data-role="admin">
|
<div class="flex gap-2" data-role="admin">
|
||||||
<button type="button" class="btn" data-download="bridge">Bridge-Datei</button>
|
<button type="button" class="btn" data-download="bridge">Bridge-Datei</button>
|
||||||
|
|||||||
@@ -66,6 +66,14 @@ if ($debugRedirect) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"react": "/vendor/react@18.2.0/es2022/react.mjs",
|
||||||
|
"react-dom": "/vendor/react-dom@18.2.0/es2022/react-dom.bundle.mjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<?php tpl_render_styles(null, 'header'); ?>
|
<?php tpl_render_styles(null, 'header'); ?>
|
||||||
<style>
|
<style>
|
||||||
.btn{display:inline-flex;align-items:center;gap:.4rem;padding:.35rem .7rem;border-radius:.75rem;border:1px solid #e5e7eb;background:#fff;font-size:.9rem;cursor:pointer;}
|
.btn{display:inline-flex;align-items:center;gap:.4rem;padding:.35rem .7rem;border-radius:.75rem;border:1px solid #e5e7eb;background:#fff;font-size:.9rem;cursor:pointer;}
|
||||||
|
|||||||
@@ -165,3 +165,9 @@ body.page-login {
|
|||||||
.gjs-block-category[data-id="Bibliothek"] {
|
.gjs-block-category[data-id="Bibliothek"] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.craft-editor-shell{display:flex;flex-direction:column;height:100%;}
|
||||||
|
.craft-editor-toolbar{display:flex;gap:.5rem;padding:.5rem;border-bottom:1px solid #e2e8f0;background:#f8fafc;}
|
||||||
|
.craft-editor-canvas{flex:1;min-height:300px;overflow:auto;padding:12px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;}
|
||||||
|
.craft-container{min-height:40px;}
|
||||||
|
.craft-text:focus{outline:2px solid #38bdf8;outline-offset:2px;}
|
||||||
|
|||||||
251
public/assets/js/craft-editor.js
Normal file
251
public/assets/js/craft-editor.js
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
// assets/js/craft-editor.js
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import {
|
||||||
|
Editor,
|
||||||
|
Frame,
|
||||||
|
Element,
|
||||||
|
useEditor,
|
||||||
|
useNode,
|
||||||
|
} from '/vendor/@craftjs/core@0.2.12/X-ZXJlYWN0LHJlYWN0LWRvbQ/es2022/core.bundle.mjs';
|
||||||
|
|
||||||
|
const RootCanvas = ({ children }) => {
|
||||||
|
return React.createElement(
|
||||||
|
'div',
|
||||||
|
{ id: 'craftCanvas', className: 'craft-editor-canvas' },
|
||||||
|
children
|
||||||
|
);
|
||||||
|
};
|
||||||
|
RootCanvas.craft = {
|
||||||
|
displayName: 'RootCanvas',
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Container = ({ children }) => {
|
||||||
|
return React.createElement('div', { className: 'craft-container' }, children);
|
||||||
|
};
|
||||||
|
Container.craft = {
|
||||||
|
displayName: 'Container',
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Text = ({ text, tag }) => {
|
||||||
|
const {
|
||||||
|
connectors: { connect, drag },
|
||||||
|
actions,
|
||||||
|
} = useNode();
|
||||||
|
const Tag = tag || 'div';
|
||||||
|
|
||||||
|
return React.createElement(Tag, {
|
||||||
|
ref: (ref) => ref && connect(drag(ref)),
|
||||||
|
className: 'craft-text',
|
||||||
|
'data-craft-text': '1',
|
||||||
|
contentEditable: true,
|
||||||
|
suppressContentEditableWarning: true,
|
||||||
|
onInput: (ev) => {
|
||||||
|
const html = ev.currentTarget.innerHTML;
|
||||||
|
actions.setProp((props) => {
|
||||||
|
props.text = html;
|
||||||
|
}, 120);
|
||||||
|
},
|
||||||
|
dangerouslySetInnerHTML: { __html: text || '' },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Text.craft = {
|
||||||
|
displayName: 'Text',
|
||||||
|
props: {
|
||||||
|
text: '',
|
||||||
|
tag: 'div',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Toolbar = ({ onAddText, onAddContainer }) => {
|
||||||
|
return React.createElement(
|
||||||
|
'div',
|
||||||
|
{ className: 'craft-editor-toolbar' },
|
||||||
|
React.createElement('button', { type: 'button', className: 'btn', onClick: onAddText }, 'Text'),
|
||||||
|
React.createElement('button', { type: 'button', className: 'btn', onClick: onAddContainer }, 'Container')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditorBridge = ({ onReady }) => {
|
||||||
|
const { actions, query } = useEditor();
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (onReady) {
|
||||||
|
onReady.current = { actions, query };
|
||||||
|
}
|
||||||
|
}, [actions, query, onReady]);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CraftApp = ({ initialData, onReady }) => {
|
||||||
|
return React.createElement(
|
||||||
|
'div',
|
||||||
|
{ className: 'craft-editor-shell' },
|
||||||
|
React.createElement(Toolbar, {
|
||||||
|
onAddText: () => {
|
||||||
|
if (!onReady?.current) return;
|
||||||
|
const { actions, query } = onReady.current;
|
||||||
|
const nodeTree = query
|
||||||
|
.parseReactElement(React.createElement(Text, { text: 'Neuer Text' }))
|
||||||
|
.toNodeTree();
|
||||||
|
actions.addNodeTree(nodeTree, 'ROOT');
|
||||||
|
},
|
||||||
|
onAddContainer: () => {
|
||||||
|
if (!onReady?.current) return;
|
||||||
|
const { actions, query } = onReady.current;
|
||||||
|
const nodeTree = query
|
||||||
|
.parseReactElement(React.createElement(Element, { is: Container, canvas: true }))
|
||||||
|
.toNodeTree();
|
||||||
|
actions.addNodeTree(nodeTree, 'ROOT');
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
React.createElement(
|
||||||
|
Editor,
|
||||||
|
{ resolver: { RootCanvas, Container, Text } },
|
||||||
|
React.createElement(EditorBridge, { onReady }),
|
||||||
|
React.createElement(
|
||||||
|
Frame,
|
||||||
|
{ data: initialData || undefined },
|
||||||
|
React.createElement(Element, { is: RootCanvas, canvas: true })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildSerializedFromHtml = (html) => {
|
||||||
|
const textId = `text-${Math.random().toString(36).slice(2, 9)}`;
|
||||||
|
const data = {
|
||||||
|
ROOT: {
|
||||||
|
type: { resolvedName: 'RootCanvas' },
|
||||||
|
isCanvas: true,
|
||||||
|
props: {},
|
||||||
|
displayName: 'RootCanvas',
|
||||||
|
nodes: [textId],
|
||||||
|
linkedNodes: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
data[textId] = {
|
||||||
|
type: { resolvedName: 'Text' },
|
||||||
|
isCanvas: false,
|
||||||
|
props: { text: html || '', tag: 'div' },
|
||||||
|
displayName: 'Text',
|
||||||
|
parent: 'ROOT',
|
||||||
|
nodes: [],
|
||||||
|
linkedNodes: {},
|
||||||
|
};
|
||||||
|
return JSON.stringify(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const looksSerialized = (payload) => {
|
||||||
|
if (!payload) return false;
|
||||||
|
try {
|
||||||
|
const parsed = typeof payload === 'string' ? JSON.parse(payload) : payload;
|
||||||
|
return !!(parsed && typeof parsed === 'object' && parsed.ROOT);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCanvasHtml = () => {
|
||||||
|
const canvas = document.getElementById('craftCanvas');
|
||||||
|
if (!canvas) return '';
|
||||||
|
if (canvas.children.length === 1 && canvas.children[0]?.dataset?.craftText === '1') {
|
||||||
|
return canvas.children[0].innerHTML;
|
||||||
|
}
|
||||||
|
return canvas.innerHTML;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function initCraftEditor() {
|
||||||
|
const container = document.getElementById('craftEditor');
|
||||||
|
const mount = document.getElementById('craftEditorMount');
|
||||||
|
if (!container || !mount) return null;
|
||||||
|
|
||||||
|
let mounted = false;
|
||||||
|
let pendingSerialized = null;
|
||||||
|
const bridgeRef = { current: null };
|
||||||
|
|
||||||
|
const ensureMount = (initialData) => {
|
||||||
|
if (mounted) return;
|
||||||
|
mounted = true;
|
||||||
|
if (ReactDOM.createRoot) {
|
||||||
|
const root = ReactDOM.createRoot(mount);
|
||||||
|
root.render(React.createElement(CraftApp, { initialData, onReady: bridgeRef }));
|
||||||
|
} else {
|
||||||
|
ReactDOM.render(React.createElement(CraftApp, { initialData, onReady: bridgeRef }), mount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applySerialized = (serialized) => {
|
||||||
|
const api = bridgeRef.current;
|
||||||
|
if (!api) {
|
||||||
|
pendingSerialized = serialized;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
api.actions.deserialize(serialized);
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const flushPending = () => {
|
||||||
|
if (pendingSerialized && bridgeRef.current) {
|
||||||
|
const next = pendingSerialized;
|
||||||
|
pendingSerialized = null;
|
||||||
|
applySerialized(next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pendingSerialized && !bridgeRef.current) {
|
||||||
|
setTimeout(flushPending, 60);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initWithSerialized = (serialized) => {
|
||||||
|
if (!mounted) {
|
||||||
|
ensureMount(serialized ? JSON.parse(serialized) : null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (serialized) {
|
||||||
|
applySerialized(serialized);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
show() {
|
||||||
|
container.classList.remove('hidden');
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
container.classList.add('hidden');
|
||||||
|
},
|
||||||
|
setContent(html, craftJson) {
|
||||||
|
const useCraft = looksSerialized(craftJson);
|
||||||
|
const serialized = useCraft
|
||||||
|
? String(craftJson)
|
||||||
|
: buildSerializedFromHtml(html || '');
|
||||||
|
initWithSerialized(serialized);
|
||||||
|
flushPending();
|
||||||
|
},
|
||||||
|
getContent() {
|
||||||
|
return getCanvasHtml();
|
||||||
|
},
|
||||||
|
getCraftJson() {
|
||||||
|
const api = bridgeRef.current;
|
||||||
|
if (!api) return pendingSerialized || '';
|
||||||
|
try {
|
||||||
|
return api.query.serialize();
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
serializeFromHtml(html) {
|
||||||
|
return buildSerializedFromHtml(html || '');
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
const empty = buildSerializedFromHtml('');
|
||||||
|
initWithSerialized(empty);
|
||||||
|
},
|
||||||
|
focus() {
|
||||||
|
const canvas = document.getElementById('craftCanvas');
|
||||||
|
if (canvas) canvas.focus();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,16 +2,19 @@
|
|||||||
// Öffnen, Befüllen, Speichern (mit Live-HTML), Preview – Race-Schutz & Lade-Overlay.
|
// Öffnen, Befüllen, Speichern (mit Live-HTML), Preview – Race-Schutz & Lade-Overlay.
|
||||||
|
|
||||||
import { apiUpdate, apiList, apiGet, toast, apiAction } from './api.js';
|
import { apiUpdate, apiList, apiGet, toast, apiAction } from './api.js';
|
||||||
|
import { initCraftEditor } from './craft-editor.js';
|
||||||
|
|
||||||
export function initEditor() {
|
export function initEditor() {
|
||||||
// ... (Alle Konstanten bleiben unverändert) ...
|
// ... (Alle Konstanten bleiben unverändert) ...
|
||||||
const dlg = document.getElementById('editorDialog');
|
const dlg = document.getElementById('editorDialog');
|
||||||
const iframe = document.getElementById('editorFrame');
|
const iframe = document.getElementById('editorFrame');
|
||||||
const btnSave = document.getElementById('btn-save');
|
const btnSave = document.getElementById('btn-save');
|
||||||
const btnPreview = document.getElementById('btn-preview');
|
const btnPreview = document.getElementById('btn-preview');
|
||||||
const btnTest = document.getElementById('btn-test');
|
const btnTest = document.getElementById('btn-test');
|
||||||
const btnClose = document.getElementById('btn-close');
|
const btnClose = document.getElementById('btn-close');
|
||||||
const btnClear = document.getElementById('btn-clear-main');
|
const btnClear = document.getElementById('btn-clear-main');
|
||||||
|
const editorSelect = document.getElementById('editorTypeSelect');
|
||||||
|
const craftEditor = initCraftEditor();
|
||||||
|
|
||||||
const prevDlg = document.getElementById('previewDialog');
|
const prevDlg = document.getElementById('previewDialog');
|
||||||
const sendDlg = document.getElementById('sendTestDialog');
|
const sendDlg = document.getElementById('sendTestDialog');
|
||||||
@@ -26,11 +29,12 @@ export function initEditor() {
|
|||||||
const prevFrame = document.getElementById('previewFrame');
|
const prevFrame = document.getElementById('previewFrame');
|
||||||
const btnPrevClose = document.getElementById('btn-close-preview');
|
const btnPrevClose = document.getElementById('btn-close-preview');
|
||||||
|
|
||||||
let current = null; // { resource, id, name }
|
let current = null; // { resource, id, name }
|
||||||
let bridgeListener = null;
|
let bridgeListener = null;
|
||||||
let reqToken = 0; // steigender Token pro Öffnen -> ignoriert verspätete Events
|
let reqToken = 0; // steigender Token pro Öffnen -> ignoriert verspätete Events
|
||||||
let senderOptions = [];
|
let senderOptions = [];
|
||||||
let senderLoadPromise = null;
|
let senderLoadPromise = null;
|
||||||
|
let currentEditorType = 'grapesjs';
|
||||||
|
|
||||||
const ok = (m) => toast(m, true);
|
const ok = (m) => toast(m, true);
|
||||||
const err = (m) => toast(m, false);
|
const err = (m) => toast(m, false);
|
||||||
@@ -69,20 +73,23 @@ export function initEditor() {
|
|||||||
</html>`;
|
</html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readEditedHtml() {
|
async function readEditedHtml() {
|
||||||
const win = iframe?.contentWindow;
|
if (currentEditorType === 'craftjs') {
|
||||||
const doc = iframe?.contentDocument;
|
return craftEditor ? craftEditor.getContent() : '';
|
||||||
if (!win || !doc) return '';
|
}
|
||||||
|
const win = iframe?.contentWindow;
|
||||||
|
const doc = iframe?.contentDocument;
|
||||||
|
if (!win || !doc) return '';
|
||||||
|
|
||||||
const ed = win.__gjs || (win.grapesjs && win.grapesjs.editors && win.grapesjs.editors[0]) || null;
|
const ed = win.__gjs || (win.grapesjs && win.grapesjs.editors && win.grapesjs.editors[0]) || null;
|
||||||
if (ed && typeof ed.getHtml === 'function') {
|
if (ed && typeof ed.getHtml === 'function') {
|
||||||
const html = ed.getHtml();
|
const html = ed.getHtml();
|
||||||
const css = (typeof ed.getCss === 'function') ? ed.getCss() : '';
|
const css = (typeof ed.getCss === 'function') ? ed.getCss() : '';
|
||||||
return css ? `<style>${css}</style>\n${html}` : html;
|
return css ? `<style>${css}</style>\n${html}` : html;
|
||||||
}
|
}
|
||||||
const root = doc.querySelector('#gjs') || doc.body || doc.documentElement;
|
const root = doc.querySelector('#gjs') || doc.body || doc.documentElement;
|
||||||
return root ? root.innerHTML : '';
|
return root ? root.innerHTML : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function waitForEditor(maxMs = 8000) {
|
function waitForEditor(maxMs = 8000) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -98,7 +105,7 @@ export function initEditor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 🚨 NEUE FUNKTION: Delegiert das Kommando an den Editor im iFrame
|
// 🚨 NEUE FUNKTION: Delegiert das Kommando an den Editor im iFrame
|
||||||
async function delegateCommand(commandName) {
|
async function delegateCommand(commandName) {
|
||||||
try {
|
try {
|
||||||
const editor = await waitForEditor(3000);
|
const editor = await waitForEditor(3000);
|
||||||
if (editor.Commands.has(commandName)) {
|
if (editor.Commands.has(commandName)) {
|
||||||
@@ -114,9 +121,9 @@ export function initEditor() {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ... (hideReadyBadge bleibt unverändert) ...
|
// ... (hideReadyBadge bleibt unverändert) ...
|
||||||
function hideReadyBadge(doc) {
|
function hideReadyBadge(doc) {
|
||||||
if (!doc) return;
|
if (!doc) return;
|
||||||
const kill = () => {
|
const kill = () => {
|
||||||
const el = doc.getElementById('badge');
|
const el = doc.getElementById('badge');
|
||||||
@@ -143,7 +150,40 @@ export function initEditor() {
|
|||||||
setTimeout(() => { kill(); /* hideByText(doc); */ }, 150);
|
setTimeout(() => { kill(); /* hideByText(doc); */ }, 150);
|
||||||
setTimeout(() => { kill(); /* hideByText(doc); */ }, 500);
|
setTimeout(() => { kill(); /* hideByText(doc); */ }, 500);
|
||||||
setTimeout(() => { kill(); /* hideByText(doc); */ }, 1200);
|
setTimeout(() => { kill(); /* hideByText(doc); */ }, 1200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractCraftHtml(craftJson, fallbackHtml) {
|
||||||
|
if (!craftJson) return fallbackHtml || '';
|
||||||
|
try {
|
||||||
|
const parsed = typeof craftJson === 'string' ? JSON.parse(craftJson) : craftJson;
|
||||||
|
if (parsed && typeof parsed.html === 'string') {
|
||||||
|
return parsed.html;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return fallbackHtml || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function looksCraftSerialized(payload) {
|
||||||
|
if (!payload) return false;
|
||||||
|
try {
|
||||||
|
const parsed = typeof payload === 'string' ? JSON.parse(payload) : payload;
|
||||||
|
return !!(parsed && typeof parsed === 'object' && parsed.ROOT);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEditorType(nextType) {
|
||||||
|
currentEditorType = nextType === 'craftjs' ? 'craftjs' : 'grapesjs';
|
||||||
|
if (editorSelect) editorSelect.value = currentEditorType;
|
||||||
|
if (currentEditorType === 'craftjs') {
|
||||||
|
iframe?.classList?.add('hidden');
|
||||||
|
craftEditor?.show();
|
||||||
|
} else {
|
||||||
|
craftEditor?.hide();
|
||||||
|
iframe?.classList?.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ... (Lade-Overlay bleibt unverändert) ...
|
// ... (Lade-Overlay bleibt unverändert) ...
|
||||||
let veilEl = null;
|
let veilEl = null;
|
||||||
@@ -328,6 +368,9 @@ export function initEditor() {
|
|||||||
let hasJson = false;
|
let hasJson = false;
|
||||||
let jsonState = '';
|
let jsonState = '';
|
||||||
|
|
||||||
|
let editorType = 'grapesjs';
|
||||||
|
let craftJson = '';
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
(async() => {
|
(async() => {
|
||||||
try {
|
try {
|
||||||
@@ -342,12 +385,34 @@ export function initEditor() {
|
|||||||
if (!fresh && !looksJson) {
|
if (!fresh && !looksJson) {
|
||||||
fresh = rawContent;
|
fresh = rawContent;
|
||||||
}
|
}
|
||||||
|
editorType = String(row?.editor_type ?? row?.item?.editor_type ?? 'grapesjs').toLowerCase();
|
||||||
|
craftJson = row?.craft_json ?? row?.item?.craft_json ?? '';
|
||||||
} catch {}
|
} catch {}
|
||||||
})(),
|
})(),
|
||||||
(async() => { snippets = await buildSnippetsForContext(current); })(),
|
(async() => { snippets = await buildSnippetsForContext(current); })(),
|
||||||
(async() => { refLib = await buildRefLibForContext(current); })()
|
(async() => { refLib = await buildRefLibForContext(current); })()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
editorType = editorType === 'craftjs' ? 'craftjs' : 'grapesjs';
|
||||||
|
setEditorType(editorType);
|
||||||
|
if (editorType === 'craftjs') {
|
||||||
|
const craftHtml = extractCraftHtml(craftJson, fresh);
|
||||||
|
craftEditor?.setContent(craftHtml, craftJson);
|
||||||
|
hideVeil();
|
||||||
|
if (dlg && typeof dlg.showModal === 'function') dlg.showModal();
|
||||||
|
if (!looksCraftSerialized(craftJson) && craftEditor?.serializeFromHtml) {
|
||||||
|
const seed = craftEditor.serializeFromHtml(craftHtml);
|
||||||
|
try {
|
||||||
|
await apiUpdate(current.resource, current.id, {
|
||||||
|
editor_type: 'craftjs',
|
||||||
|
html: craftHtml,
|
||||||
|
craft_json: seed
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// iFrame-Load -> Bridge-Ready abhören
|
// iFrame-Load -> Bridge-Ready abhören
|
||||||
iframe.onload = function () {
|
iframe.onload = function () {
|
||||||
if (myToken !== reqToken) return;
|
if (myToken !== reqToken) return;
|
||||||
@@ -417,21 +482,37 @@ export function initEditor() {
|
|||||||
dlg?.showModal?.();
|
dlg?.showModal?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 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!
|
||||||
async function save() {
|
async function save() {
|
||||||
if (!current?.id) return err('Keine aktive ID');
|
if (!current?.id) return err('Keine aktive ID');
|
||||||
|
|
||||||
return delegateCommand('save-data');
|
if (currentEditorType === 'craftjs') {
|
||||||
}
|
const html = craftEditor ? craftEditor.getContent() : '';
|
||||||
|
const craftJson = craftEditor && craftEditor.getCraftJson
|
||||||
|
? craftEditor.getCraftJson()
|
||||||
|
: JSON.stringify({ html });
|
||||||
|
const payload = { html, craft_json: craftJson, editor_type: 'craftjs' };
|
||||||
|
const res = await apiUpdate(current.resource, current.id, payload);
|
||||||
|
if (res?.ok) ok('Gespeichert');
|
||||||
|
else err(res?.error || 'Speichern fehlgeschlagen');
|
||||||
|
return res?.ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
return delegateCommand('save-data');
|
||||||
|
}
|
||||||
|
|
||||||
// ... (Der Rest der Funktionen bleibt unverändert) ...
|
// ... (Der Rest der Funktionen bleibt unverändert) ...
|
||||||
async function clearEditor() {
|
async function clearEditor() {
|
||||||
const win = iframe?.contentWindow;
|
if (currentEditorType === 'craftjs') {
|
||||||
const ed = win?.__gjs || (win?.grapesjs && win.grapesjs.editors && win.grapesjs.editors[0]) || null;
|
craftEditor?.clear();
|
||||||
if (ed) {
|
return;
|
||||||
ed.setComponents('');
|
}
|
||||||
ed.setStyle('');
|
const win = iframe?.contentWindow;
|
||||||
|
const ed = win?.__gjs || (win?.grapesjs && win.grapesjs.editors && win.grapesjs.editors[0]) || null;
|
||||||
|
if (ed) {
|
||||||
|
ed.setComponents('');
|
||||||
|
ed.setStyle('');
|
||||||
} else {
|
} else {
|
||||||
writeHtmlToFrame('');
|
writeHtmlToFrame('');
|
||||||
}
|
}
|
||||||
@@ -485,7 +566,7 @@ export function initEditor() {
|
|||||||
}
|
}
|
||||||
function closePreview(){ prevDlg?.close?.(); }
|
function closePreview(){ prevDlg?.close?.(); }
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
// nächstes Öffnen invalidiert laufende asyncs
|
// nächstes Öffnen invalidiert laufende asyncs
|
||||||
reqToken++;
|
reqToken++;
|
||||||
|
|
||||||
@@ -503,17 +584,64 @@ export function initEditor() {
|
|||||||
current = null;
|
current = null;
|
||||||
window.__currentItemId = undefined;
|
window.__currentItemId = undefined;
|
||||||
window.__currentEditorCtx = undefined;
|
window.__currentEditorCtx = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buttons
|
async function switchEditor(nextType) {
|
||||||
btnSave && (btnSave.onclick = save);
|
if (!current?.id) return;
|
||||||
btnClear && (btnClear.onclick = clearEditor);
|
const target = nextType === 'craftjs' ? 'craftjs' : 'grapesjs';
|
||||||
btnClose && (btnClose.onclick = close);
|
if (target === currentEditorType) return;
|
||||||
|
const confirmed = window.confirm('Editor wechseln? Ungespeicherte Änderungen gehen verloren.');
|
||||||
|
if (!confirmed) {
|
||||||
|
if (editorSelect) editorSelect.value = currentEditorType;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentEditorType === 'grapesjs' && target === 'craftjs') {
|
||||||
|
const html = await readEditedHtml();
|
||||||
|
const craftJson = craftEditor && craftEditor.serializeFromHtml
|
||||||
|
? craftEditor.serializeFromHtml(html)
|
||||||
|
: JSON.stringify({ html });
|
||||||
|
const res = await apiUpdate(current.resource, current.id, {
|
||||||
|
editor_type: 'craftjs',
|
||||||
|
html,
|
||||||
|
craft_json: craftJson
|
||||||
|
});
|
||||||
|
if (!res?.ok) {
|
||||||
|
err(res?.error || 'Editorwechsel fehlgeschlagen');
|
||||||
|
if (editorSelect) editorSelect.value = currentEditorType;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setEditorType('craftjs');
|
||||||
|
craftEditor?.setContent(html, craftJson);
|
||||||
|
iframe.src = 'about:blank#' + Date.now();
|
||||||
|
ok('Editor gewechselt');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentEditorType === 'craftjs' && target === 'grapesjs') {
|
||||||
|
const html = craftEditor ? craftEditor.getContent() : '';
|
||||||
|
const res = await apiUpdate(current.resource, current.id, {
|
||||||
|
editor_type: 'grapesjs',
|
||||||
|
html
|
||||||
|
});
|
||||||
|
if (!res?.ok) {
|
||||||
|
err(res?.error || 'Editorwechsel fehlgeschlagen');
|
||||||
|
if (editorSelect) editorSelect.value = currentEditorType;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ok('Editor gewechselt');
|
||||||
|
await open({ id: current.id, name: current.name }, current.resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
btnSave && (btnSave.onclick = save);
|
||||||
|
btnClear && (btnClear.onclick = clearEditor);
|
||||||
|
btnClose && (btnClose.onclick = close);
|
||||||
btnPrevClose && (btnPrevClose.onclick = closePreview);
|
btnPrevClose && (btnPrevClose.onclick = closePreview);
|
||||||
btnPreview && (btnPreview.onclick = openPreview);
|
btnPreview && (btnPreview.onclick = openPreview);
|
||||||
btnTest && (btnTest.onclick = openSend);
|
btnTest && (btnTest.onclick = openSend);
|
||||||
btnCancelSend&& (btnCancelSend.onclick= closeSend);
|
btnCancelSend&& (btnCancelSend.onclick= closeSend);
|
||||||
sendForm && (sendForm.onsubmit = doSend);
|
sendForm && (sendForm.onsubmit = doSend);
|
||||||
|
editorSelect && (editorSelect.onchange = () => switchEditor(editorSelect.value));
|
||||||
|
|
||||||
window.AdminTestSend = window.AdminTestSend || {};
|
window.AdminTestSend = window.AdminTestSend || {};
|
||||||
window.AdminTestSend.open = (opts = {}) => {
|
window.AdminTestSend.open = (opts = {}) => {
|
||||||
|
|||||||
@@ -272,6 +272,9 @@ function fillSettingsForm(settings) {
|
|||||||
settingsForm.bridge_token.value = settings.bridge_token || '';
|
settingsForm.bridge_token.value = settings.bridge_token || '';
|
||||||
settingsForm.sender_token.value = settings.sender_token || '';
|
settingsForm.sender_token.value = settings.sender_token || '';
|
||||||
settingsForm.external_api_token.value = settings.external_api_token || '';
|
settingsForm.external_api_token.value = settings.external_api_token || '';
|
||||||
|
if (settingsForm.editor_default) {
|
||||||
|
settingsForm.editor_default.value = settings.editor_default || 'grapesjs';
|
||||||
|
}
|
||||||
state.rotate = { bridge: false, sender: false, external: false };
|
state.rotate = { bridge: false, sender: false, external: false };
|
||||||
refreshAdminTables(settings.bridge_setup?.tables || [], settings.bridge_tables || []);
|
refreshAdminTables(settings.bridge_setup?.tables || [], settings.bridge_tables || []);
|
||||||
}
|
}
|
||||||
@@ -317,6 +320,7 @@ async function submitSettingsForm(ev) {
|
|||||||
bridge_token: settingsForm.bridge_token.value.trim(),
|
bridge_token: settingsForm.bridge_token.value.trim(),
|
||||||
sender_token: settingsForm.sender_token.value.trim(),
|
sender_token: settingsForm.sender_token.value.trim(),
|
||||||
external_api_token: settingsForm.external_api_token.value.trim(),
|
external_api_token: settingsForm.external_api_token.value.trim(),
|
||||||
|
editor_default: settingsForm.editor_default ? settingsForm.editor_default.value : undefined,
|
||||||
bridge_tables: bridgeTables,
|
bridge_tables: bridgeTables,
|
||||||
rotate_bridge_token: state.rotate.bridge ? 1 : 0,
|
rotate_bridge_token: state.rotate.bridge ? 1 : 0,
|
||||||
rotate_sender_token: state.rotate.sender ? 1 : 0,
|
rotate_sender_token: state.rotate.sender ? 1 : 0,
|
||||||
|
|||||||
@@ -61,12 +61,20 @@ require __DIR__ . '/../partials/structure/layout_start.php';
|
|||||||
<div class="h-full flex flex-col">
|
<div class="h-full flex flex-col">
|
||||||
<div class="px-4 py-2 border-b flex items-center gap-2 bg-white/80 backdrop-blur">
|
<div class="px-4 py-2 border-b flex items-center gap-2 bg-white/80 backdrop-blur">
|
||||||
<strong class="me-auto">E-Mail Editor</strong>
|
<strong class="me-auto">E-Mail Editor</strong>
|
||||||
|
<label class="text-xs text-slate-600">Editor</label>
|
||||||
|
<select id="editorTypeSelect" class="input h-8 py-0 text-sm">
|
||||||
|
<option value="grapesjs">GrapesJS</option>
|
||||||
|
<option value="craftjs">Craft.js</option>
|
||||||
|
</select>
|
||||||
<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>
|
||||||
<button id="btn-save" type="button" class="btn">Speichern</button>
|
<button id="btn-save" type="button" class="btn">Speichern</button>
|
||||||
<button id="btn-close" type="button" class="btn">Schließen</button>
|
<button id="btn-close" type="button" class="btn">Schließen</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="craftEditor" class="hidden flex-1 w-full">
|
||||||
|
<div id="craftEditorMount" class="w-full h-full"></div>
|
||||||
|
</div>
|
||||||
<iframe id="editorFrame" src="about:blank" class="flex-1 w-full"></iframe>
|
<iframe id="editorFrame" src="about:blank" class="flex-1 w-full"></iframe>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|||||||
17
public/vendor/@craftjs/core@0.2.12/X-ZXJlYWN0LHJlYWN0LWRvbQ/es2022/core.bundle.mjs
vendored
Normal file
17
public/vendor/@craftjs/core@0.2.12/X-ZXJlYWN0LHJlYWN0LWRvbQ/es2022/core.bundle.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
36
public/vendor/react-dom@18.2.0/es2022/react-dom.bundle.mjs
vendored
Normal file
36
public/vendor/react-dom@18.2.0/es2022/react-dom.bundle.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
16
public/vendor/react@18.2.0/es2022/react.mjs
vendored
Normal file
16
public/vendor/react@18.2.0/es2022/react.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -106,6 +106,7 @@ CREATE TABLE IF NOT EXISTS `emailtemplate_customer_settings` (
|
|||||||
`bridge_token` varchar(255) DEFAULT NULL,
|
`bridge_token` varchar(255) DEFAULT NULL,
|
||||||
`sender_token` varchar(255) DEFAULT NULL,
|
`sender_token` varchar(255) DEFAULT NULL,
|
||||||
`external_api_token` varchar(255) DEFAULT NULL,
|
`external_api_token` varchar(255) DEFAULT NULL,
|
||||||
|
`editor_default` varchar(32) DEFAULT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
PRIMARY KEY (`customer_id`)
|
PRIMARY KEY (`customer_id`)
|
||||||
@@ -204,6 +205,8 @@ CREATE TABLE IF NOT EXISTS `emailtemplate_snippets` (
|
|||||||
`category` varchar(100) NOT NULL DEFAULT '',
|
`category` varchar(100) NOT NULL DEFAULT '',
|
||||||
`json_content` mediumtext DEFAULT NULL,
|
`json_content` mediumtext DEFAULT NULL,
|
||||||
`content` mediumtext DEFAULT NULL,
|
`content` mediumtext DEFAULT NULL,
|
||||||
|
`editor_type` varchar(32) DEFAULT NULL,
|
||||||
|
`craft_json` mediumtext DEFAULT NULL,
|
||||||
`block_id` int(10) unsigned DEFAULT NULL,
|
`block_id` int(10) unsigned DEFAULT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
@@ -222,6 +225,8 @@ CREATE TABLE IF NOT EXISTS `emailtemplate_templates` (
|
|||||||
`api_name` varchar(190) NOT NULL,
|
`api_name` varchar(190) NOT NULL,
|
||||||
`json_content` mediumtext DEFAULT NULL,
|
`json_content` mediumtext DEFAULT NULL,
|
||||||
`html` mediumtext DEFAULT NULL,
|
`html` mediumtext DEFAULT NULL,
|
||||||
|
`editor_type` varchar(32) DEFAULT NULL,
|
||||||
|
`craft_json` mediumtext DEFAULT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
|
|||||||
@@ -453,20 +453,62 @@ class ApiKernel
|
|||||||
$topHtml = ($htmlCol && isset($row[$htmlCol])) ? (string)$row[$htmlCol] : null;
|
$topHtml = ($htmlCol && isset($row[$htmlCol])) ? (string)$row[$htmlCol] : null;
|
||||||
$jsonCol = $this->firstExisting($allCols, ['json_content']);
|
$jsonCol = $this->firstExisting($allCols, ['json_content']);
|
||||||
$topContent = ($jsonCol && isset($row[$jsonCol])) ? $row[$jsonCol] : null;
|
$topContent = ($jsonCol && isset($row[$jsonCol])) ? $row[$jsonCol] : null;
|
||||||
|
$editorCol = $this->firstExisting($allCols, ['editor_type', 'editor']);
|
||||||
|
$craftCol = $this->firstExisting($allCols, ['craft_json', 'craft_content', 'craft_data']);
|
||||||
|
$editorType = $editorCol && isset($row[$editorCol]) ? strtolower((string)$row[$editorCol]) : '';
|
||||||
|
$pendingUpdate = [];
|
||||||
|
|
||||||
$gjsComponents = [];
|
$gjsComponents = [];
|
||||||
|
|
||||||
|
if ($editorCol && $editorType === '') {
|
||||||
|
$settings = $this->getCustomerSettings((int)($auth['customer_id'] ?? 0));
|
||||||
|
$editorType = strtolower((string)($settings['editor_default'] ?? 'grapesjs'));
|
||||||
|
if (!in_array($editorType, ['grapesjs', 'craftjs'], true)) {
|
||||||
|
$editorType = 'grapesjs';
|
||||||
|
}
|
||||||
|
$pendingUpdate[$editorCol] = $editorType;
|
||||||
|
$rowOut[$editorCol] = $editorType;
|
||||||
|
}
|
||||||
|
|
||||||
if ($topContent !== null) {
|
if ($topContent !== null) {
|
||||||
$decodedContent = json_decode($topContent, true);
|
$decodedContent = json_decode($topContent, true);
|
||||||
if (is_array($decodedContent)) {
|
if (is_array($decodedContent)) {
|
||||||
$gjsComponents = $decodedContent;
|
$gjsComponents = $decodedContent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($gjsComponents) && $topHtml !== null) {
|
if (empty($gjsComponents) && $topHtml !== null) {
|
||||||
$gjsComponents = $this->parseHtmlToGjsComponents($topHtml);
|
$gjsComponents = $this->parseHtmlToGjsComponents($topHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($editorType === 'grapesjs' && $jsonCol && empty($topContent) && $topHtml !== null) {
|
||||||
|
$pendingUpdate[$jsonCol] = $this->encodeJson($gjsComponents);
|
||||||
|
$topContent = $pendingUpdate[$jsonCol];
|
||||||
|
$rowOut[$jsonCol] = $pendingUpdate[$jsonCol];
|
||||||
|
}
|
||||||
|
if ($editorType === 'craftjs' && $craftCol) {
|
||||||
|
$craftPayload = isset($row[$craftCol]) ? (string)$row[$craftCol] : '';
|
||||||
|
if ($craftPayload === '') {
|
||||||
|
$pendingUpdate[$craftCol] = $this->encodeJson(['html' => (string)($topHtml ?? '')]);
|
||||||
|
$rowOut[$craftCol] = $pendingUpdate[$craftCol];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pendingUpdate) {
|
||||||
|
$pendingUpdate[$idCol] = $row[$idCol] ?? $id;
|
||||||
|
[$tw, $tp] = $this->tenantWhere($auth);
|
||||||
|
$set = [];
|
||||||
|
foreach (array_keys($pendingUpdate) as $c) {
|
||||||
|
if ($c === $idCol) continue;
|
||||||
|
$set[] = "`$c` = :$c";
|
||||||
|
}
|
||||||
|
$sql = "UPDATE `$t` SET " . implode(',', $set) . " WHERE `$idCol` = :$idCol" . $tw . " LIMIT 1";
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
foreach ($pendingUpdate as $k => $v) $stmt->bindValue(":$k", $v);
|
||||||
|
foreach ($tp as $k => $v) $stmt->bindValue($k, $v);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
$usage = $this->calculateUsage($kind, (int)$rowOut['id'], $auth);
|
$usage = $this->calculateUsage($kind, (int)$rowOut['id'], $auth);
|
||||||
|
|
||||||
$this->respond([
|
$this->respond([
|
||||||
@@ -478,6 +520,8 @@ class ApiKernel
|
|||||||
'html' => $topHtml,
|
'html' => $topHtml,
|
||||||
'content' => $topContent,
|
'content' => $topContent,
|
||||||
'gjs_components' => $gjsComponents,
|
'gjs_components' => $gjsComponents,
|
||||||
|
'editor_type' => $editorType ?: null,
|
||||||
|
'craft_json' => $craftCol && isset($rowOut[$craftCol]) ? $rowOut[$craftCol] : null,
|
||||||
'usage' => $usage,
|
'usage' => $usage,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -510,9 +554,13 @@ class ApiKernel
|
|||||||
: ['content_json', 'json', 'content', 'structure_json'];
|
: ['content_json', 'json', 'content', 'structure_json'];
|
||||||
$json = $this->val($this->in, $jsonKeys, null);
|
$json = $this->val($this->in, $jsonKeys, null);
|
||||||
$settings = $this->val($this->in, ['settings_json', 'settings'], null);
|
$settings = $this->val($this->in, ['settings_json', 'settings'], null);
|
||||||
|
$editorType = strtolower(trim((string)$this->val($this->in, ['editor_type', 'editor'], '')));
|
||||||
|
$craftJson = $this->val($this->in, ['craft_json', 'craft_content', 'craft_data'], null);
|
||||||
$templateId = $this->val($this->in, ['template_id', 'tpl_id'], null);
|
$templateId = $this->val($this->in, ['template_id', 'tpl_id'], null);
|
||||||
$sectionId = $this->val($this->in, ['section_id', 'sec_id'], null);
|
$sectionId = $this->val($this->in, ['section_id', 'sec_id'], null);
|
||||||
$blockId = $this->val($this->in, ['block_id', 'blk_id'], null);
|
$blockId = $this->val($this->in, ['block_id', 'blk_id'], null);
|
||||||
|
$editorType = strtolower(trim((string)$this->val($this->in, ['editor_type', 'editor'], '')));
|
||||||
|
$craftJson = $this->val($this->in, ['craft_json', 'craft_content', 'craft_data'], null);
|
||||||
|
|
||||||
$data = [$nameCol => $name];
|
$data = [$nameCol => $name];
|
||||||
if ($kind === 'templates') {
|
if ($kind === 'templates') {
|
||||||
@@ -541,6 +589,10 @@ class ApiKernel
|
|||||||
|
|
||||||
$htmlDbCol = $this->firstExisting($allCols, ($kind === 'snippets' ? ['content'] : ['html', 'body', 'markup']));
|
$htmlDbCol = $this->firstExisting($allCols, ($kind === 'snippets' ? ['content'] : ['html', 'body', 'markup']));
|
||||||
$jsonDbCol = $this->firstExisting($allCols, ['json_content']);
|
$jsonDbCol = $this->firstExisting($allCols, ['json_content']);
|
||||||
|
$editorDbCol = $this->firstExisting($allCols, ['editor_type', 'editor']);
|
||||||
|
$craftDbCol = $this->firstExisting($allCols, ['craft_json', 'craft_content', 'craft_data']);
|
||||||
|
$editorDbCol = $this->firstExisting($allCols, ['editor_type', 'editor']);
|
||||||
|
$craftDbCol = $this->firstExisting($allCols, ['craft_json', 'craft_content', 'craft_data']);
|
||||||
|
|
||||||
// --- LOGIK mit ERWEITERTER PRÜFUNG START ---
|
// --- LOGIK mit ERWEITERTER PRÜFUNG START ---
|
||||||
|
|
||||||
@@ -570,6 +622,19 @@ class ApiKernel
|
|||||||
}
|
}
|
||||||
// --- LOGIK mit ERWEITERTER PRÜFUNG ENDE ---
|
// --- LOGIK mit ERWEITERTER PRÜFUNG ENDE ---
|
||||||
|
|
||||||
|
if ($editorDbCol) {
|
||||||
|
if ($editorType === '' && in_array($kind, ['templates', 'snippets'], true)) {
|
||||||
|
$settings = $this->getCustomerSettings((int)($auth['customer_id'] ?? 0));
|
||||||
|
$editorType = strtolower((string)($settings['editor_default'] ?? 'grapesjs'));
|
||||||
|
}
|
||||||
|
if ($editorType !== '' && in_array($editorType, ['grapesjs', 'craftjs'], true)) {
|
||||||
|
$data[$editorDbCol] = $editorType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($craftDbCol && $craftJson !== null) {
|
||||||
|
$data[$craftDbCol] = is_string($craftJson) ? $craftJson : $this->encodeJson($craftJson);
|
||||||
|
}
|
||||||
|
|
||||||
$c = $this->firstExisting($allCols, ['settings_json', 'settings']);
|
$c = $this->firstExisting($allCols, ['settings_json', 'settings']);
|
||||||
if ($c && $settings !== null) $data[$c] = is_string($settings) ? $settings : $this->encodeJson($settings);
|
if ($c && $settings !== null) $data[$c] = is_string($settings) ? $settings : $this->encodeJson($settings);
|
||||||
|
|
||||||
@@ -684,6 +749,13 @@ class ApiKernel
|
|||||||
$data[$htmlDbCol] = (string)$html;
|
$data[$htmlDbCol] = (string)$html;
|
||||||
}
|
}
|
||||||
// --- LOGIK mit ERWEITERTER PRÜFUNG ENDE ---
|
// --- LOGIK mit ERWEITERTER PRÜFUNG ENDE ---
|
||||||
|
|
||||||
|
if ($editorDbCol && $editorType !== '' && in_array($editorType, ['grapesjs', 'craftjs'], true)) {
|
||||||
|
$data[$editorDbCol] = $editorType;
|
||||||
|
}
|
||||||
|
if ($craftDbCol && $craftJson !== null) {
|
||||||
|
$data[$craftDbCol] = is_string($craftJson) ? $craftJson : $this->encodeJson($craftJson);
|
||||||
|
}
|
||||||
|
|
||||||
$c = $this->firstExisting($allCols, ['settings_json', 'settings']);
|
$c = $this->firstExisting($allCols, ['settings_json', 'settings']);
|
||||||
if ($settings !== null && $c) $data[$c] = is_string($settings) ? $settings : $this->encodeJson($settings);
|
if ($settings !== null && $c) $data[$c] = is_string($settings) ? $settings : $this->encodeJson($settings);
|
||||||
@@ -1696,6 +1768,7 @@ class ApiKernel
|
|||||||
$bridgeToken = trim((string)($this->in['bridge_token'] ?? ''));
|
$bridgeToken = trim((string)($this->in['bridge_token'] ?? ''));
|
||||||
$senderToken = trim((string)($this->in['sender_token'] ?? ''));
|
$senderToken = trim((string)($this->in['sender_token'] ?? ''));
|
||||||
$externalToken = trim((string)($this->in['external_api_token'] ?? ''));
|
$externalToken = trim((string)($this->in['external_api_token'] ?? ''));
|
||||||
|
$editorDefault = strtolower(trim((string)($this->in['editor_default'] ?? '')));
|
||||||
$rotateBridge = !empty($this->in['rotate_bridge_token']);
|
$rotateBridge = !empty($this->in['rotate_bridge_token']);
|
||||||
$rotateSender = !empty($this->in['rotate_sender_token']);
|
$rotateSender = !empty($this->in['rotate_sender_token']);
|
||||||
$rotateExternal = !empty($this->in['rotate_external_token']);
|
$rotateExternal = !empty($this->in['rotate_external_token']);
|
||||||
@@ -1710,11 +1783,16 @@ class ApiKernel
|
|||||||
if ($rotateSender || $senderToken === '') $senderToken = $this->generateToken();
|
if ($rotateSender || $senderToken === '') $senderToken = $this->generateToken();
|
||||||
if ($rotateExternal || $externalToken === '') $externalToken = $this->generateToken();
|
if ($rotateExternal || $externalToken === '') $externalToken = $this->generateToken();
|
||||||
|
|
||||||
|
if ($editorDefault !== '' && !in_array($editorDefault, ['grapesjs', 'craftjs'], true)) {
|
||||||
|
$this->fail('Ungültiger Editor-Typ', null, 422);
|
||||||
|
}
|
||||||
|
|
||||||
$settings = $this->saveCustomerSettings($customerId, [
|
$settings = $this->saveCustomerSettings($customerId, [
|
||||||
'bridge_url' => $bridgeUrl,
|
'bridge_url' => $bridgeUrl,
|
||||||
'bridge_token' => $bridgeToken,
|
'bridge_token' => $bridgeToken,
|
||||||
'sender_token' => $senderToken,
|
'sender_token' => $senderToken,
|
||||||
'external_api_token' => $externalToken,
|
'external_api_token' => $externalToken,
|
||||||
|
'editor_default' => $editorDefault ?: null,
|
||||||
'bridge_tables' => $bridgeTables,
|
'bridge_tables' => $bridgeTables,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -2240,7 +2318,7 @@ class ApiKernel
|
|||||||
{
|
{
|
||||||
if ($customerId <= 0) return [];
|
if ($customerId <= 0) return [];
|
||||||
$this->ensureCustomerSettingsTableExists();
|
$this->ensureCustomerSettingsTableExists();
|
||||||
$allowed = ['bridge_url', 'bridge_token', 'sender_token', 'external_api_token', 'bridge_tables', 'bridge_setup'];
|
$allowed = ['bridge_url', 'bridge_token', 'sender_token', 'external_api_token', 'editor_default', 'bridge_tables', 'bridge_setup'];
|
||||||
$fields = array_intersect_key($data, array_flip($allowed));
|
$fields = array_intersect_key($data, array_flip($allowed));
|
||||||
if (!$fields) return $this->getCustomerSettings($customerId);
|
if (!$fields) return $this->getCustomerSettings($customerId);
|
||||||
if (array_key_exists('bridge_tables', $fields)) {
|
if (array_key_exists('bridge_tables', $fields)) {
|
||||||
@@ -2303,6 +2381,9 @@ class ApiKernel
|
|||||||
} else {
|
} else {
|
||||||
$row['bridge_setup'] = $this->defaultBridgeSetup();
|
$row['bridge_setup'] = $this->defaultBridgeSetup();
|
||||||
}
|
}
|
||||||
|
if (empty($row['editor_default'])) {
|
||||||
|
$row['editor_default'] = 'grapesjs';
|
||||||
|
}
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2577,6 +2658,7 @@ CREATE TABLE IF NOT EXISTS `$table` (
|
|||||||
`bridge_token` varchar(255) DEFAULT NULL,
|
`bridge_token` varchar(255) DEFAULT NULL,
|
||||||
`sender_token` varchar(255) DEFAULT NULL,
|
`sender_token` varchar(255) DEFAULT NULL,
|
||||||
`external_api_token` varchar(255) DEFAULT NULL,
|
`external_api_token` varchar(255) DEFAULT NULL,
|
||||||
|
`editor_default` varchar(32) DEFAULT NULL,
|
||||||
`bridge_tables` text DEFAULT NULL,
|
`bridge_tables` text DEFAULT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
@@ -2615,6 +2697,9 @@ SQL;
|
|||||||
if (!in_array('bridge_setup', $columns, true)) {
|
if (!in_array('bridge_setup', $columns, true)) {
|
||||||
$missing[] = 'ADD COLUMN `bridge_setup` longtext DEFAULT NULL';
|
$missing[] = 'ADD COLUMN `bridge_setup` longtext DEFAULT NULL';
|
||||||
}
|
}
|
||||||
|
if (!in_array('editor_default', $columns, true)) {
|
||||||
|
$missing[] = 'ADD COLUMN `editor_default` varchar(32) DEFAULT NULL';
|
||||||
|
}
|
||||||
|
|
||||||
if (!$missing) {
|
if (!$missing) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user