This commit is contained in:
2025-12-10 22:37:59 +01:00
parent f9454605e1
commit e5fed53ac3
2 changed files with 132 additions and 43 deletions

View File

@@ -24,6 +24,7 @@ const TARGET_CAT_ID = 'placeholders';
const PLACEHOLDER_COMPONENT = 'placeholder-block';
const ALL_PLACEHOLDER_BLOCK_IDS = [];
const INLINE_PLACEHOLDER_CLASS = 'bridge-placeholder-inline';
const PLACEHOLDER_MARKER_ATTR = 'data-placeholder-marker';
const placeholderSchemaStore = {
promise: null,
@@ -220,31 +221,56 @@ const buildPlaceholderHTML = (payload) => {
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 insertCaretMarker = (rteInstance, markerId) => {
if (!markerId) return false;
const markerHtml = `<span ${PLACEHOLDER_MARKER_ATTR}="${markerId}"></span>`;
if (insertPlaceholderIntoSelection(rteInstance, markerHtml)) {
return true;
}
if (rteInstance && typeof rteInstance.insertHTML === 'function') {
rteInstance.insertHTML(markerHtml);
return true;
}
if (typeof document !== 'undefined' && document.execCommand) {
document.execCommand('insertHTML', false, markerHtml);
return true;
}
return false;
};
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');
const removeMarkerFromComponent = (component, markerId, editor) => {
if (!component || !markerId) return false;
const viewEl = component.view && component.view.el;
if (!viewEl) return false;
const selector = `[${PLACEHOLDER_MARKER_ATTR}="${markerId}"]`;
const marker = viewEl.querySelector(selector);
if (!marker || !marker.parentNode) return false;
marker.parentNode.removeChild(marker);
reparseTextComponent(component, editor);
return true;
};
const replaceMarkerWithPlaceholder = (component, markerId, payload, editor) => {
if (!component || !markerId) return false;
const viewEl = component.view && component.view.el;
if (!viewEl) return false;
const marker = viewEl.querySelector(`[${PLACEHOLDER_MARKER_ATTR}="${markerId}"]`);
if (!marker) return false;
if (typeof document === 'undefined') return false;
const temp = document.createElement('div');
temp.innerHTML = buildPlaceholderHTML(payload);
const placeholderNode = temp.firstElementChild || temp.firstChild;
if (!placeholderNode) return false;
if (typeof marker.replaceWith === 'function') {
marker.replaceWith(placeholderNode);
} else if (marker.parentNode) {
marker.parentNode.insertBefore(placeholderNode, marker);
marker.parentNode.removeChild(marker);
} else {
return false;
}
reparseTextComponent(component, editor);
return true;
};
const openPlaceholderModal = (editor, component, opts = {}) => {
@@ -361,6 +387,15 @@ const buildPlaceholderHTML = (payload) => {
container.appendChild(form);
let tablesCache = placeholderSchemaStore.tables || [];
if (tablesCache.length) {
populateTables(tablesCache);
} else if (placeholderSchemaStore.promise) {
placeholderSchemaStore.promise.then(populateTables).catch(() => populateTables([]));
} else if (placeholderSchemaStore.status !== false) {
fetchPlaceholderSchema()
.then(populateTables)
.catch(() => populateTables([]));
}
const toggleSections = () => {
const type = typeSelect.value || 'custom';
@@ -416,6 +451,16 @@ const buildPlaceholderHTML = (payload) => {
});
};
if (tablesCache.length) {
populateTables(tablesCache);
} else if (placeholderSchemaStore.promise) {
placeholderSchemaStore.promise.then(populateTables).catch(() => populateTables([]));
} else if (placeholderSchemaStore.status !== false) {
fetchPlaceholderSchema()
.then(populateTables)
.catch(() => populateTables([]));
}
tableSelect.addEventListener('change', () => {
populateColumns(tableSelect.value);
});
@@ -550,38 +595,37 @@ const buildPlaceholderHTML = (payload) => {
log('PLACEHOLDER INFO', 'Bitte zuerst ein Text-Element auswählen.', '#888');
return;
}
const selectionSnapshot = captureRteSelection(rteInstance);
const markerId = `bridge-placeholder-marker-${Date.now()}-${Math.random().toString(36).slice(2)}`;
const markerInserted = insertCaretMarker(rteInstance, markerId);
if (!markerInserted) {
log('PLACEHOLDER ERROR', 'Marker konnte nicht gesetzt werden. Bitte erneut versuchen.', 'red', 'error');
return;
}
setTimeout(() => reparseTextComponent(target, editor), 0);
const cleanupMarker = () => removeMarkerFromComponent(target, markerId, editor);
openPlaceholderModal(editor, null, {
onCancel: () => {
if (selectionSnapshot) {
restoreRteSelection(selectionSnapshot);
}
cleanupMarker();
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)) {
inserted = true;
} else if (rteInstance && typeof rteInstance.insertHTML === 'function') {
rteInstance.insertHTML(html);
inserted = true;
} else if (typeof document !== 'undefined' && document.execCommand) {
document.execCommand('insertHTML', false, html);
inserted = true;
} else {
log('PLACEHOLDER ERROR', 'Placeholder konnte nicht eingefügt werden (kein RTE).', 'red', 'error');
return false;
const replaced = replaceMarkerWithPlaceholder(target, markerId, payload, editor);
if (!replaced) {
cleanupMarker();
const viewEl = target.view && target.view.el;
if (viewEl) {
viewEl.insertAdjacentHTML('beforeend', buildPlaceholderHTML(payload));
reparseTextComponent(target, editor);
} else {
log('PLACEHOLDER ERROR', 'Placeholder konnte nicht eingefügt werden (kein Ziel).', 'red', 'error');
return false;
}
}
if (rteInstance && typeof rteInstance.focus === 'function') {
rteInstance.focus();
setTimeout(() => rteInstance.focus(), 0);
}
setTimeout(() => reparseTextComponent(target, editor), 0);
return true;
}
});
@@ -940,6 +984,9 @@ const buildPlaceholderHTML = (payload) => {
const bm = editor.BlockManager;
ensurePlaceholderComponent(editor);
ensureTextSupportsPlaceholders(editor);
if (!placeholderSchemaStore.promise && placeholderSchemaStore.status !== false) {
fetchPlaceholderSchema().catch(() => {});
}
if (!editor.Commands.get('bridge-placeholder:edit')) {
editor.Commands.add('bridge-placeholder:edit', {