Placeholder RTE
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user