/* /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, }; const execRteCommand = (rte, cmd, value, docOverride) => { try { if (rte && typeof rte.exec === 'function') { rte.exec(cmd, value); return true; } } catch {} try { const doc = docOverride || rte?.doc || rte?.el?.ownerDocument || document; if (rte?.el && typeof rte.el.focus === 'function') { rte.el.focus(); } const ok = doc.execCommand(cmd, false, value); if (ok === false && cmd === 'insertText') { doc.execCommand('insertHTML', false, String(value || '').replace(//g, '>')); } return true; } catch {} return false; }; const normalizeRteActionList = (editor, names) => { const cfg = editor.getConfig ? editor.getConfig() : {}; cfg.richTextEditor = cfg.richTextEditor || {}; const base = Array.isArray(cfg.richTextEditor.actions) ? cfg.richTextEditor.actions.slice() : ['bold', 'italic', 'underline', 'strikethrough', 'link']; names.forEach((name) => { const exists = base.some((item) => (typeof item === 'string' ? item === name : item && item.name === name)); if (!exists) base.push(name); }); cfg.richTextEditor.actions = base; }; const ensureTextToolbarButton = (editor, component) => { if (!component || !component.is || !component.is('text')) return; const toolbar = (component.get && component.get('toolbar')) || []; if (toolbar.some((item) => item && item.command === 'bridge-open-richtext')) return; toolbar.push({ attributes: { class: 'fa fa-pencil', title: 'Richtext bearbeiten' }, command: 'bridge-open-richtext', }); component.set && component.set('toolbar', toolbar); }; const openRichTextModal = (editor, component) => { if (!component || !component.is || !component.is('text')) { log('RTE', 'Bitte zuerst ein Text-Element auswaehlen.', '#888'); return; } const modal = editor.Modal; if (!modal) return; const doc = document; const container = doc.createElement('div'); container.style.display = 'flex'; container.style.flexDirection = 'column'; container.style.gap = '10px'; container.style.height = '100%'; container.style.minHeight = '360px'; const toolbar = doc.createElement('div'); toolbar.style.display = 'flex'; toolbar.style.flexWrap = 'wrap'; toolbar.style.gap = '6px'; toolbar.style.alignItems = 'center'; const content = doc.createElement('div'); content.contentEditable = 'true'; content.style.flex = '1'; content.style.minHeight = '280px'; content.style.border = '1px solid #cbd5f5'; content.style.borderRadius = '6px'; content.style.padding = '12px'; content.style.background = '#ffffff'; content.style.overflow = 'auto'; content.style.fontFamily = 'Arial, sans-serif'; content.style.fontSize = '14px'; const initialHtml = (component.view && component.view.el && component.view.el.innerHTML) || (component.get && component.get('content')) || ''; content.innerHTML = initialHtml; const addButton = (labelHtml, title, cmd, valueGetter) => { const btn = doc.createElement('button'); btn.type = 'button'; btn.innerHTML = labelHtml; btn.title = title; btn.style.padding = '4px 8px'; btn.style.border = '1px solid #cbd5f5'; btn.style.borderRadius = '4px'; btn.style.background = '#f8fafc'; btn.style.cursor = 'pointer'; btn.addEventListener('click', () => { content.focus(); const value = typeof valueGetter === 'function' ? valueGetter() : valueGetter; if (value === null || value === undefined) return; if (cmd === 'createLink' && !value) return; execRteCommand(null, cmd, value, content.ownerDocument); }); toolbar.appendChild(btn); }; const addSelect = (options, title, onChange) => { const select = doc.createElement('select'); select.title = title; select.style.padding = '4px 8px'; select.style.border = '1px solid #cbd5f5'; select.style.borderRadius = '4px'; select.style.background = '#ffffff'; options.forEach((opt) => { const optEl = doc.createElement('option'); optEl.value = opt.value; optEl.textContent = opt.label; select.appendChild(optEl); }); select.addEventListener('change', () => { const value = select.value; content.focus(); if (value) onChange(value); }); toolbar.appendChild(select); return select; }; const insertText = (text) => { content.focus(); if (!execRteCommand(null, 'insertText', text)) { execRteCommand(null, 'insertHTML', String(text).replace(//g, '>')); } }; const icon = (path) => ``; addButton('B', 'Fett', 'bold'); addButton('I', 'Kursiv', 'italic'); addButton('U', 'Unterstrichen', 'underline'); addButton('S', 'Durchgestrichen', 'strikethrough'); addButton(icon('M4 7h10v2H4zM4 11h16v2H4zM4 15h10v2H4z'), 'Liste (ungeordnet)', 'insertUnorderedList'); addButton(icon('M4 7h14v2H4zM4 11h14v2H4zM4 15h14v2H4z') + '1.', 'Liste (geordnet)', 'insertOrderedList'); addButton(icon('M4 7h10v2H4zM4 11h16v2H4zM4 15h12v2H4z'), 'Linksbundig', 'justifyLeft'); addButton(icon('M5 7h14v2H5zM4 11h16v2H4zM5 15h14v2H5z'), 'Zentriert', 'justifyCenter'); addButton(icon('M10 7h10v2H10zM4 11h16v2H4zM8 15h12v2H8z'), 'Rechtsbundig', 'justifyRight'); addButton(icon('M4 7h16v2H4zM4 11h16v2H4zM4 15h16v2H4z'), 'Blocksatz', 'justifyFull'); addButton('Link', 'Link einfuegen', 'createLink', () => prompt('Link-URL eingeben', 'https://')); addButton('Unlink', 'Link entfernen', 'unlink'); addButton('Sub', 'Tiefgestellt', 'subscript'); addButton('Sup', 'Hochgestellt', 'superscript'); addButton('Einr.', 'Einzug', 'indent'); addButton('Aus.', 'Ausruecken', 'outdent'); addButton('Clear', 'Formatierung entfernen', 'removeFormat'); const fontOptions = (B.RTE_FONTS && Array.isArray(B.RTE_FONTS) && B.RTE_FONTS.length) ? B.RTE_FONTS : [ { label: 'Arial', value: 'Arial, sans-serif' }, { label: 'Calibri', value: 'Calibri, sans-serif' }, { label: 'Cambria', value: 'Cambria, serif' }, { label: 'Georgia', value: 'Georgia, serif' }, { label: 'Tahoma', value: 'Tahoma, sans-serif' }, { label: 'Times New Roman', value: 'Times New Roman, serif' }, { label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' }, { label: 'Verdana', value: 'Verdana, sans-serif' }, ]; addSelect([ { label: 'Schriftart', value: '' }, ...fontOptions, ], 'Schriftart', (value) => execRteCommand(null, 'fontName', value, content.ownerDocument)); addSelect([ { label: 'Groesse', value: '' }, { label: '10px', value: '1' }, { label: '12px', value: '2' }, { label: '14px', value: '3' }, { label: '16px', value: '4' }, { label: '18px', value: '5' }, { label: '24px', value: '6' }, { label: '32px', value: '7' }, ], 'Schriftgroesse', (value) => execRteCommand(null, 'fontSize', value, content.ownerDocument)); const emojiBtn = doc.createElement('button'); emojiBtn.type = 'button'; emojiBtn.textContent = ':-)'; emojiBtn.title = 'Emoticon einfuegen'; emojiBtn.style.padding = '4px 8px'; emojiBtn.style.border = '1px solid #cbd5f5'; emojiBtn.style.borderRadius = '4px'; emojiBtn.style.background = '#f8fafc'; emojiBtn.style.cursor = 'pointer'; emojiBtn.addEventListener('click', () => { const pick = prompt('Emoticon eingeben', ':)'); if (pick) insertText(pick); }); toolbar.appendChild(emojiBtn); container.appendChild(toolbar); container.appendChild(content); const actions = doc.createElement('div'); actions.style.display = 'flex'; actions.style.justifyContent = 'flex-end'; actions.style.gap = '8px'; const cancelBtn = doc.createElement('button'); cancelBtn.type = 'button'; cancelBtn.textContent = 'Abbrechen'; cancelBtn.style.padding = '6px 12px'; cancelBtn.style.border = '1px solid #cbd5f5'; cancelBtn.style.borderRadius = '4px'; cancelBtn.style.background = '#f8fafc'; cancelBtn.style.cursor = 'pointer'; cancelBtn.addEventListener('click', () => modal.close()); const saveBtn = doc.createElement('button'); saveBtn.type = 'button'; saveBtn.textContent = 'Uebernehmen'; saveBtn.style.padding = '6px 12px'; saveBtn.style.border = '1px solid #0ea5e9'; saveBtn.style.borderRadius = '4px'; saveBtn.style.background = '#0ea5e9'; saveBtn.style.color = '#ffffff'; saveBtn.style.cursor = 'pointer'; saveBtn.addEventListener('click', () => { const html = content.innerHTML; try { if (component.components) { component.components(html); } if (component.set) { component.set('content', html); component.trigger && component.trigger('change:content'); } if (component.view && component.view.render) { component.view.render(); } } catch {} modal.close(); }); actions.appendChild(cancelBtn); actions.appendChild(saveBtn); container.appendChild(actions); modal.setTitle('Richtext Editor'); modal.setContent(container); modal.open(); }; const setupRichTextEditor = (editor) => { if (!editor || !editor.RichTextEditor) return; const rte = editor.RichTextEditor; const icon = (path) => ``; const resolveFontOptions = () => (B.RTE_FONTS && Array.isArray(B.RTE_FONTS) && B.RTE_FONTS.length) ? B.RTE_FONTS : [ { label: 'Arial', value: 'Arial, sans-serif' }, { label: 'Calibri', value: 'Calibri, sans-serif' }, { label: 'Cambria', value: 'Cambria, serif' }, { label: 'Georgia', value: 'Georgia, serif' }, { label: 'Tahoma', value: 'Tahoma, sans-serif' }, { label: 'Times New Roman', value: 'Times New Roman, serif' }, { label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' }, { label: 'Verdana', value: 'Verdana, sans-serif' }, ]; const addAction = (name, icon, title, command, valueGetter) => { if (rte.get && rte.get(name)) return; rte.add(name, { icon, attributes: { title }, result: (rteInstance) => { const value = typeof valueGetter === 'function' ? valueGetter() : valueGetter; if (value === null || value === undefined) return; if ((command === 'insertText' || command === 'fontName' || command === 'fontSize' || command === 'createLink') && !value) { return; } execRteCommand(rteInstance, command, value); }, }); }; addAction('bridge-align-left', icon('M4 7h10v2H4zM4 11h16v2H4zM4 15h12v2H4z'), 'Linksbundig', 'justifyLeft'); addAction('bridge-align-center', icon('M5 7h14v2H5zM4 11h16v2H4zM5 15h14v2H5z'), 'Zentriert', 'justifyCenter'); addAction('bridge-align-right', icon('M10 7h10v2H10zM4 11h16v2H4zM8 15h12v2H8z'), 'Rechtsbundig', 'justifyRight'); addAction('bridge-align-justify', icon('M4 7h16v2H4zM4 11h16v2H4zM4 15h16v2H4z'), 'Blocksatz', 'justifyFull'); addAction('bridge-ul', icon('M4 7h10v2H4zM4 11h16v2H4zM4 15h10v2H4z'), 'Liste (ungeordnet)', 'insertUnorderedList'); addAction('bridge-ol', icon('M4 7h16v2H4zM4 11h16v2H4zM4 15h16v2H4z'), 'Liste (geordnet)', 'insertOrderedList'); addAction('bridge-emoji', ':-)', 'Emoticon einfuegen', 'insertText', () => prompt('Emoticon eingeben', ':)')); addAction('bridge-font-family', 'F', 'Schriftart', 'fontName', () => { const fonts = resolveFontOptions(); const example = fonts.map((f) => f.label).slice(0, 5).join(', '); return prompt(`Schriftart (z.B. ${example})`, fonts[0]?.label || 'Arial'); }); addAction('bridge-font-size', 'Px', 'Schriftgroesse', 'fontSize', () => { const raw = prompt('Schriftgroesse in px (10-32)', '14'); const val = Number(raw || 14); if (Number.isNaN(val)) return '3'; const map = [ { px: 10, cmd: '1' }, { px: 12, cmd: '2' }, { px: 14, cmd: '3' }, { px: 16, cmd: '4' }, { px: 18, cmd: '5' }, { px: 24, cmd: '6' }, { px: 32, cmd: '7' }, ]; let best = map[0]; map.forEach((entry) => { if (Math.abs(entry.px - val) < Math.abs(best.px - val)) best = entry; }); return best.cmd; }); if (!(rte.get && rte.get('bridge-open-richtext'))) { rte.add('bridge-open-richtext', { icon: 'RTE', attributes: { title: 'Richtext Editor' }, result: () => { const component = editor.getSelected && editor.getSelected(); openRichTextModal(editor, component); }, }); } if (!editor.Commands.get('bridge-open-richtext')) { editor.Commands.add('bridge-open-richtext', { run(ed, sender, opts = {}) { if (sender && sender.set) sender.set('active', 0); const component = opts.component || ed.getSelected(); openRichTextModal(ed, component); }, }); } normalizeRteActionList(editor, [ 'bridge-open-richtext', 'bridge-font-family', 'bridge-font-size', 'bridge-align-left', 'bridge-align-center', 'bridge-align-right', 'bridge-align-justify', 'bridge-ul', 'bridge-ol', 'bridge-emoji', 'bridge-placeholder', ]); editor.on('component:selected', (model) => ensureTextToolbarButton(editor, model)); editor.on('component:add', (model) => ensureTextToolbarButton(editor, model)); editor.on('component:dblclick', (model) => { if (model && model.is && model.is('text')) { openRichTextModal(editor, model); } }); }; 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'] } }); 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'); } setupRichTextEditor(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;                  // 🛑 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 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 (!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'); } }); } // --------------------------------------------------- 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 Dokument

Inhalt ...

';                                  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');                               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); ed.loadProjectData(parsedState); applied = true; log('CONTENT', 'JSON-Projektzustand angewendet.', 'orange'); } catch (e) { log('CONTENT ERROR', `JSON loadProjectData Fehler: ${e.message}`, 'red', 'error'); } finally { window.__GJS_IS_PARSING = false; isParsing = false; } } if (!applied && 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;      };      })();