Search code examples
javascripthtmlcontenteditable

Get sub contents of content editable innerHTML


I would like to split a contenteditable's contents into two pieces on keypress.

<div contenteditable="true">This is <b>my content</b></div>

I want to get the substring of this sentence (without HTML) from 0 to 10, which is:

This is my

Is there a way to do this in JS where it knows to close the tags that would be cut off? Or do I need to write a function to determine the node and close it manually?

I would like to return:

This is <b>my</b>

and

<b> content</b>

Here is the code to retrieve the left side of the caret:

traverse = ( node: Node, selectedNode: Node, offset, direction = 0, parent = true ) => {
    if ( node.nodeName === '#text' ) {
      if ( node.isEqualNode(  selectedNode ) ) {
        return [ node.textContent.substr( 0, offset  ), node.textContent.substr( offset  ) ];
      }
      return node.textContent;
    }
    if ( node.childNodes.length === 1 && node.childNodes[0].nodeType === 3 ) {
      if ( node.childNodes[0].isEqualNode(  selectedNode ) ) {
        const newNode1 = node.cloneNode();
        const newNode2 = node.cloneNode();
        newNode1.innerHTML = node.childNodes[0].textContent.substr( 0, offset  );
        newNode2.innerHTML = node.childNodes[0].textContent.substr( offset );
        return [ newNode1.outerHTML, newNode2.outerHTML ];
      }
      return node.outerHTML;
    }
    if ( node.childNodes.length > 1 ) {
      let content = '';
      let found = false;
      for ( let x = 0; node.childNodes[x] && !found; x++ ) {
        let resp = this.traverse(  node.childNodes[x], selectedNode, offset, direction, false );
        if ( Array.isArray( resp ) ) {
          content += resp[0];
          found = true;
        } else {
          content += resp;
        }
      }
      if ( !parent ) {
        const newNode = node.cloneNode();
        newNode.innerHTML = content;
        return newNode.outerHTML;
      } else {
        return content;
      }
    }
  }

Solution

  • Well, after a lot of trial and error, I figured it out. This function accepts an element or node, the selected node, and the offset of the selected node, and will return two strings of HTML split in half with properly closed tags.

    traverse = ( node: Node, selectedNode: Node, offset, parent = true ) => {
        let found = false;
        if ( node.nodeName === '#text' ) {
          if ( node.isEqualNode(  selectedNode ) ) {
            return [ node.textContent.substr( 0, offset  ), node.textContent.substr( offset  ) ];
          }
          return node.textContent;
        }
        if ( node.childNodes.length === 1 && node.childNodes[0].nodeType === 3 ) {
          if ( node.childNodes[0].isEqualNode(  selectedNode ) ) {
            const newNode1 = node.cloneNode();
            const newNode2 = node.cloneNode();
            newNode1.innerHTML = node.childNodes[0].textContent.substr( 0, offset  );
            newNode2.innerHTML = node.childNodes[0].textContent.substr( offset );
            return [ newNode1.outerHTML, newNode2.outerHTML ];
          }
          return node.outerHTML;
        }
        if ( node.childNodes.length > 1 ) {
          let content = ['', ''];
          for ( let x = 0; node.childNodes[x]; x++ ) {
            const resp = this.traverse(  node.childNodes[x], selectedNode, offset, false );
            if ( Array.isArray( resp ) ) {
              content[0] += resp[0];
              content[1] += resp[1];
              found = true;
            } else {
              if ( found ) {
                content[1] += resp;
              } else {
                content[0] += resp;
              }
            }
          }
          if ( !parent ) {
            const newNode1 = node.cloneNode();
            const newNode2 = node.cloneNode();
            newNode1.innerHTML = content[0];
            newNode2.innerHTML = content[1];
            if ( found ) {
              return [ newNode1.outerHTML, newNode2.outerHTML ];
            } else {
              return newNode1.outerHTML;
            }
          } else {
            if ( found ) {
              return content;
            } else {
              return content[0];
            }
          }
        }
      }