This commit is contained in:
2025-12-07 02:24:48 +01:00
parent 24c7c215ee
commit 37318e69fb
4 changed files with 315 additions and 6 deletions

View File

@@ -44,13 +44,226 @@
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 = [];
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;
const defaultType = domc.getType('default');
const defaultModel = defaultType ? defaultType.model : null;
const defaultView = defaultType ? defaultType.view : null;
domc.addType(PLACEHOLDER_COMPONENT, {
model: (defaultModel || editor.DomComponents.Component).extend({
defaults: {
...(defaultModel && defaultModel.prototype?.defaults ? defaultModel.prototype.defaults : {}),
name: 'Placeholder',
tagName: 'span',
selectable: true,
hoverable: true,
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() {
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');
if (tableTrait) {
const opts = tables.map(tbl => ({ id: tbl.name, label: tbl.name }));
tableTrait.set('options', opts);
if (tableTrait.view && tableTrait.view.render) tableTrait.view.render();
}
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})`,
}))
: [];
columnTrait.set('options', colOpts);
if (columnTrait.view && columnTrait.view.render) columnTrait.view.render();
}
},
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: (defaultView || editor.DomComponents.View).extend({
render() {
defaultView && defaultView.prototype.render
? defaultView.prototype.render.apply(this, arguments)
: 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 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'})}">