This commit is contained in:
2025-12-10 22:22:00 +01:00
parent d978b64d34
commit f9454605e1

View File

@@ -20,16 +20,44 @@
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 TARGET_CAT_ID = 'placeholders';
const PLACEHOLDER_COMPONENT = 'placeholder-block';
const ALL_PLACEHOLDER_BLOCK_IDS = [];
const INLINE_PLACEHOLDER_CLASS = 'bridge-placeholder-inline';
const placeholderSchemaStore = {
promise: null,
tables: [],
status: null,
statusPromise: null,
};
const placeholderSchemaStore = {
promise: null,
tables: [],
status: null,
statusPromise: null,
};
let inlineStyleInjected = false;
const ensureInlinePlaceholderStyles = () => {
if (inlineStyleInjected || typeof document === 'undefined') return;
inlineStyleInjected = true;
const style = document.createElement('style');
style.id = 'bridge-placeholder-inline-style';
style.textContent = `
.${INLINE_PLACEHOLDER_CLASS}{
display:inline-flex;
align-items:center;
gap:4px;
padding:2px 8px;
border:1px dashed #94a3b8;
border-radius:6px;
background:#f1f5f9;
font-family:monospace;
font-size:12px;
cursor:pointer;
user-select:none;
}
.${INLINE_PLACEHOLDER_CLASS}:hover{
background:#e2e8f0;
}
`;
document.head && document.head.appendChild(style);
};
const createEl = (tag, props = {}, children = []) => {
const el = document.createElement(tag);
@@ -53,7 +81,7 @@
return el;
};
const applyInputStyles = (el) => {
const applyInputStyles = (el) => {
if (!el) return el;
el.style.width = '100%';
el.style.boxSizing = 'border-box';
@@ -65,7 +93,7 @@
return el;
};
const syncSelectOptions = (selectEl, options, selected) => {
const syncSelectOptions = (selectEl, options, selected) => {
if (!selectEl) return;
while (selectEl.firstChild) {
selectEl.removeChild(selectEl.firstChild);
@@ -80,11 +108,20 @@
if (selected !== undefined && selected !== null) {
selectEl.value = selected;
}
};
};
const refreshPlaceholderComponent = (component) => {
if (!component) return;
if (typeof component.updatePlaceholderState === 'function') {
const safeRemoveComponent = (component) => {
if (!component || typeof component.remove !== 'function') return;
try {
component.remove();
} catch (err) {
log('PLACEHOLDER WARN', `Komponente konnte nicht entfernt werden: ${err && err.message ? err.message : err}`, '#b45309');
}
};
const refreshPlaceholderComponent = (component) => {
if (!component) return;
if (typeof component.updatePlaceholderState === 'function') {
try {
component.updatePlaceholderState();
} catch (err) {
@@ -111,12 +148,14 @@
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}"`,
];
const buildPlaceholderHTML = (payload) => {
const type = payload.type === 'database' ? 'database' : 'custom';
const attrs = [
`data-gjs-type="${PLACEHOLDER_COMPONENT}"`,
`data-placeholder-type="${type}"`,
`contenteditable="false"`,
`class="${INLINE_PLACEHOLDER_CLASS}"`,
];
if (type === 'database') {
attrs.push(`data-placeholder-table="${payload.table || ''}"`);
attrs.push(`data-placeholder-column="${payload.column || ''}"`);
@@ -181,16 +220,58 @@
return true;
};
const captureRteSelection = (rteInstance) => {
const doc = rteInstance && rteInstance.doc
? rteInstance.doc
: (rteInstance && rteInstance.el && rteInstance.el.ownerDocument) || document;
if (!doc || !doc.getSelection) return null;
const sel = doc.getSelection();
if (!sel || !sel.rangeCount) return null;
return {
doc,
range: sel.getRangeAt(0).cloneRange()
};
};
const restoreRteSelection = (snapshot) => {
if (!snapshot || !snapshot.doc || !snapshot.range) return false;
const sel = snapshot.doc.getSelection && snapshot.doc.getSelection();
if (!sel) return false;
try {
sel.removeAllRanges();
sel.addRange(snapshot.range);
return true;
} catch (err) {
log('PLACEHOLDER WARN', `Auswahl konnte nicht wiederhergestellt werden: ${err && err.message ? err.message : err}`, '#b45309');
return false;
}
};
const openPlaceholderModal = (editor, component, opts = {}) => {
if (!editor) return;
const modal = editor.Modal;
if (!modal) return;
ensureInlinePlaceholderStyles();
if (component && (!component.is || !component.is(PLACEHOLDER_COMPONENT))) {
log('PLACEHOLDER WARN', 'openPlaceholderModal wurde ohne gültige Placeholder-Komponente aufgerufen.', '#b45309');
return;
}
let didSave = false;
let didCancel = false;
const fireCancel = (reason) => {
if (didSave || didCancel) return;
didCancel = true;
if (typeof opts.onCancel === 'function') {
try {
opts.onCancel({ reason, component, modal });
} catch (err) {
log('PLACEHOLDER WARN', `onCancel Fehler: ${err && err.message ? err.message : err}`, '#b45309');
}
}
};
const attrs = component && component.getAttributes ? component.getAttributes() : (opts.initial || {});
const initialType = attrs['data-placeholder-type'] || 'custom';
const initialKey = attrs['data-placeholder-key'] || 'UEBERSCHRIFT';
@@ -348,6 +429,7 @@
cancelBtn.addEventListener('click', (e) => {
e.preventDefault();
fireCancel('cancel-button');
modal.close();
});
@@ -377,6 +459,7 @@
});
}
refreshPlaceholderComponent(component);
component.__bridgePlaceholderNew = false;
return true;
};
@@ -422,6 +505,7 @@
return;
}
}
didSave = true;
modal.close();
});
@@ -432,6 +516,20 @@
modal.setTitle('Placeholder konfigurieren');
modal.setContent(container);
if (typeof modal.onceClose === 'function') {
modal.onceClose(() => fireCancel('modal-close'));
} else if (modal.getModel && typeof modal.getModel === 'function') {
const mdl = modal.getModel();
if (mdl && typeof mdl.on === 'function') {
const handler = () => {
if (!mdl.get('open')) {
mdl.off && mdl.off('change:open', handler);
fireCancel('modal-close');
}
};
mdl.on && mdl.on('change:open', handler);
}
}
modal.open();
if (component && editor.getSelected && editor.getSelected() !== component) {
editor.select && editor.select(component);
@@ -452,8 +550,20 @@
log('PLACEHOLDER INFO', 'Bitte zuerst ein Text-Element auswählen.', '#888');
return;
}
const selectionSnapshot = captureRteSelection(rteInstance);
openPlaceholderModal(editor, null, {
onCancel: () => {
if (selectionSnapshot) {
restoreRteSelection(selectionSnapshot);
}
if (rteInstance && typeof rteInstance.focus === 'function') {
setTimeout(() => rteInstance.focus(), 0);
}
},
onSubmit: (payload) => {
if (selectionSnapshot) {
restoreRteSelection(selectionSnapshot);
}
const html = buildPlaceholderHTML(payload);
let inserted = false;
if (insertPlaceholderIntoSelection(rteInstance, html)) {
@@ -589,6 +699,8 @@
attributes: {
'data-placeholder-type': 'custom',
'data-placeholder-key': 'UEBERSCHRIFT',
'contenteditable': 'false',
'class': INLINE_PLACEHOLDER_CLASS,
},
traits: [
{
@@ -645,6 +757,7 @@
const ensurePlaceholderComponent = (editor) => {
const domc = editor.DomComponents;
if (domc.getType(PLACEHOLDER_COMPONENT)) return;
ensureInlinePlaceholderStyles();
const baseType = domc.getType('text') || domc.getType('default') || {};
const BaseModel = baseType.model || editor.DomComponents.Component;
@@ -662,10 +775,12 @@
updatePlaceholderState() {
const attrs = this.getAttributes();
const type = attrs['data-placeholder-type'] || 'custom';
if (type === 'database' && placeholderSchemaStore.tables.length === 0) {
if (!attrs['data-placeholder-type']) {
this.addAttributes({ 'data-placeholder-type': 'custom' });
}
if (attrs['contenteditable'] !== 'false') {
this.addAttributes({ 'contenteditable': 'false' });
}
this.updateTraitVisibility();
this.updateSchemaTraits();
this.updateLabel();
@@ -711,9 +826,12 @@
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 + ')' }; })
const table = tables.find(function (tbl) {
return (tbl.name || '').toLowerCase() === tableName;
});
const columns = table && Array.isArray(table.columns) ? table.columns : [];
const colOpts = columns.length
? columns.map(function (col) { return { id: col.name, label: col.name + (col.type ? ' (' + col.type + ')' : '') }; })
: [{ id: '', label: table ? 'Keine Felder' : 'Feld wählen', disabled: !table }];
setTraitOptions(columnTrait, colOpts);
}
@@ -745,7 +863,11 @@
render() {
BaseView.prototype.render.apply(this, arguments);
this.el.classList.add('placeholder-block');
this.el.style.display = 'inline-block';
this.el.classList.add(INLINE_PLACEHOLDER_CLASS);
this.el.setAttribute('contenteditable', 'false');
this.el.style.display = 'inline-flex';
this.el.style.alignItems = 'center';
this.el.style.gap = '4px';
this.el.style.padding = '2px 8px';
this.el.style.border = '1px dashed #94a3b8';
this.el.style.borderRadius = '6px';
@@ -848,7 +970,14 @@
if (window.__GJS_IS_PARSING) return;
if (cmp.__bridgePlaceholderPrompted) return;
cmp.__bridgePlaceholderPrompted = true;
setTimeout(() => openPlaceholderModal(editor, cmp), 50);
cmp.__bridgePlaceholderNew = true;
setTimeout(() => openPlaceholderModal(editor, cmp, {
onCancel: () => {
if (cmp.__bridgePlaceholderNew) {
safeRemoveComponent(cmp);
}
}
}), 50);
});
}
@@ -865,25 +994,40 @@
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>`
const ensureDbBlock = (tables) => {
const fallback = { table: 'tabelle', column: 'feld' };
let tableName = fallback.table;
let columnName = fallback.column;
if (Array.isArray(tables) && tables.length) {
const tableWithColumns = tables.find(tbl => Array.isArray(tbl.columns) && tbl.columns.length) || tables[0];
tableName = tableWithColumns && tableWithColumns.name ? tableWithColumns.name : fallback.table;
const firstColumn = tableWithColumns && Array.isArray(tableWithColumns.columns) && tableWithColumns.columns[0];
columnName = firstColumn && firstColumn.name ? firstColumn.name : fallback.column;
}
const payload = { type: 'database', table: tableName, column: columnName };
const content = buildPlaceholderHTML(payload);
const blockId = 'cust-placeholder-db';
const label = '🗄️ Placeholder (DB)';
const existing = typeof bm.get === 'function' ? bm.get(blockId) : null;
if (existing && typeof existing.set === 'function') {
existing.set('content', content);
existing.set('label', label);
} else {
addOnce(bm, blockId, {
id: blockId,
label,
content,
});
}
};
ensureDbBlock();
fetchPlaceholderSchema()
.then((tables) => {
ensureDbBlock(tables);
})
.catch(() => {
log('PLACEHOLDER WARN', 'DB Placeholder Block ausgeblendet (Schemafehler).', '#b45309');
log('PLACEHOLDER WARN', 'DB Placeholder Schema konnte nicht geladen werden Fallback bleibt aktiv.', '#b45309');
});
log('SUCCESS', `Placeholder-Registrierung abgeschlossen. ${ALL_PLACEHOLDER_BLOCK_IDS.length} Blöcke erstellt.`, '#008000', 'info', true);