diff --git a/config/current.ver b/config/current.ver index a54e136..40e7d1a 100644 --- a/config/current.ver +++ b/config/current.ver @@ -1 +1 @@ -1.1.69 \ No newline at end of file +1.1.70 \ 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 a0990a6..5f8dafc 100644 --- a/public/assets/js/bridge/rte-editor.js +++ b/public/assets/js/bridge/rte-editor.js @@ -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) => ``; + const icon = (path) => ``; addButton( 'B', '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( - 'Tx', + 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( + '{}Placeholder', + '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);