From 764d427b1c99f1bf59da95e430899fa3630cae74 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Sat, 7 Feb 2026 23:16:48 +0100 Subject: [PATCH] Umbau custom blocks --- config/current.ver | 2 +- public/assets/js/bridge/blocks-custom.js | 210 +++++------------- .../bridge/blocks-custom/elements/button.js | 10 + .../bridge/blocks-custom/elements/divider.js | 9 + .../bridge/blocks-custom/elements/footer.js | 14 ++ .../js/bridge/blocks-custom/elements/hero.js | 13 ++ .../blocks-custom/elements/image-text.js | 32 +++ .../js/bridge/blocks-custom/elements/image.js | 10 + .../bridge/blocks-custom/elements/spacer.js | 9 + .../blocks-custom/elements/table-2xn.js | 57 +++++ .../js/bridge/blocks-custom/elements/text.js | 20 ++ src/ApiKernel.php | 21 ++ 12 files changed, 256 insertions(+), 151 deletions(-) create mode 100644 public/assets/js/bridge/blocks-custom/elements/button.js create mode 100644 public/assets/js/bridge/blocks-custom/elements/divider.js create mode 100644 public/assets/js/bridge/blocks-custom/elements/footer.js create mode 100644 public/assets/js/bridge/blocks-custom/elements/hero.js create mode 100644 public/assets/js/bridge/blocks-custom/elements/image-text.js create mode 100644 public/assets/js/bridge/blocks-custom/elements/image.js create mode 100644 public/assets/js/bridge/blocks-custom/elements/spacer.js create mode 100644 public/assets/js/bridge/blocks-custom/elements/table-2xn.js create mode 100644 public/assets/js/bridge/blocks-custom/elements/text.js diff --git a/config/current.ver b/config/current.ver index e0bb42e..47ddbf2 100644 --- a/config/current.ver +++ b/config/current.ver @@ -1 +1 @@ -1.2.65 \ No newline at end of file +1.2.66 \ No newline at end of file diff --git a/public/assets/js/bridge/blocks-custom.js b/public/assets/js/bridge/blocks-custom.js index 74182b7..14d32d9 100644 --- a/public/assets/js/bridge/blocks-custom.js +++ b/public/assets/js/bridge/blocks-custom.js @@ -1,29 +1,19 @@ -/* /assets/js/bridge/blocks-custom.js (FINAL & LOG-KONTROLLIERT) */ +/* /assets/js/bridge/blocks-custom.js (DYNAMIC ELEMENT LOADER) */ (function () { - const PluginName = 'blocks-custom'; const B = window.BridgeParts || (window.BridgeParts = {}); - // ---------------------------------------------------------------------- - // 🎯 NEU: LOKALE LOG-KONFIGURATION UND WRAPPER - // ---------------------------------------------------------------------- - // Setzen Sie dies auf 'false' in der config.js oder hier, um alle Logs NUR für dieses Plugin zu deaktivieren. if (B.LOG_CONFIG && B.LOG_CONFIG.PLUGINS) { - B.LOG_CONFIG.PLUGINS[PluginName] = false; // <-- HIER IST IHR SCHALTER + B.LOG_CONFIG.PLUGINS[PluginName] = false; } - // NEUER LOKALER WRAPPER, der die zentrale B.log Funktion verwendet: const log = (type, message, color = '#FFD700', logType = 'info', force = false) => { if (typeof B.log === 'function') { B.log(PluginName, `[${type}] ${message}`, color, logType, force); } else if (logType === 'error') { - // Fallback für kritische Fehler, wenn B.log fehlt console.error(`%c[${PluginName} - ${type}] %c${message}`, `color:red; font-weight:bold;`, 'color:inherit;'); } }; - // ---------------------------------------------------------------------- - - log('FILE CHECK', 'Datei-IIFE startet.'); // NEU: Kontrollierbarer Start-Log if (window.__CUSTOM_BLOCKS_LOADED) return; window.__CUSTOM_BLOCKS_LOADED = true; @@ -32,167 +22,87 @@ const ALL_CUSTOM_BLOCK_IDS = []; function addOnce(bm, id, def, category = TARGET_CAT_ID) { - // Hinzufügen des Blocks und Sicherstellen der Kategorie-Zuweisung try { - bm.add(id, { ...def, category }); - ALL_CUSTOM_BLOCK_IDS.push(id); - log('BLOCK ADD', `Block '${id}' erfolgreich hinzugefügt.`, '#B8860B'); + bm.add(id, { ...def, category }); + ALL_CUSTOM_BLOCK_IDS.push(id); + log('BLOCK ADD', `Block '${id}' erfolgreich hinzugefügt.`, '#B8860B'); } catch (e) { - log('BLOCK ERROR', `Fehler beim Hinzufügen von Block '${id}': ${e.message}`, 'red', 'error'); + log('BLOCK ERROR', `Fehler beim Hinzufügen von Block '${id}': ${e.message}`, 'red', 'error'); } } - const css = o => Object.entries(o).map(([k,v]) => `${k}:${v}`).join(';'); + const css = o => Object.entries(o).map(([k, v]) => `${k}:${v}`).join(';'); + + const loadScript = (src) => new Promise((resolve, reject) => { + const s = document.createElement('script'); + s.src = src; + s.async = true; + s.onload = () => resolve(); + s.onerror = (e) => reject(e); + document.head.appendChild(s); + }); + + const defaultElementFiles = [ + 'text.js', + 'image.js', + 'button.js', + 'table-2xn.js', + 'image-text.js', + 'divider.js', + 'spacer.js', + 'hero.js', + 'footer.js', + ]; + + async function loadElementFiles() { + const base = B.API_KERNEL_URL || '/api.php'; + const sep = base.includes('?') ? '&' : '?'; + const url = `${base}${sep}action=blocks_custom.list`; + try { + const res = await fetch(url, { credentials: 'include' }); + const data = await res.json(); + if (data && data.ok && Array.isArray(data.files) && data.files.length) { + return data.files; + } + } catch {} + return defaultElementFiles; + } function register(editor) { log('EXECUTION', `Starte Block-Registrierung für ${TARGET_CAT_ID}.`, '#DAA520'); - const bm = editor.BlockManager; + const basePath = (B.BASE_PATH_BRIDGE || '/assets/js/bridge/') + 'blocks-custom/elements/'; - // TEXT 2 - addOnce(bm, 'cust-text', { id:'cust-text', label:'📝 Text', - content:{ - type:'text', - tagName:'p', - content:'Dies ist ein Absatz. Doppelklick zum Bearbeiten.', - style:{ - 'font-family':'Arial,sans-serif', - 'font-size':'14px', - 'line-height':'1.5', - color:'#0f172a', - margin:'0 0 12px' + loadElementFiles().then(async (files) => { + const unique = Array.from(new Set(files.filter(Boolean))); + for (const file of unique) { + await loadScript(basePath + file).catch(() => { + log('LOAD ERROR', `Element-Datei konnte nicht geladen werden: ${file}`, 'red', 'error'); + }); } - } }); - // IMAGE - addOnce(bm, 'cust-image', { id:'cust-image', label:'🖼️ Bild', - content:`
- Bild
` }); + const fns = Array.isArray(window.BridgeBlocksCustomElements) ? window.BridgeBlocksCustomElements : []; + fns.forEach((fn) => { + try { + fn({ editor, bm, addOnce, css, category: TARGET_CAT_ID, log }); + } catch (e) { + log('ELEMENT ERROR', e?.message || String(e), 'red', 'error'); + } + }); - // BUTTON - addOnce(bm, 'cust-button', { id:'cust-button', label:'🔘 Button', - content:`
- Call To Action
` }); - - // TABLE - const hasBridgeTable = !!(editor && editor.DomComponents && editor.DomComponents.getType && editor.DomComponents.getType('bridge-table')); - const tableType = hasBridgeTable ? 'bridge-table' : 'default'; - addOnce(bm, 'cust-table', { id:'cust-table', label:'🧩 Tabelle (2xN)', - content:{ - type: tableType, - tagName:'table', - attributes:{ - role:'presentation', - width:'100%', - cellpadding:'0', - cellspacing:'0', - 'data-bridge-table':'1', - 'data-bridge-rows':'3', - 'data-bridge-cols':'2' - }, - style:{ - 'font-family':'Arial,sans-serif', - 'border-collapse':'collapse', - 'width':'100%', - 'margin-bottom':'16px' - }, - components: [ - { - tagName: 'tbody', - components: [ - { - tagName: 'tr', - components: [ - { tagName: 'th', content: 'Spalte A', style: {'text-align':'left','padding':'8px','border':'1px solid #e2e8f0','background-color':'#f8fafc','font-size':'13px'} }, - { tagName: 'th', content: 'Spalte B', style: {'text-align':'left','padding':'8px','border':'1px solid #e2e8f0','background-color':'#f8fafc','font-size':'13px'} }, - ], - }, - { - tagName: 'tr', - components: [ - { tagName: 'td', content: 'Zeile 1', style: {'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'} }, - { tagName: 'td', content: '...', style: {'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'} }, - ], - }, - { - tagName: 'tr', - components: [ - { tagName: 'td', content: 'Zeile 2', style: {'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'} }, - { tagName: 'td', content: '...', style: {'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'} }, - ], - }, - ], - }, - ] - } }); - - // DIVIDER - addOnce(bm, 'cust-divider',{ id:'cust-divider',label:'⎯ Divider', - content:`
` }); - - // SPACER - addOnce(bm, 'cust-spacer', { id:'cust-spacer', label:'↕ Spacer', - content:`
` }); - - // MEDIA LEFT - addOnce(bm, 'cust-media-left', { id:'cust-media-left', label:'🖼️◀ Text', - content:{ - type:'default', - tagName:'table', - attributes:{ - role:'presentation', - width:'100%', - cellpadding:'0', - cellspacing:'0' - }, - style:{ - 'font-family':'Arial,sans-serif', - 'border-collapse':'collapse', - 'margin-bottom':'16px' - }, - components:` - - - Bild - - -

Text …

- - ` - } }); - - // HERO - addOnce(bm, 'cust-hero', { id:'cust-hero', label:'🌄 Hero', - content:`
- Hero -

Titel des Newsletters

-

Kurzer Untertitel oder Einleitung.

-
` }); - - // FOOTER - addOnce(bm, 'cust-footer', { id:'cust-footer', label:'⚓ Footer', - content:`
-

Dein Unternehmen GmbH • Musterstraße 1 • 12345 Berlin

-

Abmelden · - Impressum · - Datenschutz

-
` }); - - log('SUCCESS', `Registrierung abgeschlossen. ${ALL_CUSTOM_BLOCK_IDS.length} Blöcke erstellt.`, '#008000', 'info'); + log('SUCCESS', `Registrierung abgeschlossen. ${ALL_CUSTOM_BLOCK_IDS.length} Blöcke erstellt.`, '#008000', 'info'); + }); } - // 🛑 KRITISCHE EXPORT-KORREKTUR: Exportiere 'register', um den Fehler in bridge-core.js zu beheben window.BridgeBlocksCustom = { IDS: ALL_CUSTOM_BLOCK_IDS, - register: register // <--- NEU: Exportiert die Register-Funktion + register: register }; - // Registriere das Modul als GrapesJS Plugin if (B && B.registerGrapesJSPlugin && typeof register === 'function') { B.registerGrapesJSPlugin('bridge-blocks-custom', register); log('PLUGIN REGISTER', `'bridge-blocks-custom' erfolgreich zur Bridge Plugin Registry hinzugefügt.`, '#008000'); } else { log('CRITICAL ERROR', `BridgeParts oder registerGrapesJSPlugin fehlt! Plugin-Registrierung gescheitert.`, 'red', 'error'); } - })(); diff --git a/public/assets/js/bridge/blocks-custom/elements/button.js b/public/assets/js/bridge/blocks-custom/elements/button.js new file mode 100644 index 0000000..1fc62a3 --- /dev/null +++ b/public/assets/js/bridge/blocks-custom/elements/button.js @@ -0,0 +1,10 @@ +(function(){ + window.BridgeBlocksCustomElements = window.BridgeBlocksCustomElements || []; + window.BridgeBlocksCustomElements.push(function(ctx){ + const { bm, addOnce, css } = ctx; + addOnce(bm, 'cust-button', { id:'cust-button', label:'🔘 Button', + content:`
+ Call To Action
` + }); + }); +})(); diff --git a/public/assets/js/bridge/blocks-custom/elements/divider.js b/public/assets/js/bridge/blocks-custom/elements/divider.js new file mode 100644 index 0000000..ae2437b --- /dev/null +++ b/public/assets/js/bridge/blocks-custom/elements/divider.js @@ -0,0 +1,9 @@ +(function(){ + window.BridgeBlocksCustomElements = window.BridgeBlocksCustomElements || []; + window.BridgeBlocksCustomElements.push(function(ctx){ + const { bm, addOnce, css } = ctx; + addOnce(bm, 'cust-divider',{ id:'cust-divider',label:'⎯ Divider', + content:`
` + }); + }); +})(); diff --git a/public/assets/js/bridge/blocks-custom/elements/footer.js b/public/assets/js/bridge/blocks-custom/elements/footer.js new file mode 100644 index 0000000..876c292 --- /dev/null +++ b/public/assets/js/bridge/blocks-custom/elements/footer.js @@ -0,0 +1,14 @@ +(function(){ + window.BridgeBlocksCustomElements = window.BridgeBlocksCustomElements || []; + window.BridgeBlocksCustomElements.push(function(ctx){ + const { bm, addOnce, css } = ctx; + addOnce(bm, 'cust-footer', { id:'cust-footer', label:'⚓ Footer', + content:`
+

Dein Unternehmen GmbH • Musterstraße 1 • 12345 Berlin

+

Abmelden · + Impressum · + Datenschutz

+
` + }); + }); +})(); diff --git a/public/assets/js/bridge/blocks-custom/elements/hero.js b/public/assets/js/bridge/blocks-custom/elements/hero.js new file mode 100644 index 0000000..7ddef15 --- /dev/null +++ b/public/assets/js/bridge/blocks-custom/elements/hero.js @@ -0,0 +1,13 @@ +(function(){ + window.BridgeBlocksCustomElements = window.BridgeBlocksCustomElements || []; + window.BridgeBlocksCustomElements.push(function(ctx){ + const { bm, addOnce, css } = ctx; + addOnce(bm, 'cust-hero', { id:'cust-hero', label:'🌄 Hero', + content:`
+ Hero +

Titel des Newsletters

+

Kurzer Untertitel oder Einleitung.

+
` + }); + }); +})(); diff --git a/public/assets/js/bridge/blocks-custom/elements/image-text.js b/public/assets/js/bridge/blocks-custom/elements/image-text.js new file mode 100644 index 0000000..5df1cca --- /dev/null +++ b/public/assets/js/bridge/blocks-custom/elements/image-text.js @@ -0,0 +1,32 @@ +(function(){ + window.BridgeBlocksCustomElements = window.BridgeBlocksCustomElements || []; + window.BridgeBlocksCustomElements.push(function(ctx){ + const { bm, addOnce, css } = ctx; + addOnce(bm, 'cust-media-left', { id:'cust-media-left', label:'🖼️◀ Text', + content:{ + type:'default', + tagName:'table', + attributes:{ + role:'presentation', + width:'100%', + cellpadding:'0', + cellspacing:'0' + }, + style:{ + 'font-family':'Arial,sans-serif', + 'border-collapse':'collapse', + 'margin-bottom':'16px' + }, + components:` + + + Bild + + +

Text …

+ + ` + } + }); + }); +})(); diff --git a/public/assets/js/bridge/blocks-custom/elements/image.js b/public/assets/js/bridge/blocks-custom/elements/image.js new file mode 100644 index 0000000..70a138e --- /dev/null +++ b/public/assets/js/bridge/blocks-custom/elements/image.js @@ -0,0 +1,10 @@ +(function(){ + window.BridgeBlocksCustomElements = window.BridgeBlocksCustomElements || []; + window.BridgeBlocksCustomElements.push(function(ctx){ + const { bm, addOnce, css } = ctx; + addOnce(bm, 'cust-image', { id:'cust-image', label:'🖼️ Bild', + content:`
+ Bild
` + }); + }); +})(); diff --git a/public/assets/js/bridge/blocks-custom/elements/spacer.js b/public/assets/js/bridge/blocks-custom/elements/spacer.js new file mode 100644 index 0000000..7e27ab8 --- /dev/null +++ b/public/assets/js/bridge/blocks-custom/elements/spacer.js @@ -0,0 +1,9 @@ +(function(){ + window.BridgeBlocksCustomElements = window.BridgeBlocksCustomElements || []; + window.BridgeBlocksCustomElements.push(function(ctx){ + const { bm, addOnce, css } = ctx; + addOnce(bm, 'cust-spacer', { id:'cust-spacer', label:'↕ Spacer', + content:`
` + }); + }); +})(); diff --git a/public/assets/js/bridge/blocks-custom/elements/table-2xn.js b/public/assets/js/bridge/blocks-custom/elements/table-2xn.js new file mode 100644 index 0000000..b89bb1c --- /dev/null +++ b/public/assets/js/bridge/blocks-custom/elements/table-2xn.js @@ -0,0 +1,57 @@ +(function(){ + window.BridgeBlocksCustomElements = window.BridgeBlocksCustomElements || []; + window.BridgeBlocksCustomElements.push(function(ctx){ + const { bm, addOnce } = ctx; + const hasBridgeTable = !!(ctx.editor && ctx.editor.DomComponents && ctx.editor.DomComponents.getType && ctx.editor.DomComponents.getType('bridge-table')); + const tableType = hasBridgeTable ? 'bridge-table' : 'default'; + addOnce(bm, 'cust-table', { id:'cust-table', label:'🧩 Tabelle (2xN)', + content:{ + type: tableType, + tagName:'table', + attributes:{ + role:'presentation', + width:'100%', + cellpadding:'0', + cellspacing:'0', + 'data-bridge-table':'1', + 'data-bridge-rows':'3', + 'data-bridge-cols':'2' + }, + style:{ + 'font-family':'Arial,sans-serif', + 'border-collapse':'collapse', + 'width':'100%', + 'margin-bottom':'16px' + }, + components: [ + { + tagName: 'tbody', + components: [ + { + tagName: 'tr', + components: [ + { tagName: 'th', content: 'Spalte A', style: {'text-align':'left','padding':'8px','border':'1px solid #e2e8f0','background-color':'#f8fafc','font-size':'13px'} }, + { tagName: 'th', content: 'Spalte B', style: {'text-align':'left','padding':'8px','border':'1px solid #e2e8f0','background-color':'#f8fafc','font-size':'13px'} }, + ], + }, + { + tagName: 'tr', + components: [ + { tagName: 'td', content: 'Zeile 1', style: {'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'} }, + { tagName: 'td', content: '...', style: {'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'} }, + ], + }, + { + tagName: 'tr', + components: [ + { tagName: 'td', content: 'Zeile 2', style: {'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'} }, + { tagName: 'td', content: '...', style: {'padding':'8px','border':'1px solid #e2e8f0','font-size':'13px'} }, + ], + }, + ], + }, + ] + } + }); + }); +})(); diff --git a/public/assets/js/bridge/blocks-custom/elements/text.js b/public/assets/js/bridge/blocks-custom/elements/text.js new file mode 100644 index 0000000..bb47fa8 --- /dev/null +++ b/public/assets/js/bridge/blocks-custom/elements/text.js @@ -0,0 +1,20 @@ +(function(){ + window.BridgeBlocksCustomElements = window.BridgeBlocksCustomElements || []; + window.BridgeBlocksCustomElements.push(function(ctx){ + const { bm, addOnce } = ctx; + addOnce(bm, 'cust-text', { id:'cust-text', label:'📝 Text', + content:{ + type:'text', + tagName:'p', + content:'Dies ist ein Absatz. Doppelklick zum Bearbeiten.', + style:{ + 'font-family':'Arial,sans-serif', + 'font-size':'14px', + 'line-height':'1.5', + color:'#0f172a', + margin:'0 0 12px' + } + } + }); + }); +})(); diff --git a/src/ApiKernel.php b/src/ApiKernel.php index f59a329..32a716f 100644 --- a/src/ApiKernel.php +++ b/src/ApiKernel.php @@ -3005,6 +3005,9 @@ class ApiKernel case 'account.fonts.list': $this->handleAccountFontsList(); break; + case 'blocks_custom.list': + $this->handleBlocksCustomList(); + break; case 'debug.logs.list': $this->handleDebugLogsList(); break; @@ -3090,6 +3093,24 @@ class ApiKernel } } + private function handleBlocksCustomList(): void + { + $this->requireAuth(); + $baseDir = realpath(__DIR__ . '/../public/assets/js/bridge/blocks-custom/elements'); + if (!$baseDir || !is_dir($baseDir)) { + $this->respond(['ok' => true, 'files' => []]); + return; + } + $files = glob($baseDir . '/*.js') ?: []; + $out = []; + foreach ($files as $file) { + $name = basename($file); + if ($name && $name[0] !== '.') $out[] = $name; + } + sort($out, SORT_NATURAL | SORT_FLAG_CASE); + $this->respond(['ok' => true, 'files' => $out]); + } + private function lookupTableName(string $key, string $default): string { $tables = $this->conf['tables'] ?? [];