diff --git a/config/current.ver b/config/current.ver index d66ddbd..c3635d3 100644 --- a/config/current.ver +++ b/config/current.ver @@ -1 +1 @@ -1.1.54 \ No newline at end of file +1.1.55 \ No newline at end of file diff --git a/public/assets/js/bridge/rte-editor.js b/public/assets/js/bridge/rte-editor.js index 543d69d..f116717 100644 --- a/public/assets/js/bridge/rte-editor.js +++ b/public/assets/js/bridge/rte-editor.js @@ -502,6 +502,87 @@ 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; + } + } 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 fragment = range.extractContents(); + const unwrap = (node) => { + const parent = node.parentNode; + if (!parent) return; + while (node.firstChild) parent.insertBefore(node.firstChild, node); + parent.removeChild(node); + }; + const strip = (node) => { + if (!node) return; + if (node.nodeType === 1) { + const tag = node.tagName ? node.tagName.toUpperCase() : ''; + if (tags && tags.includes(tag)) { + const children = Array.from(node.childNodes); + children.forEach(strip); + unwrap(node); + return; + } + if (styleProp && node.style) { + if (typeof clearFn === 'function') { + clearFn(node.style); + } else { + node.style[styleProp] = ''; + } + const styleAttr = node.getAttribute('style'); + if (!styleAttr || !String(styleAttr).trim()) { + const children = Array.from(node.childNodes); + children.forEach(strip); + unwrap(node); + return; + } + } + Array.from(node.childNodes).forEach(strip); + } + }; + Array.from(fragment.childNodes).forEach(strip); + range.insertNode(fragment); + 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(); @@ -669,25 +750,52 @@ if (!ui.overrideToolbar) { const icon = (path) => ``; addButton( - icon('M6 4h5a3 3 0 0 1 0 6H6V4zm0 8h6a3 3 0 0 1 0 6H6v-6z'), + 'B', 'Fett', 'bold', null, - () => applyInlineStyle('fontWeight', '700') + () => 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( - icon('M10 4h8v2h-3l-4 12h3v2H6v-2h3l4-12h-3V4z'), + 'I', 'Kursiv', 'italic', null, - () => applyInlineStyle('fontStyle', 'italic') + () => toggleInlineStyle( + 'italic', + 'fontStyle', + 'italic', + ['I', 'EM'], + (val) => String(val || '').toLowerCase() === 'italic', + (style) => { style.fontStyle = ''; } + ) ); addButton( - icon('M5 4h14v2h-6v3h4a4 4 0 0 1 0 8H7v-2h10a2 2 0 0 0 0-4h-4V6H5V4z'), + 'U', 'Unterstrichen', 'underline', null, - () => applyInlineStyle('textDecoration', 'underline') + () => 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'); @@ -702,7 +810,7 @@ addButton(icon('M7 4h4v4H7V4zm6 12h4v4h-4v-4zM7 10h10v2H7v-2z'), 'Einzug', 'indent'); addButton(icon('M13 4h4v4h-4V4zM7 16h4v4H7v-4zM7 10h10v2H7v-2z'), 'Ausruecken', 'outdent'); addButton( - icon('M5 5h14v2H5zM5 9h10v2H5zM5 13h14v2H5zM5 17h10v2H5z'), + 'Tx', 'Formatierung entfernen', null, null, @@ -776,7 +884,21 @@ editorCss = editor && typeof editor.getCss === 'function' ? String(editor.getCss() || '') : ''; } catch {} const frameCss = this.collectFrameCss(editor); - injectedStyle.textContent = `${fontCss}\n${editorCss}\n${frameCss}`.trim(); + const rteUiCss = ` + .bridge-rte-toolbar { gap: 8px; } + .bridge-rte-btn { + font-size: 14px; + line-height: 1; + min-height: 30px; + min-width: 30px; + color: #0f172a; + background: #f1f5f9; + } + .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); }