JSFiddle - React, Tailwind, and code Playground

by RichieHindle

HTML

<div contenteditable='true' id='e'><div>Content</div></div>

CSS

#e {
    font-family: "Courier New";
}

JavaScript

function getCursorPosition($elt) {
    // Remember the orignal range, so we can put it back.
    var selection = window.getSelection();
    var origRange = selection.getRangeAt(0);
    
    // Create a range that we can manipulate.
    var r = origRange.cloneRange();
    selection.removeAllRanges();
    selection.addRange(r);
    
    // Where a line starts with spaces, Firefox uses multiple adjacent text
    // nodes to represent that, so we need to add their widths to r.endOffset.
    var prefixWidth = 0;
    if (r.endContainer.nodeType == document.TEXT_NODE) {
        var node = r.endContainer.previousSibling;
        while (node && node.nodeType == document.TEXT_NODE) {
            prefixWidth += node.length;
            node = node.previousSibling;
        }
    }
    
    // We now know the column number.    
    var column = r.endOffset + prefixWidth;
    
    // To get the row number, we must get the HTML of the content as far as
    // the cursor, then count the line breaks.
    r.setStart($elt[0].firstChild, 0);    // Start of first <div>.
    var fragment = r.cloneContents();     // No innerHTML on fragments,
    var tmp = $("<div></div>");           //  hence we need to poke it into.
    tmp.html(fragment);                   //  an element to get its HTML.
    var s = tmp[0].innerHTML.toLowerCase();

    // We've finished with our range - put back the original.    
    selection.removeAllRanges();
    selection.addRange(origRange);
    
    // Count the newlines in the HTML.
    s = s.replace(/&[a-z0-9]+;/g, "x");   // Entities are one character.
    s = s.replace(/<\/[^>]+>/g, "");      // Remove closing tags.
    s = s.replace(/<div[^>]*>\s*<br[^>]*>/g, "\n"); // <div><br> is one newline.
    s = s.replace(/<br[^>]*>/g, "\n");    // <br> means newline.
    s = s.replace(/<div\b[^>]*>/g, "\n"); // <div> means newline.
    s = s.replace(/<[^>]+>/g, "");        // Remove all other tags.
    s = s.replace(/^\n/, "");             // The opening <div>...