public/assets/js/core/jwysiwyg/jwysiwyg.js

Summary

Maintainability
F
1 mo
Test Coverage
/**
 * WYSIWYG - jQuery plugin 0.97
 * (0.97.2 - From infinity)
 *
 * Copyright (c) 2008-2009 Juan M Martinez, 2010-2011 Akzhan Abdulin and all contributors
 * https://github.com/akzhan/jwysiwyg
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 */

/*jslint browser: true, forin: true */

(function ($) {
    "use strict";
    /* Wysiwyg namespace: private properties and methods */

    var console = window.console ? window.console : {
        log: $.noop,
        error: function (msg) {
            $.error(msg);
        }
    };
    var supportsProp = (('prop' in $.fn) && ('removeProp' in $.fn));

    function Wysiwyg() {
        // - the item is added by this.ui.appendControls and then appendItem
        // - click triggers this.triggerControl
        // cmd or[key] - designMode exec function name
        // tags - activates control for these tags (@see checkTargets)
        // css - activates control if one of css is applied
        this.controls = {
            bold: {
                groupIndex: 0,
                visible: true,
                tags: ["b", "strong"],
                css: {
                    fontWeight: "bold"
                },
                tooltip: "Bold",
                hotkey: {"ctrl": 1, "key": 66}
            },

            copy: {
                groupIndex: 8,
                visible: false,
                tooltip: "Copy"
            },

            createLink: {
                groupIndex: 6,
                visible: true,
                exec: function () {
                    var self = this;
                    if ($.wysiwyg.controls && $.wysiwyg.controls.link) {
                        $.wysiwyg.controls.link.init(this);
                    } else if ($.wysiwyg.autoload) {
                        $.wysiwyg.autoload.control("wysiwyg.link.js", function () {
                            self.controls.createLink.exec.apply(self);
                        });
                    } else {
                        console.error("$.wysiwyg.controls.link not defined. You need to include wysiwyg.link.js file");
                    }
                },
                tags: ["a"],
                tooltip: "Create link"
            },

            cut: {
                groupIndex: 8,
                visible: false,
                tooltip: "Cut"
            },

            decreaseFontSize: {
                groupIndex: 9,
                visible: false,
                tags: ["small"],
                tooltip: "Decrease font size",
                exec: function () {
                    this.decreaseFontSize();
                }
            },

            h1: {
                groupIndex: 7,
                visible: true,
                className: "h1",
                command: ($.browser.msie || $.browser.safari) ? "FormatBlock" : "heading",
                "arguments": ($.browser.msie || $.browser.safari) ? "<h1>" : "h1",
                tags: ["h1"],
                tooltip: "Header 1"
            },

            h2: {
                groupIndex: 7,
                visible: true,
                className: "h2",
                command: ($.browser.msie || $.browser.safari)    ? "FormatBlock" : "heading",
                "arguments": ($.browser.msie || $.browser.safari) ? "<h2>" : "h2",
                tags: ["h2"],
                tooltip: "Header 2"
            },

            h3: {
                groupIndex: 7,
                visible: true,
                className: "h3",
                command: ($.browser.msie || $.browser.safari) ? "FormatBlock" : "heading",
                "arguments": ($.browser.msie || $.browser.safari) ? "<h3>" : "h3",
                tags: ["h3"],
                tooltip: "Header 3"
            },

            highlight: {
                tooltip:     "Highlight",
                className:   "highlight",
                groupIndex:  1,
                visible:     false,
                css: {
                    backgroundColor: "rgb(255, 255, 102)"
                },
                exec: function () {
                    var command, node, selection, args;

                    if ($.browser.msie || $.browser.safari) {
                        command = "backcolor";
                    } else {
                        command = "hilitecolor";
                    }

                    if ($.browser.msie) {
                        node = this.getInternalRange().parentElement();
                    } else {
                        selection = this.getInternalSelection();
                        node = selection.extentNode || selection.focusNode;

                        while (node.style === undefined) {
                            node = node.parentNode;
                            if (node.tagName && node.tagName.toLowerCase() === "body") {
                                return;
                            }
                        }
                    }

                    if (node.style.backgroundColor === "rgb(255, 255, 102)" ||
                            node.style.backgroundColor === "#ffff66") {
                        args = "#ffffff";
                    } else {
                        args = "#ffff66";
                    }

                    this.editorDoc.execCommand(command, false, args);
                }
            },

            html: {
                groupIndex: 10,
                visible: false,
                exec: function () {
                    var elementHeight;

                    if (this.options.resizeOptions && $.fn.resizable) {
                        elementHeight = this.element.height();
                    }

                    if (this.viewHTML) { //textarea is shown
                        this.setContent(this.original.value);

                        $(this.original).hide();
                        this.editor.show();

                        if (this.options.resizeOptions && $.fn.resizable) {
                            // if element.height still the same after frame was shown
                            if (elementHeight === this.element.height()) {
                                this.element.height(elementHeight + this.editor.height());
                            }

                            this.element.resizable($.extend(true, {
                                alsoResize: this.editor
                            }, this.options.resizeOptions));
                        }
                        
                        this.ui.toolbar.find("li").each(function () {
                            var li = $(this);

                            if (li.hasClass("html")) {
                                li.removeClass("active");
                            } else {
                                li.removeClass('disabled');
                            }
                        });
                    } else { //wysiwyg is shown
                        this.saveContent();

                        $(this.original).css({
                            width:    this.element.outerWidth() - 6,
                            height: this.element.height() - this.ui.toolbar.height() - 6,
                            resize: "none"
                        }).show();
                        this.editor.hide();
                        
                        if (this.options.resizeOptions && $.fn.resizable) {
                            // if element.height still the same after frame was hidden
                            if (elementHeight === this.element.height()) {
                                this.element.height(this.ui.toolbar.height());
                            }

                            this.element.resizable("destroy");
                        }

                        this.ui.toolbar.find("li").each(function () {
                            var li = $(this);

                            if (li.hasClass("html")) {
                                li.addClass("active");
                            } else {
                                if (false === li.hasClass("fullscreen")) {
                                    li.removeClass("active").addClass('disabled');
                                }
                            }
                        });
                    }

                    this.viewHTML = !(this.viewHTML);
                },
                tooltip: "View source code"
            },

            increaseFontSize: {
                groupIndex: 9,
                visible: false,
                tags: ["big"],
                tooltip: "Increase font size",
                exec: function () {
                    this.increaseFontSize();
                }
            },

            indent: {
                groupIndex: 2,
                visible: true,
                tooltip: "Indent"
            },

            insertHorizontalRule: {
                groupIndex: 6,
                visible: true,
                tags: ["hr"],
                tooltip: "Insert Horizontal Rule"
            },

            insertImage: {
                groupIndex: 6,
                visible: true,
                exec: function () {
                    var self = this;

                    if ($.wysiwyg.controls && $.wysiwyg.controls.image) {
                        $.wysiwyg.controls.image.init(this);
                    } else if ($.wysiwyg.autoload) {
                        $.wysiwyg.autoload.control("wysiwyg.image.js", function () {
                            self.controls.insertImage.exec.apply(self);
                        });
                    } else {
                        console.error("$.wysiwyg.controls.image not defined. You need to include wysiwyg.image.js file");
                    }
                },
                tags: ["img"],
                tooltip: "Insert image"
            },

            insertOrderedList: {
                groupIndex: 5,
                visible: true,
                tags: ["ol"],
                tooltip: "Insert Ordered List"
            },

            insertTable: {
                groupIndex: 6,
                visible: true,
                exec: function () {
                    var self = this;

                    if ($.wysiwyg.controls && $.wysiwyg.controls.table) {
                        $.wysiwyg.controls.table(this);
                    } else if ($.wysiwyg.autoload) {
                        $.wysiwyg.autoload.control("wysiwyg.table.js", function () {
                            self.controls.insertTable.exec.apply(self);
                        });
                    } else {
                        console.error("$.wysiwyg.controls.table not defined. You need to include wysiwyg.table.js file");
                    }
                },
                tags: ["table"],
                tooltip: "Insert table"
            },

            insertUnorderedList: {
                groupIndex: 5,
                visible: true,
                tags: ["ul"],
                tooltip: "Insert Unordered List"
            },

            italic: {
                groupIndex: 0,
                visible: true,
                tags: ["i", "em"],
                css: {
                    fontStyle: "italic"
                },
                tooltip: "Italic",
                hotkey: {"ctrl": 1, "key": 73}
            },

            justifyCenter: {
                groupIndex: 1,
                visible: true,
                tags: ["center"],
                css: {
                    textAlign: "center"
                },
                tooltip: "Justify Center"
            },

            justifyFull: {
                groupIndex: 1,
                visible: true,
                css: {
                    textAlign: "justify"
                },
                tooltip: "Justify Full"
            },

            justifyLeft: {
                visible: true,
                groupIndex: 1,
                css: {
                    textAlign: "left"
                },
                tooltip: "Justify Left"
            },

            justifyRight: {
                groupIndex: 1,
                visible: true,
                css: {
                    textAlign: "right"
                },
                tooltip: "Justify Right"
            },

            ltr: {
                groupIndex: 10,
                visible: false,
                exec: function () {
                    var p = this.dom.getElement("p");

                    if (!p) {
                        return false;
                    }

                    $(p).attr("dir", "ltr");
                    return true;
                },
                tooltip : "Left to Right"
            },

            outdent: {
                groupIndex: 2,
                visible: true,
                tooltip: "Outdent"
            },

            paragraph: {
                groupIndex: 7,
                visible: false,
                className: "paragraph",
                command: "FormatBlock",
                "arguments": ($.browser.msie || $.browser.safari) ? "<p>" : "p",
                tags: ["p"],
                tooltip: "Paragraph"
            },

            paste: {
                groupIndex: 8,
                visible: false,
                tooltip: "Paste"
            },

            redo: {
                groupIndex: 4,
                visible: true,
                tooltip: "Redo"
            },

            removeFormat: {
                groupIndex: 10,
                visible: true,
                exec: function () {
                    this.removeFormat();
                },
                tooltip: "Remove formatting"
            },

            rtl: {
                groupIndex: 10,
                visible: false,
                exec: function () {
                    var p = this.dom.getElement("p");

                    if (!p) {
                        return false;
                    }

                    $(p).attr("dir", "rtl");
                    return true;
                },
                tooltip : "Right to Left"
            },

            strikeThrough: {
                groupIndex: 0,
                visible: true,
                tags: ["s", "strike"],
                css: {
                    textDecoration: "line-through"
                },
                tooltip: "Strike-through"
            },

            subscript: {
                groupIndex: 3,
                visible: true,
                tags: ["sub"],
                tooltip: "Subscript"
            },

            superscript: {
                groupIndex: 3,
                visible: true,
                tags: ["sup"],
                tooltip: "Superscript"
            },

            underline: {
                groupIndex: 0,
                visible: true,
                tags: ["u"],
                css: {
                    textDecoration: "underline"
                },
                tooltip: "Underline",
                hotkey: {"ctrl": 1, "key": 85}
            },

            undo: {
                groupIndex: 4,
                visible: true,
                tooltip: "Undo"
            },

            code: {
                visible : true,
                groupIndex: 6,
                tooltip: "Code snippet",
                exec: function () {
                    var range    = this.getInternalRange(),
                        common    = $(range.commonAncestorContainer),
                        $nodeName = range.commonAncestorContainer.nodeName.toLowerCase();
                    if (common.parent("code").length) {
                        common.unwrap();
                    } else {
                        if ($nodeName !== "body") {
                            common.wrap("<code/>");
                        }
                    }
                }
            },
            
            cssWrap: {
                visible : false,
                groupIndex: 6,
                tooltip: "CSS Wrapper",
                exec: function () { 
                    $.wysiwyg.controls.cssWrap.init(this);
                }
            }
            
        };

        this.defaults = {
html: '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" style="margin:0"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head><body style="margin:0;">INITIAL_CONTENT</body></html>',
            debug: false,
            controls: {},
            css: {},
            events: {},
            autoGrow: false,
            autoSave: true,
            brIE: true,                    // http://code.google.com/p/jwysiwyg/issues/detail?id=15
            formHeight: 270,
            formWidth: 440,
            iFrameClass: null,
            initialContent: "<p>Initial content</p>",
            maxHeight: 10000,            // see autoGrow
            maxLength: 0,
            messages: {
                nonSelection: "Select the text you wish to link"
            },
            toolbarHtml: '<ul role="menu" class="toolbar"></ul>',
            removeHeadings: false,
            replaceDivWithP: false,
            resizeOptions: false,
            rmUnusedControls: false,    // https://github.com/akzhan/jwysiwyg/issues/52
            rmUnwantedBr: true,            // http://code.google.com/p/jwysiwyg/issues/detail?id=11
            tableFiller: "Lorem ipsum",
            initialMinHeight: null,

            controlImage: {
                forceRelativeUrls: false
            },

            controlLink: {
                forceRelativeUrls: false
            },

            plugins: { // placeholder for plugins settings
                autoload: false,
                i18n: false,
                rmFormat: {
                    rmMsWordMarkup: false
                }
            },
            
            dialog : "default"
        };

        //these properties are set from control hashes
        this.availableControlProperties = [
            "arguments",
            "callback",
            "className",
            "command",
            "css",
            "custom",
            "exec",
            "groupIndex",
            "hotkey",
            "icon",
            "tags",
            "tooltip",
            "visible"
        ];

        this.editor            = null;  //jquery iframe holder
        this.editorDoc        = null;
        this.element        = null;
        this.options        = {};
        this.original        = null;
        this.savedRange        = null;
        this.timers            = [];
        this.validKeyCodes    = [8, 9, 13, 16, 17, 18, 19, 20, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46];

        this.isDestroyed    = false;

        this.dom        = { // DOM related properties and methods
            ie:        {
                parent: null // link to dom
            },
            w3c:    {
                parent: null // link to dom
            }
        };
        this.dom.parent        = this;
        this.dom.ie.parent    = this.dom;
        this.dom.w3c.parent    = this.dom;

        this.ui            = {};    // UI related properties and methods
        this.ui.self    = this;
        this.ui.toolbar    = null;
        this.ui.initialHeight = null; // ui.grow

        this.dom.getAncestor = function (element, filterTagName) {
            filterTagName = filterTagName.toLowerCase();
            
            while (element && typeof element.tagName != "undefined" && "body" !== element.tagName.toLowerCase()) {
                if (filterTagName === element.tagName.toLowerCase()) {
                    return element;
                }

                element = element.parentNode;
            }
            if(!element.tagName && (element.previousSibling || element.nextSibling)) {
                if(element.previousSibling) {
                    if(element.previousSibling.tagName.toLowerCase() == filterTagName) {
                        return element.previousSibling;
                    }
                }    
                if(element.nextSibling) {
                    if(element.nextSibling.tagName.toLowerCase() == filterTagName) {
                        return element.nextSibling;
                    }
                }    
            }

            return null;
        };

        this.dom.getElement = function (filterTagName) {
            var dom = this;
            
            filterTagName = filterTagName.toLowerCase();            

            if (window.getSelection) {
                return dom.w3c.getElement(filterTagName);
            } else {
                return dom.ie.getElement(filterTagName);
            }
        };

        this.dom.ie.getElement = function (filterTagName) {
            var dom            = this.parent,
                selection    = dom.parent.getInternalSelection(),
                range        = selection.createRange(),
                element;

            if ("Control" === selection.type) {
                // control selection
                if (1 === range.length) {
                    element = range.item(0);
                } else {
                    // multiple control selection
                    return null;
                }
            } else {
                element = range.parentElement();
            }

            return dom.getAncestor(element, filterTagName);
        };

        this.dom.w3c.getElement = function (filterTagName) {
            var dom        = this.parent,
                range    = dom.parent.getInternalRange(),
                element;
                
            if (!range) {
                return null;
            }

            element    = range.commonAncestorContainer;

            if (3 === element.nodeType) {
                element = element.parentNode;
            }

            // if startContainer not Text, Comment, or CDATASection element then
            // startOffset is the number of child nodes between the start of the
            // startContainer and the boundary point of the Range
            if (element === range.startContainer) {
                element = element.childNodes[range.startOffset];
            }
            
            if(!element.tagName && (element.previousSibiling || element.nextSibling)) {
                if(element.previousSibiling) {
                    if(element.previousSibiling.tagName.toLowerCase() == filterTagName) {
                        return element.previousSibiling;
                    }
                }    
                if(element.nextSibling) {
                    if(element.nextSibling.tagName.toLowerCase() == filterTagName) {
                        return element.nextSibling;
                    }
                }    
            }

            return dom.getAncestor(element, filterTagName);
        };

        this.ui.addHoverClass = function () {
            $(this).addClass("wysiwyg-button-hover");
        };

        this.ui.appendControls = function () {
            var ui = this,
                self = this.self,
                controls = self.parseControls(),
                hasVisibleControls    = true, // to prevent separator before first item
                groups = [],
                controlsByGroup = {},
                i,
                currentGroupIndex, // jslint wants all vars at top of function
                iterateGroup = function (controlName, control) { //called for every group when adding
                    if (control.groupIndex && currentGroupIndex !== control.groupIndex) {
                        currentGroupIndex = control.groupIndex;
                        hasVisibleControls = false;
                    }

                    if (!control.visible) {
                        return;
                    }

                    if (!hasVisibleControls) {
                        ui.appendItemSeparator();
                        hasVisibleControls = true;
                    }

                    if (control.custom) {
                        ui.appendItemCustom(controlName, control);
                    } else {
                        ui.appendItem(controlName, control);
                    }
                };

            $.each(controls, function (name, c) { //sort by groupIndex
                var index = "empty";

                if (undefined !== c.groupIndex) {
                    if ("" === c.groupIndex) {
                        index = "empty";
                    } else {
                        index = c.groupIndex;
                    }
                }

                if (undefined === controlsByGroup[index]) {
                    groups.push(index);
                    controlsByGroup[index] = {};
                }
                controlsByGroup[index][name] = c;
            });

            groups.sort(function (a, b) { //just sort group indexes by
                if ("number" === typeof (a) && typeof (a) === typeof (b)) {
                    return (a - b);
                } else {
                    a = a.toString();
                    b = b.toString();

                    if (a > b) {
                        return 1;
                    }

                    if (a === b) {
                        return 0;
                    }

                    return -1;
                }
            });

            if (0 < groups.length) {
                // set to first index in groups to proper placement of separator
                currentGroupIndex = groups[0];
            }

            for (i = 0; i < groups.length; i += 1) {
                $.each(controlsByGroup[groups[i]], iterateGroup);
            }
        };

        this.ui.appendItem = function (name, control) {
            var self = this.self,
                className = control.className || control.command || name || "empty",
                tooltip = control.tooltip || control.command || name || "";

            return $('<li role="menuitem" unselectable="on">' + (className) + "</li>")
                .addClass(className)
                .attr("title", tooltip)
                .hover(this.addHoverClass, this.removeHoverClass)
                .click(function (event) {
                    if ($(this).hasClass("disabled")) {
                        return false;
                    }

                    self.triggerControl.apply(self, [name, control]);

                    /**
                    * @link https://github.com/akzhan/jwysiwyg/issues/219
                    */
                    var $target = $(event.target);
                    for (var controlName in self.controls) {
                        if ($target.hasClass(controlName)) {
                            self.ui.toolbar.find("." + controlName).toggleClass("active");
                            self.editorDoc.rememberCommand = true;
                            break;
                        }
                    }
                    
                    this.blur();
                    self.ui.returnRange();
                    self.ui.focus();
                    return true;
                })
                .appendTo(self.ui.toolbar);
        };

        this.ui.appendItemCustom = function (name, control) {
            var self = this.self,
                tooltip = control.tooltip || control.command || name || "";

            if (control.callback) {
                $(window).bind("trigger-" + name + ".wysiwyg", control.callback);
            }

            return $('<li role="menuitem" unselectable="on" style="background: url(\'' + control.icon + '\') no-repeat;"></li>')
                .addClass("custom-command-" + name)
                .addClass("wysiwyg-custom-command")
                .addClass(name)
                .attr("title", tooltip)
                .hover(this.addHoverClass, this.removeHoverClass)
                .click(function () {
                    if ($(this).hasClass("disabled")) {
                        return false;
                    }

                    self.triggerControl.apply(self, [name, control]);

                    this.blur();
                    self.ui.returnRange();
                    self.ui.focus();

                    self.triggerControlCallback(name);
                    return true;
                })
                .appendTo(self.ui.toolbar);
        };

        this.ui.appendItemSeparator = function () {
            var self = this.self;
            return $('<li role="separator" class="separator"></li>').appendTo(self.ui.toolbar);
        };

        this.autoSaveFunction = function () {
            this.saveContent();
        };

        //called after click in wysiwyg "textarea"
        this.ui.checkTargets = function (element) {
            var self = this.self;

            //activate controls
            $.each(self.options.controls, function (name, control) {
                var className = control.className || control.command || name || "empty",
                    tags,
                    elm,
                    css,
                    el,
                    checkActiveStatus = function (cssProperty, cssValue) {
                        var handler;

                        if ("function" === typeof (cssValue)) {
                            handler = cssValue;
                            if (handler(el.css(cssProperty).toString().toLowerCase(), self)) {
                                self.ui.toolbar.find("." + className).addClass("active");
                            }
                        } else {
                            if (el.css(cssProperty).toString().toLowerCase() === cssValue) {
                                self.ui.toolbar.find("." + className).addClass("active");
                            }
                        }
                    };

                if ("fullscreen" !== className) {
                    self.ui.toolbar.find("." + className).removeClass("active");
                }

                //activate by allowed tags
                if (control.tags || (control.options && control.options.tags)) {
                    tags = control.tags || (control.options && control.options.tags);

                    elm = element;
                    while (elm) {
                        if (elm.nodeType !== 1) {
                            break;
                        }

                        if ($.inArray(elm.tagName.toLowerCase(), tags) !== -1) {
                            self.ui.toolbar.find("." + className).addClass("active");
                        }

                        elm = elm.parentNode;
                    }
                }

                //activate by supposed css
                if (control.css || (control.options && control.options.css)) {
                    css = control.css || (control.options && control.options.css);
                    el = $(element);

                    while (el) {
                        if (el[0].nodeType !== 1) {
                            break;
                        }
                        $.each(css, checkActiveStatus);

                        el = el.parent();
                    }
                }
            });
        };

        this.ui.designMode = function () {
            var attempts = 3,
                self = this.self,
                runner;
                runner = function (attempts) {
                    if ("on" === self.editorDoc.designMode) {
                        if (self.timers.designMode) {
                            window.clearTimeout(self.timers.designMode);
                        }

                        // IE needs to reget the document element (this.editorDoc) after designMode was set
                        if (self.innerDocument() !== self.editorDoc) {
                            self.ui.initFrame();
                        }

                        return;
                    }

                    try {
                        self.editorDoc.designMode = "on";
                    } catch (e) {
                    }

                    attempts -= 1;
                    if (attempts > 0) {
                        self.timers.designMode = window.setTimeout(function () { runner(attempts); }, 100);
                    }
                };

            runner(attempts);
        };

        this.destroy = function () {
            this.isDestroyed = true;

            var i, $form = this.element.closest("form");

            for (i = 0; i < this.timers.length; i += 1) {
                window.clearTimeout(this.timers[i]);
            }

            // Remove bindings
            $form.unbind(".wysiwyg");
            this.element.remove();
            $.removeData(this.original, "wysiwyg");
            $(this.original).show();
            return this;
        };

        this.getRangeText = function () {
            var r = this.getInternalRange();

            if (r.toString) {
                r = r.toString();
            } else if (r.text) {    // IE
                r = r.text;
            }

            return r;
        };
        //not used?
        this.execute = function (command, arg) {
            if (typeof (arg) === "undefined") {
                arg = null;
            }
            this.editorDoc.execCommand(command, false, arg);
        };

        this.extendOptions = function (options) {
            var controls = {};

            /**
             * If the user set custom controls, we catch it, and merge with the
             * defaults controls later.
             */
            if ("object" === typeof options.controls) {
                controls = options.controls;
                delete options.controls;
            }

            options = $.extend(true, {}, this.defaults, options);
            options.controls = $.extend(true, {}, controls, this.controls, controls);

            if (options.rmUnusedControls) {
                $.each(options.controls, function (controlName) {
                    if (!controls[controlName]) {
                        delete options.controls[controlName];
                    }
                });
            }

            return options;
        };

        this.ui.focus = function () {
            var self = this.self;

            self.editor.get(0).contentWindow.focus();
            return self;
        };

        this.ui.returnRange = function () {
            var self = this.self, sel;

            if (self.savedRange !== null) {
                if (window.getSelection) { //non IE and there is already a selection
                    sel = window.getSelection();
                    if (sel.rangeCount > 0) {
                        sel.removeAllRanges();
                    }
                    try {
                        sel.addRange(self.savedRange);
                    } catch (e) {
                        console.error(e);
                    }
                } else if (window.document.createRange) { // non IE and no selection
                    window.getSelection().addRange(self.savedRange);
                } else if (window.document.selection) { //IE
                    self.savedRange.select();
                }

                self.savedRange = null;
            }
        };

        this.increaseFontSize = function () {
            if ($.browser.mozilla || $.browser.opera) {
                this.editorDoc.execCommand("increaseFontSize", false, null);
            } else if ($.browser.safari) {                
                var Range = this.getInternalRange(),
                    Selection = this.getInternalSelection(),
                    newNode = this.editorDoc.createElement("big");

                // If cursor placed on text node
                if (true === Range.collapsed && 3 === Range.commonAncestorContainer.nodeType) {
                    var text = Range.commonAncestorContainer.nodeValue.toString(),
                        start = text.lastIndexOf(" ", Range.startOffset) + 1,
                        end = (-1 === text.indexOf(" ", Range.startOffset)) ? text : text.indexOf(" ", Range.startOffset);

                    Range.setStart(Range.commonAncestorContainer, start);
                    Range.setEnd(Range.commonAncestorContainer, end);

                    Range.surroundContents(newNode);
                    Selection.addRange(Range);
                } else {
                    Range.surroundContents(newNode);
                    Selection.removeAllRanges();
                    Selection.addRange(Range);
                }
            } else {
                console.error("Internet Explorer?");
            }
        };

        this.decreaseFontSize = function () {
            if ($.browser.mozilla || $.browser.opera) {
                this.editorDoc.execCommand("decreaseFontSize", false, null);
            } else if ($.browser.safari) {
                var Range = this.getInternalRange(),
                    Selection = this.getInternalSelection(),
                    newNode = this.editorDoc.createElement("small");

                // If cursor placed on text node
                if (true === Range.collapsed && 3 === Range.commonAncestorContainer.nodeType) {
                    var text = Range.commonAncestorContainer.nodeValue.toString(),
                        start = text.lastIndexOf(" ", Range.startOffset) + 1,
                        end = (-1 === text.indexOf(" ", Range.startOffset)) ? text : text.indexOf(" ", Range.startOffset);
    
                    Range.setStart(Range.commonAncestorContainer, start);
                    Range.setEnd(Range.commonAncestorContainer, end);
    
                    Range.surroundContents(newNode);
                    Selection.addRange(Range);
                } else {
                    Range.surroundContents(newNode);
                    Selection.removeAllRanges();
                    Selection.addRange(Range);
                }
            } else {
                console.error("Internet Explorer?");
            }
        };

        this.getContent = function () {
            if (this.viewHTML) {
                this.setContent(this.original.value);
            }
            return this.events.filter('getContent', this.editorDoc.body.innerHTML);
        };
        
        /**
         * A jWysiwyg specific event system.
         *
         * Example:
         * 
         * $("#editor").getWysiwyg().events.bind("getContent", function (orig) {
         *     return "<div id='content'>"+orgi+"</div>";
         * });
         * 
         * This makes it so that when ever getContent is called, it is wrapped in a div#content.
         */
        this.events = {
            _events : {},
            
            /**
             * Similar to jQuery's bind, but for jWysiwyg only.
             */
            bind : function (eventName, callback) {
                if (typeof (this._events.eventName) !== "object") {
                    this._events[eventName] = [];
                }
                this._events[eventName].push(callback);
            },
            
            /**
             * Similar to jQuery's trigger, but for jWysiwyg only.
             */
            trigger : function (eventName, args) {
                if (typeof (this._events.eventName) === "object") {
                    var editor = this.editor;
                    $.each(this._events[eventName], function (k, v) {
                        if (typeof (v) === "function") {
                            v.apply(editor, args);
                        }
                    });
                }
            },
            
            /**
             * This "filters" `originalText` by passing it as the first argument to every callback
             * with the name `eventName` and taking the return value and passing it to the next function.
             *
             * This function returns the result after all the callbacks have been applied to `originalText`.
             */
            filter : function (eventName, originalText) {
                if (typeof (this._events[eventName]) === "object") {
                    var editor = this.editor,
                        args = Array.prototype.slice.call(arguments, 1);

                    $.each(this._events[eventName], function (k, v) {
                        if (typeof (v) === "function") {
                            originalText = v.apply(editor, args);
                        }
                    });
                }
                return originalText;
            }
        };

        this.getElementByAttributeValue = function (tagName, attributeName, attributeValue) {
            var i, value, elements = this.editorDoc.getElementsByTagName(tagName);

            for (i = 0; i < elements.length; i += 1) {
                value = elements[i].getAttribute(attributeName);

                if ($.browser.msie) {
                    /** IE add full path, so I check by the last chars. */
                    value = value.substr(value.length - attributeValue.length);
                }

                if (value === attributeValue) {
                    return elements[i];
                }
            }

            return false;
        };

        this.getInternalRange = function () {
            var selection = this.getInternalSelection();

            if (!selection) {
                return null;
            }

            if (selection.rangeCount && selection.rangeCount > 0) { // w3c
                return selection.getRangeAt(0);
            } else if (selection.createRange) { // ie
                return selection.createRange();
            }

            return null;
        };

        this.getInternalSelection = function () {
            // firefox: document.getSelection is deprecated
            if (this.editor.get(0).contentWindow) {
                if (this.editor.get(0).contentWindow.getSelection) {
                    return this.editor.get(0).contentWindow.getSelection();
                }
                if (this.editor.get(0).contentWindow.selection) {
                    return this.editor.get(0).contentWindow.selection;
                }
            }
            if (this.editorDoc.getSelection) {
                return this.editorDoc.getSelection();
            }
            if (this.editorDoc.selection) {
                return this.editorDoc.selection;
            }

            return null;
        };

        this.getRange = function () {
            var selection = this.getSelection();

            if (!selection) {
                return null;
            }

            if (selection.rangeCount && selection.rangeCount > 0) { // w3c
                selection.getRangeAt(0);
            } else if (selection.createRange) { // ie
                return selection.createRange();
            }

            return null;
        };

        this.getSelection = function () {
            return (window.getSelection) ? window.getSelection() : window.document.selection;
        };

        // :TODO: you can type long string and letters will be hidden because of overflow
        this.ui.grow = function () {
            var self = this.self,
                innerBody = $(self.editorDoc.body),
                innerHeight = $.browser.msie ? innerBody[0].scrollHeight : innerBody.height() + 2 + 20, // 2 - borders, 20 - to prevent content jumping on grow
                minHeight = self.ui.initialHeight,
                height = Math.max(innerHeight, minHeight);

            height = Math.min(height, self.options.maxHeight);

            self.editor.attr("scrolling", height < self.options.maxHeight ? "no" : "auto"); // hide scrollbar firefox
            innerBody.css("overflow", height < self.options.maxHeight ? "hidden" : ""); // hide scrollbar chrome

            self.editor.get(0).height = height;

            return self;
        };

        this.init = function (element, options) {
            var self = this,
                $form = $(element).closest("form"),
                newX = (element.width || element.clientWidth || 0),
                newY = (element.height || element.clientHeight || 0)
                ;

            this.options    = this.extendOptions(options);
            this.original    = element;
            this.ui.toolbar    = $(this.options.toolbarHtml);

            if ($.browser.msie && parseInt($.browser.version, 10) < 8) {
                this.options.autoGrow = false;
            }

            if (newX === 0 && element.cols) {
                newX = (element.cols * 8) + 21;
            }
            if (newY === 0 && element.rows) {
                newY = (element.rows * 16) + 16;
            }

            this.editor = $(window.location.protocol === "https:" ? '<iframe src="javascript:false;"></iframe>' : "<iframe></iframe>").attr("frameborder", "0");

            if (this.options.iFrameClass) {
                this.editor.addClass(this.options.iFrameClass);
            } else {
                this.editor.css({
                    minHeight: (newY - 6).toString() + "px",
                    // fix for issue 12 ( http://github.com/akzhan/jwysiwyg/issues/issue/12 )
                    width: (newX > 50) ? (newX - 8).toString() + "px" : ""
                });
                if ($.browser.msie && parseInt($.browser.version, 10) < 7) {
                    this.editor.css("height", newY.toString() + "px");
                }
            }
            /** 
             * Automagically add id to iframe if textarea has its own when possible 
             * ( http://github.com/akzhan/jwysiwyg/issues/245 )
             */
            if (element.id) {
                var proposedId = element.id + '-wysiwyg-iframe';
                if (! document.getElementById(proposedId)) {
                    this.editor.attr('id', proposedId);
                }
            }

            /**
             * http://code.google.com/p/jwysiwyg/issues/detail?id=96
             */
            this.editor.attr("tabindex", $(element).attr("tabindex"));

            this.element = $("<div/>").addClass("wysiwyg");

            if (!this.options.iFrameClass) {
                this.element.css({
                    width: (newX > 0) ? newX.toString() + "px" : "100%"
                });
            }

            $(element).hide().before(this.element);

            this.viewHTML = false;

            /**
             * @link http://code.google.com/p/jwysiwyg/issues/detail?id=52
             */
            this.initialContent = $(element).val();
            this.ui.initFrame();

            if (this.options.resizeOptions && $.fn.resizable) {
                this.element.resizable($.extend(true, {
                    alsoResize: this.editor
                }, this.options.resizeOptions));
            }

            if (this.options.autoSave) {
                $form.bind("submit.wysiwyg", function () { self.autoSaveFunction(); });
            }

            $form.bind("reset.wysiwyg", function () { self.resetFunction(); });
        };

        this.ui.initFrame = function () {
            var self = this.self,
                stylesheet,
                growHandler,
                saveHandler;

            self.ui.appendControls();
            self.element.append(self.ui.toolbar)
                .append($("<div><!-- --></div>")
                    .css({
                        clear: "both"
                    }))
                .append(self.editor);

            self.editorDoc = self.innerDocument();

            if (self.isDestroyed) {
                return null;
            }

            self.ui.designMode();
            self.editorDoc.open();
            self.editorDoc.write(
                self.options.html
                    /**
                     * @link http://code.google.com/p/jwysiwyg/issues/detail?id=144
                     */
                    .replace(/INITIAL_CONTENT/, function () { return self.wrapInitialContent(); })
            );
            self.editorDoc.close();

            $.wysiwyg.plugin.bind(self);

            $(self.editorDoc).trigger("initFrame.wysiwyg");

            $(self.editorDoc).bind("click.wysiwyg", function (event) {
                self.ui.checkTargets(event.target ? event.target : event.srcElement);
            });

            /**
             * @link https://github.com/akzhan/jwysiwyg/issues/251
             */
            setInterval(function () {
                var offset = null;

                try {
                    var range = self.getInternalRange();
                    if (range) {
                        offset = {
                            range: range,
                            parent: $.browser.msie ? range.parentElement() : range.endContainer.parentNode,
                            width: ($.browser.msie ? range.boundingWidth : range.startOffset - range.endOffset) || 0
                        };
                    }
                }
                catch (e) { console.error(e); }

                if (offset && offset.width == 0 && !self.editorDoc.rememberCommand) {
                    self.ui.checkTargets(offset.parent);
                }
            }, 400);
            
            /**
             * @link http://code.google.com/p/jwysiwyg/issues/detail?id=20
             */
            $(self.original).focus(function () {
                if ($(this).filter(":visible").length === 0) {
                    return;
                }
                self.ui.focus();
            });

            $(self.editorDoc).keydown(function (event) {
                var emptyContentRegex;
                if (event.keyCode === 8) { // backspace
                    emptyContentRegex = /^<([\w]+)[^>]*>(<br\/?>)?<\/\1>$/;
                    if (emptyContentRegex.test(self.getContent())) { // if content is empty
                        event.stopPropagation(); // prevent remove single empty tag
                        return false;
                    }
                }
                
                self.editorDoc.rememberCommand = false;
                return true;
            });

            if (!$.browser.msie) {
                $(self.editorDoc).keydown(function (event) {
                    var controlName;

                    /* Meta for Macs. tom@punkave.com */
                    if (event.ctrlKey || event.metaKey) {
                        for (controlName in self.controls) {
                            if (self.controls[controlName].hotkey && self.controls[controlName].hotkey.ctrl) {
                                if (event.keyCode === self.controls[controlName].hotkey.key) {
                                    self.triggerControl.apply(self, [controlName, self.controls[controlName]]);

                                    return false;
                                }
                            }
                        }
                    }

                    return true;
                });
            } else if (self.options.brIE) {
                $(self.editorDoc).keydown(function (event) {
                    if (event.keyCode === 13) {
                        var rng = self.getRange();
                        rng.pasteHTML("<br/>");
                        rng.collapse(false);
                        rng.select();

                        return false;
                    }

                    return true;
                });
            }

            if (self.options.plugins.rmFormat.rmMsWordMarkup) {
                $(self.editorDoc).bind("keyup.wysiwyg", function (event) {
                    if (event.ctrlKey || event.metaKey) {
                        // CTRL + V (paste)
                        if (86 === event.keyCode) {
                            if ($.wysiwyg.rmFormat) {
                                if ("object" === typeof (self.options.plugins.rmFormat.rmMsWordMarkup)) {
                                    $.wysiwyg.rmFormat.run(self, {rules: { msWordMarkup: self.options.plugins.rmFormat.rmMsWordMarkup }});
                                } else {
                                    $.wysiwyg.rmFormat.run(self, {rules: { msWordMarkup: { enabled: true }}});
                                }
                            }
                        }
                    }
                });
            }

            if (self.options.autoSave) {
                $(self.editorDoc).keydown(function () { self.autoSaveFunction(); })
                    .keyup(function () { self.autoSaveFunction(); })
                    .mousedown(function () { self.autoSaveFunction(); })
                    .bind($.support.noCloneEvent ? "input.wysiwyg" : "paste.wysiwyg", function () { self.autoSaveFunction(); });
            }

            if (self.options.autoGrow) {
                if (self.options.initialMinHeight !== null) {
                    self.ui.initialHeight = self.options.initialMinHeight;
                } else {
                    self.ui.initialHeight = $(self.editorDoc).height();
                }
                $(self.editorDoc.body).css("border", "1px solid white"); // cancel margin collapsing

                growHandler = function () {
                    self.ui.grow();
                };

                $(self.editorDoc).keyup(growHandler);
                $(self.editorDoc).bind("editorRefresh.wysiwyg", growHandler);

                // fix when content height > textarea height
                self.ui.grow();
            }

            if (self.options.css) {
                if (String === self.options.css.constructor) {
                    if ($.browser.msie) {
                        stylesheet = self.editorDoc.createStyleSheet(self.options.css);
                        $(stylesheet).attr({
                            "media":    "all"
                        });
                    } else {
                        stylesheet = $("<link/>").attr({
                            "href":        self.options.css,
                            "media":    "all",
                            "rel":        "stylesheet",
                            "type":        "text/css"
                        });

                        $(self.editorDoc).find("head").append(stylesheet);
                    }
                } else {
                    self.timers.initFrame_Css = window.setTimeout(function () {
                        $(self.editorDoc.body).css(self.options.css);
                    }, 0);
                }
            }

            if (self.initialContent.length === 0) {
                if ("function" === typeof (self.options.initialContent)) {
                    self.setContent(self.options.initialContent());
                } else {
                    self.setContent(self.options.initialContent);
                }
            }

            if (self.options.maxLength > 0) {
                $(self.editorDoc).keydown(function (event) {
                    if ($(self.editorDoc).text().length >= self.options.maxLength && $.inArray(event.which, self.validKeyCodes) === -1) {
                        event.preventDefault();
                    }
                });
            }
            
            // Support event callbacks
            $.each(self.options.events, function (key, handler) {
                $(self.editorDoc).bind(key + ".wysiwyg", function (event) {
                    // Trigger event handler, providing the event and api to 
                    // support additional functionality.
                    handler.apply(self.editorDoc, [event, self]);
                });
            });

            // restores selection properly on focus
            if ($.browser.msie) {
                // Event chain: beforedeactivate => focusout => blur.
                // Focusout & blur fired too late to handle internalRange() in dialogs.
                // When clicked on input boxes both got range = null
                $(self.editorDoc).bind("beforedeactivate.wysiwyg", function () {
                    self.savedRange = self.getInternalRange();
                });
            } else {
                $(self.editorDoc).bind("blur.wysiwyg", function () {
                    self.savedRange = self.getInternalRange();
                });
            }

            $(self.editorDoc.body).addClass("wysiwyg");
            if (self.options.events && self.options.events.save) {
                saveHandler = self.options.events.save;

                $(self.editorDoc).bind("keyup.wysiwyg", saveHandler);
                $(self.editorDoc).bind("change.wysiwyg", saveHandler);

                if ($.support.noCloneEvent) {
                    $(self.editorDoc).bind("input.wysiwyg", saveHandler);
                } else {
                    $(self.editorDoc).bind("paste.wysiwyg", saveHandler);
                    $(self.editorDoc).bind("cut.wysiwyg", saveHandler);
                }
            }
            
            /**
             * XHTML5 {@link https://github.com/akzhan/jwysiwyg/issues/152}
             */
            if (self.options.xhtml5 && self.options.unicode) {
                var replacements = {ne:8800,le:8804,para:182,xi:958,darr:8595,nu:957,oacute:243,Uacute:218,omega:969,prime:8242,pound:163,igrave:236,thorn:254,forall:8704,emsp:8195,lowast:8727,brvbar:166,alefsym:8501,nbsp:160,delta:948,clubs:9827,lArr:8656,Omega:937,Auml:196,cedil:184,and:8743,plusmn:177,ge:8805,raquo:187,uml:168,equiv:8801,laquo:171,rdquo:8221,Epsilon:917,divide:247,fnof:402,chi:967,Dagger:8225,iacute:237,rceil:8969,sigma:963,Oslash:216,acute:180,frac34:190,lrm:8206,upsih:978,Scaron:352,part:8706,exist:8707,nabla:8711,image:8465,prop:8733,zwj:8205,omicron:959,aacute:225,Yuml:376,Yacute:221,weierp:8472,rsquo:8217,otimes:8855,kappa:954,thetasym:977,harr:8596,Ouml:214,Iota:921,ograve:242,sdot:8901,copy:169,oplus:8853,acirc:226,sup:8835,zeta:950,Iacute:205,Oacute:211,crarr:8629,Nu:925,bdquo:8222,lsquo:8216,apos:39,Beta:914,eacute:233,egrave:232,lceil:8968,Kappa:922,piv:982,Ccedil:199,ldquo:8220,Xi:926,cent:162,uarr:8593,hellip:8230,Aacute:193,ensp:8194,sect:167,Ugrave:217,aelig:230,ordf:170,curren:164,sbquo:8218,macr:175,Phi:934,Eta:919,rho:961,Omicron:927,sup2:178,euro:8364,aring:229,Theta:920,mdash:8212,uuml:252,otilde:245,eta:951,uacute:250,rArr:8658,nsub:8836,agrave:224,notin:8713,ndash:8211,Psi:936,Ocirc:212,sube:8838,szlig:223,micro:181,not:172,sup1:185,middot:183,iota:953,ecirc:234,lsaquo:8249,thinsp:8201,sum:8721,ntilde:241,scaron:353,cap:8745,atilde:227,lang:10216,__replacement:65533,isin:8712,gamma:947,Euml:203,ang:8736,upsilon:965,Ntilde:209,hearts:9829,Alpha:913,Tau:932,spades:9824,dagger:8224,THORN:222,"int":8747,lambda:955,Eacute:201,Uuml:220,infin:8734,rlm:8207,Aring:197,ugrave:249,Egrave:200,Acirc:194,rsaquo:8250,ETH:208,oslash:248,alpha:945,Ograve:210,Prime:8243,mu:956,ni:8715,real:8476,bull:8226,beta:946,icirc:238,eth:240,prod:8719,larr:8592,ordm:186,perp:8869,Gamma:915,reg:174,ucirc:251,Pi:928,psi:968,tilde:732,asymp:8776,zwnj:8204,Agrave:192,deg:176,AElig:198,times:215,Delta:916,sim:8764,Otilde:213,Mu:924,uArr:8657,circ:710,theta:952,Rho:929,sup3:179,diams:9830,tau:964,Chi:935,frac14:188,oelig:339,shy:173,or:8744,dArr:8659,phi:966,iuml:239,Lambda:923,rfloor:8971,iexcl:161,cong:8773,ccedil:231,Icirc:206,frac12:189,loz:9674,rarr:8594,cup:8746,radic:8730,frasl:8260,euml:235,OElig:338,hArr:8660,Atilde:195,Upsilon:933,there4:8756,ouml:246,oline:8254,Ecirc:202,yacute:253,auml:228,permil:8240,sigmaf:962,iquest:191,empty:8709,pi:960,Ucirc:219,supe:8839,Igrave:204,yen:165,rang:10217,trade:8482,lfloor:8970,minus:8722,Zeta:918,sub:8834,epsilon:949,yuml:255,Sigma:931,Iuml:207,ocirc:244};
                self.events.bind("getContent", function (text) {
                    return text.replace(/&(?:amp;)?(?!amp|lt|gt|quot)([a-z][a-z0-9]*);/gi, function (str, p1) {
                        if (!replacements[p1]) {
                            p1 = p1.toLowerCase();
                            if (!replacements[p1]) {
                                p1 = "__replacement";
                            }
                        }
                        
                        var num = replacements[p1];
                        /* Numeric return if ever wanted: return replacements[p1] ? "&#"+num+";" : ""; */
                        return String.fromCharCode(num);
                    });
                });
            }
            $(self.original).trigger('ready.jwysiwyg', [self.editorDoc, self]);
        };

        this.innerDocument = function () {
            var element = this.editor.get(0);

            if (element.nodeName.toLowerCase() === "iframe") {
                if (element.contentDocument) {                // Gecko
                    return element.contentDocument;
                } else if (element.contentWindow) {            // IE
                    return element.contentWindow.document;
                }

                if (this.isDestroyed) {
                    return null;
                }

                console.error("Unexpected error in innerDocument");

                /*
                 return ( $.browser.msie )
                 ? document.frames[element.id].document
                 : element.contentWindow.document // contentDocument;
                 */
            }

            return element;
        };

        this.insertHtml = function (szHTML) {
            var img, range;

            if (!szHTML || szHTML.length === 0) {
                return this;
            }

            if ($.browser.msie) {
                this.ui.focus();
                this.editorDoc.execCommand("insertImage", false, "#jwysiwyg#");
                img = this.getElementByAttributeValue("img", "src", "#jwysiwyg#");
                if (img) {
                    $(img).replaceWith(szHTML);
                }
            } else {
                if ($.browser.mozilla) { // @link https://github.com/akzhan/jwysiwyg/issues/50
                    if (1 === $(szHTML).length) {
                        range = this.getInternalRange();
                        range.deleteContents();
                        range.insertNode($(szHTML).get(0));
                    } else {
                        this.editorDoc.execCommand("insertHTML", false, szHTML);
                    }
                } else {
                    if (!this.editorDoc.execCommand("insertHTML", false, szHTML)) {
                        this.editor.focus();
                        /* :TODO: place caret at the end
                        if (window.getSelection) {
                        } else {
                        }
                        this.editor.focus();
                        */
                        this.editorDoc.execCommand("insertHTML", false, szHTML);
                    }
                }
            }

            this.saveContent();
            
            return this;
        };

        //check allowed properties
        this.parseControls = function () {
            var self = this;

            $.each(this.options.controls, function (controlName, control) {
                $.each(control, function (propertyName) {
                    if (-1 === $.inArray(propertyName, self.availableControlProperties)) {
                        throw controlName + '["' + propertyName + '"]: property "' + propertyName + '" not exists in Wysiwyg.availableControlProperties';
                    }
                });
            });

            if (this.options.parseControls) { //user callback
                return this.options.parseControls.call(this);
            }

            return this.options.controls;
        };

        this.removeFormat = function () {
            if ($.browser.msie) {
                this.ui.focus();
            }

            if (this.options.removeHeadings) {
                this.editorDoc.execCommand("formatBlock", false, "<p>"); // remove headings
            }

            this.editorDoc.execCommand("removeFormat", false, null);
            this.editorDoc.execCommand("unlink", false, null);

            if ($.wysiwyg.rmFormat && $.wysiwyg.rmFormat.enabled) {
                if ("object" === typeof (this.options.plugins.rmFormat.rmMsWordMarkup)) {
                    $.wysiwyg.rmFormat.run(this, {rules: { msWordMarkup: this.options.plugins.rmFormat.rmMsWordMarkup }});
                } else {
                    $.wysiwyg.rmFormat.run(this, {rules: { msWordMarkup: { enabled: true }}});
                }
            }

            return this;
        };

        this.ui.removeHoverClass = function () {
            $(this).removeClass("wysiwyg-button-hover");
        };

        this.resetFunction = function () {
            this.setContent(this.initialContent);
        };

        this.saveContent = function () {
            if (this.viewHTML)
            {
                return; // no need
            }
            if (this.original) {
                var content, newContent;

                content = this.getContent();

                if (this.options.rmUnwantedBr) {
                    content = content.replace(/<br\/?>$/, "");
                }

                if (this.options.replaceDivWithP) {
                    newContent = $("<div/>").addClass("temp").append(content);

                    newContent.children("div").each(function () {
                        var element = $(this), p = element.find("p"), i;

                        if (0 === p.length) {
                            p = $("<p></p>");

                            if (this.attributes.length > 0) {
                                for (i = 0; i < this.attributes.length; i += 1) {
                                    p.attr(this.attributes[i].name, element.attr(this.attributes[i].name));
                                }
                            }

                            p.append(element.html());

                            element.replaceWith(p);
                        }
                    });
                    
                    content = newContent.html();
                }

                $(this.original).val(content);

                if (this.options.events && this.options.events.save) {
                    this.options.events.save.call(this);
                }
            }

            return this;
        };

        this.setContent = function (newContent) {
            this.editorDoc.body.innerHTML = newContent;
            this.saveContent();

            return this;
        };

        this.triggerControl = function (name, control) {
            var cmd = control.command || name,                            //command directly for designMode=on iframe (this.editorDoc)
                args = control["arguments"] || [];

            if (control.exec) {
                control.exec.apply(this);  //custom exec function in control, allows DOM changing
            } else {
                this.ui.focus();
                this.ui.withoutCss(); //disable style="" attr inserting in mozzila's designMode
                // when click <Cut>, <Copy> or <Paste> got "Access to XPConnect service denied" code: "1011"
                // in Firefox untrusted JavaScript is not allowed to access the clipboard
                try {
                    this.editorDoc.execCommand(cmd, false, args);
                } catch (e) {
                    console.error(e);
                }
            }

            if (this.options.autoSave) {
                this.autoSaveFunction();
            }
        };

        this.triggerControlCallback = function (name) {
            $(window).trigger("trigger-" + name + ".wysiwyg", [this]);
        };

        this.ui.withoutCss = function () {
            var self = this.self;

            if ($.browser.mozilla) {
                try {
                    self.editorDoc.execCommand("styleWithCSS", false, false);
                } catch (e) {
                    try {
                        self.editorDoc.execCommand("useCSS", false, true);
                    } catch (e2) {
                    }
                }
            }

            return self;
        };

        this.wrapInitialContent = function () {
            var content = this.initialContent,
                found = content.match(/<\/?p>/gi);

            if (!found) {
                return "<p>" + content + "</p>";
            } else {
                // :TODO: checking/replacing
            }

            return content;
        };
    }

    /*
     * Wysiwyg namespace: public properties and methods
     */
    $.wysiwyg = {
        messages: {
            noObject: "Something goes wrong, check object"
        },

        /**
         * Custom control support by Alec Gorge ( http://github.com/alecgorge )
         */
        addControl: function (object, name, settings) {
            return object.each(function () {
                var oWysiwyg = $(this).data("wysiwyg"),
                    customControl = {},
                    toolbar;

                if (!oWysiwyg) {
                    return this;
                }

                customControl[name] = $.extend(true, {visible: true, custom: true}, settings);
                $.extend(true, oWysiwyg.options.controls, customControl);

                // render new toolbar
                toolbar = $(oWysiwyg.options.toolbarHtml);
                oWysiwyg.ui.toolbar.replaceWith(toolbar);
                oWysiwyg.ui.toolbar = toolbar;
                oWysiwyg.ui.appendControls();
            });
        },

        clear: function (object) {
            return object.each(function () {
                var oWysiwyg = $(this).data("wysiwyg");

                if (!oWysiwyg) {
                    return this;
                }

                oWysiwyg.setContent("");
            });
        },

        console: console, // let our console be available for extensions

        destroy: function (object) {
            return object.each(function () {
                var oWysiwyg = $(this).data("wysiwyg");

                if (!oWysiwyg) {
                    return this;
                }

                oWysiwyg.destroy();
            });
        },

        "document": function (object) {
            // no chains because of return
            var oWysiwyg = object.data("wysiwyg");

            if (!oWysiwyg) {
                return undefined;
            }

            return $(oWysiwyg.editorDoc);
        },

        getContent: function (object) {
            // no chains because of return
            var oWysiwyg = object.data("wysiwyg");

            if (!oWysiwyg) {
                return undefined;
            }

            return oWysiwyg.getContent();
        },

        init: function (object, options) {
            return object.each(function () {
                var opts = $.extend(true, {}, options),
                    obj;

                // :4fun:
                // remove this textarea validation and change line in this.saveContent function
                // $(this.original).val(content); to $(this.original).html(content);
                // now you can make WYSIWYG editor on h1, p, and many more tags
                if (("textarea" !== this.nodeName.toLowerCase()) || $(this).data("wysiwyg")) {
                    return;
                }

                obj = new Wysiwyg();
                obj.init(this, opts);
                $.data(this, "wysiwyg", obj);

                $(obj.editorDoc).trigger("afterInit.wysiwyg");
            });
        },

        insertHtml: function (object, szHTML) {
            return object.each(function () {
                var oWysiwyg = $(this).data("wysiwyg");

                if (!oWysiwyg) {
                    return this;
                }

                oWysiwyg.insertHtml(szHTML);
            });
        },

        plugin: {
            listeners: {},

            bind: function (Wysiwyg) {
                var self = this;

                $.each(this.listeners, function (action, handlers) {
                    var i, plugin;

                    for (i = 0; i < handlers.length; i += 1) {
                        plugin = self.parseName(handlers[i]);

                        $(Wysiwyg.editorDoc).bind(action + ".wysiwyg", {plugin: plugin}, function (event) {
                            $.wysiwyg[event.data.plugin.name][event.data.plugin.method].apply($.wysiwyg[event.data.plugin.name], [Wysiwyg]);
                        });
                    }
                });
            },

            exists: function (name) {
                var plugin;

                if ("string" !== typeof (name)) {
                    return false;
                }

                plugin = this.parseName(name);

                if (!$.wysiwyg[plugin.name] || !$.wysiwyg[plugin.name][plugin.method]) {
                    return false;
                }

                return true;
            },

            listen: function (action, handler) {
                var plugin;

                plugin = this.parseName(handler);

                if (!$.wysiwyg[plugin.name] || !$.wysiwyg[plugin.name][plugin.method]) {
                    return false;
                }

                if (!this.listeners[action]) {
                    this.listeners[action] = [];
                }

                this.listeners[action].push(handler);

                return true;
            },

            parseName: function (name) {
                var elements;

                if ("string" !== typeof (name)) {
                    return false;
                }

                elements = name.split(".");

                if (2 > elements.length) {
                    return false;
                }

                return {name: elements[0], method: elements[1]};
            },

            register: function (data) {
                if (!data.name) {
                    console.error("Plugin name missing");
                }

                $.each($.wysiwyg, function (pluginName) {
                    if (pluginName === data.name) {
                        console.error("Plugin with name '" + data.name + "' was already registered");
                    }
                });

                $.wysiwyg[data.name] = data;

                return true;
            }
        },

        removeFormat: function (object) {
            return object.each(function () {
                var oWysiwyg = $(this).data("wysiwyg");

                if (!oWysiwyg) {
                    return this;
                }

                oWysiwyg.removeFormat();
            });
        },

        save: function (object) {
            return object.each(function () {
                var oWysiwyg = $(this).data("wysiwyg");

                if (!oWysiwyg) {
                    return this;
                }

                oWysiwyg.saveContent();
            });
        },

        selectAll: function (object) {
            var oWysiwyg = object.data("wysiwyg"), oBody, oRange, selection;

            if (!oWysiwyg) {
                return this;
            }

            oBody = oWysiwyg.editorDoc.body;
            if (window.getSelection) {
                selection = oWysiwyg.getInternalSelection();
                selection.selectAllChildren(oBody);
            } else {
                oRange = oBody.createTextRange();
                oRange.moveToElementText(oBody);
                oRange.select();
            }
        },

        setContent: function (object, newContent) {
            return object.each(function () {
                var oWysiwyg = $(this).data("wysiwyg");

                if (!oWysiwyg) {
                    return this;
                }

                oWysiwyg.setContent(newContent);
            });
        },

        triggerControl: function (object, controlName) {
            return object.each(function () {
                var oWysiwyg = $(this).data("wysiwyg");

                if (!oWysiwyg) {
                    return this;
                }

                if (!oWysiwyg.controls[controlName]) {
                    console.error("Control '" + controlName + "' not exists");
                }

                oWysiwyg.triggerControl.apply(oWysiwyg, [controlName, oWysiwyg.controls[controlName]]);
            });
        },

        support: {
            prop: supportsProp
        },

        utils: {
            extraSafeEntities: [["<", ">", "'", '"', " "], [32]],

            encodeEntities: function (str) {
                var self = this, aStr, aRet = [];

                if (this.extraSafeEntities[1].length === 0) {
                    $.each(this.extraSafeEntities[0], function (i, ch) {
                        self.extraSafeEntities[1].push(ch.charCodeAt(0));
                    });
                }
                aStr = str.split("");
                $.each(aStr, function (i) {
                    var iC = aStr[i].charCodeAt(0);
                    if ($.inArray(iC, self.extraSafeEntities[1]) && (iC < 65 || iC > 127 || (iC > 90 && iC < 97))) {
                        aRet.push('&#' + iC + ';');
                    } else {
                        aRet.push(aStr[i]);
                    }
                });

                return aRet.join('');
            }
        }
    };

    /**
     * Unifies dialog methods to allow custom implementations
     * 
     * Events:
     *     * afterOpen
     *     * beforeShow
     *     * afterShow
     *     * beforeHide
     *     * afterHide
     *     * beforeClose
     *     * afterClose
     * 
     * Example:
     * var dialog = new ($.wysiwyg.dialog)($('#idToTextArea').data('wysiwyg'), {"title": "Test", "content": "form data, etc."});
     * 
     * dialog.bind("afterOpen", function () { alert('you should see a dialog behind this one!'); });
     * 
     * dialog.open();
     * 
     * 
     */
    $.wysiwyg.dialog = function (jWysiwyg, opts) {
        
        var theme    = (jWysiwyg && jWysiwyg.options && jWysiwyg.options.dialog) ? jWysiwyg.options.dialog : (opts.theme ? opts.theme : "default"),
            obj        = new $.wysiwyg.dialog.createDialog(theme),
            that    = this,
            $that    = $(that);
                
        this.options = {
            "modal": true,
            "draggable": true,
            "title": "Title",
            "content": "Content",
            "width":  "auto",
            "height": "auto",
            "zIndex": 2000,
            "open": false,
            "close": false
        };

        this.isOpen = false;

        $.extend(this.options, opts);

        this.object = obj;

        // Opens a dialog with the specified content
        this.open = function () {
            this.isOpen = true;

            obj.init.apply(that, []);
            var $dialog = obj.show.apply(that, []);

            $that.trigger("afterOpen", [$dialog]);
            
        };

        this.show = function () {
            this.isOpen = true;
            
            $that.trigger("beforeShow");
            
            var $dialog = obj.show.apply(that, []);
            
            $that.trigger("afterShow");
        };

        this.hide = function () {
            this.isOpen = false;
            
            $that.trigger("beforeHide");
            
            var $dialog = obj.hide.apply(that, []);
            
            $that.trigger("afterHide", [$dialog]);
        };

        // Closes the dialog window.
        this.close = function () {
            this.isOpen = false;
                        
            var $dialog = obj.hide.apply(that, []);
            
            $that.trigger("beforeClose", [$dialog]);
            
            obj.destroy.apply(that, []);
            
            $that.trigger("afterClose", [$dialog]);
            
        };

        if (this.options.open) {
            $that.bind("afterOpen", this.options.open);
        }
        if (this.options.close) {
            $that.bind("afterClose", this.options.close);
        }

        return this;
    };

    // "Static" Dialog methods.
    $.extend(true, $.wysiwyg.dialog, {
        _themes : {}, // sample {"Theme Name": object}
        _theme : "", // the current theme

        register : function(name, obj) {
            $.wysiwyg.dialog._themes[name] = obj;
        },

        deregister : function (name) {
            delete $.wysiwyg.dialog._themes[name];
        },

        createDialog : function (name) {
            return new ($.wysiwyg.dialog._themes[name]);
        },
        
        getDimensions : function () {
            var width  = document.body.scrollWidth,
                height = document.body.scrollHeight;

            if ($.browser.opera) {
                height = Math.max(
                    $(document).height(),
                    $(window).height(),
                    document.documentElement.clientHeight);
            }

            return [width, height];
        }
    });

    $(function () { // need access to jQuery UI stuff.
        if (jQuery.ui) {
            $.wysiwyg.dialog.register("jqueryui", function () {
                var that = this;

                this._$dialog = null;

                this.init = function() {
                    var abstractDialog    = this,
                        content         = this.options.content;

                    if (typeof content === 'object') {
                        if (typeof content.html === 'function') {
                            content = content.html();
                        } else if(typeof content.toString === 'function') {
                            content = content.toString();
                        }
                    }

                    that._$dialog = $('<div></div>').attr('title', this.options.title).html(content);

                    var dialogHeight = this.options.height == 'auto' ? 300 : this.options.height,
                        dialogWidth = this.options.width == 'auto' ? 450 : this.options.width;

                    // console.log(that._$dialog);
                    
                    that._$dialog.dialog({
                        modal: this.options.modal,
                        draggable: this.options.draggable,
                        height: dialogHeight,
                        width: dialogWidth
                    });

                    return that._$dialog;
                };

                this.show = function () {
                    that._$dialog.dialog("open");
                    return that._$dialog;
                };

                this.hide = function () {
                    that._$dialog.dialog("close");
                    return that._$dialog;
                };

                this.destroy = function() {
                    that._$dialog.dialog("destroy");
                    return that._$dialog;
                };
            });
        }

        $.wysiwyg.dialog.register("default", function () {
            var that = this;

            this._$dialog = null;

            this.init = function() {
                var abstractDialog    = this,
                    content         = this.options.content;

                if (typeof content === 'object') {
                    if(typeof content.html === 'function') {
                        content = content.html();
                    }
                    else if(typeof content.toString === 'function') {
                        content = content.toString();
                    }
                }

                that._$dialog = $('<div class="wysiwyg-dialog"></div>').css({"z-index": this.options.zIndex});

                var $topbar = $('<div class="wysiwyg-dialog-topbar"><div class="wysiwyg-dialog-close-wrapper"></div><div class="wysiwyg-dialog-title">'+this.options.title+'</div></div>');
                var $link = $('<a href="#" class="wysiwyg-dialog-close-button">X</a>');

                $link.click(function () {
                    abstractDialog.close(); // this is important it makes sure that is close from the abstract $.wysiwyg.dialog instace, not just locally 
                });
                
                $topbar.find('.wysiwyg-dialog-close-wrapper').prepend($link);

                var $dcontent = $('<div class="wysiwyg-dialog-content">'+content+'</div>');

                that._$dialog.append($topbar).append($dcontent);
                
                // Set dialog's height & width, and position it correctly:
                var dialogHeight = this.options.height == 'auto' ? 300 : this.options.height,
                    dialogWidth = this.options.width == 'auto' ? 450 : this.options.width;
                that._$dialog.hide().css({
                    "width": dialogWidth,
                    "height": dialogHeight,
                    "left": (($(window).width() - dialogWidth) / 2),
                    "top": (($(window).height() - dialogHeight) / 3)
                });

                $("body").append(that._$dialog);

                return that._$dialog;
            };

            this.show = function () {

                // Modal feature:
                if (this.options.modal) {
                    var dimensions = $.wysiwyg.dialog.getDimensions(),
                        wrapper    = $('<div class="wysiwyg-dialog-modal-div"></div>')
                        .css({"width": dimensions[0], "height": dimensions[1]});
                    that._$dialog.wrap(wrapper);
                }
                
                // Draggable feature:
                if (this.options.draggable) { 
                    
                    var mouseDown = false;
                    
                    that._$dialog.find("div.wysiwyg-dialog-topbar").bind("mousedown", function (e) {
                        e.preventDefault();
                        $(this).css({ "cursor": "move" });
                        var $topbar = $(this),
                            _dialog = $(this).parents(".wysiwyg-dialog"),
                            offsetX = (e.pageX - parseInt(_dialog.css("left"), 10)),
                            offsetY = (e.pageY - parseInt(_dialog.css("top"), 10));
                        mouseDown = true;
                        $(this).css({ "cursor": "move" });
                        
                        $(document).bind("mousemove", function (e) {
                            e.preventDefault();
                            if (mouseDown) {
                                _dialog.css({
                                    "top": (e.pageY - offsetY),
                                    "left": (e.pageX - offsetX)
                                });
                            }
                        }).bind("mouseup", function (e) {
                            e.preventDefault();
                            mouseDown = false;
                            $topbar.css({ "cursor": "auto" });
                            $(document).unbind("mousemove").unbind("mouseup");
                        });
                    
                    });
                }
                
                that._$dialog.show();
                return that._$dialog;

            };

            this.hide = function () {
                that._$dialog.hide();
                return that._$dialog;
            };

            this.destroy = function() {
            
                // Modal feature:
                if (this.options.modal) { 
                    that._$dialog.unwrap();
                }
                
                // Draggable feature:
                if (this.options.draggable) { 
                    that._$dialog.find("div.wysiwyg-dialog-topbar").unbind("mousedown");
                }
                
                that._$dialog.remove();
                return that._$dialog;
            };
        });
    });
    // end Dialog

    $.fn.wysiwyg = function (method) {
        var args = arguments, plugin;

        if ("undefined" !== typeof $.wysiwyg[method]) {
            // set argument object to undefined
            args = Array.prototype.concat.call([args[0]], [this], Array.prototype.slice.call(args, 1));
            return $.wysiwyg[method].apply($.wysiwyg, Array.prototype.slice.call(args, 1));
        } else if ("object" === typeof method || !method) {
            Array.prototype.unshift.call(args, this);
            return $.wysiwyg.init.apply($.wysiwyg, args);
        } else if ($.wysiwyg.plugin.exists(method)) {
            plugin = $.wysiwyg.plugin.parseName(method);
            args = Array.prototype.concat.call([args[0]], [this], Array.prototype.slice.call(args, 1));
            return $.wysiwyg[plugin.name][plugin.method].apply($.wysiwyg[plugin.name], Array.prototype.slice.call(args, 1));
        } else {
            console.error("Method '" +  method + "' does not exist on jQuery.wysiwyg.\nTry to include some extra controls or plugins");
        }
    };
    
    $.fn.getWysiwyg = function () {
        return this.data("wysiwyg");
    };
})(jQuery);

