Develoraptor Blog

Login

Creating a replacement for execCommand

I use a contenteditable custom form element to author my blog posts as HTML. If I want to make text bold or italic or add a link, I can reach for trusty old document.execCommand. But what if I want to mark up code? There's no way with execCommand to format with custom inline elements, and as usual I couldn't find an existing library to do so, so I've chosen to make my own.

It turns out the Range class built into modern browsers makes a basic implementation actually relatively straight forward:

  function wrapInline(tag){
    // Get the current selecton
    const sel = window.getSelection();

    // A selection could have multiple ranges
    for(let i=0; i<sel.rangeCount; i ){
      const range = sel.getRangeAt(i);
      // Remove the range from its parent and return it as a fragment
      // (this takes into account any elements that need to be split)
      const frag = range.extractContents();
      // Remove any <code> tags from inside the fragment if they're present
      for(const node of frag.querySelectorAll(tag)) {
        node.replaceWith(...node.childNodes)
      }
      // Create the new <code> element, give it children, and insert it into the document
      const el = document.createElement(tag);
      el.appendChild(frag);
      range.insertNode(el);
      range.selectNodeContents(el);
    }
  // At this point I also need to dispatch an InputEvent to the html-area for the changes to register
  }


Et voila! A basic formatting function. I haven't released this as a library yet since (a) I'm not sure how I want to package/distribute browser-only modules at the moment and (b) there are some edge cases I still want to tackle, such as how to unformat a selection, and whether/how to join neighbouring elements if they're of the same tag name. But when that's complete hopefully it'll be a nice replacement for much of the functionality covered by the deprecated execCommand.