/* /assets/js/bridge/rte-editor.js */
(function () {
const PluginName = 'bridge-rte-editor';
const editorInstances = new WeakMap();
class BridgeRTE {
constructor(bridgeParts) {
this.B = bridgeParts || (window.BridgeParts || (window.BridgeParts = {}));
this.editor = null;
this.modalOpen = false;
this.allowClose = false;
this.lastContent = new WeakMap();
this.restoring = new WeakSet();
this.restoreUntil = new WeakMap();
this.restoreWindowMs = 3000;
}
static serializeHtml(editor) {
if (!editor || typeof editor.getHtml !== 'function') return '';
let html = editor.getHtml() || '';
if (!html) {
try {
const wrapper = editor.getWrapper && editor.getWrapper();
if (wrapper && typeof wrapper.toHTML === 'function') {
html = wrapper.toHTML();
}
} catch {}
}
if (!html) {
try {
const wrapper = editor.getWrapper && editor.getWrapper();
const el = wrapper && wrapper.view && wrapper.view.el ? wrapper.view.el : null;
if (el && el.innerHTML) {
html = String(el.innerHTML || '');
}
} catch {}
}
if (!html) {
try {
const body = editor.Canvas && editor.Canvas.getBody ? editor.Canvas.getBody() : null;
if (body && body.innerHTML) {
html = String(body.innerHTML || '');
}
} catch {}
}
try {
const wrapper = editor.getWrapper && editor.getWrapper();
if (wrapper && wrapper.find) {
const doc = new DOMParser().parseFromString(html, 'text/html');
const candidates = wrapper.find('[data-gjs-type=\"text\"], [data-gjs-type=\"link\"], [data-gjs-type=\"button\"]');
candidates.forEach((model) => {
const content = model && model.get ? String(model.get('content') || '') : '';
const id = model && (model.getId ? model.getId() : model.get && model.get('id'));
if (!content || !id) return;
const el = doc.getElementById(id);
if (el) {
el.innerHTML = content;
}
});
if (doc.body && doc.body.innerHTML !== undefined) {
html = doc.body.innerHTML;
}
}
} catch {}
if (html) {
const bodyMatch = html.match(/
]*>([\s\S]*?)<\/body>/i);
if (bodyMatch) html = bodyMatch[1];
}
return html;
}
log(type, message, color = '#94a3b8', logType = 'info', force = false) {
if (typeof this.B.log === 'function') {
this.B.log(PluginName, `[${type}] ${message}`, color, logType, force);
}
}
escapeHtml(text) {
return String(text || '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
sanitizeInlineHtml(rawHtml, fallbackText) {
const wrapper = document.createElement('div');
wrapper.innerHTML = String(rawHtml || '');
wrapper.querySelectorAll('script,style').forEach((node) => node.remove());
const inlineTags = new Set(['A', 'B', 'STRONG', 'I', 'EM', 'U', 'S', 'BR', 'SUB', 'SUP', 'SPAN', 'FONT']);
const blockTags = new Set([
'DIV', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
'UL', 'OL', 'LI', 'TABLE', 'TBODY', 'THEAD', 'TFOOT', 'TR', 'TD', 'TH',
'SECTION', 'ARTICLE', 'ASIDE', 'HEADER', 'FOOTER',
]);
const unwrap = (el, addBreak) => {
const frag = document.createDocumentFragment();
if (addBreak) {
const prev = el.previousSibling;
const hasPrevContent = !!(prev && (prev.nodeType === 3 ? prev.textContent.trim() : (prev.tagName && prev.tagName !== 'BR')));
if (hasPrevContent) {
frag.appendChild(document.createElement('br'));
}
}
while (el.firstChild) {
frag.appendChild(el.firstChild);
}
el.replaceWith(frag);
};
Array.from(wrapper.querySelectorAll('*')).forEach((el) => {
const tag = el.tagName;
const tagLower = tag.toLowerCase();
if (inlineTags.has(tag)) {
if (tagLower === 'font') {
const face = el.getAttribute('face');
const size = el.getAttribute('size');
const color = el.getAttribute('color');
const styles = [];
if (face) styles.push(`font-family:${face}`);
if (color) styles.push(`color:${color}`);
if (size) {
const sizeMap = {
1: '10px',
2: '12px',
3: '14px',
4: '16px',
5: '18px',
6: '24px',
7: '32px',
};
const mapped = sizeMap[size] || sizeMap[parseInt(size, 10)] || null;
if (mapped) styles.push(`font-size:${mapped}`);
}
if (styles.length) {
const span = document.createElement('span');
span.setAttribute('style', styles.join(';'));
while (el.firstChild) span.appendChild(el.firstChild);
el.replaceWith(span);
} else {
unwrap(el, false);
}
return;
}
if (tagLower === 'a' && !el.getAttribute('href')) {
unwrap(el, false);
return;
}
Array.from(el.attributes).forEach((attr) => {
const name = attr.name.toLowerCase();
if (tagLower === 'a') {
if (!['href', 'target', 'rel', 'style', 'class'].includes(name)) {
el.removeAttribute(attr.name);
}
} else if (!['style', 'class'].includes(name)) {
el.removeAttribute(attr.name);
}
});
return;
}
unwrap(el, blockTags.has(tag));
});
let html = wrapper.innerHTML
.replace(/
/gi, '
')
.trim();
const brCount = (html.match(/
/g) || []).length;
if (brCount <= 1) {
html = html.replace(/(
)+$/g, '').trim();
}
if (!html) {
const text = String(fallbackText || wrapper.textContent || '').trim();
if (text) html = this.escapeHtml(text);
}
return html;
}
isTextLike(component) {
return !!(component && component.is && (component.is('text') || component.is('button') || component.is('link')));
}
logConsoleSnapshot(editor, component, label) {
try {
if (!this.B || !this.B.DEBUG_RTE) return;
const viewEl = component && component.view ? component.view.el : null;
const modelContent = component && component.get ? String(component.get('content') || '') : '';
const editorHtml = editor && typeof editor.getHtml === 'function' ? String(editor.getHtml() || '') : '';
const maxLen = 1000;
console.log(`[RTE DEBUG] ${label}`, {
modelId: component && (component.getId ? component.getId() : component.get && component.get('id')),
modelType: component && component.get ? component.get('type') : undefined,
modelContentLen: modelContent.length,
modelContent: modelContent.slice(0, maxLen),
viewHtmlLen: viewEl ? String(viewEl.innerHTML || '').length : 0,
viewHtml: viewEl ? String(viewEl.innerHTML || '').slice(0, maxLen) : '',
viewOuterLen: viewEl ? String(viewEl.outerHTML || '').length : 0,
viewOuter: viewEl ? String(viewEl.outerHTML || '').slice(0, maxLen) : '',
editorHtmlLen: editorHtml.length,
editorHtml: editorHtml.slice(0, maxLen),
});
} catch {}
}
applyContentToComponent(editor, component, html) {
if (!component) return;
const content = String(html || '');
const isText = (component.is && component.is('text'))
|| (component.get && component.get('type') === 'text');
try {
const hasInlineStyle = /style\s*=\s*["'][^"']*["']/.test(content) || / {
try {
const currentHtml = String(component.view.el.innerHTML || '').trim();
const targetHtml = String(content || '').trim();
if (targetHtml && currentHtml !== targetHtml) {
component.view.el.innerHTML = targetHtml;
}
} catch {}
};
setTimeout(reapply, 0);
setTimeout(reapply, 50);
}
}
collectFrameCss(editor) {
const cssParts = [];
try {
const frameDoc = editor && editor.Canvas && editor.Canvas.getDocument ? editor.Canvas.getDocument() : null;
if (frameDoc) {
frameDoc.querySelectorAll('style').forEach((styleEl) => {
const css = String(styleEl.textContent || '').trim();
if (css) cssParts.push(css);
});
frameDoc.querySelectorAll('link[rel="stylesheet"][href]').forEach((linkEl) => {
const href = linkEl.getAttribute('href');
if (!href) return;
try {
const abs = new URL(href, frameDoc.baseURI).href;
cssParts.push(`@import url("${abs}");`);
} catch {}
});
}
} catch {}
return cssParts.join('\n');
}
getEditableTag(component) {
const viewEl = component && component.view ? component.view.el : null;
const raw = (component && component.get && (component.get('tagName') || component.get('tag')))
|| (viewEl && viewEl.tagName)
|| 'div';
const tag = String(raw || 'div').toLowerCase();
const allowed = new Set([
'div', 'p', 'span', 'a', 'button',
'strong', 'b', 'em', 'i', 'u', 's',
'sub', 'sup', 'ul', 'ol', 'li',
]);
return allowed.has(tag) ? tag : 'div';
}
applyComponentPreviewStyles(component, content) {
if (!component || !content) return;
const mergeStyles = () => {
const out = {};
const fromGetStyle = (component.getStyle && component.getStyle()) || {};
const fromGetAttr = (component.get && component.get('style')) || {};
const add = (obj) => {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return;
Object.entries(obj).forEach(([key, val]) => {
if (val === undefined || val === null || val === '') return;
out[key] = val;
});
};
add(fromGetStyle);
add(fromGetAttr);
return out;
};
const styleObj = mergeStyles();
const applyStyle = (prop, value) => {
if (!value) return;
try { content.style[prop] = value; } catch {}
};
const pick = (keys) => {
for (const key of keys) {
if (styleObj[key]) return styleObj[key];
}
return '';
};
applyStyle('fontFamily', pick(['font-family', 'fontFamily']));
applyStyle('fontSize', pick(['font-size', 'fontSize']));
applyStyle('fontWeight', pick(['font-weight', 'fontWeight']));
applyStyle('fontStyle', pick(['font-style', 'fontStyle']));
applyStyle('textDecoration', pick(['text-decoration', 'textDecoration', 'text-decoration-line']));
applyStyle('color', pick(['color']));
applyStyle('lineHeight', pick(['line-height', 'lineHeight']));
applyStyle('letterSpacing', pick(['letter-spacing', 'letterSpacing']));
applyStyle('textAlign', pick(['text-align', 'textAlign']));
applyStyle('backgroundColor', pick(['background-color', 'backgroundColor']));
}
applyComputedPreviewStyles(component, content) {
try {
const viewEl = component && component.view ? component.view.el : null;
if (!viewEl || !viewEl.ownerDocument) return;
const computed = viewEl.ownerDocument.defaultView
? viewEl.ownerDocument.defaultView.getComputedStyle(viewEl)
: null;
if (!computed) return;
const map = {
fontFamily: 'font-family',
fontSize: 'font-size',
fontWeight: 'font-weight',
fontStyle: 'font-style',
textDecoration: 'text-decoration-line',
color: 'color',
lineHeight: 'line-height',
letterSpacing: 'letter-spacing',
textAlign: 'text-align',
backgroundColor: 'background-color',
};
Object.entries(map).forEach(([prop, cssProp]) => {
const val = computed.getPropertyValue(cssProp);
if (val && val.trim()) {
try { content.style[prop] = val.trim(); } catch {}
}
});
} catch {}
}
openRichTextModal(editor, component) {
if (!this.isTextLike(component)) {
this.log('RTE', 'Bitte zuerst ein Text-Element auswaehlen.', '#888');
return;
}
const modal = editor && editor.Modal;
if (!modal || this.modalOpen) return;
this.modalOpen = true;
this.allowClose = false;
let rteInstance = null;
let rteTargetEl = null;
try {
const editing = editor.getEditing && editor.getEditing();
if (editing && editing.model === component && editor.setEditing) {
editor.setEditing(null);
}
if (editor.RichTextEditor && editor.RichTextEditor.disable && component.view && component.view.el) {
rteInstance = editor.RichTextEditor;
rteTargetEl = component.view.el;
editor.RichTextEditor.disable(component.view.el);
}
} catch {}
const closeModal = () => {
try { window.__bridgeRteOpen = false; } catch {}
this.allowClose = true;
this.modalOpen = false;
if (rteInstance && typeof rteInstance.enable === 'function' && rteTargetEl) {
try {
const res = rteInstance.enable(rteTargetEl);
if (res && typeof res.catch === 'function') {
res.catch(() => {});
}
} catch {}
}
if (this.B.allowModalCloseOnce) this.B.allowModalCloseOnce();
if (typeof modal.close === 'function') {
modal.close();
} else if (modal.getModel && modal.getModel().set) {
modal.getModel().set('open', false);
}
this.allowClose = false;
};
const doc = document;
const container = doc.createElement('div');
try { window.__bridgeRteOpen = true; } catch {}
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.gap = '10px';
container.style.height = '100%';
container.style.minHeight = '360px';
container.style.position = 'relative';
container.className = 'bridge-rte-container';
container.setAttribute('data-bridge-rte', 'container');
const toolbar = doc.createElement('div');
toolbar.style.display = 'flex';
toolbar.style.flexWrap = 'wrap';
toolbar.style.gap = '6px';
toolbar.style.alignItems = 'center';
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';
content.style.minHeight = '280px';
content.style.border = '1px solid #cbd5f5';
content.style.borderRadius = '6px';
content.style.padding = '12px';
content.style.background = '#ffffff';
content.style.overflow = 'auto';
content.style.fontFamily = 'Arial, sans-serif';
content.style.fontSize = '14px';
content.className = [content.className, 'bridge-rte-content'].filter(Boolean).join(' ');
content.setAttribute('data-bridge-rte', 'content');
if (component && component.view && component.view.el) {
const viewEl = component.view.el;
if (viewEl.className) {
content.className = [content.className, viewEl.className].filter(Boolean).join(' ');
}
if (viewEl.id) content.id = viewEl.id;
}
const modelContent = (component.get && component.get('content')) || '';
const viewHtml = (component.view && component.view.el && component.view.el.innerHTML) || '';
const pickHtmlSource = (view, model) => {
const v = String(view || '');
const m = String(model || '');
if (!v && m) return m;
if (!m) return v;
const vHasSpan = / v.length + 20) return m;
return v;
};
let htmlSource = pickHtmlSource(viewHtml, modelContent) || '';
if (!htmlSource && component && typeof component.toHTML === 'function') {
try {
const fullHtml = String(component.toHTML() || '');
if (fullHtml) {
const wrapper = document.createElement('div');
wrapper.innerHTML = fullHtml;
const first = wrapper.firstElementChild;
if (first && first.innerHTML !== undefined) {
htmlSource = first.innerHTML;
} else {
htmlSource = wrapper.innerHTML;
}
}
} catch {}
}
if (component && component.view && component.view.el) {
const viewEl = component.view.el;
const hasInline = / {
if (val) styles.push(`${prop}:${val}`);
};
pushStyle('font-family', cs.fontFamily);
pushStyle('font-size', cs.fontSize);
pushStyle('font-weight', cs.fontWeight);
pushStyle('font-style', cs.fontStyle);
if (cs.textDecorationLine && cs.textDecorationLine !== 'none') {
pushStyle('text-decoration', cs.textDecorationLine);
}
if (cs.textAlign && cs.textAlign !== 'start') {
pushStyle('text-align', cs.textAlign);
}
if (cs.lineHeight && cs.lineHeight !== 'normal') {
pushStyle('line-height', cs.lineHeight);
}
if (cs.color) {
pushStyle('color', cs.color);
}
if (styles.length) {
htmlSource = `${htmlSource}`;
}
} catch {}
}
}
const initialHtml = this.sanitizeInlineHtml(htmlSource, modelContent || '');
content.innerHTML = initialHtml;
const existingStyle = component && component.get && component.get('style') ? component.get('style') : null;
if (existingStyle && typeof existingStyle === 'object') {
if (existingStyle.fontFamily) {
content.style.fontFamily = existingStyle.fontFamily;
}
if (existingStyle.fontSize) {
content.style.fontSize = existingStyle.fontSize;
}
}
this.applyComponentPreviewStyles(component, content);
this.applyComputedPreviewStyles(component, content);
let savedRange = null;
const saveSelection = () => {
try {
const sel = (content.ownerDocument || document).getSelection();
if (!sel || sel.rangeCount === 0) return;
const range = sel.getRangeAt(0);
if (content.contains(range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
} catch {}
};
const restoreSelection = () => {
try {
const sel = (content.ownerDocument || document).getSelection();
if (!sel || !savedRange) return;
sel.removeAllRanges();
sel.addRange(savedRange);
} catch {}
};
const exec = (cmd, value) => {
try {
content.focus();
restoreSelection();
const docRef = content.ownerDocument || document;
docRef.execCommand(cmd, false, value);
saveSelection();
} catch {}
};
const getSelectionRange = () => {
try {
const docRef = content.ownerDocument || document;
const sel = docRef.getSelection();
if (!sel || sel.rangeCount === 0) return null;
const range = sel.getRangeAt(0);
if (!content.contains(range.commonAncestorContainer)) return null;
return range;
} catch {
return null;
}
};
const findLinkAtSelection = () => {
const range = getSelectionRange();
if (!range) return null;
let node = range.commonAncestorContainer;
if (node && node.nodeType === 3) node = node.parentNode;
while (node && node !== content) {
if (node.tagName && node.tagName.toUpperCase() === 'A') return node;
node = node.parentNode;
}
return null;
};
const applyInlineStyle = (styleProp, value) => {
try {
content.focus();
restoreSelection();
const docRef = content.ownerDocument || document;
const range = getSelectionRange();
if (!range || range.collapsed) return;
const wrapper = docRef.createElement('span');
wrapper.style[styleProp] = value;
const fragment = range.extractContents();
wrapper.appendChild(fragment);
range.insertNode(wrapper);
const sel = docRef.getSelection();
if (sel) {
sel.removeAllRanges();
const newRange = docRef.createRange();
newRange.selectNodeContents(wrapper);
sel.addRange(newRange);
}
saveSelection();
} catch {}
};
const selectionHasStyle = (range, styleProp, tags, styleMatch) => {
if (!range) return false;
try {
const fragment = range.cloneContents();
const walker = (node) => {
if (!node) return false;
if (node.nodeType === 1) {
const tag = node.tagName ? node.tagName.toUpperCase() : '';
if (tags && tags.includes(tag)) return true;
if (styleProp && node.style) {
if (styleMatch && styleMatch(node.style[styleProp] || '', node)) return true;
}
for (const child of Array.from(node.childNodes)) {
if (walker(child)) return true;
}
}
return false;
};
for (const child of Array.from(fragment.childNodes)) {
if (walker(child)) return true;
}
const checkAncestors = (node) => {
let cur = node;
while (cur && cur !== content) {
if (cur.nodeType === 1) {
const tag = cur.tagName ? cur.tagName.toUpperCase() : '';
if (tags && tags.includes(tag)) return true;
if (styleProp && cur.style) {
if (styleMatch && styleMatch(cur.style[styleProp] || '', cur)) return true;
}
}
cur = cur.parentNode;
}
return false;
};
if (checkAncestors(range.startContainer)) return true;
if (checkAncestors(range.endContainer)) return true;
} catch {}
return false;
};
const removeInlineStyle = (styleProp, tags, clearFn) => {
try {
content.focus();
restoreSelection();
const docRef = content.ownerDocument || document;
const range = getSelectionRange();
if (!range || range.collapsed) return;
const unwrap = (node) => {
const parent = node.parentNode;
if (!parent) return;
while (node.firstChild) parent.insertBefore(node.firstChild, node);
parent.removeChild(node);
};
const depth = (node) => {
let d = 0;
let cur = node;
while (cur && cur !== content) {
d += 1;
cur = cur.parentNode;
}
return d;
};
const nodes = [];
const walker = docRef.createTreeWalker(content, NodeFilter.SHOW_ELEMENT, null);
let node = walker.nextNode();
while (node) {
if (node !== content && range.intersectsNode(node)) {
nodes.push(node);
}
node = walker.nextNode();
}
nodes.sort((a, b) => depth(b) - depth(a));
nodes.forEach((el) => {
const tag = el.tagName ? el.tagName.toUpperCase() : '';
if (tag === 'BR') return;
if (tags && tags.includes(tag)) {
unwrap(el);
return;
}
if (styleProp && el.style) {
if (typeof clearFn === 'function') {
clearFn(el.style);
} else {
el.style[styleProp] = '';
}
const styleAttr = el.getAttribute('style');
if ((!styleAttr || !String(styleAttr).trim()) && tag === 'SPAN') {
unwrap(el);
}
}
});
saveSelection();
} catch {}
};
const toggleInlineStyle = (cmd, styleProp, value, tags, styleMatch, clearFn) => {
const range = getSelectionRange();
if (!range || range.collapsed) return;
if (selectionHasStyle(range, styleProp, tags, styleMatch)) {
if (cmd) exec(cmd);
removeInlineStyle(styleProp, tags, clearFn);
return;
}
if (cmd) exec(cmd);
if (styleProp) applyInlineStyle(styleProp, value);
};
const removeInlineFormatting = () => {
try {
content.focus();
restoreSelection();
const docRef = content.ownerDocument || document;
const range = getSelectionRange();
if (!range || range.collapsed) return;
const unwrap = (node) => {
const parent = node.parentNode;
if (!parent) return;
while (node.firstChild) parent.insertBefore(node.firstChild, node);
parent.removeChild(node);
};
const depth = (node) => {
let d = 0;
let cur = node;
while (cur && cur !== content) {
d += 1;
cur = cur.parentNode;
}
return d;
};
const nodes = [];
const walker = docRef.createTreeWalker(content, NodeFilter.SHOW_ELEMENT, null);
let node = walker.nextNode();
while (node) {
if (node !== content && range.intersectsNode(node)) {
nodes.push(node);
}
node = walker.nextNode();
}
nodes.sort((a, b) => depth(b) - depth(a));
nodes.forEach((el) => {
const tag = el.tagName ? el.tagName.toUpperCase() : '';
if (tag === 'BR') return;
if (tag === 'B' || tag === 'STRONG' || tag === 'I' || tag === 'EM' || tag === 'U' || tag === 'S' || tag === 'STRIKE') {
unwrap(el);
return;
}
if (tag === 'SPAN') {
try {
el.style.fontWeight = '';
el.style.fontStyle = '';
el.style.textDecoration = '';
el.style.textDecorationLine = '';
el.style.textDecorationStyle = '';
} catch {}
const styleAttr = el.getAttribute('style');
if (!styleAttr || !String(styleAttr).trim()) {
unwrap(el);
}
}
});
saveSelection();
} catch {}
};
const addButton = (labelHtml, title, cmd, valueGetter, handler, target) => {
const btn = doc.createElement('button');
btn.type = 'button';
btn.innerHTML = labelHtml;
btn.title = title;
btn.setAttribute('aria-label', title);
btn.className = 'bridge-rte-btn';
btn.setAttribute('data-bridge-rte', 'button');
btn.classList.add('bridge-rte-btn--tool');
btn.style.padding = '8px 12px';
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 = '8px';
btn.addEventListener('mousedown', (evt) => {
evt.preventDefault();
saveSelection();
});
btn.addEventListener('click', () => {
if (typeof handler === 'function') {
handler();
return;
}
const value = typeof valueGetter === 'function' ? valueGetter() : valueGetter;
const hasValue = typeof valueGetter !== 'undefined';
if (hasValue && (value === null || value === undefined)) return;
if (cmd === 'createLink' && !value) return;
exec(cmd, value);
});
(target || toolbar).appendChild(btn);
};
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 = '8px 12px';
select.style.border = '1px solid #cbd5f5';
select.style.borderRadius = '4px';
select.style.background = '#ffffff';
options.forEach((opt) => {
const optEl = doc.createElement('option');
optEl.value = opt.value;
optEl.textContent = opt.label;
select.appendChild(optEl);
});
select.addEventListener('mousedown', () => {
saveSelection();
});
select.addEventListener('change', () => {
const value = select.value;
if (value) onChange(value);
});
(target || toolbar).appendChild(select);
return select;
};
const applyComponentStyle = (styleObj, opts = {}) => {
try {
if (opts.preview && content) {
Object.entries(styleObj).forEach(([key, val]) => {
content.style[key] = val;
});
return;
}
const normalizeKeys = (obj) => {
const out = {};
Object.entries(obj || {}).forEach(([key, val]) => {
if (key === 'fontFamily') out['font-family'] = val;
else if (key === 'fontSize') out['font-size'] = val;
else out[key] = val;
});
return out;
};
if (component && component.setStyle && component.getStyle) {
const current = component.getStyle() || {};
const safeCurrent = (current && typeof current === 'object' && !Array.isArray(current)) ? current : {};
component.setStyle({ ...safeCurrent, ...normalizeKeys(styleObj) });
} else if (component && component.set) {
const current = component.get && component.get('style') ? component.get('style') : {};
const safeCurrent = (current && typeof current === 'object' && !Array.isArray(current)) ? current : {};
component.set('style', { ...safeCurrent, ...normalizeKeys(styleObj) });
}
if (component && component.view && component.view.el) {
Object.entries(styleObj).forEach(([key, val]) => {
component.view.el.style[key] = val;
});
}
} catch {}
};
const pendingComponentStyle = {};
const ui = this.B.RTE_UI || {};
const ctx = {
editor,
component,
modal,
toolbar,
toolbarSecondary,
content,
addButton,
addSelect,
exec,
applyInlineStyle,
getSelectionRange,
applyComponentStyle,
pendingComponentStyle,
saveSelection,
restoreSelection,
};
if (typeof ui.beforeOpen === 'function') {
try { ui.beforeOpen(ctx); } catch {}
}
if (typeof ui.buildToolbar === 'function') {
try { ui.buildToolbar(ctx); } catch {}
}
if (!ui.overrideToolbar) {
const icon = (path) => ``;
addButton(
'B',
'Fett',
'bold',
null,
() => toggleInlineStyle(
'bold',
'fontWeight',
'700',
['B', 'STRONG'],
(val) => {
const num = parseInt(String(val || '').replace(/[^0-9]/g, ''), 10);
return Number.isFinite(num) ? num >= 600 : /bold/i.test(String(val || ''));
},
(style) => { style.fontWeight = ''; }
)
);
addButton(
'I',
'Kursiv',
'italic',
null,
() => toggleInlineStyle(
'italic',
'fontStyle',
'italic',
['I', 'EM'],
(val) => String(val || '').toLowerCase() === 'italic',
(style) => { style.fontStyle = ''; }
)
);
addButton(
'U',
'Unterstrichen',
'underline',
null,
() => toggleInlineStyle(
'underline',
'textDecoration',
'underline',
['U'],
(val) => /underline/i.test(String(val || '')),
(style) => {
style.textDecoration = '';
style.textDecorationLine = '';
}
)
);
addButton(icon('M4 7h10v2H4zM4 11h16v2H4zM4 15h10v2H4z'), 'Liste (ungeordnet)', 'insertUnorderedList');
addButton(icon('M4 6h4v2H4V6zm0 4h4v2H4v-2zm0 4h4v2H4v-2zm6-8h10v2H10V6zm0 4h10v2H10v-2zm0 4h10v2H10v-2z'), 'Liste (geordnet)', 'insertOrderedList');
addButton(icon('M4 7h10v2H4zM4 11h16v2H4zM4 15h12v2H4z'), 'Linksbundig', null, null, () => {
applyComponentStyle({ textAlign: 'left' });
});
addButton(icon('M5 7h14v2H5zM4 11h16v2H4zM5 15h14v2H5z'), 'Zentriert', null, null, () => {
applyComponentStyle({ textAlign: 'center' });
});
addButton(icon('M10 7h10v2H10zM4 11h16v2H4zM8 15h12v2H8z'), 'Rechtsbuendig', null, null, () => {
applyComponentStyle({ textAlign: 'right' });
});
addButton(icon('M4 7h16v2H4zM4 11h16v2H4zM4 15h16v2H4z'), 'Blocksatz', null, null, () => {
applyComponentStyle({ textAlign: 'justify' });
});
const linkModal = doc.createElement('div');
linkModal.style.display = 'none';
linkModal.style.position = 'absolute';
linkModal.style.inset = '0';
linkModal.style.background = 'rgba(15,23,42,0.35)';
linkModal.style.alignItems = 'center';
linkModal.style.justifyContent = 'center';
linkModal.style.zIndex = '20';
linkModal.className = 'bridge-rte-link-modal';
linkModal.setAttribute('data-bridge-rte', 'link-modal');
const linkPanel = doc.createElement('div');
linkPanel.style.display = 'flex';
linkPanel.style.flexDirection = 'column';
linkPanel.style.gap = '10px';
linkPanel.style.padding = '12px';
linkPanel.style.border = '1px solid #cbd5f5';
linkPanel.style.borderRadius = '8px';
linkPanel.style.background = '#ffffff';
linkPanel.style.boxShadow = '0 6px 24px rgba(15,23,42,0.2)';
linkPanel.style.minWidth = '320px';
const linkTitle = doc.createElement('div');
linkTitle.textContent = 'Link einfuegen';
linkTitle.style.fontWeight = '600';
linkTitle.style.fontSize = '14px';
const linkInput = doc.createElement('input');
linkInput.type = 'text';
linkInput.placeholder = 'https://...';
linkInput.style.padding = '6px 8px';
linkInput.style.border = '1px solid #cbd5f5';
linkInput.style.borderRadius = '4px';
linkInput.style.fontSize = '14px';
const linkActions = doc.createElement('div');
linkActions.style.display = 'flex';
linkActions.style.justifyContent = 'flex-end';
linkActions.style.gap = '8px';
const linkApply = doc.createElement('button');
linkApply.type = 'button';
linkApply.textContent = 'OK';
linkApply.className = 'bridge-rte-btn';
linkApply.style.padding = '6px 10px';
const linkRemove = doc.createElement('button');
linkRemove.type = 'button';
linkRemove.textContent = 'Entfernen';
linkRemove.className = 'bridge-rte-btn';
linkRemove.style.padding = '6px 10px';
const linkCancel = doc.createElement('button');
linkCancel.type = 'button';
linkCancel.textContent = 'Abbrechen';
linkCancel.className = 'bridge-rte-btn';
linkCancel.style.padding = '6px 10px';
linkActions.appendChild(linkCancel);
linkActions.appendChild(linkRemove);
linkActions.appendChild(linkApply);
linkPanel.appendChild(linkTitle);
linkPanel.appendChild(linkInput);
linkPanel.appendChild(linkActions);
linkModal.appendChild(linkPanel);
container.appendChild(linkModal);
const hideLinkPanel = () => {
linkModal.style.display = 'none';
};
const showLinkPanel = () => {
saveSelection();
const linkEl = findLinkAtSelection();
linkInput.value = linkEl && linkEl.getAttribute ? (linkEl.getAttribute('href') || '') : '';
linkModal.style.display = 'flex';
linkInput.focus();
linkInput.select();
};
addButton(
icon('M7 7h10l-1.5 1.5-3-3-5.5 5.5v5h5l5.5-5.5-3-3L17 7z'),
'Link einfuegen/bearbeiten',
null,
null,
() => {
showLinkPanel();
}
);
linkApply.addEventListener('click', () => {
const url = String(linkInput.value || '').trim();
if (!url) {
hideLinkPanel();
return;
}
restoreSelection();
try {
const linkEl = findLinkAtSelection();
if (linkEl && linkEl.setAttribute) {
linkEl.setAttribute('href', url);
hideLinkPanel();
return;
}
} catch {}
const range = savedRange ? savedRange.cloneRange() : getSelectionRange();
if (!range || range.collapsed) {
hideLinkPanel();
return;
}
try {
const docRef = content.ownerDocument || document;
const anchor = docRef.createElement('a');
anchor.setAttribute('href', url);
anchor.setAttribute('target', '_blank');
anchor.setAttribute('rel', 'noopener');
const fragment = range.extractContents();
anchor.appendChild(fragment);
range.insertNode(anchor);
} catch {
exec('createLink', url);
}
hideLinkPanel();
});
linkRemove.addEventListener('click', () => {
try {
const linkEl = findLinkAtSelection();
if (linkEl && linkEl.parentNode) {
while (linkEl.firstChild) {
linkEl.parentNode.insertBefore(linkEl.firstChild, linkEl);
}
linkEl.parentNode.removeChild(linkEl);
hideLinkPanel();
return;
}
} catch {}
restoreSelection();
exec('unlink');
const stillLinked = findLinkAtSelection();
if (stillLinked && stillLinked.parentNode) {
while (stillLinked.firstChild) {
stillLinked.parentNode.insertBefore(stillLinked.firstChild, stillLinked);
}
stillLinked.parentNode.removeChild(stillLinked);
}
hideLinkPanel();
});
linkCancel.addEventListener('click', hideLinkPanel);
addButton(icon('M7 6h10v2H7zM9 10h6v2H9zM10 14h4v2h-4z'), 'Tiefgestellt', 'subscript');
addButton(icon('M7 6h10v2H7zM9 10h6v2H9zM8 14h8v2H8z'), 'Hochgestellt', 'superscript');
addButton(icon('M7 4h4v4H7V4zm6 12h4v4h-4v-4zM7 10h10v2H7v-2z'), 'Einzug', 'indent');
addButton(icon('M13 4h4v4h-4V4zM7 16h4v4H7v-4zM7 10h10v2H7v-2z'), 'Ausruecken', 'outdent');
addButton(
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,
() => {
exec('removeFormat');
removeInlineFormatting();
}
);
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 createInlineModal = () => {
const overlay = doc.createElement('div');
overlay.style.display = 'none';
overlay.style.position = 'absolute';
overlay.style.inset = '0';
overlay.style.background = 'rgba(15,23,42,0.35)';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
overlay.style.zIndex = '30';
const panel = doc.createElement('div');
panel.style.display = 'flex';
panel.style.flexDirection = 'column';
panel.style.gap = '10px';
panel.style.padding = '12px';
panel.style.border = '1px solid #cbd5f5';
panel.style.borderRadius = '8px';
panel.style.background = '#ffffff';
panel.style.boxShadow = '0 6px 24px rgba(15,23,42,0.2)';
panel.style.minWidth = '320px';
panel.style.maxWidth = '640px';
panel.style.maxHeight = '80vh';
panel.style.overflow = 'auto';
const titleEl = doc.createElement('div');
titleEl.style.fontWeight = '600';
titleEl.style.fontSize = '14px';
const bodyEl = doc.createElement('div');
panel.appendChild(titleEl);
panel.appendChild(bodyEl);
overlay.appendChild(panel);
container.appendChild(overlay);
let closeCb = null;
return {
el: overlay,
setTitle(text) { titleEl.textContent = text || ''; },
setContent(node) {
bodyEl.innerHTML = '';
if (node) bodyEl.appendChild(node);
},
open() { overlay.style.display = 'flex'; },
close() {
overlay.style.display = 'none';
if (typeof closeCb === 'function') closeCb();
},
onceClose(cb) { closeCb = cb; },
};
};
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
: [
{ label: 'Arial', value: 'Arial, sans-serif' },
{ label: 'Calibri', value: 'Calibri, sans-serif' },
{ label: 'Cambria', value: 'Cambria, serif' },
{ label: 'Georgia', value: 'Georgia, serif' },
{ label: 'Tahoma', value: 'Tahoma, sans-serif' },
{ label: 'Times New Roman', value: 'Times New Roman, serif' },
{ label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' },
{ label: 'Verdana', value: 'Verdana, sans-serif' },
];
addButton(
'{}Placeholder',
'Placeholder einfuegen',
null,
null,
() => {
saveSelection();
const api = window.BridgeBlocksPlaceholder;
if (!api || typeof api.openModal !== 'function') return;
const inlineModal = createInlineModal();
api.openModal(editor, null, {
modal: inlineModal,
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;
const range = getSelectionRange();
if (range && !range.collapsed) {
applyInlineStyle('fontFamily', value);
return;
}
pendingComponentStyle.fontFamily = value;
applyComponentStyle({ fontFamily: value }, { preview: true });
}, toolbarSecondary);
if (existingStyle && existingStyle.fontFamily && fontSelect) {
fontSelect.value = existingStyle.fontFamily;
}
addSelect([
{ label: 'Groesse', value: '' },
{ label: '10px', value: '10' },
{ label: '12px', value: '12' },
{ label: '14px', value: '14' },
{ label: '16px', value: '16' },
{ label: '18px', value: '18' },
{ label: '24px', value: '24' },
{ label: '32px', value: '32' },
{ label: 'Custom…', value: '__custom__' },
], 'Schriftgroesse', (value) => {
if (value === '__custom__') {
const raw = prompt('Schriftgroesse in px', '14');
const num = Number(raw || 0);
if (!Number.isFinite(num) || num <= 0) return;
const range = getSelectionRange();
if (range && !range.collapsed) {
applyInlineStyle('fontSize', `${num}px`);
} else {
pendingComponentStyle.fontSize = `${num}px`;
applyComponentStyle({ fontSize: `${num}px` }, { preview: true });
}
return;
}
const range = getSelectionRange();
if (range && !range.collapsed) {
applyInlineStyle('fontSize', `${value}px`);
return;
}
pendingComponentStyle.fontSize = `${value}px`;
applyComponentStyle({ fontSize: `${value}px` }, { preview: true });
}, toolbarSecondary);
}
const injectedStyle = doc.createElement('style');
injectedStyle.setAttribute('data-bridge-rte-style', '1');
const fontCss = (this.B && typeof this.B.RTE_FONT_FACE_CSS === 'string' && this.B.RTE_FONT_FACE_CSS.trim())
? this.B.RTE_FONT_FACE_CSS.trim()
: '';
let editorCss = '';
try {
editorCss = editor && typeof editor.getCss === 'function' ? String(editor.getCss() || '') : '';
} catch {}
const frameCss = this.collectFrameCss(editor);
const rteUiCss = `
.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;
font-weight: 600;
color: #0f172a;
background: #f8fafc;
}
.bridge-rte-btn--tool {
font-size: 16px;
min-height: 40px;
min-width: 40px;
}
.bridge-rte-btn--tool svg { width: 18px; height: 18px; }
.bridge-rte-select {
font-size: 15px;
min-height: 40px;
}
.bridge-rte-btn:hover { background: #e2e8f0; }
.bridge-rte-btn:active { transform: translateY(1px); }
.bridge-rte-actions .bridge-rte-btn { min-width: 88px; }
`.trim();
injectedStyle.textContent = [fontCss, editorCss, frameCss, rteUiCss].filter(Boolean).join('\n');
if (injectedStyle.textContent) {
container.appendChild(injectedStyle);
}
container.appendChild(toolbar);
container.appendChild(toolbarSecondary);
container.appendChild(content);
content.addEventListener('keyup', saveSelection);
content.addEventListener('mouseup', saveSelection);
content.addEventListener('focus', saveSelection);
const actions = doc.createElement('div');
actions.style.display = 'flex';
actions.style.justifyContent = 'flex-end';
actions.style.gap = '8px';
actions.className = 'bridge-rte-actions';
actions.setAttribute('data-bridge-rte', 'actions');
const cancelBtn = doc.createElement('button');
cancelBtn.type = 'button';
cancelBtn.textContent = 'Abbrechen';
cancelBtn.className = 'bridge-rte-btn bridge-rte-btn-cancel';
cancelBtn.style.padding = '6px 12px';
cancelBtn.style.border = '1px solid #cbd5f5';
cancelBtn.style.borderRadius = '4px';
cancelBtn.style.background = '#f8fafc';
cancelBtn.style.cursor = 'pointer';
cancelBtn.addEventListener('click', closeModal);
const saveBtn = doc.createElement('button');
saveBtn.type = 'button';
saveBtn.textContent = 'Speichern';
saveBtn.className = 'bridge-rte-btn bridge-rte-btn-save';
saveBtn.style.padding = '6px 12px';
saveBtn.style.border = '1px solid #0ea5e9';
saveBtn.style.borderRadius = '4px';
saveBtn.style.background = '#0ea5e9';
saveBtn.style.color = '#ffffff';
saveBtn.style.cursor = 'pointer';
saveBtn.addEventListener('click', () => {
const rawHtml = content.innerHTML || '';
const html = this.sanitizeInlineHtml(rawHtml, content.textContent || '');
this.lastContent.set(component, html);
try {
this.restoreUntil.set(component, Date.now() + this.restoreWindowMs);
} catch {}
this.logConsoleSnapshot(editor, component, 'before-save');
const forceApply = () => {
if (Object.keys(pendingComponentStyle).length) {
applyComponentStyle(pendingComponentStyle);
}
this.applyContentToComponent(editor, component, html);
this.logConsoleSnapshot(editor, component, 'after-save');
};
forceApply();
setTimeout(forceApply, 0);
setTimeout(forceApply, 50);
if (this.B && this.B.DEBUG_RTE) {
try {
const gjsHtml = editor && typeof editor.getHtml === 'function' ? editor.getHtml() : '';
const serHtml = BridgeRTE.serializeHtml(editor);
console.group('[RTE DEBUG] save snapshot');
console.log('component id', component && (component.getId ? component.getId() : component.get && component.get('id')));
console.log('model content', component && component.get ? component.get('content') : '');
console.log('view html', component?.view?.el?.innerHTML || '');
console.log('editor.getHtml len', String(gjsHtml || '').length);
console.log('serializeHtml len', String(serHtml || '').length);
console.log('editor.getHtml', gjsHtml);
console.log('serializeHtml', serHtml);
console.groupEnd();
const modelContent = String(component && component.get ? component.get('content') : '');
const viewHtml = String(component?.view?.el?.innerHTML || '');
const visibleText = String(component?.view?.el?.textContent || '');
const summary = {
source: 'rte',
modelId: component && (component.getId ? component.getId() : component.get && component.get('id')),
expectedFromRte: String(html || '').slice(0, 500),
actualModelContent: modelContent.slice(0, 500),
visibleText: visibleText.slice(0, 500),
viewHtml: viewHtml.slice(0, 500),
viewHtmlLen: viewHtml.length,
modelContentLen: modelContent.length,
visibleTextLen: visibleText.length,
editorHtmlLen: String(gjsHtml || '').length,
serializeHtmlLen: String(serHtml || '').length,
};
console.warn('[EDIT SUMMARY]', JSON.stringify(summary));
} catch {}
}
closeModal();
// RTE-Reaktivierung kann den DOM-Stand überschreiben; danach nochmal anwenden.
setTimeout(() => this.applyContentToComponent(editor, component, html), 0);
setTimeout(() => this.applyContentToComponent(editor, component, html), 50);
});
if (typeof ui.buildFooter === 'function') {
try { ui.buildFooter({ ...ctx, actions, cancelBtn, saveBtn }); } catch {}
}
if (!ui.overrideFooter) {
actions.appendChild(cancelBtn);
actions.appendChild(saveBtn);
}
container.appendChild(actions);
modal.setTitle('Richtext Editor');
modal.setContent(container);
const mdl = modal.getModel && modal.getModel();
if (mdl && mdl.set) {
mdl.set('closeOnEsc', false);
mdl.set('closeOnClick', false);
}
if (modal.el) {
modal.el.classList.add('bridge-rte-modal');
const closeBtn = modal.el.querySelector('.gjs-mdl-btn-close');
if (closeBtn) closeBtn.style.display = 'none';
const backdrop = modal.el.querySelector('.gjs-mdl-dialog');
if (backdrop) {
backdrop.addEventListener('click', (evt) => {
evt.stopPropagation();
});
}
}
const styleEl = document.getElementById('bridge-rte-modal-style') || document.createElement('style');
styleEl.id = 'bridge-rte-modal-style';
styleEl.textContent = [
'.bridge-rte-modal .gjs-mdl-btn-close{display:none!important;}',
'.bridge-rte-modal .gjs-mdl-dialog{max-height:90vh;overflow:hidden;}',
'.bridge-rte-container{max-height:80vh;}',
'.bridge-rte-content{max-height:55vh;overflow:auto;}',
].join('');
if (!styleEl.parentNode) {
document.head.appendChild(styleEl);
}
if (typeof ui.afterOpen === 'function') {
try { ui.afterOpen(ctx); } catch {}
}
modal.open();
}
ensureTextToolbarButton(component) {
if (!this.isTextLike(component) || !component.get) return;
const toolbar = component.get('toolbar') || [];
if (toolbar.some((item) => item && item.command === 'bridge-open-richtext')) return;
toolbar.push({
label: '',
attributes: { title: 'Richtext bearbeiten' },
command: 'bridge-open-richtext',
});
component.set('toolbar', toolbar);
}
setupEditor(editor) {
if (!editor) return;
this.editor = editor;
if (editor.Commands && editor.Commands.add) {
editor.Commands.add('bridge-open-richtext', {
run: (ed, sender, opts = {}) => {
if (sender && sender.set) sender.set('active', 0);
const component = opts.component || ed.getSelected();
this.openRichTextModal(ed, component);
},
});
}
const cfg = editor.getConfig ? editor.getConfig() : {};
cfg.richTextEditor = cfg.richTextEditor || {};
const actions = Array.isArray(cfg.richTextEditor.actions)
? cfg.richTextEditor.actions.slice()
: ['bold', 'italic', 'underline', 'strikethrough', 'link'];
if (!actions.includes('bridge-open-richtext')) actions.push('bridge-open-richtext');
cfg.richTextEditor.actions = actions;
const restoreIfCollapsed = (model) => {
if (!model || this.restoring.has(model)) return;
const restoreDeadline = this.restoreUntil.get(model);
if (!restoreDeadline || Date.now() > restoreDeadline) return;
const last = this.lastContent.get(model);
if (!last || !model.get) return;
const viewEl = model.view && model.view.el;
if (viewEl && (viewEl.isContentEditable || viewEl.getAttribute('contenteditable') === 'true')) return;
const viewHtml = String(viewEl && viewEl.innerHTML || '').trim();
if (viewHtml) return;
const current = String(model.get('content') || '').trim();
if (!current || current === '' || current === '
' || current === '
') {
this.restoring.add(model);
this.logConsoleSnapshot(editor, model, 'restore-start');
this.applyContentToComponent(editor, model, last);
this.logConsoleSnapshot(editor, model, 'restore-end');
this.restoring.delete(model);
}
};
editor.on('component:update', (model) => restoreIfCollapsed(model));
editor.on('component:input', (model) => restoreIfCollapsed(model));
editor.on('component:selected', (model) => this.ensureTextToolbarButton(model));
editor.on('component:add', (model) => this.ensureTextToolbarButton(model));
editor.on('component:dblclick', (model) => {
if (this.isTextLike(model)) this.openRichTextModal(editor, model);
});
}
mount(editor) {
this.setupEditor(editor);
}
}
const setupRichTextEditor = (editor) => {
if (!editor) return;
let instance = editorInstances.get(editor);
if (!instance) {
instance = new BridgeRTE(window.BridgeParts || (window.BridgeParts = {}));
editorInstances.set(editor, instance);
}
instance.mount(editor);
};
const B = window.BridgeParts || (window.BridgeParts = {});
B.BridgeRTE = BridgeRTE;
B.setupRichTextEditor = setupRichTextEditor;
})();