/**
 * rmFormat plugin
 *
 * Depends on jWYSIWYG
 */
(function ($) {
    if (undefined === $.wysiwyg) {
        throw "wysiwyg.rmFormat.js depends on $.wysiwyg";
    }

    /*
     * Wysiwyg namespace: public properties and methods
     */
    var rmFormat = {
        name: "rmFormat",
        version: "",
        defaults: {
            rules: {
                heading: false,
                table: false,
                inlineCSS: false,
                /*
                 * rmAttr       - { "all" | object with names } remove all
                 *                attributes or attributes with following names
                 *
                 * rmWhenEmpty  - if element contains no text or { \s, \n, <br>, <br/> }
                 *                then it will be removed
                 *
                 * rmWhenNoAttr - if element contains no attributes (i.e. <span>Some Text</span>)
                 *                then it will be removed
                 */
                msWordMarkup: {
                    enabled: false,
                    tags: {
                        "a": {
                            rmWhenEmpty: true
                        },

                        "b": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },

                        "div": {
                            rmAttr: "all",
                            rmWhenEmpty: true,
                            rmWhenNoAttr: true
                        },

                        "em": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },

                        "font": {
                            rmAttr: "all",
                            rmWhenEmpty: true,
                            rmWhenNoAttr: true
                        },

                        "h1": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },
                        "h2": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },
                        "h3": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },
                        "h4": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },
                        "h5": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },
                        "h6": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },

                        "i": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },
                        
                        "ul": {
                            rmAttr: "all"
                        },
                        
                        "li": {
                            rmAttr: "all"
                        },

                        "p": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },

                        "span": {
                            rmAttr: "all",
                            rmWhenEmpty: true,
                            rmWhenNoAttr: true
                        },

                        "strong": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },

                        "u": {
                            rmAttr: "all",
                            rmWhenEmpty: true
                        },
                        
                        "table": {
                            rmAttr: "all"
                        },
                        
                        "tr": {
                            rmAttr: "all"
                        },
                        
                        "td": {
                            rmAttr: "all"
                        },
                        
                        "th": {
                            rmAttr: "all"
                        }
                    }
                }
            }
        },
        options: {},
        enabled: false,
        debug:    false,

        domRemove: function (node) {
            // replace h1-h6 with p
            if (this.options.rules.heading) {
                if (node.nodeName.toLowerCase().match(/^h[1-6]$/)) {
                    // in chromium change this to
                    // $(node).replaceWith($('<p/>').html(node.innerHTML));
                    // to except DOM error: also try in other browsers
                    $(node).replaceWith($('<p/>').html($(node).contents()));

                    return true;
                }
            }

            // remove tables not smart enough )
            if (this.options.rules.table) {
                if (node.nodeName.toLowerCase().match(/^(table|t[dhr]|t(body|foot|head))$/)) {
                    $(node).replaceWith($(node).contents());

                    return true;
                }
            }

            return false;
        },
        
        removeStyle: function(node) {
          if (this.options.rules.inlineCSS) {
            $(node).removeAttr('style');
          }
        },

        domTraversing: function (el, start, end, canRemove, cnt) {
            if (null === canRemove) {
                canRemove = false;
            }

            var isDomRemoved, p;

            while (el) {
                if (this.debug) { console.log(cnt, "canRemove=", canRemove); }
                
                this.removeStyle(el);

                if (el.firstElementChild) {

                    if (this.debug) { console.log(cnt, "domTraversing", el.firstElementChild); }

                    return this.domTraversing(el.firstElementChild, start, end, canRemove, cnt + 1);
                } else {

                    if (this.debug) { console.log(cnt, "routine", el); }

                    isDomRemoved = false;

                    if (start === el) {
                        canRemove = true;
                    }

                    if (canRemove) {
                        if (el.previousElementSibling) {
                            p = el.previousElementSibling;
                        } else {
                            p = el.parentNode;
                        }

                        if (this.debug) { console.log(cnt, el.nodeName, el.previousElementSibling, el.parentNode, p); }

                        isDomRemoved = this.domRemove(el);
                        if (this.domRemove(el)) {

                            if (this.debug) { console.log("p", p); }

                            // step back to parent or previousElement to traverse again then element is removed
                            el = p;
                        }

                        if (start !== end && end === el) {
                            return true;
                        }
                    }

                    if (false === isDomRemoved) {
                        el = el.nextElementSibling;
                    }
                }
            }

            return false;
        },

        msWordMarkup: function (text) {
            var tagName, attrName, rules, reg, regAttr, found, attrs;

            // @link https://github.com/akzhan/jwysiwyg/issues/165
            text = text.replace(/&lt;/g, "<").replace(/&gt;/g, ">");

            text = text.replace(/<meta\s[^>]+>/g, "");
            text = text.replace(/<link\s[^>]+>/g, "");
            text = text.replace(/<title>[\s\S]*?<\/title>/g, "");
            text = text.replace(/<style(?:\s[^>]*)?>[\s\S]*?<\/style>/gm, "");
            text = text.replace(/<w:([^\s>]+)(?:\s[^\/]+)?\/>/g, "");
            text = text.replace(/<w:([^\s>]+)(?:\s[^>]+)?>[\s\S]*?<\/w:\1>/gm, "");
            text = text.replace(/<m:([^\s>]+)(?:\s[^\/]+)?\/>/g, "");
            text = text.replace(/<m:([^\s>]+)(?:\s[^>]+)?>[\s\S]*?<\/m:\1>/gm, "");

            // after running the above.. it still needed these
            text = text.replace(/<xml>[\s\S]*?<\/xml>/g, "");
            text = text.replace(/<object(?:\s[^>]*)?>[\s\S]*?<\/object>/g, "");
            text = text.replace(/<o:([^\s>]+)(?:\s[^\/]+)?\/>/g, "");
            text = text.replace(/<o:([^\s>]+)(?:\s[^>]+)?>[\s\S]*?<\/o:\1>/gm, "");
            text = text.replace(/<st1:([^\s>]+)(?:\s[^\/]+)?\/>/g, "");
            text = text.replace(/<st1:([^\s>]+)(?:\s[^>]+)?>[\s\S]*?<\/st1:\1>/gm, "");
            // ----
            text = text.replace(/<!--[^>]+>\s*<[^>]+>/gm, ""); // firefox needed this 1

                        
            text = text.replace(/^[\s\n]+/gm, "");

            if (this.options.rules.msWordMarkup.tags) {
                for (tagName in this.options.rules.msWordMarkup.tags) {
                    rules = this.options.rules.msWordMarkup.tags[tagName];
                    
                    if ("string" === typeof (rules)) {
                        if ("" === rules) {
                            reg = new RegExp("<" + tagName + "(?:\\s[^>]+)?/?>", "gmi");
                            text = text.replace(reg, "<" + tagName + ">");
                        }
                    } else if ("object" === typeof (rules)) {
                        reg = new RegExp("<" + tagName + "(\\s[^>]+)?/?>", "gmi");
                        found = reg.exec(text);
                        attrs = "";

                        if (found && found[1]) {
                            attrs = found[1];
                        }

                        if (rules.rmAttr) {
                            if ("all" === rules.rmAttr) {
                                attrs = "";
                            } else if ("object" === typeof (rules.rmAttr) && attrs) {
                                for (attrName in rules.rmAttr) {
                                    regAttr = new RegExp(attrName + '="[^"]*"', "mi");
                                    attrs = attrs.replace(regAttr, "");
                                }
                            }
                        }

                        if (attrs) {
                            attrs = attrs.replace(/[\s\n]+/gm, " ");
                            
                            if (" " === attrs) {
                                attrs = "";
                            }
                        }

                        text = text.replace(reg, "<" + tagName + attrs + ">");
                    }
                }

                for (tagName in this.options.rules.msWordMarkup.tags) {
                    rules = this.options.rules.msWordMarkup.tags[tagName];

                    if ("string" === typeof (rules)) {
                        //
                    } else if ("object" === typeof (rules)) {
                        if (rules.rmWhenEmpty) {
                            reg = new RegExp("<" + tagName + "(\\s[^>]+)?>(?:[\\s\\n]|<br/?>)*?</" + tagName + ">", "gmi");
                            text = text.replace(reg, "");
                        }

                        if (rules.rmWhenNoAttr) {
                            reg = new RegExp("<" + tagName + ">(?!<" + tagName + ">)([\\s\\S]*?)</" + tagName + ">", "mi");
                            found = reg.exec(text);
                            while (found) {
                                text = text.replace(reg, found[1]);

                                found = reg.exec(text);
                            }
                        }
                    }
                }
            }

            return text;
        },

        run: function (Wysiwyg, options) {
            options = options || {};
            this.options = $.extend(true, this.defaults, options);

            if (this.options.rules.heading || this.options.rules.table) {
                var r = Wysiwyg.getInternalRange(),
                    start,
                    end,
                    node,
                    traversing;

                start = r.startContainer;
                if (start.nodeType === 3) {
                    start = start.parentNode;
                }

                end = r.endContainer;
                if (end.nodeType === 3) {
                    end = end.parentNode;
                }

                if (this.debug) {
                    console.log("start", start, start.nodeType, start.nodeName, start.parentNode);
                    console.log("end", end, end.nodeType, end.nodeName, end.parentNode);
                }

                node = r.commonAncestorContainer;
                if (node.nodeType === 3) {
                    node = node.parentNode;
                }

                if (this.debug) {
                    console.log("node", node, node.nodeType, node.nodeName.toLowerCase(), node.parentNode, node.firstElementChild);
                    console.log(start === end);
                }

                traversing = null;
                if (start === end) {
                    traversing = this.domTraversing(node, start, end, true, 1);
                } else {
                    traversing = this.domTraversing(node.firstElementChild, start, end, null, 1);
                }

                if (this.debug) { console.log("DomTraversing=", traversing); }
            }

            if (this.options.rules.msWordMarkup.enabled) {
                Wysiwyg.setContent(this.msWordMarkup(Wysiwyg.getContent()));
            }
        }
    };

    $.wysiwyg.plugin.register(rmFormat);
})(jQuery);

