Placeholder RTE
This commit is contained in:
@@ -1 +1 @@
|
|||||||
1.1.69
|
1.1.70
|
||||||
@@ -393,6 +393,14 @@
|
|||||||
toolbar.className = 'bridge-rte-toolbar';
|
toolbar.className = 'bridge-rte-toolbar';
|
||||||
toolbar.setAttribute('data-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));
|
const content = doc.createElement(this.getEditableTag(component));
|
||||||
content.contentEditable = 'true';
|
content.contentEditable = 'true';
|
||||||
content.style.flex = '1';
|
content.style.flex = '1';
|
||||||
@@ -681,7 +689,7 @@
|
|||||||
} catch {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addButton = (labelHtml, title, cmd, valueGetter, handler) => {
|
const addButton = (labelHtml, title, cmd, valueGetter, handler, target) => {
|
||||||
const btn = doc.createElement('button');
|
const btn = doc.createElement('button');
|
||||||
btn.type = 'button';
|
btn.type = 'button';
|
||||||
btn.innerHTML = labelHtml;
|
btn.innerHTML = labelHtml;
|
||||||
@@ -689,11 +697,15 @@
|
|||||||
btn.setAttribute('aria-label', title);
|
btn.setAttribute('aria-label', title);
|
||||||
btn.className = 'bridge-rte-btn';
|
btn.className = 'bridge-rte-btn';
|
||||||
btn.setAttribute('data-bridge-rte', 'button');
|
btn.setAttribute('data-bridge-rte', 'button');
|
||||||
btn.style.padding = '4px 8px';
|
btn.style.padding = '6px 10px';
|
||||||
btn.style.border = '1px solid #cbd5f5';
|
btn.style.border = '1px solid #cbd5f5';
|
||||||
btn.style.borderRadius = '4px';
|
btn.style.borderRadius = '4px';
|
||||||
btn.style.background = '#f8fafc';
|
btn.style.background = '#f8fafc';
|
||||||
btn.style.cursor = 'pointer';
|
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) => {
|
btn.addEventListener('mousedown', (evt) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
saveSelection();
|
saveSelection();
|
||||||
@@ -709,16 +721,16 @@
|
|||||||
if (cmd === 'createLink' && !value) return;
|
if (cmd === 'createLink' && !value) return;
|
||||||
exec(cmd, value);
|
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');
|
const select = doc.createElement('select');
|
||||||
select.title = title;
|
select.title = title;
|
||||||
select.setAttribute('aria-label', title);
|
select.setAttribute('aria-label', title);
|
||||||
select.className = 'bridge-rte-select';
|
select.className = 'bridge-rte-select';
|
||||||
select.setAttribute('data-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.border = '1px solid #cbd5f5';
|
||||||
select.style.borderRadius = '4px';
|
select.style.borderRadius = '4px';
|
||||||
select.style.background = '#ffffff';
|
select.style.background = '#ffffff';
|
||||||
@@ -735,7 +747,7 @@
|
|||||||
const value = select.value;
|
const value = select.value;
|
||||||
if (value) onChange(value);
|
if (value) onChange(value);
|
||||||
});
|
});
|
||||||
toolbar.appendChild(select);
|
(target || toolbar).appendChild(select);
|
||||||
return select;
|
return select;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -780,6 +792,7 @@
|
|||||||
component,
|
component,
|
||||||
modal,
|
modal,
|
||||||
toolbar,
|
toolbar,
|
||||||
|
toolbarSecondary,
|
||||||
content,
|
content,
|
||||||
addButton,
|
addButton,
|
||||||
addSelect,
|
addSelect,
|
||||||
@@ -798,7 +811,7 @@
|
|||||||
try { ui.buildToolbar(ctx); } catch {}
|
try { ui.buildToolbar(ctx); } catch {}
|
||||||
}
|
}
|
||||||
if (!ui.overrideToolbar) {
|
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(
|
addButton(
|
||||||
'<strong>B</strong>',
|
'<strong>B</strong>',
|
||||||
'Fett',
|
'Fett',
|
||||||
@@ -1006,7 +1019,7 @@
|
|||||||
addButton(icon('M7 4h4v4H7V4zm6 12h4v4h-4v-4zM7 10h10v2H7v-2z'), 'Einzug', 'indent');
|
addButton(icon('M7 4h4v4H7V4zm6 12h4v4h-4v-4zM7 10h10v2H7v-2z'), 'Einzug', 'indent');
|
||||||
addButton(icon('M13 4h4v4h-4V4zM7 16h4v4H7v-4zM7 10h10v2H7v-2z'), 'Ausruecken', 'outdent');
|
addButton(icon('M13 4h4v4h-4V4zM7 16h4v4H7v-4zM7 10h10v2H7v-2z'), 'Ausruecken', 'outdent');
|
||||||
addButton(
|
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',
|
'Formatierung entfernen',
|
||||||
null,
|
null,
|
||||||
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)
|
const fontOptions = (this.B.RTE_FONTS && Array.isArray(this.B.RTE_FONTS) && this.B.RTE_FONTS.length)
|
||||||
? this.B.RTE_FONTS
|
? this.B.RTE_FONTS
|
||||||
: [
|
: [
|
||||||
@@ -1028,11 +1075,41 @@
|
|||||||
{ label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' },
|
{ label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' },
|
||||||
{ label: 'Verdana', value: 'Verdana, 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) => {
|
const fontSelect = addSelect([{ label: 'Schriftart', value: '' }, ...fontOptions], 'Schriftart', (value) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
pendingComponentStyle.fontFamily = value;
|
pendingComponentStyle.fontFamily = value;
|
||||||
applyComponentStyle({ fontFamily: value }, { preview: true });
|
applyComponentStyle({ fontFamily: value }, { preview: true });
|
||||||
});
|
}, toolbarSecondary);
|
||||||
if (existingStyle && existingStyle.fontFamily && fontSelect) {
|
if (existingStyle && existingStyle.fontFamily && fontSelect) {
|
||||||
fontSelect.value = existingStyle.fontFamily;
|
fontSelect.value = existingStyle.fontFamily;
|
||||||
}
|
}
|
||||||
@@ -1067,7 +1144,7 @@
|
|||||||
}
|
}
|
||||||
pendingComponentStyle.fontSize = `${value}px`;
|
pendingComponentStyle.fontSize = `${value}px`;
|
||||||
applyComponentStyle({ fontSize: `${value}px` }, { preview: true });
|
applyComponentStyle({ fontSize: `${value}px` }, { preview: true });
|
||||||
});
|
}, toolbarSecondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
const injectedStyle = doc.createElement('style');
|
const injectedStyle = doc.createElement('style');
|
||||||
@@ -1081,15 +1158,27 @@
|
|||||||
} catch {}
|
} catch {}
|
||||||
const frameCss = this.collectFrameCss(editor);
|
const frameCss = this.collectFrameCss(editor);
|
||||||
const rteUiCss = `
|
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 {
|
.bridge-rte-btn {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
min-height: 30px;
|
min-height: 34px;
|
||||||
min-width: 30px;
|
min-width: 34px;
|
||||||
|
font-weight: 600;
|
||||||
color: #0f172a;
|
color: #0f172a;
|
||||||
background: #f1f5f9;
|
background: #f8fafc;
|
||||||
}
|
}
|
||||||
|
.bridge-rte-btn svg { width: 16px; height: 16px; }
|
||||||
.bridge-rte-btn:hover { background: #e2e8f0; }
|
.bridge-rte-btn:hover { background: #e2e8f0; }
|
||||||
.bridge-rte-btn:active { transform: translateY(1px); }
|
.bridge-rte-btn:active { transform: translateY(1px); }
|
||||||
.bridge-rte-actions .bridge-rte-btn { min-width: 88px; }
|
.bridge-rte-actions .bridge-rte-btn { min-width: 88px; }
|
||||||
@@ -1100,6 +1189,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
container.appendChild(toolbar);
|
container.appendChild(toolbar);
|
||||||
|
container.appendChild(toolbarSecondary);
|
||||||
container.appendChild(content);
|
container.appendChild(content);
|
||||||
content.addEventListener('keyup', saveSelection);
|
content.addEventListener('keyup', saveSelection);
|
||||||
content.addEventListener('mouseup', saveSelection);
|
content.addEventListener('mouseup', saveSelection);
|
||||||
|
|||||||
Reference in New Issue
Block a user