dasds
This commit is contained in:
@@ -31,6 +31,287 @@
|
|||||||
statusPromise: 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 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 openPlaceholderModal = (editor, component) => {
|
||||||
|
if (!editor || !component || !component.is || !component.is(PLACEHOLDER_COMPONENT)) return;
|
||||||
|
const modal = editor.Modal;
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const attrs = component.getAttributes ? component.getAttributes() : {};
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
component.addAttributes({
|
||||||
|
'data-placeholder-type': 'custom',
|
||||||
|
'data-placeholder-key': key,
|
||||||
|
'data-placeholder-table': '',
|
||||||
|
'data-placeholder-column': '',
|
||||||
|
});
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
component.addAttributes({
|
||||||
|
'data-placeholder-type': 'database',
|
||||||
|
'data-placeholder-key': '',
|
||||||
|
'data-placeholder-table': table,
|
||||||
|
'data-placeholder-column': column,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
modal.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
toggleSections();
|
||||||
|
if (typeSelect.value === 'database') {
|
||||||
|
loadTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.setTitle('Placeholder bearbeiten');
|
||||||
|
modal.setContent(container);
|
||||||
|
modal.open();
|
||||||
|
if (editor.getSelected() !== component) {
|
||||||
|
editor.select && editor.select(component);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function addOnce(bm, id, def, category = TARGET_CAT_ID) {
|
function addOnce(bm, id, def, category = TARGET_CAT_ID) {
|
||||||
try {
|
try {
|
||||||
bm.add(id, { ...def, category });
|
bm.add(id, { ...def, category });
|
||||||
@@ -173,6 +454,12 @@
|
|||||||
changeProp: true,
|
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({
|
const PlaceholderModel = BaseModel.extend({
|
||||||
init() {
|
init() {
|
||||||
@@ -308,6 +595,39 @@
|
|||||||
const bm = editor.BlockManager;
|
const bm = editor.BlockManager;
|
||||||
ensurePlaceholderComponent(editor);
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
addOnce(bm, 'cust-placeholder-custom', {
|
addOnce(bm, 'cust-placeholder-custom', {
|
||||||
id: 'cust-placeholder-custom',
|
id: 'cust-placeholder-custom',
|
||||||
label: '🔖 Placeholder (Text)',
|
label: '🔖 Placeholder (Text)',
|
||||||
@@ -340,7 +660,8 @@
|
|||||||
|
|
||||||
window.BridgeBlocksPlaceholder = {
|
window.BridgeBlocksPlaceholder = {
|
||||||
IDS: ALL_PLACEHOLDER_BLOCK_IDS,
|
IDS: ALL_PLACEHOLDER_BLOCK_IDS,
|
||||||
register
|
register,
|
||||||
|
openModal: openPlaceholderModal
|
||||||
};
|
};
|
||||||
|
|
||||||
if (B && B.registerGrapesJSPlugin && typeof register === 'function') {
|
if (B && B.registerGrapesJSPlugin && typeof register === 'function') {
|
||||||
|
|||||||
Reference in New Issue
Block a user