Digit linkifier

by dipish

HTML

<h1>Digit linkifier</h1>
<p><i>Hacking div contenteditable with caret positioning madness...</i></p>

<p class="toolbar">
    <button id="btn-0">0</button>
    <button id="btn-1">1</button>
    <button id="btn-2">2</button>
    <button id="btn-3">3</button>
    <button id="btn-4">4</button>
    <button id="btn-5">5</button>
    <button id="btn-6">6</button>
    <button id="btn-7">7</button>
    <button id="btn-8">8</button>
    <button id="btn-9">9</button>
</p>
<div id="myeditor" class="editor-wrapper">
<div class="editor" contenteditable>
    1 banana<br />
    2 banana<br />
    3 banana<br />
    four<br />
    4 make a bunch<br />
    and so do many more<br />
</div>
    
    
<div class="editor-ghost" contenteditable>
    1 banana<br />
    2 banana<br />
    3 banana<br />
    four<br />
    4 make a bunch<br />
    and so do many more<br />
</div>
</div>

CSS

* {
    box-sizing: border-box;
}
body {
    margin: 20px;
}

h1 {
    font-size: 2em;
    font-family: sans-serif;
    margin: 0.3em 0;
}

.editor-wrapper {
    position: relative;
    width: 400px;
    height: 200px;
}
.editor {
    border: solid 1px black;
    width: 100%;
    height: 100%;    
    padding: 5px;
    overflow: auto;
    line-height: 1.5;
}

    .editor a {
        background: rgb(169, 214, 255);
        text-decoration: none;
        padding: 0px 4px;
        margin: 1px;
        color: #000;
        border: solid 1px rgb(0, 122, 255);
        border-radius: 5px;
    }

.editor-ghost {
    display: none;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: solid 1px black;
    padding: 5px;
    z-index: -1;
    overflow: auto;
    line-height: 1.5;
}

.toolbar {
    margin-top: 10px;
}
    .toolbar button {
    
    }

    .toolbar .active {
        background: #55D155;
    }

JavaScript

var wrapper = $('#myeditor'),
    editor = wrapper.find('.editor'),
    ghost = wrapper.find('.editor-ghost'),
    usedDigits = {},
        selection, range;
        
// See http://stackoverflow.com/questions/1181700/set-cursor-position-on-contenteditable-div
var captureSelection = function(e) {
    // Don't capture selection outside editable region
    var isOrContainsAnchor = false,
        isOrContainsFocus = false,
        sel = window.getSelection(),
        parentAnchor = sel.anchorNode,
        parentFocus = sel.focusNode;

    while(parentAnchor && parentAnchor != document.documentElement) {
        if(parentAnchor == editor[0]) {
            isOrContainsAnchor = true;
        }
        parentAnchor = parentAnchor.parentNode;
    }

    while(parentFocus && parentFocus != document.documentElement) {
        if(parentFocus == editor[0]) {
            isOrContainsFocus = true;
        }
        parentFocus = parentFocus.parentNode;
    }

    if(!isOrContainsAnchor || !isOrContainsFocus) {
        return;
    }
    selection = window.getSelection();

    // Get range (standards)
    if(selection.getRangeAt !== undefined) {
        range = selection.getRangeAt(0);

    // Get range (Safari 2)
    } else if(
        document.createRange &&
        selection.anchorNode &&
        selection.anchorOffset &&
        selection.focusNode &&
        selection.focusOffset
    ) {
        range = document.createRange();
        range.setStart(selection.anchorNode, selection.anchorOffset);
        range.setEnd(selection.focusNode, selection.focusOffset);
    } else {
        // Failure here, not handled by the rest of the script.
        // Probably IE or some older browser
    }
                                    
    //////////////////////////////////////                                
    // pushing cursor element to find it later
    var cursorStart = document.createElement('span'),
        collapsed = !!range.collapsed;

    cursorStart.id = 'cursorStart';
   ...