diff --git a/public/assets/js/bridge/blocks-placeholder.js b/public/assets/js/bridge/blocks-placeholder.js index 4263b24..fa1f6a9 100644 --- a/public/assets/js/bridge/blocks-placeholder.js +++ b/public/assets/js/bridge/blocks-placeholder.js @@ -237,11 +237,70 @@ const refreshPlaceholderComponent = (component) => { return true; }; - const insertCaretMarker = () => false; + const getRteDocument = (rteInstance) => { + return rteInstance && rteInstance.doc + ? rteInstance.doc + : (rteInstance && rteInstance.el && rteInstance.el.ownerDocument) || document; + }; - const removeMarkerFromComponent = () => false; + const insertCaretMarker = (rteInstance) => { + const doc = getRteDocument(rteInstance); + if (!doc || !doc.getSelection) return null; + const sel = doc.getSelection(); + if (!sel || !sel.rangeCount) return null; + const range = sel.getRangeAt(0); + if (!range || !range.collapsed) return null; - const replaceMarkerWithPlaceholder = () => false; + const markerId = `bridge-placeholder-marker-${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`; + const marker = doc.createElement('span'); + marker.setAttribute(PLACEHOLDER_MARKER_ATTR, markerId); + marker.style.display = 'inline-block'; + marker.style.width = '0'; + marker.style.height = '0'; + marker.style.padding = '0'; + marker.style.margin = '0'; + marker.style.lineHeight = '0'; + marker.style.overflow = 'hidden'; + + range.insertNode(marker); + range.setStartAfter(marker); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + + return { doc, id: markerId }; + }; + + const removeMarkerFromComponent = (markerInfo, rteInstance) => { + if (!markerInfo || !markerInfo.id) return false; + const doc = markerInfo.doc || getRteDocument(rteInstance); + if (!doc || !doc.querySelector) return false; + const marker = doc.querySelector(`[${PLACEHOLDER_MARKER_ATTR}="${markerInfo.id}"]`); + if (!marker || !marker.parentNode) return false; + marker.parentNode.removeChild(marker); + return true; + }; + + const replaceMarkerWithPlaceholder = (markerInfo, rteInstance, text) => { + if (!markerInfo || !markerInfo.id) return false; + const doc = markerInfo.doc || getRteDocument(rteInstance); + if (!doc || !doc.querySelector) return false; + const marker = doc.querySelector(`[${PLACEHOLDER_MARKER_ATTR}="${markerInfo.id}"]`); + if (!marker || !marker.parentNode) return false; + + const textNode = doc.createTextNode(text); + marker.parentNode.replaceChild(textNode, marker); + + const sel = doc.getSelection && doc.getSelection(); + if (sel && doc.createRange) { + const range = doc.createRange(); + range.setStartAfter(textNode); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + } + return true; + }; const openPlaceholderModal = (editor, component, opts = {}) => { if (!editor) return; @@ -561,19 +620,32 @@ const refreshPlaceholderComponent = (component) => { log('PLACEHOLDER ERROR', 'Keine Text-Selektion gefunden. Bitte Cursor setzen und erneut versuchen.', 'red', 'error'); return; } + const markerInfo = insertCaretMarker(rteInstance); const restoreSelection = () => restoreRteSelection(selectionSnapshot); openPlaceholderModal(editor, null, { onCancel: () => { + if (markerInfo) { + removeMarkerFromComponent(markerInfo, rteInstance); + } if (rteInstance && typeof rteInstance.focus === 'function') { setTimeout(() => rteInstance.focus(), 0); } }, onSubmit: (payload) => { + const text = buildPlaceholderText(payload); + if (markerInfo && replaceMarkerWithPlaceholder(markerInfo, rteInstance, text)) { + if (rteInstance && typeof rteInstance.focus === 'function') { + setTimeout(() => rteInstance.focus(), 0); + } + return true; + } + if (markerInfo) { + removeMarkerFromComponent(markerInfo, rteInstance); + } if (!restoreSelection()) { log('PLACEHOLDER ERROR', 'Cursor-Position konnte nicht wiederhergestellt werden.', 'red', 'error'); return false; } - const text = buildPlaceholderText(payload); if (!insertTextIntoSelection(rteInstance, text)) { log('PLACEHOLDER ERROR', 'Placeholder konnte nicht eingefügt werden.', 'red', 'error'); return false;