Editor without contenteditable

by Tonio Loewald

HTML

<h1>Editor Test</h1>
<form>
    <label>Input Field (focusable)
        <input />
    </label>
</form>
<div class="tools">
    <button value="bold"><b>B</b></button>
    <button value="italic"><i>I</i></button>
    <select name="paragraph-style">
        <option value="setBlocks h1">Major Heading</option>
        <option value="setBlocks h2">Heading</option>
        <option value="setBlocks h3">Sub-heading</option>
        <option value="setBlocks p">Body</option>
        <option value="?"><i>Mixed</i></option>
    </select>
</div>
<div class="editor">
    <h2>This is a test</h2>
    <p>
        How horrible, little Beryl's baby crocodile now occupies father's nighty.
    </p>
    <p>
        Oh, be a fine girl kiss me right now smack. <b>All stations to central</b>. Cah won't go soh toa.
    </p>
    <p>
        Normally, both your asses would be dead as fucking fried chicken, but you happen to pull this shit while I'm in a transitional period so I don't wanna kill you, I wanna help you. But I can't give you this case, it don't belong to me. Besides, I've already been through too much shit this morning over this case to hand it over to your dumb ass.
    </p>
</div

CSS

.editor {
    border: 1px solid #aaa;
    padding: 8px 12px;
    background: white;
}
body {
    background: #eee;
}
.first-block {
    border-left: 4px solid #afa;
}
.last-block {
    border-right: 4px solid #faa;
}
.caret {
    display: inline-block;
    height: 14px;
    width: 2px;
    background-color: #aaf;
}
}

JavaScript

console.clear();

$.fn.editor = function(options){
    this.data('editor', new Editor(this, options));
    console.log(this, this.data('editor'));
    return this;
}

function Editor(elt, options){
    this.root = $(elt);
    this.options = $.extend({}, options);
    
    var defaults = {
    }
    
    for(var key in defaults){
        if(this.options.key === undefined){
            this.options[key] = defaults[key];
        }
    }
    
    this.setup();
    
    return this;
}

Editor.prototype = {
    setup: function(){
        this.root.attr('tabindex', 0);
        this.root.on('mousedown.editor', this.mousedown);
        this.root.on('mouseup.editor', this.mouseup);
        this.root.on('keydown.editor', this.keydown);
        this.root.on('keyup.editor', this.keyup);
    
        if(this.options.tools){
            var editor = this;
            $(this.options.tools).on('click.editor', 'button', function(evt){ editor.doCommand(evt) })
            				.on('change.editor', 'select', function(evt){ editor.doCommand(evt) });
        }
    },
    /* tool commands */
    doCommand: function(evt){
        var command = $(this).attr('value') || $(this).val();
        var parameters = command.split(' ');
        command = parameters.shift();
        switch(command){
            case 'setBlocks':
                console.log(this.selectedBlocks());
                break;
            case 'bold':
                break;
            case 'italic':
                break;
        }
        console.log(command);
    },
    /* event handlers */
    mousedown: function(evt){
      	console.log('mousedown');  
    },
    mouseup: function(evt){
      	$(this).data('editor').recordSelection();
    },
    keydown: function(evt){
      	console.log('keydown');
    },
    keyup: function(evt){
      	console.log('keyup');
    },
    attr: function(node, attribute, value){
        if(!node){
            return;
        }
        if($(node)[0].nodeType === 3){
            node =...