Files
emailtemplate.it/public/assets/js/bridge/blocks-placeholder.js
2025-12-09 02:33:49 +01:00

820 lines
34 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-placeholder.js (LOG-KONTROLLIERT) */
(function () {
const PluginName = 'blocks-placeholder';
const B = window.BridgeParts || (window.BridgeParts = {});
if (B.LOG_CONFIG && B.LOG_CONFIG.PLUGINS) {
B.LOG_CONFIG.PLUGINS[PluginName] = false; // Lokaler Schalter für dieses Plugin
}
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') {
console.error(`%c[${PluginName} - ${type}] %c${message}`, `color:red; font-weight:bold;`, 'color:inherit;');
}
};
log('FILE CHECK', 'Placeholder-Datei-IIFE startet.');
if (window.__PLACEHOLDER_BLOCKS_LOADED) return;
window.__PLACEHOLDER_BLOCKS_LOADED = true;
const TARGET_CAT_ID = 'placeholders';
const PLACEHOLDER_COMPONENT = 'placeholder-block';
const ALL_PLACEHOLDER_BLOCK_IDS = [];
const placeholderSchemaStore = {
promise: null,
tables: [],
status: null,
statusPromise: null,
};
const createEl = (tag, props = {}, children = []) => {
const el = document.createElement(tag);
Object.entries(props || {}).forEach(([key, value]) => {
if (value === undefined || value === null) return;
if (key === 'className') {
el.className = value;
} else if (key === 'text') {
el.textContent = value;
} else if (key === 'html') {
el.innerHTML = value;
} else if (key in el) {
el[key] = value;
} else {
el.setAttribute(key, value);
}
});
(Array.isArray(children) ? children : [children]).forEach(child => {
if (child) el.appendChild(child);
});
return el;
};
const applyInputStyles = (el) => {
if (!el) return el;
el.style.width = '100%';
el.style.boxSizing = 'border-box';
el.style.padding = '6px 8px';
el.style.border = '1px solid #cbd5f5';
el.style.borderRadius = '4px';
el.style.fontSize = '13px';
el.style.fontFamily = 'inherit';
return el;
};
const syncSelectOptions = (selectEl, options, selected) => {
if (!selectEl) return;
while (selectEl.firstChild) {
selectEl.removeChild(selectEl.firstChild);
}
(options || []).forEach(opt => {
const optionEl = document.createElement('option');
optionEl.value = opt.id ?? opt.value ?? '';
optionEl.textContent = opt.label ?? opt.text ?? '';
if (opt.disabled) optionEl.disabled = true;
selectEl.appendChild(optionEl);
});
if (selected !== undefined && selected !== null) {
selectEl.value = selected;
}
};
const refreshPlaceholderComponent = (component) => {
if (!component) return;
if (typeof component.updatePlaceholderState === 'function') {
try {
component.updatePlaceholderState();
} catch (err) {
log('PLACEHOLDER WARN', `updatePlaceholderState Fehler: ${err && err.message ? err.message : err}`, '#b45309');
}
} else if (typeof component.trigger === 'function') {
component.trigger('change:attributes');
}
if (component.view && typeof component.view.render === 'function') {
component.view.render();
}
if (component.em && typeof component.em.trigger === 'function') {
component.em.trigger('component:update', component);
}
};
const buildPlaceholderLabel = (payload) => {
const type = payload.type === 'database' ? 'database' : 'custom';
if (type === 'database') {
const table = (payload.table || 'TABELLE').toUpperCase();
const column = (payload.column || 'FELD').toUpperCase();
return `${table}.${column}`;
}
return (payload.key || 'PLATZHALTER').toUpperCase();
};
const buildPlaceholderHTML = (payload) => {
const type = payload.type === 'database' ? 'database' : 'custom';
const attrs = [
`data-gjs-type="${PLACEHOLDER_COMPONENT}"`,
`data-placeholder-type="${type}"`,
];
if (type === 'database') {
attrs.push(`data-placeholder-table="${payload.table || ''}"`);
attrs.push(`data-placeholder-column="${payload.column || ''}"`);
} else {
attrs.push(`data-placeholder-key="${payload.key || ''}"`);
}
const label = buildPlaceholderLabel(payload);
return `<span ${attrs.join(' ')}>{{${label}}}</span>`;
};
const buildField = (labelText, control) => {
const controlId = `bridge-placeholder-field-${Math.random().toString(36).slice(2)}`;
control.id = controlId;
const label = createEl('label', { htmlFor: controlId, text: labelText });
label.style.display = 'block';
label.style.fontWeight = '600';
label.style.fontSize = '13px';
label.style.marginBottom = '4px';
const wrapper = createEl('div', { className: 'bridge-placeholder-field' }, [label, control]);
wrapper.style.marginBottom = '12px';
return wrapper;
};
const reparseTextComponent = (component, editor) => {
if (!component || typeof component.components !== 'function') return;
const viewEl = component.view && component.view.el;
if (!viewEl) return;
const html = viewEl.innerHTML;
try {
component.components(html);
if (component.em && typeof component.em.trigger === 'function') {
component.em.trigger('component:update', component);
} else if (editor && typeof editor.trigger === 'function') {
editor.trigger('component:update', component);
}
} catch (err) {
log('PLACEHOLDER ERROR', `Reparsing des Text-Elements fehlgeschlagen: ${err && err.message ? err.message : err}`, 'red', 'error');
}
};
const openPlaceholderModal = (editor, component, opts = {}) => {
if (!editor) return;
const modal = editor.Modal;
if (!modal) return;
if (component && (!component.is || !component.is(PLACEHOLDER_COMPONENT))) {
log('PLACEHOLDER WARN', 'openPlaceholderModal wurde ohne gültige Placeholder-Komponente aufgerufen.', '#b45309');
return;
}
const attrs = component && component.getAttributes ? component.getAttributes() : (opts.initial || {});
const initialType = attrs['data-placeholder-type'] || 'custom';
const initialKey = attrs['data-placeholder-key'] || 'UEBERSCHRIFT';
const initialTable = attrs['data-placeholder-table'] || '';
const initialColumn = attrs['data-placeholder-column'] || '';
const container = createEl('div', { className: 'bridge-placeholder-modal' });
container.style.padding = '8px 4px';
const form = createEl('form', { className: 'bridge-placeholder-form' });
form.style.display = 'flex';
form.style.flexDirection = 'column';
const typeSelect = applyInputStyles(createEl('select'));
[
{ id: 'custom', label: 'Placeholder (Text)' },
{ id: 'database', label: 'Placeholder (DB)' },
].forEach(opt => {
const optionEl = createEl('option', { value: opt.id, text: opt.label });
typeSelect.appendChild(optionEl);
});
typeSelect.value = initialType;
const keyInput = applyInputStyles(createEl('input', { type: 'text', value: initialKey }));
keyInput.placeholder = 'z.B. UEBERSCHRIFT';
const tableSelect = applyInputStyles(createEl('select'));
const columnSelect = applyInputStyles(createEl('select'));
tableSelect.disabled = true;
columnSelect.disabled = true;
const dbMessage = createEl('p', { className: 'bridge-placeholder-hint', text: '' });
dbMessage.style.fontSize = '12px';
dbMessage.style.margin = '4px 0 0';
dbMessage.style.color = '#475569';
const columnMessage = createEl('p', { className: 'bridge-placeholder-hint', text: '' });
columnMessage.style.fontSize = '12px';
columnMessage.style.margin = '4px 0 0';
columnMessage.style.color = '#475569';
const customWrap = createEl('div', { className: 'bridge-placeholder-section' }, [
buildField('Bezeichner', keyInput),
]);
const dbWrap = createEl('div', { className: 'bridge-placeholder-section' }, [
buildField('Tabelle', tableSelect),
dbMessage,
buildField('Feld', columnSelect),
columnMessage,
]);
const errorBox = createEl('p', { className: 'bridge-placeholder-error', text: '' });
errorBox.style.fontSize = '12px';
errorBox.style.color = '#b91c1c';
errorBox.style.margin = '4px 0 0';
form.appendChild(buildField('Typ', typeSelect));
form.appendChild(customWrap);
form.appendChild(dbWrap);
form.appendChild(errorBox);
const actions = createEl('div', { className: 'bridge-placeholder-actions' });
actions.style.marginTop = '16px';
actions.style.display = 'flex';
actions.style.justifyContent = 'flex-end';
actions.style.gap = '8px';
const cancelBtn = createEl('button', { type: 'button', text: 'Abbrechen' });
cancelBtn.style.padding = '6px 12px';
cancelBtn.style.border = '1px solid #cbd5f5';
cancelBtn.style.borderRadius = '4px';
cancelBtn.style.background = '#f8fafc';
cancelBtn.style.cursor = 'pointer';
const saveBtn = createEl('button', { type: 'submit', text: 'Speichern' });
saveBtn.style.padding = '6px 16px';
saveBtn.style.border = '1px solid #0ea5e9';
saveBtn.style.background = '#0ea5e9';
saveBtn.style.color = '#fff';
saveBtn.style.borderRadius = '4px';
saveBtn.style.cursor = 'pointer';
actions.appendChild(cancelBtn);
actions.appendChild(saveBtn);
form.appendChild(actions);
container.appendChild(form);
let tablesCache = placeholderSchemaStore.tables || [];
const toggleSections = () => {
const type = typeSelect.value || 'custom';
customWrap.style.display = type === 'custom' ? '' : 'none';
dbWrap.style.display = type === 'database' ? '' : 'none';
};
const populateColumns = (tableName, preferredColumn) => {
const table = (tablesCache || []).find(tbl => (tbl.name || '').toLowerCase() === (tableName || '').toLowerCase());
const columns = table && Array.isArray(table.columns) ? table.columns : [];
if (!columns.length) {
syncSelectOptions(columnSelect, [{ id: '', label: columns.length ? ' Feld wählen ' : 'Keine Felder verfügbar' }], '');
columnSelect.disabled = true;
columnMessage.textContent = table ? 'Diese Tabelle enthält keine Felder.' : 'Bitte zuerst eine Tabelle wählen.';
return;
}
const opts = columns.map(col => ({
id: col.name,
label: col.name + (col.type ? ` (${col.type})` : ''),
}));
syncSelectOptions(columnSelect, opts, preferredColumn || initialColumn || opts[0].id);
columnSelect.disabled = false;
columnMessage.textContent = '';
};
const populateTables = (tables) => {
tablesCache = Array.isArray(tables) ? tables : [];
if (!tablesCache.length) {
syncSelectOptions(tableSelect, [{ id: '', label: 'Keine Tabellen gefunden' }], '');
tableSelect.disabled = true;
columnSelect.disabled = true;
dbMessage.textContent = 'Kein Tabellen-Schema verfügbar. Bitte Bridge-Setup prüfen.';
columnMessage.textContent = '';
return;
}
const opts = tablesCache.map(tbl => ({ id: tbl.name, label: tbl.name }));
syncSelectOptions(tableSelect, opts, initialTable || opts[0].id);
tableSelect.disabled = false;
dbMessage.textContent = '';
populateColumns(tableSelect.value, initialColumn);
};
const loadTables = () => {
if (placeholderSchemaStore.tables && placeholderSchemaStore.tables.length) {
populateTables(placeholderSchemaStore.tables);
return;
}
dbMessage.textContent = 'Tabellen werden geladen …';
fetchPlaceholderSchema()
.then(populateTables)
.catch(() => {
populateTables([]);
});
};
tableSelect.addEventListener('change', () => {
populateColumns(tableSelect.value);
});
typeSelect.addEventListener('change', () => {
toggleSections();
if (typeSelect.value === 'database' && (!tablesCache || !tablesCache.length)) {
loadTables();
}
});
cancelBtn.addEventListener('click', (e) => {
e.preventDefault();
modal.close();
});
const applyPayload = (payload) => {
if (typeof opts.onSubmit === 'function') {
const res = opts.onSubmit(payload, { component, modal });
return res !== false;
}
if (!component || typeof component.addAttributes !== 'function') {
log('PLACEHOLDER INFO', 'Keine Ziel-Komponente gefunden Placeholder-Daten werden verworfen.', '#888');
return true;
}
if (payload.type === 'custom') {
component.addAttributes({
'data-placeholder-type': 'custom',
'data-placeholder-key': payload.key,
'data-placeholder-table': '',
'data-placeholder-column': '',
});
} else {
component.addAttributes({
'data-placeholder-type': 'database',
'data-placeholder-key': '',
'data-placeholder-table': payload.table,
'data-placeholder-column': payload.column,
});
}
refreshPlaceholderComponent(component);
return true;
};
form.addEventListener('submit', (e) => {
e.preventDefault();
errorBox.textContent = '';
const type = typeSelect.value || 'custom';
if (type === 'custom') {
const key = (keyInput.value || '').trim();
if (!key) {
errorBox.textContent = 'Bitte einen Bezeichner eingeben.';
keyInput.focus();
return;
}
const payload = {
type: 'custom',
key,
table: '',
column: '',
};
if (!applyPayload(payload)) {
return;
}
} else {
const table = tableSelect.value || '';
const column = columnSelect.value || '';
if (!table || !column) {
errorBox.textContent = 'Bitte Tabelle und Feld auswählen.';
if (!table) {
tableSelect.focus();
} else {
columnSelect.focus();
}
return;
}
const payload = {
type: 'database',
key: '',
table,
column,
};
if (!applyPayload(payload)) {
return;
}
}
modal.close();
});
toggleSections();
if (typeSelect.value === 'database') {
loadTables();
}
modal.setTitle('Placeholder konfigurieren');
modal.setContent(container);
modal.open();
if (component && editor.getSelected && editor.getSelected() !== component) {
editor.select && editor.select(component);
}
};
const ensureRtePlaceholderButton = (editor) => {
const rte = editor && editor.RichTextEditor;
if (!rte || rte.__bridgePlaceholderButton) return;
rte.__bridgePlaceholderButton = true;
rte.add('bridge-placeholder', {
icon: '<i class="fa fa-bookmark"></i>',
attributes: { title: 'Placeholder einfügen' },
result: (rteInstance) => {
const target = editor && editor.getSelected && editor.getSelected();
if (!target || !target.is || !target.is('text')) {
log('PLACEHOLDER INFO', 'Bitte zuerst ein Text-Element auswählen.', '#888');
return;
}
openPlaceholderModal(editor, null, {
onSubmit: (payload) => {
const html = buildPlaceholderHTML(payload);
if (rteInstance && typeof rteInstance.insertHTML === 'function') {
rteInstance.insertHTML(html);
} else if (typeof document !== 'undefined' && document.execCommand) {
document.execCommand('insertHTML', false, html);
} else {
log('PLACEHOLDER ERROR', 'Placeholder konnte nicht eingefügt werden (kein RTE).', 'red', 'error');
return false;
}
if (rteInstance && typeof rteInstance.focus === 'function') {
rteInstance.focus();
}
setTimeout(() => reparseTextComponent(target, editor), 0);
return true;
}
});
},
});
log('RTE', 'Placeholder-Button im RichTextEditor registriert.', '#DAA520');
};
function addOnce(bm, id, def, category = TARGET_CAT_ID) {
try {
bm.add(id, { ...def, category });
ALL_PLACEHOLDER_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 ensureBridgeAvailability = () => {
if (placeholderSchemaStore.status !== null) {
return Promise.resolve(placeholderSchemaStore.status);
}
if (placeholderSchemaStore.statusPromise) {
return placeholderSchemaStore.statusPromise;
}
const base = B.API_KERNEL_URL || '/api.php';
const sep = base.includes('?') ? '&' : '?';
const url = `${base}${sep}action=placeholders.status`;
placeholderSchemaStore.statusPromise = fetch(url, { credentials: 'include' })
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => {
const available = !!(data && (data.available || (data.settings && data.settings.available)));
placeholderSchemaStore.status = available;
placeholderSchemaStore.statusPromise = null;
if (!available) {
log('PLACEHOLDER INFO', 'Bridge-Placeholders nicht konfiguriert DB-Funktionen deaktiviert.', '#64748b');
}
return available;
})
.catch(err => {
placeholderSchemaStore.status = false;
placeholderSchemaStore.statusPromise = null;
log('PLACEHOLDER WARN', `Bridge-Status konnte nicht geprüft werden: ${err && err.message ? err.message : err}`, '#b45309');
return false;
});
return placeholderSchemaStore.statusPromise;
};
const fetchPlaceholderSchema = () => {
if (placeholderSchemaStore.promise) return placeholderSchemaStore.promise;
placeholderSchemaStore.promise = ensureBridgeAvailability().then(isAvailable => {
if (!isAvailable) throw new Error('Bridge not available');
const base = B.API_KERNEL_URL || '/api.php';
const sep = base.includes('?') ? '&' : '?';
const url = `${base}${sep}action=placeholders.schema`;
return fetch(url, { credentials: 'include' })
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => {
const tbls = data && Array.isArray(data.tables) ? data.tables : [];
placeholderSchemaStore.tables = tbls;
return placeholderSchemaStore.tables;
});
}).catch(err => {
placeholderSchemaStore.tables = [];
placeholderSchemaStore.promise = null;
const msg = err && err.message ? err.message : err;
if (msg === 'Bridge not available') {
log('PLACEHOLDER INFO', 'Schema-Abfrage übersprungen (keine Bridge verfügbar).', '#64748b');
} else {
log('PLACEHOLDER ERROR', `Schema konnte nicht geladen werden: ${msg}`, '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;
const baseType = domc.getType('text') || domc.getType('default') || {};
const BaseModel = baseType.model || editor.DomComponents.Component;
const BaseView = baseType.view || editor.DomComponents.View;
const placeholderDefaults = {};
if (BaseModel.prototype && BaseModel.prototype.defaults) {
for (const key in BaseModel.prototype.defaults) {
placeholderDefaults[key] = BaseModel.prototype.defaults[key];
}
}
placeholderDefaults.name = 'Placeholder';
placeholderDefaults.tagName = 'span';
placeholderDefaults.droppable = false;
placeholderDefaults.attributes = {
'data-placeholder-type': 'custom',
'data-placeholder-key': 'UEBERSCHRIFT',
};
placeholderDefaults.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,
},
];
const baseToolbar = Array.isArray(placeholderDefaults.toolbar) ? placeholderDefaults.toolbar.slice() : [];
baseToolbar.push({
attributes: { class: 'fa fa-edit', title: 'Placeholder bearbeiten' },
command: 'bridge-placeholder:edit',
});
placeholderDefaults.toolbar = baseToolbar;
const PlaceholderModel = BaseModel.extend({
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 && tableTrait.view && tableTrait.view.el) {
tableTrait.view.el.style.display = isDb ? '' : 'none';
}
if (columnTrait && columnTrait.view && columnTrait.view.el) {
columnTrait.view.el.style.display = isDb ? '' : 'none';
}
if (keyTrait && keyTrait.view && 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(function (tbl) { return { 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(function (tbl) { return tbl.name.toLowerCase() === tableName; });
const colOpts = table
? table.columns.map(function (col) { return { 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();
const onlyText = comps.length === 1 && comps.at(0).is('textnode');
if (onlyText) {
comps.at(0).set('content', text);
} else {
comps.reset([{ type: 'textnode', content: text }]);
}
}
});
PlaceholderModel.prototype.defaults = placeholderDefaults;
PlaceholderModel.isComponent = function (el) {
if (el && el.hasAttribute && el.hasAttribute('data-placeholder-type')) {
return { type: PLACEHOLDER_COMPONENT };
}
return false;
};
const PlaceholderView = BaseView.extend({
render() {
BaseView.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;
},
});
domc.addType(PLACEHOLDER_COMPONENT, {
model: PlaceholderModel,
view: PlaceholderView,
});
};
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 Placeholder-Registrierung für ${TARGET_CAT_ID}.`, '#DAA520');
const bm = editor.BlockManager;
ensurePlaceholderComponent(editor);
if (!editor.Commands.get('bridge-placeholder:edit')) {
editor.Commands.add('bridge-placeholder:edit', {
run(ed, sender, opts = {}) {
const component = opts.component || ed.getSelected();
if (component && component.is && component.is(PLACEHOLDER_COMPONENT)) {
openPlaceholderModal(ed, component);
} else {
log('PLACEHOLDER INFO', 'Kein Placeholder ausgewählt.', '#888');
}
},
});
}
if (!editor.__bridgePlaceholderEventsBound) {
editor.__bridgePlaceholderEventsBound = true;
editor.on('component:dblclick', (cmp) => {
if (cmp && cmp.is && cmp.is(PLACEHOLDER_COMPONENT)) {
openPlaceholderModal(editor, cmp);
return false;
}
return undefined;
});
editor.on('component:add', (cmp) => {
if (!cmp || !cmp.is || !cmp.is(PLACEHOLDER_COMPONENT)) return;
if (window.__GJS_IS_PARSING) return;
if (cmp.__bridgePlaceholderPrompted) return;
cmp.__bridgePlaceholderPrompted = true;
setTimeout(() => openPlaceholderModal(editor, cmp), 50);
});
}
const bindRteButton = () => ensureRtePlaceholderButton(editor);
if (editor.RichTextEditor) {
bindRteButton();
} else if (typeof editor.on === 'function') {
editor.on('load', bindRteButton, { once: true });
}
addOnce(bm, 'cust-placeholder-custom', {
id: 'cust-placeholder-custom',
label: '🔖 Placeholder (Text)',
content: `<span data-gjs-type="${PLACEHOLDER_COMPONENT}" data-placeholder-type="custom" data-placeholder-key="UEBERSCHRIFT">{{UEBERSCHRIFT}}</span>`
});
fetchPlaceholderSchema()
.then(tables => {
if (!tables || !tables.length) {
log('PLACEHOLDER INFO', 'Keine Tabellen DB Placeholder Block wird nicht angezeigt.', '#888');
return;
}
const firstTable = tables[0] || {};
const tableName = firstTable.name || 'tabelle';
const columns = Array.isArray(firstTable.columns) ? firstTable.columns : [];
const firstColumn = columns.length ? columns[0].name : 'feld';
const placeholderLabel = (tableName + '.' + firstColumn).toUpperCase();
addOnce(bm, 'cust-placeholder-db', {
id: 'cust-placeholder-db',
label: '🗄️ Placeholder (DB)',
content: `<span data-gjs-type="${PLACEHOLDER_COMPONENT}" data-placeholder-type="database" data-placeholder-table="${tableName}" data-placeholder-column="${firstColumn}">{{${placeholderLabel}}}</span>`
});
})
.catch(() => {
log('PLACEHOLDER WARN', 'DB Placeholder Block ausgeblendet (Schemafehler).', '#b45309');
});
log('SUCCESS', `Placeholder-Registrierung abgeschlossen. ${ALL_PLACEHOLDER_BLOCK_IDS.length} Blöcke erstellt.`, '#008000', 'info', true);
}
window.BridgeBlocksPlaceholder = {
IDS: ALL_PLACEHOLDER_BLOCK_IDS,
register,
openModal: openPlaceholderModal
};
if (B && B.registerGrapesJSPlugin && typeof register === 'function') {
B.registerGrapesJSPlugin('bridge-blocks-placeholder', register);
log('PLUGIN REGISTER', `'bridge-blocks-placeholder' erfolgreich registriert.`, '#008000');
} else {
log('CRITICAL ERROR', `BridgeParts oder registerGrapesJSPlugin fehlt! Placeholder Plugin-Registrierung gescheitert.`, 'red', 'error');
}
})();