/* /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; 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"')) { 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'); } const storageConf = { autoload: false, autosave: false, }; 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'] } }); 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'); } const hasPlainTextNodes = (el) => { if (!el) return false; const nodes = Array.from(el.childNodes || []); return nodes.some((node) => node && node.nodeType === 3 && node.textContent !== ''); }; const buildTextnodeComponents = (el) => { if (!el) return []; const nodes = Array.from(el.childNodes || []); const comps = []; nodes.forEach((node) => { if (!node) return; if (node.nodeType === 3) { const text = node.textContent; if (text !== null && text !== undefined && text !== '') { comps.push({ type: 'textnode', content: text }); } return; } if (node.nodeType === 1 && node.outerHTML) { comps.push(node.outerHTML); } }); return comps; }; const isTextLike = (cmp) => { const type = cmp && cmp.get ? cmp.get('type') : null; return type === 'text' || type === 'link' || type === 'button'; }; const modelHasContent = (cmp) => { if (!cmp) return false; if (typeof cmp.components === 'function') { const comps = cmp.components(); if (comps && comps.length) return true; } if (typeof cmp.get === 'function') { const content = cmp.get('content'); if (typeof content === 'string' && content !== '') return true; } return false; }; const fixPlainTextContent = (cmp) => { if (!cmp || !cmp.view || !cmp.view.el) return; if (!isTextLike(cmp)) return; const el = cmp.view.el; if (!hasPlainTextNodes(el)) return; if (modelHasContent(cmp)) return; const comps = buildTextnodeComponents(el); if (!comps.length) return; try { if (typeof cmp.components === 'function') { cmp.components(comps); } else if (typeof cmp.set === 'function') { cmp.set('content', el.innerHTML); } } catch (err) { log('CORE WARN', `Plain-Text Fix fehlgeschlagen: ${err.message}`, 'orange', 'warn'); } }; ed.on('rte:disable', (cmp) => { if (ed.__bridgeFixPlainText) return; ed.__bridgeFixPlainText = true; setTimeout(() => { try { fixPlainTextContent(cmp); } finally { ed.__bridgeFixPlainText = false; } }, 0); }); // Keep the fix off live updates to avoid cursor jumps; run only on RTE close. 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') === '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 ensurePlaceholderBlocks = () => { 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-Plugin nachträglich aktiviert.', 'lime'); } catch (err) { log('PLACEHOLDER ERROR', `Fehler beim Aktivieren des Placeholder-Plugins: ${err.message}`, 'red', 'error'); } return true; } return false; }; if (!ensurePlaceholderBlocks()) { const fallbackSrc = B.BASE_PATH_BRIDGE + 'blocks-placeholder.js'; log('PLACEHOLDER LOAD', 'Placeholder-Plugin fehlt – lade Fallback-Skript.', '#B45309', 'warn'); loadScript(fallbackSrc, () => { if (!ensurePlaceholderBlocks()) { log('PLACEHOLDER ERROR', 'Placeholder-Plugin konnte auch nach Fallback nicht initialisiert werden.', 'red', 'error'); } }); } // --------------------------------------------------- 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(); var hasJson = !!data.hasJson; if (!html) html = '
Neues DokumentInhalt ... |