JSFiddle - React, Tailwind, and code Playground

by Ben Gillbanks

HTML

<pre class="editor" contenteditable="true" spellcheck="false"></pre>

CSS

.editor {
  font-family: 'Roboto Mono', monospace;
  font-size: 12px;
  outline: none;
  overflow-y: auto;
  padding-left: 48px;
  counter-reset: line;
}

.editor div {
  display: block;
  position: relative;
  white-space: pre-wrap;
}

.editor br::before {
  content: counter(line);
  counter-increment: line;
  position: absolute;
  right: calc(100% + 16px);
  opacity: 0.5;
}

JavaScript

// Syntax highlight for JS
const js = el => {
    const s = el.innerText
      .replace(/(\/\/.*)/g, '<em>$1</em>')
      .replace(
        /\b(new|if|else|do|while|switch|for|in|of|continue|break|return|typeof|function|var|const|let|\.length|\.\w+)(?=[^\w])/g,
        '<strong>$1</strong>',
      )
      .replace(/(".*?"|'.*?'|`.*?`)/g, '<strong><em>$1</em></strong>')
      .replace(/\b(\d+)/g, '<em><strong>$1</strong></em>');
    el.innerHTML = s.split('\n').join('<br/>');
    console.log(el.innerHTML);
};

const editor = (el, highlight = js, tab = '    ') => {

  const caret = () => {
    const range = window.getSelection().getRangeAt(0);
    const prefix = range.cloneRange();
    prefix.selectNodeContents(el);
    prefix.setEnd(range.endContainer, range.endOffset);
    return prefix.toString().length;
  };

  const setCaret = (pos, parent = el) => {
    for (const node of parent.childNodes) {
      if (node.nodeType == Node.TEXT_NODE) {
        if (node.length >= pos) {
          const range = document.createRange();
          const sel = window.getSelection();
          range.setStart(node, pos);
          range.collapse(true);
          sel.removeAllRanges();
          sel.addRange(range);
          return -1;
        } else {
          pos = pos - node.length;
        }
      } else {
        pos = setCaret(pos, node);
        if (pos < 0) {
          return pos;
        }
      }
    }
    return pos;
  };

  highlight(el);

  el.addEventListener('keydown', e => {
    if (e.which === 9) {
      const pos = caret() + tab.length;
      const range = window.getSelection().getRangeAt(0);
      range.deleteContents();
      range.insertNode(document.createTextNode(tab));
      highlight(el);
      setCaret(pos);
      e.preventDefault();
    }
    console.log(e.which);
  });

  el.addEventListener('keyup', e => {
    if (e.keyCode >= 0x30 || e.keyCode == 0x20) {
      const pos = caret();
      highlight(el);
      setCaret(pos);
    }
  });
};
 
// Turn div...