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:`
-

` });
+ 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:`` });
-
- // 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:`
-
-
-
- |
-
- Text …
- |
-
`
- } });
-
- // HERO
- addOnce(bm, 'cust-hero', { id:'cust-hero', label:'🌄 Hero',
- content:`
-

-
Titel des Newsletters
-
Kurzer Untertitel oder Einleitung.
-
` });
-
- // FOOTER
- addOnce(bm, 'cust-footer', { id:'cust-footer', label:'⚓ Footer',
- content:`` });
-
- 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:``
+ });
+ });
+})();
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:``
+ });
+ });
+})();
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:`
+

+
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:`
+
+
+
+ |
+
+ 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:`
+

`
+ });
+ });
+})();
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'] ?? [];