// UKNova textmarkup buttons

// Clickable smiley list
//
var PUNCTUATEDEMOTES= new Array(new Array(':-)', 'smile'), new Array(';-)', 'wink'), new Array(':-(', 'frown'));
var NAMEDEMOTES= new Array(
    new Array('deadpan', 'grr', 'lol', 'woot', 'erm', 'huh', 'wtf', 'erk', 'eek'),
    new Array('whistle', 'whome', 'zzz', 'blush', 'ugh', 'yuck', 'cheek', 'sob', 'hmm')
);

// Class for panel added to each textmarkup textarea
//
var MarkupPanel= Object.subclass();
MarkupPanel.prototype._init= function(textarea) {
    this.textarea= textarea;
    this.preclickvalue= getPartitionedValue(this.textarea);
    this.preclickparent= null;
    this._mouseddown= this.mouseddown.bind(this);

    this.markuprow= document.createElement('div');
    this.markuprow.className= 'markuprow';
    this.addButtons();

    this.emoterow= document.createElement('div');
    this.emoterow.className= 'emoterow';
    this.emoterow.appendChild(document.createElement('div'));
    this.emoterow.firstChild.className= 'wrap';
    this.addEmotes();

    this.emoterow.style.display= this.button_emote.selected? 'block' : 'none';
    var panel= document.createElement('div');
    panel.className= 'markuppanel';

    if (this.textarea.offsetHeight) {
        this.resizeheight= this.textarea.offsetHeight;
        this.resizer= new Image();
        this.resizer.src= '/img/markupbar/resizer.gif';
        this.resizer.alt= '^v';
        this.resizer.title= 'Drag to resize textmarkup area';
        this.resizer.className= 'markuprow-resize';
        this.resizer.style.cursor= 's-resize';
        this.resizer.onmousedown= this.resizeDown.bind(this);
        this.resizer.ondragstart= Function_return(false);
        panel.appendChild(this.resizer);
        this.resizeMoveCB= this.resizeMove.bind(this);
        this.resizeUpCB= this.resizeUp.bind(this);
        this.textarea.style.resize= 'none';
    }

    panel.appendChild(this.markuprow);
    panel.appendChild(this.emoterow);
    this.textarea.parentNode.appendChild(panel);

    this.checkStatus();
    this.interval= window.setInterval(this.checkStatus.bind(this), 500);
    this.previewer= new MarkupPreviewer(this.textarea);
};

// In response to a mousedown, browser will blur the textarea. So need to
// remember what was selected for subsequent click event
//
MarkupPanel.prototype.mouseddown= function() {
    var previewing= this.button_preview.selected;
    if (!previewing)
        this.preclickvalue= getPartitionedValue(this.textarea);
};

MarkupPanel.prototype.addButtons= function() {
    this.addButton('info', textmarkup_infoPopup, 'Click for information about textmarkup');
    this.addSep();
    this.addButton('italic', this.click_italic.bind(this), 'Select words and click to italicise');
    this.addButton('bold', this.click_bold.bind(this), 'Select words and click to embolden');
    this.addButton('link', this.click_link.bind(this), 'Select link text and click to add link URL');
    this.addButton('img', this.click_img.bind(this), 'Select alternative text and click to add image');
    this.addButton('emote', this.click_emote.bind(this), 'Click for emotes. Click emote to add, shift-click to add without closing', true);
    this.addSep();
    this.addButton('quote', this.click_quote.bind(this), 'Select body text inside another comment on the page, then click to add as a quote');
    this.button_quote.onmousedown= this.mouseddown_quote.bind(this);
    // this.addButton('spoiler', this.click_spoiler.bind(this), 'Select text and click to put in a hidden spoiler block');
    this.addButton('code', this.click_code.bind(this), 'Select text and click to make a code block');
    this.addButton('list', this.click_list.bind(this), 'Select text and click to make a list item. Shift-click for a numbered list');
    this.addButton('head', this.click_head.bind(this), 'Select text and click to make a heading. Shift-click for a subheading');
    this.addSep();
    this.addButton('preview', this.click_preview.bind(this), 'Enter textmarkup source in the text field and click to show as preview', true);
};

MarkupPanel.prototype.addButton= function(name, onclick, title, selectable) {
    var div= document.createElement('div');
    div.className= 'markupbutton markup-'+name;
    div.title= title;
    var button= new SpriteButton(div, false, false, false, selectable? false : null);
    button.onmousedown= this._mouseddown;
    button.onclick= onclick;
    this.markuprow.appendChild(div);
    this['button_'+name]= button;
};

MarkupPanel.prototype.addSep= function() {
    var sep= document.createElement('div');
    sep.className= 'markupsep';
    this.markuprow.appendChild(sep);
};

MarkupPanel.prototype.addEmotes= function() {
    var parent= this.emoterow.firstChild;
    for (var emotei= 0; emotei<PUNCTUATEDEMOTES.length; emotei++) {
        var text= PUNCTUATEDEMOTES[emotei][0];
        var src= PUNCTUATEDEMOTES[emotei][1];
        var img= new Image();
        img.src= '/img/emote/'+src+'.gif';
        img.alt=img.title=  text;
        img.style.cursor= 'pointer';
        img.onmousedown= this._mouseddown;
        img.onclick= this.click_emoterow.bind(this);
        parent.appendChild(img);
    }
    for (var rowi= 0; rowi<NAMEDEMOTES.length; rowi++) {
        parent.appendChild(document.createElement('br'));
        for (var emotei= 0; emotei<NAMEDEMOTES[rowi].length; emotei++) {
            var src= NAMEDEMOTES[rowi][emotei];
            var img= new Image();
            img.src= '/img/emote/'+src+'.gif';
            img.alt=img.title= ':'+src+':';
            img.style.cursor= 'pointer';
            img.onmousedown= this._mouseddown;
            img.onclick= this.click_emoterow.bind(this);
            parent.appendChild(img);
        }
    }
};

// Called every half-sec, see if there is a selection in the textarea/page.
// Enable/disable the panel buttons that require one
//
MarkupPanel.prototype.checkStatus= function() {
    var previewing= this.button_preview.selected;
    var ownselection= false;
    var commentselection= false;
    if (!previewing) {
        var ownselection= getPartitionedValue(this.textarea)[1]!='';
        var parent= textmarkup_getPageSelectionParent();
        var commentselection= (parent!=null && textmarkup_getSenderForParent(parent)!==null);
    }

    this.button_italic.disabled= !ownselection; this.button_italic.update();
    this.button_bold.disabled= !ownselection; this.button_bold.update();
    this.button_link.disabled= !ownselection; this.button_link.update();
    this.button_img.disabled= previewing; this.button_img.update();
    this.button_emote.disabled= previewing; this.button_emote.update();
    // this.button_spoiler.disabled= !ownselection; this.button_spoiler.update();
    this.button_code.disabled= !ownselection; this.button_code.update();
    this.button_list.disabled= !ownselection; this.button_list.update();
    this.button_head.disabled= !ownselection; this.button_head.update();
    this.button_quote.disabled= !commentselection; this.button_quote.update();
    this.button_preview.disabled= !previewing && this.textarea.value==''; this.button_preview.update();
};

// Markup functions

// italic
//
MarkupPanel.prototype.click_italic= function() {
    var sel= this.preclickvalue;
    if (sel[1]=='') return alert(this.button_italic.element.title);
    if (sel[1].indexOf('/')!=-1) return alert('Can\'t italicise text including \u2018/\u2019');
    sel[1]= textmarkup_decorateLines(sel[1], '/', '/', true);
    setPartitionedValue(this.textarea, sel);
};

// bold
//
MarkupPanel.prototype.click_bold= function() {
    var sel= this.preclickvalue;
    if (sel[1]=='') return alert(this.button_bold.element.title);
    if (sel[1].indexOf('*')!=-1) return alert('Can\'t embolden text including \u2018*\u2019');
    sel[1]= textmarkup_decorateLines(sel[1], '*', '*', true);
    setPartitionedValue(this.textarea, sel);
};

// link
//
MarkupPanel.prototype.click_link= function() {
    var sel= this.preclickvalue;
    if (sel[1]=='') return alert(this.button_link.element.title);
    if (sel[1].indexOf(')')!=-1) return alert('Can\'t link text including \')\'');
    var p= new ErsatzPrompt('Link to URL (allowed: http://..., https://..., mailto:...)', 'http://www.uknova.com/');
    p.callback(function(url) {
        if (url===null) return;
        if (!String_startsWith(url, 'http://', 'https://', 'mailto:'))
            return alert('Unknown link URL type - must be http/https/mailto');
        sel[1]= textmarkup_decorateLines(sel[1], url+' (', ')');
        setPartitionedValue(this.textarea, sel);
    }.bind(this));
    return false;
};

// image
//
MarkupPanel.prototype.click_img= function() {
    if (this.button_preview.selected) return alert(this.button_img.element.title);
    var sel= this.preclickvalue;
    if (sel[1].indexOf('\n')!=-1) return alert('Can\'t use multi-line text as image alternative');
    if (sel[1].indexOf(']')!=-1) return alert('Can\'t include \u2018]\u2019 in image alternative text');
    var p= new ErsatzPrompt('Image URL (allowed: http://..., https://...)', 'http://');
    p.callback(function(url) {
        if (url===null) return;
        if (!String_startsWith(url, 'http://', 'https://'))
            return alert('Unknown image URL type - must be http/https');
        sel[1]= url+' ['+sel[1]+']'
        setPartitionedValue(this.textarea, sel);
    }.bind(this));
    return false;
};

// spoiler
//
MarkupPanel.prototype.click_spoiler= function() {
    var sel= this.preclickvalue;
    if (sel[1]=='') return alert(this.button_spoiler.element.title);
    sel[1]= textmarkup_decorateBlocks(sel[1], '] ', '] ');
    setPartitionedValue(this.textarea, sel);
};

// code block
//
MarkupPanel.prototype.click_code= function() {
    var sel= this.preclickvalue;
    if (sel[1]=='') return alert(this.button_code.element.title);
    sel[1]= textmarkup_decorateBlocks(sel[1], ': ', ': ');
    setPartitionedValue(this.textarea, sel);
};

// list
//
MarkupPanel.prototype.click_list= function(e) {
    if (!e) e= window.event;
    var bullet= e.shiftKey? '#' : '*';

    var sel= this.preclickvalue;
    if (sel[1]=='') return alert(this.button_list.element.title);
    sel[1]= textmarkup_decorateBlocks(sel[1], bullet+' ', '  ');
    setPartitionedValue(this.textarea, sel);
};

// headings
//
MarkupPanel.prototype.click_head= function(e) {
    if (!e) e= window.event;
    var headline= e.shiftKey? '---' : '===';

    var sel= this.preclickvalue;
    if (sel[1]=='') return alert(this.button_head.element.title);
    sel[1]= textmarkup_decorateLines(sel[1], '\n', '\n'+headline+'\n');
    setPartitionedValue(this.textarea, sel);
};

// quotes
//
MarkupPanel.prototype.mouseddown_quote= function() {
    this.preclickparent= textmarkup_getPageSelectionParent();
    if (this.preclickparent) {
        this.preclicksender= textmarkup_getSenderForParent(this.preclickparent);
        this.preclickcontent= textmarkup_getPageSelectionContent();
    }
    this.mouseddown();
};
MarkupPanel.prototype.click_quote= function() {
    if (this.preclickparent===null) return alert(this.button_quote.element.title);
    var body= textmarkup_fragmentToMarkup(this.preclickcontent, '').trim();
    var sender= (this.preclicksender===null)? '> ' : this.preclicksender+'> ';

    var sel= this.preclickvalue;
    if (sel[0]!='' && !String_endsWith(sel[0], '\n')) sel[0]+= '\n';
    sel[1]= textmarkup_decorateBlocks(body, sender, '> ')+'\n';
    if (!String_startsWith(sel[2], '\n')) sel[2]= '\n'+sel[2];
    setPartitionedValue(this.textarea, sel);
};

// emotes show/hide
//
MarkupPanel.prototype.click_emote= function() {
    if (this.button_preview.selected) return alert(this.button_emote.element.title);
    this.button_emote.selected= !this.button_emote.selected;
    this.emoterow.style.display= this.button_emote.selected? 'block' : 'none';
    setPartitionedValue(this.textarea, this.preclickvalue);
};
MarkupPanel.prototype.click_emoterow= function(e) {
    if (!e) e= window.event;
    if (this.button_preview.selected) return alert(this.button_emote.element.title);

    var target= null;
    if (e.target) target= e.target;
    if (e.srcElement) target= e.srcElement;
    if (!target) return;

    var sel=this.preclickvalue;
    if (!String_endsWith(sel[0], ' ', '\n'))
        sel[0]+= ' ';
    sel[0]+= target.alt;
    sel[1]= '';
    if (sel[2]!='' && !String_startsWith(sel[2], ' ', '\n'))
        sel[0]+= ' ';
    setPartitionedValue(this.textarea, sel);

    if (!e.shiftKey) {
        this.emoterow.style.display= 'none';
        this.button_emote.selected= false;
        this.button_emote.update();
    }
};

// Toggle preview mode
//
MarkupPanel.prototype.click_preview= function() {
    if (this.textarea.value=='') return alert(this.button_preview.element.title);
    var state= this.button_preview.selected;
    if (!state && this.button_emote.selected)
        this.click_emote();
    
    state=this.button_preview.selected= !state;
    this.button_preview.update();
    if (state) this.previewer.start();
    else this.previewer.stop();
};

// Resizing
//
MarkupPanel.prototype.resizeDown= function(e) {
    if (!e) e= window.event;
    this.resizeorigin= e.pageY? e.pageY : e.clientY;
    this.resizeheight= this.previewer.active? this.previewer.previewarea.offsetHeight : this.textarea.offsetHeight;
    document.onmousemove= this.resizeMoveCB;
    document.onmouseup= this.resizeUpCB;
    return false;
};
MarkupPanel.prototype.resizeUp= function(e) {
    this.resizeMove(e);
    document.onmousemove=document.onmouseup= '';
};
MarkupPanel.prototype.resizeMove= function(e) {
    if (!e) e= window.event;
    var y= e.pageY? e.pageY : e.clientY;
    var h= this.resizeheight+y-this.resizeorigin;
    h= h>64? h:64;
    this.textarea.style.height= (h-4)+'px';
    this.previewer.previewarea.style.height= (h-4)+'px';
};


// Return name of sender of a selection parent, or null if not fully contained
// in a post body
//
function textmarkup_getSenderForParent(parent) {
    if (parent.nodeType==3) // TEXT_NODE
        parent= parent.parentNode;
    while (parent && parent.nodeType==1) { // ELEMENT_NODE
        var sender= Element_getClassArg(parent, 'sender');
        if (sender) return sender;
        parent= parent.parentNode;
    }
    return null;
}

// Grab content from a node's childNodes, return as textmarkup
//
var NLRE= new RegExp('[\\r\\n]+', 'g');

function textmarkup_fragmentToMarkup(frag, context) {
    var output= new Array();
    for (var nodei= 0; nodei<frag.childNodes.length; nodei++) { var node= frag.childNodes[nodei];
        if (node.nodeType==3) { // TEXT_NODE
            output[output.length]= node.data.replace(NLRE, ' ');
        } else if (node.nodeType==1) { // ELEMENT_NODE
            var tag= node.tagName.toLowerCase();
            if (tag=='table' || tag=='tbody' || tag=='tr' || tag=='td') {
                output[output.length]= textmarkup_fragmentToMarkup(node, context);
            } else if (tag=='br') {
                output[output.length]= '\n'+context;
            } else if (tag=='p') {
                output[output.length]= context;
                output[output.length]= textmarkup_fragmentToMarkup(node, context);
                output[output.length]= '\n\n';
            } else if (tag=='h3') {
                output[output.length]= context;
                output[output.length]= textmarkup_fragmentToMarkup(node, context);
                output[output.length]= context+'===\n\n';
            } else if (tag=='h4') {
                output[output.length]= context;
                output[output.length]= textmarkup_fragmentToMarkup(node, context);
                output[output.length]= context+'---\n\n';
            } else if (tag=='em') {
                output[output.length]= '/';
                output[output.length]= textmarkup_fragmentToMarkup(node, context);
                output[output.length]= '/';
            } else if (tag=='strong') {
                output[output.length]= '*';
                output[output.length]= textmarkup_fragmentToMarkup(node, context);
                output[output.length]= '*';
            } else if (tag=='a') {
                var href= node.href;
                var content= textmarkup_fragmentToMarkup(node);
                output[output.length]= href;
                if (href!=content) {
                    output[output.length]= ' (';
                    output[output.length]= content;
                    output[output.length]= ')';
                }
            } else if (tag=='img') {
                if (node.alt) output[output.length]= node.alt;
                else output[output.length]= '-img-';
            } else if (tag=='hr') {
                output[output.length]= '---\n\n';
            } else if (tag=='ul') {
                output[output.length]= textmarkup_fragmentToMarkup(node, '* '+context);
            } else if (tag=='ol') {
                output[output.length]= textmarkup_fragmentToMarkup(node, '# '+context);
            } else if (tag=='li') {
                output[output.length]= context;
                output[output.length]= textmarkup_fragmentToMarkup(node, '  '+context.substring(2));
            } else if (tag=='pre') { // no markup inside pre
                var text= node.firstChild? node.firstChild.data : '';
                text= text.split('\n').join('\n  ');
                output[output.length]= context+': '+text+'\n';
            } else if (tag=='pre') {
                output[output.length]= textmarkup_fragmentToMarkup(node, ': '+context);
            } else if (tag=='div' && Element_hasClass(node, 'spoiler')) {
                output[output.length]= textmarkup_fragmentToMarkup(node, '] '+context);
            } else if (tag=='div') {
                output[output.length]= textmarkup_fragmentToMarkup(node, context);
            } // deliberately skip over 'blockquote' - no need to quote quotes
        }
    }
    return output.join('');
}

// Return a string, prefixed and suffixed per line
// Try to cope with IE's annoying inclusion of CR characters
//
function textmarkup_decorateLines(s, prefix, suffix, strip) {
    var lines= s.split('\n');
    for (var i= lines.length; i-->0;) { var line= lines[i];
        var stripped= lines[i].trim();
        if (stripped!='')
            lines[i]= prefix+(strip? stripped:line)+suffix;
    }
    return lines.join('\n');
}

// Return a string, prefixing first and subsequent lines differently
//
function textmarkup_decorateBlocks(s, firstprefix, nextprefix) {
    var lines= s.split('\n');
    var firstline= true;
    for (var i= 0; i<lines.length; i++) {
        if (lines[i].trim()!='') {
            lines[i]= (firstline? firstprefix : nextprefix)+lines[i];
            firstline= false;
    }   }
    return lines.join('\n');
}

// Open info window
//
function textmarkup_infoPopup() {
    var url= '/textmarkup.html';
    var w= open(url, '_blank', 'width=640,height=480,resizable=yes,scrollbars=yes');
    if (!w) location.href= url;
}


// Preview box cache
//
var textmarkup_CACHELEN= 8;
var textmarkup_sources= new Array();
var textmarkup_markups= new Array();

// Textmarkup preview box
//
var MarkupPreviewer= AsyncRequest.subclass();
MarkupPreviewer.prototype._init= function(textarea) {
    this.active= false;
    this.textarea= textarea;
    this.previewarea= document.createElement('div');
    this.previewarea.className= 'markuppreview textmarkup';
    this.previewarea.style.display= 'none';
    this.textarea.parentNode.insertBefore(this.previewarea, this.textarea);
    if (this.textarea.offsetHeight)
        this.previewarea.style.height= (this.textarea.offsetHeight-2)+'px';
};
MarkupPreviewer.prototype.start= function() {
    if (!this.active) {
        this.active= true;
        this.textarea.style.display= 'none';
        this.previewarea.style.display= '';
        Node_clearChildren(this.previewarea);
        var source= this.textarea.value;

        var cacheix= textmarkup_sources.indexOf(source);
        if (cacheix!=-1) {
            this.setpreview(textmarkup_markups[cacheix]);
        } else {
            var pars= new Object();
            pars.source= source;
            this.open(false, '/log/preview', pars, 10000); // timeout in 10s
        }
    }
};
MarkupPreviewer.prototype.stop= function() {
    if (this.active) {
        this.active= false;
        this.close();
        this.textarea.style.display= '';
        this.previewarea.style.display= 'none';
    }
};
MarkupPreviewer.prototype.oncancel= function() {
    this.previewarea.appendChild(document.createTextNode('(preview connection failed)'));
};
MarkupPreviewer.prototype.oncomplete= function(html) {
    textmarkup_sources[textmarkup_sources.length]= this.textarea.value;
    textmarkup_markups[textmarkup_markups.length]= html;
    if (textmarkup_sources.length>textmarkup_CACHELEN) {
        textmarkup_sources.splice(0, 1);
        textmarkup_markups.splice(0, 1);
    }
    this.setpreview(html);
};
MarkupPreviewer.prototype.setpreview= function(html) {
    this.previewarea.innerHTML= html;
};

// Cross-browser textarea selection handling

// Get selection, returning an Array of the selected text and either an IE TextRange
// object, or start-and-end indices on other browsers. This difference is necessary
// because IE is incapable of returning reliable indices into a textarea - you can
// nearly do it with Range manipulations, but any leading/trailing newlines get
// unrecoverably miscounted. This is in addition to the various problems caused
// by IE's conversion of newlines to CR+LF pairs. In summary, IE is bumwater.
//
var CRRE= new RegExp('\\r', 'g');
function textmarkup_getOwnSelection(textarea) {
    if (textarea.style.display!='none') {
        if (textarea.selectionStart!==window.undefined) { // Moz etc.
            var range= new Array(textarea.selectionStart, textarea.selectionEnd);
            return new Array(textarea.value.substring(range[0], range[1]), range);
        } else if (document.selection!==window.undefined) { // IE
            var range= document.selection.createRange().duplicate();
            if (range.parentElement()==textarea)
                return new Array(range.text.replace(CRRE, ''), range);
            range= textarea.createTextRange();
            range.setEndPoint('StartToEnd', range);
            return new Array('', range);
    }   }
    return new Array('', new Array(textarea.value.length, textarea.value.length));
}


// Cross-browser on-page-selection handling
//
function textmarkup_getPageSelectionParent() {
    var range= textmarkup_getPageRange();
    if (range) {
        if (range.commonAncestorContainer!==window.undefined) // W3
            return range.commonAncestorContainer;
        if (range.parentElement) // IE
            return range.parentElement();
    }
    return null;
}

function textmarkup_getPageSelectionContent() {
    var range= textmarkup_getPageRange();
    if (range.cloneContents) // W3
        return range.cloneContents();
    if (range.htmlText) { // IE
        var div= document.createElement('dom');
        div.innerHTML= range.htmlText;
        var frag= document.createDocumentFragment();
        while (div.firstChild)
            frag.appendChild(div.firstChild);
        return frag;
    }
    return null;
}

function textmarkup_getPageRange() {
    if (window.getSelection) {
        var selection= window.getSelection();
        if (selection==null) return null;
        var range= null;
        if (selection.getRangeAt) { // Moz etc.
            if (selection.rangeCount!=0)
                return selection.getRangeAt(0);
        } else { // Safari etc.
            var range = document.createRange();
            range.setStart(selection.anchorNode,selection.anchorOffset);
            range.setEnd(selection.focusNode,selection.focusOffset);
            return range;
        }
    }
    if (document.selection) { // IE
        return document.selection.createRange();
    }
    return null;
}

// Cross-browser input selection handling

// getPartitionedValue: for an input/textarea, return the value text, split into
// an array of [before-selection, selection, after-selection] strings.
//
function getPartitionedValue(input) {
    var value= input.value;
    var start= value.length;
    var end= start;
    if (input.selectionStart!==undefined) {
        start= input.selectionStart;
        end= input.selectionEnd;
    } else if (document.selection!==undefined) {
        value= value.split('\r').join('');
        start=end= value.length;
        var range= document.selection.createRange();
        if (range.parentElement()===input) {
            var start= -range.moveStart('character', -10000000);
            var end= -range.moveEnd('character', -10000000);
            range.moveToElementText(input);
            var error= -range.moveStart('character', -10000000);
            start-= error;
            end-= error;
        }
    }
    return [
        value.substring(0, start),
        value.substring(start, end),
        value.substring(end)
    ];
}

// setPartitionedValue: set the value text and selected region in an input/
// textarea.
//
function setPartitionedValue(input, value) {
    var oldtop= input.scrollTop!==undefined? input.scrollTop : null;
    input.value= value.join('');
    input.focus();
    var start= value[0].length;
    var end= value[0].length+value[1].length;
    if (input.selectionStart!==undefined) {
        input.selectionStart= start;
        input.selectionEnd= end;
        if (oldtop!==null)
            input.scrollTop= oldtop;
    }
    else if (document.selection!==undefined) {
        var range= input.createTextRange();
        range.collapse(true);
        range.moveEnd('character', end);
        range.moveStart('character', start);
        range.select();
    }
}


// CSS-sprite-based hoverable/selectable/disablable interface element
//
var SpriteButton= Object.subclass();
SpriteButton.prototype._init=  function(element, disabled, hovered, pressed, selected) {
    // Store args, which should be true if button is initially in that
    // state, false if it isn't, or null if that state cannot be achieved.
    // eg. (el, null, false, null, null) for a simple hover-only button.
    //
    this.element= element;
    this.disablable= disabled!==null;
    this.hoverable= hovered!==null;
    this.pressable= pressed!==null;
    this.selectable= selected!==null;
    this.disabled= disabled==true;
    this.hovered= hovered==true;
    this.pressed= pressed==true;
    this.selected= selected==true;

    this.element.onmouseover= this.mousedover.bind(this);
    this.element.onmouseout= this.mousedout.bind(this);
    this.element.onmouseup= this.mousedup.bind(this);
    this.element.onmousedown= this.mouseddown.bind(this);
    this.element.onclick= this.clicked.bind(this);
    if (IE)
        this.element.ondragstart= Function_return(false);
};

// Set state in response to interaction
//
SpriteButton.prototype.mousedover= function() {
    this.hovered= true;
    if (this.hoverable) this.update();
};
SpriteButton.prototype.mousedout= function() {
    this.hovered=this.pressed= false;
    if (this.hoverable||this.pressable) this.update();
};
SpriteButton.prototype.mouseddown= function() {
    this.pressed= true;
    if (this.pressable) this.update();
    return this.onmousedown();
};
SpriteButton.prototype.mousedup= function() {
    this.pressed= false;
    if (this.pressable) this.update();
};

SpriteButton.prototype.update= function() {
    Element_setClass(this.element, 'sprite-disabled', this.disablable && this.disabled);
    Element_setClass(this.element, 'sprite-hovered', this.hoverable && this.hovered);
    Element_setClass(this.element, 'sprite-pressed', this.pressable && this.pressed);
    Element_setClass(this.element, 'sprite-selected', this.selectable && this.selected);
};
SpriteButton.prototype.clicked= function() {
    return this.onclick.apply(this, arguments);
};
SpriteButton.prototype.onclick= Function_return(true);
SpriteButton.prototype.onmousedown= Function_return(true);


// bind to textareas
//
Array_fromSequence(document.getElementsByClassName('markupbar', 'textarea')).map(function(textarea) {
    new MarkupPanel(textarea);
});


