Placeholder RTE

This commit is contained in:
2026-01-31 01:21:30 +01:00
parent 32eeeaf27a
commit ffed8fd9d9
2 changed files with 105 additions and 15 deletions

View File

@@ -1 +1 @@
1.1.69
1.1.70

View File

@@ -393,6 +393,14 @@
toolbar.className = 'bridge-rte-toolbar';
toolbar.setAttribute('data-bridge-rte', 'toolbar');
const toolbarSecondary = doc.createElement('div');
toolbarSecondary.style.display = 'flex';
toolbarSecondary.style.flexWrap = 'wrap';
toolbarSecondary.style.gap = '6px';
toolbarSecondary.style.alignItems = 'center';
toolbarSecondary.className = 'bridge-rte-toolbar bridge-rte-toolbar-secondary';
toolbarSecondary.setAttribute('data-bridge-rte', 'toolbar-secondary');
const content = doc.createElement(this.getEditableTag(component));
content.contentEditable = 'true';
content.style.flex = '1';
@@ -681,7 +689,7 @@
} catch {}
};
const addButton = (labelHtml, title, cmd, valueGetter, handler) => {
const addButton = (labelHtml, title, cmd, valueGetter, handler, target) => {
const btn = doc.createElement('button');
btn.type = 'button';
btn.innerHTML = labelHtml;
@@ -689,11 +697,15 @@
btn.setAttribute('aria-label', title);
btn.className = 'bridge-rte-btn';
btn.setAttribute('data-bridge-rte', 'button');
btn.style.padding = '4px 8px';
btn.style.padding = '6px 10px';
btn.style.border = '1px solid #cbd5f5';
btn.style.borderRadius = '4px';
btn.style.background = '#f8fafc';
btn.style.cursor = 'pointer';
btn.style.display = 'inline-flex';
btn.style.alignItems = 'center';
btn.style.justifyContent = 'center';
btn.style.gap = '6px';
btn.addEventListener('mousedown', (evt) => {
evt.preventDefault();
saveSelection();
@@ -709,16 +721,16 @@
if (cmd === 'createLink' && !value) return;
exec(cmd, value);
});
toolbar.appendChild(btn);
(target || toolbar).appendChild(btn);
};
const addSelect = (options, title, onChange) => {
const addSelect = (options, title, onChange, target) => {
const select = doc.createElement('select');
select.title = title;
select.setAttribute('aria-label', title);
select.className = 'bridge-rte-select';
select.setAttribute('data-bridge-rte', 'select');
select.style.padding = '4px 8px';
select.style.padding = '6px 10px';
select.style.border = '1px solid #cbd5f5';
select.style.borderRadius = '4px';
select.style.background = '#ffffff';
@@ -735,7 +747,7 @@
const value = select.value;
if (value) onChange(value);
});
toolbar.appendChild(select);
(target || toolbar).appendChild(select);
return select;
};
@@ -780,6 +792,7 @@
component,
modal,
toolbar,
toolbarSecondary,
content,
addButton,
addSelect,
@@ -798,7 +811,7 @@
try { ui.buildToolbar(ctx); } catch {}
}
if (!ui.overrideToolbar) {
const icon = (path) => `<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="${path}" fill="currentColor"/></svg>`;
const icon = (path) => `<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path d="${path}" fill="currentColor"/></svg>`;
addButton(
'<strong>B</strong>',
'Fett',
@@ -1006,7 +1019,7 @@
addButton(icon('M7 4h4v4H7V4zm6 12h4v4h-4v-4zM7 10h10v2H7v-2z'), 'Einzug', 'indent');
addButton(icon('M13 4h4v4h-4V4zM7 16h4v4H7v-4zM7 10h10v2H7v-2z'), 'Ausruecken', 'outdent');
addButton(
'<span style="font-weight:600;">Tx</span>',
icon('M3 15.5L14.5 4 20 9.5 8.5 21H3v-5.5zm2.5 3H8l9.5-9.5-2.5-2.5L5.5 16v2.5z'),
'Formatierung entfernen',
null,
null,
@@ -1016,6 +1029,40 @@
}
);
const buildPlaceholderLabel = (payload) => {
const type = payload && payload.type === 'database' ? 'database' : 'custom';
if (type === 'database') {
const table = (payload.table || 'TABELLE').toUpperCase();
const column = (payload.column || 'FELD').toUpperCase();
return `${table}.${column}`;
}
return (payload.key || 'PLATZHALTER').toUpperCase();
};
const buildPlaceholderText = (payload) => `{{${buildPlaceholderLabel(payload)}}}`;
const insertTextAtSelection = (text) => {
try {
content.focus();
restoreSelection();
const range = getSelectionRange();
if (!range) return false;
range.deleteContents();
const docRef = content.ownerDocument || document;
const node = docRef.createTextNode(text);
range.insertNode(node);
range.setStartAfter(node);
range.setEndAfter(node);
const sel = docRef.getSelection();
if (sel) {
sel.removeAllRanges();
sel.addRange(range);
}
saveSelection();
return true;
} catch {
return false;
}
};
const fontOptions = (this.B.RTE_FONTS && Array.isArray(this.B.RTE_FONTS) && this.B.RTE_FONTS.length)
? this.B.RTE_FONTS
: [
@@ -1028,11 +1075,41 @@
{ label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' },
{ label: 'Verdana', value: 'Verdana, sans-serif' },
];
addButton(
'<span style="font-weight:700;font-family:monospace;">{}</span><span style="font-size:12px;">Placeholder</span>',
'Placeholder einfuegen',
null,
null,
() => {
saveSelection();
const api = window.BridgeBlocksPlaceholder;
if (!api || typeof api.openModal !== 'function') return;
api.openModal(editor, null, {
onCancel: () => {
if (content && typeof content.focus === 'function') {
setTimeout(() => content.focus(), 0);
}
},
onSubmit: (payload) => {
const text = buildPlaceholderText(payload || {});
restoreSelection();
if (!insertTextAtSelection(text)) {
exec('insertText', text);
}
if (content && typeof content.focus === 'function') {
setTimeout(() => content.focus(), 0);
}
return true;
},
});
},
toolbarSecondary
);
const fontSelect = addSelect([{ label: 'Schriftart', value: '' }, ...fontOptions], 'Schriftart', (value) => {
if (!value) return;
pendingComponentStyle.fontFamily = value;
applyComponentStyle({ fontFamily: value }, { preview: true });
});
}, toolbarSecondary);
if (existingStyle && existingStyle.fontFamily && fontSelect) {
fontSelect.value = existingStyle.fontFamily;
}
@@ -1067,7 +1144,7 @@
}
pendingComponentStyle.fontSize = `${value}px`;
applyComponentStyle({ fontSize: `${value}px` }, { preview: true });
});
}, toolbarSecondary);
}
const injectedStyle = doc.createElement('style');
@@ -1081,15 +1158,27 @@
} catch {}
const frameCss = this.collectFrameCss(editor);
const rteUiCss = `
.bridge-rte-toolbar { gap: 8px; }
.bridge-rte-toolbar {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.bridge-rte-toolbar-secondary {
margin-top: 6px;
padding-top: 6px;
border-top: 1px dashed #e2e8f0;
}
.bridge-rte-btn {
font-size: 14px;
line-height: 1;
min-height: 30px;
min-width: 30px;
min-height: 34px;
min-width: 34px;
font-weight: 600;
color: #0f172a;
background: #f1f5f9;
background: #f8fafc;
}
.bridge-rte-btn svg { width: 16px; height: 16px; }
.bridge-rte-btn:hover { background: #e2e8f0; }
.bridge-rte-btn:active { transform: translateY(1px); }
.bridge-rte-actions .bridge-rte-btn { min-width: 88px; }
@@ -1100,6 +1189,7 @@
}
container.appendChild(toolbar);
container.appendChild(toolbarSecondary);
container.appendChild(content);
content.addEventListener('keyup', saveSelection);
content.addEventListener('mouseup', saveSelection);