/**
 * Controls: Table plugin
 * 
 * Depends on jWYSIWYG
 */
(function ($) {
    "use strict";

    if (undefined === $.wysiwyg) {
        throw "wysiwyg.table.js depends on $.wysiwyg";
    }

    if (!$.wysiwyg.controls) {
        $.wysiwyg.controls = {};
    }

    var insertTable = function (colCount, rowCount, filler) {
        if (isNaN(rowCount) || isNaN(colCount) || rowCount === null || colCount === null) {
            return;
        }

        var i, j, html = ['<table border="1" style="width: 100%;"><tbody>'];

        colCount = parseInt(colCount, 10);
        rowCount = parseInt(rowCount, 10);

        if (filler === null) {
            filler = "&nbsp;";
        }
        filler = "<td>" + filler + "</td>";

        for (i = rowCount; i > 0; i -= 1) {
            html.push("<tr>");
            for (j = colCount; j > 0; j -= 1) {
                html.push(filler);
            }
            html.push("</tr>");
        }
        html.push("</tbody></table>");

        return this.insertHtml(html.join(""));
    };

    /*
     * Wysiwyg namespace: public properties and methods
     */
    $.wysiwyg.controls.table = function (Wysiwyg) {
        var adialog, dialog, colCount, rowCount, formTableHtml, dialogReplacements, key, translation, regexp;

        dialogReplacements = {
            legend: "Insert table",
            cols  : "Count of columns",
            rows  : "Count of rows",
            submit: "Insert table",
            reset: "Cancel"
        };

        formTableHtml = '<form class="wysiwyg" id="wysiwyg-tableInsert"><fieldset><legend>{legend}</legend>' +
            '<label>{cols}: <input type="text" name="colCount" value="3" /></label><br/>' +
            '<label>{rows}: <input type="text" name="rowCount" value="3" /></label><br/>' +
            '<input type="submit" class="button" value="{submit}"/> ' +
            '<input type="reset" value="{reset}"/></fieldset></form>';
        
        for (key in dialogReplacements) {
            if ($.wysiwyg.i18n) {
                translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs.table");

                if (translation === dialogReplacements[key]) { // if not translated search in dialogs 
                    translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs");
                }

                dialogReplacements[key] = translation;
            }

            regexp = new RegExp("{" + key + "}", "g");
            formTableHtml = formTableHtml.replace(regexp, dialogReplacements[key]);
        }

        if (!Wysiwyg.insertTable) {
            Wysiwyg.insertTable = insertTable;
        }

        adialog = new $.wysiwyg.dialog(Wysiwyg, {
            "title"   : dialogReplacements.legend,
            "content" : formTableHtml,
            "open"    : function (e, dialog) {
                dialog.find("form#wysiwyg-tableInsert").submit(function (e) {
                    e.preventDefault();
                    rowCount = dialog.find("input[name=rowCount]").val();
                    colCount = dialog.find("input[name=colCount]").val();

                    Wysiwyg.insertTable(colCount, rowCount, Wysiwyg.defaults.tableFiller);

                    adialog.close();
                    return false;
                });

                dialog.find("input:reset").click(function (e) {
                    e.preventDefault();
                    adialog.close();
                    return false;
                });
            }
        });
        
        adialog.open();

        $(Wysiwyg.editorDoc).trigger("editorRefresh.wysiwyg");
    };

    $.wysiwyg.insertTable = function (object, colCount, rowCount, filler) {
        return object.each(function () {
            var Wysiwyg = $(this).data("wysiwyg");

            if (!Wysiwyg.insertTable) {
                Wysiwyg.insertTable = insertTable;
            }

            if (!Wysiwyg) {
                return this;
            }

            Wysiwyg.insertTable(colCount, rowCount, filler);
            $(Wysiwyg.editorDoc).trigger("editorRefresh.wysiwyg");

            return this;
        });
    };
})(jQuery);

