diff --git a/public/assets/js/bridge/blocks-custom.js b/public/assets/js/bridge/blocks-custom.js
index 14b2355..7bae7b8 100644
--- a/public/assets/js/bridge/blocks-custom.js
+++ b/public/assets/js/bridge/blocks-custom.js
@@ -31,10 +31,10 @@
const TARGET_CAT_ID = 'bausteine';
const ALL_CUSTOM_BLOCK_IDS = [];
- function addOnce(bm, id, def) {
+ function addOnce(bm, id, def, category = TARGET_CAT_ID) {
// Hinzufügen des Blocks und Sicherstellen der Kategorie-Zuweisung
try {
- bm.add(id, { ...def, category: TARGET_CAT_ID });
+ bm.add(id, { ...def, category });
ALL_CUSTOM_BLOCK_IDS.push(id);
log('BLOCK ADD', `Block '${id}' erfolgreich hinzugefügt.`, '#B8860B');
} catch (e) {
@@ -44,312 +44,10 @@
const css = o => Object.entries(o).map(([k,v]) => `${k}:${v}`).join(';');
- const PLACEHOLDER_COMPONENT = 'placeholder-block';
- const placeholderSchemaStore = {
- promise: null,
- tables: [],
- status: null,
- statusPromise: null,
- };
-
- 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 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 Block-Registrierung für ${TARGET_CAT_ID}.`, '#DAA520');
const bm = editor.BlockManager;
- ensurePlaceholderComponent(editor);
-
- // --- Custom-Blöcke DEFINIEREN ---
-
- // PLACEHOLDER (Custom)
- const customPlaceholderBlock = {
- id: 'cust-placeholder-custom',
- label: '🔖 Placeholder (Text)',
- content: `{{UEBERSCHRIFT}}`
- };
- addOnce(bm, customPlaceholderBlock.id, customPlaceholderBlock);
-
- // Datenbank Placeholder – erst registrieren, wenn Tabellen verfügbar
- 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: `{{${placeholderLabel}}}`
- });
- })
- .catch(() => {
- log('PLACEHOLDER WARN', 'DB Placeholder Block ausgeblendet (Schemafehler).', '#b45309');
- });
// TEXT
addOnce(bm, 'cust-text', { id:'cust-text', label:'📝 Text',
diff --git a/public/assets/js/bridge/blocks-placeholder.js b/public/assets/js/bridge/blocks-placeholder.js
new file mode 100644
index 0000000..e195e9b
--- /dev/null
+++ b/public/assets/js/bridge/blocks-placeholder.js
@@ -0,0 +1,352 @@
+/* /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,
+ };
+
+ 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 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);
+
+ addOnce(bm, 'cust-placeholder-custom', {
+ id: 'cust-placeholder-custom',
+ label: '🔖 Placeholder (Text)',
+ content: `{{UEBERSCHRIFT}}`
+ });
+
+ 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: `{{${placeholderLabel}}}`
+ });
+ })
+ .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
+ };
+
+ 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');
+ }
+})();
diff --git a/public/assets/js/bridge/category-config.js b/public/assets/js/bridge/category-config.js
index 25f596c..0a2cfaf 100644
--- a/public/assets/js/bridge/category-config.js
+++ b/public/assets/js/bridge/category-config.js
@@ -17,6 +17,13 @@
files: ['blocks-standard.js'],
registration_mode: 'sync',
},
+ placeholders: {
+ ord: 5,
+ open: true,
+ label: '🔖 Placeholder',
+ files: ['blocks-placeholder.js'],
+ registration_mode: 'sync',
+ },
// --- 2. BAUSTEINE (bausteine) ---
bausteine: {
// ... (Bleibt unverändert, da es kein API-Async-Laden nutzt)