asdasd
This commit is contained in:
@@ -20,16 +20,44 @@
|
|||||||
if (window.__PLACEHOLDER_BLOCKS_LOADED) return;
|
if (window.__PLACEHOLDER_BLOCKS_LOADED) return;
|
||||||
window.__PLACEHOLDER_BLOCKS_LOADED = true;
|
window.__PLACEHOLDER_BLOCKS_LOADED = true;
|
||||||
|
|
||||||
const TARGET_CAT_ID = 'placeholders';
|
const TARGET_CAT_ID = 'placeholders';
|
||||||
const PLACEHOLDER_COMPONENT = 'placeholder-block';
|
const PLACEHOLDER_COMPONENT = 'placeholder-block';
|
||||||
const ALL_PLACEHOLDER_BLOCK_IDS = [];
|
const ALL_PLACEHOLDER_BLOCK_IDS = [];
|
||||||
|
const INLINE_PLACEHOLDER_CLASS = 'bridge-placeholder-inline';
|
||||||
|
|
||||||
const placeholderSchemaStore = {
|
const placeholderSchemaStore = {
|
||||||
promise: null,
|
promise: null,
|
||||||
tables: [],
|
tables: [],
|
||||||
status: null,
|
status: null,
|
||||||
statusPromise: 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 createEl = (tag, props = {}, children = []) => {
|
||||||
const el = document.createElement(tag);
|
const el = document.createElement(tag);
|
||||||
@@ -53,7 +81,7 @@
|
|||||||
return el;
|
return el;
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyInputStyles = (el) => {
|
const applyInputStyles = (el) => {
|
||||||
if (!el) return el;
|
if (!el) return el;
|
||||||
el.style.width = '100%';
|
el.style.width = '100%';
|
||||||
el.style.boxSizing = 'border-box';
|
el.style.boxSizing = 'border-box';
|
||||||
@@ -65,7 +93,7 @@
|
|||||||
return el;
|
return el;
|
||||||
};
|
};
|
||||||
|
|
||||||
const syncSelectOptions = (selectEl, options, selected) => {
|
const syncSelectOptions = (selectEl, options, selected) => {
|
||||||
if (!selectEl) return;
|
if (!selectEl) return;
|
||||||
while (selectEl.firstChild) {
|
while (selectEl.firstChild) {
|
||||||
selectEl.removeChild(selectEl.firstChild);
|
selectEl.removeChild(selectEl.firstChild);
|
||||||
@@ -80,9 +108,18 @@
|
|||||||
if (selected !== undefined && selected !== null) {
|
if (selected !== undefined && selected !== null) {
|
||||||
selectEl.value = selected;
|
selectEl.value = selected;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshPlaceholderComponent = (component) => {
|
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 (!component) return;
|
||||||
if (typeof component.updatePlaceholderState === 'function') {
|
if (typeof component.updatePlaceholderState === 'function') {
|
||||||
try {
|
try {
|
||||||
@@ -111,11 +148,13 @@
|
|||||||
return (payload.key || 'PLATZHALTER').toUpperCase();
|
return (payload.key || 'PLATZHALTER').toUpperCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildPlaceholderHTML = (payload) => {
|
const buildPlaceholderHTML = (payload) => {
|
||||||
const type = payload.type === 'database' ? 'database' : 'custom';
|
const type = payload.type === 'database' ? 'database' : 'custom';
|
||||||
const attrs = [
|
const attrs = [
|
||||||
`data-gjs-type="${PLACEHOLDER_COMPONENT}"`,
|
`data-gjs-type="${PLACEHOLDER_COMPONENT}"`,
|
||||||
`data-placeholder-type="${type}"`,
|
`data-placeholder-type="${type}"`,
|
||||||
|
`contenteditable="false"`,
|
||||||
|
`class="${INLINE_PLACEHOLDER_CLASS}"`,
|
||||||
];
|
];
|
||||||
if (type === 'database') {
|
if (type === 'database') {
|
||||||
attrs.push(`data-placeholder-table="${payload.table || ''}"`);
|
attrs.push(`data-placeholder-table="${payload.table || ''}"`);
|
||||||
@@ -181,16 +220,58 @@
|
|||||||
return true;
|
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 = {}) => {
|
const openPlaceholderModal = (editor, component, opts = {}) => {
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
const modal = editor.Modal;
|
const modal = editor.Modal;
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
|
ensureInlinePlaceholderStyles();
|
||||||
|
|
||||||
if (component && (!component.is || !component.is(PLACEHOLDER_COMPONENT))) {
|
if (component && (!component.is || !component.is(PLACEHOLDER_COMPONENT))) {
|
||||||
log('PLACEHOLDER WARN', 'openPlaceholderModal wurde ohne gültige Placeholder-Komponente aufgerufen.', '#b45309');
|
log('PLACEHOLDER WARN', 'openPlaceholderModal wurde ohne gültige Placeholder-Komponente aufgerufen.', '#b45309');
|
||||||
return;
|
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 attrs = component && component.getAttributes ? component.getAttributes() : (opts.initial || {});
|
||||||
const initialType = attrs['data-placeholder-type'] || 'custom';
|
const initialType = attrs['data-placeholder-type'] || 'custom';
|
||||||
const initialKey = attrs['data-placeholder-key'] || 'UEBERSCHRIFT';
|
const initialKey = attrs['data-placeholder-key'] || 'UEBERSCHRIFT';
|
||||||
@@ -348,6 +429,7 @@
|
|||||||
|
|
||||||
cancelBtn.addEventListener('click', (e) => {
|
cancelBtn.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
fireCancel('cancel-button');
|
||||||
modal.close();
|
modal.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -377,6 +459,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
refreshPlaceholderComponent(component);
|
refreshPlaceholderComponent(component);
|
||||||
|
component.__bridgePlaceholderNew = false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -422,6 +505,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
didSave = true;
|
||||||
modal.close();
|
modal.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -432,6 +516,20 @@
|
|||||||
|
|
||||||
modal.setTitle('Placeholder konfigurieren');
|
modal.setTitle('Placeholder konfigurieren');
|
||||||
modal.setContent(container);
|
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();
|
modal.open();
|
||||||
if (component && editor.getSelected && editor.getSelected() !== component) {
|
if (component && editor.getSelected && editor.getSelected() !== component) {
|
||||||
editor.select && editor.select(component);
|
editor.select && editor.select(component);
|
||||||
@@ -452,8 +550,20 @@
|
|||||||
log('PLACEHOLDER INFO', 'Bitte zuerst ein Text-Element auswählen.', '#888');
|
log('PLACEHOLDER INFO', 'Bitte zuerst ein Text-Element auswählen.', '#888');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const selectionSnapshot = captureRteSelection(rteInstance);
|
||||||
openPlaceholderModal(editor, null, {
|
openPlaceholderModal(editor, null, {
|
||||||
|
onCancel: () => {
|
||||||
|
if (selectionSnapshot) {
|
||||||
|
restoreRteSelection(selectionSnapshot);
|
||||||
|
}
|
||||||
|
if (rteInstance && typeof rteInstance.focus === 'function') {
|
||||||
|
setTimeout(() => rteInstance.focus(), 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
onSubmit: (payload) => {
|
onSubmit: (payload) => {
|
||||||
|
if (selectionSnapshot) {
|
||||||
|
restoreRteSelection(selectionSnapshot);
|
||||||
|
}
|
||||||
const html = buildPlaceholderHTML(payload);
|
const html = buildPlaceholderHTML(payload);
|
||||||
let inserted = false;
|
let inserted = false;
|
||||||
if (insertPlaceholderIntoSelection(rteInstance, html)) {
|
if (insertPlaceholderIntoSelection(rteInstance, html)) {
|
||||||
@@ -589,6 +699,8 @@
|
|||||||
attributes: {
|
attributes: {
|
||||||
'data-placeholder-type': 'custom',
|
'data-placeholder-type': 'custom',
|
||||||
'data-placeholder-key': 'UEBERSCHRIFT',
|
'data-placeholder-key': 'UEBERSCHRIFT',
|
||||||
|
'contenteditable': 'false',
|
||||||
|
'class': INLINE_PLACEHOLDER_CLASS,
|
||||||
},
|
},
|
||||||
traits: [
|
traits: [
|
||||||
{
|
{
|
||||||
@@ -645,6 +757,7 @@
|
|||||||
const ensurePlaceholderComponent = (editor) => {
|
const ensurePlaceholderComponent = (editor) => {
|
||||||
const domc = editor.DomComponents;
|
const domc = editor.DomComponents;
|
||||||
if (domc.getType(PLACEHOLDER_COMPONENT)) return;
|
if (domc.getType(PLACEHOLDER_COMPONENT)) return;
|
||||||
|
ensureInlinePlaceholderStyles();
|
||||||
|
|
||||||
const baseType = domc.getType('text') || domc.getType('default') || {};
|
const baseType = domc.getType('text') || domc.getType('default') || {};
|
||||||
const BaseModel = baseType.model || editor.DomComponents.Component;
|
const BaseModel = baseType.model || editor.DomComponents.Component;
|
||||||
@@ -662,10 +775,12 @@
|
|||||||
|
|
||||||
updatePlaceholderState() {
|
updatePlaceholderState() {
|
||||||
const attrs = this.getAttributes();
|
const attrs = this.getAttributes();
|
||||||
const type = attrs['data-placeholder-type'] || 'custom';
|
if (!attrs['data-placeholder-type']) {
|
||||||
if (type === 'database' && placeholderSchemaStore.tables.length === 0) {
|
|
||||||
this.addAttributes({ 'data-placeholder-type': 'custom' });
|
this.addAttributes({ 'data-placeholder-type': 'custom' });
|
||||||
}
|
}
|
||||||
|
if (attrs['contenteditable'] !== 'false') {
|
||||||
|
this.addAttributes({ 'contenteditable': 'false' });
|
||||||
|
}
|
||||||
this.updateTraitVisibility();
|
this.updateTraitVisibility();
|
||||||
this.updateSchemaTraits();
|
this.updateSchemaTraits();
|
||||||
this.updateLabel();
|
this.updateLabel();
|
||||||
@@ -711,9 +826,12 @@
|
|||||||
if (columnTrait) {
|
if (columnTrait) {
|
||||||
const attrs = this.getAttributes();
|
const attrs = this.getAttributes();
|
||||||
const tableName = (attrs['data-placeholder-table'] || '').toLowerCase();
|
const tableName = (attrs['data-placeholder-table'] || '').toLowerCase();
|
||||||
const table = tables.find(function (tbl) { return tbl.name.toLowerCase() === tableName; });
|
const table = tables.find(function (tbl) {
|
||||||
const colOpts = table
|
return (tbl.name || '').toLowerCase() === tableName;
|
||||||
? table.columns.map(function (col) { return { id: col.name, label: col.name + ' (' + col.type + ')' }; })
|
});
|
||||||
|
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 }];
|
: [{ id: '', label: table ? 'Keine Felder' : 'Feld wählen', disabled: !table }];
|
||||||
setTraitOptions(columnTrait, colOpts);
|
setTraitOptions(columnTrait, colOpts);
|
||||||
}
|
}
|
||||||
@@ -745,7 +863,11 @@
|
|||||||
render() {
|
render() {
|
||||||
BaseView.prototype.render.apply(this, arguments);
|
BaseView.prototype.render.apply(this, arguments);
|
||||||
this.el.classList.add('placeholder-block');
|
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.padding = '2px 8px';
|
||||||
this.el.style.border = '1px dashed #94a3b8';
|
this.el.style.border = '1px dashed #94a3b8';
|
||||||
this.el.style.borderRadius = '6px';
|
this.el.style.borderRadius = '6px';
|
||||||
@@ -848,7 +970,14 @@
|
|||||||
if (window.__GJS_IS_PARSING) return;
|
if (window.__GJS_IS_PARSING) return;
|
||||||
if (cmp.__bridgePlaceholderPrompted) return;
|
if (cmp.__bridgePlaceholderPrompted) return;
|
||||||
cmp.__bridgePlaceholderPrompted = true;
|
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>`
|
content: `<span data-gjs-type="${PLACEHOLDER_COMPONENT}" data-placeholder-type="custom" data-placeholder-key="UEBERSCHRIFT">{{UEBERSCHRIFT}}</span>`
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchPlaceholderSchema()
|
const ensureDbBlock = (tables) => {
|
||||||
.then(tables => {
|
const fallback = { table: 'tabelle', column: 'feld' };
|
||||||
if (!tables || !tables.length) {
|
let tableName = fallback.table;
|
||||||
log('PLACEHOLDER INFO', 'Keine Tabellen – DB Placeholder Block wird nicht angezeigt.', '#888');
|
let columnName = fallback.column;
|
||||||
return;
|
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 firstTable = tables[0] || {};
|
const payload = { type: 'database', table: tableName, column: columnName };
|
||||||
const tableName = firstTable.name || 'tabelle';
|
const content = buildPlaceholderHTML(payload);
|
||||||
const columns = Array.isArray(firstTable.columns) ? firstTable.columns : [];
|
const blockId = 'cust-placeholder-db';
|
||||||
const firstColumn = columns.length ? columns[0].name : 'feld';
|
const label = '🗄️ Placeholder (DB)';
|
||||||
const placeholderLabel = (tableName + '.' + firstColumn).toUpperCase();
|
const existing = typeof bm.get === 'function' ? bm.get(blockId) : null;
|
||||||
addOnce(bm, 'cust-placeholder-db', {
|
if (existing && typeof existing.set === 'function') {
|
||||||
id: 'cust-placeholder-db',
|
existing.set('content', content);
|
||||||
label: '🗄️ Placeholder (DB)',
|
existing.set('label', label);
|
||||||
content: `<span data-gjs-type="${PLACEHOLDER_COMPONENT}" data-placeholder-type="database" data-placeholder-table="${tableName}" data-placeholder-column="${firstColumn}">{{${placeholderLabel}}}</span>`
|
} else {
|
||||||
|
addOnce(bm, blockId, {
|
||||||
|
id: blockId,
|
||||||
|
label,
|
||||||
|
content,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ensureDbBlock();
|
||||||
|
fetchPlaceholderSchema()
|
||||||
|
.then((tables) => {
|
||||||
|
ensureDbBlock(tables);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.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);
|
log('SUCCESS', `Placeholder-Registrierung abgeschlossen. ${ALL_PLACEHOLDER_BLOCK_IDS.length} Blöcke erstellt.`, '#008000', 'info', true);
|
||||||
|
|||||||
Reference in New Issue
Block a user