/* /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(/
/gi, '')
.replace(/ /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 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 {
if (target.components) {
target.components(stored.html);
}
target.set('content', stored.html);
target.trigger && target.trigger('change:content');
try { console.log('[PLAIN TEXT RESTORE] Inhalt wiederhergestellt.'); } catch {}
} catch {}
};
editor.on('component:update', (model) => restoreIfEmpty(model));
editor.on('component:input', (model) => restoreIfEmpty(model));
editor.on('component:deselected', (model) => restoreIfEmpty(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 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 = model && model.get ? String(model.get('content') || '') : '';
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 summary = {
source: 'ui',
modelId: model && (model.getId ? model.getId() : model.get && model.get('id')),
expectedFromView: viewHtml.slice(0, 500),
actualModelContent: modelContent.slice(0, 500),
viewHtmlLen: viewHtml.length,
modelContentLen: modelContent.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 buildSnapshot = (target, selected, selectedEl, editorHtml) => {
let modelContent = '';
try {
modelContent = selected && selected.get ? String(selected.get('content') || '') : '';
} 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 logBlur = (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;
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 {}
};
body.addEventListener('focusin', logFocus, true);
body.addEventListener('blur', logBlur, true);
body.addEventListener('focusout', logBlur, 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 = '
Neues DokumentInhalt ... |