// 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(); }, }; }