/* /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"') || 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 = [...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) return; const isTextLike = component.is('text') || component.is('button') || component.is('link'); if (!isTextLike) return; const toolbar = (component.get && component.get('toolbar')) || []; if (toolbar.some((item) => item && item.command === 'bridge-open-richtext')) return; toolbar.push({ label: '', attributes: { title: 'Richtext bearbeiten' }, command: 'bridge-open-richtext', }); component.set && component.set('toolbar', toolbar); }; const openRichTextModal = (editor, component) => { if (!component || !component.is || !(component.is('text') || component.is('button') || component.is('link'))) { log('RTE', 'Bitte zuerst ein Text-Element auswaehlen.', '#888'); return; } const modal = editor.Modal; if (!modal) return; if (editor.__bridgeRteModalOpen) return; editor.__bridgeRteModalOpen = true; editor.__bridgeRteAllowClose = false; let reopenGuard = false; const closeModal = () => { editor.__bridgeRteAllowClose = true; editor.__bridgeRteModalOpen = false; const mdl = modal.getModel && modal.getModel(); if (modal.__bridgeOriginalClose) { modal.__bridgeOriginalClose(); } else if (mdl && typeof mdl.set === 'function') { mdl.set('open', false); } else if (modal.el) { modal.el.style.display = 'none'; } }; if (!modal.__bridgeCloseLocked) { modal.__bridgeCloseLocked = true; modal.__bridgeOriginalClose = modal.close ? modal.close.bind(modal) : null; modal.close = function (...args) { if (editor.__bridgeRteAllowClose && typeof modal.__bridgeOriginalClose === 'function') { editor.__bridgeRteAllowClose = false; return modal.__bridgeOriginalClose(...args); } if (editor.__bridgeRteAllowClose && !modal.__bridgeOriginalClose) { editor.__bridgeRteAllowClose = false; const mdl = modal.getModel && modal.getModel(); if (mdl && typeof mdl.set === 'function') { mdl.set('open', false); } else if (modal.el) { modal.el.style.display = 'none'; } } }; } 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', () => { closeModal(); }); const saveBtn = doc.createElement('button'); saveBtn.type = 'button'; saveBtn.textContent = 'Speichern'; 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; const isTextLike = component && component.is && (component.is('text') || component.is('button') || component.is('link')); const hasTags = /<[^>]+>/.test(html); try { if (!isTextLike && component.components) { component.components(html); } if (component.set) { const nextContent = (!hasTags && isTextLike) ? html : html; component.set('content', nextContent); component.trigger && component.trigger('change:content'); component.trigger && component.trigger('change:components'); } if (component.view && component.view.render) { component.view.render(); } if (editor && typeof editor.trigger === 'function') { editor.trigger('component:update', component); } } catch {} closeModal(); }); actions.appendChild(cancelBtn); actions.appendChild(saveBtn); container.appendChild(actions); modal.setTitle('Richtext Editor'); modal.setContent(container); const mdl = modal.getModel && modal.getModel(); if (mdl && typeof mdl.set === 'function') { mdl.set('closeOnEsc', false); mdl.set('closeOnClick', false); } if (typeof modal.onceClose === 'function') { modal.onceClose(() => { editor.__bridgeRteModalOpen = false; editor.__bridgeRteAllowClose = false; }); } if (mdl && typeof mdl.on === 'function') { const handler = () => { if (editor.__bridgeRteAllowClose) { mdl.off && mdl.off('change:open', handler); return; } if (!mdl.get('open') && !reopenGuard) { reopenGuard = true; mdl.set('open', true); setTimeout(() => { reopenGuard = false; }, 0); } }; mdl.on('change:open', handler); } try { modal.open({ closeOnEsc: false, closeOnClick: false }); } catch { 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: '', 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', ]); const isTextLike = (model) => !!(model && model.is && (model.is('text') || model.is('button') || model.is('link'))); let lastTextSelection = { id: null, ts: 0 }; editor.on('component:selected', (model) => { ensureTextToolbarButton(editor, model); if (!isTextLike(model)) return; const now = Date.now(); if (lastTextSelection.id === model.cid && (now - lastTextSelection.ts) < 450) { openRichTextModal(editor, model); } lastTextSelection = { id: model.cid, ts: now }; }); editor.on('component:add', (model) => ensureTextToolbarButton(editor, model)); editor.on('component:dblclick', (model) => { if (isTextLike(model)) { openRichTextModal(editor, model); } }); editor.on('canvas:frame:load', () => { const body = editor.Canvas && editor.Canvas.getBody && editor.Canvas.getBody(); if (!body || body.__bridgeRteDblclickBound) return; body.__bridgeRteDblclickBound = true; body.addEventListener('dblclick', () => { const selected = editor.getSelected && editor.getSelected(); if (isTextLike(selected)) { openRichTextModal(editor, selected); } }, true); }); }; const setupTableBuilder = (editor) => { const domc = editor.DomComponents; if (!domc) return; const icon = (path) => ``; const tableIcon = icon('M3 4h18v16H3V4zm2 2v3h6V6H5zm8 0v3h6V6h-6zM5 11v3h6v-3H5zm8 0v3h6v-3h-6zM5 16v2h6v-2H5zm8 0v2h6v-2h-6z'); const ensureTableTags = () => { const baseType = domc.getType('default'); if (!baseType) return; const BaseModel = baseType.model; const BaseView = baseType.view; const tags = ['table', 'tbody', 'thead', 'tfoot', 'tr', 'td', 'th']; tags.forEach((tag) => { if (domc.getType(tag)) return; domc.addType(tag, { model: BaseModel, view: BaseView, isComponent: el => { if (el.tagName && el.tagName.toLowerCase() === tag) { return { type: tag }; } return ''; }, }); }); }; const collectTableCells = (component) => { const el = component?.view?.el; if (!el) return []; return Array.from(el.querySelectorAll('tr')).map(row => Array.from(row.children || []).map(cell => cell.innerHTML || '') ); }; const buildTableHtml = (rows, cols, existing) => { const safeRows = Math.max(1, Math.min(20, Number(rows) || 1)); const safeCols = Math.max(2, Math.min(2, Number(cols) || 2)); const cellStyle = "padding:8px;border:1px solid #e2e8f0;font-size:13px"; const headStyle = "text-align:left;padding:8px;border:1px solid #e2e8f0;background-color:#f8fafc;font-size:13px"; let html = ''; for (let r = 0; r < safeRows; r++) { html += '
Neues DokumentInhalt ... |