This commit is contained in:
2025-12-04 22:33:05 +01:00
parent 316175e158
commit 9dee06cdd6
145 changed files with 16865 additions and 88 deletions

View File

@@ -0,0 +1,624 @@
/* /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.
    };
    // ----------------------------------------------------------------------
    // 🛑 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;
    
    // --- Initialisierung BridgeParts (B) und Plugin-Registry ---
    B.BASE_PATH_BRIDGE = '../assets/js/bridge/';
    B.BASE_PATH_CONFIG = B.BASE_PATH_BRIDGE; 
    // NEU: Standard-API-Endpunkt für Fallbacks, falls category-config.js ihn nicht setzt.
    // **KORREKTUR**: Auf '/api/editor' FIX eingestellt.
    B.API_BASE = '/api/editor'; // <<< FIX AUF /api/editor
    B.STORAGE_URL_BASE = '/api/editor'; // <<< FIX: Erzwingt, dass auch der Storage Manager diesen Pfad verwendet
    
    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"')) {
                    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 = [...coreFiles];
        
        function recursiveLoader(list, index = 0) {
            if (index >= list.length) {
                log('LOAD END', 'Initial-Bridge-Skripte geladen.', 'green');
                
                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, 
            // 🛑 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');
        }
        
        // Speicherkonfiguration extrahieren, um die URL in onLoad zu verwenden.
        // 🎯 KORREKTUR für mehr Flexibilität: Verwende B.STORAGE_URL_BASE, falls gesetzt, anstatt window.location.href.
        // Verwenden Sie B.API_BASE (Standard /api/editor) als Fallback für die Storage-URL
        const storageBase = B.STORAGE_URL_BASE || B.API_BASE; // B.API_BASE sollte jetzt korrekt sein
        // Robustes Anhängen von Query-Parametern.
        // Prüft, ob 'storageBase' bereits Query-Parameter enthält ('?')
        const actionSeparator = storageBase.indexOf('?') === -1 ? '?' : '&';
        const loadUrl = storageBase + actionSeparator + 'action=get&resource=' + (window.__editorMode || 'templates') + '&id=' + (window.__editorId || 0); // KRITISCHE ERGÄNZUNG: Resource und ID
        const storeUrl = storageBase + actionSeparator + 'action=update&resource=' + (window.__editorMode || 'templates') + '&id=' + (window.__editorId || 0); // KRITISCHE ERGÄNZUNG: Resource und ID
        const storageConf = {
            type: 'remote',
            // urlLoad: loadUrl, // ENTFERNT (korrekt, da customFetch verwendet wird)
            urlStore: storeUrl,
            
            // 🛑 KRITISCHER ABSCHNITT: customFetch MUSS DIE ERWARTETE SIGNATUR HABEN: customFetch(url, options)
            customFetch: async (url, options) => { // <<< KORREKTUR DER SIGNATUR
                // 1. Log Start
                log('STORAGE START', 'Template wird geladen.', '#008080', 'info', true); 
                // 2. Log Link
                log('API REQUEST', `Link für den API Request: ${loadUrl}`, '#4682B4', 'log', false); 
                const fetchOptions = {
                    method: 'GET',
                    headers: { 'Content-Type': 'application/json' },
                    // Wichtig: Die übergebenen Optionen nicht vergessen zu mergen
                    ...options
                };
                
                let data = {};
                let rawResponse = '';
                try {
                    // Verwendung der intern definierten loadUrl
                    const response = await fetch(loadUrl, fetchOptions); 
                    
                    if (!response.ok) {
                        const errorText = await response.text();
                        throw new Error(`HTTP-Fehler ${response.status}: ${errorText}`);
                    }
                    // Holen des Raw Texts, um ihn loggen und parsen zu können
                    rawResponse = await response.text();
                    
                    // 3. Log Result
                    log('API RESPONSE', 'Result vom API Request (Raw Text/JSON):', '#4682B4', 'log', false);
                    console.log(rawResponse); // Loggt den reinen String für die Analyse
                    
                    // Versuch der JSON-Analyse (um den GrapesJS-Fehler zu vermeiden)
                    try {
                        data = JSON.parse(rawResponse);
                        log('STORAGE PARSE', 'Raw Response als JSON geparst.', 'green');
                    } catch (e) {
                        log('STORAGE PARSE ERROR', `Fehler beim Parsen der Antwort: ${e.message}. Antwort war wahrscheinlich kein gültiges JSON.`, 'red', 'error', true);
                        // Im Falle eines Parsing-Fehlers, leeres Objekt für Fallback-Logik
                        data = {};
                    }
                } catch (e) {
                    log('STORAGE FETCH ERROR', `Fehler beim Abruf: ${e.message}`, 'red', 'error', true);
                    // Sicherstellen, dass die Promise mit einem leeren Zustand erfüllt wird
                    // Wir müssen dennoch den Log End ausführen, bevor wir zurückkehren
                }
                
                // 4. Log End
                log('STORAGE END', 'Template wurde geladen.', '#008080', 'info', true); 
                
                // --- Logik zur Extraktion des GrapesJS States aus der API-Antwort ---
                let state = {};
                if (data && data.gjs_data) {
                    log('STORAGE LOAD', 'Voller GrapesJS State aus "gjs_data" geladen.', 'green');
                    state = data.gjs_data; 
                }
                else if (data && data.content) {
                    try {
                        const parsedState = JSON.parse(data.content);
                        log('STORAGE LOAD', 'Voller GrapesJS State aus "content" (JSON-String) geladen.', 'yellow');
                        state = parsedState;
                    } catch (e) {
                        log('STORAGE ERROR', `Fehler beim Parsen von "content": ${e.message}.`, 'red', 'error');
                    }
                }
                // HINWEIS: Füge Fallback für "topContent" hinzu, basierend auf dem Server-Log
                else if (data && data.topContent) {
                    try {
                        const parsedState = JSON.parse(data.topContent);
                        log('STORAGE LOAD', 'Voller GrapesJS State aus "topContent" (JSON-String) geladen.', 'green');
                        state = parsedState;
                    } catch (e) {
                        log('STORAGE ERROR', `Fehler beim Parsen von "topContent": ${e.message}.`, 'red', 'error');
                    }
                }
                else {
                    log('STORAGE WARNING', 'Kein vollständiger GrapesJS State gefunden. Editor lädt leeren State.', 'orange', 'warn');
                }
                // customFetch MUSS den geladenen State zurückgeben
                return state;
            },
            // --- ENDE customFetch ---
            // onLoad ist bei customFetch nicht mehr nötig
            // onLoad: (response) => { ... }, 
            
            // KRITISCH: Speichert den vollen State als JSON-String im Feld 'json_content'.
            onStore: (data) => {
                // ACHTUNG: ed existiert hier nicht, muss über window.__gjs geladen werden ODER ed als Argument akzeptiert werden
                const ed = window.__gjs;
                return {
                    json_content: JSON.stringify(data), 
                    html: ed ? ed.getHtml() : '' // Fügen Sie den HTML-Output zur Abwärtskompatibilität hinzu
                };
            },
        };
        var ed = grapesjs.init({
            container: '#gjs',
            height: '100vh',
            
            // 🛑 KRITISCHE KORREKTUR: storageManager aktivieren und konfigurieren
            storageManager: storageConf,
            
            plugins: pluginsList, 
            pluginsOpts: {},
            // 🛑 KRITISCHE ERGÄNZUNG: Verhindert das automatische Ausblenden leerer Kategorien
            blockManager: { 
                hideEmpty: false 
            }
        });
        
        window.__gjs = ed;
        
        // 🛑 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.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' && 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);
                            } 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');
                        }
                    });
                }
            });
        }
        // ---------------------------------------------------
        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);
                var html = (data.html || '').trim();
                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 (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'); 
                            
                            // 🛑 KRITISCHE KORREKTUR: Entferne das erzwungene ed.setComponents(html)
                            // Das Laden des Inhalts wird jetzt vom storageManager übernommen (via customFetch).
                            if (!ed.__contentLoaded) {
                                log('CONTENT', 'Erster Ladevorgang (storageManager) ist abgeschlossen.', 'orange');
                                
                                // HINWEIS: Wenn der Editor initial leer lädt (z.B. neue Vorlage),
                                // MUSS hier der initiale HTML-Code eingefügt werden.
                                // Da der storageManager aber automatisch lädt, 
                                // sollte dieser Block nur für den Initialfall "Neu" greifen.
                                if (html && !ed.getComponents().length) {
                                    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; 
    };
    
})();