In the following structure:
<div contenteditable="true">
<span class="some-class">Hello</span>
World <!-- no span here -->
<span class="some-other-class">and good bye!</span>
</div>
Is it possible to remove selected span elements, but not the text node, without interrupting the cursor position in the contenteditable div?
So even if the cursor is inside the span, keep the same text content, but just remove the span elements around it
For example if the styling of some-class is red text color and I put the cursor somewhere inside the word "Hello", then I want the styling to be gone and remove the wrapping span
Here ya go!
<div contenteditable="true">
<span class="some-class" style="color: red">Hello</span>
<span class="some-class" style="color: blue">world</span>
<span class="some-other-class" style="color: orange">and good bye!</span>
<span style="color: green">test</span>
</div>
<script>
const div = document.querySelector('div');
const getSelection = () => {
let offset = 0;
const selection = window.getSelection();
const range = selection.getRangeAt(0);
let start = range.startOffset;
let end = range.endOffset;
if (selection.baseNode.parentNode.hasChildNodes()) {
for (const node of selection.baseNode.parentNode.childNodes) {
const cnode = node;
if (cnode.nodeType == document.TEXT_NODE && !(offset + cnode.length > start)) {
offset = offset + cnode.length;
}
if (cnode.nodeType == document.ELEMENT_NODE && !(offset + cnode.textContent.length > start)) {
offset = offset + cnode.textContent.length;
}
}
}
start = start + offset;
end = end + offset;
return { start, end };
};
const removeSpan = ({ target, currentTarget: { selectionStart } }) => {
// Get our selection points for within our span element
const { start, end } = getSelection();
// Only run this logic for span elements
if (target.tagName === 'SPAN') {
// Create new text node from our span's innerText
const text = document.createTextNode(target.innerText + ' ');
// Replace span with just the text
target.replaceWith(text);
// Create a range and set selection range back to what it was to not affect cursor
const range = document.createRange();
range.setStart(text, start);
range.setEnd(text, end);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
};
div.addEventListener('click', removeSpan);
</script>