Files
emailtemplate.it/public/editor/bridge-core.js
2026-01-28 23:58:28 +01:00

1365 lines
68 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* /editor/bridge-core.js — Loader + Orchestrator (FINAL & LOG-KONTROLLIERT) */
(function () {
    
    // --- Initialisierung BridgeParts (B) und Plugin-Registry ---
    if (!window.BridgeParts) window.BridgeParts = {};
    const B = window.BridgeParts;
    
    // ----------------------------------------------------------------------
    // 🎯 LOKALE LOG-KONFIGURATION & WRAPPER
    // ----------------------------------------------------------------------
    const PluginName = 'bridge-core';
    
    // Setzen Sie dies auf 'false', um alle Logs NUR für dieses Plugin zu deaktivieren.
    if (B.LOG_CONFIG) {
    B.LOG_CONFIG.PLUGINS[PluginName] = true; // bridge-core spezifisch deaktivieren (optional)
    }
    
    /**
     * NEUER LOKALER WRAPPER, der die zentrale B.log Funktion verwendet.
     */
const log = (type, message, color = '#1E90FF', logType = 'info', force = false) => {
// Loggt NUR, wenn B.log verfügbar ist (aus general-functions.js).
if (typeof B.log === 'function') {
B.log(PluginName, `[${type}] ${message}`, color, logType, force);
}
// Ansonsten wird NICHTS geloggt, bis general-functions.js geladen ist.
};
// ----------------------------------------------------------------------
const installModalGuards = () => {
if (window.__bridgeModalGuardsInstalled) return;
window.__bridgeModalGuardsInstalled = true;
window.__bridgeModalAllowClose = false;
const allowCloseOnce = () => {
window.__bridgeModalAllowClose = true;
setTimeout(() => { window.__bridgeModalAllowClose = false; }, 0);
};
document.addEventListener('click', (evt) => {
const closeHit = evt.target && evt.target.closest
? evt.target.closest('.gjs-mdl-btn-close,[data-bridge-modal-close="1"]')
: null;
if (closeHit) {
allowCloseOnce();
return;
}
const container = evt.target && evt.target.closest
? evt.target.closest('.gjs-mdl-container')
: null;
if (container) {
const content = evt.target.closest ? evt.target.closest('.gjs-mdl-content') : null;
if (!content) {
evt.preventDefault();
evt.stopPropagation();
return;
}
}
}, true);
document.addEventListener('mousedown', (evt) => {
const closeHit = evt.target && evt.target.closest
? evt.target.closest('.gjs-mdl-btn-close,[data-bridge-modal-close="1"]')
: null;
if (closeHit) {
allowCloseOnce();
}
}, true);
document.addEventListener('keydown', (evt) => {
if (evt.key !== 'Escape') return;
const hasModal = document.querySelector('.gjs-mdl-container');
if (hasModal) {
evt.preventDefault();
evt.stopPropagation();
}
}, true);
};
    // 🛑 GLOBALER LOG ZUR BESTÄTIGUNG DER SKRIPT-AUSFÜHRUNG
    // log('START', `SKRIPT-AUSFÜHRUNG GESTARTET.`, '#DC143C', 'info', true); // DEAKTIVIERT/IGNORIERT DURCH FEHLENDEN B.log
    // ----------------------------------------------------------------------
    // 🛑 KONFIGURATION: NEWSLETTER-PRESET-TOGGLE
    // ----------------------------------------------------------------------
    const LOAD_NEWSLETTER_PRESET = false; // <<< KRITISCHER FIX: Auf FALSE gesetzt, um den "defaults" Konflikt zu beheben!
    // ----------------------------------------------------------------------
    
if (window.__bridgeCoreInitialized) {
log('INIT ABORT', 'Bridge Core wurde bereits initialisiert.', 'orange');
return; 
}
window.__bridgeCoreInitialized = true;
installModalGuards();
    
    // --- Initialisierung BridgeParts (B) und Plugin-Registry ---
B.BASE_PATH_BRIDGE = '../assets/js/bridge/';
B.BASE_PATH_CONFIG = B.BASE_PATH_BRIDGE;
const apiFallback = '/api.php';
B.API_BASE = B.API_BASE || apiFallback;
B.API_KERNEL_URL = B.API_KERNEL_URL || B.API_BASE;
    
    B.GrapesJSPlugins = []; 
    
    B.registerGrapesJSPlugin = (name, pluginFn) => {
        B.GrapesJSPlugins.push({ name, pluginFn });
        log('PLUGIN REGISTER', `Plugin zur Registry hinzugefügt: ${name}`, 'yellow');
    };
    // --- DEBUG-HELPER UND LOADER-HELPER ---
    const badgeSay = (text, type = 'info') => {
        const b=document.getElementById('badge');
        if (!b) return;
        b.textContent = text;
        switch(type) {
            case 'ok': b.style.background = '#dcfce7'; b.style.color = '#15803d'; b.style.borderColor = '#bbf7d0'; break;
            case 'error': b.style.background = '#fee2e2'; b.style.color = '#7f1d1d'; b.style.borderColor = '#fecaca'; break;
            default: b.style.background = '#eef2ff'; b.style.color = '#1e3a8a'; b.style.borderColor = '#c7d2fe';
        }
    };
    function loadScript(url, done) {
        const filename = url.split('/').pop();
        var s = document.createElement('script');
        s.src = url + (url.indexOf('?') === -1 ? '?v=' : '&v=') + Date.now(); 
        s.async = false;
        
        s.onload = function(){ 
            log('LOAD SUCCESS', `Skript geladen: ${filename}`, 'green');
            try { 
                done && done(); 
            } catch(e){ 
                if (e.message.includes('setting getter-only property "defaults"') || e.message.includes('Cannot set property defaults')) {
                    log('RUNTIME WARNING', `IGNORIERE Block-Konflikt in ${filename}: ${e.message}`, 'orange', 'warn');
                } else {
                    // 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
                    log('RUNTIME ERROR', `Fehler in Callback nach ${filename}: ${e.message}`, 'red', 'error', false); 
                }
            } 
        };
        
        s.onerror = function(){ 
            // 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
            log('LOAD FAILED', `Skript FEHLT oder Pfad falsch: ${filename}`, 'red', 'error', false);
            badgeSay(`Ladefehler: ${filename}`, 'error');
            try { done && done(); } catch(e){} 
        };
        document.head.appendChild(s);
    }
    /**
     * HILFSFUNKTION: Wandelt den Dateinamen (z.B. blocks-standard.js) in den globalen
     * Objektnamen (z.B. BridgeBlocksStandard) um.
     */
    function getPluginObjectName(fileName) {
        // 1. Entferne Dateiendung (.js)
        let name = fileName.replace('.js', ''); // 'blocks-standard'
        
        // 2. Teile in Bestandteile zerlegen und den ersten Buchstaben groß schreiben
        const parts = name.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)); // ['Blocks', 'Standard']
        
        // 3. Mit 'Bridge' prefixen und zusammenfügen
        return 'Bridge' + parts.join(''); // 'BridgeBlocksStandard'
    }
    
    // 🛑 NEUE FUNKTION: Erstellt alle in der Konfiguration definierten Kategorien.
    function ensureConfiguredCategories(editor) {
        const bm = editor.BlockManager;
        // HINWEIS: B.CATEGORY_CONFIG wird in category-config.js befüllt (muss vorher geladen werden)
        const config = window.BridgeParts?.CATEGORY_CONFIG || {};
        Object.keys(config)
            .sort((a, b) => (config[a].ord || 999) - (config[b].ord || 999))
            .forEach(catId => {
                const catConf = config[catId];
                // Category wird nur erstellt, wenn sie noch nicht existiert
                if (!bm.getCategories().get(catId)) {
                    bm.getCategories().add({
                        id: catId,
                        label: catConf.label,
                        open: catConf.open !== false,
                        order: catConf.ord || 999
                    });
                    log('CAT INIT', `Kategorie '${catId}' explizit erstellt.`, 'green');
                }
            });
    }
    function loadBridgeParts(cb){
        const base = B.BASE_PATH_BRIDGE; 
        
        // 🛑 LOKALES LOGGING ENTFERNT, DA ES VOR B.log LIEGT.
        // log('LOAD START', 'Starte Laden der modularen Bridge-Teile (Geordnet).'); 
        
        const coreFiles = [
            base + 'general-functions.js', // <<< RE-AKTIVIERT: Für B.log
            base + 'category-config.js', // <<< RE-AKTIVIERT: Für B.CATEGORY_CONFIG (und damit API-Flexibilität)
            base + 'library-parts.js',
            base + 'categorization-master.js',
            base + 'categorization-cleanup.js',
        ];
        const initialLoadList = B.ENABLE_EDITOR_EXTENSIONS === false
            ? [base + 'general-functions.js']
            : [...coreFiles];
if (B.ENABLE_EDITOR_EXTENSIONS !== false && B.ENABLE_RTE) {
initialLoadList.push(base + 'rte-editor.js');
}
if (B.ENABLE_EDITOR_EXTENSIONS !== false && B.ENABLE_TABLE_BUILDER) {
initialLoadList.push(base + 'table-builder.js');
}
        
        function recursiveLoader(list, index = 0) {
            if (index >= list.length) {
                log('LOAD END', 'Initial-Bridge-Skripte geladen.', 'green');
                if (B.ENABLE_EDITOR_EXTENSIONS === false) {
                     return cb && cb(B);
                 }
                
                const config = window.BridgeParts?.CATEGORY_CONFIG || {};
                let allBlockFiles = [];
                
                // Dynamisches Sammeln der Block-Dateien aus der Config
                Object.keys(config)
                    .sort((a, b) => (config[a].ord || 999) - (config[b].ord || 999))
                    .forEach(key => {
                        // Sammelt alle Dateien, egal ob sync oder async
                        if (Array.isArray(config[key].files)) {
                            allBlockFiles.push(...config[key].files.map(file => base + file)); 
                        }
                    });
                
                // Duplikate entfernen (falls eine Datei in mehreren Kategorien gelistet ist)
                allBlockFiles = Array.from(new Set(allBlockFiles));
                
                function loadBlockFiles(blockIndex = 0) {
                    if (blockIndex >= allBlockFiles.length) {
                        log('LOAD END', 'Alle Blöcke geladen.', 'green');
                        return cb && cb(B);
                    }
                    log('LOADING BLOCKS', `Lade Block-Skript [${blockIndex + 1}/${allBlockFiles.length}]: ${allBlockFiles[blockIndex].split('/').pop()}`);
                    loadScript(allBlockFiles[blockIndex], function(){
                        loadBlockFiles(blockIndex + 1);
                    });
                }
                loadBlockFiles();
                return;
            }
            
            // 🛑 LOKALES LOGGING ENTFERNT, DA ES VOR B.log LIEGT.
            log('LOADING CORE', `Lade Skript [${index + 1}/${initialLoadList.length}]: ${list[index].split('/').pop()}`); // Loggt ab dem 3. Skript, da general-functions.js an 2. Stelle geladen wird.
            loadScript(list[index], function(){
                recursiveLoader(list, index + 1);
            });
        }
        
        recursiveLoader(initialLoadList);
    }
    try { parent.postMessage({ source:'bridge', type:'boot' }, '*'); } catch {}
    var MODE = (window.__editorMode || 'templates').toLowerCase();
    const replaceReferenceLibrary = (editor, ref, mode) => { 
        (window.BridgeParts?.addReferenceLibrary || (()=>{}))(editor, ref, mode); 
    };
    const upsertCustomForBothCats = (editor, payload) => { 
        (window.BridgeParts?.upsertCustomForBothCats || (()=>{}))(editor, payload);
    };
    
    // --- Init & Events (Plugin integriert) ---------------------------------------------
    loadBridgeParts(function(B){
        log('INIT START', 'Alle Bridge-Teile geladen, starte GrapesJS-Initialisierung.', 'orange');
        
        if (typeof grapesjs === 'undefined' || !grapesjs.init) {
            // 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
            log('CRITICAL ERROR', 'Das globale Objekt grapesjs ist nicht verfügbar! Laden von grapes.min.js ist fehlgeschlagen.', 'red', 'error', false);
            badgeSay('Fehler: GrapesJS nicht geladen!', 'error');
            return; 
        }
        // 🛑 KRITISCHER FIX TEIL 1: Registriere alle gesammelten Bridge-Plugins global.
        if (typeof grapesjs.plugins.add === 'function') {
            B.GrapesJSPlugins.forEach(p => {
                grapesjs.plugins.add(p.name, p.pluginFn);
                log('PLUGIN ACTIVATION', `GrapesJS Plugin global bereitgestellt: ${p.name}`, 'lime');
            });
        } else {
            // 🛑 KORREKTUR: force: true explizit auf false setzen (oder weglassen)
            log('PLUGIN ERROR', `GrapesJS Plugin-API (grapesjs.plugins.add) fehlt. Plugins können nicht registriert werden.`, 'red', 'error', false);
        }
        // 🛑 KRITISCHER FIX: Safety Plugin MUSS die fehlenden Views Panels hinzufügen.
        function safetyPlugin(editor){
            const pn = editor.Panels, orig = pn.getButton.bind(pn);
            pn.getButton = (pid, id) => orig(pid, id) || { set(){}, get(){ return null; } }; 
            
            // Fügen Sie das Panel 'views' hinzu, wenn es fehlt
            if(!pn.getPanel('views')) {
                pn.addPanel({ id: 'views', el: '.gjs-pn-views' }); 
                log('PANEL FIX', "Das 'views' Panel wurde nachträglich hinzugefügt.", 'yellow', 'warn');
            }
            
            // Stellen Sie sicher, dass der Block Manager in den Views-Container rendert
            editor.Config.blockManager = editor.Config.blockManager || {};
            editor.Config.blockManager.appendTo = editor.Config.blockManager.appendTo || '.gjs-blocks';
            
            // Der fehlerhafte Timeout-Block wurde entfernt.
        }
let pluginsList = [
safetyPlugin,
];
if (B.ENABLE_EDITOR_EXTENSIONS !== false) {
pluginsList.push(
// 🛑 KRITISCHE ERGÄNZUNG: Aktiviert das registrierte API-Plugin
'bridge-blocks-api',
'bridge-categorization-master',
'bridge-categorization-cleanup'
);
}
        if (LOAD_NEWSLETTER_PRESET) {
            pluginsList.push('gjs-preset-newsletter');
        }
        
const storageConf = {
autoload: false,
autosave: false,
};
// RichText-Editor ausgelagert nach /assets/js/bridge/rte-editor.js
const setupPlainTextPreserver = (editor) => {
const isTextLike = (model) => !!(model && model.is && (model.is('text') || model.is('button') || model.is('link') || model.is('textnode')));
const resolveTextModel = (model) => {
if (!model) return model;
if (model.is && model.is('textnode') && typeof model.parent === 'function') {
return model.parent();
}
return model;
};
const lastContent = new Map();
const syncing = new WeakSet();
const normalizeHtml = (value) => {
return String(value || '')
.replace(/<br\s*\/?>/gi, '')
.replace(/&nbsp;/g, '')
.trim();
};
const snapshotContent = (model) => {
const rawContent = model && model.get ? model.get('content') : '';
const el = model.view && model.view.el;
const html = String(rawContent || '').trim();
const inner = el && el.innerHTML ? String(el.innerHTML).trim() : '';
const text = el && el.textContent ? String(el.textContent).trim() : '';
const composite = html || inner || text;
return {
html: composite,
normalized: normalizeHtml(composite),
};
};
const normalizeViewHtmlForModel = (html) => {
return String(html || '')
.replace(/<br\b[^>]*>/gi, '<br>')
.replace(/\sdata-gjs-type="[^"]*"/gi, '')
.replace(/\sdraggable="[^"]*"/gi, '')
.replace(/\scontenteditable="[^"]*"/gi, '')
.trim();
};
const getModelHtml = (model) => {
if (!model) return '';
let html = '';
try {
html = String(model.get ? model.get('content') || '' : '').trim();
} catch {}
if (!html && model.components) {
try {
const comps = model.components();
if (comps && typeof comps.toHTML === 'function') {
html = String(comps.toHTML() || '').trim();
}
} catch {}
}
if (!html && typeof model.toHTML === 'function') {
try {
const full = String(model.toHTML() || '').trim();
if (full) {
const wrapper = document.createElement('div');
wrapper.innerHTML = full;
const first = wrapper.firstElementChild;
html = first ? String(first.innerHTML || '').trim() : String(wrapper.innerHTML || '').trim();
}
} catch {}
}
return html;
};
const storeSnapshot = (model, snap) => {
if (!model || !snap) return;
const key = model.cid || model;
lastContent.set(key, snap);
model.__bridgeLastHtml = snap.html;
model.__bridgeLastNormalized = snap.normalized;
const el = model.view && model.view.el;
if (el) {
el.__bridgeLastHtml = snap.html;
el.__bridgeLastNormalized = snap.normalized;
}
};
const rememberIfPresent = (model) => {
const target = resolveTextModel(model);
if (!isTextLike(target)) return;
const snap = snapshotContent(target);
if (!snap.normalized) return;
storeSnapshot(target, snap);
};
const restoreIfEmpty = (model) => {
const target = resolveTextModel(model);
if (!isTextLike(target) || !target.get) return;
const current = snapshotContent(target);
if (current.normalized) {
rememberIfPresent(target);
return;
}
const key = target.cid || target;
const stored = lastContent.get(key)
|| (target.__bridgeLastNormalized ? { html: target.__bridgeLastHtml, normalized: target.__bridgeLastNormalized } : null)
|| ((target.view && target.view.el && target.view.el.__bridgeLastNormalized)
? { html: target.view.el.__bridgeLastHtml, normalized: target.view.el.__bridgeLastNormalized }
: null);
if (!stored || !stored.normalized) return;
try {
target.set('content', stored.html);
target.trigger && target.trigger('change:content');
try { console.log('[PLAIN TEXT RESTORE] Inhalt wiederhergestellt.'); } catch {}
} catch {}
};
const ensureViewMatchesModel = (model) => {
const target = resolveTextModel(model);
if (!isTextLike(target) || !target.view || !target.view.el) return;
const el = target.view.el;
if (el.isContentEditable || el.getAttribute('contenteditable') === 'true') return;
const modelHtml = String(getModelHtml(target) || '').trim();
if (!modelHtml) return;
const viewHtml = String(el.innerHTML || '').trim();
if (viewHtml) return;
const apply = () => {
try {
const current = String(el.innerHTML || '').trim();
if (current !== modelHtml) el.innerHTML = modelHtml;
} catch {}
};
apply();
setTimeout(apply, 0);
setTimeout(apply, 60);
};
const syncTextFromViewOnDeselect = (model) => {
const target = resolveTextModel(model);
if (!isTextLike(target) || !target.view || !target.view.el || syncing.has(target)) return;
if (window.__bridgeRteOpen) return;
const viewHtml = normalizeViewHtmlForModel(target.view.el.innerHTML || '');
if (!viewHtml) return;
const modelHtml = normalizeViewHtmlForModel(getModelHtml(target));
if (viewHtml === modelHtml) return;
try {
syncing.add(target);
if (target.components) {
try { target.components(viewHtml); } catch {}
}
if (target.set) target.set('content', viewHtml);
target.trigger && target.trigger('change:content');
if (target.view && typeof target.view.render === 'function') {
target.view.render();
}
if (editor && typeof editor.trigger === 'function') {
editor.trigger('component:update', target);
}
const reapply = () => {
try {
const current = normalizeViewHtmlForModel(target.view.el.innerHTML || '');
if (current !== viewHtml) target.view.el.innerHTML = viewHtml;
} catch {}
};
setTimeout(reapply, 0);
setTimeout(reapply, 60);
} catch {} finally {
syncing.delete(target);
}
};
const syncTextFromViewOnInput = (model) => {
const target = resolveTextModel(model);
if (!isTextLike(target) || !target.view || !target.view.el || syncing.has(target)) return;
if (window.__bridgeRteOpen) return;
const viewHtml = normalizeViewHtmlForModel(target.view.el.innerHTML || '');
if (!viewHtml) return;
const modelHtml = normalizeViewHtmlForModel(getModelHtml(target));
const comps = target.components ? target.components() : null;
const hasComps = !!(comps && comps.length);
if (viewHtml === modelHtml && hasComps) return;
try {
syncing.add(target);
if (target.components) {
try { target.components(viewHtml); } catch {}
}
if (target.set) target.set('content', viewHtml);
target.trigger && target.trigger('change:content');
if (target.view && typeof target.view.render === 'function') {
target.view.render();
}
if (editor && typeof editor.trigger === 'function') {
editor.trigger('component:update', target);
}
} catch {} finally {
syncing.delete(target);
}
};
editor.on('component:update', (model) => {
restoreIfEmpty(model);
ensureViewMatchesModel(model);
});
editor.on('component:input', (model) => {
syncTextFromViewOnInput(model);
restoreIfEmpty(model);
});
editor.on('component:deselected', (model) => {
syncTextFromViewOnDeselect(model);
restoreIfEmpty(model);
ensureViewMatchesModel(model);
});
editor.on('component:add', (model) => rememberIfPresent(model));
};
// Table-Builder ausgelagert nach /assets/js/bridge/table-builder.js
const setupBlurLogger = (editor) => {
if (!editor || !editor.Canvas || !editor.Canvas.getBody) return;
const getModelHtmlForLog = (model) => {
if (!model) return '';
let html = '';
try {
html = String(model.get ? model.get('content') || '' : '').trim();
} catch {}
if (!html && model.components) {
try {
const comps = model.components();
if (comps && typeof comps.toHTML === 'function') {
html = String(comps.toHTML() || '').trim();
}
} catch {}
}
if (!html && typeof model.toHTML === 'function') {
try {
const full = String(model.toHTML() || '').trim();
if (full) {
const wrapper = document.createElement('div');
wrapper.innerHTML = full;
const first = wrapper.firstElementChild;
html = first ? String(first.innerHTML || '').trim() : String(wrapper.innerHTML || '').trim();
}
} catch {}
}
return html;
};
const logComponentInput = (model, label) => {
try {
const modelType = model && model.get ? model.get('type') : undefined;
const selectedEl = model && model.view && model.view.el;
const viewOuter = selectedEl ? String(selectedEl.outerHTML || '') : '';
const modelContent = String(getModelHtmlForLog(model) || '');
const editorHtml = editor && typeof editor.getHtml === 'function' ? String(editor.getHtml() || '') : '';
console.warn(`[UI EDIT ${label}]`, {
modelType,
modelId: model && (model.getId ? model.getId() : model.get && model.get('id')),
modelContentLen: modelContent.length,
modelContent: modelContent.slice(0, 1000),
viewOuterLen: viewOuter.length,
viewOuter: viewOuter.slice(0, 1000),
editorHtmlLen: editorHtml.length,
editorHtml: editorHtml.slice(0, 1000),
});
if (label === 'DESELECT' && model && model.get && model.get('type') === 'text') {
const viewHtml = selectedEl ? String(selectedEl.innerHTML || '') : '';
const visibleText = selectedEl ? String(selectedEl.textContent || '') : '';
const summary = {
source: 'ui',
modelId: model && (model.getId ? model.getId() : model.get && model.get('id')),
expectedFromView: viewHtml.slice(0, 500),
actualModelContent: modelContent.slice(0, 500),
visibleText: visibleText.slice(0, 500),
viewHtml: viewHtml.slice(0, 500),
viewHtmlLen: viewHtml.length,
modelContentLen: modelContent.length,
visibleTextLen: visibleText.length,
editorHtmlLen: editorHtml.length,
};
console.warn('[EDIT SUMMARY]', JSON.stringify(summary));
}
} catch {}
};
if (!editor.__bridgeUiEditDebugBound) {
editor.__bridgeUiEditDebugBound = true;
editor.on('component:input', (model) => logComponentInput(model, 'INPUT'));
editor.on('component:update', (model) => logComponentInput(model, 'UPDATE'));
editor.on('component:deselected', (model) => logComponentInput(model, 'DESELECT'));
}
editor.on('canvas:frame:load', () => {
const body = editor.Canvas && editor.Canvas.getBody && editor.Canvas.getBody();
if (!body || body.__bridgeBlurLoggerBound) return;
body.__bridgeBlurLoggerBound = true;
const focusSnapshots = new WeakMap();
const syncing = new WeakSet();
const normalizeViewHtml = (html) => {
return String(html || '')
.replace(/<br\b[^>]*>/gi, '<br>')
.replace(/\sdata-gjs-type="[^"]*"/gi, '')
.replace(/\sdraggable="[^"]*"/gi, '')
.replace(/\scontenteditable="[^"]*"/gi, '')
.trim();
};
const buildSnapshot = (target, selected, selectedEl, editorHtml) => {
let modelContent = '';
try {
modelContent = String(getModelHtmlForLog(selected) || '');
} catch {}
let viewOuter = '';
try {
viewOuter = selectedEl ? String(selectedEl.outerHTML || '') : '';
} catch {}
return {
tag: target && target.tagName,
htmlLen: String(target && target.innerHTML || '').length,
textLen: String(target && target.textContent || '').length,
modelType: selected && selected.get ? selected.get('type') : undefined,
modelId: selected && (selected.getId ? selected.getId() : selected.get && selected.get('id')),
modelContentLen: modelContent.length,
modelContent: modelContent.slice(0, 1000),
viewOuterLen: viewOuter.length,
viewOuter: viewOuter.slice(0, 1000),
editorHtmlLen: editorHtml.length,
editorHtml: editorHtml.slice(0, 1000),
};
};
const resolveComponentFromTarget = (target) => {
if (!target || !target.closest) return null;
const rootEl = target.closest('[data-gjs-type="text"]') || target;
const id = rootEl && rootEl.getAttribute ? rootEl.getAttribute('id') : null;
if (!id || !editor.getWrapper || !editor.getWrapper().find) return null;
const found = editor.getWrapper().find(`#${id}`);
return found && found[0];
};
const syncFromBlur = (evt) => {
const target = evt && evt.target;
if (!target || window.__bridgeRteOpen) return;
const isEditable = !!(target.isContentEditable || (target.getAttribute && target.getAttribute('contenteditable') === 'true'));
if (!isEditable) return;
const component = resolveComponentFromTarget(target) || (editor.getSelected && editor.getSelected());
if (!component || syncing.has(component)) return;
const type = component.get && component.get('type');
if (type && type !== 'text' && type !== 'textnode') return;
const viewEl = component.view && component.view.el;
if (!viewEl) return;
const viewHtml = normalizeViewHtml(viewEl.innerHTML || '');
if (!viewHtml) return;
const modelHtml = normalizeViewHtml(getModelHtml(component));
if (viewHtml === modelHtml) return;
try {
syncing.add(component);
if (component.components) {
try { component.components(viewHtml); } catch {}
}
if (component.set) component.set('content', viewHtml);
component.trigger && component.trigger('change:content');
if (component.view && typeof component.view.render === 'function') {
component.view.render();
}
if (editor && typeof editor.trigger === 'function') {
editor.trigger('component:update', component);
}
} catch {} finally {
syncing.delete(component);
}
};
const logBlur = (evt) => {
const target = evt && evt.target;
if (!target) return;
const isEditable = !!(target.isContentEditable || (target.getAttribute && target.getAttribute('contenteditable') === 'true'));
if (!isEditable) return;
syncFromBlur(evt);
const selected = editor.getSelected && editor.getSelected();
const selectedEl = selected && selected.view && selected.view.el;
const inSelected = !!(selectedEl && (selectedEl === target || selectedEl.contains(target)));
let editorHtml = '';
try {
editorHtml = editor && typeof editor.getHtml === 'function' ? String(editor.getHtml() || '') : '';
} catch {}
try {
const before = focusSnapshots.get(target);
const after = buildSnapshot(target, selected, selectedEl, editorHtml);
console.warn('[BLUR LOG]', {
inSelected,
before,
after,
});
} catch {}
};
const logFocus = (evt) => {
const target = evt && evt.target;
if (!target) return;
const isEditable = !!(target.isContentEditable || (target.getAttribute && target.getAttribute('contenteditable') === 'true'));
if (!isEditable) return;
const selected = editor.getSelected && editor.getSelected();
const selectedEl = selected && selected.view && selected.view.el;
let editorHtml = '';
try {
editorHtml = editor && typeof editor.getHtml === 'function' ? String(editor.getHtml() || '') : '';
} catch {}
try {
focusSnapshots.set(target, buildSnapshot(target, selected, selectedEl, editorHtml));
} catch {}
};
const syncFromDomInput = (evt) => {
const target = evt && evt.target;
if (!target) return;
if (window.__bridgeRteOpen) return;
const isEditable = !!(target.isContentEditable || (target.getAttribute && target.getAttribute('contenteditable') === 'true'));
if (!isEditable) return;
const selected = resolveComponentFromTarget(target) || (editor.getSelected && editor.getSelected());
const selectedEl = selected && selected.view && selected.view.el;
if (!selected || !selectedEl) return;
if (syncing.has(selected)) return;
try {
const viewHtml = normalizeViewHtml(selectedEl.innerHTML || '');
if (!viewHtml) return;
syncing.add(selected);
if (selected.components) {
try { selected.components(viewHtml); } catch {}
}
if (selected.set) selected.set('content', viewHtml);
selected.trigger && selected.trigger('change:content');
if (selected.view && typeof selected.view.render === 'function') {
selected.view.render();
}
if (editor && typeof editor.trigger === 'function') {
editor.trigger('component:update', selected);
}
} catch {} finally {
syncing.delete(selected);
}
};
body.addEventListener('focusin', logFocus, true);
body.addEventListener('blur', logBlur, true);
body.addEventListener('focusout', logBlur, true);
body.addEventListener('input', syncFromDomInput, true);
body.addEventListener('keyup', syncFromDomInput, true);
});
};
const loadDynamicFonts = async () => {
try {
const base = B.API_KERNEL_URL || '/api.php';
const sep = base.includes('?') ? '&' : '?';
const res = await fetch(`${base}${sep}action=account.fonts.list`, { credentials: 'include' });
if (!res.ok) return;
const data = await res.json();
if (!data || !data.ok) return;
const incoming = Array.isArray(data.fonts) ? data.fonts : [];
if (incoming.length) {
const merged = [];
const seen = new Set();
const addFont = (item) => {
if (!item || !item.label || !item.value) return;
const key = String(item.label).toLowerCase();
if (seen.has(key)) return;
seen.add(key);
merged.push({ label: String(item.label), value: String(item.value) });
};
(B.RTE_FONTS || []).forEach(addFont);
incoming.forEach(addFont);
B.RTE_FONTS = merged;
}
if (typeof data.font_face_css === 'string' && data.font_face_css.trim()) {
const existing = typeof B.RTE_FONT_FACE_CSS === 'string' ? B.RTE_FONT_FACE_CSS.trim() : '';
B.RTE_FONT_FACE_CSS = existing
? `${existing}\n${data.font_face_css.trim()}`
: data.font_face_css.trim();
const styleId = 'bridge-font-faces';
let styleEl = document.getElementById(styleId);
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = styleId;
document.head.appendChild(styleEl);
}
styleEl.textContent = B.RTE_FONT_FACE_CSS;
}
} catch {}
};
var ed = grapesjs.init({
container: '#gjs',
height: '100vh',
noticeOnUnload: 0,
// 🛑 KRITISCHE KORREKTUR: storageManager aktivieren und konfigurieren
storageManager: storageConf,
plugins: pluginsList,
pluginsOpts: {},
// 🛑 KRITISCHE ERGÄNZUNG: Verhindert das automatische Ausblenden leerer Kategorien
blockManager: {
hideEmpty: false
},
parser: {
textTags: ['p','span','div','br','b','strong','i','em','u','a','ul','ol','li'],
optionsHtml: {
keepEmptyTextNodes: true
}
},
domComponents: {
// Preserve plain text when editing text blocks.
textTags: ['p','span','div','br','b','strong','i','em','u','a','ul','ol','li']
}
});
const ensureCommandStubs = (editor) => {
if (!editor || !editor.Commands || !editor.Commands.add) return;
editor.Commands.add('bridge-placeholder:edit', {
__bridgePlaceholderStub: true,
run(ed, sender, opts = {}) {
if (sender && sender.set) sender.set('active', 0);
const component = opts.component || (ed.getSelected && ed.getSelected());
if (window.BridgeBlocksPlaceholder && typeof window.BridgeBlocksPlaceholder.openModal === 'function') {
window.BridgeBlocksPlaceholder.openModal(ed, component);
}
},
});
editor.Commands.add('bridge-table:edit', {
__bridgeTableStub: true,
run(ed, sender, opts = {}) {
if (sender && sender.set) sender.set('active', 0);
},
});
};
if (B.ENABLE_EDITOR_EXTENSIONS !== false && B.ENABLE_EDITOR_BEHAVIOR !== false) {
ensureCommandStubs(ed);
}
if (B.ENABLE_EDITOR_EXTENSIONS !== false && B.ENABLE_EDITOR_BEHAVIOR !== false) {
try {
const textTags = ['p','span','div','br','b','strong','i','em','u','a','ul','ol','li'];
if (ed.Config) {
ed.Config.domComponents = ed.Config.domComponents || {};
ed.Config.domComponents.textTags = textTags;
}
if (ed.DomComponents && ed.DomComponents.getConfig) {
ed.DomComponents.getConfig().textTags = textTags;
}
if (ed.Parser && ed.Parser.getConfig) {
const parserCfg = ed.Parser.getConfig();
parserCfg.textTags = textTags;
parserCfg.optionsHtml = parserCfg.optionsHtml || {};
parserCfg.optionsHtml.textTags = textTags;
parserCfg.optionsHtml.keepEmptyTextNodes = true;
}
} catch (e) {
log('CORE WARN', `textTags Konfiguration fehlgeschlagen: ${e.message}`, 'orange', 'warn');
}
}
if (B.ENABLE_EDITOR_EXTENSIONS !== false && B.ENABLE_RTE) {
if (typeof B.setupRichTextEditor === 'function') {
B.setupRichTextEditor(ed);
} else {
log('RTE WARN', 'RichText-Editor nicht geladen.', 'orange', 'warn');
}
}
if (B.ENABLE_EDITOR_EXTENSIONS !== false && (B.ENABLE_EDITOR_BEHAVIOR !== false || B.ENABLE_TABLE_BUILDER)) {
if (typeof B.setupTableBuilder === 'function') {
B.setupTableBuilder(ed);
} else {
log('TABLE WARN', 'Table-Builder nicht geladen.', 'orange', 'warn');
}
}
if (B.ENABLE_EDITOR_EXTENSIONS !== false && B.ENABLE_EDITOR_BEHAVIOR !== false) {
setupPlainTextPreserver(ed);
}
setupBlurLogger(ed);
loadDynamicFonts();
// Entfernt: jegliche Blur/RTE-Handler, die Inhalte verändern.
// Keep the fix off live updates to avoid cursor jumps; run only on RTE close.
        
window.__gjs = ed;
if (ed && ed.Modal && !ed.Modal.__bridgeCloseGuarded) {
ed.Modal.__bridgeCloseGuarded = true;
ed.Modal.__bridgeOriginalClose = ed.Modal.close ? ed.Modal.close.bind(ed.Modal) : null;
if (ed.Modal.close) {
ed.Modal.close = function (...args) {
if (window.__bridgeModalAllowClose && ed.Modal.__bridgeOriginalClose) {
return ed.Modal.__bridgeOriginalClose(...args);
}
return undefined;
};
}
B.allowModalCloseOnce = () => {
window.__bridgeModalAllowClose = true;
setTimeout(() => { window.__bridgeModalAllowClose = false; }, 0);
};
}
if (ed && ed.on) {
ed.on('modal:open', () => {
try {
const modalEl = ed.Modal && ed.Modal.el;
if (!modalEl) return;
const contentEl = modalEl.querySelector('.gjs-mdl-content');
if (!contentEl) return;
if (modalEl.querySelector('[data-bridge-viewcode-close]')) return;
const footer = document.createElement('div');
footer.style.display = 'flex';
footer.style.justifyContent = 'flex-end';
footer.style.paddingTop = '12px';
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = 'Schließen';
btn.setAttribute('data-bridge-viewcode-close', '1');
btn.setAttribute('data-bridge-modal-close', '1');
btn.style.padding = '6px 12px';
btn.style.border = '1px solid #cbd5f5';
btn.style.borderRadius = '4px';
btn.style.background = '#f8fafc';
btn.style.cursor = 'pointer';
btn.addEventListener('click', () => {
if (B.allowModalCloseOnce) B.allowModalCloseOnce();
if (ed.Modal && ed.Modal.close) ed.Modal.close();
});
footer.appendChild(btn);
contentEl.appendChild(footer);
} catch {}
});
}
if (ed && ed.Modal && ed.Modal.getModel && !ed.Modal.__bridgeModelHooked) {
ed.Modal.__bridgeModelHooked = true;
const mdl = ed.Modal.getModel();
if (mdl && typeof mdl.on === 'function') {
mdl.on('change:open', () => {
if (!mdl.get('open')) return;
setTimeout(() => {
try {
const modalEl = ed.Modal && ed.Modal.el;
if (!modalEl) return;
const contentEl = modalEl.querySelector('.gjs-mdl-content');
if (!contentEl) return;
if (modalEl.querySelector('[data-bridge-viewcode-close]')) return;
const footer = document.createElement('div');
footer.style.display = 'flex';
footer.style.justifyContent = 'flex-end';
footer.style.paddingTop = '12px';
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = 'Schließen';
btn.setAttribute('data-bridge-viewcode-close', '1');
btn.setAttribute('data-bridge-modal-close', '1');
btn.style.padding = '6px 12px';
btn.style.border = '1px solid #cbd5f5';
btn.style.borderRadius = '4px';
btn.style.background = '#f8fafc';
btn.style.cursor = 'pointer';
btn.addEventListener('click', () => {
if (B.allowModalCloseOnce) B.allowModalCloseOnce();
if (ed.Modal && ed.Modal.close) ed.Modal.close();
});
footer.appendChild(btn);
contentEl.appendChild(footer);
} catch {}
}, 0);
});
}
}
if (ed && !ed.__bridgeModalObserver) {
ed.__bridgeModalObserver = true;
const injectCloseButton = () => {
try {
const modalEl = ed.Modal && ed.Modal.el;
if (!modalEl) return;
const contentEl = modalEl.querySelector('.gjs-mdl-content');
if (!contentEl) return;
if (modalEl.querySelector('[data-bridge-modal-close]')) return;
const footer = document.createElement('div');
footer.style.display = 'flex';
footer.style.justifyContent = 'flex-end';
footer.style.paddingTop = '12px';
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = 'Schließen';
btn.setAttribute('data-bridge-modal-close', '1');
btn.style.padding = '6px 12px';
btn.style.border = '1px solid #cbd5f5';
btn.style.borderRadius = '4px';
btn.style.background = '#f8fafc';
btn.style.cursor = 'pointer';
btn.addEventListener('click', () => {
if (B.allowModalCloseOnce) B.allowModalCloseOnce();
if (ed.Modal && ed.Modal.close) ed.Modal.close();
});
footer.appendChild(btn);
contentEl.appendChild(footer);
} catch {}
};
const observer = new MutationObserver(() => {
injectCloseButton();
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(injectCloseButton, 0);
}
        
        // 🛑 KRITISCHE KORREKTUR 1: Explizite Erstellung aller konfigurierten Kategorien
        ensureConfiguredCategories(ed); 
        // 🛑 KRITISCHE KORREKTUR 2: Sofortige Label-Korrektur
        // Überschreibt den potenziell falschen, durch GrapesJS gesetzten Label-Namen
        Object.keys(B.CATEGORY_CONFIG || {}).forEach(catId => {
            const expectedLabel = B.CATEGORY_CONFIG[catId].label;
            const categoryModel = ed.BlockManager.getCategories().get(catId);
            
            if (categoryModel && categoryModel.get('label') !== expectedLabel) {
                // Setzen ohne das 'change:label' Event auszulösen (optional, aber sauber)
                categoryModel.set('label', expectedLabel, { silent: true }); 
                log('LABEL FIX', `Kategorie '${catId}' Label auf korrigiert: '${expectedLabel}'`, 'yellow', 'warn');
            }
        });
        // ---------------------------------------------------
        B.ensureViews && B.ensureViews(ed);
        
        log('BLOCK REGISTER', 'Registriere Bridge Blöcke, um Preset-Defaults zu überschreiben.', 'purple');
// 🛑 DYNAMISCHE AKTIVIERUNG DER SYNCHRONEN BLÖCKE (Ersetzt die fixen Aufrufe)
if (B.ENABLE_EDITOR_EXTENSIONS !== false && B.CATEGORY_CONFIG && ed) {
log('DYNAMIC ACTIVATION', 'Starte Aktivierung synchroner Block-Plugins (via Config).', 'purple');
// Iteriere über die konfigurierten Kategorien
Object.keys(B.CATEGORY_CONFIG).forEach(catId => {
const config = B.CATEGORY_CONFIG[catId];
// Verarbeite nur SYNCHRONE Plugins, die Dateien angeben
if ((config.registration_mode || 'sync') === 'sync' && Array.isArray(config.files)) {
config.files.forEach(fileName => {
// Korrigierte Funktion liefert jetzt z.B. 'BridgeBlocksCustom'
const objectName = getPluginObjectName(fileName);
                        const plugin = window[objectName];
                        
                        // Prüfen, ob das Skript geladen wurde und die Register-Funktion vorhanden ist
                        if (plugin && typeof plugin.register === 'function') {
                            log('DYNAMIC ACTIVATION', `Registriere sync Plugin: ${objectName} (${fileName})`, 'lime');
try {
plugin.register(ed);
if (objectName === 'BridgeBlocksPlaceholder') {
ed.__bridgePlaceholderActive = true;
}
} catch(e) {
log('DYNAMIC ACTIVATION ERROR', `Fehler beim Registrieren von ${objectName}: ${e.message}`, 'red', 'error');
}
                        } else {
                            log('DYNAMIC ACTIVATION WARNING', `Sync Plugin Objekt oder .register() Methode nicht gefunden: ${objectName} (${fileName})`, 'orange', 'warn');
                        }
});
}
});
}
const ensurePlaceholderRte = () => {
if (ed.__bridgePlaceholderActive) {
return true;
}
if (window.BridgeBlocksPlaceholder && typeof window.BridgeBlocksPlaceholder.register === 'function') {
try {
window.BridgeBlocksPlaceholder.register(ed);
ed.__bridgePlaceholderActive = true;
log('PLACEHOLDER SYNC', 'Placeholder-RTE aktiv.', 'lime');
} catch (err) {
log('PLACEHOLDER ERROR', `Placeholder-RTE Fehler: ${err.message}`, 'red', 'error');
}
return true;
}
return false;
};
if (B.ENABLE_EDITOR_EXTENSIONS !== false && B.ENABLE_PLACEHOLDERS !== false) {
if (!ensurePlaceholderRte()) {
const fallbackSrc = B.BASE_PATH_BRIDGE + 'blocks-placeholder.js';
log('PLACEHOLDER LOAD', 'Placeholder-RTE wird geladen.', '#B45309', 'warn');
loadScript(fallbackSrc, () => {
if (!ensurePlaceholderRte()) {
log('PLACEHOLDER ERROR', 'Placeholder-RTE konnte nicht initialisiert werden.', 'red', 'error');
}
});
}
} else {
log('PLACEHOLDER SKIP', 'Placeholder-RTE deaktiviert (Test).', '#B45309', 'warn');
}
// ---------------------------------------------------
log('INIT API', 'API-Elemente werden nun durch das Plugin bridge-blocks-api geladen. (ASYNCHRON)', 'orange');
        // ----------------------------------------------------------------------
        // DEBUGGING: ZÄHLE REKURSIVE EVENTS
        // ----------------------------------------------------------------------
        let eventCounts = {};
        let isParsing = false; 
        const MAX_CALLS = 1000; 
        const debugEvents = [
            'component:add', 
            'component:update', 
            'change:components', 
            'block:add',
            'change:attributes',
            'comp:update:status'
        ];
        const debugListener = (event, model) => {
            if (!isParsing) return;
            if (!eventCounts[event]) {
                eventCounts[event] = 0;
            }
            eventCounts[event]++;
            if (eventCounts[event] === MAX_CALLS + 1) { 
                // Diese kritischen Debug-Meldungen bleiben DIREKT im console-Objekt, 
                // da sie immer sichtbar sein müssen, um Endlosschleifen zu erkennen.
                console.error(`%c[DEBUG RECURSION ALARM] 🚨 Event '${event}' hat den Grenzwert von ${MAX_CALLS} überschritten!`, 'color:red; font-size: 1.1em; font-weight: bold;');
            }
            
            if (eventCounts[event] > MAX_CALLS && eventCounts[event] < (MAX_CALLS + 10)) { 
                 const type = (model && typeof model.get === 'function') ? model.get('type') : 'N/A';
                 const parentType = (model && typeof model.parent === 'function' && model.parent()) ? model.parent().get('type') : 'N/A';
                 // Diese bleiben console.log aus demselben Grund
                 console.log(`%c [RECURSION SOURCE] Event: ${event}, Type: ${type}, Parent: ${parentType}`, 'color: #8b0000;');
            }
        };
        ed.on('load', function() {
            debugEvents.forEach(event => ed.on(event, (model) => debugListener(event, model)));
            
            setTimeout(() => {
                (B.waitForBlocks ? B.waitForBlocks(ed) : Promise.resolve()).then(function(){
                    try {
                        log('CORE WARN', 'Führe finalen, verzögerten Cleanup-Lauf durch (2000ms).', 'orange', 'warn');
                        
                        B.normalizeCategories && B.normalizeCategories(ed); 
                        B.renderBlocks && B.renderBlocks(ed); 
                    } catch(e) {
                        log('CORE ERROR', `Finaler Cleanup-Fehler: ${e.message}`, 'red', 'error');
                    }
                });
            }, 2000); 
        }, { once: true });
        // ----------------------------------------------------------------------
        // MESSAGE HANDLER
        // ----------------------------------------------------------------------
        window.addEventListener('message', async function(ev){ 
            var data = ev.data || {};
            if (data.source !== 'admin') return;
if (data.type === 'init'){
B.ensureViews && B.ensureViews(ed);
ed.__contentLoaded = false;
try {
ed.setComponents('');
ed.setStyle('');
} catch {}
var html = (data.html || '').trim();
var hasJson = !!data.hasJson;
                if (!html) html = '<table style="width:100%;font-family:Arial,sans-serif"><tr><td><h1>Neues Dokument</h1><p>Inhalt ...</p></td></tr></table>';
                
                const applySnips = function(arr){
                    const list = (Array.isArray(arr)?arr:[]).map(s => ({ id:s.id, name:s.name, html: s.html || s.content || '' }));
                    
                    B.replaceSnippetBlocks && B.replaceSnippetBlocks(ed, list); 
                    
                    upsertCustomForBothCats(ed, {
                        ref: (data.ref && (Array.isArray(data.ref.sections) || Array.isArray(data.ref.blocks))) ? {
                            sections: data.ref.sections || [],
                            blocks:      data.ref.blocks        || []
                        } : { sections: [], blocks: [] },
                        snippets: list
                    });
                    
                    setTimeout(() => {
                        try {
                            // Erneutes Normalisieren nach Laden der Snippets (falls nötig)
                            B.normalizeCategories && B.normalizeCategories(ed); 
                            B.ensureViews && B.ensureViews(ed);
                            B.renderBlocks && B.renderBlocks(ed);
                            log('CORE WARN', 'normalize/render nach applySnips ausgeführt (1ms).', 'orange', 'warn'); 
                        } catch(e) {
                            log('CORE ERROR', `applySnips-Cleanup-Fehler: ${e.message}`, 'red', 'error');
                        }
                    }, 1); 
                };
                if (Array.isArray(data.snippets) && data.snippets.length) {
                    applySnips(data.snippets);
                } else if (B.USE_DYNAMIC_SECTIONS) {
                    applySnips([]);
                } else {
                    (B.fetchSnippets ? B.fetchSnippets() : Promise.resolve([])).then(applySnips);
                }
                if (data.ref && (Array.isArray(data.ref.sections) || Array.isArray(data.ref.blocks))) {
                    replaceReferenceLibrary(ed, {
                        sections: data.ref.sections || [],
                        blocks:      data.ref.blocks        || []
                    }, MODE);
                }
                // Finaler Aufruf nachrichtengesteuert (konsolidiert)
                setTimeout(() => {
                    (B.waitForBlocks ? B.waitForBlocks(ed) : Promise.resolve()).then(function(){
                        try {
                            log('CORE WARN', 'Führe nachrichtengesteuerten Final-Cleanup-Lauf durch (100ms).', 'orange', 'warn'); 
                            
if (!ed.__contentLoaded) {
log('CONTENT', 'Verarbeite initiale Editor-Daten (postMessage).', 'orange');
const jsonPayload = typeof data.json === 'string' ? data.json : '';
let applied = false;
if (jsonPayload) {
window.__GJS_IS_PARSING = true;
isParsing = true;
eventCounts = {};
try {
const parsedState = JSON.parse(jsonPayload);
const isArray = Array.isArray(parsedState);
const hasPages = parsedState && Array.isArray(parsedState.pages);
const isLegacy = parsedState && typeof parsedState === 'object'
&& !hasPages
&& (parsedState.components || parsedState.styles);
if (isArray) {
const looksHtmlWrapper = parsedState.length === 1 && parsedState[0] && parsedState[0].type === 'html';
if (!looksHtmlWrapper) {
ed.setComponents(parsedState);
applied = true;
log('CONTENT', 'JSON-Komponenten (Array) angewendet.', 'orange');
} else {
log('CONTENT WARN', 'JSON enthält nur HTML-Wrapper, nutze HTML-Fallback.', 'orange', 'warn');
applied = false;
}
} else if (isLegacy) {
const legacyComponents = parsedState.components || parsedState;
const looksHtmlWrapper = Array.isArray(legacyComponents)
&& legacyComponents.length === 1
&& legacyComponents[0]
&& legacyComponents[0].type === 'html';
if (!looksHtmlWrapper) {
ed.setComponents(legacyComponents);
if (parsedState.styles) {
ed.setStyle(parsedState.styles);
}
applied = true;
log('CONTENT', 'Legacy JSON (components/styles) angewendet.', 'orange');
} else {
log('CONTENT WARN', 'Legacy JSON enthält nur HTML-Wrapper, nutze HTML-Fallback.', 'orange', 'warn');
applied = false;
}
} else if (parsedState && typeof parsedState === 'object' && !hasPages) {
const looksHtmlWrapper = parsedState && parsedState.type === 'html';
if (!looksHtmlWrapper) {
ed.setComponents(parsedState);
applied = true;
log('CONTENT', 'JSON ohne Pages als Komponenten angewendet.', 'orange');
} else {
log('CONTENT WARN', 'JSON ohne Pages ist HTML-Wrapper, nutze HTML-Fallback.', 'orange', 'warn');
applied = false;
}
} else {
ed.loadProjectData(parsedState);
applied = true;
log('CONTENT', 'JSON-Projektzustand angewendet.', 'orange');
const hasComponents = ed.getComponents && ed.getComponents().length > 0;
const hasHtml = (ed.getHtml && ed.getHtml().trim()) ? true : false;
if (!hasComponents && !hasHtml && html) {
log('CONTENT WARN', 'JSON geladen aber leer, wechsle auf HTML-Fallback.', 'orange', 'warn');
applied = false;
}
}
} catch (e) {
log('CONTENT ERROR', `JSON loadProjectData Fehler: ${e.message}`, 'red', 'error');
try {
const fallback = parsedState && parsedState.components ? parsedState.components : parsedState;
if (fallback) {
ed.setComponents(fallback);
applied = true;
log('CONTENT', 'JSON-Fallback via setComponents angewendet.', 'orange');
}
} catch {}
if (!applied && !html) {
html = jsonPayload;
}
} finally {
window.__GJS_IS_PARSING = false;
isParsing = false;
}
}
if (!applied && html) {
window.__GJS_IS_PARSING = true; 
isParsing = true;
eventCounts = {};
try {
ed.setComponents(html); 
                                    } catch (e) {
                                        log('SET COMPONENTS FAILED', `setComponents Fehler: ${e.message}. Aufgerufene Event-Zähler: ${JSON.stringify(eventCounts)}`, 'red', 'error');
                                        throw e; 
                                    } finally {
                                        window.__GJS_IS_PARSING = false;
                                        isParsing = false;
                                        log('CONTENT', 'HTML-Inhalt in den Editor geladen (FALLBACK).', 'orange');
                                    }
                                }
ed.__contentLoaded = true;
}
                            
                            // Normalisierung am Ende
                            B.normalizeCategories && B.normalizeCategories(ed); 
                            B.renderBlocks && B.renderBlocks(ed);
                            
                        } catch(e) {
                            log('CORE ERROR', `Nachrichten-Final-Cleanup-Fehler: ${e.message}. Event-Zähler (im Log-Objekt): ${JSON.stringify(eventCounts)}`, 'red', 'error');
                        }
                    });
                }, 100); 
                try { var b=ed.Panels.getButton('views','open-blocks'); b && b.set('active',true); } catch {}
                badgeSay('Inhalt geladen','ok');
                setTimeout(function(){ badgeSay('bereit'); }, 1200);
            }
        }, false);
        
        try { B.send && B.send('core-ready', { mode: MODE }); } catch {}
        try { var bd=document.getElementById('badge'); if (bd) bd.remove(); } catch {}
    });
    
    window.onerror = function(message, source, lineno, colno, error) { 
        // Diese kritische Funktion MUSS console.error verwenden.
        console.error(`%c[${PluginName} - GLOBAL ERROR] Uncaught JS Error: ${message} (Quelle: ${source}:${lineno})`, 'color:red; font-weight:bold;');
        return false; 
    };
    
})();