Files
emailtemplate.it/public/assets/js/bridge/blocks-custom.js
2025-12-08 00:19:58 +01:00

352 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* /assets/js/bridge/blocks-custom.js (FINAL & LOG-KONTROLLIERT) */
(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
}
// 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;
const TARGET_CAT_ID = 'bausteine';
const ALL_CUSTOM_BLOCK_IDS = [];
function addOnce(bm, id, def) {
// Hinzufügen des Blocks und Sicherstellen der Kategorie-Zuweisung
try {
bm.add(id, { ...def, category: TARGET_CAT_ID });
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');
}
}
const css = o => Object.entries(o).map(([k,v]) => `${k}:${v}`).join(';');
const PLACEHOLDER_COMPONENT = 'placeholder-block';
const placeholderSchemaStore = {
promise: null,
tables: [],
};
const fetchPlaceholderSchema = () => {
if (placeholderSchemaStore.promise) return placeholderSchemaStore.promise;
const base = B.API_KERNEL_URL || '/api.php';
const sep = base.includes('?') ? '&' : '?';
const url = `${base}${sep}action=placeholders.schema`;
placeholderSchemaStore.promise = fetch(url, { credentials: 'include' })
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => {
placeholderSchemaStore.tables = Array.isArray(data?.tables) ? data.tables : [];
return placeholderSchemaStore.tables;
})
.catch(err => {
placeholderSchemaStore.tables = [];
placeholderSchemaStore.promise = null;
log('PLACEHOLDER ERROR', `Schema konnte nicht geladen werden: ${err.message || err}`, 'red', 'error');
throw err;
});
return placeholderSchemaStore.promise;
};
const getTraitByName = (model, name) => {
if (typeof model.getTrait === 'function') return model.getTrait(name);
const traits = model.get('traits');
if (!traits) return null;
if (typeof traits.where === 'function') {
const found = traits.where({ name });
return found && found[0];
}
if (Array.isArray(traits.models)) {
return traits.models.find(t => t.get && t.get('name') === name) || null;
}
return null;
};
const ensurePlaceholderComponent = (editor) => {
const domc = editor.DomComponents;
if (domc.getType(PLACEHOLDER_COMPONENT)) return;
domc.addType(PLACEHOLDER_COMPONENT, {
extend: 'text',
model: {
defaults: {
name: 'Placeholder',
tagName: 'span',
droppable: false,
attributes: {
'data-placeholder-type': 'custom',
'data-placeholder-key': 'UEBERSCHRIFT',
},
traits: [
{
type: 'select',
name: 'data-placeholder-type',
label: 'Typ',
options: [
{ id: 'custom', label: 'Allgemein' },
{ id: 'database', label: 'Datenbank' },
],
changeProp: true,
},
{
type: 'text',
name: 'data-placeholder-key',
label: 'Bezeichner',
placeholder: 'UEBERSCHRIFT',
changeProp: true,
},
{
type: 'select',
name: 'data-placeholder-table',
label: 'Tabelle',
options: [],
changeProp: true,
},
{
type: 'select',
name: 'data-placeholder-column',
label: 'Feld',
options: [],
changeProp: true,
},
],
},
init() {
this.listenTo(this, 'change:attributes', this.updatePlaceholderState);
this.updatePlaceholderState();
fetchPlaceholderSchema()
.then(() => this.updateSchemaTraits())
.catch(() => this.updateSchemaTraits([]));
},
updatePlaceholderState() {
const attrs = this.getAttributes();
const type = attrs['data-placeholder-type'] || 'custom';
if (type === 'database' && placeholderSchemaStore.tables.length === 0) {
this.addAttributes({ 'data-placeholder-type': 'custom' });
}
this.updateTraitVisibility();
this.updateSchemaTraits();
this.updateLabel();
},
updateTraitVisibility() {
const attrs = this.getAttributes();
const type = attrs['data-placeholder-type'] || 'custom';
const isDb = type === 'database';
const tableTrait = getTraitByName(this, 'data-placeholder-table');
const columnTrait = getTraitByName(this, 'data-placeholder-column');
const keyTrait = getTraitByName(this, 'data-placeholder-key');
if (tableTrait?.view?.el) {
tableTrait.view.el.style.display = isDb ? '' : 'none';
}
if (columnTrait?.view?.el) {
columnTrait.view.el.style.display = isDb ? '' : 'none';
}
if (keyTrait?.view?.el) {
keyTrait.view.el.style.display = isDb ? 'none' : '';
}
},
updateSchemaTraits(tablesOverride) {
const tables = Array.isArray(tablesOverride) ? tablesOverride : placeholderSchemaStore.tables;
const tableTrait = getTraitByName(this, 'data-placeholder-table');
const columnTrait = getTraitByName(this, 'data-placeholder-column');
const loading = !tablesOverride && placeholderSchemaStore.promise && !tables.length;
if (tableTrait) {
let opts;
if (tables.length) {
opts = tables.map(tbl => ({ id: tbl.name, label: tbl.name }));
} else if (loading) {
opts = [{ id: '', label: 'Tabellen werden geladen…', disabled: true }];
} else {
opts = [{ id: '', label: 'Keine Tabellen verfügbar', disabled: true }];
}
setTraitOptions(tableTrait, opts);
}
if (columnTrait) {
const attrs = this.getAttributes();
const tableName = (attrs['data-placeholder-table'] || '').toLowerCase();
const table = tables.find(tbl => tbl.name.toLowerCase() === tableName);
const colOpts = table
? table.columns.map(col => ({ id: col.name, label: `${col.name} (${col.type})` }))
: [{ id: '', label: table ? 'Keine Felder' : 'Feld wählen', disabled: !table }];
setTraitOptions(columnTrait, colOpts);
}
},
updateLabel() {
const attrs = this.getAttributes();
const type = attrs['data-placeholder-type'] || 'custom';
let label;
if (type === 'database') {
const table = attrs['data-placeholder-table'] || 'TABELLE';
const column = attrs['data-placeholder-column'] || 'FELD';
label = `${table}.${column}`.toUpperCase();
} else {
label = (attrs['data-placeholder-key'] || 'PLATZHALTER').toUpperCase();
}
const text = `{{${label}}}`;
const comps = this.components();
if (comps.length === 1 && comps.at(0).is('textnode')) {
comps.at(0).set('content', text);
} else {
comps.reset([{ type: 'textnode', content: text }]);
}
},
}, {
isComponent(el) {
if (el && el.hasAttribute && el.hasAttribute('data-placeholder-type')) {
return { type: PLACEHOLDER_COMPONENT };
}
return false;
},
}),
view: editor.DomComponents.View.extend({
render() {
editor.DomComponents.View.prototype.render.apply(this, arguments);
this.el.classList.add('placeholder-block');
this.el.style.display = 'inline-block';
this.el.style.padding = '2px 8px';
this.el.style.border = '1px dashed #94a3b8';
this.el.style.borderRadius = '6px';
this.el.style.background = '#f1f5f9';
this.el.style.fontFamily = 'monospace';
this.el.style.fontSize = '12px';
return this;
},
}),
});
};
function setTraitOptions(trait, options) {
if (!trait) return;
trait.set('options', options);
if (trait.view && typeof trait.view.render === 'function') {
trait.view.render();
}
}
function register(editor) {
log('EXECUTION', `Starte Block-Registrierung für ${TARGET_CAT_ID}.`, '#DAA520');
const bm = editor.BlockManager;
ensurePlaceholderComponent(editor);
// --- Custom-Blöcke DEFINIEREN ---
// PLACEHOLDER
addOnce(bm, 'cust-placeholder', {
id: 'cust-placeholder',
label: '🔖 Placeholder',
content: `<span data-gjs-type="${PLACEHOLDER_COMPONENT}" data-placeholder-type="custom" data-placeholder-key="UEBERSCHRIFT">{{UEBERSCHRIFT}}</span>`
});
// TEXT
addOnce(bm, 'cust-text', { id:'cust-text', label:'📝 Text',
content:`<div style="${css({'font-family':'Arial,sans-serif','font-size':'14px','line-height':'1.5',color:'#0f172a',margin:'0 0 12px'})}">
<p style="${css({margin:'0 0 12px'})}">Dies ist ein Absatz. Doppelklick zum Bearbeiten.</p></div>` });
// IMAGE
addOnce(bm, 'cust-image', { id:'cust-image', label:'🖼️ Bild',
content:`<div style="${css({'text-align':'center',margin:'0 0 16px'})}">
<img src="https://placehold.co/600x300" alt="Bild" style="${css({width:'100%',height:'auto','max-width':'600px',border:'0',display:'inline-block'})}"></div>` });
// BUTTON
addOnce(bm, 'cust-button', { id:'cust-button', label:'🔘 Button',
content:`<div style="${css({'text-align':'center',margin:'0 0 16px'})}">
<a href="#" style="${css({display:'inline-block','background-color':'#0ea5e9',color:'#fff','text-decoration':'none',padding:'10px 18px','border-radius':'6px','font-family':'Arial,sans-serif','font-size':'14px'})}">Call To Action</a></div>` });
// DIVIDER
addOnce(bm, 'cust-divider',{ id:'cust-divider',label:'⎯ Divider',
content:`<hr style="${css({border:'0',height:'1px','background-color':'#e2e8f0',margin:'16px 0'})}">` });
// SPACER
addOnce(bm, 'cust-spacer', { id:'cust-spacer', label:'↕ Spacer',
content:`<div style="${css({height:'24px'})}"></div>` });
// 2 COL
addOnce(bm, 'cust-2col', { id:'cust-2col', label:'🧩 2 Spalten',
content:`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="${css({'font-family':'Arial,sans-serif','border-collapse':'collapse','margin-bottom':'16px'})}">
<tr><td width="50%" valign="top" style="${css({padding:'0 8px 0 0'})}">
<div style="${css({'font-size':'14px','line-height':'1.5',color:'#0f172a'})}"><p style="${css({margin:'0 0 12px'})}">Linke Spalte Inhalt hier.</p></div>
</td><td width="50%" valign="top" style="${css({padding:'0 0 0 8px'})}">
<div style="${css({'font-size':'14px','line-height':'1.5',color:'#0f172a'})}"><p style="${css({margin:'0 0 12px'})}">Rechte Spalte Inhalt hier.</p></div>
</td></tr></table>` });
// MEDIA LEFT
addOnce(bm, 'cust-media-left', { id:'cust-media-left', label:'🖼️◀ Text',
content:`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="${css({'font-family':'Arial,sans-serif','border-collapse':'collapse','margin-bottom':'16px'})}">
<tr><td width="40%" valign="top" style="${css({padding:'0 8px 0 0'})}">
<img src="https://placehold.co/400x260" alt="Bild" style="${css({width:'100%',height:'auto',border:'0',display:'block'})}">
</td><td width="60%" valign="top" style="${css({padding:'0 0 0 8px'})}">
<h3 style="${css({margin:'0 0 8px','font-size':'18px',color:'#0f172a'})}">Überschrift</h3>
<p style="${css({margin:'0 0 12px','font-size':'14px',color:'#0f172a','line-height':'1.5'})}">Beschreibungstext …</p>
<a href="#" style="${css({display:'inline-block','background-color':'#0ea5e9',color:'#fff','text-decoration':'none',padding:'8px 14px','border-radius':'6px','font-size':'14px'})}">Mehr erfahren</a>
</td></tr></table>` });
// HERO
addOnce(bm, 'cust-hero', { id:'cust-hero', label:'🌄 Hero',
content:`<div style="${css({'text-align':'center',margin:'0 0 16px',padding:'12px','background-color':'#eef2ff',color:'#1e3a8a','border':'1px solid #c7d2fe','border-radius':'8px'})}">
<img src="https://placehold.co/640x240" alt="Hero" style="${css({width:'100%',height:'auto','max-width':'640px',border:'0',display:'inline-block','border-radius':'6px'})}">
<h2 style="${css({'font-family':'Arial,sans-serif',margin:'12px 0 8px','font-size':'22px'})}">Titel des Newsletters</h2>
<p style="${css({'font-size':'14px',margin:'0 0 12px'})}">Kurzer Untertitel oder Einleitung.</p>
</div>` });
// FOOTER
addOnce(bm, 'cust-footer', { id:'cust-footer', label:'⚓ Footer',
content:`<div style="${css({'font-family':'Arial,sans-serif','font-size':'12px',color:'#475569','line-height':'1.5','border-top':'1px solid #e2e8f0',padding:'12px 0','text-align':'center'})}">
<p style="${css({margin:'0 0 6px'})}"><strong>Dein Unternehmen GmbH</strong> • Musterstraße 1 • 12345 Berlin</p>
<p style="${css({margin:'0'})}"><a href="#" style="${css({color:'#0ea5e9','text-decoration':'none'})}">Abmelden</a> ·
<a href="#" style="${css({color:'#0ea5e9','text-decoration':'none'})}">Impressum</a> ·
<a href="#" style="${css({color:'#0ea5e9','text-decoration':'none'})}">Datenschutz</a></p>
</div>` });
log('SUCCESS', `Registrierung abgeschlossen. ${ALL_CUSTOM_BLOCK_IDS.length} Blöcke erstellt.`, '#008000', 'info', true);
}
// 🛑 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
};
// 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');
}
})();