DEV Community

Arnav Bansal
Arnav Bansal

Posted on

6 2

How do you split contenteditable text preserving html formatting? [solved]

I'm using contenteditable divs to represent paragraphs in my editor. I've implemented most of the usual editing features

Splitting paragraphs breaks when any HTML formatting is involved.

For example, adding a space and hitting enter results in

&
nbsp;

Of course, ' ' being a single character that represents a space.

Solution (Update)

Okay I figured it out.

innerHTML and innerText aren't useful for DOM manipulation.

However, the selection and ranges API is great for this. extractContents automatically splits elements without breaking markup.

function splitNode(selection, root) {
  let range = selection.getRangeAt(0);
  let {firstChild, lastChild} = root;

  let previousRange = document.createRange();
  previousRange.setStart(firstChild, 0);
  previousRange.setEnd(range.startContainer, range.startOffset);

  let nextRange = document.createRange();
  nextRange.setStart(range.endContainer, range.endOffset);
  nextRange.setEnd(lastChild, lastChild.length);
  return {
    previous: previousRange,
    current: range,
    next: nextRange,
  };
}

let ranges = splitNode(document.getSelection(), contentEditableDiv);
let nextFragment = ranges.next.extractContents();

Top comments (1)

Collapse
 
merrimanxyz profile image
merriman-xyz • Edited

Thanks for posting this.

Worked wonderfully until I tried splitting a content editable that ended in bold text.

Looked at the code and recognized that it could only handle cases where firstChild and lastChild are text nodes.

Made a few tweaks to fix:

  • previousRange.setStart(firstChild, 0); becomes previousRange.setStartBefore(firstChild);
  • nextRange.setEnd(lastChild, lastChild.length); becomes nextRange.setEndAfter(lastChild);
export function splitNode(selection, root) {
let range = selection.getRangeAt(0);
let {firstChild, lastChild} = root;
let previousRange = document.createRange();
previousRange.setStartBefore(firstChild);
previousRange.setEnd(range.startContainer, range.startOffset);
let nextRange = document.createRange();
nextRange.setStart(range.endContainer, range.endOffset);
nextRange.setEndAfter(lastChild);
return {
previous: previousRange,
current: range,
next: nextRange,
};
}
view raw split.js hosted with ❤ by GitHub

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay