/*
 Redactor v9.1.0
 Updated: Aug 5, 2013

 http://imperavi.com/redactor/

 Copyright (c) 2009-2013, Imperavi LLC.
 License: http://imperavi.com/redactor/license/

 Usage: $('#content').redactor();
 */

(function($)
{
    var uuid = 0;
    var rtePaste = false;

    "use strict";

    var Range = function(range)
    {
        this[0] = range.startOffset;
        this[1] = range.endOffset;

        this.range = range;

        return this;
    };

    Range.prototype.equals = function()
    {
        return this[0] === this[1];
    };

    // Plugin
    $.fn.redactor = function(options)
    {
        var val = [];
        var args = Array.prototype.slice.call(arguments, 1);

        if (typeof options === 'string')
        {
            this.each(function()
            {
                var instance = $.data(this, 'redactor');
                if (typeof instance !== 'undefined' && $.isFunction(instance[options]))
                {
                    var methodVal = instance[options].apply(instance, args);
                    if (methodVal !== undefined && methodVal !== instance) val.push(methodVal);
                }
                else return $.error('No such method "' + options + '" for Redactor');
            });
        }
        else
        {
            this.each(function()
            {
                if (!$.data(this, 'redactor')) $.data(this, 'redactor', Redactor(this, options));
            });
        }

        if (val.length === 0) return this;
        else if (val.length === 1) return val[0];
        else return val;

    };

    // Initialization
    function Redactor(el, options)
    {
        return new Redactor.prototype.init(el, options);
    }

    $.Redactor = Redactor;
    $.Redactor.VERSION = '9.1.0';
    $.Redactor.opts = {

        // settings
        rangy: false,

        iframe: false,
        fullpage: false,
        css: false, // url

        lang: 'en',
        direction: 'ltr', // ltr or rtl

        placeholder: false,

        wym: false,
        mobile: true,
        cleanup: true,
        pastePlainText: false,
        removeEmptyTags: true,
        templateVars: false,

        visual: true,
        focus: false,
        tabindex: false,
        autoresize: true,
        minHeight: false,
        shortcuts: true,

        autosave: false, // false or url
        autosaveInterval: 60, // seconds

        plugins: false, // array

        linkAnchor: false,
        linkEmail: false,
        linkProtocol: 'http://',
        linkNofollow: false,

        imageGetJson: false, // url (ex. /folder/images.json ) or false

        imageUpload: false, // url
        fileUpload: false, // url
        clipboardUpload: true, // or false
        clipboardUploadUrl: false, // url
        dragUpload: true, // false

        dnbImageTypes: ['image/png', 'image/jpeg', 'image/gif'], // or false

        s3: false,
        uploadFields: false,

        observeImages: true,

        modalOverlay: true,

        tabFocus: true,

        air: false,
        airButtons: ['formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'fontcolor', 'backcolor'],

        toolbar: true,
        toolbarFixed: false,
        toolbarFixedTarget: document,
        toolbarFixedTopOffset: 0, // pixels
        toolbarFixedBox: false,
        toolbarExternal: false, // ID selector
        buttonSource: true,

        buttonSeparator: '<li class="redactor_separator"></li>',

        buttonsCustom: {},
        buttonsAdd: [],
        buttons: ['html', '|', 'formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image', 'video', 'file', 'table', 'link', '|', 'fontcolor', 'backcolor', '|', 'alignment', '|', 'horizontalrule'], // 'underline', 'alignleft', 'aligncenter', 'alignright', 'justify'
        colors: ['#ffffff', '#000000', '#eeece1', '#1f497d', '#4f81bd', '#c0504d', '#9bbb59', '#8064a2', '#4bacc6', '#f79646', '#ffff00', '#f2f2f2', '#7f7f7f', '#ddd9c3', '#c6d9f0', '#dbe5f1', '#f2dcdb', '#ebf1dd', '#e5e0ec', '#dbeef3', '#fdeada', '#fff2ca', '#d8d8d8', '#595959', '#c4bd97', '#8db3e2', '#b8cce4', '#e5b9b7', '#d7e3bc', '#ccc1d9', '#b7dde8', '#fbd5b5', '#ffe694', '#bfbfbf', '#3f3f3f', '#938953', '#548dd4', '#95b3d7', '#d99694', '#c3d69b', '#b2a2c7', '#b7dde8', '#fac08f', '#f2c314', '#a5a5a5', '#262626', '#494429', '#17365d', '#366092', '#953734', '#76923c', '#5f497a', '#92cddc', '#e36c09', '#c09100', '#7f7f7f', '#0c0c0c', '#1d1b10', '#0f243e', '#244061', '#632423', '#4f6128', '#3f3151', '#31859b', '#974806', '#7f6000'],

        activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist', 'alignleft', 'aligncenter', 'alignright', 'justify', 'table'],
        activeButtonsStates: {
            b: 'bold',
            strong: 'bold',
            i: 'italic',
            em: 'italic',
            del: 'deleted',
            strike: 'deleted',
            ul: 'unorderedlist',
            ol: 'orderedlist',
            u: 'underline',
            tr: 'table',
            td: 'table',
            table: 'table'
        },
        activeButtonsAdd: false, // object, ex.: { tag: 'buttonName' }

        formattingTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],

        linebreaks: false,
        paragraphy: true,
        convertDivs: true,
        convertLinks: true,
        convertImageLinks: false,
        convertVideoLinks: false,
        formattingPre: false,
        phpTags: false,

        allowedTags: false,
        deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'],

        boldTag: 'strong',
        italicTag: 'em',

        // private
        indentValue: 20,
        buffer: [],
        rebuffer: [],
        textareamode: false,
        emptyHtml: '<p>&#x200b;</p>',
        invisibleSpace: '&#x200b;',
        rBlockTest: /^(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)$/i,
        alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'TD',
            'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION',
            'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
        ownLine: ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'],
        contOwnLine: ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'],
        newLevel: ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul'],
        blockLevelElements: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'LI',
            'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'PRE', 'ADDRESS', 'SECTION',
            'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
        // lang
        langs: {
            en: {
                html: 'HTML',
                video: 'Insert Video',
                image: 'Insert Image',
                table: 'Table',
                link: 'Link',
                link_insert: 'Insert link',
                link_edit: 'Edit link',
                unlink: 'Unlink',
                formatting: 'Formatting',
                paragraph: 'Normal text',
                quote: 'Quote',
                code: 'Code',
                header1: 'Header 1',
                header2: 'Header 2',
                header3: 'Header 3',
                header4: 'Header 4',
                header5: 'Header 5',
                bold: 'Bold',
                italic: 'Italic',
                fontcolor: 'Font Color',
                backcolor: 'Back Color',
                unorderedlist: 'Unordered List',
                orderedlist: 'Ordered List',
                outdent: 'Outdent',
                indent: 'Indent',
                cancel: 'Cancel',
                insert: 'Insert',
                save: 'Save',
                _delete: 'Delete',
                insert_table: 'Insert Table',
                insert_row_above: 'Add Row Above',
                insert_row_below: 'Add Row Below',
                insert_column_left: 'Add Column Left',
                insert_column_right: 'Add Column Right',
                delete_column: 'Delete Column',
                delete_row: 'Delete Row',
                delete_table: 'Delete Table',
                rows: 'Rows',
                columns: 'Columns',
                add_head: 'Add Head',
                delete_head: 'Delete Head',
                title: 'Title',
                image_position: 'Position',
                none: 'None',
                left: 'Left',
                right: 'Right',
                image_web_link: 'Image Web Link',
                text: 'Text',
                mailto: 'Email',
                web: 'URL',
                video_html_code: 'Video Embed Code',
                file: 'Insert File',
                upload: 'Upload',
                download: 'Download',
                choose: 'Choose',
                or_choose: 'Or choose',
                drop_file_here: 'Drop file here',
                align_left: 'Align text to the left',
                align_center: 'Center text',
                align_right: 'Align text to the right',
                align_justify: 'Justify text',
                horizontalrule: 'Insert Horizontal Rule',
                deleted: 'Deleted',
                anchor: 'Anchor',
                link_new_tab: 'Open link in new tab',
                underline: 'Underline',
                alignment: 'Alignment',
                filename: 'Name (optional)',
                edit: 'Edit'
            }
        }
    };

    // Functionality
    Redactor.fn = $.Redactor.prototype = {

        keyCode:
        {
            BACKSPACE: 8,
            DELETE: 46,
            DOWN: 40,
            ENTER: 13,
            ESC: 27,
            TAB: 9,
            CTRL: 17,
            META: 91,
            LEFT: 37,
            LEFT_WIN: 91
        },

        // Initialization
        init: function(el, options)
        {

            this.$element = this.$source = $(el);
            this.uuid = uuid++;

            // current settings
            this.opts = $.extend(
                {},
                $.Redactor.opts,
                this.$element.data(),
                options
            );

            this.start = true;
            this.dropdowns = [];

            // get sizes
            this.sourceHeight = this.$source.css('height');
            this.sourceWidth = this.$source.css('width');

            // dependency of the editor modes
            if (this.opts.fullpage) this.opts.iframe = true;
            if (this.opts.linebreaks) this.opts.paragraphy = false;
            if (this.opts.paragraphy) this.opts.linebreaks = false;
            if (this.opts.toolbarFixedBox) this.opts.toolbarFixed = true;

            // the alias for iframe mode
            this.document = document;
            this.window = window;

            // selection saved
            this.savedSel = false;

            // clean setup
            this.cleanlineBefore = new RegExp('^<(/?' + this.opts.ownLine.join('|/?' ) + '|' + this.opts.contOwnLine.join('|') + ')[ >]');
            this.cleanlineAfter = new RegExp('^<(br|/?' + this.opts.ownLine.join('|/?' ) + '|/' + this.opts.contOwnLine.join('|/') + ')[ >]');
            this.cleannewLevel = new RegExp('^</?(' + this.opts.newLevel.join('|' ) + ')[ >]');

            // block level
            this.rTestBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i');

            // setup formatting permissions
            if (this.opts.linebreaks === false)
            {
                if (this.opts.allowedTags !== false && $.inArray('p', this.opts.allowedTags) === '-1') this.opts.allowedTags.push('p');

                if (this.opts.deniedTags !== false)
                {
                    var pos = $.inArray('p', this.opts.deniedTags);
                    if (pos !== '-1') this.opts.deniedTags.splice(pos, pos);
                }
            }

            // ie & opera
            if (this.browser('msie') || this.browser('opera'))
            {
                this.opts.buttons = this.removeFromArrayByValue(this.opts.buttons, 'horizontalrule');
            }

            // load lang
            this.opts.curLang = this.opts.langs[this.opts.lang];

            // Build
            this.buildStart();

        },
        initToolbar: function(lang)
        {
            return {
                html:
                {
                    title: lang.html,
                    func: 'toggle'
                },
                formatting:
                {
                    title: lang.formatting,
                    func: 'show',
                    dropdown:
                    {
                        p:
                        {
                            title: lang.paragraph,
                            func: 'formatBlocks'
                        },
                        blockquote:
                        {
                            title: lang.quote,
                            func: 'formatQuote',
                            className: 'redactor_format_blockquote'
                        },
                        pre:
                        {
                            title: lang.code,
                            func: 'formatBlocks',
                            className: 'redactor_format_pre'
                        },
                        h1:
                        {
                            title: lang.header1,
                            func: 'formatBlocks',
                            className: 'redactor_format_h1'
                        },
                        h2:
                        {
                            title: lang.header2,
                            func: 'formatBlocks',
                            className: 'redactor_format_h2'
                        },
                        h3:
                        {
                            title: lang.header3,
                            func: 'formatBlocks',
                            className: 'redactor_format_h3'
                        },
                        h4:
                        {
                            title: lang.header4,
                            func: 'formatBlocks',
                            className: 'redactor_format_h4'
                        },
                        h5:
                        {
                            title: lang.header5,
                            func: 'formatBlocks',
                            className: 'redactor_format_h5'
                        }
                    }
                },
                bold:
                {
                    title: lang.bold,
                    exec: 'bold'
                },
                italic:
                {
                    title: lang.italic,
                    exec: 'italic'
                },
                deleted:
                {
                    title: lang.deleted,
                    exec: 'strikethrough'
                },
                underline:
                {
                    title: lang.underline,
                    exec: 'underline'
                },
                unorderedlist:
                {
                    title: '&bull; ' + lang.unorderedlist,
                    exec: 'insertunorderedlist'
                },
                orderedlist:
                {
                    title: '1. ' + lang.orderedlist,
                    exec: 'insertorderedlist'
                },
                outdent:
                {
                    title: '< ' + lang.outdent,
                    func: 'indentingOutdent'
                },
                indent:
                {
                    title: '> ' + lang.indent,
                    func: 'indentingIndent'
                },
                image:
                {
                    title: lang.image,
                    func: 'imageShow'
                },
                video:
                {
                    title: lang.video,
                    func: 'videoShow'
                },
                file:
                {
                    title: lang.file,
                    func: 'fileShow'
                },
                table:
                {
                    title: lang.table,
                    func: 'show',
                    dropdown:
                    {
                        insert_table:
                        {
                            title: lang.insert_table,
                            func: 'tableShow'
                        },
                        separator_drop1:
                        {
                            name: 'separator'
                        },
                        insert_row_above:
                        {
                            title: lang.insert_row_above,
                            func: 'tableAddRowAbove'
                        },
                        insert_row_below:
                        {
                            title: lang.insert_row_below,
                            func: 'tableAddRowBelow'
                        },
                        insert_column_left:
                        {
                            title: lang.insert_column_left,
                            func: 'tableAddColumnLeft'
                        },
                        insert_column_right:
                        {
                            title: lang.insert_column_right,
                            func: 'tableAddColumnRight'
                        },
                        separator_drop2:
                        {
                            name: 'separator'
                        },
                        add_head:
                        {
                            title: lang.add_head,
                            func: 'tableAddHead'
                        },
                        delete_head:
                        {
                            title: lang.delete_head,
                            func: 'tableDeleteHead'
                        },
                        separator_drop3:
                        {
                            name: 'separator'
                        },
                        delete_column:
                        {
                            title: lang.delete_column,
                            func: 'tableDeleteColumn'
                        },
                        delete_row:
                        {
                            title: lang.delete_row,
                            func: 'tableDeleteRow'
                        },
                        delete_table:
                        {
                            title: lang.delete_table,
                            func: 'tableDeleteTable'
                        }
                    }
                },
                link: {
                    title: lang.link,
                    func: 'show',
                    dropdown:
                    {
                        link:
                        {
                            title: lang.link_insert,
                            func: 'linkShow'
                        },
                        unlink:
                        {
                            title: lang.unlink,
                            exec: 'unlink'
                        }
                    }
                },
                fontcolor:
                {
                    title: lang.fontcolor,
                    func: 'show'
                },
                backcolor:
                {
                    title: lang.backcolor,
                    func: 'show'
                },
                alignment:
                {
                    title: lang.alignment,
                    func: 'show',
                    dropdown:
                    {
                        alignleft:
                        {
                            title: lang.align_left,
                            func: 'alignmentLeft'
                        },
                        aligncenter:
                        {
                            title: lang.align_center,
                            func: 'alignmentCenter'
                        },
                        alignright:
                        {
                            title: lang.align_right,
                            func: 'alignmentRight'
                        },
                        justify:
                        {
                            title: lang.align_justify,
                            func: 'alignmentJustify'
                        }
                    }
                },
                alignleft:
                {
                    title: lang.align_left,
                    func: 'alignmentLeft'
                },
                aligncenter:
                {
                    title: lang.align_center,
                    func: 'alignmentCenter'
                },
                alignright:
                {
                    title: lang.align_right,
                    func: 'alignmentRight'
                },
                justify:
                {
                    title: lang.align_justify,
                    func: 'alignmentJustify'
                },
                horizontalrule:
                {
                    exec: 'inserthorizontalrule',
                    title: lang.horizontalrule
                }

            }
        },

        // CALLBACKS
        callback: function(type, event, data)
        {
            var callback = this.opts[ type + 'Callback' ];
            if ($.isFunction(callback))
            {
                if (event === false) return callback.call(this, data);
                else return callback.call(this, event, data);
            }
            else return data;
        },


        // DESTROY
        destroy: function()
        {
            clearInterval(this.autosaveInterval);

            $(window).off('.redactor');
            this.$element.off('.redactor').removeData('redactor');

            var html = this.get();

            if (this.opts.textareamode)
            {
                this.$box.after(this.$source);
                this.$box.remove();
                this.$source.val(html).show();
            }
            else
            {
                var $elem = this.$editor;
                if (this.opts.iframe) $elem = this.$element;

                this.$box.after($elem);
                this.$box.remove();

                $elem.removeClass('redactor_editor').removeClass('redactor_editor_wym').removeAttr('contenteditable').html(html).show();
            }

            if (this.opts.air)
            {
                $('.redactor_air').remove();
            }
        },

        // API GET
        getObject: function()
        {
            return $.extend({}, this);
        },
        getEditor: function()
        {
            return this.$editor;
        },
        getBox: function()
        {
            return this.$box;
        },
        getIframe: function()
        {
            return (this.opts.iframe) ? this.$frame : false;
        },
        getToolbar: function()
        {
            return this.$toolbar;
        },


        // CODE GET & SET
        get: function()
        {
            return this.$source.val();
        },
        getCodeIframe: function()
        {
            this.$editor.removeAttr('contenteditable').removeAttr('dir');
            var html = this.outerHtml(this.$frame.contents().children());
            this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction });

            return html;
        },
        set: function(html, strip, placeholderRemove)
        {
            html = html.toString();

            if (this.opts.fullpage) this.setCodeIframe(html);
            else this.setEditor(html, strip);

            if (placeholderRemove !== false) this.placeholderRemove();
        },
        setEditor: function(html, strip)
        {
            if (strip !== false)
            {
                html = this.cleanSavePreCode(html);
                html = this.cleanStripTags(html);
                html = this.cleanConvertProtected(html);
                html = this.cleanConvertInlineTags(html);

                if (this.opts.linebreaks === false) html = this.cleanConverters(html);
                else html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
            }

            html = this.cleanEmpty(html);

            this.$editor.html(html);
            this.sync();
        },
        setCodeIframe: function(html)
        {
            var doc = this.iframePage();
            this.$frame[0].src = "about:blank";

            html = this.cleanConvertProtected(html);
            html = this.cleanConvertInlineTags(html);
            html = this.cleanRemoveSpaces(html);

            doc.open();
            doc.write(html);
            doc.close();

            // redefine editor for fullpage mode
            if (this.opts.fullpage)
            {
                this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });
            }

            this.sync();

        },
        setFullpageOnInit: function(html)
        {
            html = this.cleanSavePreCode(html, true);
            html = this.cleanConverters(html);
            html = this.cleanEmpty(html);

            // set code
            this.$editor.html(html);
            this.sync();
        },

        // SYNC
        sync: function()
        {
            var html = '';

            this.cleanUnverified();

            if (this.opts.fullpage) html = this.getCodeIframe();
            else html = this.$editor.html();

            html = this.syncClean(html);
            html = this.cleanRemoveSpaces(html);
            html = this.cleanRemoveEmptyTags(html);

            // fix second level up ul, ol
            html = html.replace(/<\/li><(ul|ol)>([\w\W]*?)<\/(ul|ol)>/gi, '<$1>$2</$1></li>');

            if ($.trim(html) === '<br>') html = '';

            if (html !== '') html = this.cleanHtml(html);

            // before callback
            html = this.callback('syncBefore', false, html);

            this.$source.val(html);

            // onchange & after callback
            this.callback('syncAfter', false, html);

            if (this.start === false)
            {
                this.callback('change', false, html);
            }

        },
        syncClean: function(html)
        {
            if (!this.opts.fullpage) html = this.cleanStripTags(html);

            html = $.trim(html);

            // removeplaceholder
            html = this.placeholderRemoveFromCode(html);

            // remove space
            html = html.replace(/&#x200b;/gi, '');
            html = html.replace(/&#8203;/gi, '');
            html = html.replace(/&nbsp;/gi, ' ');

            // link nofollow
            if (this.opts.linkNofollow)
            {
                html = html.replace(/<a(.*?)rel="nofollow"(.*?)>/gi, '<a$1$2>');
                html = html.replace(/<a(.*?)>/gi, '<a$1 rel="nofollow">');
            }

            // php code fix
            html = html.replace('<!--?php', '<?php');
            html = html.replace('?-->', '?>');

            html = html.replace(/ data-tagblock=""/gi, '');
            html = html.replace(/<br\s?\/?>\n?<\/(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)>/gi, '</$1>');

            // remove image resize
            html = html.replace(/<span(.*?)id="redactor-image-box"(.*?)>([\w\W]*?)<img(.*?)><\/span>/i, '$3<img$4>');
            html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?)>(.*?)<\/span>/i, '');
            html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?)>(.*?)<\/span>/i, '');

            // remove spans
            html = html.replace(/<span\s*?>([\w\W]*?)<\/span>/gi, '$1');
            html = html.replace(/<span(.*?)data-redactor="verified"(.*?)>([\w\W]*?)<\/span>/gi, '<span$1$2>$3</span>');
            html = html.replace(/<span(.*?)data-redactor-inlineMethods=""(.*?)>([\w\W]*?)<\/span>/gi, '<span$1$2>$3</span>' );
            html = html.replace(/<span\s*?>([\w\W]*?)<\/span>/gi, '$1');
            html = html.replace(/<span\s*?id="selection-marker(.*?)"(.*?)>([\w\W]*?)<\/span>/gi, '');
            html = html.replace(/<span\s*?>([\w\W]*?)<\/span>/gi, '$1');
            html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');

            html = this.cleanReConvertProtected(html);

            return html;
        },


        // BUILD
        buildStart: function()
        {
            // content
            this.content = '';

            // container
            this.$box = $('<div class="redactor_box" />');

            // textarea test
            if (this.$source[0].tagName === 'TEXTAREA') this.opts.textareamode = true;

            // mobile
            if (this.opts.mobile === false && this.isMobile())
            {
                this.buildMobile();
            }
            else
            {
                // get the content at the start
                this.buildContent();

                if (this.opts.iframe)
                {
                    // build as iframe
                    this.opts.autoresize = false;
                    this.iframeStart();
                }
                else if (this.opts.textareamode) this.buildFromTextarea();
                else this.buildFromElement();

                // options and final setup
                if (!this.opts.iframe)
                {
                    this.buildOptions();
                    this.buildAfter();
                }
            }
        },
        buildMobile: function()
        {
            if (!this.opts.textareamode)
            {
                this.$editor = this.$source;
                this.$editor.hide();
                this.$source = this.buildCodearea(this.$editor);
                this.$source.val(this.content);
            }

            this.$box.insertAfter(this.$source).append(this.$source);
        },
        buildContent: function()
        {
            if (this.opts.textareamode) this.content = $.trim(this.$source.val());
            else this.content = $.trim(this.$source.html());
        },
        buildFromTextarea: function()
        {
            this.$editor = $('<div />');
            this.$box.insertAfter(this.$source).append(this.$editor).append(this.$source);

            // enable
            this.buildAddClasses(this.$editor);
            this.buildEnable();
        },
        buildFromElement: function()
        {
            this.$editor = this.$source;
            this.$source = this.buildCodearea(this.$editor);
            this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$source);

            // enable
            this.buildEnable();
        },
        buildCodearea: function($source)
        {
            return $('<textarea />').attr('name', $source.attr('id')).css('height', this.sourceHeight);
        },
        buildAddClasses: function(el)
        {
            // append textarea classes to editable layer
            $.each(this.$source.get(0).className.split(/\s+/), function(i,s)
            {
                el.addClass('redactor_' + s);
            });
        },
        buildEnable: function()
        {
            this.$editor.addClass('redactor_editor').attr({ 'contenteditable': true, 'dir': this.opts.direction });
            this.$source.attr('dir', this.opts.direction).hide();

            // set code
            this.set(this.content, true, false);
        },
        buildOptions: function()
        {
            var $source = this.$editor;
            if (this.opts.iframe) $source = this.$frame;

            // options
            if (this.opts.tabindex) $source.attr('tabindex', this.opts.tabindex);
            if (this.opts.minHeight) $source.css('min-height', this.opts.minHeight + 'px');
            if (this.opts.wym) this.$editor.addClass('redactor_editor_wym');
            if (!this.opts.autoresize) $source.css('height', this.sourceHeight);
        },
        buildAfter: function()
        {
            this.start = false;

            // load toolbar
            if (this.opts.toolbar)
            {
                this.opts.toolbar = this.initToolbar(this.opts.curLang);
                this.toolbarBuild();
            }

            // modal templates
            this.modalTemplatesInit();

            // plugins
            this.buildPlugins();

            // enter, tab, etc.
            this.buildBindKeyboard();

            // autosave
            if (this.opts.autosave) this.autosave();

            // observers
            setTimeout($.proxy(this.observeStart, this), 4);

            // FF fix
            if (this.browser('mozilla'))
            {
                try {
                    this.document.execCommand('enableObjectResizing', false, false);
                    this.document.execCommand('enableInlineTableEditing', false, false);
                } catch (e) {}
            }

            // focus
            if (this.opts.focus) setTimeout($.proxy(this.focus, this), 100);

            // code mode
            if (!this.opts.visual)
            {
                setTimeout($.proxy(function()
                {
                    this.opts.visual = true;
                    this.toggle(false);

                }, this), 200);
            }

            // init callback
            this.callback('init');
        },
        buildBindKeyboard: function()
        {
            if (this.opts.dragUpload)
            {
                this.$editor.on('drop.redactor', $.proxy(this.buildEventDrop, this));
            }

            this.$editor.on('paste.redactor', $.proxy(this.buildEventPaste, this));
            this.$editor.on('keydown.redactor', $.proxy(this.buildEventKeydown, this));
            this.$editor.on('keyup.redactor', $.proxy(this.buildEventKeyup, this));

            // focus callback
            if ($.isFunction(this.opts.focusCallback))
            {
                this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
            }

            // blur callback
            if ($.isFunction(this.opts.blurCallback))
            {
                this.$editor.on('blur.redactor', $.proxy(this.opts.blurCallback, this));
            }

        },
        buildEventDrop: function(e)
        {
            e = e.originalEvent || e;

            if (window.FormData === undefined)
            {
                return true;
            }

            var length = e.dataTransfer.files.length;
            if (length == 0)
            {
                return true;
            }

            e.preventDefault();

            var file = e.dataTransfer.files[0];

            if (this.opts.dnbImageTypes !== false && this.opts.dnbImageTypes.indexOf(file.type) == -1)
            {
                return true;
            }

            this.bufferSet();
            var $img = $('<img>');

            var progress = $('<div id="redactor-progress-drag" class="redactor-progress redactor-progress-striped"><div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div></div>');
            $(document.body).append(progress);

            var fd = new FormData();
            fd.append('file', file);

            $.ajax({
                url: this.opts.imageUpload,
                dataType: 'html',
                data: fd,
                cache: false,
                contentType: false,
                processData: false,
                type: 'POST',
                success: $.proxy(function(data)
                {
                    progress.fadeOut('slow', function()
                    {
                        $(this).remove();
                    });

                    var json = $.parseJSON(data);

                    $img.attr('src', json.filelink).attr('id', 'drag-image-marker');

                    this.insertNodeToCaretPositionFromPoint(e, $img[0]);

                    var image = $(this.$editor.find('img#drag-image-marker'));

                    if (image.length) image.removeAttr('id');
                    else image = false;

                    this.sync();
                    this.observeImages();

                    // upload callback
                    if (image)
                    {
                        this.callback('imageUpload', image, json);
                    }

                }, this),
                error: $.proxy(function(json)
                {
                    this.callback('imageUploadError', json);

                }, this)
            });

        },
        buildEventPaste: function(e)
        {
            var oldsafari = false;
            if (this.browser('webkit') && navigator.userAgent.indexOf('Chrome') === -1)
            {
                var arr = this.browser('version').split('.');
                if (arr[0] < 536) oldsafari = true;
            }

            if (oldsafari) return true;

            // paste except opera (not webkit)
            if (this.browser('opera')) return true;

            // clipboard upload
            if (this.opts.clipboardUpload && this.buildEventClipboardUpload(e)) return true;

            if (this.opts.cleanup)
            {
                rtePaste = true;

                this.selectionSave();

                if (!this.selectall)
                {
                    if (this.opts.autoresize === true )
                    {
                        this.$editor.height(this.$editor.height());
                        this.saveScroll = this.document.body.scrollTop;
                    }
                    else
                    {
                        this.saveScroll = this.$editor.scrollTop();
                    }
                }

                var frag = this.extractContent();

                setTimeout($.proxy(function()
                {
                    var pastedFrag = this.extractContent();
                    this.$editor.append(frag);

                    this.selectionRestore();

                    var html = this.getFragmentHtml(pastedFrag);
                    this.pasteClean(html);

                    if (this.opts.autoresize === true) this.$editor.css('height', 'auto');

                }, this), 1);
            }
        },
        buildEventClipboardUpload: function(e)
        {
            var event = e.originalEvent || e;
            this.clipboardFilePaste = false;
            if (event.clipboardData.items)
            {
                var file = event.clipboardData.items[0].getAsFile();
                if (file !== null)
                {
                    this.bufferSet();
                    this.clipboardFilePaste = true;

                    var reader = new FileReader();
                    reader.onload = $.proxy(this.pasteClipboardUpload, this);
                    reader.readAsDataURL(file);

                    return true;
                }
            }

            return false;

        },
        buildEventKeydown: function(e)
        {
            if (rtePaste) return false;

            var key = e.which;
            var ctrl = e.ctrlKey || e.metaKey;
            var parent = this.getParent();
            var current = this.getCurrent();
            var block = this.getBlock();
            var pre = false;

            this.callback('keydown', e);

            this.imageResizeHide(false);

            // pre & down
            if ((parent && $(parent).get(0).tagName === 'PRE') || (current && $(current).get(0).tagName === 'PRE'))
            {
                pre = true;
                if (key === this.keyCode.DOWN) this.insertAfterLastElement(block);
            }

            // down
            if (key === this.keyCode.DOWN)
            {
                if (parent && $(parent).get(0).tagName === 'BLOCKQUOTE') this.insertAfterLastElement(parent);
                if (current && $(current).get(0).tagName === 'BLOCKQUOTE') this.insertAfterLastElement(current);
            }

            // shortcuts setup
            if (ctrl && !e.shiftKey) this.shortcuts(e, key);

            // buffer setup
            if (ctrl && key === 90 && !e.shiftKey && !e.altKey) // z key
            {
                e.preventDefault();
                if (this.opts.buffer.length) this.bufferUndo();
                else this.document.execCommand('undo', false, false);
                return;
            }
            // undo
            else if (ctrl && key === 90 && e.shiftKey && !e.altKey)
            {
                e.preventDefault();
                if (this.opts.rebuffer.length != 0) this.bufferRedo();
                else this.document.execCommand('redo', false, false);
                return;
            }

            // select all
            if (ctrl && key === 65) this.selectall = true;
            else if (key != this.keyCode.LEFT_WIN && !ctrl) this.selectall = false;

            // enter
            if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey )
            {
                // In ie, opera in the tables are created paragraphs, fix it.
                if (parent.nodeType == 1 && (parent.tagName == 'TD' || parent.tagName == 'TH'))
                {
                    e.preventDefault();
                    this.bufferSet();
                    this.insertNode(document.createElement('br'));
                    this.callback('enter', e);
                    return false;
                }

                // pre
                if (pre === true)
                {
                    e.preventDefault();
                    this.bufferSet();
                    var html = $(current).parent().text();
                    this.insertNode(document.createTextNode('\n'));
                    if (html.search(/\s$/) == -1)
                    {
                        this.insertNode(document.createTextNode('\n'));
                    }

                    this.sync();
                    this.callback('enter', e);
                    return false;
                }
                else
                {
                    if (!this.opts.linebreaks)
                    {
                        // replace div to p
                        if (block && this.opts.rBlockTest.test(block.tagName))
                        {
                            // hit enter
                            this.bufferSet();

                            setTimeout($.proxy(function()
                            {
                                var blockElem = this.getBlock();
                                if (blockElem.tagName === 'DIV' && !$(blockElem).hasClass('redactor_editor'))
                                {
                                    var node = $('<p>' + this.opts.invisibleSpace + '</p>');
                                    $(blockElem).replaceWith(node);
                                    this.selectionStart(node);
                                }

                            }, this), 1);
                        }
                        else if (block === false)
                        {
                            // hit enter
                            this.bufferSet();

                            var node = $('<p>' + this.opts.invisibleSpace + '</p>');
                            this.insertNode(node[0]);
                            this.selectionStart(node);
                            this.callback('enter', e);
                            return false;
                        }

                    }

                    if (this.opts.linebreaks)
                    {
                        // replace div to br
                        if (block && this.opts.rBlockTest.test(block.tagName))
                        {
                            // hit enter
                            this.bufferSet();

                            setTimeout($.proxy(function()
                            {
                                var blockElem = this.getBlock();
                                if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && !$(blockElem).hasClass('redactor_editor'))
                                {
                                    this.replaceLineBreak(blockElem);
                                }

                            }, this), 1);
                        }
                        else
                        {
                            return this.buildEventKeydownInsertLineBreak(e);
                        }
                    }

                    // blockquote, figcaption
                    if (block.tagName == 'BLOCKQUOTE' || block.tagName == 'FIGCAPTION')
                    {
                        return this.buildEventKeydownInsertLineBreak(e);
                    }

                }

                this.callback('enter', e);
            }
            else if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey)) // Shift+Enter or Ctrl+Enter
            {
                this.bufferSet();

                e.preventDefault();
                this.insertLineBreak();
            }

            // tab
            if (key === this.keyCode.TAB && this.opts.shortcuts )
            {
                if (!this.opts.tabFocus) return true;
                if (this.isEmpty(this.get())) return true;

                e.preventDefault();

                if (pre === true && !e.shiftKey)
                {
                    this.bufferSet();
                    this.insertNode(document.createTextNode('\t'));
                    this.sync();
                    return false;
                }
                else
                {
                    if (!e.shiftKey) this.indentingIndent();
                    else this.indentingOutdent();
                }

                return false;
            }

            // delete zero-width space before the removing
            if (key === this.keyCode.BACKSPACE)
            {
                if (typeof current.tagName !== 'undefined' && /^(H[1-6])$/i.test(current.tagName))
                {
                    var node;
                    if (this.opts.linebreaks === false) node = $('<p>' + this.opts.invisibleSpace + '</p>');
                    else node = $('<br>' + this.opts.invisibleSpace);

                    $(current).replaceWith(node);
                    this.selectionStart(node);
                }

                if (typeof current.nodeValue !== 'undefined' && current.nodeValue !== null)
                {
                    var value = $.trim(current.nodeValue.replace(/[^\u0000-~]/g, ''));
                    if (current.remove && current.nodeType === 3 && current.nodeValue.charCodeAt(0) == 8203 && value == '')
                    {
                        current.remove();
                    }
                }
            }
        },
        buildEventKeydownInsertLineBreak: function(e)
        {
            this.bufferSet();
            e.preventDefault();
            this.insertLineBreak();
            this.callback('enter', e);
            return;
        },
        buildEventKeyup: function(e)
        {
            if (rtePaste) return false;

            var key = e.which;
            var parent = this.getParent();
            var current = this.getCurrent();

            // replace to p before / after the table or body
            if (!this.opts.linebreaks && current.nodeType == 3 && (parent == false || parent.tagName == 'BODY'))
            {
                var node = $('<p>').append($(current).clone());
                $(current).replaceWith(node);
                var next = $(node).next();
                if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
                {
                    next.remove();
                }
                this.selectionEnd(node);
            }

            // convert links
            if ((this.opts.convertLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER)
            {
                this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks);

                if (this.opts.convertImageLinks)
                {
                    this.observeImages();
                }
            }

            // if empty
            if (this.opts.linebreaks === false && (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE))
            {
                return this.formatEmpty(e);
            }

            this.callback('keyup', e);
            this.sync();
        },
        buildPlugins: function()
        {
            if (!this.opts.plugins ) return;

            $.each(this.opts.plugins, $.proxy(function(i, s)
            {
                if (RedactorPlugins[s])
                {
                    $.extend(this, RedactorPlugins[s]);
                    if ($.isFunction( RedactorPlugins[ s ].init)) this.init();
                }

            }, this ));
        },

        // IFRAME
        iframeStart: function()
        {
            this.iframeCreate();

            if (this.opts.textareamode) this.iframeAppend(this.$source);
            else
            {
                this.$sourceOld = this.$source.hide();
                this.$source = this.buildCodearea(this.$sourceOld);
                this.iframeAppend(this.$sourceOld);
            }
        },
        iframeAppend: function(el)
        {
            this.$source.attr('dir', this.opts.direction).hide();
            this.$box.insertAfter(el).append(this.$frame).append(this.$source);
        },
        iframeCreate: function()
        {
            this.$frame = $('<iframe style="width: 100%;" frameborder="0" />').one('load', $.proxy(function()
            {
                if (this.opts.fullpage)
                {
                    this.iframePage();

                    if (this.content === '') this.content = this.opts.invisibleSpace;

                    this.$frame.contents()[0].write(this.content);
                    this.$frame.contents()[0].close();

                    var timer = setInterval($.proxy(function()
                    {
                        if (this.$frame.contents().find('body').html())
                        {
                            clearInterval(timer);
                            this.iframeLoad();
                        }

                    }, this), 0);
                }
                else this.iframeLoad();

            }, this));
        },
        iframeDoc: function()
        {
            return this.$frame[0].contentWindow.document;
        },
        iframePage: function()
        {
            var doc = this.iframeDoc();
            if (doc.documentElement) doc.removeChild(doc.documentElement);

            return doc;
        },
        iframeAddCss: function(css)
        {
            css = css || this.opts.css;

            if (this.isString(css))
            {
                this.$frame.contents().find('head').append('<link rel="stylesheet" href="' + css + '" />');
            }

            if ($.isArray(css))
            {
                $.each(css, $.proxy(function(i, url)
                {
                    this.iframeAddCss(url);

                }, this));
            }
        },
        iframeLoad: function()
        {
            this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });

            // set document & window
            if (this.$editor[0])
            {
                this.document = this.$editor[0].ownerDocument;
                this.window = this.document.defaultView || window;
            }

            // iframe css
            this.iframeAddCss();

            if (this.opts.fullpage) this.setFullpageOnInit(this.$editor.html());
            else this.set(this.content, true, false);

            this.buildOptions();
            this.buildAfter();
        },

        // PLACEHOLDER
        placeholderStart: function(html)
        {
            if (this.isEmpty(html))
            {
                if (this.$element.attr('placeholder')) this.opts.placeholder = this.$element.attr('placeholder');
                if (this.opts.placeholder === '') this.opts.placeholder = false;

                if (this.opts.placeholder !== false)
                {
                    this.opts.focus = false;
                    this.$editor.one('focus.redactor_placeholder', $.proxy(this.placeholderFocus, this));

                    return $('<span class="redactor_placeholder" data-redactor="verified">').attr('contenteditable', false).text(this.opts.placeholder);
                }
            }

            return false;
        },
        placeholderFocus: function()
        {
            this.$editor.find('span.redactor_placeholder').remove();

            var html = '';
            if (this.opts.linebreaks === false) html = this.opts.emptyHtml;

            this.$editor.off('focus.redactor_placeholder');
            this.$editor.html(html);

            if (this.opts.linebreaks === false)
            {
                // place the cursor inside emptyHtml
                this.selectionStart(this.$editor.children()[0]);
            }

            this.sync();
        },
        placeholderRemove: function()
        {
            this.opts.placeholder = false;
            this.$editor.find('span.redactor_placeholder').remove();
            this.$editor.off('focus.redactor_placeholder');
        },
        placeholderRemoveFromCode: function(html)
        {
            return html.replace(/<span class="redactor_placeholder"(.*?)>(.*?)<\/span>/i, '');
        },

        // SHORTCUTS
        shortcuts: function(e, key)
        {

            if (!this.opts.shortcuts) return;

            if (!e.altKey)
            {
                if (key === 77) this.shortcutsLoad(e, 'removeFormat'); // Ctrl + m
                else if (key === 66) this.shortcutsLoad(e, 'bold'); // Ctrl + b
                else if (key === 73) this.shortcutsLoad(e, 'italic'); // Ctrl + i

                else if (key === 74) this.shortcutsLoad(e, 'insertunorderedlist'); // Ctrl + j
                else if (key === 75) this.shortcutsLoad(e, 'insertorderedlist'); // Ctrl + k

                else if (key === 72) this.shortcutsLoad(e, 'superscript'); // Ctrl + h
                else if (key === 76) this.shortcutsLoad(e, 'subscript'); // Ctrl + l
            }
            else
            {
                if (key === 48) this.shortcutsLoadFormat(e, 'p'); // ctrl + alt + 0
                else if (key === 49) this.shortcutsLoadFormat(e, 'h1'); // ctrl + alt + 1
                else if (key === 50) this.shortcutsLoadFormat(e, 'h2'); // ctrl + alt + 2
                else if (key === 51) this.shortcutsLoadFormat(e, 'h3'); // ctrl + alt + 3
                else if (key === 52) this.shortcutsLoadFormat(e, 'h4'); // ctrl + alt + 4
                else if (key === 53) this.shortcutsLoadFormat(e, 'h5'); // ctrl + alt + 5
                else if (key === 54) this.shortcutsLoadFormat(e, 'h6'); // ctrl + alt + 6

            }

        },
        shortcutsLoad: function(e, cmd)
        {
            e.preventDefault();
            this.execCommand(cmd, false);
        },
        shortcutsLoadFormat: function(e, cmd)
        {
            e.preventDefault();
            this.formatBlocks(cmd);
        },

        // FOCUS
        focus: function()
        {
            if (!this.browser('opera')) this.window.setTimeout($.proxy(this.focusSet, this, true), 1);
            else this.$editor.focus();
        },
        focusEnd: function()
        {
            this.focusSet();
        },
        focusSet: function(collapse)
        {
            this.$editor.focus();

            var range = this.getRange();
            range.selectNodeContents(this.$editor[0]);

            // collapse - controls the position of focus: the beginning (true), at the end (false).
            range.collapse(collapse || false);

            var sel = this.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        },


        // TOGGLE
        toggle: function(direct)
        {
            var html;
            if (this.opts.visual)
            {
                if (direct !== false) this.selectionSave();

                var height = null;
                if (this.opts.iframe)
                {
                    height = this.$frame.height();
                    if (this.opts.fullpage) this.$editor.removeAttr('contenteditable');
                    this.$frame.hide();
                }
                else
                {
                    height = this.$editor.innerHeight();
                    this.$editor.hide();
                }

                html = this.$source.val();
                this.modified = html;

                this.$source.height(height).show().focus();

                // textarea indenting
                this.$source.on('keydown.redactor-textarea', function (e)
                {
                    if (e.keyCode === 9)
                    {
                        var $el = $(this);
                        var start = $el.get(0).selectionStart;
                        $el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
                        $el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
                        return false;
                    }
                });

                this.buttonInactiveVisual();
                this.buttonActive('html');
                this.opts.visual = false;

            }
            else
            {
                html = this.$source.hide().val();

                if (typeof this.modified !== 'undefined')
                {
                    this.modified = this.cleanRemoveSpaces(this.modified, false) !== this.cleanRemoveSpaces(html, false);
                }

                if (this.modified)
                {
                    // don't remove the iframe even if cleared all.
                    if (this.opts.fullpage && html === '') this.setFullpageOnInit(html);
                    else
                    {
                        this.set(html);
                        if (this.opts.fullpage) this.buildBindKeyboard();
                    }
                }

                if (this.opts.iframe) this.$frame.show();
                else this.$editor.show();

                if (this.opts.fullpage ) this.$editor.attr('contenteditable', true );

                this.$source.off('keydown.redactor-textarea');

                this.$editor.focus();
                this.selectionRestore();

                this.observeStart();
                this.buttonActiveVisual();
                this.buttonInactive('html');
                this.opts.visual = true;
            }
        },


        // AUTOSAVE
        autosave: function()
        {
            var savedHtml = false;
            this.autosaveInterval = setInterval($.proxy(function()
            {
                var html = this.get();
                if (savedHtml !== html)
                {
                    $.ajax({
                        url: this.opts.autosave,
                        type: 'post',
                        data: this.$source.attr('name') + '=' + escape(encodeURIComponent(html)),
                        success: $.proxy(function(data)
                        {
                            this.callback('autosave', false, data);
                            savedHtml = html;

                        }, this)
                    });
                }
            }, this), this.opts.autosaveInterval*1000);
        },

        // TOOLBAR
        toolbarBuild: function()
        {
            // extend buttons
            if (this.opts.air)
            {
                this.opts.buttons = this.opts.airButtons;
            }
            else
            {
                if (!this.opts.buttonSource)
                {
                    var index = this.opts.buttons.indexOf('html'), next = this.opts.buttons[index + 1];
                    this.opts.buttons.splice(index, 1);
                    if (next === '|') this.opts.buttons.splice(index, 1);
                }
            }

            $.extend(this.opts.toolbar, this.opts.buttonsCustom);
            $.each(this.opts.buttonsAdd, $.proxy(function(i, s)
            {
                this.opts.buttons.push(s);

            }, this));

            // formatting tags
            if (this.opts.toolbar)
            {
                $.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
                {
                    if ($.inArray(i, this.opts.formattingTags ) == '-1') delete this.opts.toolbar.formatting.dropdown[i];

                }, this));
            }

            // if no buttons don't create a toolbar
            if (this.opts.buttons.length === 0) return false;

            // air enable
            this.airEnable();

            // toolbar build
            this.$toolbar = $('<ul>').addClass('redactor_toolbar').attr('id', 'redactor_toolbar_' + this.uuid);

            if (this.opts.air)
            {
                // air box
                this.$air = $('<div class="redactor_air">').attr('id', 'redactor_air_' + this.uuid).hide();
                this.$air.append(this.$toolbar);
                $('body').append(this.$air);
            }
            else
            {
                if (this.opts.toolbarExternal) $(this.opts.toolbarExternal).html(this.$toolbar);
                else this.$box.prepend(this.$toolbar);
            }

            $.each(this.opts.buttons, $.proxy(function(i, btnName)
            {


                // separator
                if ( btnName === '|' ) this.$toolbar.append($(this.opts.buttonSeparator));
                else if(this.opts.toolbar[btnName])
                {
                    var btnObject = this.opts.toolbar[btnName];
                    if (this.opts.fileUpload === false && btnName === 'file') return true;
                    this.$toolbar.append( $('<li>').append(this.buttonBuild(btnName, btnObject)));
                }

            }, this));

            this.$toolbar.find('a').attr('tabindex', '-1');

            // fixed
            if (this.opts.toolbarFixed)
            {
                this.toolbarObserveScroll();
                $(this.opts.toolbarFixedTarget).on('scroll.redactor', $.proxy(this.toolbarObserveScroll, this));
            }

            // buttons response
            if (this.opts.activeButtons)
            {
                var buttonActiveObserver = $.proxy(this.buttonActiveObserver, this);
                this.$editor.on('mouseup.redactor keyup.redactor', buttonActiveObserver);
            }
        },
        toolbarObserveScroll: function()
        {
            var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
            var boxTop = this.$box.offset().top;
            var left = 0;

            var end = boxTop + this.$box.height() + 40;

            if (scrollTop > boxTop)
            {
                var width = '100%';
                if (this.opts.toolbarFixedBox)
                {
                    left = this.$box.offset().left;
                    width = this.$box.innerWidth();
                    this.$toolbar.addClass('toolbar_fixed_box');
                }

                this.toolbarFixed = true;
                this.$toolbar.css({
                    position: 'fixed',
                    width: width,
                    zIndex: 1005,
                    top: this.opts.toolbarFixedTopOffset + 'px',
                    left: left
                });

                if (scrollTop < end) this.$toolbar.css('visibility', 'visible');
                else this.$toolbar.css('visibility', 'hidden');
            }
            else
            {
                this.toolbarFixed = false;
                this.$toolbar.css({
                    position: 'relative',
                    width: 'auto',
                    top: 0,
                    left: left
                });

                if (this.opts.toolbarFixedBox) this.$toolbar.removeClass('toolbar_fixed_box');
            }
        },

        // AIR
        airEnable: function()
        {
            if (!this.opts.air) return;

            this.$editor.on('mouseup.redactor keyup.redactor', this, $.proxy(function(e)
            {
                var text = this.getSelectionText();

                if (e.type === 'mouseup' && text != '') this.airShow(e);

                if (e.type === 'keyup' && e.shiftKey && text != '')
                {
                    var $focusElem = $(this.getElement(this.getSelection().focusNode)), offset = $focusElem.offset();
                    offset.height = $focusElem.height();
                    this.airShow(offset, true);
                }

            }, this));
        },
        airShow: function (e, keyboard)
        {
            if (!this.opts.air) return;

            var left, top;
            $('.redactor_air').hide();

            if (keyboard)
            {
                left = e.left;
                top = e.top + e.height + 14;

                if (this.opts.iframe)
                {
                    top += this.$box.position().top - $(this.document).scrollTop();
                    left += this.$box.position().left;
                }
            }
            else
            {
                var width = this.$air.innerWidth();

                left = e.clientX;
                if ($(this.document).width() < (left + width)) left -= width;

                top = e.clientY + 14;
                if (this.opts.iframe)
                {
                    top += this.$box.position().top;
                    left += this.$box.position().left;
                }
                else top += $( this.document ).scrollTop();
            }

            this.$air.css({
                left: left + 'px',
                top: top + 'px'
            }).show();

            this.airBindHide();
        },
        airBindHide: function()
        {
            if (!this.opts.air) return;

            var hideHandler = $.proxy(function(doc)
            {
                $(doc).on('mousedown.redactor', $.proxy(function(e)
                    {
                        if ($( e.target ).closest(this.$toolbar).length === 0)
                        {
                            this.$air.fadeOut(100);
                            this.selectionRemove();
                            $(doc).off(e);
                        }

                    }, this)).on('keydown.redactor', $.proxy(function(e)
                    {
                        if (e.which === this.keyCode.ESC)
                        {
                            this.getSelection().collapseToStart();
                        }

                        this.$air.fadeOut(100);
                        $(doc).off(e);

                    }, this));
            }, this);

            // Hide the toolbar at events in all documents (iframe)
            hideHandler(document);
            if (this.opts.iframe) hideHandler(this.document);
        },
        airBindMousemoveHide: function()
        {
            if (!this.opts.air) return;

            var hideHandler = $.proxy(function(doc)
            {
                $(doc).on('mousemove.redactor', $.proxy(function(e)
                {
                    if ($( e.target ).closest(this.$toolbar).length === 0)
                    {
                        this.$air.fadeOut(100);
                        $(doc).off(e);
                    }

                }, this));
            }, this);

            // Hide the toolbar at events in all documents (iframe)
            hideHandler(document);
            if (this.opts.iframe) hideHandler(this.document);
        },

        // COLORPICKER
        pickerBuild: function($dropdown, key)
        {
            $dropdown.width(210);

            var rule = 'color';
            if (key === 'backcolor') rule = 'background-color';

            var len = this.opts.colors.length;
            var _self = this;
            for (var i = 0; i < len; i++)
            {
                var color = this.opts.colors[i];

                var $swatch = $('<a rel="' + color + '" href="javascript:;" class="redactor_color_link"></a>').css({ 'backgroundColor': color });
                $dropdown.append($swatch);

                $swatch.on('click', function()
                {
                    var type = $(this).attr('rel');
                    if (key === 'backcolor') type = $(this).css('background-color');

                    _self.pickerSet(rule, type);
                });
            }

            var $elNone = $('<a href="javascript:;" class="redactor_color_none"></a>')
                .html(this.opts.curLang.none)
                .on('click', function()
                {
                    _self.pickerSet(rule, false);
                });

            $dropdown.append($elNone);
        },

        pickerSet: function(rule, type)
        {
            this.bufferSet();

            this.$editor.focus();
            this.inlineRemoveStyle(rule);
            if (type !== false) this.inlineSetStyle(rule, type);
            if (this.opts.air) this.$air.fadeOut(100);
            this.sync();
        },

        // DROPDOWNS
        dropdownBuild: function($dropdown, dropdownObject)
        {
            $.each(dropdownObject, $.proxy(function(btnName, btnObject)
            {
                if (!btnObject.className) btnObject.className = '';

                var $item;
                if (btnObject.name === 'separator') $item = $('<a class="redactor_separator_drop">');
                else
                {
                    $item = $('<a href="javascript:;" class="' + btnObject.className + ' redactor_dropdown_' + btnName + '">' + btnObject.title + '</a>');
                    $item.on('click', $.proxy(function(e)
                    {
                        if (e.preventDefault) e.preventDefault();
                        if (this.browser('msie')) e.returnValue = false;

                        if (btnObject.callback) btnObject.callback.call(this, btnName, $item, btnObject, e);
                        if (btnObject.exec) this.execCommand(btnObject.exec, btnName);
                        if (btnObject.func) this[btnObject.func](btnName);

                        this.buttonActiveObserver();
                        if (this.opts.air) this.$air.fadeOut(100);

                    }, this));
                }

                $dropdown.append($item);

            }, this));
        },
        dropdownShow: function (e, $dropdown, key)
        {
            if (!this.opts.visual)
            {
                e.preventDefault();
                return false;
            }

            if (this.buttonGet(key).hasClass('dropact')) this.dropdownHideAll();
            else
            {
                this.dropdownHideAll();

                this.buttonActive(key);
                this.buttonGet(key).addClass('dropact');

                var keyPosition = this.buttonGet(key).position();
                if (this.toolbarFixed)
                {
                    keyPosition = this.buttonGet(key).offset();
                }

                var left = keyPosition.left + 'px';
                var btnHeight = 29;

                if (this.opts.air)
                {
                    $dropdown.css({ position: 'absolute', left: left, top: btnHeight + 'px' }).show();
                }
                else if (this.opts.toolbarFixed && this.toolbarFixed)
                {
                    $dropdown.css({ position: 'fixed', left: left, top: btnHeight + 'px' }).show();
                }
                else
                {
                    $dropdown.css({ position: 'absolute', left: left, top: keyPosition.top + btnHeight + 'px' }).show();
                }
            }

            var hdlHideDropDown = $.proxy(function(e)
            {
                this.dropdownHide(e, $dropdown);

            }, this);

            $(document).one('click', hdlHideDropDown);
            this.$editor.one('click', hdlHideDropDown);

            e.stopPropagation();
        },
        dropdownHideAll: function()
        {
            this.$toolbar.find('a.dropact').removeClass('redactor_act').removeClass('dropact');
            $('.redactor_dropdown').hide();
        },
        dropdownHide: function (e, $dropdown)
        {
            if (!$(e.target).hasClass('dropact'))
            {
                $dropdown.removeClass('dropact');
                this.dropdownHideAll();
            }
        },

        // BUTTONS
        buttonBuild: function(btnName, btnObject)
        {
            var $button = $('<a href="javascript:;" title="' + btnObject.title + '" class="redactor_btn redactor_btn_' + btnName + '"></a>');
            var $dropdown = $('<div class="redactor_dropdown" style="display: none;">');

            $button.on('click', $.proxy(function(e)
            {
                if (e.preventDefault) e.preventDefault();
                if (this.browser('msie')) e.returnValue = false;

                if ($button.hasClass('redactor_button_disabled')) return false;

                if (this.isFocused() === false && !btnObject.exec)
                {
                    this.$editor.focus();
                }

                if (btnObject.exec)
                {
                    this.$editor.focus();
                    this.execCommand(btnObject.exec, btnName);
                    this.airBindMousemoveHide();

                }
                else if (btnObject.func && btnObject.func !== 'show')
                {
                    this[btnObject.func](btnName);
                    this.airBindMousemoveHide();

                }
                else if (btnObject.callback)
                {
                    btnObject.callback.call(this, btnName, $button, btnObject, e);
                    this.airBindMousemoveHide();

                }
                else if (btnName === 'backcolor' || btnName === 'fontcolor' || btnObject.dropdown)
                {
                    this.dropdownShow(e, $dropdown, btnName);
                }

                this.buttonActiveObserver(false, btnName);

            }, this));

            // dropdown
            if (btnName === 'backcolor' || btnName === 'fontcolor' || btnObject.dropdown)
            {
                $dropdown.appendTo(this.$toolbar);

                if ( btnName === 'backcolor' || btnName === 'fontcolor') this.pickerBuild($dropdown, btnName);
                else this.dropdownBuild($dropdown, btnObject.dropdown);
            }

            return $button;
        },
        buttonGet: function(key)
        {
            if (!this.opts.toolbar) return false;
            return $(this.$toolbar.find('a.redactor_btn_' + key));
        },
        buttonActiveToggle: function(key)
        {
            var btn = this.buttonGet(key);

            if (btn.hasClass('redactor_act')) btn.removeClass('redactor_act');
            else btn.addClass('redactor_act');
        },
        buttonActive: function(key)
        {
            this.buttonGet(key).addClass('redactor_act');
        },
        buttonInactive: function(key)
        {
            this.buttonGet(key).removeClass('redactor_act');
        },
        buttonInactiveAll: function(btnName)
        {
            $.each(this.opts.toolbar, $.proxy(function(k)
            {
                if (k != btnName) this.buttonInactive(k);

            }, this));
        },
        buttonActiveVisual: function()
        {
            this.$toolbar.find('a.redactor_btn').not('a.redactor_btn_html').removeClass('redactor_button_disabled');
        },
        buttonInactiveVisual: function()
        {
            this.$toolbar.find('a.redactor_btn').not('a.redactor_btn_html').addClass('redactor_button_disabled');
        },
        buttonChangeIcon: function (key, classname)
        {
            this.buttonGet(key).addClass('redactor_btn_' + classname);
        },
        buttonRemoveIcon: function(key, classname)
        {
            this.buttonGet(key).removeClass('redactor_btn_' + classname);
        },
        buttonAddSeparator: function()
        {
            this.$toolbar.append($(this.opts.buttonSeparator));
        },
        buttonAddSeparatorAfter: function(key)
        {
            this.buttonGet(key).parent().after($(this.opts.buttonSeparator));
        },
        buttonAddSeparatorBefore: function(key)
        {
            this.buttonGet(key).parent().before($(this.opts.buttonSeparator));
        },
        buttonRemoveSeparatorAfter: function(key)
        {
            this.buttonGet(key).parent().next().remove();
        },
        buttonRemoveSeparatorBefore: function(key)
        {
            this.buttonGet(key).parent().prev().remove();
        },
        buttonSetRight: function(key)
        {
            if (!this.opts.toolbar) return;
            this.buttonGet(key).parent().addClass('redactor_btn_right');
        },
        buttonSetLeft: function(key)
        {
            if (!this.opts.toolbar) return;
            this.buttonGet(key).parent().removeClass('redactor_btn_right');
        },
        buttonAdd: function(key, title, callback, dropdown)
        {
            if (!this.opts.toolbar) return;
            var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
            this.$toolbar.append( $('<li>').append(btn));
        },
        buttonAddFirst: function(key, title, callback, dropdown)
        {
            if (!this.opts.toolbar) return;
            var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
            this.$toolbar.prepend($('<li>').append(btn));
        },
        buttonAddAfter: function(afterkey, key, title, callback, dropdown)
        {
            if (!this.opts.toolbar) return;
            var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
            var $btn = this.buttonGet(afterkey);
            $btn.parent().after($('<li>').append(btn));
        },
        buttonAddBefore: function(beforekey, key, title, callback, dropdown)
        {
            if (!this.opts.toolbar) return;
            var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
            var $btn = this.buttonGet(beforekey);
            $btn.parent().before($('<li>').append(btn));
        },
        buttonRemove: function (key, separator)
        {
            var $btn = this.buttonGet(key);
            if (separator) $btn.parent().next().remove();
            $btn.parent().removeClass('redactor_btn_right');
            $btn.remove();
        },
        buttonActiveObserver: function(e, btnName)
        {
            var parent = this.getParent();
            this.buttonInactiveAll(btnName);

            if (e === false && btnName !== 'html')
            {
                if ($.inArray(btnName, this.opts.activeButtons) != -1)
                {
                    this.buttonActiveToggle(btnName);
                }
                return;
            }

            if (parent && parent.tagName === 'A') this.$toolbar.find('a.redactor_dropdown_link').text(this.opts.curLang.link_edit);
            else this.$toolbar.find('a.redactor_dropdown_link').text(this.opts.curLang.link_insert);

            if (this.opts.activeButtonsAdd)
            {
                $.each(this.opts.activeButtonsAdd, $.proxy(function(i,s)
                {
                    this.opts.activeButtons.push(s);

                }, this));

                $.extend(this.opts.activeButtonsStates, this.opts.activeButtonsAdd);
            }

            $.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
            {
                if ($(parent).closest(key, this.$editor.get()[0]).length != 0)
                {
                    this.buttonActive(value);
                }

            }, this));

            var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
            if ($parent.length)
            {
                var align = $parent.css('text-align');

                switch (align)
                {
                    case 'right':
                        this.buttonActive('alignright');
                        break;
                    case 'center':
                        this.buttonActive('aligncenter');
                        break;
                    case 'justify':
                        this.buttonActive('justify');
                        break;
                    default:
                        this.buttonActive('alignleft');
                        break;
                }
            }
        },

        // EXEC
        exec: function(cmd, param, sync)
        {
            if (cmd === 'formatblock' && this.browser('msie')) param = '<' + param + '>';

            if (cmd === 'inserthtml' && this.browser('msie'))
            {
                this.$editor.focus();
                this.document.selection.createRange().pasteHTML(param);
            }
            else
            {
                this.document.execCommand(cmd, false, param);
            }

            if (sync !== false) this.sync();
            this.callback('execCommand', cmd, param);
        },
        execCommand: function(cmd, param, sync)
        {
            if (!this.opts.visual)
            {
                this.$source.focus();
                return false;
            }

            if (cmd === 'inserthtml')
            {
                this.insertHtml(param, sync);
                this.callback('execCommand', cmd, param);
                return;
            }

            // stop formatting pre
            if (this.currentOrParentIs('PRE') && !this.opts.formattingPre)
            {
                return false;
            }

            if (cmd === 'insertunorderedlist' || cmd === 'insertorderedlist')
            {
                this.bufferSet();

                var parent = this.getParent();
                var $list = $(parent).closest('ol, ul');
                var remove = false;

                if ($list.length)
                {
                    remove = true;
                    var listTag = $list[0].tagName;
                    if ((cmd === 'insertunorderedlist' && listTag === 'OL')
                        || (cmd === 'insertorderedlist' && listTag === 'UL'))
                    {
                        remove = false;
                    }
                }

                this.selectionSave();

                // remove lists
                if (remove)
                {
                    var nodes = this.getNodes();
                    var elems = this.getBlocks(nodes);

                    if (typeof nodes[0] != 'undefined' && nodes.length > 1 && nodes[0].nodeType == 3)
                    {
                        // fix the adding the first li to the array
                        elems.unshift(this.getBlock());
                    }

                    var data = '', replaced = '';
                    $.each(elems, $.proxy(function(i,s)
                    {
                        if (s.tagName == 'LI')
                        {
                            var $s = $(s);
                            var cloned = $s.clone();
                            cloned.find('ul', 'ol').remove();

                            if (this.opts.linebreaks === false) data += this.outerHtml($('<p>').append(cloned.contents()));
                            else data += cloned.html() + '<br>';

                            if (i == 0)
                            {
                                $s.addClass('redactor-replaced').empty();
                                replaced = this.outerHtml($s);
                            }
                            else $s.remove();
                        }

                    }, this));

                    html = this.$editor.html().replace(replaced, '</' + listTag + '>' + data + '<' + listTag + '>');

                    this.$editor.html(html);
                    this.$editor.find(listTag + ':empty').remove();

                }

                // insert lists
                else
                {
                    this.document.execCommand(cmd);

                    var parent = this.getParent();
                    var $list = $(parent).closest('ol, ul');

                    if ($list.length)
                    {
                        if ((this.browser('msie') || this.browser('mozilla')) && parent.tagName !== 'LI')
                        {
                            $(parent).replaceWith($(parent).html());
                        }

                        var $listParent = $list.parent();
                        if (this.isParentRedactor($listParent) && this.nodeTestBlocks($listParent[0]))
                        {
                            $listParent.replaceWith($listParent.contents());
                        }
                    }

                    if (this.browser('mozilla')) this.$editor.focus();

                }

                this.selectionRestore();

                this.sync();
                this.callback('execCommand', cmd, param);
                return;
            }

            if (cmd === 'unlink' )
            {
                this.bufferSet();

                var parent = this.getParent();
                if (parent && $(parent)[0].tagName === 'A')
                {
                    $(parent).replaceWith($(parent).text());

                    this.sync();
                    this.callback('execCommand', cmd, param);
                    return;
                }
            }

            this.exec(cmd, param, sync);

            if (cmd === 'inserthorizontalrule')
            {
                this.$editor.find('hr').removeAttr('id');
            }
        },

        // INDENTING
        indentingIndent: function()
        {
            this.indentingStart('indent');
        },
        indentingOutdent: function()
        {
            this.indentingStart('outdent');
        },
        indentingStart: function(cmd)
        {
            this.bufferSet();

            if (cmd === 'indent')
            {
                var block = this.getBlock();

                this.selectionSave();


                if (block && block.tagName == 'LI')
                {
                    // li
                    var parent = this.getParent();

                    var $list = $(parent).closest('ol, ul');
                    var listTag = $list[0].tagName;

                    var elems = this.getBlocks();

                    $.each(elems, function(i,s)
                    {
                        if (s.tagName == 'LI')
                        {
                            var $prev = $(s).prev();
                            if ($prev.size() != 0 && $prev[0].tagName == 'LI')
                            {
                                var $childList = $prev.children('ul, ol');
                                if ($childList.size() == 0)
                                {
                                    $prev.append($('<' + listTag + '>').append(s));
                                }
                                else $childList.append(s);
                            }
                        }
                    });
                }
                // linebreaks
                else if (block === false && this.opts.linebreaks === true)
                {
                    this.exec('formatBlock', 'blockquote');
                    var newblock = this.getBlock();
                    var block = $('<div data-tagblock="">').html($(newblock).html());
                    $(newblock).replaceWith(block);

                    var left = this.normalize($(block).css('margin-left')) + this.opts.indentValue;
                    $(block).css('margin-left', left + 'px');
                }
                else
                {
                    // all block tags
                    var elements = this.getBlocks();
                    $.each(elements, $.proxy(function(i, elem)
                    {
                        var $el = false;

                        if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
                        {
                            $el = $(elem);
                        }
                        else
                        {
                            $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
                        }

                        var left = this.normalize($el.css('margin-left')) + this.opts.indentValue;
                        $el.css('margin-left', left + 'px');

                    }, this));
                }

                this.selectionRestore();

            }
            // outdent
            else
            {
                this.selectionSave();

                var block = this.getBlock();
                if (block && block.tagName == 'LI')
                {
                    // li
                    var elems = this.getBlocks();
                    var index = 0;

                    this.insideOutdent(block, index, elems);
                }
                else
                {
                    // all block tags
                    var elements = this.getBlocks();
                    $.each(elements, $.proxy(function(i, elem)
                    {
                        var $el = false;

                        if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
                        {
                            $el = $(elem);
                        }
                        else
                        {
                            $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
                        }

                        var left = this.normalize($el.css('margin-left')) - this.opts.indentValue;
                        if (left <= 0)
                        {
                            // linebreaks
                            if (this.opts.linebreaks === true && typeof($el.data('tagblock')) !== 'undefined')
                            {
                                $el.replaceWith($el.html());
                            }
                            // all block tags
                            else
                            {
                                $el.css('margin-left', '');
                                this.removeEmptyAttr($el, 'style');
                            }
                        }
                        else
                        {
                            $el.css('margin-left', left + 'px');
                        }

                    }, this));
                }


                this.selectionRestore();
            }

        },
        insideOutdent: function (li, index, elems)
        {
            if (li && li.tagName == 'LI')
            {
                var $parent = $(li).parent().parent();
                if ($parent.size() != 0 && $parent[0].tagName == 'LI')
                {
                    $parent.after(li);
                }
                else
                {
                    if (typeof elems[index] != 'undefined')
                    {
                        li = elems[index];
                        index++;

                        this.insideOutdent(li, index, elems);
                    }
                    else
                    {
                        this.execCommand('insertunorderedlist');
                    }
                }
            }
        },

        // CLEAN
        cleanEmpty: function(html)
        {
            var ph = this.placeholderStart(html);
            if (ph !== false) return ph;

            if (this.opts.linebreaks === false)
            {
                if (html === '') html = this.opts.emptyHtml;
                else if (html.search(/^<hr\s?\/?>$/gi) !== -1) html = '<hr>' + this.opts.emptyHtml;
            }

            return html;
        },
        cleanConverters: function(html)
        {
            // convert div to p
            if (this.opts.convertDivs) html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
            if (this.opts.paragraphy) html = this.cleanParagraphy(html);

            return html;
        },
        cleanConvertProtected: function(html)
        {
            if (this.opts.templateVars)
            {
                html = html.replace(/\{\{(.*?)\}\}/gi, '<!-- template double $1 -->');
                html = html.replace(/\{(.*?)\}/gi, '<!-- template $1 -->');
            }

            html = html.replace(/<script(.*?)>([\w\W]*?)<\/script>/gi, '<title type="text/javascript" style="display: none;" class="redactor-script-tag"$1>$2</title>');
            html = html.replace(/<style(.*?)>([\w\W]*?)<\/style>/gi, '<section$1 style="display: none;" rel="redactor-style-tag">$2</section>');
            html = html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');

            // php tags convertation
            if (this.opts.phpTags) html = html.replace(/<\?php([\w\W]*?)\?>/gi, '<section style="display: none;" rel="redactor-php-tag">$1</section>');
            else html = html.replace(/<\?php([\w\W]*?)\?>/gi, '');

            return html;
        },
        cleanReConvertProtected: function(html)
        {
            if (this.opts.templateVars)
            {
                html = html.replace(/<!-- template double (.*?) -->/gi, '{{$1}}');
                html = html.replace(/<!-- template (.*?) -->/gi, '{$1}');
            }

            html = html.replace(/<title type="text\/javascript" style="display: none;" class="redactor-script-tag"(.*?)>([\w\W]*?)<\/title>/gi, '<script$1 type="text/javascript">$2</script>');
            html = html.replace(/<section(.*?) style="display: none;" rel="redactor-style-tag">([\w\W]*?)<\/section>/gi, '<style$1>$2</style>');
            html = html.replace(/<section(.*?)rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');

            // php tags convertation
            if (this.opts.phpTags) html = html.replace(/<section style="display: none;" rel="redactor-php-tag">([\w\W]*?)<\/section>/gi, '<?php\r\n$1\r\n?>');

            return html;
        },
        cleanRemoveSpaces: function(html, buffer)
        {
            if (buffer !== false)
            {
                var buffer = []
                var matches = html.match(/<(pre|style|script|title)(.*?)>([\w\W]*?)<\/(pre|style|script|title)>/gi);

                if (this.opts.phpTags)
                {
                    var phpMatches = html.match(/<\?php([\w\W]*?)\?>/gi);
                    if (phpMatches) matches = $.merge(matches, phpMatches);
                }

                if (matches)
                {
                    $.each(matches, function(i, s)
                    {
                        html = html.replace(s, 'buffer_' + i);
                        buffer.push(s);
                    });
                }
            }

            html = html.replace(/\n/g, ' ');
            html = html.replace(/[\t]*/g, '');
            html = html.replace(/\n\s*\n/g, "\n");
            html = html.replace(/^[\s\n]*/g, ' ');
            html = html.replace(/[\s\n]*$/g, ' ');
            html = html.replace( />\s{2,}</g, '><' ); // between inline tags can be only one space

            html = this.cleanReplacer(html, buffer);

            html = html.replace(/\n\n/g, "\n");

            return html;
        },
        cleanReplacer: function(html, buffer)
        {
            if (buffer === false) return html;

            $.each(buffer, function(i,s)
            {
                html = html.replace('buffer_' + i, s);
            });

            return html;
        },
        cleanRemoveEmptyTags: function(html)
        {
            html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');

            // remove zero width-space
            html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');

            var etagsInline = ["<b>\\s*</b>", "<b>&nbsp;</b>", "<em>\\s*</em>"]
            var etags = ["<pre></pre>", "<blockquote>\\s*</blockquote>", "<dd></dd>", "<dt></dt>", "<ul></ul>", "<ol></ol>", "<li></li>", "<table></table>", "<tr></tr>", "<span>\\s*<span>", "<span>&nbsp;<span>", "<p>\\s*</p>", "<p></p>", "<p>&nbsp;</p>",  "<p>\\s*<br>\\s*</p>", "<div>\\s*</div>", "<div>\\s*<br>\\s*</div>"];

            if (this.opts.removeEmptyTags)
            {
                etags = etags.concat(etagsInline);
            }
            else etags = etagsInline;

            var len = etags.length;
            for (var i = 0; i < len; ++i)
            {
                html = html.replace(new RegExp(etags[i], 'gi'), "");
            }

            return html;
        },
        cleanParagraphy: function(html)
        {
            html = $.trim(html);

            if (this.opts.linebreaks === true) return html;
            if (html === '' || html === '<p></p>') return this.opts.emptyHtml;

            html = html + "\n";

            var safes = [];
            var matches = html.match(/<(table|div|pre|object)(.*?)>([\w\W]*?)<\/(table|div|pre|object)>/gi);

            var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
            if (commentsMatches) matches = $.merge(matches, commentsMatches);

            if (this.opts.phpTags)
            {
                var phpMatches = html.match(/<section(.*?)rel="redactor-php-tag">([\w\W]*?)<\/section>/gi);
                if (phpMatches) matches = $.merge(matches, phpMatches);
            }

            if (matches)
            {
                $.each(matches, function(i,s)
                {
                    safes[i] = s;
                    html = html.replace(s, '{replace' + i + '}\n');
                });
            }

            html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");

            function R(str, mod, r)
            {
                return html.replace(new RegExp(str, mod), r);
            }

            var blocks = '(comment|html|body|head|title|meta|style|script|link|iframe|table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|option|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)';

            html = R('(<' + blocks + '[^>]*>)', 'gi', "\n$1");
            html = R('(</' + blocks + '>)', 'gi', "$1\n\n");
            html = R("\r\n", 'g', "\n");
            html = R("\r", 'g', "\n");
            html = R("/\n\n+/", 'g', "\n\n");

            var htmls = html.split(new RegExp('\n\s*\n', 'g'), -1);

            html = '';
            for (var i in htmls)
            {
                if (htmls.hasOwnProperty(i))
                {
                    if (htmls[i].search('{replace') == -1)
                    {
                        html += '<p>' +  htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
                    }
                    else html += htmls[i];
                }
            }

            // blockquote
            if (html.search(/<blockquote/gi) !== -1)
            {
                $.each(html.match(/<blockquote(.*?)>([\w\W]*?)<\/blockquote>/gi), function(i,s)
                {
                    var str = '';
                    str = s.replace('<p>', '');
                    str = str.replace('</p>', '<br>');
                    html = html.replace(s, str);
                });
            }

            html = R('<p>\s*</p>', 'gi', '');
            html = R('<p>([^<]+)</(div|address|form)>', 'gi', "<p>$1</p></$2>");
            html = R('<p>\s*(</?' + blocks + '[^>]*>)\s*</p>', 'gi', "$1");
            html = R("<p>(<li.+?)</p>", 'gi', "$1");
            html = R('<p>\s*(</?' + blocks + '[^>]*>)', 'gi', "$1");

            html = R('(</?' + blocks + '[^>]*>)\s*</p>', 'gi', "$1");
            html = R('(</?' + blocks + '[^>]*>)\s*<br />', 'gi', "$1");
            html = R('<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)', 'gi', '$1');
            html = R("\n</p>", 'gi', '</p>');

            html = R('</li><p>', 'gi', '</li>');
            //html = R('</ul><p>(.*?)</li>', 'gi', '</ul></li>');
            /* html = R('</ol><p>', 'gi', '</ol>'); */
            html = R('<p>\t?\n?<p>', 'gi', '<p>');
            html = R('</dt><p>', 'gi', '</dt>');
            html = R('</dd><p>', 'gi', '</dd>');
            html = R('<br></p></blockquote>', 'gi', '</blockquote>');
            html = R('<p>    </p>', 'gi', '');

            // restore safes
            $.each(safes, function(i,s)
            {
                html = html.replace('{replace' + i + '}', s);
            });

            return $.trim(html);
        },
        cleanConvertInlineTags: function(html)
        {
            var boldTag = 'strong';
            if (this.opts.boldTag === 'b') boldTag = 'b';

            var italicTag = 'em';
            if (this.opts.italicTag === 'i') italicTag = 'i';

            html = html.replace(/<span style="font-style: italic;">([\w\W]*?)<\/span>/gi, '<' + italicTag + '>$1</' + italicTag + '>');
            html = html.replace(/<span style="font-weight: bold;">([\w\W]*?)<\/span>/gi, '<' + boldTag + '>$1</' + boldTag + '>');

            // bold, italic, del
            if (this.opts.boldTag === 'strong') html = html.replace(/<b>([\w\W]*?)<\/b>/gi, '<strong>$1</strong>');
            else html = html.replace(/<strong>([\w\W]*?)<\/strong>/gi, '<b>$1</b>');

            if (this.opts.italicTag === 'em') html = html.replace(/<i>([\w\W]*?)<\/i>/gi, '<em>$1</em>');
            else html = html.replace(/<em>([\w\W]*?)<\/em>/gi, '<i>$1</i>');

            html = html.replace(/<strike>([\w\W]*?)<\/strike>/gi, '<del>$1</del>');

            if (!/<span(.*?)data-redactor="verified"(.*?)>([\w\W]*?)<\/span>/gi.test(html))
            {
                html = html.replace(/<span(.*?)(?!data-redactor="verified")(.*?)>([\w\W]*?)<\/span>/gi, '<span$1 data-redactor="verified"$2>$3</span>');
            }

            return html;
        },
        cleanStripTags: function(html)
        {
            if (html == '' || typeof html == 'undefined') return html;

            var allowed = false;
            if (this.opts.allowedTags !== false) allowed = true;

            var arr = allowed === true ? this.opts.allowedTags : this.opts.deniedTags;

            var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
            html = html.replace(tags, function ($0, $1)
            {
                if (allowed === true) return $.inArray($1.toLowerCase(), arr) > '-1' ? $0 : '';
                else return $.inArray($1.toLowerCase(), arr) > '-1' ? '' : $0;
            });

            html = this.cleanConvertInlineTags(html);

            return html;

        },
        cleanSavePreCode: function(html, encode)
        {
            var pre = html.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/gi);
            if (pre !== null)
            {
                $.each(pre, $.proxy(function(i,s)
                {
                    var arr = s.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/i);

                    arr[3] = arr[3].replace(/&nbsp;/g, ' ');
                    if (encode !== false) arr[3] = this.cleanEncodeEntities(arr[3]);

                    html = html.replace(s, '<' + arr[1] + arr[2] + '>' + arr[3] + '</' + arr[1] + '>');

                }, this));
            }

            return html;
        },
        cleanEncodeEntities: function(str)
        {
            str = String(str).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"');
            return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
        },
        cleanUnverified: function()
        {
            // label, abbr, mark, meter, code, q, dfn, ins, time, kbd, var

            var $elem = this.$editor.find('li, img, a, b, strong, sub, sup, i, em, u, small, strike, del, span, cite');

            $elem.not('[data-redactor="verified"]').filter('[style*="font-size"][style*="line-height"]')
                .css('font-size', '')
                .css('line-height', '');

            $elem.not('[data-redactor="verified"]').filter('[style*="background-color: transparent;"][style*="line-height"]')
                .css('background-color', '')
                .css('line-height', '');

            $elem.not('[data-redactor="verified"]').filter('[style*="background-color: transparent;"]')
                .css('background-color', '');

            $elem.not('[data-redactor="verified"]').css('line-height', '');

            $.each($elem, $.proxy(function(i,s)
            {
                this.removeEmptyAttr(s, 'style');
            }, this));

            // When we paste text in Safari is wrapping inserted div (remove it)
            this.$editor.find('div[style="text-align: -webkit-auto;"]').contents().unwrap();
        },
        cleanHtml: function(code)
        {
            var i = 0,
                codeLength = code.length,
                point = 0,
                start = null,
                end = null,
                tag = '',
                out = '',
                cont = '';

            this.cleanlevel = 0;

            for (; i < codeLength; i++)
            {
                point = i;

                // if no more tags, copy and exit
                if (-1 == code.substr(i).indexOf( '<' ))
                {
                    out += code.substr(i);

                    return this.cleanFinish(out);
                }

                // copy verbatim until a tag
                while (point < codeLength && code.charAt(point) != '<')
                {
                    point++;
                }

                if (i != point)
                {
                    cont = code.substr(i, point - i);
                    if (!cont.match(/^\s{2,}$/g))
                    {
                        if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
                        else if ('\n' == cont.charAt(0))
                        {
                            out += '\n' + this.cleanGetTabs();
                            cont = cont.replace(/^\s+/, '');
                        }

                        out += cont;
                    }

                    if (cont.match(/\n/)) out += '\n' + this.cleanGetTabs();
                }

                start = point;

                // find the end of the tag
                while (point < codeLength && '>' != code.charAt(point))
                {
                    point++;
                }

                tag = code.substr(start, point - start);
                i = point;

                var t;

                if ('!--' == tag.substr(1, 3))
                {
                    if (!tag.match(/--$/))
                    {
                        while ('-->' != code.substr(point, 3))
                        {
                            point++;
                        }
                        point += 2;
                        tag = code.substr(start, point - start);
                        i = point;
                    }

                    if ('\n' != out.charAt(out.length - 1)) out += '\n';

                    out += this.cleanGetTabs();
                    out += tag + '>\n';
                }
                else if ('!' == tag[1])
                {
                    out = this.placeTag(tag + '>', out);
                }
                else if ('?' == tag[1])
                {
                    out += tag + '>\n';
                }
                else if (t = tag.match(/^<(script|style|pre)/i))
                {
                    t[1] = t[1].toLowerCase();
                    tag = this.cleanTag(tag);
                    out = this.placeTag(tag, out);
                    end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);

                    if (end)
                    {
                        cont = code.substr(i + 1, end);
                        i += end;
                        out += cont;
                    }
                }
                else
                {
                    tag = this.cleanTag(tag);
                    out = this.placeTag(tag, out);
                }
            }

            return this.cleanFinish( out );
        },
        cleanGetTabs: function()
        {
            var s = '';
            for ( var j = 0; j < this.cleanlevel; j++ )
            {
                s += '\t';
            }

            return s;
        },
        cleanFinish: function(code)
        {
            code = code.replace( /\n\s*\n/g, '\n' );
            code = code.replace( /^[\s\n]*/, '' );
            code = code.replace( /[\s\n]*$/, '' );
            code = code.replace( /<script(.*?)>\n<\/script>/gi, '<script$1></script>' );

            this.cleanlevel = 0;

            return code;
        },
        cleanTag: function (tag)
        {
            var tagout = '';
            tag = tag.replace(/\n/g, ' ');
            tag = tag.replace(/\s{2,}/g, ' ');
            tag = tag.replace(/^\s+|\s+$/g, ' ');

            var suffix = '';
            if (tag.match(/\/$/))
            {
                suffix = '/';
                tag = tag.replace(/\/+$/, '');
            }

            var m;
            while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
            {
                if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
                else if (m[1]) tagout += m[1].toLowerCase();

                tagout += ' ';
                tag = tag.substr(m[0].length);
            }

            return tagout.replace(/\s*$/, '') + suffix + '>';
        },
        placeTag: function (tag, out)
        {
            var nl = tag.match(this.cleannewLevel);
            if (tag.match(this.cleanlineBefore) || nl)
            {
                out = out.replace(/\s*$/, '');
                out += '\n';
            }

            if (nl && '/' == tag.charAt(1)) this.cleanlevel--;
            if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
            if (nl && '/' != tag.charAt(1)) this.cleanlevel++;

            out += tag;

            if (tag.match(this.cleanlineAfter) || tag.match(this.cleannewLevel))
            {
                out = out.replace(/ *$/, '');
                out += '\n';
            }

            return out;
        },

        // ALIGNMENT
        alignmentLeft: function()
        {
            this.alignmentSet('', 'JustifyLeft');
        },
        alignmentRight: function()
        {
            this.alignmentSet('right', 'JustifyRight');
        },
        alignmentCenter: function()
        {
            this.alignmentSet('center', 'JustifyCenter');
        },
        alignmentJustify: function()
        {
            this.alignmentSet('justify', 'JustifyFull');
        },
        alignmentSet: function(type, cmd)
        {
            this.bufferSet();

            if (this.oldIE())
            {
                this.document.execCommand(cmd, false, false);
                return true;
            }

            this.selectionSave();

            var block = this.getBlock();
            if (!block && this.opts.linebreaks)
            {
                // one element
                this.exec('formatBlock', 'blockquote');
                var newblock = this.getBlock();
                var block = $('<div data-tagblock="">').html($(newblock).html());
                $(newblock).replaceWith(block);

                $(block).css('text-align', type);
                this.removeEmptyAttr(block, 'style');

                if (type == '' && typeof($(block).data('tagblock')) !== 'undefined')
                {
                    $(block).replaceWith($(block).html());
                }
            }
            else
            {
                var elements = this.getBlocks();
                $.each(elements, $.proxy(function(i, elem)
                {
                    var $el = false;

                    if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
                    {
                        $el = $(elem);
                    }
                    else
                    {
                        $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
                    }

                    if ($el)
                    {
                        $el.css('text-align', type);
                        this.removeEmptyAttr($el, 'style');
                    }

                }, this));
            }

            this.selectionRestore();

            this.sync();
        },

        // FORMAT
        formatEmpty: function(e)
        {
            var html = $.trim(this.$editor.html());

            html = html.replace(/<br\s?\/?>/i, '');
            var thtml = html.replace(/<p>\s?<\/p>/gi, '');

            if (html === '' || thtml === '')
            {
                e.preventDefault();

                var node = $(this.opts.emptyHtml).get(0);
                this.$editor.html(node);
                this.focus();
            }

            this.sync();
        },
        formatBlocks: function(tag)
        {
            this.bufferSet();

            var nodes = this.getBlocks();
            this.selectionSave();

            $.each(nodes, $.proxy(function(i, node)
            {
                if (node.tagName !== 'LI')
                {
                    this.formatBlock(tag, node);
                }

            }, this));

            this.selectionRestore();
            this.sync();
        },
        formatBlock: function(tag, block)
        {
            if (block === false) block = this.getBlock();
            if (block === false)
            {
                if (this.opts.linebreaks === true) this.execCommand('formatblock', tag);
                return true;
            }

            var contents = '';
            if (tag !== 'pre')
            {
                contents = $(block).contents();
            }
            else
            {
                //contents = this.cleanEncodeEntities($(block).text());
                contents = $(block).html();
                if ($.trim(contents) === '')
                {
                    contents = '<span id="selection-marker-1"></span>';
                }
            }

            if (block.tagName === 'PRE') tag = 'p';

            if (this.opts.linebreaks === true && tag === 'p')
            {
                $(block).replaceWith($('<div>').append(contents).html() + '<br>');
            }
            else
            {
                var node = $('<' + tag + '>').append(contents);
                $(block).replaceWith(node);
            }
        },
        formatChangeTag: function(fromElement, toTagName, save)
        {
            if (save !== false) this.selectionSave();

            var newElement = $('<' + toTagName + '/>');
            $(fromElement).replaceWith(function() { return newElement.append($(this).contents()); });

            if (save !== false) this.selectionRestore();

            return newElement;
        },

        // QUOTE
        formatQuote: function()
        {
            this.bufferSet();

            if (this.opts.linebreaks === false)
            {
                this.selectionSave();

                var blocks = this.getBlocks();
                if (blocks)
                {
                    $.each(blocks, $.proxy(function(i,s)
                    {
                        if (s.tagName === 'BLOCKQUOTE')
                        {
                            this.formatBlock('p', s, false);
                        }
                        else if (s.tagName !== 'LI')
                        {
                            this.formatBlock('blockquote', s, false);
                        }

                    }, this));
                }

                this.selectionRestore();
            }
            // linebreaks
            else
            {
                var block = this.getBlock();
                if (block.tagName === 'BLOCKQUOTE')
                {
                    this.selectionSave();

                    $(block).replaceWith($(block).html() + '<br>');

                    this.selectionRestore();
                }
                else
                {
                    var wrapper = this.selectionWrap('blockquote');
                    var html = $(wrapper).html();

                    var blocksElemsRemove = ['ul', 'ol', 'table', 'tr', 'tbody', 'thead', 'tfoot', 'dl'];
                    $.each(blocksElemsRemove, function(i,s)
                    {
                        html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
                        html = html.replace(new RegExp('</' + s + '>', 'gi'), '');
                    });

                    var blocksElems = this.opts.blockLevelElements;
                    blocksElems.push('td');
                    $.each(blocksElems, function(i,s)
                    {
                        html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
                        html = html.replace(new RegExp('</' + s + '>', 'gi'), '<br>');
                    });

                    $(wrapper).html(html);
                    this.selectionElement(wrapper);
                    var next = $(wrapper).next();
                    if (next.size() != 0 && next[0].tagName === 'BR')
                    {
                        next.remove();
                    }
                }
            }

            this.sync();
        },


        // BLOCK CLASS AND STYLE
        blockRemoveAttr: function(attr, value)
        {
            var nodes = this.getBlocks();
            $(nodes).removeAttr(attr);

            this.sync();
        },
        blockSetAttr: function(attr, value)
        {
            var nodes = this.getBlocks();
            $(nodes).attr(attr, value);

            this.sync();
        },
        blockRemoveStyle: function(rule)
        {
            var nodes = this.getBlocks();
            $(nodes).css(rule, '');
            this.removeEmptyAttr(nodes, 'style');

            this.sync();
        },
        blockSetStyle: function (rule, value)
        {
            var nodes = this.getBlocks();
            $(nodes).css(rule, value);

            this.sync();
        },
        blockRemoveClass: function(className)
        {
            var nodes = this.getBlocks();
            $(nodes).removeClass(className);
            this.removeEmptyAttr(nodes, 'class');

            this.sync();
        },
        blockSetClass: function(className)
        {
            var nodes = this.getBlocks();
            $(nodes).addClass(className);

            this.sync();
        },

        // INLINE CLASS AND STYLE
        inlineRemoveClass: function(className)
        {
            this.selectionSave();

            this.inlineEachNodes(function(node)
            {
                $(node).removeClass(className);
                this.removeEmptyAttr(node, 'class');
            });

            this.selectionRestore();
            this.sync();
        },

        inlineSetClass: function(className)
        {
            var current = this.getCurrent();
            if (!$(current).hasClass(className)) this.inlineMethods('addClass', className);
        },
        inlineRemoveStyle: function (rule)
        {
            this.selectionSave();

            this.inlineEachNodes(function(node)
            {
                $(node).css(rule, '');
                this.removeEmptyAttr(node, 'style');
            });

            this.selectionRestore();
            this.sync();
        },
        inlineSetStyle: function(rule, value)
        {
            this.inlineMethods('css', rule, value);
        },
        inlineRemoveAttr: function (attr)
        {
            this.selectionSave();

            var range = this.getRange(), node = this.getElement(), nodes = this.getNodes();

            if (range.collapsed || range.startContainer === range.endContainer && node)
            {
                nodes = $( node );
            }

            $(nodes).removeAttr(attr);

            this.inlineUnwrapSpan();

            this.selectionRestore();
            this.sync();
        },
        inlineSetAttr: function(attr, value)
        {
            this.inlineMethods('attr', attr, value );
        },
        inlineMethods: function(type, attr, value)
        {
            this.bufferSet();
            this.selectionSave();

            var range = this.getRange()
            var el = this.getElement();

            if (range.collapsed || range.startContainer === range.endContainer && el)
            {
                $(el)[type](attr, value);
            }
            else
            {
                this.document.execCommand('fontSize', false, 4 );

                var fonts = this.$editor.find('font');
                $.each(fonts, $.proxy(function(i, s)
                {
                    this.inlineSetMethods(type, s, attr, value);

                }, this));
            }

            this.selectionRestore();

            this.sync();
        },
        inlineSetMethods: function(type, s, attr, value)
        {
            var parent = $(s).parent(), el;

            if (parent && parent[0].tagName === 'SPAN' && parent[0].attributes.length != 0)
            {
                el = parent;
                $(s).replaceWith($(s).html());
            }
            else
            {
                el = $('<span data-redactor="verified" data-redactor-inlineMethods>').append($(s).contents());
                $(s).replaceWith(el);
            }

            $(el)[type](attr, value);

            return el;
        },
        // Sort elements and execute callback
        inlineEachNodes: function(callback)
        {
            var range = this.getRange(),
                node = this.getElement(),
                nodes = this.getNodes(),
                collapsed;

            if (range.collapsed || range.startContainer === range.endContainer && node)
            {
                nodes = $(node);
                collapsed = true;
            }

            $.each(nodes, $.proxy(function(i, node)
            {
                if (!collapsed && node.tagName !== 'SPAN')
                {
                    if (node.parentNode.tagName === 'SPAN' && !$(node.parentNode).hasClass('redactor_editor'))
                    {
                        node = node.parentNode;
                    }
                    else return;
                }
                callback.call(this, node);

            }, this ) );
        },
        inlineUnwrapSpan: function()
        {
            var $spans = this.$editor.find('span[data-redactor-inlineMethods]');

            $.each($spans, $.proxy(function(i, span)
            {
                var $span = $(span);

                if ($span.attr('class') === undefined && $span.attr('style') === undefined)
                {
                    $span.contents().unwrap();
                }

            }, this));
        },
        inlineFormat: function(tag)
        {
            this.selectionSave();

            this.document.execCommand('fontSize', false, 4 );

            var fonts = this.$editor.find('font');
            var last;
            $.each(fonts, function(i, s)
            {
                var el = $('<' + tag + '/>').append($(s).contents());
                $(s).replaceWith(el);
                last = el;
            });

            this.selectionRestore();

            this.sync();
        },
        inlineRemoveFormat: function(tag)
        {
            this.selectionSave();

            var utag = tag.toUpperCase();
            var nodes = this.getNodes();

            $.each(nodes, function(i, s)
            {
                if (s.tagName === utag) $(s).replaceWith($(s).contents());
            });

            this.selectionRestore();
            this.sync();
        },

        // INSERT
        insertHtml: function (html, sync)
        {
            var current = this.getCurrent();
            var parent = current.parentNode;

            this.$editor.focus();

            this.bufferSet();

            var $html = $('<div>').append($.parseHTML(html));
            html = $html.html();

            html = this.cleanRemoveEmptyTags(html);

            // Update value
            $html = $('<div>').append($.parseHTML(html));

            var currBlock = this.getBlock();

            if ($html.contents().length == 1)
            {
                var htmlTagName = $html.contents()[0].tagName;

                // If the inserted and received text tags match
                if (htmlTagName != 'P' && htmlTagName == currBlock.tagName || htmlTagName == 'PRE')
                {
                    html = $html.text();
                    $html = $('<div>').append(html);
                }
            }

            // add text in a paragraph
            if (!this.opts.linebreaks && $html.contents().length == 1 && $html.contents()[0].nodeType == 3
                && (this.getRangeSelectedNodes().length > 2 || (!current || current.tagName == 'BODY' && !parent || parent.tagName == 'HTML')))
            {
                html = '<p>' + html + '</p>';
            }

            // in the quote, we can insert text or links only
            /*
             if (currBlock.tagName == 'BLOCKQUOTE' && html.indexOf('<a') === -1)
             {
             this.insertText(html);
             }
             else
             */
            if ($html.contents().length > 1 && currBlock
                || $html.contents().is('p, :header, ul, ol, div, table, blockquote, pre, address, section, header, footer, aside, article'))
            {
                if (this.browser('msie')) this.document.selection.createRange().pasteHTML(html);
                else this.document.execCommand('inserthtml', false, html);
            }
            else this.insertHtmlAdvanced(html);

            if (this.selectall)
            {
                this.window.setTimeout($.proxy(function()
                {
                    if (!this.opts.linebreaks) this.selectionEnd(this.$editor.contents().last());
                    else this.focusEnd();

                }, this), 1);
            }

            this.observeStart();

            if (sync !== false) this.sync();
        },
        insertHtmlAdvanced: function(html)
        {
            var sel = this.getSelection();

            if (sel.getRangeAt && sel.rangeCount)
            {
                var range = sel.getRangeAt(0);
                range.deleteContents();

                var el = this.document.createElement('div');
                el.innerHTML = html;
                var frag = this.document.createDocumentFragment(), node, lastNode;
                while ((node = el.firstChild))
                {
                    lastNode = frag.appendChild(node);
                }

                range.insertNode(frag);

                if (lastNode)
                {
                    range = range.cloneRange();
                    range.setStartAfter(lastNode);
                    range.collapse(true);
                    sel.removeAllRanges();
                    sel.addRange(range);
                }
            }
        },
        insertText: function(html)
        {
            var $html = $($.parseHTML(html));

            if ($html.length) html = $html.text();

            this.$editor.focus();

            if (this.browser('msie')) this.document.selection.createRange().pasteHTML(html);
            else this.document.execCommand('inserthtml', false, html);

            this.sync();
        },
        insertNode: function(node)
        {
            node = node[0] || node;
            var sel = this.getSelection();
            if (sel.getRangeAt && sel.rangeCount)
            {
                // with delete contents
                range = sel.getRangeAt(0);
                range.deleteContents();
                range.insertNode(node);
                range.setEndAfter(node);
                range.setStartAfter(node);
                sel.removeAllRanges();
                sel.addRange(range);
            }
        },
        insertNodeToCaretPositionFromPoint: function(e, node)
        {
            var range;
            var x = e.clientX, y = e.clientY;
            if (this.document.caretPositionFromPoint)
            {
                var pos = this.document.caretPositionFromPoint(x, y);
                range = this.getRange();
                range.setStart(pos.offsetNode, pos.offset);
                range.collapse(true);
                range.insertNode(node);
            }
            else if (this.document.caretRangeFromPoint)
            {
                range = this.document.caretRangeFromPoint(x, y);
                range.insertNode(node);
            }
            else if (typeof document.body.createTextRange != "undefined")
            {
                range = this.document.body.createTextRange();
                range.moveToPoint(x, y);
                var endRange = range.duplicate();
                endRange.moveToPoint(x, y);
                range.setEndPoint("EndToEnd", endRange);
                range.select();
            }

        },
        insertAfterLastElement: function(element)
        {
            if (this.isEndOfElement())
            {

                if ($($.trim(this.$editor.html())).get(0) != $.trim(element)
                    && this.$editor.contents().last()[0] !== element) return false;

                this.bufferSet();

                if (this.opts.linebreaks === false)
                {
                    var node = $(this.opts.emptyHtml);
                    $(element).after(node);
                    this.selectionStart(node);
                }
                else
                {
                    var node = $('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>', this.document)[0];
                    $(element).after(node);
                    $(node).after(this.opts.invisibleSpace);
                    this.selectionRestore();
                }
            }
        },
        insertLineBreak: function()
        {
            this.selectionSave();
            this.$editor.find('#selection-marker-1').before('<br>' + (this.browser('webkit') ? this.opts.invisibleSpace : ''));
            this.selectionRestore();
        },
        insertDoubleLineBreak: function()
        {
            this.selectionSave();
            this.$editor.find('#selection-marker-1').before('<br><br>' + (this.browser('webkit') ? this.opts.invisibleSpace : ''));
            this.selectionRestore();
        },
        replaceLineBreak: function(element)
        {
            //var node = this.document.createTextNode('\uFEFF');
            var node = $('<br>' + this.opts.invisibleSpace);
            $(element).replaceWith(node);
            this.selectionStart(node);
        },

        // PASTE
        pasteClean: function(html)
        {
            html = this.callback('pasteBefore', false, html);


            if (this.opts.pastePlainText)
            {
                var tmp = this.document.createElement('div');

                html = html.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');

                tmp.innerHTML = html;
                html = tmp.textContent || tmp.innerText;

                html = html.replace('\n', '<br>');
                html = this.cleanParagraphy(html);

                this.pasteInsert(html);
            }

            // clean up pre
            if (this.currentOrParentIs('PRE'))
            {
                html = this.pastePre(html);
                this.pasteInsert(html);
                return true;
            }

            // ms word list
            html = html.replace(/<p(.*?)class="MsoListParagraphCxSpFirst"([\w\W]*?)<\/p>/gi, '<ul><p$2</p>');
            html = html.replace(/<p(.*?)class="MsoListParagraphCxSpLast"([\w\W]*?)<\/p>/gi, '<p$2</p></ul>');

            // remove comments and php tags
            html = html.replace(/<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi, '');

            // remove nbsp
            html = html.replace(/(&nbsp;){2,}/gi, '&nbsp;');
            html = html.replace(/&nbsp;/gi, ' ');

            // remove google docs marker
            html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
            html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");

            // strip tags
            html = this.cleanStripTags(html);

            // prevert
            html = html.replace(/<td><\/td>/gi, '[td]');
            html = html.replace(/<td>&nbsp;<\/td>/gi, '[td]');
            html = html.replace(/<td><br><\/td>/gi, '[td]');
            html = html.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
            html = html.replace(/<iframe(.*?)>([\w\W]*?)<\/iframe>/gi, '[iframe$1]$2[/iframe]');
            html = html.replace(/<video(.*?)>([\w\W]*?)<\/video>/gi, '[video$1]$2[/video]');
            html = html.replace(/<audio(.*?)>([\w\W]*?)<\/audio>/gi, '[audio$1]$2[/audio]');
            html = html.replace(/<embed(.*?)>([\w\W]*?)<\/embed>/gi, '[embed$1]$2[/embed]');
            html = html.replace(/<object(.*?)>([\w\W]*?)<\/object>/gi, '[object$1]$2[/object]');
            html = html.replace(/<param(.*?)>/gi, '[param$1]');
            html = html.replace(/<img(.*?)style="(.*?)"(.*?)>/gi, '[img$1$3]');
            html = html.replace(/<img(.*?)>/gi, '[img$1]');

            // remove classes
            html = html.replace(/ class="(.*?)"/gi, '');

            // remove all attributes
            html = html.replace(/<(\w+)([\w\W]*?)>/gi, '<$1>');

            // remove empty
            html = html.replace(/<[^\/>][^>]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');

            html = html.replace(/<div>\s*?\t*?\n*?(<ul>|<ol>|<p>)/gi, '$1');

            // revert
            html = html.replace(/\[td\]/gi, '<td>&nbsp;</td>');
            html = html.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
            html = html.replace(/\[iframe(.*?)\]([\w\W]*?)\[\/iframe\]/gi, '<iframe$1>$2</iframe>');
            html = html.replace(/\[video(.*?)\]([\w\W]*?)\[\/video\]/gi, '<video$1>$2</video>');
            html = html.replace(/\[audio(.*?)\]([\w\W]*?)\[\/audio\]/gi, '<audio$1>$2</audio>');
            html = html.replace(/\[embed(.*?)\]([\w\W]*?)\[\/embed\]/gi, '<embed$1>$2</embed>');
            html = html.replace(/\[object(.*?)\]([\w\W]*?)\[\/object\]/gi, '<object$1>$2</object>');
            html = html.replace(/\[param(.*?)\]/gi, '<param$1>');
            html = html.replace(/\[img(.*?)\]/gi, '<img$1>');

            // convert div to p
            if (this.opts.convertDivs)
            {
                html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p>$2</p>');
                html = html.replace(/<\/div><p>/gi, '<p>');
                html = html.replace(/<\/p><\/div>/gi, '</p>');
            }

            html = this.cleanParagraphy(html);

            // remove span
            html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');

            // remove empty
            html = html.replace(/<img>/gi, '');
            html = html.replace(/<[^\/>][^>][^img|param|source]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');

            html = html.replace(/\n{3,}/gi, '\n');

            // remove dirty p
            html = html.replace(/<p><p>/gi, '<p>');
            html = html.replace(/<\/p><\/p>/gi, '</p>');

            html = html.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
            html = html.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');

            if (this.opts.linebreaks === true)
            {
                html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
            }

            // remove empty finally
            html = html.replace(/<[^\/>][^>][^img|param|source]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');

            // remove safari local images
            html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');

            // FF specific
            this.pasteClipboardMozilla = false;
            if (this.browser('mozilla'))
            {
                if (this.opts.clipboardUpload)
                {
                    var matches = html.match(/<img src="data:image(.*?)"(.*?)>/gi);
                    if (matches !== null)
                    {
                        this.pasteClipboardMozilla = matches;
                        for (k in matches)
                        {
                            var img = matches[k].replace('<img', '<img data-mozilla-paste-image="' + k + '" ');
                            html = html.replace(matches[k], img);
                        }
                    }
                }

                // FF fix
                while (/<br>$/gi.test(html))
                {
                    html = html.replace(/<br>$/gi, '');
                }
            }

            // bullets
            html = html.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');

            // ie inserts a blank font tags when pasting
            while (/<font>([\w\W]*?)<\/font>/gi.test(html))
            {
                html = html.replace(/<font>([\w\W]*?)<\/font>/gi, '$1');
            }


            this.pasteInsert(html);

        },
        pastePre: function(s)
        {
            s = s.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');

            var tmp = this.document.createElement('div');
            tmp.innerHTML = s;
            return this.cleanEncodeEntities(tmp.textContent || tmp.innerText);
        },
        pasteInsert: function(html)
        {
            if (this.selectall)
            {
                if (!this.opts.linebreaks) this.$editor.html(this.opts.emptyHtml);
                else this.$editor.html('');

                this.$editor.focus();
            }

            html = this.callback('pasteAfter', false, html);

            this.insertHtml(html);


            this.selectall = false;
            setTimeout($.proxy(function()
            {
                rtePaste = false;

                // FF specific
                if (this.pasteClipboardMozilla !== false)
                {
                    this.pasteClipboardUploadMozilla();
                }

            }, this), 100);

            if (this.opts.autoresize) $(this.document.body).scrollTop(this.saveScroll);
            else this.$editor.scrollTop(this.saveScroll);
        },

        pasteClipboardUploadMozilla: function()
        {
            var imgs = this.$editor.find('img[data-mozilla-paste-image]');
            $.each(imgs, $.proxy(function(i,s)
            {
                var $s = $(s);
                var arr = s.src.split(",");
                var data = arr[1]; // raw base64
                var contentType = arr[0].split(";")[0].split(":")[1];

                $.post(this.opts.clipboardUploadUrl, {
                        contentType: contentType,
                        data: data
                    },
                    $.proxy(function(data)
                    {
                        var json = $.parseJSON(data);
                        $s.attr('src', json.filelink);
                        $s.removeAttr('data-mozilla-paste-image');

                        this.sync();

                        // upload callback
                        this.callback('imageUpload', $s, json);

                    }, this));

            }, this));
        },
        pasteClipboardUpload: function(e)
        {
            var result = e.target.result;
            var arr = result.split(",");
            var data = arr[1]; // raw base64
            var contentType = arr[0].split(";")[0].split(":")[1];

            if (this.opts.clipboardUpload)
            {
                $.post(this.opts.clipboardUploadUrl, {
                        contentType: contentType,
                        data: data
                    },
                    $.proxy(function(data)
                    {
                        var json = $.parseJSON(data);

                        var html = '<img src="' + json.filelink + '" id="clipboard-image-marker" />';
                        this.execCommand('inserthtml', html, false);

                        var image = $(this.$editor.find('img#clipboard-image-marker'));

                        if (image.length) image.removeAttr('id');
                        else image = false;

                        this.sync();

                        // upload callback
                        if (image)
                        {
                            this.callback('imageUpload', image, json);
                        }


                    }, this));
            }
            else
            {
                this.insertHtml('<img src="' + result + '" />');
            }
        },

        // BUFFER
        bufferSet: function(html)
        {
            if (html !== undefined) this.opts.buffer.push(html);
            else
            {
                this.selectionSave();
                this.opts.buffer.push(this.$editor.html());
                this.selectionRemoveMarkers('buffer');
            }
        },
        bufferUndo: function()
        {
            if (this.opts.buffer.length === 0)
            {
                this.$editor.focus();
                return;
            }

            // rebuffer
            this.selectionSave();
            this.opts.rebuffer.push(this.$editor.html());
            this.selectionRestore(false, true);

            this.$editor.html(this.opts.buffer.pop());

            this.selectionRestore();
            setTimeout($.proxy(this.observeStart, this), 100);
        },
        bufferRedo: function()
        {
            if (this.opts.rebuffer.length === 0)
            {
                this.$editor.focus();
                return false;
            }

            // buffer
            this.selectionSave();
            this.opts.buffer.push(this.$editor.html());
            this.selectionRestore(false, true);

            this.$editor.html(this.opts.rebuffer.pop());
            this.selectionRestore(true);
            setTimeout($.proxy(this.observeStart, this), 4);
        },


        // OBSERVE
        observeStart: function()
        {
            this.observeImages();
            this.observeTables();
        },
        observeTables: function()
        {
            this.$editor.find('table').on('click', $.proxy(this.tableObserver, this));
        },
        observeImages: function()
        {
            if (this.opts.observeImages === false) return false;

            this.$editor.find('img').each($.proxy(function(i, elem)
            {
                if (this.browser('msie')) $(elem).attr('unselectable', 'on');
                this.imageResize(elem);

            }, this));
        },


        // SELECTION
        getSelection: function()
        {
            if (!this.opts.rangy) return this.document.getSelection();
            else // rangy
            {
                if (!this.opts.iframe) return rangy.getSelection();
                else return rangy.getSelection(this.$frame[0]);
            }
        },
        getRange: function()
        {
            if (!this.opts.rangy)
            {
                if (this.document.getSelection)
                {
                    var sel = this.document.getSelection();
                    if (sel.getRangeAt && sel.rangeCount) return sel.getRangeAt(0);
                }

                return this.document.createRange();
            }
            else // rangy
            {
                if (!this.opts.iframe) return rangy.createRange();
                else return rangy.createRange(this.iframeDoc());
            }
        },
        selectionElement: function(node)
        {
            this.setCaret(node);
        },
        selectionStart: function(node)
        {
            this.selectionSet(node[0] || node, 0, null, 0);
        },
        selectionEnd: function(node)
        {
            this.selectionSet(node[0] || node, 1, null, 1);
        },
        selectionSet: function(orgn, orgo, focn, foco)
        {
            if (focn == null) focn = orgn;
            if (foco == null) foco = orgo;

            var sel = this.getSelection();
            if (!sel) return;

            var range = this.getRange();
            range.setStart(orgn, orgo);
            range.setEnd(focn, foco );

            try {
                sel.removeAllRanges();
            } catch (e) {}

            sel.addRange(range);
        },
        selectionWrap: function(tag)
        {
            tag = tag.toLowerCase();

            var block = this.getBlock();
            if (block)
            {
                var wrapper = this.formatChangeTag(block, tag);
                this.sync();
                return wrapper;
            }

            var sel = this.getSelection();
            var range = sel.getRangeAt(0);
            var wrapper = document.createElement(tag);
            wrapper.appendChild(range.extractContents());
            range.insertNode(wrapper);

            this.selectionElement(wrapper);

            return wrapper;
        },
        selectionAll: function()
        {
            var range = this.getRange();
            range.selectNodeContents(this.$editor[0]);

            var sel = this.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        },
        selectionRemove: function()
        {
            this.getSelection().removeAllRanges();
        },
        getCaretOffset: function (element)
        {
            var caretOffset = 0;

            var range = this.getRange();
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            caretOffset = $.trim(preCaretRange.toString()).length;

            return caretOffset;
        },
        getCaretOffsetRange: function()
        {
            return new Range(this.getSelection().getRangeAt(0));
        },
        setCaret: function (el, start, end)
        {
            if (typeof end === 'undefined') end = start;
            el = el[0] || el;

            var range = this.getRange();
            range.selectNodeContents(el);

            var textNodes = this.getTextNodesIn(el);
            var foundStart = false;
            var charCount = 0, endCharCount;

            if (textNodes.length == 1 && start)
            {
                range.setStart(textNodes[0], start);
                range.setEnd(textNodes[0], end);
            }
            else
            {
                for (var i = 0, textNode; textNode = textNodes[i++];)
                {
                    endCharCount = charCount + textNode.length;
                    if (!foundStart && start >= charCount && (start < endCharCount || (start == endCharCount && i < textNodes.length)))
                    {
                        range.setStart(textNode, start - charCount);
                        foundStart = true;
                    }

                    if (foundStart && end <= endCharCount)
                    {
                        range.setEnd( textNode, end - charCount );
                        break;
                    }

                    charCount = endCharCount;
                }
            }

            var sel = this.getSelection();
            sel.removeAllRanges();
            sel.addRange( range );
        },
        getTextNodesIn: function (node)
        {
            var textNodes = [];

            if (node.nodeType == 3) textNodes.push(node);
            else
            {
                var children = node.childNodes;
                for (var i = 0, len = children.length; i < len; ++i)
                {
                    textNodes.push.apply(textNodes, this.getTextNodesIn(children[i]));
                }
            }

            return textNodes;
        },

        // SAVE & RESTORE
        selectionSave: function()
        {
            if (!this.isFocused()) this.$editor.focus();

            if (!this.opts.rangy)
            {
                this.selectionCreateMarker(this.getRange());
            }
            // rangy
            else
            {
                this.savedSel = rangy.saveSelection();
            }
        },
        selectionCreateMarker: function(range, remove)
        {
            if (!range) return;

            var node1 = $('<span id="selection-marker-1" class="redactor-selection-marker">' + this.opts.invisibleSpace + '</span>', this.document)[0];
            var node2 = $('<span id="selection-marker-2" class="redactor-selection-marker">' + this.opts.invisibleSpace + '</span>', this.document)[0];

            if (range.collapsed === true)
            {
                this.selectionSetMarker(range, node1, true);
            }
            else
            {
                this.selectionSetMarker(range, node1, true);
                this.selectionSetMarker(range, node2, false);
            }

            this.savedSel = this.$editor.html();

            this.selectionRestore(false, false);
        },
        selectionSetMarker: function(range, node, type)
        {
            var boundaryRange = range.cloneRange();

            boundaryRange.collapse(type);

            boundaryRange.insertNode(node);
            boundaryRange.detach();
        },
        selectionRestore: function(replace, remove)
        {
            if (!this.opts.rangy)
            {
                if (replace === true && this.savedSel)
                {
                    this.$editor.html(this.savedSel);
                }

                var node1 = this.$editor.find('span#selection-marker-1');
                var node2 = this.$editor.find('span#selection-marker-2');

                if (this.browser('mozilla'))
                {
                    this.$editor.focus();
                }
                else if (!this.isFocused())
                {
                    this.$editor.focus();
                }

                if (node1.length != 0 && node2.length != 0)
                {
                    this.selectionSet(node1[0], 0, node2[0], 0);
                }
                else if (node1.length != 0)
                {
                    this.selectionSet(node1[0], 0, null, 0);
                }

                if (remove !== false)
                {
                    this.selectionRemoveMarkers();
                    this.savedSel = false;
                }
            }
            // rangy
            else
            {
                rangy.restoreSelection(this.savedSel);
            }
        },
        selectionRemoveMarkers: function(type)
        {
            if (!this.opts.rangy)
            {
                $.each(this.$editor.find('span.redactor-selection-marker'), function()
                {
                    var html = $.trim($(this).html().replace(/[^\u0000-~]/g, ''));
                    if (html == '')
                    {
                        $(this).remove();
                    }
                    else
                    {
                        $(this).removeAttr('class').removeAttr('id');
                    }
                });
            }
            // rangy
            else
            {
                rangy.removeMarkers(this.savedSel);
            }
        },
        // GET ELEMENTS
        getCurrent: function()
        {
            var el = false;
            var sel = this.getSelection();

            if (sel.rangeCount > 0) el = sel.getRangeAt(0).startContainer;

            return this.isParentRedactor(el);
        },
        getParent: function(elem)
        {
            elem = elem || this.getCurrent();
            if (elem) return this.isParentRedactor( $( elem ).parent()[0] );
            else return false;
        },
        getBlock: function(node)
        {
            if (typeof node === 'undefined') node = this.getCurrent();

            while (node)
            {
                if (this.nodeTestBlocks(node))
                {
                    if ($(node).hasClass('redactor_editor')) return false;
                    return node;
                }

                node = node.parentNode;
            }

            return false;
        },
        getBlocks: function(nodes)
        {
            var newnodes = [];
            if (typeof nodes == 'undefined')
            {
                var range = this.getRange();
                if (range && range.collapsed === true) return [this.getBlock()];
                var nodes = this.getNodes(range);
            }

            $.each(nodes, $.proxy(function(i,node)
            {
                if (this.opts.iframe === false && $(node).parents('div.redactor_editor').size() == 0) return false;
                if (this.nodeTestBlocks(node)) newnodes.push(node);

            }, this));

            if (newnodes.length === 0) newnodes = [this.getBlock()];

            return newnodes;
        },
        nodeTestBlocks: function(node)
        {
            return node.nodeType == 1 && this.rTestBlock.test(node.nodeName);
        },
        tagTestBlock: function(tag)
        {
            return this.rTestBlock.test(tag);
        },
        getSelectedNodes: function(range)
        {
            if (typeof range == 'undefined' || range == false) var range = this.getRange()
            if (range && range.collapsed === true)
            {
                return [this.getCurrent()];
            }

            var sel = this.getSelection();
            try {
                var frag = sel.getRangeAt(0).cloneContents();
            }
            catch(e)
            {
                return(false);
            }

            var tempspan = this.document.createElement("span");
            tempspan.appendChild(frag);

            window.selnodes = tempspan.childNodes;

            var len = selnodes.length;
            var output = [];
            for(var i = 0, u = len; i<u; i++)
            {
                output.push(selnodes[i]);
            }

            if (output.length == 0) output.push(this.getCurrent());

            return output;
        },
        getNodes: function(range, tag)
        {
            if (this.opts.linebreaks)
            {
                return this.getSelectedNodes(range);
            }


            if (typeof range == 'undefined' || range == false) var range = this.getRange();
            if (range && range.collapsed === true)
            {
                if (typeof tag === 'undefined' && this.tagTestBlock(tag))
                {
                    var block = this.getBlock();
                    if (block.tagName == tag) return [block];
                    else return [];
                }
                else
                {
                    return [this.getCurrent()];
                }
            }

            var nodes = [], finalnodes = [];

            var sel = this.document.getSelection();
            if (!sel.isCollapsed) nodes = this.getRangeSelectedNodes(sel.getRangeAt(0));

            $.each(nodes, $.proxy(function(i,node)
            {
                if (this.opts.iframe === false && $(node).parents('div.redactor_editor').size() == 0) return false;

                if (typeof tag === 'undefined')
                {
                    if ($.trim(node.textContent) != '')
                    {
                        finalnodes.push(node);
                    }
                }
                else if (node.tagName == tag)
                {
                    finalnodes.push(node);
                }

            }, this));

            if (finalnodes.length == 0)
            {
                if (typeof tag === 'undefined' && this.tagTestBlock(tag))
                {
                    var block = this.getBlock();
                    if (block.tagName == tag) return finalnodes.push(block);
                    else return [];
                }
                else
                {
                    finalnodes.push(this.getCurrent());
                }
            }

            return finalnodes;
        },
        getElement: function(node)
        {
            if (!node) node = this.getCurrent();
            while (node)
            {
                if (node.nodeType == 1)
                {
                    if ($(node).hasClass('redactor_editor')) return false;
                    return node;
                }

                node = node.parentNode;
            }

            return false;
        },
        getRangeSelectedNodes: function(range)
        {
            range = range || this.getRange();
            var node = range.startContainer;
            var endNode = range.endContainer;

            if (node == endNode) return [node];

            var rangeNodes = [];
            while (node && node != endNode)
            {
                rangeNodes.push(node = this.nextNode(node));
            }

            node = range.startContainer;
            while (node && node != range.commonAncestorContainer)
            {
                rangeNodes.unshift( node );
                node = node.parentNode;
            }

            return rangeNodes;
        },
        nextNode: function(node)
        {
            if (node.hasChildNodes()) return node.firstChild;
            else
            {
                while (node && !node.nextSibling)
                {
                    node = node.parentNode;
                }

                if (!node) return null;
                return node.nextSibling;
            }
        },


        // GET SELECTION HTML OR TEXT
        getSelectionText: function()
        {
            return this.getSelection().toString();
        },
        getSelectionHtml: function()
        {
            var html = '';

            var sel = this.getSelection();
            if (sel.rangeCount)
            {
                var container = this.document.createElement( "div" );
                var len = sel.rangeCount;
                for (var i = 0; i < len; ++i)
                {
                    container.appendChild(sel.getRangeAt(i).cloneContents());
                }

                html = container.innerHTML;
            }

            return this.syncClean(html);
        },

        // TABLE
        tableShow: function()
        {
            this.selectionSave();

            this.modalInit(this.opts.curLang.table, this.opts.modal_table, 300, $.proxy(function()
            {
                $('#redactor_insert_table_btn').click($.proxy(this.tableInsert, this));

                setTimeout(function()
                {
                    $('#redactor_table_rows').focus();

                }, 200);

            }, this));
        },
        tableInsert: function()
        {
            var rows = $('#redactor_table_rows').val(),
                columns = $('#redactor_table_columns').val(),
                $table_box = $('<div></div>'),
                tableId = Math.floor(Math.random() * 99999),
                $table = $('<table id="table' + tableId + '"><tbody></tbody></table>'),
                i, $row, z, $column;

            for (i = 0; i < rows; i++)
            {
                $row = $('<tr></tr>');

                for (z = 0; z < columns; z++)
                {
                    $column = $('<td>' + this.opts.invisibleSpace + '</td>');

                    // set the focus to the first td
                    if (i === 0 && z === 0) $column.append('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');

                    $($row).append($column);
                }

                $table.append($row);
            }

            $table_box.append($table);
            var html = $table_box.html();

            this.modalClose();
            this.selectionRestore();

            var current = this.getBlock() || this.getCurrent();
            if (current)
            {
                $(current).after(html)
            }
            else
            {
                this.insertHtmlAdvanced(html);

            }

            this.selectionRestore();

            var table = this.$editor.find('#table' + tableId);
            this.tableObserver(table);
            this.buttonActiveObserver();

            table.removeAttr('id');

            this.sync();
        },
        tableObserver: function(e)
        {
            this.$table = $(e.target || e).closest('table');

            this.$tbody = $(e.target).closest('tbody');
            this.$thead = this.$table.find('thead');

            this.$current_td = $(e.target || this.$table.find('td').first());
            this.$current_tr = $(e.target || this.$table.find('tr').first()).closest('tr');
        },
        tableDeleteTable: function()
        {
            this.bufferSet();

            if (!this.$table) return;

            this.$table.remove();
            this.$table = false;
            this.sync();

        },
        tableDeleteRow: function()
        {
            this.bufferSet();

            if (!this.$current_tr) return;

            // Set the focus correctly
            var $focusTR = this.$current_tr.prev().length ? this.$current_tr.prev() : this.$current_tr.next();
            if ($focusTR.length)
            {
                var $focusTD = $focusTR.children('td' ).first();
                if ($focusTD.length)
                {
                    $focusTD.prepend('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
                    this.selectionRestore();
                }
            }

            this.$current_tr.remove();
            this.sync();
        },
        tableDeleteColumn: function()
        {
            this.bufferSet();
            var index = this.$current_td.get(0).cellIndex;

            // Set the focus correctly
            this.$table.find('tr').each($.proxy(function(i, elem)
            {
                var focusIndex = index - 1 < 0 ? index + 1 : index - 1;
                if (i === 0)
                {
                    $(elem).find('td').eq(focusIndex).prepend('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
                    this.selectionRestore();
                }

                $(elem).find('td').eq(index).remove();

            }, this));

            this.sync();
        },
        tableAddHead: function()
        {
            this.bufferSet();

            if (this.$table.find('thead').size() !== 0) this.tableDeleteHead();
            else
            {
                var tr = this.$table.find('tr').first().clone();
                tr.find('td').html( this.opts.invisibleSpace );
                this.$thead = $('<thead></thead>');
                this.$thead.append(tr);
                this.$table.prepend(this.$thead);

                this.sync();
            }
        },
        tableDeleteHead: function()
        {
            this.bufferSet();

            $(this.$thead).remove();
            this.$thead = false;

            this.sync();
        },
        tableAddRowAbove: function()
        {
            this.tableAddRow('before');
        },
        tableAddRowBelow: function()
        {
            this.tableAddRow('after');
        },
        tableAddColumnLeft: function()
        {
            this.tableAddColumn('before');
        },
        tableAddColumnRight: function()
        {
            this.tableAddColumn('after');
        },
        tableAddRow: function(type)
        {
            this.bufferSet();

            var new_tr = this.$current_tr.clone();
            new_tr.find('td').html(this.opts.invisibleSpace);

            if (type === 'after') this.$current_tr.after(new_tr);
            else this.$current_tr.before(new_tr);

            this.sync();
        },
        tableAddColumn: function (type)
        {
            this.bufferSet();

            var index = 0;

            this.$current_tr.find('td').each($.proxy(function(i, elem)
            {
                if ($(elem)[0] === this.$current_td[0]) index = i;

            }, this));

            this.$table.find('tr').each($.proxy(function(i, elem)
            {
                var $current = $(elem).find('td').eq(index);

                var td = $current.clone();
                td.html(this.opts.invisibleSpace);

                type === 'after' ? $current.after(td) : $current.before(td);

            }, this));

            this.sync();
        },

        // VIDEO
        videoShow: function()
        {
            this.selectionSave();

            this.modalInit(this.opts.curLang.video, this.opts.modal_video, 600, $.proxy(function()
            {
                $('#redactor_insert_video_btn').click($.proxy(this.videoInsert, this));

                setTimeout(function()
                {
                    $('#redactor_insert_video_area').focus();

                }, 200);

            }, this));
        },
        videoInsert: function ()
        {
            var data = $('#redactor_insert_video_area').val();
            data = this.cleanStripTags(data);

            this.selectionRestore();

            var current = this.getBlock() || this.getCurrent();

            if (current) $(current).after(data)
            else this.insertHtmlAdvanced(data);

            this.sync();
            this.modalClose();
        },

        // LINK
        linkShow: function()
        {
            this.selectionSave();

            var callback = $.proxy(function()
            {
                this.insert_link_node = false;

                var sel = this.getSelection();
                var url = '', text = '', target = '';

                var elem = this.getParent();
                var par = $(elem).parent().get(0);
                if (par && par.tagName === 'A')
                {
                    elem = par;
                }

                if (elem && elem.tagName === 'A')
                {
                    url = elem.href;
                    text = $(elem).text();
                    target = elem.target;

                    this.insert_link_node = elem;
                }
                else text = sel.toString();

                $('.redactor_link_text').val(text);

                var thref = self.location.href.replace(/\/$/i, '');
                var turl = url.replace(thref, '');

                // remove host from href
                if (this.opts.linkProtocol === false)
                {
                    var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
                    turl = turl.replace(re, '');
                }

                var tabs = $('#redactor_tabs').find('a');

                if (this.opts.linkEmail === false) tabs.eq(1).remove();
                if (this.opts.linkAnchor === false) tabs.eq(2).remove();
                if (this.opts.linkEmail === false && this.opts.linkAnchor === false)
                {
                    $('#redactor_tabs').remove();
                    $('#redactor_link_url').val(turl);
                }
                else
                {
                    if (url.search('mailto:') === 0)
                    {
                        this.modalSetTab.call(this, 2);

                        $('#redactor_tab_selected').val(2);
                        $('#redactor_link_mailto').val(url.replace('mailto:', ''));
                    }
                    else if (turl.search(/^#/gi) === 0)
                    {
                        this.modalSetTab.call(this, 3);

                        $('#redactor_tab_selected').val(3);
                        $('#redactor_link_anchor').val(turl.replace(/^#/gi, '' ));
                    }
                    else
                    {
                        $('#redactor_link_url').val(turl);
                    }
                }

                if (target === '_blank') $('#redactor_link_blank').prop('checked', true);

                $('#redactor_insert_link_btn').click($.proxy(this.linkProcess, this));

                setTimeout(function()
                {
                    $('#redactor_link_url').focus();

                }, 200);

            }, this);

            this.modalInit(this.opts.curLang.link, this.opts.modal_link, 460, callback);

        },
        linkProcess: function()
        {
            var tab_selected = $('#redactor_tab_selected').val();
            var link = '', text = '', target = '', targetBlank = '';

            // url
            if (tab_selected === '1')
            {
                link = $('#redactor_link_url').val();
                text = $('#redactor_link_url_text').val();

                if ($('#redactor_link_blank').prop('checked'))
                {
                    target = ' target="_blank"';
                    targetBlank = '_blank';
                }

                // test url (add protocol)
                var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}';
                var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
                var re2 = new RegExp('^' + pattern, 'i');

                if (link.search(re) == -1 && link.search(re2) == 0 && this.opts.linkProtocol)
                {
                    link = this.opts.linkProtocol + link;
                }
            }
            // mailto
            else if (tab_selected === '2')
            {
                link = 'mailto:' + $('#redactor_link_mailto').val();
                text = $('#redactor_link_mailto_text').val();
            }
            // anchor
            else if (tab_selected === '3')
            {
                link = '#' + $('#redactor_link_anchor').val();
                text = $('#redactor_link_anchor_text').val();
            }

            this.linkInsert('<a href="' + link + '"' + target + '>' + text + '</a>', $.trim(text), link, targetBlank);

        },
        linkInsert: function (a, text, link, target)
        {
            this.selectionRestore();

            if (text !== '')
            {
                if (this.insert_link_node)
                {
                    this.bufferSet();

                    $(this.insert_link_node).text(text).attr('href', link);

                    if (target !== '') $(this.insert_link_node).attr('target', target);
                    else $(this.insert_link_node).removeAttr('target');

                    this.sync();
                }
                else
                {
                    this.exec('inserthtml', a);
                }
            }

            this.modalClose();
        },

        // FILE
        fileShow: function ()
        {

            this.selectionSave();

            var callback = $.proxy(function()
            {
                var sel = this.getSelection();

                var text = '';
                if (this.oldIE()) text = sel.text;
                else text = sel.toString();

                $('#redactor_filename').val(text);

                // dragupload
                if (!this.isMobile())
                {
                    this.draguploadInit('#redactor_file', {
                        url: this.opts.fileUpload,
                        uploadFields: this.opts.uploadFields,
                        success: $.proxy(this.fileCallback, this),
                        error: $.proxy( function(obj, json)
                        {
                            this.callback('fileUploadError', json);

                        }, this)
                    });
                }

                this.uploadInit('redactor_file', {
                    auto: true,
                    url: this.opts.fileUpload,
                    success: $.proxy(this.fileCallback, this),
                    error: $.proxy(function(obj, json)
                    {
                        this.callback('fileUploadError', json);

                    }, this)
                });

            }, this);

            this.modalInit(this.opts.curLang.file, this.opts.modal_file, 500, callback);
        },
        fileCallback: function(json)
        {

            this.selectionRestore();

            if (json !== false)
            {

                var text = $('#redactor_filename').val();
                if (text === '') text = json.filename;

                var link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';

                // chrome fix
                if (this.browser('webkit') && !!this.window.chrome)
                {
                    link = link + '&nbsp;';
                }

                this.execCommand('inserthtml', link, false);

                var linkmarker = $(this.$editor.find('a#filelink-marker'));
                if (linkmarker.size() != 0) linkmarker.removeAttr('id');
                else linkmarker = false;

                this.sync();

                // file upload callback
                this.callback('fileUpload', linkmarker, json);
            }

            this.modalClose();
        },

        // IMAGE
        imageShow: function()
        {

            this.selectionSave();

            var callback = $.proxy(function()
            {
                // json
                if (this.opts.imageGetJson)
                {
                    $.getJSON(this.opts.imageGetJson, $.proxy(function(data)
                    {
                        var folders = {}, count = 0;

                        // folders
                        $.each(data, $.proxy(function(key, val)
                        {
                            if (typeof val.folder !== 'undefined')
                            {
                                count++;
                                folders[val.folder] = count;
                            }

                        }, this));

                        var folderclass = false;
                        $.each(data, $.proxy(function(key, val)
                        {
                            // title
                            var thumbtitle = '';
                            if (typeof val.title !== 'undefined') thumbtitle = val.title;

                            var folderkey = 0;
                            if (!$.isEmptyObject(folders) && typeof val.folder !== 'undefined')
                            {
                                folderkey = folders[val.folder];
                                if (folderclass === false) folderclass = '.redactorfolder' + folderkey;
                            }

                            var img = $('<img src="' + val.thumb + '" class="redactorfolder redactorfolder' + folderkey + '" rel="' + val.image + '" title="' + thumbtitle + '" />');
                            $('#redactor_image_box').append(img);
                            $(img).click($.proxy(this.imageThumbClick, this));

                        }, this));

                        // folders
                        if (!$.isEmptyObject(folders))
                        {
                            $('.redactorfolder').hide();
                            $(folderclass).show();

                            var onchangeFunc = function(e)
                            {
                                $('.redactorfolder').hide();
                                $('.redactorfolder' + $(e.target).val()).show();
                            };

                            var select = $('<select id="redactor_image_box_select">');
                            $.each( folders, function(k, v)
                            {
                                select.append( $('<option value="' + v + '">' + k + '</option>'));
                            });

                            $('#redactor_image_box').before(select);
                            select.change(onchangeFunc);
                        }
                    }, this));

                }
                else
                {
                    $('#redactor_tabs').find('a').eq(1).remove();
                }

                if (this.opts.imageUpload || this.opts.s3)
                {
                    // dragupload
                    if (!this.isMobile() && this.opts.s3 === false)
                    {
                        if ($('#redactor_file' ).length)
                        {
                            this.draguploadInit('#redactor_file', {
                                url: this.opts.imageUpload,
                                uploadFields: this.opts.uploadFields,
                                success: $.proxy(this.imageCallback, this),
                                error: $.proxy(function(obj, json)
                                {
                                    this.callback('imageUploadError', json);

                                }, this)
                            });
                        }
                    }

                    if (this.opts.s3 === false)
                    {
                        // ajax upload
                        this.uploadInit('redactor_file', {
                            auto: true,
                            url: this.opts.imageUpload,
                            success: $.proxy(this.imageCallback, this),
                            error: $.proxy(function(obj, json)
                            {
                                this.callback('imageUploadError', json);

                            }, this)
                        });
                    }
                    // s3 upload
                    else
                    {
                        $('#redactor_file').on('change.redactor', $.proxy(this.s3handleFileSelect, this));
                    }

                }
                else
                {
                    $('.redactor_tab').hide();
                    if (!this.opts.imageGetJson)
                    {
                        $('#redactor_tabs').remove();
                        $('#redactor_tab3').show();
                    }
                    else
                    {
                        var tabs = $('#redactor_tabs').find('a');
                        tabs.eq(0).remove();
                        tabs.eq(1).addClass('redactor_tabs_act');
                        $('#redactor_tab2').show();
                    }
                }

                $('#redactor_upload_btn').click($.proxy(this.imageCallbackLink, this));

                if (!this.opts.imageUpload && !this.opts.imageGetJson)
                {
                    setTimeout(function()
                    {
                        $('#redactor_file_link').focus();

                    }, 200);
                }

            }, this);

            this.modalInit(this.opts.curLang.image, this.opts.modal_image, 610, callback);

        },
        imageEdit: function(image)
        {
            var $el = image;
            var parent = $el.parent().parent();

            var callback = $.proxy(function()
            {
                $('#redactor_file_alt').val($el.attr('alt'));
                $('#redactor_image_edit_src').attr('href', $el.attr('src'));
                $('#redactor_form_image_align').val($el.css('float'));

                if ($(parent).get(0).tagName === 'A')
                {
                    $('#redactor_file_link').val($(parent).attr('href'));

                    if ($(parent).attr('target') == '_blank')
                    {
                        $('#redactor_link_blank').prop('checked', true);
                    }
                }

                $('#redactor_image_delete_btn').click($.proxy(function()
                {
                    this.imageRemove($el);

                }, this));

                $('#redactorSaveBtn').click($.proxy(function()
                {
                    this.imageSave($el);

                }, this));

            }, this);

            this.modalInit(this.opts.curLang.image, this.opts.modal_image_edit, 380, callback);

        },
        imageRemove: function(el)
        {
            var parent = $(el).parent();
            $(el).remove();

            if (parent.length && parent[0].tagName === 'P')
            {
                this.$editor.focus();
                this.selectionStart(parent);
            }

            // delete callback
            this.callback('imageDelete', el);

            this.modalClose();
            this.sync();
        },
        imageSave: function(el)
        {
            var $el = $(el);
            var parent = $el.parent();

            $el.attr('alt', $('#redactor_file_alt').val());

            var floating = $('#redactor_form_image_align').val();

            if (floating === 'left')
            {
                $el.css({ 'float': 'left', 'margin': '0 10px 10px 0' });
            }
            else if (floating === 'right')
            {
                $el.css({ 'float': 'right', 'margin': '0 0 10px 10px' });
            }
            else
            {
                var imageBox = $el.closest('#redactor-image-box');
                if (imageBox.size() != 0) imageBox.css({ 'float': '', 'margin': '' });
                $el.css({ 'float': '', 'margin': '' });
            }

            // as link
            var link = $.trim($('#redactor_file_link').val());
            if (link !== '')
            {
                var target = false;
                if ($('#redactor_link_blank').prop('checked'))
                {
                    target = true;
                }

                if (parent.get(0).tagName !== 'A')
                {
                    var a = $('<a href="' + link + '">' + this.outerHtml(el) + '</a>');

                    if (target)
                    {
                        a.attr('target', '_blank');
                    }

                    $el.replaceWith(a);
                }
                else
                {
                    parent.attr('href', link);
                    if (target)
                    {
                        parent.attr('target', '_blank');
                    }
                    else
                    {
                        parent.removeAttr('target');
                    }
                }
            }
            else
            {
                if (parent.get(0).tagName === 'A')
                {
                    parent.replaceWith(this.outerHtml(el));
                }
            }

            this.modalClose();
            this.observeImages();
            this.sync();

        },
        imageResizeHide: function(e)
        {
            if (e !== false && $(e.target).parent().size() != 0 && $(e.target).parent()[0].id === 'redactor-image-box')
            {
                return false;
            }

            var imageBox = this.$editor.find('#redactor-image-box');
            if (imageBox.size() == 0)
            {
                return false;
            }

            this.$editor.find('#redactor-image-editter, #redactor-image-resizer').remove();

            var margin = imageBox.css('margin');
            if (margin != '0px')
            {
                imageBox.find('img').css('margin', margin);
                imageBox.css('margin', '');
            }

            imageBox.find('img').css('opacity', 1);
            imageBox.replaceWith(function()
            {
                return $(this).contents();
            });

            $(document).off('click.redactor-image-resize-hide');
            this.$editor.off('click.redactor-image-resize-hide');
            this.$editor.off('keydown.redactor-image-delete');

        },
        imageResize: function(image)
        {
            var $image = $(image);

            $image.on('mousedown', $.proxy(function()
            {
                this.imageResizeHide(false);
            }, this));

            $image.on('dragstart', $.proxy(function()
            {
                this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
                {
                    setTimeout($.proxy(function()
                    {
                        this.observeImages();
                        this.$editor.off('drop.redactor-image-inside-drop');
                        this.sync();

                    }, this), 1);

                },this));
            }, this));

            $image.on('click', $.proxy(function(e)
            {
                if (this.$editor.find('#redactor-image-box').size() != 0)
                {
                    return false;
                }

                var clicked = false,
                    start_x,
                    start_y,
                    ratio = $image.width() / $image.height(),
                    min_w = 20,
                    min_h = 10;

                var imageResizer = this.imageResizeControls($image);

                // resize
                var isResizing = false;
                imageResizer.on('mousedown', function(e)
                {
                    isResizing = true;
                    e.preventDefault();

                    ratio = $image.width() / $image.height();

                    start_x = Math.round(e.pageX - $image.eq(0).offset().left);
                    start_y = Math.round(e.pageY - $image.eq(0).offset().top);

                });

                $(this.document.body).on('mousemove', $.proxy(function(e)
                    {
                        if (isResizing)
                        {
                            var mouse_x = Math.round(e.pageX - $image.eq(0).offset().left) - start_x;
                            var mouse_y = Math.round(e.pageY - $image.eq(0).offset().top) - start_y;

                            var div_h = $image.height();

                            var new_h = parseInt(div_h, 10) + mouse_y;
                            var new_w = Math.round(new_h * ratio);

                            if (new_w > min_w)
                            {
                                $image.width(new_w);

                                if (new_w < 100)
                                {
                                    this.imageEditter.css({
                                        marginTop: '-7px',
                                        marginLeft: '-13px',
                                        fontSize: '9px',
                                        padding: '3px 5px'
                                    });
                                }
                                else
                                {
                                    this.imageEditter.css({
                                        marginTop: '-11px',
                                        marginLeft: '-18px',
                                        fontSize: '11px',
                                        padding: '7px 10px'
                                    });
                                }
                            }

                            start_x = Math.round(e.pageX - $image.eq(0).offset().left);
                            start_y = Math.round(e.pageY - $image.eq(0).offset().top);

                            this.sync()
                        }
                    }, this)).on('mouseup', function()
                    {
                        isResizing = false;
                    });


                this.$editor.on('keydown.redactor-image-delete', $.proxy(function(e)
                {
                    var key = e.which;

                    if (this.keyCode.BACKSPACE == key || this.keyCode.DELETE == key)
                    {
                        this.imageResizeHide(false);
                        this.imageRemove($image);
                    }

                }, this));

                $(document).on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));
                this.$editor.on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));


            }, this));
        },
        imageResizeControls: function($image)
        {
            var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
            imageBox.css({
                position: 'relative',
                display: 'inline-block',
                lineHeight: 0,
                outline: '1px dashed rgba(0, 0, 0, .6)',
                'float': $image.css('float')
            });
            imageBox.attr('contenteditable', false);

            var margin = $image.css('margin');
            if (margin != '0px')
            {
                imageBox.css('margin', margin);
                $image.css('margin', '');
            }

            $image.css('opacity', .5).after(imageBox);

            // editter
            this.imageEditter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.opts.curLang.edit + '</span>');
            this.imageEditter.css({
                position: 'absolute',
                zIndex: 2,
                top: '50%',
                left: '50%',
                marginTop: '-11px',
                marginLeft: '-18px',
                lineHeight: 1,
                backgroundColor: '#000',
                color: '#fff',
                fontSize: '11px',
                padding: '7px 10px',
                cursor: 'pointer'
            });
            this.imageEditter.attr('contenteditable', false);
            this.imageEditter.on('click', $.proxy(function()
            {
                this.imageEdit($image);
            }, this));
            imageBox.append(this.imageEditter);

            // resizer
            var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
            imageResizer.css({
                position: 'absolute',
                zIndex: 2,
                lineHeight: 1,
                cursor: 'nw-resize',
                bottom: '-4px',
                right: '-5px',
                border: '1px solid #fff',
                backgroundColor: '#000',
                width: '8px',
                height: '8px'
            });
            imageResizer.attr('contenteditable', false);
            imageBox.append(imageResizer);

            imageBox.append($image);

            return imageResizer;
        },
        imageThumbClick: function(e)
        {
            var img = '<img id="image-marker" src="' + $(e.target).attr('rel') + '" alt="' + $(e.target).attr('title') + '" />';

            if (this.opts.paragraphy) img = '<p>' + img + '</p>';

            this.imageInsert(img, true);
        },
        imageCallbackLink: function()
        {
            var val = $('#redactor_file_link').val();

            if (val !== '')
            {
                var data = '<img id="image-marker" src="' + val + '" />';
                if (this.opts.linebreaks === false) data = '<p>' + data + '</p>';

                this.imageInsert(data, true);

            }
            else this.modalClose();
        },
        imageCallback: function(data)
        {
            this.imageInsert(data);
        },
        imageInsert: function(json, link)
        {
            this.selectionRestore();

            if (json !== false)
            {
                var html = '';
                if (link !== true)
                {
                    html = '<img id="image-marker" src="' + json.filelink + '" />';
                    if (this.opts.paragraphy) html = '<p>' + html + '</p>';
                }
                else
                {
                    html = json;
                }

                this.execCommand('inserthtml', html, false);

                var image = $(this.$editor.find('img#image-marker'));

                if (image.length) image.removeAttr('id');
                else image = false;

                this.sync();

                // upload image callback
                link !== true && this.callback('imageUpload', image, json);
            }

            this.modalClose();
            this.observeImages();
        },

        // MODAL
        modalTemplatesInit: function()
        {
            $.extend( this.opts,
                {
                    modal_file: String()
                        + '<section>'
                        + '<div id="redactor-progress" class="redactor-progress redactor-progress-striped" style="display: none;">'
                        + '<div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div>'
                        + '</div>'
                        + '<form id="redactorUploadFileForm" method="post" action="" enctype="multipart/form-data">'
                        + '<label>' + this.opts.curLang.filename + '</label>'
                        + '<input type="text" id="redactor_filename" class="redactor_input" />'
                        + '<div style="margin-top: 7px;">'
                        + '<input type="file" id="redactor_file" name="file" />'
                        + '</div>'
                        + '</form>'
                        + '</section>',

                    modal_image_edit: String()
                        + '<section>'
                        + '<label>' + this.opts.curLang.title + '</label>'
                        + '<input id="redactor_file_alt" class="redactor_input" />'
                        + '<label>' + this.opts.curLang.link + '</label>'
                        + '<input id="redactor_file_link" class="redactor_input" />'
                        + '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
                        + '<label>' + this.opts.curLang.image_position + '</label>'
                        + '<select id="redactor_form_image_align">'
                        + '<option value="none">' + this.opts.curLang.none + '</option>'
                        + '<option value="left">' + this.opts.curLang.left + '</option>'
                        + '<option value="right">' + this.opts.curLang.right + '</option>'
                        + '</select>'
                        + '</section>'
                        + '<footer>'
                        + '<button id="redactor_image_delete_btn" class="redactor_modal_btn">' + this.opts.curLang._delete + '</button>&nbsp;&nbsp;&nbsp;'
                        + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
                        + '<input type="button" name="save" class="redactor_modal_btn" id="redactorSaveBtn" value="' + this.opts.curLang.save + '" />'
                        + '</footer>',

                    modal_image: String()
                        + '<section>'
                        + '<div id="redactor_tabs">'
                        + '<a href="#" class="redactor_tabs_act">' + this.opts.curLang.upload + '</a>'
                        + '<a href="#">' + this.opts.curLang.choose + '</a>'
                        + '<a href="#">' + this.opts.curLang.link + '</a>'
                        + '</div>'
                        + '<div id="redactor-progress" class="redactor-progress redactor-progress-striped" style="display: none;">'
                        + '<div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div>'
                        + '</div>'
                        + '<form id="redactorInsertImageForm" method="post" action="" enctype="multipart/form-data">'
                        + '<div id="redactor_tab1" class="redactor_tab">'
                        + '<input type="file" id="redactor_file" name="file" />'
                        + '</div>'
                        + '<div id="redactor_tab2" class="redactor_tab" style="display: none;">'
                        + '<div id="redactor_image_box"></div>'
                        + '</div>'
                        + '</form>'
                        + '<div id="redactor_tab3" class="redactor_tab" style="display: none;">'
                        + '<label>' + this.opts.curLang.image_web_link + '</label>'
                        + '<input type="text" name="redactor_file_link" id="redactor_file_link" class="redactor_input"  />'
                        + '</div>'
                        + '</section>'
                        + '<footer>'
                        + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
                        + '<input type="button" name="upload" class="redactor_modal_btn" id="redactor_upload_btn" value="' + this.opts.curLang.insert + '" />'
                        + '</footer>',

                    modal_link: String()
                        + '<section>'
                        + '<form id="redactorInsertLinkForm" method="post" action="">'
                        + '<div id="redactor_tabs">'
                        + '<a href="#" class="redactor_tabs_act">URL</a>'
                        + '<a href="#">Email</a>'
                        + '<a href="#">' + this.opts.curLang.anchor + '</a>'
                        + '</div>'
                        + '<input type="hidden" id="redactor_tab_selected" value="1" />'
                        + '<div class="redactor_tab" id="redactor_tab1">'
                        + '<label>URL</label>'
                        + '<input type="text" id="redactor_link_url" class="redactor_input"  />'
                        + '<label>' + this.opts.curLang.text + '</label>'
                        + '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_url_text" />'
                        + '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
                        + '</div>'
                        + '<div class="redactor_tab" id="redactor_tab2" style="display: none;">'
                        + '<label>Email</label>'
                        + '<input type="text" id="redactor_link_mailto" class="redactor_input" />'
                        + '<label>' + this.opts.curLang.text + '</label>'
                        + '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_mailto_text" />'
                        + '</div>'
                        + '<div class="redactor_tab" id="redactor_tab3" style="display: none;">'
                        + '<label>' + this.opts.curLang.anchor + '</label>'
                        + '<input type="text" class="redactor_input" id="redactor_link_anchor"  />'
                        + '<label>' + this.opts.curLang.text + '</label>'
                        + '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_anchor_text" />'
                        + '</div>'
                        + '</form>'
                        + '</section>'
                        + '<footer>'
                        + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
                        + '<input type="button" class="redactor_modal_btn" id="redactor_insert_link_btn" value="' + this.opts.curLang.insert + '" />'
                        + '</footer>',

                    modal_table: String()
                        + '<section>'
                        + '<label>' + this.opts.curLang.rows + '</label>'
                        + '<input type="text" size="5" value="2" id="redactor_table_rows" />'
                        + '<label>' + this.opts.curLang.columns + '</label>'
                        + '<input type="text" size="5" value="3" id="redactor_table_columns" />'
                        + '</section>'
                        + '<footer>'
                        + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
                        + '<input type="button" name="upload" class="redactor_modal_btn" id="redactor_insert_table_btn" value="' + this.opts.curLang.insert + '" />'
                        + '</footer>',

                    modal_video: String()
                        + '<section>'
                        + '<form id="redactorInsertVideoForm">'
                        + '<label>' + this.opts.curLang.video_html_code + '</label>'
                        + '<textarea id="redactor_insert_video_area" style="width: 99%; height: 160px;"></textarea>'
                        + '</form>'
                        + '</section>'
                        + '<footer>'
                        + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
                        + '<input type="button" class="redactor_modal_btn" id="redactor_insert_video_btn" value="' + this.opts.curLang.insert + '" />'
                        + '</footer>'

                });
        },
        modalInit: function(title, content, width, callback)
        {
            var $redactorModalOverlay = $('#redactor_modal_overlay');

            // modal overlay
            if (!$redactorModalOverlay.length)
            {
                this.$overlay = $redactorModalOverlay = $('<div id="redactor_modal_overlay" style="display: none;"></div>');
                $('body').prepend(this.$overlay);
            }

            if (this.opts.modalOverlay)
            {
                $redactorModalOverlay.show().on('click', $.proxy(this.modalClose, this));
            }

            var $redactorModal = $('#redactor_modal');

            if (!$redactorModal.length)
            {
                this.$modal = $redactorModal = $('<div id="redactor_modal" style="display: none;"><div id="redactor_modal_close">&times;</div><header id="redactor_modal_header"></header><div id="redactor_modal_inner"></div></div>');
                $('body').append(this.$modal);
            }

            $('#redactor_modal_close').on('click', $.proxy(this.modalClose, this));

            this.hdlModalClose = $.proxy(function(e)
            {
                if (e.keyCode === this.keyCode.ESC)
                {
                    this.modalClose();
                    return false;
                }

            }, this);

            $(document).keyup(this.hdlModalClose);
            this.$editor.keyup(this.hdlModalClose);

            // set content
            this.modalcontent = false;
            if (content.indexOf('#') == 0)
            {
                this.modalcontent = $(content);
                $('#redactor_modal_inner').empty().append(this.modalcontent.html());
                this.modalcontent.html('');

            }
            else
            {
                $('#redactor_modal_inner').empty().append(content);
            }

            $redactorModal.find('#redactor_modal_header').html(title);

            // draggable
            if (typeof $.fn.draggable !== 'undefined')
            {
                $redactorModal.draggable({ handle: '#redactor_modal_header' });
                $redactorModal.find('#redactor_modal_header').css('cursor', 'move');
            }

            var $redactor_tabs = $('#redactor_tabs');

            // tabs
            if ($redactor_tabs.length )
            {
                var that = this;
                $redactor_tabs.find('a').each(function(i, s)
                {
                    i++;
                    $(s).on('click', function(e)
                    {
                        e.preventDefault();

                        $redactor_tabs.find('a').removeClass('redactor_tabs_act');
                        $(this).addClass('redactor_tabs_act');
                        $('.redactor_tab').hide();
                        $('#redactor_tab' + i ).show();
                        $('#redactor_tab_selected').val(i);

                        if (that.isMobile() === false)
                        {
                            var height = $redactorModal.outerHeight();
                            $redactorModal.css('margin-top', '-' + (height + 10) / 2 + 'px');
                        }
                    });
                });
            }

            $redactorModal.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose, this));

            // save scroll
            if (this.opts.autoresize === true) this.saveModalScroll = this.document.body.scrollTop;

            if (this.isMobile() === false)
            {
                $redactorModal.css({
                    position: 'fixed',
                    top: '-2000px',
                    left: '50%',
                    width: width + 'px',
                    marginLeft: '-' + (width + 60) / 2 + 'px'
                }).show();

                this.modalSaveBodyOveflow = $(document.body).css('overflow');
                $(document.body).css('overflow', 'hidden');

            }
            else
            {
                $redactorModal.css({
                    position: 'fixed',
                    width: '100%',
                    height: '100%',
                    top: '0',
                    left: '0',
                    margin: '0',
                    minHeight: '300px'
                }).show();
            }

            // callback
            if (typeof callback === 'function') callback();

            if (this.isMobile() === false)
            {
                setTimeout(function()
                {
                    var height = $redactorModal.outerHeight();
                    $redactorModal.css({
                        top: '50%',
                        height: 'auto',
                        minHeight: 'auto',
                        marginTop: '-' + (height + 10) / 2 + 'px'
                    });
                }, 10);
            }

        },
        modalClose: function()
        {
            $('#redactor_modal_close').off('click', this.modalClose );
            $('#redactor_modal').fadeOut('fast', $.proxy(function()
            {
                var redactorModalInner = $('#redactor_modal_inner');

                if (this.modalcontent !== false)
                {
                    this.modalcontent.html(redactorModalInner.html());
                    this.modalcontent = false;
                }

                redactorModalInner.html('');

                if (this.opts.modalOverlay)
                {
                    $('#redactor_modal_overlay').hide().off('click', this.modalClose);
                }

                $(document).unbind('keyup', this.hdlModalClose);
                this.$editor.unbind('keyup', this.hdlModalClose);

                this.selectionRestore();

                // restore scroll
                if (this.opts.autoresize && this.saveModalScroll) $(this.document.body).scrollTop(this.saveModalScroll);

            }, this));


            if (this.isMobile() === false)
            {
                $(document.body).css('overflow', this.modalSaveBodyOveflow ? this.modalSaveBodyOveflow : 'visible');
            }

            return false;
        },
        modalSetTab: function(num)
        {
            $('.redactor_tab').hide();
            $('#redactor_tabs').find('a').removeClass('redactor_tabs_act').eq(num - 1).addClass('redactor_tabs_act');
            $('#redactor_tab' + num).show();
        },


        // S3
        s3handleFileSelect: function(e)
        {
            var files = e.target.files;

            for (var i = 0, f; f = files[i]; i++)
            {
                this.s3uploadFile(f);
            }
        },
        s3uploadFile: function(file)
        {
            this.s3executeOnSignedUrl(file, $.proxy(function(signedURL)
            {
                this.s3uploadToS3(file, signedURL);
            }, this));
        },
        s3executeOnSignedUrl: function(file, callback)
        {
            var xhr = new XMLHttpRequest();
            xhr.open('GET', this.opts.s3 + '?name=' + file.name + '&type=' + file.type, true);

            // Hack to pass bytes through unprocessed.
            xhr.overrideMimeType('text/plain; charset=x-user-defined');

            xhr.onreadystatechange = function(e)
            {
                if (this.readyState == 4 && this.status == 200)
                {
                    $('#redactor-progress').fadeIn();
                    callback(decodeURIComponent(this.responseText));
                }
                else if(this.readyState == 4 && this.status != 200)
                {
                    //setProgress(0, 'Could not contact signing script. Status = ' + this.status);
                }
            };

            xhr.send();
        },
        s3createCORSRequest: function(method, url)
        {
            var xhr = new XMLHttpRequest();
            if ("withCredentials" in xhr)
            {
                xhr.open(method, url, true);
            }
            else if (typeof XDomainRequest != "undefined")
            {
                xhr = new XDomainRequest();
                xhr.open(method, url);
            }
            else
            {
                xhr = null;
            }

            return xhr;
        },
        s3uploadToS3: function(file, url)
        {
            var xhr = this.s3createCORSRequest('PUT', url);
            if (!xhr)
            {
                //setProgress(0, 'CORS not supported');
            }
            else
            {
                xhr.onload = $.proxy(function()
                {
                    if (xhr.status == 200)
                    {
                        //setProgress(100, 'Upload completed.');

                        $('#redactor-progress').hide();

                        var s3image = url.split('?');

                        if (!s3image[0])
                        {
                            // url parsing is fail
                            return false;
                        }

                        this.selectionRestore();

                        var html = '';
                        html = '<img id="image-marker" src="' + s3image[0] + '" />';
                        if (this.opts.paragraphy) html = '<p>' + html + '</p>';

                        this.execCommand('inserthtml', html, false);

                        var image = $(this.$editor.find('img#image-marker'));

                        if (image.length) image.removeAttr('id');
                        else image = false;

                        this.sync();

                        // upload image callback
                        this.callback('imageUpload', image, false);

                        this.modalClose();
                        this.observeImages();

                    }
                    else
                    {
                        //setProgress(0, 'Upload error: ' + xhr.status);
                    }
                }, this);

                xhr.onerror = function()
                {
                    //setProgress(0, 'XHR error.');
                };

                xhr.upload.onprogress = function(e)
                {
                    /*
                     if (e.lengthComputable)
                     {
                     var percentLoaded = Math.round((e.loaded / e.total) * 100);
                     setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.');
                     }
                     */
                };

                xhr.setRequestHeader('Content-Type', file.type);
                xhr.setRequestHeader('x-amz-acl', 'public-read');

                xhr.send(file);
            }
        },


        // UPLOAD
        uploadInit: function(el, options)
        {
            this.uploadOptions = {
                url: false,
                success: false,
                error: false,
                start: false,
                trigger: false,
                auto: false,
                input: false
            };

            $.extend(this.uploadOptions, options);

            var $el = $('#' + el);

            // Test input or form
            if ($el.length && $el[0].tagName === 'INPUT')
            {
                this.uploadOptions.input = $el;
                this.el = $($el[0].form);
            }
            else this.el = $el;

            this.element_action = this.el.attr('action');

            // Auto or trigger
            if (this.uploadOptions.auto)
            {
                $(this.uploadOptions.input).change($.proxy(function(e)
                {
                    this.el.submit(function(e)
                    {
                        return false;
                    });

                    this.uploadSubmit(e);

                }, this));

            }
            else if (this.uploadOptions.trigger)
            {
                $('#' + this.uploadOptions.trigger).click($.proxy(this.uploadSubmit, this));
            }
        },
        uploadSubmit: function(e)
        {
            $('#redactor-progress').fadeIn();
            this.uploadForm(this.element, this.uploadFrame());
        },
        uploadFrame: function()
        {
            this.id = 'f' + Math.floor(Math.random() * 99999);

            var d = this.document.createElement('div');
            var iframe = '<iframe style="display:none" id="' + this.id + '" name="' + this.id + '"></iframe>';

            d.innerHTML = iframe;
            $(d).appendTo("body");

            // Start
            if (this.uploadOptions.start) this.uploadOptions.start();

            $( '#' + this.id ).load($.proxy(this.uploadLoaded, this));

            return this.id;
        },
        uploadForm: function(f, name)
        {
            if (this.uploadOptions.input)
            {
                var formId = 'redactorUploadForm' + this.id,
                    fileId = 'redactorUploadFile' + this.id;

                this.form = $('<form  action="' + this.uploadOptions.url + '" method="POST" target="' + name + '" name="' + formId + '" id="' + formId + '" enctype="multipart/form-data" />');

                // append hidden fields
                if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object')
                {
                    $.each(this.opts.uploadFields, $.proxy(function(k, v)
                    {
                        if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();

                        var hidden = $('<input/>', {
                            'type': "hidden",
                            'name': k,
                            'value': v
                        });

                        $(this.form).append(hidden);

                    }, this));
                }

                var oldElement = this.uploadOptions.input;
                var newElement = $(oldElement).clone();

                $(oldElement).attr('id', fileId).before(newElement).appendTo(this.form);

                $(this.form).css('position', 'absolute')
                    .css('top', '-2000px')
                    .css('left', '-2000px')
                    .appendTo('body');

                this.form.submit();

            }
            else
            {
                f.attr('target', name)
                    .attr('method', 'POST')
                    .attr('enctype', 'multipart/form-data')
                    .attr('action', this.uploadOptions.url);

                this.element.submit();
            }
        },
        uploadLoaded: function()
        {
            var i = $( '#' + this.id)[0], d;

            if (i.contentDocument) d = i.contentDocument;
            else if (i.contentWindow) d = i.contentWindow.document;
            else d = window.frames[this.id].document;

            // Success
            if (this.uploadOptions.success)
            {
                $('#redactor-progress').hide();

                if (typeof d !== 'undefined')
                {
                    // Remove bizarre <pre> tag wrappers around our json data:
                    var rawString = d.body.innerHTML;
                    var jsonString = rawString.match(/\{(.|\n)*\}/)[0];

                    jsonString = jsonString.replace(/^\[/, '');
                    jsonString = jsonString.replace(/\]$/, '');

                    var json = $.parseJSON(jsonString);

                    if (typeof json.error == 'undefined') this.uploadOptions.success(json);
                    else
                    {
                        this.uploadOptions.error(this, json);
                        this.modalClose();
                    }
                }
                else
                {
                    this.modalClose();
                    alert('Upload failed!');
                }
            }

            this.el.attr('action', this.element_action);
            this.el.attr('target', '');
        },

        // DRAGUPLOAD
        draguploadInit: function (el, options)
        {
            this.draguploadOptions = $.extend({
                url: false,
                success: false,
                error: false,
                preview: false,
                uploadFields: false,
                text: this.opts.curLang.drop_file_here,
                atext: this.opts.curLang.or_choose
            }, options);

            if (window.FormData === undefined) return false;

            this.droparea = $('<div class="redactor_droparea"></div>');
            this.dropareabox = $('<div class="redactor_dropareabox">' + this.draguploadOptions.text + '</div>');
            this.dropalternative = $('<div class="redactor_dropalternative">' + this.draguploadOptions.atext + '</div>');

            this.droparea.append(this.dropareabox);

            $(el).before(this.droparea);
            $(el).before(this.dropalternative);

            // drag over
            this.dropareabox.on('dragover', $.proxy(function()
            {
                return this.draguploadOndrag();

            }, this));

            // drag leave
            this.dropareabox.on('dragleave', $.proxy(function()
            {
                return this.draguploadOndragleave();

            }, this));

            // drop
            this.dropareabox.get(0).ondrop = $.proxy(function(e)
            {
                e.preventDefault();

                this.dropareabox.removeClass('hover').addClass('drop');

                //this.handleFileSelect(e);
                this.draguploadUpload(e.dataTransfer.files[0]);

            }, this );
        },
        draguploadUpload: function(file)
        {
            var xhr = jQuery.ajaxSettings.xhr();

            if (xhr.upload)
            {
                xhr.upload.addEventListener('progress', $.proxy(this.uploadProgress, this), false);
            }

            var provider = function () { return xhr; };

            var fd = new FormData();

            // append hidden fields
            if (this.draguploadOptions.uploadFields !== false && typeof this.draguploadOptions.uploadFields === 'object')
            {
                $.each(this.draguploadOptions.uploadFields, $.proxy(function(k, v)
                {
                    if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();
                    fd.append(k, v);

                }, this));
            }

            // append file data
            fd.append('file', file);

            $.ajax({
                url: this.draguploadOptions.url,
                dataType: 'html',
                data: fd,
                xhr: provider,
                cache: false,
                contentType: false,
                processData: false,
                type: 'POST',
                success: $.proxy(function(data)
                {
                    data = data.replace(/^\[/, '');
                    data = data.replace(/\]$/, '');

                    var json = $.parseJSON(data);

                    if (typeof json.error == 'undefined')
                    {
                        this.draguploadOptions.success(json);
                    }
                    else
                    {
                        this.draguploadOptions.error(this, json);
                        this.draguploadOptions.success(false);
                    }

                }, this)
            });

        },
        draguploadOndrag: function()
        {
            this.dropareabox.addClass('hover');
            return false;
        },
        draguploadOndragleave: function()
        {
            this.dropareabox.removeClass('hover');
            return false;
        },
        uploadProgress: function(e, text)
        {
            var percent = e.loaded ? parseInt(e.loaded / e.total * 100, 10) : e;
            this.dropareabox.text('Loading ' + percent + '% ' + (text || ''));
        },


        // UTILS
        isMobile: function()
        {
            return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
        },
        normalize: function(str)
        {
            if (typeof(str) === 'undefined') return 0;
            return parseInt(str.replace('px',''), 10);
        },
        outerHtml: function(el)
        {
            return $('<div>').append($(el).eq(0).clone()).html();
        },
        isString: function(obj)
        {
            return Object.prototype.toString.call(obj) == '[object String]';
        },
        isEmpty: function(html)
        {
            html = html.replace(/&#x200b;|<br>|<br\/>|&nbsp;/gi, '');
            html = html.replace(/\s/g, '');
            html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');


            return html == '';
        },
        browser: function(browser)
        {
            var ua = navigator.userAgent.toLowerCase();
            var match = /(chrome)[ \/]([\w.]+)/.exec(ua)
                || /(webkit)[ \/]([\w.]+)/.exec(ua)
                || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua)
                || /(msie) ([\w.]+)/.exec(ua)
                || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)
                || [];

            if (browser == 'version') return match[2];
            if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'webkit');

            return match[1] == browser;
        },
        oldIE: function()
        {
            if (this.browser('msie') && parseInt(this.browser('version'), 10) < 9) return true;
            return false;
        },
        getFragmentHtml: function (fragment)
        {
            var cloned = fragment.cloneNode(true);
            var div = this.document.createElement('div');

            div.appendChild(cloned);
            return div.innerHTML;
        },
        extractContent: function()
        {
            var node = this.$editor[0];
            var frag = this.document.createDocumentFragment();
            var child;

            while ((child = node.firstChild))
            {
                frag.appendChild(child);
            }

            return frag;
        },
        isParentRedactor: function(el)
        {
            if (!el) return false;
            if (this.opts.iframe) return el;

            if ($(el).parents('div.redactor_editor').length == 0 || $(el).hasClass('redactor_editor')) return false;
            else return el;
        },
        currentOrParentIs: function(tagName)
        {
            var parent = this.getParent(), current = this.getCurrent();
            return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
        },
        isEndOfElement: function()
        {
            var current = this.getBlock();
            var offset = this.getCaretOffset(current);

            var text = $.trim($(current).text()).replace(/\n\r\n/g, '');

            var len = text.length;

            if (offset == len) return true;
            else return false;
        },
        isFocused: function()
        {
            var el, sel = this.getSelection();

            if (sel && sel.rangeCount && sel.rangeCount > 0) el = sel.getRangeAt(0).startContainer;
            if (!el) return false;
            if (this.opts.iframe)
            {
                if (this.getCaretOffsetRange().equals()) return !this.$editor.is(el);
                else return true;
            }

            return $(el).closest('div.redactor_editor').length != 0;
        },
        removeEmptyAttr: function (el, attr)
        {
            if ($(el).attr(attr) == '') $(el).removeAttr(attr);
        },
        removeFromArrayByValue: function(array, value)
        {
            var index = null;

            while ((index = array.indexOf(value)) !== -1)
            {
                array.splice(index, 1);
            }

            return array;
        }
    };

    // constructor
    Redactor.prototype.init.prototype = Redactor.prototype;

    // LINKIFY
    $.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertImageLinks, convertVideoLinks)
    {
        var url1 = /(^|&lt;|\s)(www\..+?\..+?)(\s|&gt;|$)/g,
            url2 = /(^|&lt;|\s)(((https?|ftp):\/\/|mailto:).+?)(\s|&gt;|$)/g,
            urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi,
            urlYoutube = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;

        var childNodes = (this.$editor ? this.$editor.get(0) : this).childNodes, i = childNodes.length;
        while (i--)
        {
            var n = childNodes[i];
            if (n.nodeType === 3)
            {
                var html = n.nodeValue;

                // youtube
                if (convertVideoLinks && html && html.match(urlYoutube))
                {
                    html = html.replace(urlYoutube, '<iframe width="560" height="315" src="//www.youtube.com/embed/$2" frameborder="0" allowfullscreen></iframe>');
                    $(n).after(html).remove();
                }

                // image
                else if (convertImageLinks && html && html.match(urlImage))
                {
                    html = html.replace(urlImage, '<img src="$1">');
                    $(n).after(html).remove();
                }

                // link
                else if (convertLinks && html && (html.match(url1) || html.match(url2)))
                {
                    html = html.replace(/&/g, '&amp;')
                        .replace(/</g, '&lt;')
                        .replace(/>/g, '&gt;')
                        .replace(url1, '$1<a href="' + protocol + '$2">$2</a>$3')
                        .replace(url2, '$1<a href="$2">$2</a>$5');

                    $(n).after(html).remove();
                }
            }
            else if (n.nodeType === 1 && !/^(a|button|textarea)$/i.test(n.tagName))
            {
                $.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertImageLinks, convertVideoLinks);
            }
        }
    };


})(jQuery);