From f9454605e1685faf4c21e38161b3c3d4c1594035 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Wed, 10 Dec 2025 22:22:00 +0100 Subject: [PATCH] asdasd --- public/assets/js/bridge/blocks-placeholder.js | 232 ++++++++++++++---- 1 file changed, 188 insertions(+), 44 deletions(-) diff --git a/public/assets/js/bridge/blocks-placeholder.js b/public/assets/js/bridge/blocks-placeholder.js index e50809b..181f970 100644 --- a/public/assets/js/bridge/blocks-placeholder.js +++ b/public/assets/js/bridge/blocks-placeholder.js @@ -20,16 +20,44 @@ if (window.__PLACEHOLDER_BLOCKS_LOADED) return; window.__PLACEHOLDER_BLOCKS_LOADED = true; - const TARGET_CAT_ID = 'placeholders'; - const PLACEHOLDER_COMPONENT = 'placeholder-block'; - const ALL_PLACEHOLDER_BLOCK_IDS = []; +const TARGET_CAT_ID = 'placeholders'; +const PLACEHOLDER_COMPONENT = 'placeholder-block'; +const ALL_PLACEHOLDER_BLOCK_IDS = []; +const INLINE_PLACEHOLDER_CLASS = 'bridge-placeholder-inline'; - const placeholderSchemaStore = { - promise: null, - tables: [], - status: null, - statusPromise: null, - }; +const placeholderSchemaStore = { + promise: null, + tables: [], + status: null, + statusPromise: null, +}; +let inlineStyleInjected = false; + +const ensureInlinePlaceholderStyles = () => { + if (inlineStyleInjected || typeof document === 'undefined') return; + inlineStyleInjected = true; + const style = document.createElement('style'); + style.id = 'bridge-placeholder-inline-style'; + style.textContent = ` + .${INLINE_PLACEHOLDER_CLASS}{ + display:inline-flex; + align-items:center; + gap:4px; + padding:2px 8px; + border:1px dashed #94a3b8; + border-radius:6px; + background:#f1f5f9; + font-family:monospace; + font-size:12px; + cursor:pointer; + user-select:none; + } + .${INLINE_PLACEHOLDER_CLASS}:hover{ + background:#e2e8f0; + } + `; + document.head && document.head.appendChild(style); +}; const createEl = (tag, props = {}, children = []) => { const el = document.createElement(tag); @@ -53,7 +81,7 @@ return el; }; - const applyInputStyles = (el) => { +const applyInputStyles = (el) => { if (!el) return el; el.style.width = '100%'; el.style.boxSizing = 'border-box'; @@ -65,7 +93,7 @@ return el; }; - const syncSelectOptions = (selectEl, options, selected) => { +const syncSelectOptions = (selectEl, options, selected) => { if (!selectEl) return; while (selectEl.firstChild) { selectEl.removeChild(selectEl.firstChild); @@ -80,11 +108,20 @@ if (selected !== undefined && selected !== null) { selectEl.value = selected; } - }; +}; - const refreshPlaceholderComponent = (component) => { - if (!component) return; - if (typeof component.updatePlaceholderState === 'function') { +const safeRemoveComponent = (component) => { + if (!component || typeof component.remove !== 'function') return; + try { + component.remove(); + } catch (err) { + log('PLACEHOLDER WARN', `Komponente konnte nicht entfernt werden: ${err && err.message ? err.message : err}`, '#b45309'); + } +}; + +const refreshPlaceholderComponent = (component) => { + if (!component) return; + if (typeof component.updatePlaceholderState === 'function') { try { component.updatePlaceholderState(); } catch (err) { @@ -111,12 +148,14 @@ return (payload.key || 'PLATZHALTER').toUpperCase(); }; - const buildPlaceholderHTML = (payload) => { - const type = payload.type === 'database' ? 'database' : 'custom'; - const attrs = [ - `data-gjs-type="${PLACEHOLDER_COMPONENT}"`, - `data-placeholder-type="${type}"`, - ]; +const buildPlaceholderHTML = (payload) => { + const type = payload.type === 'database' ? 'database' : 'custom'; + const attrs = [ + `data-gjs-type="${PLACEHOLDER_COMPONENT}"`, + `data-placeholder-type="${type}"`, + `contenteditable="false"`, + `class="${INLINE_PLACEHOLDER_CLASS}"`, + ]; if (type === 'database') { attrs.push(`data-placeholder-table="${payload.table || ''}"`); attrs.push(`data-placeholder-column="${payload.column || ''}"`); @@ -181,16 +220,58 @@ return true; }; + const captureRteSelection = (rteInstance) => { + const doc = rteInstance && rteInstance.doc + ? rteInstance.doc + : (rteInstance && rteInstance.el && rteInstance.el.ownerDocument) || document; + if (!doc || !doc.getSelection) return null; + const sel = doc.getSelection(); + if (!sel || !sel.rangeCount) return null; + return { + doc, + range: sel.getRangeAt(0).cloneRange() + }; + }; + + const restoreRteSelection = (snapshot) => { + if (!snapshot || !snapshot.doc || !snapshot.range) return false; + const sel = snapshot.doc.getSelection && snapshot.doc.getSelection(); + if (!sel) return false; + try { + sel.removeAllRanges(); + sel.addRange(snapshot.range); + return true; + } catch (err) { + log('PLACEHOLDER WARN', `Auswahl konnte nicht wiederhergestellt werden: ${err && err.message ? err.message : err}`, '#b45309'); + return false; + } + }; + const openPlaceholderModal = (editor, component, opts = {}) => { if (!editor) return; const modal = editor.Modal; if (!modal) return; + ensureInlinePlaceholderStyles(); if (component && (!component.is || !component.is(PLACEHOLDER_COMPONENT))) { log('PLACEHOLDER WARN', 'openPlaceholderModal wurde ohne gültige Placeholder-Komponente aufgerufen.', '#b45309'); return; } + let didSave = false; + let didCancel = false; + const fireCancel = (reason) => { + if (didSave || didCancel) return; + didCancel = true; + if (typeof opts.onCancel === 'function') { + try { + opts.onCancel({ reason, component, modal }); + } catch (err) { + log('PLACEHOLDER WARN', `onCancel Fehler: ${err && err.message ? err.message : err}`, '#b45309'); + } + } + }; + const attrs = component && component.getAttributes ? component.getAttributes() : (opts.initial || {}); const initialType = attrs['data-placeholder-type'] || 'custom'; const initialKey = attrs['data-placeholder-key'] || 'UEBERSCHRIFT'; @@ -348,6 +429,7 @@ cancelBtn.addEventListener('click', (e) => { e.preventDefault(); + fireCancel('cancel-button'); modal.close(); }); @@ -377,6 +459,7 @@ }); } refreshPlaceholderComponent(component); + component.__bridgePlaceholderNew = false; return true; }; @@ -422,6 +505,7 @@ return; } } + didSave = true; modal.close(); }); @@ -432,6 +516,20 @@ modal.setTitle('Placeholder konfigurieren'); modal.setContent(container); + if (typeof modal.onceClose === 'function') { + modal.onceClose(() => fireCancel('modal-close')); + } else if (modal.getModel && typeof modal.getModel === 'function') { + const mdl = modal.getModel(); + if (mdl && typeof mdl.on === 'function') { + const handler = () => { + if (!mdl.get('open')) { + mdl.off && mdl.off('change:open', handler); + fireCancel('modal-close'); + } + }; + mdl.on && mdl.on('change:open', handler); + } + } modal.open(); if (component && editor.getSelected && editor.getSelected() !== component) { editor.select && editor.select(component); @@ -452,8 +550,20 @@ log('PLACEHOLDER INFO', 'Bitte zuerst ein Text-Element auswählen.', '#888'); return; } + const selectionSnapshot = captureRteSelection(rteInstance); openPlaceholderModal(editor, null, { + onCancel: () => { + if (selectionSnapshot) { + restoreRteSelection(selectionSnapshot); + } + if (rteInstance && typeof rteInstance.focus === 'function') { + setTimeout(() => rteInstance.focus(), 0); + } + }, onSubmit: (payload) => { + if (selectionSnapshot) { + restoreRteSelection(selectionSnapshot); + } const html = buildPlaceholderHTML(payload); let inserted = false; if (insertPlaceholderIntoSelection(rteInstance, html)) { @@ -589,6 +699,8 @@ attributes: { 'data-placeholder-type': 'custom', 'data-placeholder-key': 'UEBERSCHRIFT', + 'contenteditable': 'false', + 'class': INLINE_PLACEHOLDER_CLASS, }, traits: [ { @@ -645,6 +757,7 @@ const ensurePlaceholderComponent = (editor) => { const domc = editor.DomComponents; if (domc.getType(PLACEHOLDER_COMPONENT)) return; + ensureInlinePlaceholderStyles(); const baseType = domc.getType('text') || domc.getType('default') || {}; const BaseModel = baseType.model || editor.DomComponents.Component; @@ -662,10 +775,12 @@ updatePlaceholderState() { const attrs = this.getAttributes(); - const type = attrs['data-placeholder-type'] || 'custom'; - if (type === 'database' && placeholderSchemaStore.tables.length === 0) { + if (!attrs['data-placeholder-type']) { this.addAttributes({ 'data-placeholder-type': 'custom' }); } + if (attrs['contenteditable'] !== 'false') { + this.addAttributes({ 'contenteditable': 'false' }); + } this.updateTraitVisibility(); this.updateSchemaTraits(); this.updateLabel(); @@ -711,9 +826,12 @@ if (columnTrait) { const attrs = this.getAttributes(); const tableName = (attrs['data-placeholder-table'] || '').toLowerCase(); - const table = tables.find(function (tbl) { return tbl.name.toLowerCase() === tableName; }); - const colOpts = table - ? table.columns.map(function (col) { return { id: col.name, label: col.name + ' (' + col.type + ')' }; }) + const table = tables.find(function (tbl) { + return (tbl.name || '').toLowerCase() === tableName; + }); + const columns = table && Array.isArray(table.columns) ? table.columns : []; + const colOpts = columns.length + ? columns.map(function (col) { return { id: col.name, label: col.name + (col.type ? ' (' + col.type + ')' : '') }; }) : [{ id: '', label: table ? 'Keine Felder' : 'Feld wählen', disabled: !table }]; setTraitOptions(columnTrait, colOpts); } @@ -745,7 +863,11 @@ render() { BaseView.prototype.render.apply(this, arguments); this.el.classList.add('placeholder-block'); - this.el.style.display = 'inline-block'; + this.el.classList.add(INLINE_PLACEHOLDER_CLASS); + this.el.setAttribute('contenteditable', 'false'); + this.el.style.display = 'inline-flex'; + this.el.style.alignItems = 'center'; + this.el.style.gap = '4px'; this.el.style.padding = '2px 8px'; this.el.style.border = '1px dashed #94a3b8'; this.el.style.borderRadius = '6px'; @@ -848,7 +970,14 @@ if (window.__GJS_IS_PARSING) return; if (cmp.__bridgePlaceholderPrompted) return; cmp.__bridgePlaceholderPrompted = true; - setTimeout(() => openPlaceholderModal(editor, cmp), 50); + cmp.__bridgePlaceholderNew = true; + setTimeout(() => openPlaceholderModal(editor, cmp, { + onCancel: () => { + if (cmp.__bridgePlaceholderNew) { + safeRemoveComponent(cmp); + } + } + }), 50); }); } @@ -865,25 +994,40 @@ content: `{{UEBERSCHRIFT}}` }); - fetchPlaceholderSchema() - .then(tables => { - if (!tables || !tables.length) { - log('PLACEHOLDER INFO', 'Keine Tabellen – DB Placeholder Block wird nicht angezeigt.', '#888'); - return; - } - const firstTable = tables[0] || {}; - const tableName = firstTable.name || 'tabelle'; - const columns = Array.isArray(firstTable.columns) ? firstTable.columns : []; - const firstColumn = columns.length ? columns[0].name : 'feld'; - const placeholderLabel = (tableName + '.' + firstColumn).toUpperCase(); - addOnce(bm, 'cust-placeholder-db', { - id: 'cust-placeholder-db', - label: '🗄️ Placeholder (DB)', - content: `{{${placeholderLabel}}}` + const ensureDbBlock = (tables) => { + const fallback = { table: 'tabelle', column: 'feld' }; + let tableName = fallback.table; + let columnName = fallback.column; + if (Array.isArray(tables) && tables.length) { + const tableWithColumns = tables.find(tbl => Array.isArray(tbl.columns) && tbl.columns.length) || tables[0]; + tableName = tableWithColumns && tableWithColumns.name ? tableWithColumns.name : fallback.table; + const firstColumn = tableWithColumns && Array.isArray(tableWithColumns.columns) && tableWithColumns.columns[0]; + columnName = firstColumn && firstColumn.name ? firstColumn.name : fallback.column; + } + const payload = { type: 'database', table: tableName, column: columnName }; + const content = buildPlaceholderHTML(payload); + const blockId = 'cust-placeholder-db'; + const label = '🗄️ Placeholder (DB)'; + const existing = typeof bm.get === 'function' ? bm.get(blockId) : null; + if (existing && typeof existing.set === 'function') { + existing.set('content', content); + existing.set('label', label); + } else { + addOnce(bm, blockId, { + id: blockId, + label, + content, }); + } + }; + + ensureDbBlock(); + fetchPlaceholderSchema() + .then((tables) => { + ensureDbBlock(tables); }) .catch(() => { - log('PLACEHOLDER WARN', 'DB Placeholder Block ausgeblendet (Schemafehler).', '#b45309'); + log('PLACEHOLDER WARN', 'DB Placeholder Schema konnte nicht geladen werden – Fallback bleibt aktiv.', '#b45309'); }); log('SUCCESS', `Placeholder-Registrierung abgeschlossen. ${ALL_PLACEHOLDER_BLOCK_IDS.length} Blöcke erstellt.`, '#008000', 'info', true);