/**
 * Controls: Link plugin
 *
 * Depends on jWYSIWYG
 *
 * By: Esteban Beltran (academo) <sergies@gmail.com>
 */
(function ($) {
    "use strict";

    if (undefined === $.wysiwyg) {
        throw "wysiwyg.link.js depends on $.wysiwyg";
    }

    if (!$.wysiwyg.controls) {
        $.wysiwyg.controls = {};
    }

    /*
    * Wysiwyg namespace: public properties and methods
    */
    $.wysiwyg.controls.link = {
        init: function (Wysiwyg) {
            var self = this, elements, dialog, url, a, selection,
                formLinkHtml, dialogReplacements, key, translation, regexp,
                baseUrl, img;

            dialogReplacements = {
                legend: "Insert Link",
                url   : "Link URL",
                title : "Link Title",
                target: "Link Target",
                submit: "Insert Link",
                reset: "Cancel"
            };

            formLinkHtml = '<form class="wysiwyg"><fieldset><legend>{legend}</legend>' +
                '<label>{url}: <input type="text" name="linkhref" value=""/></label>' +
                '<label>{title}: <input type="text" name="linktitle" value=""/></label>' +
                '<label>{target}: <input type="text" name="linktarget" value=""/></label>' +
                '<input type="submit" class="button" value="{submit}"/> ' +
                '<input type="reset" value="{reset}"/></fieldset></form>';

            for (key in dialogReplacements) {
                if ($.wysiwyg.i18n) {
                    translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs.link");

                    if (translation === dialogReplacements[key]) { // if not translated search in dialogs 
                        translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs");
                    }

                    dialogReplacements[key] = translation;
                }

                regexp = new RegExp("{" + key + "}", "g");
                formLinkHtml = formLinkHtml.replace(regexp, dialogReplacements[key]);
            }

            a = {
                self: Wysiwyg.dom.getElement("a"), // link to element node
                href: "http://",
                title: "",
                target: ""
            };

            if (a.self) {
                a.href = a.self.href ? a.self.href : a.href;
                a.title = a.self.title ? a.self.title : "";
                a.target = a.self.target ? a.self.target : "";
            }

            if ($.fn.dialog) {
                elements = $(formLinkHtml);
                elements.find("input[name=linkhref]").val(a.href);
                elements.find("input[name=linktitle]").val(a.title);
                elements.find("input[name=linktarget]").val(a.target);

                if ($.browser.msie) {
                    try {
                        dialog = elements.appendTo(Wysiwyg.editorDoc.body);
                    } catch (err) {
                        dialog = elements.appendTo("body");
                    }
                } else {
                    dialog = elements.appendTo("body");
                }

                dialog.dialog({
                    modal: true,
                    open: function (ev, ui) {
                        $("input:submit", dialog).click(function (e) {
                            e.preventDefault();

                            var url = $('input[name="linkhref"]', dialog).val(),
                                title = $('input[name="linktitle"]', dialog).val(),
                                target = $('input[name="linktarget"]', dialog).val(),
                                baseUrl,
                                img;

                            if (Wysiwyg.options.controlLink.forceRelativeUrls) {
                                baseUrl = window.location.protocol + "//" + window.location.hostname;
                                if (0 === url.indexOf(baseUrl)) {
                                    url = url.substr(baseUrl.length);
                                }
                            }

                            if (a.self) {
                                if ("string" === typeof (url)) {
                                    if (url.length > 0) {
                                        // to preserve all link attributes
                                        $(a.self).attr("href", url).attr("title", title).attr("target", target);
                                    } else {
                                        $(a.self).replaceWith(a.self.innerHTML);
                                    }
                                }
                            } else {
                                if ($.browser.msie) {
                                    Wysiwyg.ui.returnRange();
                                }

                                //Do new link element
                                selection = Wysiwyg.getRangeText();
                                img = Wysiwyg.dom.getElement("img");

                                if ((selection && selection.length > 0) || img) {
                                    if ($.browser.msie) {
                                        Wysiwyg.ui.focus();
                                    }

                                    if ("string" === typeof (url)) {
                                        if (url.length > 0) {
                                            Wysiwyg.editorDoc.execCommand("createLink", false, url);
                                        } else {
                                            Wysiwyg.editorDoc.execCommand("unlink", false, null);
                                        }
                                    }

                                    a.self = Wysiwyg.dom.getElement("a");

                                    $(a.self).attr("href", url).attr("title", title);

                                    /**
                                     * @url https://github.com/akzhan/jwysiwyg/issues/16
                                     */
                                    $(a.self).attr("target", target);
                                } else if (Wysiwyg.options.messages.nonSelection) {
                                    window.alert(Wysiwyg.options.messages.nonSelection);
                                }
                            }

                            Wysiwyg.saveContent();

                            $(dialog).dialog("close");
                        });
                        $("input:reset", dialog).click(function (e) {
                            e.preventDefault();
                            $(dialog).dialog("close");
                        });
                    },
                    close: function (ev, ui) {
                        dialog.dialog("destroy");
                        dialog.remove();
                    }
                });
            } else {
                if (a.self) {
                    url = window.prompt("URL", a.href);

                    if (Wysiwyg.options.controlLink.forceRelativeUrls) {
                        baseUrl = window.location.protocol + "//" + window.location.hostname;
                        if (0 === url.indexOf(baseUrl)) {
                            url = url.substr(baseUrl.length);
                        }
                    }

                    if ("string" === typeof (url)) {
                        if (url.length > 0) {
                            $(a.self).attr("href", url);
                        } else {
                            $(a.self).replaceWith(a.self.innerHTML);
                        }
                    }
                } else {
                    //Do new link element
                    selection = Wysiwyg.getRangeText();
                    img = Wysiwyg.dom.getElement("img");

                    if ((selection && selection.length > 0) || img) {
                        if ($.browser.msie) {
                            Wysiwyg.ui.focus();
                            Wysiwyg.editorDoc.execCommand("createLink", true, null);
                        } else {
                            url = window.prompt(dialogReplacements.url, a.href);

                            if (Wysiwyg.options.controlLink.forceRelativeUrls) {
                                baseUrl = window.location.protocol + "//" + window.location.hostname;
                                if (0 === url.indexOf(baseUrl)) {
                                    url = url.substr(baseUrl.length);
                                }
                            }

                            if ("string" === typeof (url)) {
                                if (url.length > 0) {
                                    Wysiwyg.editorDoc.execCommand("createLink", false, url);
                                } else {
                                    Wysiwyg.editorDoc.execCommand("unlink", false, null);
                                }
                            }
                        }
                    } else if (Wysiwyg.options.messages.nonSelection) {
                        window.alert(Wysiwyg.options.messages.nonSelection);
                    }
                }

                Wysiwyg.saveContent();
            }

            $(Wysiwyg.editorDoc).trigger("editorRefresh.wysiwyg");
        }
    };

    $.wysiwyg.createLink = function (object, url) {
        return object.each(function () {
            var oWysiwyg = $(this).data("wysiwyg"),
                selection;

            if (!oWysiwyg) {
                return this;
            }

            if (!url || url.length === 0) {
                return this;
            }

            selection = oWysiwyg.getRangeText();

            if (selection && selection.length > 0) {
                if ($.browser.msie) {
                    oWysiwyg.ui.focus();
                }
                oWysiwyg.editorDoc.execCommand("unlink", false, null);
                oWysiwyg.editorDoc.execCommand("createLink", false, url);
            } else if (oWysiwyg.options.messages.nonSelection) {
                window.alert(oWysiwyg.options.messages.nonSelection);
            }
            return this;
        });
    };
})(jQuery);