diff --git a/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.css b/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.css
new file mode 100644
index 0000000..934c515
--- /dev/null
+++ b/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.css
@@ -0,0 +1,44 @@
+ul.wysihtml5-toolbar {
+	margin: 0;
+	padding: 0;
+	display: block;
+}
+
+ul.wysihtml5-toolbar::after {
+	clear: both;
+	display: table;
+	content: "";
+}
+
+ul.wysihtml5-toolbar > li {
+	float: left;
+	display: list-item;
+	list-style: none;
+	margin: 0 5px 10px 0;
+}
+
+ul.wysihtml5-toolbar a[data-wysihtml5-command=bold] {
+	font-weight: bold;
+}
+
+ul.wysihtml5-toolbar a[data-wysihtml5-command=italic] {
+	font-style: italic;
+}
+
+ul.wysihtml5-toolbar a[data-wysihtml5-command=underline] {
+	text-decoration: underline;
+}
+
+ul.wysihtml5-toolbar a.btn.wysihtml5-command-active {
+	background-image: none;
+	-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+	-moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+	box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+	background-color: #E6E6E6;
+	background-color: #D9D9D9 9;
+	outline: 0;
+}
+
+ul.wysihtml5-commands-disabled .dropdown-menu {
+	display: none !important;
+}
diff --git a/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.js b/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.js
new file mode 100644
index 0000000..4e3c636
--- /dev/null
+++ b/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.js
@@ -0,0 +1,299 @@
+!function($, wysi) {
+	"use strict"
+	
+	var templates = {
+		"font-styles": "<li class='dropdown'>" +
+							"<a class='btn dropdown-toggle' data-toggle='dropdown' href='#'>" +
+								"<i class='icon-font'></i>&nbsp;<span class='current-font'>Normal text</span>&nbsp;<b class='caret'></b>" +
+							"</a>" +
+						    "<ul class='dropdown-menu'>" +
+						      	"<li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='div'>Normal text</a></li>" +
+					            "<li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='h1'>Heading 1</a></li>" +
+					            "<li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='h2'>Heading 2</a></li>" +
+						    "</ul>" +
+						"</li>",
+		"emphasis":     "<li>" +
+							"<div class='btn-group'>" 
+							    + "<a class='btn' data-wysihtml5-command='bold' title='CTRL+B'>Bold</a>" 
+							    + "<a class='btn' data-wysihtml5-command='italic' title='CTRL+I'>Italic</a>" 
+							    //,+ "<a class='btn' data-wysihtml5-command='underline' title='CTRL+U'>Underline</a>" 
+							+ "</div>" 
+						+ "</li>",
+		"lists": 	"<li>" 
+						+ "<div class='btn-group'>" 
+					    	+ "<a class='btn' data-wysihtml5-command='insertUnorderedList' title='Unordered List'><i class='icon-list'></i></a>" 
+						    + "<a class='btn' data-wysihtml5-command='insertOrderedList' title='Ordered List'><i class='icon-th-list'></i></a>" 
+						    + "<a class='btn' data-wysihtml5-command='Outdent' title='Outdent'><i class='icon-indent-right'></i></a>"  							    
+						    + "<a class='btn' data-wysihtml5-command='Indent' title='Indent'><i class='icon-indent-left'></i></a>" 
+						+ "</div>" 
+					+ "</li>",
+
+		"link": 	"<li>" 
+						
+						+ "<div class='bootstrap-wysihtml5-insert-link-modal modal hide fade'>"
+							+ "<div class='modal-header'>"
+							+ "<a class='close' data-dismiss='modal'>×</a>"
+							  + "<h3>Insert Link</h3>"
+							+ "</div>"
+							+ "<div class='modal-body'>"
+							  + "<input value='http://' class='bootstrap-wysihtml5-insert-link-url input-xlarge'>"
+							+ "</div>"
+							+ "<div class='modal-footer'>"
+							  + "<a href='#' class='btn' data-dismiss='modal'>Cancel</a>"
+							  + "<a href='#' class='btn btn-primary' data-dismiss='modal'>Insert link</a>"
+							+ "</div>"
+						+ "</div>"
+
+				    	+ "<a class='btn' data-wysihtml5-command='createLink' title='Link'><i class='icon-share'></i></a>" 
+
+					+ "</li>",
+
+			"image": "<li>" 
+						
+						+ "<div class='bootstrap-wysihtml5-insert-image-modal modal hide fade'>"
+							+ "<div class='modal-header'>"
+							+ "<a class='close' data-dismiss='modal'>×</a>"
+							  + "<h3>Insert Image</h3>"
+							+ "</div>"
+							+ "<div class='modal-body'>"
+							  + "<input value='http://' class='bootstrap-wysihtml5-insert-image-url input-xlarge'>"
+							+ "</div>"
+							+ "<div class='modal-footer'>"
+							  + "<a href='#' class='btn' data-dismiss='modal'>Cancel</a>"
+							  + "<a href='#' class='btn btn-primary' data-dismiss='modal'>Insert image</a>"
+							+ "</div>"
+						+ "</div>"
+
+						+ "<a class='btn' data-wysihtml5-command='insertImage' title='Insert image'><i class='icon-picture'></i></a>" 
+
+					+ "</li>",
+
+		"html": 
+						"<li>"
+							+ "<div class='btn-group'>"
+								+ "<a class='btn' data-wysihtml5-action='change_view' title='Edit HTML'><i class='icon-pencil'></i></a>" 
+							+ "</div>"
+						+ "</li>"
+	};
+	
+	var defaultOptions = {
+		"font-styles": true,
+		"emphasis": true,
+		"lists": true,
+		"html": false,
+		"link": true,
+		"image": true,
+		events: {},
+		parserRules: {
+			tags: {
+				"b":  {},
+				"i":  {},
+				"br": {},
+				"ol": {},
+				"ul": {},
+				"li": {},
+				"h1": {},
+				"h2": {},
+				"u": 1,
+				"img": {
+					"check_attributes": {
+			            "width": "numbers",
+			            "alt": "alt",
+			            "src": "url",
+			            "height": "numbers"
+			        }
+				},
+				"a":  {
+					set_attributes: {
+						target: "_blank",
+						rel:    "nofollow"
+					},
+					check_attributes: {
+						href:   "url" // important to avoid XSS
+					}
+				}
+			}
+		}
+	};
+
+	var Wysihtml5 = function(el, options) {
+		this.el = el;
+		this.toolbar = this.createToolbar(el, options || defaultOptions);
+		this.editor =  this.createEditor(options);
+		
+		window.editor = this.editor;
+
+  		$('iframe.wysihtml5-sandbox').each(function(i, el){
+			$(el.contentWindow).off('focus.wysihtml5').on({
+			  'focus.wysihtml5' : function(){
+			     $('li.dropdown').removeClass('open');
+			   }
+			});
+		});
+	};
+
+	Wysihtml5.prototype = {
+		constructor: Wysihtml5,
+
+		createEditor: function(options) {
+			var parserRules = defaultOptions.parserRules; 
+
+			if(options && options.parserRules) {
+				parserRules = options.parserRules;
+			}
+				
+			var editor = new wysi.Editor(this.el.attr('id'), {
+	    		toolbar: this.toolbar.attr('id'),
+				parserRules: parserRules
+	  		});
+
+	  		if(options && options.events) {
+				for(var eventName in options.events) {
+					editor.on(eventName, options.events[eventName]);
+				}
+			}	
+
+	  		return editor;
+		},
+		
+		createToolbar: function(el, options) {
+			var self = this;
+			var toolbar = $("<ul/>", {
+				'id' : el.attr('id') + "-wysihtml5-toolbar",
+				'class' : "wysihtml5-toolbar",
+				'style': "display:none"
+			});
+
+			for(var key in defaultOptions) {
+				var value = false;
+				
+				if(options[key] != undefined) {
+					if(options[key] == true) {
+						value = true;
+					}
+				} else {
+					value = defaultOptions[key];
+				}
+				
+				if(value == true) {
+					toolbar.append(templates[key]);
+
+					if(key == "html") {
+						this.initHtml(toolbar);
+					}
+
+					if(key == "link") {
+						this.initInsertLink(toolbar);
+					}
+
+					if(key == "image") {
+						this.initInsertImage(toolbar);
+					}
+				}
+			}
+			
+			var self = this;
+			
+			toolbar.find("a[data-wysihtml5-command='formatBlock']").click(function(e) {
+				var el = $(e.srcElement);
+				self.toolbar.find('.current-font').text(el.html())
+			});
+			
+			this.el.before(toolbar);
+			
+			return toolbar;
+		},
+
+		initHtml: function(toolbar) {
+			var changeViewSelector = "a[data-wysihtml5-action='change_view']";
+			toolbar.find(changeViewSelector).click(function(e) {
+				toolbar.find('a.btn').not(changeViewSelector).toggleClass('disabled');
+			});
+		},
+
+		initInsertImage: function(toolbar) {
+			var self = this;
+			var insertImageModal = toolbar.find('.bootstrap-wysihtml5-insert-image-modal');
+			var urlInput = insertImageModal.find('.bootstrap-wysihtml5-insert-image-url');
+			var insertButton = insertImageModal.find('a.btn-primary');
+			var initialValue = urlInput.val();
+
+			var insertImage = function() { 
+				var url = urlInput.val();
+				urlInput.val(initialValue);
+				self.editor.composer.commands.exec("insertImage", url);
+			};
+			
+			urlInput.keypress(function(e) {
+				if(e.which == 13) {
+					insertImage();
+					insertImageModal.modal('hide');
+				}
+			});
+
+			insertButton.click(insertImage);
+
+			insertImageModal.on('shown', function() {
+				urlInput.focus();
+			});
+
+			insertImageModal.on('hide', function() { 
+				self.editor.currentView.element.focus();
+			});
+
+			toolbar.find('a[data-wysihtml5-command=insertImage]').click(function() {
+				insertImageModal.modal('show');
+			});
+		},
+
+		initInsertLink: function(toolbar) {
+			var self = this;
+			var insertLinkModal = toolbar.find('.bootstrap-wysihtml5-insert-link-modal');
+			var urlInput = insertLinkModal.find('.bootstrap-wysihtml5-insert-link-url');
+			var insertButton = insertLinkModal.find('a.btn-primary');
+			var initialValue = urlInput.val();
+
+			var insertLink = function() { 
+				var url = urlInput.val();
+				urlInput.val(initialValue);
+				self.editor.composer.commands.exec("createLink", { 
+					href: url, 
+					target: "_blank", 
+					rel: "nofollow" 
+				});
+			};
+			var pressedEnter = false;
+
+			urlInput.keypress(function(e) {
+				if(e.which == 13) {
+					insertLink();
+					insertLinkModal.modal('hide');
+				}
+			});
+
+			insertButton.click(insertLink);
+
+			insertLinkModal.on('shown', function() {
+				urlInput.focus();
+			});
+
+			insertLinkModal.on('hide', function() { 
+				self.editor.currentView.element.focus();
+			});
+
+			toolbar.find('a[data-wysihtml5-command=createLink]').click(function() {
+				insertLinkModal.modal('show');
+			});
+		}
+	};
+
+	$.fn.wysihtml5 = function (options) {
+		return this.each(function () {
+			var $this = $(this);
+	      	$this.data('wysihtml5', new Wysihtml5($this, options));
+	    })
+  	};
+
+  	$.fn.wysihtml5.Constructor = Wysihtml5;
+
+}(window.jQuery, window.wysihtml5);
diff --git a/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.min.js b/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.min.js
new file mode 100644
index 0000000..d736b04
--- /dev/null
+++ b/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.min.js
@@ -0,0 +1 @@
+!function(a,b){"use strict";var c={"font-styles":"<li class='dropdown'><a class='btn dropdown-toggle' data-toggle='dropdown' href='#'><i class='icon-font'></i>&nbsp;<span class='current-font'>Normal text</span>&nbsp;<b class='caret'></b></a><ul class='dropdown-menu'><li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='div'>Normal text</a></li><li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='h1'>Heading 1</a></li><li><a data-wysihtml5-command='formatBlock' data-wysihtml5-command-value='h2'>Heading 2</a></li></ul></li>",emphasis:"<li><div class='btn-group'><a class='btn' data-wysihtml5-command='bold' title='CTRL+B'>Bold</a><a class='btn' data-wysihtml5-command='italic' title='CTRL+I'>Italic</a></div></li>",lists:"<li><div class='btn-group'><a class='btn' data-wysihtml5-command='insertUnorderedList' title='Unordered List'><i class='icon-list'></i></a><a class='btn' data-wysihtml5-command='insertOrderedList' title='Ordered List'><i class='icon-th-list'></i></a><a class='btn' data-wysihtml5-command='Outdent' title='Outdent'><i class='icon-indent-right'></i></a><a class='btn' data-wysihtml5-command='Indent' title='Indent'><i class='icon-indent-left'></i></a></div></li>",link:"<li><div class='bootstrap-wysihtml5-insert-link-modal modal hide fade'><div class='modal-header'><a class='close' data-dismiss='modal'>×</a><h3>Insert Link</h3></div><div class='modal-body'><input value='http://' class='bootstrap-wysihtml5-insert-link-url input-xlarge'></div><div class='modal-footer'><a href='#' class='btn' data-dismiss='modal'>Cancel</a><a href='#' class='btn btn-primary' data-dismiss='modal'>Insert link</a></div></div><a class='btn' data-wysihtml5-command='createLink' title='Link'><i class='icon-share'></i></a></li>",image:"<li><div class='bootstrap-wysihtml5-insert-image-modal modal hide fade'><div class='modal-header'><a class='close' data-dismiss='modal'>×</a><h3>Insert Image</h3></div><div class='modal-body'><input value='http://' class='bootstrap-wysihtml5-insert-image-url input-xlarge'></div><div class='modal-footer'><a href='#' class='btn' data-dismiss='modal'>Cancel</a><a href='#' class='btn btn-primary' data-dismiss='modal'>Insert image</a></div></div><a class='btn' data-wysihtml5-command='insertImage' title='Insert image'><i class='icon-picture'></i></a></li>",html:"<li><div class='btn-group'><a class='btn' data-wysihtml5-action='change_view' title='Edit HTML'><i class='icon-pencil'></i></a></div></li>"},d={"font-styles":!0,emphasis:!0,lists:!0,html:!1,link:!0,image:!0,events:{},parserRules:{tags:{b:{},i:{},br:{},ol:{},ul:{},li:{},h1:{},h2:{},u:1,img:{check_attributes:{width:"numbers",alt:"alt",src:"url",height:"numbers"}},a:{set_attributes:{target:"_blank",rel:"nofollow"},check_attributes:{href:"url"}}}}},e=function(b,c){this.el=b,this.toolbar=this.createToolbar(b,c||d),this.editor=this.createEditor(c),window.editor=this.editor,a("iframe.wysihtml5-sandbox").each(function(b,c){a(c.contentWindow).off("focus.wysihtml5").on({"focus.wysihtml5":function(){a("li.dropdown").removeClass("open")}})})};e.prototype={constructor:e,createEditor:function(a){var c=d.parserRules;a&&a.parserRules&&(c=a.parserRules);var e=new b.Editor(this.el.attr("id"),{toolbar:this.toolbar.attr("id"),parserRules:c});if(a&&a.events)for(var f in a.events)e.on(f,a.events[f]);return e},createToolbar:function(b,e){var f=this,g=a("<ul/>",{id:b.attr("id")+"-wysihtml5-toolbar","class":"wysihtml5-toolbar",style:"display:none"});for(var h in d){var i=!1;e[h]!=undefined?e[h]==1&&(i=!0):i=d[h],i==1&&(g.append(c[h]),h=="html"&&this.initHtml(g),h=="link"&&this.initInsertLink(g),h=="image"&&this.initInsertImage(g))}var f=this;return g.find("a[data-wysihtml5-command='formatBlock']").click(function(b){var c=a(b.srcElement);f.toolbar.find(".current-font").text(c.html())}),this.el.before(g),g},initHtml:function(a){var b="a[data-wysihtml5-action='change_view']";a.find(b).click(function(c){a.find("a.btn").not(b).toggleClass("disabled")})},initInsertImage:function(a){var b=this,c=a.find(".bootstrap-wysihtml5-insert-image-modal"),d=c.find(".bootstrap-wysihtml5-insert-image-url"),e=c.find("a.btn-primary"),f=d.val(),g=function(){var a=d.val();d.val(f),b.editor.composer.commands.exec("insertImage",a)};d.keypress(function(a){a.which==13&&(g(),c.modal("hide"))}),e.click(g),c.on("shown",function(){d.focus()}),c.on("hide",function(){b.editor.currentView.element.focus()}),a.find("a[data-wysihtml5-command=insertImage]").click(function(){c.modal("show")})},initInsertLink:function(a){var b=this,c=a.find(".bootstrap-wysihtml5-insert-link-modal"),d=c.find(".bootstrap-wysihtml5-insert-link-url"),e=c.find("a.btn-primary"),f=d.val(),g=function(){var a=d.val();d.val(f),b.editor.composer.commands.exec("createLink",{href:a,target:"_blank",rel:"nofollow"})},h=!1;d.keypress(function(a){a.which==13&&(g(),c.modal("hide"))}),e.click(g),c.on("shown",function(){d.focus()}),c.on("hide",function(){b.editor.currentView.element.focus()}),a.find("a[data-wysihtml5-command=createLink]").click(function(){c.modal("show")})}},a.fn.wysihtml5=function(b){return this.each(function(){var c=a(this);c.data("wysihtml5",new e(c,b))})},a.fn.wysihtml5.Constructor=e}(window.jQuery,window.wysihtml5);
\ No newline at end of file
diff --git a/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0_rc2.js b/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0_rc2.js
new file mode 100644
index 0000000..e63330e
--- /dev/null
+++ b/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0_rc2.js
@@ -0,0 +1,9463 @@
+/**
+ * @license wysihtml5 v0.3.0_rc2
+ * https://github.com/xing/wysihtml5
+ *
+ * Author: Christopher Blum (https://github.com/tiff)
+ *
+ * Copyright (C) 2012 XING AG
+ * Licensed under the MIT license (MIT)
+ *
+ */
+var wysihtml5 = {
+  version: "0.3.0_rc2",
+  
+  // namespaces
+  commands:   {},
+  dom:        {},
+  quirks:     {},
+  toolbar:    {},
+  lang:       {},
+  selection:  {},
+  views:      {},
+  
+  INVISIBLE_SPACE: "\uFEFF",
+  
+  EMPTY_FUNCTION: function() {},
+  
+  ELEMENT_NODE: 1,
+  TEXT_NODE:    3,
+  
+  BACKSPACE_KEY:  8,
+  ENTER_KEY:      13,
+  ESCAPE_KEY:     27,
+  SPACE_KEY:      32,
+  DELETE_KEY:     46
+};/**
+ * @license Rangy, a cross-browser JavaScript range and selection library
+ * http://code.google.com/p/rangy/
+ *
+ * Copyright 2011, Tim Down
+ * Licensed under the MIT license.
+ * Version: 1.2.2
+ * Build date: 13 November 2011
+ */
+window['rangy'] = (function() {
+
+
+    var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";
+
+    var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
+        "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"];
+
+    var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
+        "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
+        "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];
+
+    var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];
+
+    // Subset of TextRange's full set of methods that we're interested in
+    var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark",
+        "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"];
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Trio of functions taken from Peter Michaux's article:
+    // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
+    function isHostMethod(o, p) {
+        var t = typeof o[p];
+        return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
+    }
+
+    function isHostObject(o, p) {
+        return !!(typeof o[p] == OBJECT && o[p]);
+    }
+
+    function isHostProperty(o, p) {
+        return typeof o[p] != UNDEFINED;
+    }
+
+    // Creates a convenience function to save verbose repeated calls to tests functions
+    function createMultiplePropertyTest(testFunc) {
+        return function(o, props) {
+            var i = props.length;
+            while (i--) {
+                if (!testFunc(o, props[i])) {
+                    return false;
+                }
+            }
+            return true;
+        };
+    }
+
+    // Next trio of functions are a convenience to save verbose repeated calls to previous two functions
+    var areHostMethods = createMultiplePropertyTest(isHostMethod);
+    var areHostObjects = createMultiplePropertyTest(isHostObject);
+    var areHostProperties = createMultiplePropertyTest(isHostProperty);
+
+    function isTextRange(range) {
+        return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
+    }
+
+    var api = {
+        version: "1.2.2",
+        initialized: false,
+        supported: true,
+
+        util: {
+            isHostMethod: isHostMethod,
+            isHostObject: isHostObject,
+            isHostProperty: isHostProperty,
+            areHostMethods: areHostMethods,
+            areHostObjects: areHostObjects,
+            areHostProperties: areHostProperties,
+            isTextRange: isTextRange
+        },
+
+        features: {},
+
+        modules: {},
+        config: {
+            alertOnWarn: false,
+            preferTextRange: false
+        }
+    };
+
+    function fail(reason) {
+        window.alert("Rangy not supported in your browser. Reason: " + reason);
+        api.initialized = true;
+        api.supported = false;
+    }
+
+    api.fail = fail;
+
+    function warn(msg) {
+        var warningMessage = "Rangy warning: " + msg;
+        if (api.config.alertOnWarn) {
+            window.alert(warningMessage);
+        } else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) {
+            window.console.log(warningMessage);
+        }
+    }
+
+    api.warn = warn;
+
+    if ({}.hasOwnProperty) {
+        api.util.extend = function(o, props) {
+            for (var i in props) {
+                if (props.hasOwnProperty(i)) {
+                    o[i] = props[i];
+                }
+            }
+        };
+    } else {
+        fail("hasOwnProperty not supported");
+    }
+
+    var initListeners = [];
+    var moduleInitializers = [];
+
+    // Initialization
+    function init() {
+        if (api.initialized) {
+            return;
+        }
+        var testRange;
+        var implementsDomRange = false, implementsTextRange = false;
+
+        // First, perform basic feature tests
+
+        if (isHostMethod(document, "createRange")) {
+            testRange = document.createRange();
+            if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
+                implementsDomRange = true;
+            }
+            testRange.detach();
+        }
+
+        var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];
+
+        if (body && isHostMethod(body, "createTextRange")) {
+            testRange = body.createTextRange();
+            if (isTextRange(testRange)) {
+                implementsTextRange = true;
+            }
+        }
+
+        if (!implementsDomRange && !implementsTextRange) {
+            fail("Neither Range nor TextRange are implemented");
+        }
+
+        api.initialized = true;
+        api.features = {
+            implementsDomRange: implementsDomRange,
+            implementsTextRange: implementsTextRange
+        };
+
+        // Initialize modules and call init listeners
+        var allListeners = moduleInitializers.concat(initListeners);
+        for (var i = 0, len = allListeners.length; i < len; ++i) {
+            try {
+                allListeners[i](api);
+            } catch (ex) {
+                if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
+                    window.console.log("Init listener threw an exception. Continuing.", ex);
+                }
+
+            }
+        }
+    }
+
+    // Allow external scripts to initialize this library in case it's loaded after the document has loaded
+    api.init = init;
+
+    // Execute listener immediately if already initialized
+    api.addInitListener = function(listener) {
+        if (api.initialized) {
+            listener(api);
+        } else {
+            initListeners.push(listener);
+        }
+    };
+
+    var createMissingNativeApiListeners = [];
+
+    api.addCreateMissingNativeApiListener = function(listener) {
+        createMissingNativeApiListeners.push(listener);
+    };
+
+    function createMissingNativeApi(win) {
+        win = win || window;
+        init();
+
+        // Notify listeners
+        for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) {
+            createMissingNativeApiListeners[i](win);
+        }
+    }
+
+    api.createMissingNativeApi = createMissingNativeApi;
+
+    /**
+     * @constructor
+     */
+    function Module(name) {
+        this.name = name;
+        this.initialized = false;
+        this.supported = false;
+    }
+
+    Module.prototype.fail = function(reason) {
+        this.initialized = true;
+        this.supported = false;
+
+        throw new Error("Module '" + this.name + "' failed to load: " + reason);
+    };
+
+    Module.prototype.warn = function(msg) {
+        api.warn("Module " + this.name + ": " + msg);
+    };
+
+    Module.prototype.createError = function(msg) {
+        return new Error("Error in Rangy " + this.name + " module: " + msg);
+    };
+
+    api.createModule = function(name, initFunc) {
+        var module = new Module(name);
+        api.modules[name] = module;
+
+        moduleInitializers.push(function(api) {
+            initFunc(api, module);
+            module.initialized = true;
+            module.supported = true;
+        });
+    };
+
+    api.requireModules = function(modules) {
+        for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) {
+            moduleName = modules[i];
+            module = api.modules[moduleName];
+            if (!module || !(module instanceof Module)) {
+                throw new Error("Module '" + moduleName + "' not found");
+            }
+            if (!module.supported) {
+                throw new Error("Module '" + moduleName + "' not supported");
+            }
+        }
+    };
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Wait for document to load before running tests
+
+    var docReady = false;
+
+    var loadHandler = function(e) {
+
+        if (!docReady) {
+            docReady = true;
+            if (!api.initialized) {
+                init();
+            }
+        }
+    };
+
+    // Test whether we have window and document objects that we will need
+    if (typeof window == UNDEFINED) {
+        fail("No window found");
+        return;
+    }
+    if (typeof document == UNDEFINED) {
+        fail("No document found");
+        return;
+    }
+
+    if (isHostMethod(document, "addEventListener")) {
+        document.addEventListener("DOMContentLoaded", loadHandler, false);
+    }
+
+    // Add a fallback in case the DOMContentLoaded event isn't supported
+    if (isHostMethod(window, "addEventListener")) {
+        window.addEventListener("load", loadHandler, false);
+    } else if (isHostMethod(window, "attachEvent")) {
+        window.attachEvent("onload", loadHandler);
+    } else {
+        fail("Window does not have required addEventListener or attachEvent method");
+    }
+
+    return api;
+})();
+rangy.createModule("DomUtil", function(api, module) {
+
+    var UNDEF = "undefined";
+    var util = api.util;
+
+    // Perform feature tests
+    if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
+        module.fail("document missing a Node creation method");
+    }
+
+    if (!util.isHostMethod(document, "getElementsByTagName")) {
+        module.fail("document missing getElementsByTagName method");
+    }
+
+    var el = document.createElement("div");
+    if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
+            !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
+        module.fail("Incomplete Element implementation");
+    }
+
+    // innerHTML is required for Range's createContextualFragment method
+    if (!util.isHostProperty(el, "innerHTML")) {
+        module.fail("Element is missing innerHTML property");
+    }
+
+    var textNode = document.createTextNode("test");
+    if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
+            !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
+            !util.areHostProperties(textNode, ["data"]))) {
+        module.fail("Incomplete Text Node implementation");
+    }
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
+    // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
+    // contains just the document as a single element and the value searched for is the document.
+    var arrayContains = /*Array.prototype.indexOf ?
+        function(arr, val) {
+            return arr.indexOf(val) > -1;
+        }:*/
+
+        function(arr, val) {
+            var i = arr.length;
+            while (i--) {
+                if (arr[i] === val) {
+                    return true;
+                }
+            }
+            return false;
+        };
+
+    // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
+    function isHtmlNamespace(node) {
+        var ns;
+        return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
+    }
+
+    function parentElement(node) {
+        var parent = node.parentNode;
+        return (parent.nodeType == 1) ? parent : null;
+    }
+
+    function getNodeIndex(node) {
+        var i = 0;
+        while( (node = node.previousSibling) ) {
+            i++;
+        }
+        return i;
+    }
+
+    function getNodeLength(node) {
+        var childNodes;
+        return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0);
+    }
+
+    function getCommonAncestor(node1, node2) {
+        var ancestors = [], n;
+        for (n = node1; n; n = n.parentNode) {
+            ancestors.push(n);
+        }
+
+        for (n = node2; n; n = n.parentNode) {
+            if (arrayContains(ancestors, n)) {
+                return n;
+            }
+        }
+
+        return null;
+    }
+
+    function isAncestorOf(ancestor, descendant, selfIsAncestor) {
+        var n = selfIsAncestor ? descendant : descendant.parentNode;
+        while (n) {
+            if (n === ancestor) {
+                return true;
+            } else {
+                n = n.parentNode;
+            }
+        }
+        return false;
+    }
+
+    function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
+        var p, n = selfIsAncestor ? node : node.parentNode;
+        while (n) {
+            p = n.parentNode;
+            if (p === ancestor) {
+                return n;
+            }
+            n = p;
+        }
+        return null;
+    }
+
+    function isCharacterDataNode(node) {
+        var t = node.nodeType;
+        return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
+    }
+
+    function insertAfter(node, precedingNode) {
+        var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
+        if (nextNode) {
+            parent.insertBefore(node, nextNode);
+        } else {
+            parent.appendChild(node);
+        }
+        return node;
+    }
+
+    // Note that we cannot use splitText() because it is bugridden in IE 9.
+    function splitDataNode(node, index) {
+        var newNode = node.cloneNode(false);
+        newNode.deleteData(0, index);
+        node.deleteData(index, node.length - index);
+        insertAfter(newNode, node);
+        return newNode;
+    }
+
+    function getDocument(node) {
+        if (node.nodeType == 9) {
+            return node;
+        } else if (typeof node.ownerDocument != UNDEF) {
+            return node.ownerDocument;
+        } else if (typeof node.document != UNDEF) {
+            return node.document;
+        } else if (node.parentNode) {
+            return getDocument(node.parentNode);
+        } else {
+            throw new Error("getDocument: no document found for node");
+        }
+    }
+
+    function getWindow(node) {
+        var doc = getDocument(node);
+        if (typeof doc.defaultView != UNDEF) {
+            return doc.defaultView;
+        } else if (typeof doc.parentWindow != UNDEF) {
+            return doc.parentWindow;
+        } else {
+            throw new Error("Cannot get a window object for node");
+        }
+    }
+
+    function getIframeDocument(iframeEl) {
+        if (typeof iframeEl.contentDocument != UNDEF) {
+            return iframeEl.contentDocument;
+        } else if (typeof iframeEl.contentWindow != UNDEF) {
+            return iframeEl.contentWindow.document;
+        } else {
+            throw new Error("getIframeWindow: No Document object found for iframe element");
+        }
+    }
+
+    function getIframeWindow(iframeEl) {
+        if (typeof iframeEl.contentWindow != UNDEF) {
+            return iframeEl.contentWindow;
+        } else if (typeof iframeEl.contentDocument != UNDEF) {
+            return iframeEl.contentDocument.defaultView;
+        } else {
+            throw new Error("getIframeWindow: No Window object found for iframe element");
+        }
+    }
+
+    function getBody(doc) {
+        return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
+    }
+
+    function getRootContainer(node) {
+        var parent;
+        while ( (parent = node.parentNode) ) {
+            node = parent;
+        }
+        return node;
+    }
+
+    function comparePoints(nodeA, offsetA, nodeB, offsetB) {
+        // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
+        var nodeC, root, childA, childB, n;
+        if (nodeA == nodeB) {
+
+            // Case 1: nodes are the same
+            return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
+        } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
+
+            // Case 2: node C (container B or an ancestor) is a child node of A
+            return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
+        } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
+
+            // Case 3: node C (container A or an ancestor) is a child node of B
+            return getNodeIndex(nodeC) < offsetB  ? -1 : 1;
+        } else {
+
+            // Case 4: containers are siblings or descendants of siblings
+            root = getCommonAncestor(nodeA, nodeB);
+            childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
+            childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
+
+            if (childA === childB) {
+                // This shouldn't be possible
+
+                throw new Error("comparePoints got to case 4 and childA and childB are the same!");
+            } else {
+                n = root.firstChild;
+                while (n) {
+                    if (n === childA) {
+                        return -1;
+                    } else if (n === childB) {
+                        return 1;
+                    }
+                    n = n.nextSibling;
+                }
+                throw new Error("Should not be here!");
+            }
+        }
+    }
+
+    function fragmentFromNodeChildren(node) {
+        var fragment = getDocument(node).createDocumentFragment(), child;
+        while ( (child = node.firstChild) ) {
+            fragment.appendChild(child);
+        }
+        return fragment;
+    }
+
+    function inspectNode(node) {
+        if (!node) {
+            return "[No node]";
+        }
+        if (isCharacterDataNode(node)) {
+            return '"' + node.data + '"';
+        } else if (node.nodeType == 1) {
+            var idAttr = node.id ? ' id="' + node.id + '"' : "";
+            return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]";
+        } else {
+            return node.nodeName;
+        }
+    }
+
+    /**
+     * @constructor
+     */
+    function NodeIterator(root) {
+        this.root = root;
+        this._next = root;
+    }
+
+    NodeIterator.prototype = {
+        _current: null,
+
+        hasNext: function() {
+            return !!this._next;
+        },
+
+        next: function() {
+            var n = this._current = this._next;
+            var child, next;
+            if (this._current) {
+                child = n.firstChild;
+                if (child) {
+                    this._next = child;
+                } else {
+                    next = null;
+                    while ((n !== this.root) && !(next = n.nextSibling)) {
+                        n = n.parentNode;
+                    }
+                    this._next = next;
+                }
+            }
+            return this._current;
+        },
+
+        detach: function() {
+            this._current = this._next = this.root = null;
+        }
+    };
+
+    function createIterator(root) {
+        return new NodeIterator(root);
+    }
+
+    /**
+     * @constructor
+     */
+    function DomPosition(node, offset) {
+        this.node = node;
+        this.offset = offset;
+    }
+
+    DomPosition.prototype = {
+        equals: function(pos) {
+            return this.node === pos.node & this.offset == pos.offset;
+        },
+
+        inspect: function() {
+            return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
+        }
+    };
+
+    /**
+     * @constructor
+     */
+    function DOMException(codeName) {
+        this.code = this[codeName];
+        this.codeName = codeName;
+        this.message = "DOMException: " + this.codeName;
+    }
+
+    DOMException.prototype = {
+        INDEX_SIZE_ERR: 1,
+        HIERARCHY_REQUEST_ERR: 3,
+        WRONG_DOCUMENT_ERR: 4,
+        NO_MODIFICATION_ALLOWED_ERR: 7,
+        NOT_FOUND_ERR: 8,
+        NOT_SUPPORTED_ERR: 9,
+        INVALID_STATE_ERR: 11
+    };
+
+    DOMException.prototype.toString = function() {
+        return this.message;
+    };
+
+    api.dom = {
+        arrayContains: arrayContains,
+        isHtmlNamespace: isHtmlNamespace,
+        parentElement: parentElement,
+        getNodeIndex: getNodeIndex,
+        getNodeLength: getNodeLength,
+        getCommonAncestor: getCommonAncestor,
+        isAncestorOf: isAncestorOf,
+        getClosestAncestorIn: getClosestAncestorIn,
+        isCharacterDataNode: isCharacterDataNode,
+        insertAfter: insertAfter,
+        splitDataNode: splitDataNode,
+        getDocument: getDocument,
+        getWindow: getWindow,
+        getIframeWindow: getIframeWindow,
+        getIframeDocument: getIframeDocument,
+        getBody: getBody,
+        getRootContainer: getRootContainer,
+        comparePoints: comparePoints,
+        inspectNode: inspectNode,
+        fragmentFromNodeChildren: fragmentFromNodeChildren,
+        createIterator: createIterator,
+        DomPosition: DomPosition
+    };
+
+    api.DOMException = DOMException;
+});rangy.createModule("DomRange", function(api, module) {
+    api.requireModules( ["DomUtil"] );
+
+
+    var dom = api.dom;
+    var DomPosition = dom.DomPosition;
+    var DOMException = api.DOMException;
+    
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Utility functions
+
+    function isNonTextPartiallySelected(node, range) {
+        return (node.nodeType != 3) &&
+               (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true));
+    }
+
+    function getRangeDocument(range) {
+        return dom.getDocument(range.startContainer);
+    }
+
+    function dispatchEvent(range, type, args) {
+        var listeners = range._listeners[type];
+        if (listeners) {
+            for (var i = 0, len = listeners.length; i < len; ++i) {
+                listeners[i].call(range, {target: range, args: args});
+            }
+        }
+    }
+
+    function getBoundaryBeforeNode(node) {
+        return new DomPosition(node.parentNode, dom.getNodeIndex(node));
+    }
+
+    function getBoundaryAfterNode(node) {
+        return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1);
+    }
+
+    function insertNodeAtPosition(node, n, o) {
+        var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
+        if (dom.isCharacterDataNode(n)) {
+            if (o == n.length) {
+                dom.insertAfter(node, n);
+            } else {
+                n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o));
+            }
+        } else if (o >= n.childNodes.length) {
+            n.appendChild(node);
+        } else {
+            n.insertBefore(node, n.childNodes[o]);
+        }
+        return firstNodeInserted;
+    }
+
+    function cloneSubtree(iterator) {
+        var partiallySelected;
+        for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
+            partiallySelected = iterator.isPartiallySelectedSubtree();
+
+            node = node.cloneNode(!partiallySelected);
+            if (partiallySelected) {
+                subIterator = iterator.getSubtreeIterator();
+                node.appendChild(cloneSubtree(subIterator));
+                subIterator.detach(true);
+            }
+
+            if (node.nodeType == 10) { // DocumentType
+                throw new DOMException("HIERARCHY_REQUEST_ERR");
+            }
+            frag.appendChild(node);
+        }
+        return frag;
+    }
+
+    function iterateSubtree(rangeIterator, func, iteratorState) {
+        var it, n;
+        iteratorState = iteratorState || { stop: false };
+        for (var node, subRangeIterator; node = rangeIterator.next(); ) {
+            //log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node));
+            if (rangeIterator.isPartiallySelectedSubtree()) {
+                // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the
+                // node selected by the Range.
+                if (func(node) === false) {
+                    iteratorState.stop = true;
+                    return;
+                } else {
+                    subRangeIterator = rangeIterator.getSubtreeIterator();
+                    iterateSubtree(subRangeIterator, func, iteratorState);
+                    subRangeIterator.detach(true);
+                    if (iteratorState.stop) {
+                        return;
+                    }
+                }
+            } else {
+                // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
+                // descendant
+                it = dom.createIterator(node);
+                while ( (n = it.next()) ) {
+                    if (func(n) === false) {
+                        iteratorState.stop = true;
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    function deleteSubtree(iterator) {
+        var subIterator;
+        while (iterator.next()) {
+            if (iterator.isPartiallySelectedSubtree()) {
+                subIterator = iterator.getSubtreeIterator();
+                deleteSubtree(subIterator);
+                subIterator.detach(true);
+            } else {
+                iterator.remove();
+            }
+        }
+    }
+
+    function extractSubtree(iterator) {
+
+        for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
+
+
+            if (iterator.isPartiallySelectedSubtree()) {
+                node = node.cloneNode(false);
+                subIterator = iterator.getSubtreeIterator();
+                node.appendChild(extractSubtree(subIterator));
+                subIterator.detach(true);
+            } else {
+                iterator.remove();
+            }
+            if (node.nodeType == 10) { // DocumentType
+                throw new DOMException("HIERARCHY_REQUEST_ERR");
+            }
+            frag.appendChild(node);
+        }
+        return frag;
+    }
+
+    function getNodesInRange(range, nodeTypes, filter) {
+        //log.info("getNodesInRange, " + nodeTypes.join(","));
+        var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
+        var filterExists = !!filter;
+        if (filterNodeTypes) {
+            regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
+        }
+
+        var nodes = [];
+        iterateSubtree(new RangeIterator(range, false), function(node) {
+            if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) {
+                nodes.push(node);
+            }
+        });
+        return nodes;
+    }
+
+    function inspect(range) {
+        var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
+        return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
+                dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
+    }
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
+
+    /**
+     * @constructor
+     */
+    function RangeIterator(range, clonePartiallySelectedTextNodes) {
+        this.range = range;
+        this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
+
+
+
+        if (!range.collapsed) {
+            this.sc = range.startContainer;
+            this.so = range.startOffset;
+            this.ec = range.endContainer;
+            this.eo = range.endOffset;
+            var root = range.commonAncestorContainer;
+
+            if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) {
+                this.isSingleCharacterDataNode = true;
+                this._first = this._last = this._next = this.sc;
+            } else {
+                this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ?
+                    this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true);
+                this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ?
+                    this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true);
+            }
+
+        }
+    }
+
+    RangeIterator.prototype = {
+        _current: null,
+        _next: null,
+        _first: null,
+        _last: null,
+        isSingleCharacterDataNode: false,
+
+        reset: function() {
+            this._current = null;
+            this._next = this._first;
+        },
+
+        hasNext: function() {
+            return !!this._next;
+        },
+
+        next: function() {
+            // Move to next node
+            var current = this._current = this._next;
+            if (current) {
+                this._next = (current !== this._last) ? current.nextSibling : null;
+
+                // Check for partially selected text nodes
+                if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
+                    if (current === this.ec) {
+
+                        (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
+                    }
+                    if (this._current === this.sc) {
+
+                        (current = current.cloneNode(true)).deleteData(0, this.so);
+                    }
+                }
+            }
+
+            return current;
+        },
+
+        remove: function() {
+            var current = this._current, start, end;
+
+            if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
+                start = (current === this.sc) ? this.so : 0;
+                end = (current === this.ec) ? this.eo : current.length;
+                if (start != end) {
+                    current.deleteData(start, end - start);
+                }
+            } else {
+                if (current.parentNode) {
+                    current.parentNode.removeChild(current);
+                } else {
+
+                }
+            }
+        },
+
+        // Checks if the current node is partially selected
+        isPartiallySelectedSubtree: function() {
+            var current = this._current;
+            return isNonTextPartiallySelected(current, this.range);
+        },
+
+        getSubtreeIterator: function() {
+            var subRange;
+            if (this.isSingleCharacterDataNode) {
+                subRange = this.range.cloneRange();
+                subRange.collapse();
+            } else {
+                subRange = new Range(getRangeDocument(this.range));
+                var current = this._current;
+                var startContainer = current, startOffset = 0, endContainer = current, endOffset = dom.getNodeLength(current);
+
+                if (dom.isAncestorOf(current, this.sc, true)) {
+                    startContainer = this.sc;
+                    startOffset = this.so;
+                }
+                if (dom.isAncestorOf(current, this.ec, true)) {
+                    endContainer = this.ec;
+                    endOffset = this.eo;
+                }
+
+                updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
+            }
+            return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
+        },
+
+        detach: function(detachRange) {
+            if (detachRange) {
+                this.range.detach();
+            }
+            this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
+        }
+    };
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Exceptions
+
+    /**
+     * @constructor
+     */
+    function RangeException(codeName) {
+        this.code = this[codeName];
+        this.codeName = codeName;
+        this.message = "RangeException: " + this.codeName;
+    }
+
+    RangeException.prototype = {
+        BAD_BOUNDARYPOINTS_ERR: 1,
+        INVALID_NODE_TYPE_ERR: 2
+    };
+
+    RangeException.prototype.toString = function() {
+        return this.message;
+    };
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    /**
+     * Currently iterates through all nodes in the range on creation until I think of a decent way to do it
+     * TODO: Look into making this a proper iterator, not requiring preloading everything first
+     * @constructor
+     */
+    function RangeNodeIterator(range, nodeTypes, filter) {
+        this.nodes = getNodesInRange(range, nodeTypes, filter);
+        this._next = this.nodes[0];
+        this._position = 0;
+    }
+
+    RangeNodeIterator.prototype = {
+        _current: null,
+
+        hasNext: function() {
+            return !!this._next;
+        },
+
+        next: function() {
+            this._current = this._next;
+            this._next = this.nodes[ ++this._position ];
+            return this._current;
+        },
+
+        detach: function() {
+            this._current = this._next = this.nodes = null;
+        }
+    };
+
+    var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
+    var rootContainerNodeTypes = [2, 9, 11];
+    var readonlyNodeTypes = [5, 6, 10, 12];
+    var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
+    var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
+
+    function createAncestorFinder(nodeTypes) {
+        return function(node, selfIsAncestor) {
+            var t, n = selfIsAncestor ? node : node.parentNode;
+            while (n) {
+                t = n.nodeType;
+                if (dom.arrayContains(nodeTypes, t)) {
+                    return n;
+                }
+                n = n.parentNode;
+            }
+            return null;
+        };
+    }
+
+    var getRootContainer = dom.getRootContainer;
+    var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
+    var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
+    var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
+
+    function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
+        if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
+            throw new RangeException("INVALID_NODE_TYPE_ERR");
+        }
+    }
+
+    function assertNotDetached(range) {
+        if (!range.startContainer) {
+            throw new DOMException("INVALID_STATE_ERR");
+        }
+    }
+
+    function assertValidNodeType(node, invalidTypes) {
+        if (!dom.arrayContains(invalidTypes, node.nodeType)) {
+            throw new RangeException("INVALID_NODE_TYPE_ERR");
+        }
+    }
+
+    function assertValidOffset(node, offset) {
+        if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
+            throw new DOMException("INDEX_SIZE_ERR");
+        }
+    }
+
+    function assertSameDocumentOrFragment(node1, node2) {
+        if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
+            throw new DOMException("WRONG_DOCUMENT_ERR");
+        }
+    }
+
+    function assertNodeNotReadOnly(node) {
+        if (getReadonlyAncestor(node, true)) {
+            throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
+        }
+    }
+
+    function assertNode(node, codeName) {
+        if (!node) {
+            throw new DOMException(codeName);
+        }
+    }
+
+    function isOrphan(node) {
+        return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
+    }
+
+    function isValidOffset(node, offset) {
+        return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length);
+    }
+
+    function assertRangeValid(range) {
+        assertNotDetached(range);
+        if (isOrphan(range.startContainer) || isOrphan(range.endContainer) ||
+                !isValidOffset(range.startContainer, range.startOffset) ||
+                !isValidOffset(range.endContainer, range.endOffset)) {
+            throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
+        }
+    }
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Test the browser's innerHTML support to decide how to implement createContextualFragment
+    var styleEl = document.createElement("style");
+    var htmlParsingConforms = false;
+    try {
+        styleEl.innerHTML = "<b>x</b>";
+        htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
+    } catch (e) {
+        // IE 6 and 7 throw
+    }
+
+    api.features.htmlParsingConforms = htmlParsingConforms;
+
+    var createContextualFragment = htmlParsingConforms ?
+
+        // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
+        // discussion and base code for this implementation at issue 67.
+        // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
+        // Thanks to Aleks Williams.
+        function(fragmentStr) {
+            // "Let node the context object's start's node."
+            var node = this.startContainer;
+            var doc = dom.getDocument(node);
+
+            // "If the context object's start's node is null, raise an INVALID_STATE_ERR
+            // exception and abort these steps."
+            if (!node) {
+                throw new DOMException("INVALID_STATE_ERR");
+            }
+
+            // "Let element be as follows, depending on node's interface:"
+            // Document, Document Fragment: null
+            var el = null;
+
+            // "Element: node"
+            if (node.nodeType == 1) {
+                el = node;
+
+            // "Text, Comment: node's parentElement"
+            } else if (dom.isCharacterDataNode(node)) {
+                el = dom.parentElement(node);
+            }
+
+            // "If either element is null or element's ownerDocument is an HTML document
+            // and element's local name is "html" and element's namespace is the HTML
+            // namespace"
+            if (el === null || (
+                el.nodeName == "HTML"
+                && dom.isHtmlNamespace(dom.getDocument(el).documentElement)
+                && dom.isHtmlNamespace(el)
+            )) {
+
+            // "let element be a new Element with "body" as its local name and the HTML
+            // namespace as its namespace.""
+                el = doc.createElement("body");
+            } else {
+                el = el.cloneNode(false);
+            }
+
+            // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
+            // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
+            // "In either case, the algorithm must be invoked with fragment as the input
+            // and element as the context element."
+            el.innerHTML = fragmentStr;
+
+            // "If this raises an exception, then abort these steps. Otherwise, let new
+            // children be the nodes returned."
+
+            // "Let fragment be a new DocumentFragment."
+            // "Append all new children to fragment."
+            // "Return fragment."
+            return dom.fragmentFromNodeChildren(el);
+        } :
+
+        // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
+        // previous versions of Rangy used (with the exception of using a body element rather than a div)
+        function(fragmentStr) {
+            assertNotDetached(this);
+            var doc = getRangeDocument(this);
+            var el = doc.createElement("body");
+            el.innerHTML = fragmentStr;
+
+            return dom.fragmentFromNodeChildren(el);
+        };
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
+        "commonAncestorContainer"];
+
+    var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
+    var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
+
+    function RangePrototype() {}
+
+    RangePrototype.prototype = {
+        attachListener: function(type, listener) {
+            this._listeners[type].push(listener);
+        },
+
+        compareBoundaryPoints: function(how, range) {
+            assertRangeValid(this);
+            assertSameDocumentOrFragment(this.startContainer, range.startContainer);
+
+            var nodeA, offsetA, nodeB, offsetB;
+            var prefixA = (how == e2s || how == s2s) ? "start" : "end";
+            var prefixB = (how == s2e || how == s2s) ? "start" : "end";
+            nodeA = this[prefixA + "Container"];
+            offsetA = this[prefixA + "Offset"];
+            nodeB = range[prefixB + "Container"];
+            offsetB = range[prefixB + "Offset"];
+            return dom.comparePoints(nodeA, offsetA, nodeB, offsetB);
+        },
+
+        insertNode: function(node) {
+            assertRangeValid(this);
+            assertValidNodeType(node, insertableNodeTypes);
+            assertNodeNotReadOnly(this.startContainer);
+
+            if (dom.isAncestorOf(node, this.startContainer, true)) {
+                throw new DOMException("HIERARCHY_REQUEST_ERR");
+            }
+
+            // No check for whether the container of the start of the Range is of a type that does not allow
+            // children of the type of node: the browser's DOM implementation should do this for us when we attempt
+            // to add the node
+
+            var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
+            this.setStartBefore(firstNodeInserted);
+        },
+
+        cloneContents: function() {
+            assertRangeValid(this);
+
+            var clone, frag;
+            if (this.collapsed) {
+                return getRangeDocument(this).createDocumentFragment();
+            } else {
+                if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) {
+                    clone = this.startContainer.cloneNode(true);
+                    clone.data = clone.data.slice(this.startOffset, this.endOffset);
+                    frag = getRangeDocument(this).createDocumentFragment();
+                    frag.appendChild(clone);
+                    return frag;
+                } else {
+                    var iterator = new RangeIterator(this, true);
+                    clone = cloneSubtree(iterator);
+                    iterator.detach();
+                }
+                return clone;
+            }
+        },
+
+        canSurroundContents: function() {
+            assertRangeValid(this);
+            assertNodeNotReadOnly(this.startContainer);
+            assertNodeNotReadOnly(this.endContainer);
+
+            // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
+            // no non-text nodes.
+            var iterator = new RangeIterator(this, true);
+            var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
+                    (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
+            iterator.detach();
+            return !boundariesInvalid;
+        },
+
+        surroundContents: function(node) {
+            assertValidNodeType(node, surroundNodeTypes);
+
+            if (!this.canSurroundContents()) {
+                throw new RangeException("BAD_BOUNDARYPOINTS_ERR");
+            }
+
+            // Extract the contents
+            var content = this.extractContents();
+
+            // Clear the children of the node
+            if (node.hasChildNodes()) {
+                while (node.lastChild) {
+                    node.removeChild(node.lastChild);
+                }
+            }
+
+            // Insert the new node and add the extracted contents
+            insertNodeAtPosition(node, this.startContainer, this.startOffset);
+            node.appendChild(content);
+
+            this.selectNode(node);
+        },
+
+        cloneRange: function() {
+            assertRangeValid(this);
+            var range = new Range(getRangeDocument(this));
+            var i = rangeProperties.length, prop;
+            while (i--) {
+                prop = rangeProperties[i];
+                range[prop] = this[prop];
+            }
+            return range;
+        },
+
+        toString: function() {
+            assertRangeValid(this);
+            var sc = this.startContainer;
+            if (sc === this.endContainer && dom.isCharacterDataNode(sc)) {
+                return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
+            } else {
+                var textBits = [], iterator = new RangeIterator(this, true);
+
+                iterateSubtree(iterator, function(node) {
+                    // Accept only text or CDATA nodes, not comments
+
+                    if (node.nodeType == 3 || node.nodeType == 4) {
+                        textBits.push(node.data);
+                    }
+                });
+                iterator.detach();
+                return textBits.join("");
+            }
+        },
+
+        // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
+        // been removed from Mozilla.
+
+        compareNode: function(node) {
+            assertRangeValid(this);
+
+            var parent = node.parentNode;
+            var nodeIndex = dom.getNodeIndex(node);
+
+            if (!parent) {
+                throw new DOMException("NOT_FOUND_ERR");
+            }
+
+            var startComparison = this.comparePoint(parent, nodeIndex),
+                endComparison = this.comparePoint(parent, nodeIndex + 1);
+
+            if (startComparison < 0) { // Node starts before
+                return (endComparison > 0) ? n_b_a : n_b;
+            } else {
+                return (endComparison > 0) ? n_a : n_i;
+            }
+        },
+
+        comparePoint: function(node, offset) {
+            assertRangeValid(this);
+            assertNode(node, "HIERARCHY_REQUEST_ERR");
+            assertSameDocumentOrFragment(node, this.startContainer);
+
+            if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
+                return -1;
+            } else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
+                return 1;
+            }
+            return 0;
+        },
+
+        createContextualFragment: createContextualFragment,
+
+        toHtml: function() {
+            assertRangeValid(this);
+            var container = getRangeDocument(this).createElement("div");
+            container.appendChild(this.cloneContents());
+            return container.innerHTML;
+        },
+
+        // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
+        // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
+        intersectsNode: function(node, touchingIsIntersecting) {
+            assertRangeValid(this);
+            assertNode(node, "NOT_FOUND_ERR");
+            if (dom.getDocument(node) !== getRangeDocument(this)) {
+                return false;
+            }
+
+            var parent = node.parentNode, offset = dom.getNodeIndex(node);
+            assertNode(parent, "NOT_FOUND_ERR");
+
+            var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset),
+                endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
+
+            return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
+        },
+
+
+        isPointInRange: function(node, offset) {
+            assertRangeValid(this);
+            assertNode(node, "HIERARCHY_REQUEST_ERR");
+            assertSameDocumentOrFragment(node, this.startContainer);
+
+            return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
+                   (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
+        },
+
+        // The methods below are non-standard and invented by me.
+
+        // Sharing a boundary start-to-end or end-to-start does not count as intersection.
+        intersectsRange: function(range, touchingIsIntersecting) {
+            assertRangeValid(this);
+
+            if (getRangeDocument(range) != getRangeDocument(this)) {
+                throw new DOMException("WRONG_DOCUMENT_ERR");
+            }
+
+            var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset),
+                endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset);
+
+            return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
+        },
+
+        intersection: function(range) {
+            if (this.intersectsRange(range)) {
+                var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
+                    endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
+
+                var intersectionRange = this.cloneRange();
+
+                if (startComparison == -1) {
+                    intersectionRange.setStart(range.startContainer, range.startOffset);
+                }
+                if (endComparison == 1) {
+                    intersectionRange.setEnd(range.endContainer, range.endOffset);
+                }
+                return intersectionRange;
+            }
+            return null;
+        },
+
+        union: function(range) {
+            if (this.intersectsRange(range, true)) {
+                var unionRange = this.cloneRange();
+                if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
+                    unionRange.setStart(range.startContainer, range.startOffset);
+                }
+                if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
+                    unionRange.setEnd(range.endContainer, range.endOffset);
+                }
+                return unionRange;
+            } else {
+                throw new RangeException("Ranges do not intersect");
+            }
+        },
+
+        containsNode: function(node, allowPartial) {
+            if (allowPartial) {
+                return this.intersectsNode(node, false);
+            } else {
+                return this.compareNode(node) == n_i;
+            }
+        },
+
+        containsNodeContents: function(node) {
+            return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0;
+        },
+
+        containsRange: function(range) {
+            return this.intersection(range).equals(range);
+        },
+
+        containsNodeText: function(node) {
+            var nodeRange = this.cloneRange();
+            nodeRange.selectNode(node);
+            var textNodes = nodeRange.getNodes([3]);
+            if (textNodes.length > 0) {
+                nodeRange.setStart(textNodes[0], 0);
+                var lastTextNode = textNodes.pop();
+                nodeRange.setEnd(lastTextNode, lastTextNode.length);
+                var contains = this.containsRange(nodeRange);
+                nodeRange.detach();
+                return contains;
+            } else {
+                return this.containsNodeContents(node);
+            }
+        },
+
+        createNodeIterator: function(nodeTypes, filter) {
+            assertRangeValid(this);
+            return new RangeNodeIterator(this, nodeTypes, filter);
+        },
+
+        getNodes: function(nodeTypes, filter) {
+            assertRangeValid(this);
+            return getNodesInRange(this, nodeTypes, filter);
+        },
+
+        getDocument: function() {
+            return getRangeDocument(this);
+        },
+
+        collapseBefore: function(node) {
+            assertNotDetached(this);
+
+            this.setEndBefore(node);
+            this.collapse(false);
+        },
+
+        collapseAfter: function(node) {
+            assertNotDetached(this);
+
+            this.setStartAfter(node);
+            this.collapse(true);
+        },
+
+        getName: function() {
+            return "DomRange";
+        },
+
+        equals: function(range) {
+            return Range.rangesEqual(this, range);
+        },
+
+        inspect: function() {
+            return inspect(this);
+        }
+    };
+
+    function copyComparisonConstantsToObject(obj) {
+        obj.START_TO_START = s2s;
+        obj.START_TO_END = s2e;
+        obj.END_TO_END = e2e;
+        obj.END_TO_START = e2s;
+
+        obj.NODE_BEFORE = n_b;
+        obj.NODE_AFTER = n_a;
+        obj.NODE_BEFORE_AND_AFTER = n_b_a;
+        obj.NODE_INSIDE = n_i;
+    }
+
+    function copyComparisonConstants(constructor) {
+        copyComparisonConstantsToObject(constructor);
+        copyComparisonConstantsToObject(constructor.prototype);
+    }
+
+    function createRangeContentRemover(remover, boundaryUpdater) {
+        return function() {
+            assertRangeValid(this);
+
+            var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
+
+            var iterator = new RangeIterator(this, true);
+
+            // Work out where to position the range after content removal
+            var node, boundary;
+            if (sc !== root) {
+                node = dom.getClosestAncestorIn(sc, root, true);
+                boundary = getBoundaryAfterNode(node);
+                sc = boundary.node;
+                so = boundary.offset;
+            }
+
+            // Check none of the range is read-only
+            iterateSubtree(iterator, assertNodeNotReadOnly);
+
+            iterator.reset();
+
+            // Remove the content
+            var returnValue = remover(iterator);
+            iterator.detach();
+
+            // Move to the new position
+            boundaryUpdater(this, sc, so, sc, so);
+
+            return returnValue;
+        };
+    }
+
+    function createPrototypeRange(constructor, boundaryUpdater, detacher) {
+        function createBeforeAfterNodeSetter(isBefore, isStart) {
+            return function(node) {
+                assertNotDetached(this);
+                assertValidNodeType(node, beforeAfterNodeTypes);
+                assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
+
+                var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
+                (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
+            };
+        }
+
+        function setRangeStart(range, node, offset) {
+            var ec = range.endContainer, eo = range.endOffset;
+            if (node !== range.startContainer || offset !== range.startOffset) {
+                // Check the root containers of the range and the new boundary, and also check whether the new boundary
+                // is after the current end. In either case, collapse the range to the new position
+                if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) {
+                    ec = node;
+                    eo = offset;
+                }
+                boundaryUpdater(range, node, offset, ec, eo);
+            }
+        }
+
+        function setRangeEnd(range, node, offset) {
+            var sc = range.startContainer, so = range.startOffset;
+            if (node !== range.endContainer || offset !== range.endOffset) {
+                // Check the root containers of the range and the new boundary, and also check whether the new boundary
+                // is after the current end. In either case, collapse the range to the new position
+                if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) {
+                    sc = node;
+                    so = offset;
+                }
+                boundaryUpdater(range, sc, so, node, offset);
+            }
+        }
+
+        function setRangeStartAndEnd(range, node, offset) {
+            if (node !== range.startContainer || offset !== range.startOffset || node !== range.endContainer || offset !== range.endOffset) {
+                boundaryUpdater(range, node, offset, node, offset);
+            }
+        }
+
+        constructor.prototype = new RangePrototype();
+
+        api.util.extend(constructor.prototype, {
+            setStart: function(node, offset) {
+                assertNotDetached(this);
+                assertNoDocTypeNotationEntityAncestor(node, true);
+                assertValidOffset(node, offset);
+
+                setRangeStart(this, node, offset);
+            },
+
+            setEnd: function(node, offset) {
+                assertNotDetached(this);
+                assertNoDocTypeNotationEntityAncestor(node, true);
+                assertValidOffset(node, offset);
+
+                setRangeEnd(this, node, offset);
+            },
+
+            setStartBefore: createBeforeAfterNodeSetter(true, true),
+            setStartAfter: createBeforeAfterNodeSetter(false, true),
+            setEndBefore: createBeforeAfterNodeSetter(true, false),
+            setEndAfter: createBeforeAfterNodeSetter(false, false),
+
+            collapse: function(isStart) {
+                assertRangeValid(this);
+                if (isStart) {
+                    boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
+                } else {
+                    boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
+                }
+            },
+
+            selectNodeContents: function(node) {
+                // This doesn't seem well specified: the spec talks only about selecting the node's contents, which
+                // could be taken to mean only its children. However, browsers implement this the same as selectNode for
+                // text nodes, so I shall do likewise
+                assertNotDetached(this);
+                assertNoDocTypeNotationEntityAncestor(node, true);
+
+                boundaryUpdater(this, node, 0, node, dom.getNodeLength(node));
+            },
+
+            selectNode: function(node) {
+                assertNotDetached(this);
+                assertNoDocTypeNotationEntityAncestor(node, false);
+                assertValidNodeType(node, beforeAfterNodeTypes);
+
+                var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
+                boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
+            },
+
+            extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
+
+            deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
+
+            canSurroundContents: function() {
+                assertRangeValid(this);
+                assertNodeNotReadOnly(this.startContainer);
+                assertNodeNotReadOnly(this.endContainer);
+
+                // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
+                // no non-text nodes.
+                var iterator = new RangeIterator(this, true);
+                var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
+                        (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
+                iterator.detach();
+                return !boundariesInvalid;
+            },
+
+            detach: function() {
+                detacher(this);
+            },
+
+            splitBoundaries: function() {
+                assertRangeValid(this);
+
+
+                var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
+                var startEndSame = (sc === ec);
+
+                if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
+                    dom.splitDataNode(ec, eo);
+
+                }
+
+                if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) {
+
+                    sc = dom.splitDataNode(sc, so);
+                    if (startEndSame) {
+                        eo -= so;
+                        ec = sc;
+                    } else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) {
+                        eo++;
+                    }
+                    so = 0;
+
+                }
+                boundaryUpdater(this, sc, so, ec, eo);
+            },
+
+            normalizeBoundaries: function() {
+                assertRangeValid(this);
+
+                var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
+
+                var mergeForward = function(node) {
+                    var sibling = node.nextSibling;
+                    if (sibling && sibling.nodeType == node.nodeType) {
+                        ec = node;
+                        eo = node.length;
+                        node.appendData(sibling.data);
+                        sibling.parentNode.removeChild(sibling);
+                    }
+                };
+
+                var mergeBackward = function(node) {
+                    var sibling = node.previousSibling;
+                    if (sibling && sibling.nodeType == node.nodeType) {
+                        sc = node;
+                        var nodeLength = node.length;
+                        so = sibling.length;
+                        node.insertData(0, sibling.data);
+                        sibling.parentNode.removeChild(sibling);
+                        if (sc == ec) {
+                            eo += so;
+                            ec = sc;
+                        } else if (ec == node.parentNode) {
+                            var nodeIndex = dom.getNodeIndex(node);
+                            if (eo == nodeIndex) {
+                                ec = node;
+                                eo = nodeLength;
+                            } else if (eo > nodeIndex) {
+                                eo--;
+                            }
+                        }
+                    }
+                };
+
+                var normalizeStart = true;
+
+                if (dom.isCharacterDataNode(ec)) {
+                    if (ec.length == eo) {
+                        mergeForward(ec);
+                    }
+                } else {
+                    if (eo > 0) {
+                        var endNode = ec.childNodes[eo - 1];
+                        if (endNode && dom.isCharacterDataNode(endNode)) {
+                            mergeForward(endNode);
+                        }
+                    }
+                    normalizeStart = !this.collapsed;
+                }
+
+                if (normalizeStart) {
+                    if (dom.isCharacterDataNode(sc)) {
+                        if (so == 0) {
+                            mergeBackward(sc);
+                        }
+                    } else {
+                        if (so < sc.childNodes.length) {
+                            var startNode = sc.childNodes[so];
+                            if (startNode && dom.isCharacterDataNode(startNode)) {
+                                mergeBackward(startNode);
+                            }
+                        }
+                    }
+                } else {
+                    sc = ec;
+                    so = eo;
+                }
+
+                boundaryUpdater(this, sc, so, ec, eo);
+            },
+
+            collapseToPoint: function(node, offset) {
+                assertNotDetached(this);
+
+                assertNoDocTypeNotationEntityAncestor(node, true);
+                assertValidOffset(node, offset);
+
+                setRangeStartAndEnd(this, node, offset);
+            }
+        });
+
+        copyComparisonConstants(constructor);
+    }
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Updates commonAncestorContainer and collapsed after boundary change
+    function updateCollapsedAndCommonAncestor(range) {
+        range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
+        range.commonAncestorContainer = range.collapsed ?
+            range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
+    }
+
+    function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
+        var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset);
+        var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset);
+
+        range.startContainer = startContainer;
+        range.startOffset = startOffset;
+        range.endContainer = endContainer;
+        range.endOffset = endOffset;
+
+        updateCollapsedAndCommonAncestor(range);
+        dispatchEvent(range, "boundarychange", {startMoved: startMoved, endMoved: endMoved});
+    }
+
+    function detach(range) {
+        assertNotDetached(range);
+        range.startContainer = range.startOffset = range.endContainer = range.endOffset = null;
+        range.collapsed = range.commonAncestorContainer = null;
+        dispatchEvent(range, "detach", null);
+        range._listeners = null;
+    }
+
+    /**
+     * @constructor
+     */
+    function Range(doc) {
+        this.startContainer = doc;
+        this.startOffset = 0;
+        this.endContainer = doc;
+        this.endOffset = 0;
+        this._listeners = {
+            boundarychange: [],
+            detach: []
+        };
+        updateCollapsedAndCommonAncestor(this);
+    }
+
+    createPrototypeRange(Range, updateBoundaries, detach);
+
+    api.rangePrototype = RangePrototype.prototype;
+
+    Range.rangeProperties = rangeProperties;
+    Range.RangeIterator = RangeIterator;
+    Range.copyComparisonConstants = copyComparisonConstants;
+    Range.createPrototypeRange = createPrototypeRange;
+    Range.inspect = inspect;
+    Range.getRangeDocument = getRangeDocument;
+    Range.rangesEqual = function(r1, r2) {
+        return r1.startContainer === r2.startContainer &&
+               r1.startOffset === r2.startOffset &&
+               r1.endContainer === r2.endContainer &&
+               r1.endOffset === r2.endOffset;
+    };
+
+    api.DomRange = Range;
+    api.RangeException = RangeException;
+});rangy.createModule("WrappedRange", function(api, module) {
+    api.requireModules( ["DomUtil", "DomRange"] );
+
+    /**
+     * @constructor
+     */
+    var WrappedRange;
+    var dom = api.dom;
+    var DomPosition = dom.DomPosition;
+    var DomRange = api.DomRange;
+
+
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    /*
+    This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
+    method. For example, in the following (where pipes denote the selection boundaries):
+
+    <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
+
+    var range = document.selection.createRange();
+    alert(range.parentElement().id); // Should alert "ul" but alerts "b"
+
+    This method returns the common ancestor node of the following:
+    - the parentElement() of the textRange
+    - the parentElement() of the textRange after calling collapse(true)
+    - the parentElement() of the textRange after calling collapse(false)
+     */
+    function getTextRangeContainerElement(textRange) {
+        var parentEl = textRange.parentElement();
+
+        var range = textRange.duplicate();
+        range.collapse(true);
+        var startEl = range.parentElement();
+        range = textRange.duplicate();
+        range.collapse(false);
+        var endEl = range.parentElement();
+        var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
+
+        return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
+    }
+
+    function textRangeIsCollapsed(textRange) {
+        return textRange.compareEndPoints("StartToEnd", textRange) == 0;
+    }
+
+    // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as
+    // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has
+    // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling
+    // for inputs and images, plus optimizations.
+    function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) {
+        var workingRange = textRange.duplicate();
+
+        workingRange.collapse(isStart);
+        var containerElement = workingRange.parentElement();
+
+        // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
+        // check for that
+        // TODO: Find out when. Workaround for wholeRangeContainerElement may break this
+        if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) {
+            containerElement = wholeRangeContainerElement;
+
+        }
+
+
+
+        // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
+        // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
+        if (!containerElement.canHaveHTML) {
+            return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
+        }
+
+        var workingNode = dom.getDocument(containerElement).createElement("span");
+        var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
+        var previousNode, nextNode, boundaryPosition, boundaryNode;
+
+        // Move the working range through the container's children, starting at the end and working backwards, until the
+        // working range reaches or goes past the boundary we're interested in
+        do {
+            containerElement.insertBefore(workingNode, workingNode.previousSibling);
+            workingRange.moveToElementText(workingNode);
+        } while ( (comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 &&
+                workingNode.previousSibling);
+
+        // We've now reached or gone past the boundary of the text range we're interested in
+        // so have identified the node we want
+        boundaryNode = workingNode.nextSibling;
+
+        if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) {
+            // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the
+            // node containing the text range's boundary, so we move the end of the working range to the boundary point
+            // and measure the length of its text to get the boundary's offset within the node.
+            workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
+
+
+            var offset;
+
+            if (/[\r\n]/.test(boundaryNode.data)) {
+                /*
+                For the particular case of a boundary within a text node containing line breaks (within a <pre> element,
+                for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:
+
+                - Each line break is represented as \r in the text node's data/nodeValue properties
+                - Each line break is represented as \r\n in the TextRange's 'text' property
+                - The 'text' property of the TextRange does not contain trailing line breaks
+
+                To get round the problem presented by the final fact above, we can use the fact that TextRange's
+                moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
+                the same as the number of characters it was instructed to move. The simplest approach is to use this to
+                store the characters moved when moving both the start and end of the range to the start of the document
+                body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
+                However, this is extremely slow when the document is large and the range is near the end of it. Clearly
+                doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
+                problem.
+
+                Another approach that works is to use moveStart() to move the start boundary of the range up to the end
+                boundary one character at a time and incrementing a counter with the value returned by the moveStart()
+                call. However, the check for whether the start boundary has reached the end boundary is expensive, so
+                this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
+                the range within the document).
+
+                The method below is a hybrid of the two methods above. It uses the fact that a string containing the
+                TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
+                text of the TextRange, so the start of the range is moved that length initially and then a character at
+                a time to make up for any trailing line breaks not contained in the 'text' property. This has good
+                performance in most situations compared to the previous two methods.
+                */
+                var tempRange = workingRange.duplicate();
+                var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
+
+                offset = tempRange.moveStart("character", rangeLength);
+                while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
+                    offset++;
+                    tempRange.moveStart("character", 1);
+                }
+            } else {
+                offset = workingRange.text.length;
+            }
+            boundaryPosition = new DomPosition(boundaryNode, offset);
+        } else {
+
+
+            // If the boundary immediately follows a character data node and this is the end boundary, we should favour
+            // a position within that, and likewise for a start boundary preceding a character data node
+            previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
+            nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
+
+
+
+            if (nextNode && dom.isCharacterDataNode(nextNode)) {
+                boundaryPosition = new DomPosition(nextNode, 0);
+            } else if (previousNode && dom.isCharacterDataNode(previousNode)) {
+                boundaryPosition = new DomPosition(previousNode, previousNode.length);
+            } else {
+                boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
+            }
+        }
+
+        // Clean up
+        workingNode.parentNode.removeChild(workingNode);
+
+        return boundaryPosition;
+    }
+
+    // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
+    // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
+    // (http://code.google.com/p/ierange/)
+    function createBoundaryTextRange(boundaryPosition, isStart) {
+        var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
+        var doc = dom.getDocument(boundaryPosition.node);
+        var workingNode, childNodes, workingRange = doc.body.createTextRange();
+        var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);
+
+        if (nodeIsDataNode) {
+            boundaryNode = boundaryPosition.node;
+            boundaryParent = boundaryNode.parentNode;
+        } else {
+            childNodes = boundaryPosition.node.childNodes;
+            boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
+            boundaryParent = boundaryPosition.node;
+        }
+
+        // Position the range immediately before the node containing the boundary
+        workingNode = doc.createElement("span");
+
+        // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
+        // element rather than immediately before or after it, which is what we want
+        workingNode.innerHTML = "&#feff;";
+
+        // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
+        // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
+        if (boundaryNode) {
+            boundaryParent.insertBefore(workingNode, boundaryNode);
+        } else {
+            boundaryParent.appendChild(workingNode);
+        }
+
+        workingRange.moveToElementText(workingNode);
+        workingRange.collapse(!isStart);
+
+        // Clean up
+        boundaryParent.removeChild(workingNode);
+
+        // Move the working range to the text offset, if required
+        if (nodeIsDataNode) {
+            workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
+        }
+
+        return workingRange;
+    }
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
+        // This is a wrapper around the browser's native DOM Range. It has two aims:
+        // - Provide workarounds for specific browser bugs
+        // - provide convenient extensions, which are inherited from Rangy's DomRange
+
+        (function() {
+            var rangeProto;
+            var rangeProperties = DomRange.rangeProperties;
+            var canSetRangeStartAfterEnd;
+
+            function updateRangeProperties(range) {
+                var i = rangeProperties.length, prop;
+                while (i--) {
+                    prop = rangeProperties[i];
+                    range[prop] = range.nativeRange[prop];
+                }
+            }
+
+            function updateNativeRange(range, startContainer, startOffset, endContainer,endOffset) {
+                var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
+                var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
+
+                // Always set both boundaries for the benefit of IE9 (see issue 35)
+                if (startMoved || endMoved) {
+                    range.setEnd(endContainer, endOffset);
+                    range.setStart(startContainer, startOffset);
+                }
+            }
+
+            function detach(range) {
+                range.nativeRange.detach();
+                range.detached = true;
+                var i = rangeProperties.length, prop;
+                while (i--) {
+                    prop = rangeProperties[i];
+                    range[prop] = null;
+                }
+            }
+
+            var createBeforeAfterNodeSetter;
+
+            WrappedRange = function(range) {
+                if (!range) {
+                    throw new Error("Range must be specified");
+                }
+                this.nativeRange = range;
+                updateRangeProperties(this);
+            };
+
+            DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);
+
+            rangeProto = WrappedRange.prototype;
+
+            rangeProto.selectNode = function(node) {
+                this.nativeRange.selectNode(node);
+                updateRangeProperties(this);
+            };
+
+            rangeProto.deleteContents = function() {
+                this.nativeRange.deleteContents();
+                updateRangeProperties(this);
+            };
+
+            rangeProto.extractContents = function() {
+                var frag = this.nativeRange.extractContents();
+                updateRangeProperties(this);
+                return frag;
+            };
+
+            rangeProto.cloneContents = function() {
+                return this.nativeRange.cloneContents();
+            };
+
+            // TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
+            // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
+            // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
+            // insertNode, which works but is almost certainly slower than the native implementation.
+/*
+            rangeProto.insertNode = function(node) {
+                this.nativeRange.insertNode(node);
+                updateRangeProperties(this);
+            };
+*/
+
+            rangeProto.surroundContents = function(node) {
+                this.nativeRange.surroundContents(node);
+                updateRangeProperties(this);
+            };
+
+            rangeProto.collapse = function(isStart) {
+                this.nativeRange.collapse(isStart);
+                updateRangeProperties(this);
+            };
+
+            rangeProto.cloneRange = function() {
+                return new WrappedRange(this.nativeRange.cloneRange());
+            };
+
+            rangeProto.refresh = function() {
+                updateRangeProperties(this);
+            };
+
+            rangeProto.toString = function() {
+                return this.nativeRange.toString();
+            };
+
+            // Create test range and node for feature detection
+
+            var testTextNode = document.createTextNode("test");
+            dom.getBody(document).appendChild(testTextNode);
+            var range = document.createRange();
+
+            /*--------------------------------------------------------------------------------------------------------*/
+
+            // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
+            // correct for it
+
+            range.setStart(testTextNode, 0);
+            range.setEnd(testTextNode, 0);
+
+            try {
+                range.setStart(testTextNode, 1);
+                canSetRangeStartAfterEnd = true;
+
+                rangeProto.setStart = function(node, offset) {
+                    this.nativeRange.setStart(node, offset);
+                    updateRangeProperties(this);
+                };
+
+                rangeProto.setEnd = function(node, offset) {
+                    this.nativeRange.setEnd(node, offset);
+                    updateRangeProperties(this);
+                };
+
+                createBeforeAfterNodeSetter = function(name) {
+                    return function(node) {
+                        this.nativeRange[name](node);
+                        updateRangeProperties(this);
+                    };
+                };
+
+            } catch(ex) {
+
+
+                canSetRangeStartAfterEnd = false;
+
+                rangeProto.setStart = function(node, offset) {
+                    try {
+                        this.nativeRange.setStart(node, offset);
+                    } catch (ex) {
+                        this.nativeRange.setEnd(node, offset);
+                        this.nativeRange.setStart(node, offset);
+                    }
+                    updateRangeProperties(this);
+                };
+
+                rangeProto.setEnd = function(node, offset) {
+                    try {
+                        this.nativeRange.setEnd(node, offset);
+                    } catch (ex) {
+                        this.nativeRange.setStart(node, offset);
+                        this.nativeRange.setEnd(node, offset);
+                    }
+                    updateRangeProperties(this);
+                };
+
+                createBeforeAfterNodeSetter = function(name, oppositeName) {
+                    return function(node) {
+                        try {
+                            this.nativeRange[name](node);
+                        } catch (ex) {
+                            this.nativeRange[oppositeName](node);
+                            this.nativeRange[name](node);
+                        }
+                        updateRangeProperties(this);
+                    };
+                };
+            }
+
+            rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
+            rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
+            rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
+            rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
+
+            /*--------------------------------------------------------------------------------------------------------*/
+
+            // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
+            // the 0th character of the text node
+            range.selectNodeContents(testTextNode);
+            if (range.startContainer == testTextNode && range.endContainer == testTextNode &&
+                    range.startOffset == 0 && range.endOffset == testTextNode.length) {
+                rangeProto.selectNodeContents = function(node) {
+                    this.nativeRange.selectNodeContents(node);
+                    updateRangeProperties(this);
+                };
+            } else {
+                rangeProto.selectNodeContents = function(node) {
+                    this.setStart(node, 0);
+                    this.setEnd(node, DomRange.getEndOffset(node));
+                };
+            }
+
+            /*--------------------------------------------------------------------------------------------------------*/
+
+            // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
+            // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
+
+            range.selectNodeContents(testTextNode);
+            range.setEnd(testTextNode, 3);
+
+            var range2 = document.createRange();
+            range2.selectNodeContents(testTextNode);
+            range2.setEnd(testTextNode, 4);
+            range2.setStart(testTextNode, 2);
+
+            if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &
+                    range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
+                // This is the wrong way round, so correct for it
+
+
+                rangeProto.compareBoundaryPoints = function(type, range) {
+                    range = range.nativeRange || range;
+                    if (type == range.START_TO_END) {
+                        type = range.END_TO_START;
+                    } else if (type == range.END_TO_START) {
+                        type = range.START_TO_END;
+                    }
+                    return this.nativeRange.compareBoundaryPoints(type, range);
+                };
+            } else {
+                rangeProto.compareBoundaryPoints = function(type, range) {
+                    return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
+                };
+            }
+
+            /*--------------------------------------------------------------------------------------------------------*/
+
+            // Test for existence of createContextualFragment and delegate to it if it exists
+            if (api.util.isHostMethod(range, "createContextualFragment")) {
+                rangeProto.createContextualFragment = function(fragmentStr) {
+                    return this.nativeRange.createContextualFragment(fragmentStr);
+                };
+            }
+
+            /*--------------------------------------------------------------------------------------------------------*/
+
+            // Clean up
+            dom.getBody(document).removeChild(testTextNode);
+            range.detach();
+            range2.detach();
+        })();
+
+        api.createNativeRange = function(doc) {
+            doc = doc || document;
+            return doc.createRange();
+        };
+    } else if (api.features.implementsTextRange) {
+        // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
+        // prototype
+
+        WrappedRange = function(textRange) {
+            this.textRange = textRange;
+            this.refresh();
+        };
+
+        WrappedRange.prototype = new DomRange(document);
+
+        WrappedRange.prototype.refresh = function() {
+            var start, end;
+
+            // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
+            var rangeContainerElement = getTextRangeContainerElement(this.textRange);
+
+            if (textRangeIsCollapsed(this.textRange)) {
+                end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);
+            } else {
+
+                start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
+                end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);
+            }
+
+            this.setStart(start.node, start.offset);
+            this.setEnd(end.node, end.offset);
+        };
+
+        DomRange.copyComparisonConstants(WrappedRange);
+
+        // Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
+        var globalObj = (function() { return this; })();
+        if (typeof globalObj.Range == "undefined") {
+            globalObj.Range = WrappedRange;
+        }
+
+        api.createNativeRange = function(doc) {
+            doc = doc || document;
+            return doc.body.createTextRange();
+        };
+    }
+
+    if (api.features.implementsTextRange) {
+        WrappedRange.rangeToTextRange = function(range) {
+            if (range.collapsed) {
+                var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
+
+
+
+                return tr;
+
+                //return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
+            } else {
+                var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
+                var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
+                var textRange = dom.getDocument(range.startContainer).body.createTextRange();
+                textRange.setEndPoint("StartToStart", startRange);
+                textRange.setEndPoint("EndToEnd", endRange);
+                return textRange;
+            }
+        };
+    }
+
+    WrappedRange.prototype.getName = function() {
+        return "WrappedRange";
+    };
+
+    api.WrappedRange = WrappedRange;
+
+    api.createRange = function(doc) {
+        doc = doc || document;
+        return new WrappedRange(api.createNativeRange(doc));
+    };
+
+    api.createRangyRange = function(doc) {
+        doc = doc || document;
+        return new DomRange(doc);
+    };
+
+    api.createIframeRange = function(iframeEl) {
+        return api.createRange(dom.getIframeDocument(iframeEl));
+    };
+
+    api.createIframeRangyRange = function(iframeEl) {
+        return api.createRangyRange(dom.getIframeDocument(iframeEl));
+    };
+
+    api.addCreateMissingNativeApiListener(function(win) {
+        var doc = win.document;
+        if (typeof doc.createRange == "undefined") {
+            doc.createRange = function() {
+                return api.createRange(this);
+            };
+        }
+        doc = win = null;
+    });
+});rangy.createModule("WrappedSelection", function(api, module) {
+    // This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
+    // spec (http://html5.org/specs/dom-range.html)
+
+    api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
+
+    api.config.checkSelectionRanges = true;
+
+    var BOOLEAN = "boolean",
+        windowPropertyName = "_rangySelection",
+        dom = api.dom,
+        util = api.util,
+        DomRange = api.DomRange,
+        WrappedRange = api.WrappedRange,
+        DOMException = api.DOMException,
+        DomPosition = dom.DomPosition,
+        getSelection,
+        selectionIsCollapsed,
+        CONTROL = "Control";
+
+
+
+    function getWinSelection(winParam) {
+        return (winParam || window).getSelection();
+    }
+
+    function getDocSelection(winParam) {
+        return (winParam || window).document.selection;
+    }
+
+    // Test for the Range/TextRange and Selection features required
+    // Test for ability to retrieve selection
+    var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),
+        implementsDocSelection = api.util.isHostObject(document, "selection");
+
+    var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
+
+    if (useDocumentSelection) {
+        getSelection = getDocSelection;
+        api.isSelectionValid = function(winParam) {
+            var doc = (winParam || window).document, nativeSel = doc.selection;
+
+            // Check whether the selection TextRange is actually contained within the correct document
+            return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
+        };
+    } else if (implementsWinGetSelection) {
+        getSelection = getWinSelection;
+        api.isSelectionValid = function() {
+            return true;
+        };
+    } else {
+        module.fail("Neither document.selection or window.getSelection() detected.");
+    }
+
+    api.getNativeSelection = getSelection;
+
+    var testSelection = getSelection();
+    var testRange = api.createNativeRange(document);
+    var body = dom.getBody(document);
+
+    // Obtaining a range from a selection
+    var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] &&
+                                     util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));
+    api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
+
+    // Test for existence of native selection extend() method
+    var selectionHasExtend = util.isHostMethod(testSelection, "extend");
+    api.features.selectionHasExtend = selectionHasExtend;
+
+    // Test if rangeCount exists
+    var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
+    api.features.selectionHasRangeCount = selectionHasRangeCount;
+
+    var selectionSupportsMultipleRanges = false;
+    var collapsedNonEditableSelectionsSupported = true;
+
+    if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
+            typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {
+
+        (function() {
+            var iframe = document.createElement("iframe");
+            body.appendChild(iframe);
+
+            var iframeDoc = dom.getIframeDocument(iframe);
+            iframeDoc.open();
+            iframeDoc.write("<html><head></head><body>12</body></html>");
+            iframeDoc.close();
+
+            var sel = dom.getIframeWindow(iframe).getSelection();
+            var docEl = iframeDoc.documentElement;
+            var iframeBody = docEl.lastChild, textNode = iframeBody.firstChild;
+
+            // Test whether the native selection will allow a collapsed selection within a non-editable element
+            var r1 = iframeDoc.createRange();
+            r1.setStart(textNode, 1);
+            r1.collapse(true);
+            sel.addRange(r1);
+            collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
+            sel.removeAllRanges();
+
+            // Test whether the native selection is capable of supporting multiple ranges
+            var r2 = r1.cloneRange();
+            r1.setStart(textNode, 0);
+            r2.setEnd(textNode, 2);
+            sel.addRange(r1);
+            sel.addRange(r2);
+
+            selectionSupportsMultipleRanges = (sel.rangeCount == 2);
+
+            // Clean up
+            r1.detach();
+            r2.detach();
+
+            body.removeChild(iframe);
+        })();
+    }
+
+    api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
+    api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
+
+    // ControlRanges
+    var implementsControlRange = false, testControlRange;
+
+    if (body && util.isHostMethod(body, "createControlRange")) {
+        testControlRange = body.createControlRange();
+        if (util.areHostProperties(testControlRange, ["item", "add"])) {
+            implementsControlRange = true;
+        }
+    }
+    api.features.implementsControlRange = implementsControlRange;
+
+    // Selection collapsedness
+    if (selectionHasAnchorAndFocus) {
+        selectionIsCollapsed = function(sel) {
+            return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
+        };
+    } else {
+        selectionIsCollapsed = function(sel) {
+            return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
+        };
+    }
+
+    function updateAnchorAndFocusFromRange(sel, range, backwards) {
+        var anchorPrefix = backwards ? "end" : "start", focusPrefix = backwards ? "start" : "end";
+        sel.anchorNode = range[anchorPrefix + "Container"];
+        sel.anchorOffset = range[anchorPrefix + "Offset"];
+        sel.focusNode = range[focusPrefix + "Container"];
+        sel.focusOffset = range[focusPrefix + "Offset"];
+    }
+
+    function updateAnchorAndFocusFromNativeSelection(sel) {
+        var nativeSel = sel.nativeSelection;
+        sel.anchorNode = nativeSel.anchorNode;
+        sel.anchorOffset = nativeSel.anchorOffset;
+        sel.focusNode = nativeSel.focusNode;
+        sel.focusOffset = nativeSel.focusOffset;
+    }
+
+    function updateEmptySelection(sel) {
+        sel.anchorNode = sel.focusNode = null;
+        sel.anchorOffset = sel.focusOffset = 0;
+        sel.rangeCount = 0;
+        sel.isCollapsed = true;
+        sel._ranges.length = 0;
+    }
+
+    function getNativeRange(range) {
+        var nativeRange;
+        if (range instanceof DomRange) {
+            nativeRange = range._selectionNativeRange;
+            if (!nativeRange) {
+                nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));
+                nativeRange.setEnd(range.endContainer, range.endOffset);
+                nativeRange.setStart(range.startContainer, range.startOffset);
+                range._selectionNativeRange = nativeRange;
+                range.attachListener("detach", function() {
+
+                    this._selectionNativeRange = null;
+                });
+            }
+        } else if (range instanceof WrappedRange) {
+            nativeRange = range.nativeRange;
+        } else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
+            nativeRange = range;
+        }
+        return nativeRange;
+    }
+
+    function rangeContainsSingleElement(rangeNodes) {
+        if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
+            return false;
+        }
+        for (var i = 1, len = rangeNodes.length; i < len; ++i) {
+            if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    function getSingleElementFromRange(range) {
+        var nodes = range.getNodes();
+        if (!rangeContainsSingleElement(nodes)) {
+            throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
+        }
+        return nodes[0];
+    }
+
+    function isTextRange(range) {
+        return !!range && typeof range.text != "undefined";
+    }
+
+    function updateFromTextRange(sel, range) {
+        // Create a Range from the selected TextRange
+        var wrappedRange = new WrappedRange(range);
+        sel._ranges = [wrappedRange];
+
+        updateAnchorAndFocusFromRange(sel, wrappedRange, false);
+        sel.rangeCount = 1;
+        sel.isCollapsed = wrappedRange.collapsed;
+    }
+
+    function updateControlSelection(sel) {
+        // Update the wrapped selection based on what's now in the native selection
+        sel._ranges.length = 0;
+        if (sel.docSelection.type == "None") {
+            updateEmptySelection(sel);
+        } else {
+            var controlRange = sel.docSelection.createRange();
+            if (isTextRange(controlRange)) {
+                // This case (where the selection type is "Control" and calling createRange() on the selection returns
+                // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
+                // ControlRange have been removed from the ControlRange and removed from the document.
+                updateFromTextRange(sel, controlRange);
+            } else {
+                sel.rangeCount = controlRange.length;
+                var range, doc = dom.getDocument(controlRange.item(0));
+                for (var i = 0; i < sel.rangeCount; ++i) {
+                    range = api.createRange(doc);
+                    range.selectNode(controlRange.item(i));
+                    sel._ranges.push(range);
+                }
+                sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
+                updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
+            }
+        }
+    }
+
+    function addRangeToControlSelection(sel, range) {
+        var controlRange = sel.docSelection.createRange();
+        var rangeElement = getSingleElementFromRange(range);
+
+        // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
+        // contained by the supplied range
+        var doc = dom.getDocument(controlRange.item(0));
+        var newControlRange = dom.getBody(doc).createControlRange();
+        for (var i = 0, len = controlRange.length; i < len; ++i) {
+            newControlRange.add(controlRange.item(i));
+        }
+        try {
+            newControlRange.add(rangeElement);
+        } catch (ex) {
+            throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
+        }
+        newControlRange.select();
+
+        // Update the wrapped selection based on what's now in the native selection
+        updateControlSelection(sel);
+    }
+
+    var getSelectionRangeAt;
+
+    if (util.isHostMethod(testSelection,  "getRangeAt")) {
+        getSelectionRangeAt = function(sel, index) {
+            try {
+                return sel.getRangeAt(index);
+            } catch(ex) {
+                return null;
+            }
+        };
+    } else if (selectionHasAnchorAndFocus) {
+        getSelectionRangeAt = function(sel) {
+            var doc = dom.getDocument(sel.anchorNode);
+            var range = api.createRange(doc);
+            range.setStart(sel.anchorNode, sel.anchorOffset);
+            range.setEnd(sel.focusNode, sel.focusOffset);
+
+            // Handle the case when the selection was selected backwards (from the end to the start in the
+            // document)
+            if (range.collapsed !== this.isCollapsed) {
+                range.setStart(sel.focusNode, sel.focusOffset);
+                range.setEnd(sel.anchorNode, sel.anchorOffset);
+            }
+
+            return range;
+        };
+    }
+
+    /**
+     * @constructor
+     */
+    function WrappedSelection(selection, docSelection, win) {
+        this.nativeSelection = selection;
+        this.docSelection = docSelection;
+        this._ranges = [];
+        this.win = win;
+        this.refresh();
+    }
+
+    api.getSelection = function(win) {
+        win = win || window;
+        var sel = win[windowPropertyName];
+        var nativeSel = getSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
+        if (sel) {
+            sel.nativeSelection = nativeSel;
+            sel.docSelection = docSel;
+            sel.refresh(win);
+        } else {
+            sel = new WrappedSelection(nativeSel, docSel, win);
+            win[windowPropertyName] = sel;
+        }
+        return sel;
+    };
+
+    api.getIframeSelection = function(iframeEl) {
+        return api.getSelection(dom.getIframeWindow(iframeEl));
+    };
+
+    var selProto = WrappedSelection.prototype;
+
+    function createControlSelection(sel, ranges) {
+        // Ensure that the selection becomes of type "Control"
+        var doc = dom.getDocument(ranges[0].startContainer);
+        var controlRange = dom.getBody(doc).createControlRange();
+        for (var i = 0, el; i < rangeCount; ++i) {
+            el = getSingleElementFromRange(ranges[i]);
+            try {
+                controlRange.add(el);
+            } catch (ex) {
+                throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
+            }
+        }
+        controlRange.select();
+
+        // Update the wrapped selection based on what's now in the native selection
+        updateControlSelection(sel);
+    }
+
+    // Selecting a range
+    if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
+        selProto.removeAllRanges = function() {
+            this.nativeSelection.removeAllRanges();
+            updateEmptySelection(this);
+        };
+
+        var addRangeBackwards = function(sel, range) {
+            var doc = DomRange.getRangeDocument(range);
+            var endRange = api.createRange(doc);
+            endRange.collapseToPoint(range.endContainer, range.endOffset);
+            sel.nativeSelection.addRange(getNativeRange(endRange));
+            sel.nativeSelection.extend(range.startContainer, range.startOffset);
+            sel.refresh();
+        };
+
+        if (selectionHasRangeCount) {
+            selProto.addRange = function(range, backwards) {
+                if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
+                    addRangeToControlSelection(this, range);
+                } else {
+                    if (backwards && selectionHasExtend) {
+                        addRangeBackwards(this, range);
+                    } else {
+                        var previousRangeCount;
+                        if (selectionSupportsMultipleRanges) {
+                            previousRangeCount = this.rangeCount;
+                        } else {
+                            this.removeAllRanges();
+                            previousRangeCount = 0;
+                        }
+                        this.nativeSelection.addRange(getNativeRange(range));
+
+                        // Check whether adding the range was successful
+                        this.rangeCount = this.nativeSelection.rangeCount;
+
+                        if (this.rangeCount == previousRangeCount + 1) {
+                            // The range was added successfully
+
+                            // Check whether the range that we added to the selection is reflected in the last range extracted from
+                            // the selection
+                            if (api.config.checkSelectionRanges) {
+                                var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
+                                if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {
+                                    // Happens in WebKit with, for example, a selection placed at the start of a text node
+                                    range = new WrappedRange(nativeRange);
+                                }
+                            }
+                            this._ranges[this.rangeCount - 1] = range;
+                            updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));
+                            this.isCollapsed = selectionIsCollapsed(this);
+                        } else {
+                            // The range was not added successfully. The simplest thing is to refresh
+                            this.refresh();
+                        }
+                    }
+                }
+            };
+        } else {
+            selProto.addRange = function(range, backwards) {
+                if (backwards && selectionHasExtend) {
+                    addRangeBackwards(this, range);
+                } else {
+                    this.nativeSelection.addRange(getNativeRange(range));
+                    this.refresh();
+                }
+            };
+        }
+
+        selProto.setRanges = function(ranges) {
+            if (implementsControlRange && ranges.length > 1) {
+                createControlSelection(this, ranges);
+            } else {
+                this.removeAllRanges();
+                for (var i = 0, len = ranges.length; i < len; ++i) {
+                    this.addRange(ranges[i]);
+                }
+            }
+        };
+    } else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") &&
+               implementsControlRange && useDocumentSelection) {
+
+        selProto.removeAllRanges = function() {
+            // Added try/catch as fix for issue #21
+            try {
+                this.docSelection.empty();
+
+                // Check for empty() not working (issue #24)
+                if (this.docSelection.type != "None") {
+                    // Work around failure to empty a control selection by instead selecting a TextRange and then
+                    // calling empty()
+                    var doc;
+                    if (this.anchorNode) {
+                        doc = dom.getDocument(this.anchorNode);
+                    } else if (this.docSelection.type == CONTROL) {
+                        var controlRange = this.docSelection.createRange();
+                        if (controlRange.length) {
+                            doc = dom.getDocument(controlRange.item(0)).body.createTextRange();
+                        }
+                    }
+                    if (doc) {
+                        var textRange = doc.body.createTextRange();
+                        textRange.select();
+                        this.docSelection.empty();
+                    }
+                }
+            } catch(ex) {}
+            updateEmptySelection(this);
+        };
+
+        selProto.addRange = function(range) {
+            if (this.docSelection.type == CONTROL) {
+                addRangeToControlSelection(this, range);
+            } else {
+                WrappedRange.rangeToTextRange(range).select();
+                this._ranges[0] = range;
+                this.rangeCount = 1;
+                this.isCollapsed = this._ranges[0].collapsed;
+                updateAnchorAndFocusFromRange(this, range, false);
+            }
+        };
+
+        selProto.setRanges = function(ranges) {
+            this.removeAllRanges();
+            var rangeCount = ranges.length;
+            if (rangeCount > 1) {
+                createControlSelection(this, ranges);
+            } else if (rangeCount) {
+                this.addRange(ranges[0]);
+            }
+        };
+    } else {
+        module.fail("No means of selecting a Range or TextRange was found");
+        return false;
+    }
+
+    selProto.getRangeAt = function(index) {
+        if (index < 0 || index >= this.rangeCount) {
+            throw new DOMException("INDEX_SIZE_ERR");
+        } else {
+            return this._ranges[index];
+        }
+    };
+
+    var refreshSelection;
+
+    if (useDocumentSelection) {
+        refreshSelection = function(sel) {
+            var range;
+            if (api.isSelectionValid(sel.win)) {
+                range = sel.docSelection.createRange();
+            } else {
+                range = dom.getBody(sel.win.document).createTextRange();
+                range.collapse(true);
+            }
+
+
+            if (sel.docSelection.type == CONTROL) {
+                updateControlSelection(sel);
+            } else if (isTextRange(range)) {
+                updateFromTextRange(sel, range);
+            } else {
+                updateEmptySelection(sel);
+            }
+        };
+    } else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
+        refreshSelection = function(sel) {
+            if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
+                updateControlSelection(sel);
+            } else {
+                sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
+                if (sel.rangeCount) {
+                    for (var i = 0, len = sel.rangeCount; i < len; ++i) {
+                        sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
+                    }
+                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));
+                    sel.isCollapsed = selectionIsCollapsed(sel);
+                } else {
+                    updateEmptySelection(sel);
+                }
+            }
+        };
+    } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
+        refreshSelection = function(sel) {
+            var range, nativeSel = sel.nativeSelection;
+            if (nativeSel.anchorNode) {
+                range = getSelectionRangeAt(nativeSel, 0);
+                sel._ranges = [range];
+                sel.rangeCount = 1;
+                updateAnchorAndFocusFromNativeSelection(sel);
+                sel.isCollapsed = selectionIsCollapsed(sel);
+            } else {
+                updateEmptySelection(sel);
+            }
+        };
+    } else {
+        module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
+        return false;
+    }
+
+    selProto.refresh = function(checkForChanges) {
+        var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
+        refreshSelection(this);
+        if (checkForChanges) {
+            var i = oldRanges.length;
+            if (i != this._ranges.length) {
+                return false;
+            }
+            while (i--) {
+                if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    };
+
+    // Removal of a single range
+    var removeRangeManually = function(sel, range) {
+        var ranges = sel.getAllRanges(), removed = false;
+        sel.removeAllRanges();
+        for (var i = 0, len = ranges.length; i < len; ++i) {
+            if (removed || range !== ranges[i]) {
+                sel.addRange(ranges[i]);
+            } else {
+                // According to the draft WHATWG Range spec, the same range may be added to the selection multiple
+                // times. removeRange should only remove the first instance, so the following ensures only the first
+                // instance is removed
+                removed = true;
+            }
+        }
+        if (!sel.rangeCount) {
+            updateEmptySelection(sel);
+        }
+    };
+
+    if (implementsControlRange) {
+        selProto.removeRange = function(range) {
+            if (this.docSelection.type == CONTROL) {
+                var controlRange = this.docSelection.createRange();
+                var rangeElement = getSingleElementFromRange(range);
+
+                // Create a new ControlRange containing all the elements in the selected ControlRange minus the
+                // element contained by the supplied range
+                var doc = dom.getDocument(controlRange.item(0));
+                var newControlRange = dom.getBody(doc).createControlRange();
+                var el, removed = false;
+                for (var i = 0, len = controlRange.length; i < len; ++i) {
+                    el = controlRange.item(i);
+                    if (el !== rangeElement || removed) {
+                        newControlRange.add(controlRange.item(i));
+                    } else {
+                        removed = true;
+                    }
+                }
+                newControlRange.select();
+
+                // Update the wrapped selection based on what's now in the native selection
+                updateControlSelection(this);
+            } else {
+                removeRangeManually(this, range);
+            }
+        };
+    } else {
+        selProto.removeRange = function(range) {
+            removeRangeManually(this, range);
+        };
+    }
+
+    // Detecting if a selection is backwards
+    var selectionIsBackwards;
+    if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
+        selectionIsBackwards = function(sel) {
+            var backwards = false;
+            if (sel.anchorNode) {
+                backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
+            }
+            return backwards;
+        };
+
+        selProto.isBackwards = function() {
+            return selectionIsBackwards(this);
+        };
+    } else {
+        selectionIsBackwards = selProto.isBackwards = function() {
+            return false;
+        };
+    }
+
+    // Selection text
+    // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
+    selProto.toString = function() {
+
+        var rangeTexts = [];
+        for (var i = 0, len = this.rangeCount; i < len; ++i) {
+            rangeTexts[i] = "" + this._ranges[i];
+        }
+        return rangeTexts.join("");
+    };
+
+    function assertNodeInSameDocument(sel, node) {
+        if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
+            throw new DOMException("WRONG_DOCUMENT_ERR");
+        }
+    }
+
+    // No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
+    selProto.collapse = function(node, offset) {
+        assertNodeInSameDocument(this, node);
+        var range = api.createRange(dom.getDocument(node));
+        range.collapseToPoint(node, offset);
+        this.removeAllRanges();
+        this.addRange(range);
+        this.isCollapsed = true;
+    };
+
+    selProto.collapseToStart = function() {
+        if (this.rangeCount) {
+            var range = this._ranges[0];
+            this.collapse(range.startContainer, range.startOffset);
+        } else {
+            throw new DOMException("INVALID_STATE_ERR");
+        }
+    };
+
+    selProto.collapseToEnd = function() {
+        if (this.rangeCount) {
+            var range = this._ranges[this.rangeCount - 1];
+            this.collapse(range.endContainer, range.endOffset);
+        } else {
+            throw new DOMException("INVALID_STATE_ERR");
+        }
+    };
+
+    // The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
+    // never used by Rangy.
+    selProto.selectAllChildren = function(node) {
+        assertNodeInSameDocument(this, node);
+        var range = api.createRange(dom.getDocument(node));
+        range.selectNodeContents(node);
+        this.removeAllRanges();
+        this.addRange(range);
+    };
+
+    selProto.deleteFromDocument = function() {
+        // Sepcial behaviour required for Control selections
+        if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
+            var controlRange = this.docSelection.createRange();
+            var element;
+            while (controlRange.length) {
+                element = controlRange.item(0);
+                controlRange.remove(element);
+                element.parentNode.removeChild(element);
+            }
+            this.refresh();
+        } else if (this.rangeCount) {
+            var ranges = this.getAllRanges();
+            this.removeAllRanges();
+            for (var i = 0, len = ranges.length; i < len; ++i) {
+                ranges[i].deleteContents();
+            }
+            // The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
+            // range. Firefox moves the selection to where the final selected range was, so we emulate that
+            this.addRange(ranges[len - 1]);
+        }
+    };
+
+    // The following are non-standard extensions
+    selProto.getAllRanges = function() {
+        return this._ranges.slice(0);
+    };
+
+    selProto.setSingleRange = function(range) {
+        this.setRanges( [range] );
+    };
+
+    selProto.containsNode = function(node, allowPartial) {
+        for (var i = 0, len = this._ranges.length; i < len; ++i) {
+            if (this._ranges[i].containsNode(node, allowPartial)) {
+                return true;
+            }
+        }
+        return false;
+    };
+
+    selProto.toHtml = function() {
+        var html = "";
+        if (this.rangeCount) {
+            var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");
+            for (var i = 0, len = this._ranges.length; i < len; ++i) {
+                container.appendChild(this._ranges[i].cloneContents());
+            }
+            html = container.innerHTML;
+        }
+        return html;
+    };
+
+    function inspect(sel) {
+        var rangeInspects = [];
+        var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
+        var focus = new DomPosition(sel.focusNode, sel.focusOffset);
+        var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
+
+        if (typeof sel.rangeCount != "undefined") {
+            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
+                rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
+            }
+        }
+        return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
+                ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
+
+    }
+
+    selProto.getName = function() {
+        return "WrappedSelection";
+    };
+
+    selProto.inspect = function() {
+        return inspect(this);
+    };
+
+    selProto.detach = function() {
+        this.win[windowPropertyName] = null;
+        this.win = this.anchorNode = this.focusNode = null;
+    };
+
+    WrappedSelection.inspect = inspect;
+
+    api.Selection = WrappedSelection;
+
+    api.selectionPrototype = selProto;
+
+    api.addCreateMissingNativeApiListener(function(win) {
+        if (typeof win.getSelection == "undefined") {
+            win.getSelection = function() {
+                return api.getSelection(this);
+            };
+        }
+        win = null;
+    });
+});
+/*
+	Base.js, version 1.1a
+	Copyright 2006-2010, Dean Edwards
+	License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+var Base = function() {
+	// dummy
+};
+
+Base.extend = function(_instance, _static) { // subclass
+	var extend = Base.prototype.extend;
+	
+	// build the prototype
+	Base._prototyping = true;
+	var proto = new this;
+	extend.call(proto, _instance);
+  proto.base = function() {
+    // call this method from any other method to invoke that method's ancestor
+  };
+	delete Base._prototyping;
+	
+	// create the wrapper for the constructor function
+	//var constructor = proto.constructor.valueOf(); //-dean
+	var constructor = proto.constructor;
+	var klass = proto.constructor = function() {
+		if (!Base._prototyping) {
+			if (this._constructing || this.constructor == klass) { // instantiation
+				this._constructing = true;
+				constructor.apply(this, arguments);
+				delete this._constructing;
+			} else if (arguments[0] != null) { // casting
+				return (arguments[0].extend || extend).call(arguments[0], proto);
+			}
+		}
+	};
+	
+	// build the class interface
+	klass.ancestor = this;
+	klass.extend = this.extend;
+	klass.forEach = this.forEach;
+	klass.implement = this.implement;
+	klass.prototype = proto;
+	klass.toString = this.toString;
+	klass.valueOf = function(type) {
+		//return (type == "object") ? klass : constructor; //-dean
+		return (type == "object") ? klass : constructor.valueOf();
+	};
+	extend.call(klass, _static);
+	// class initialisation
+	if (typeof klass.init == "function") klass.init();
+	return klass;
+};
+
+Base.prototype = {	
+	extend: function(source, value) {
+		if (arguments.length > 1) { // extending with a name/value pair
+			var ancestor = this[source];
+			if (ancestor && (typeof value == "function") && // overriding a method?
+				// the valueOf() comparison is to avoid circular references
+				(!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
+				/\bbase\b/.test(value)) {
+				// get the underlying method
+				var method = value.valueOf();
+				// override
+				value = function() {
+					var previous = this.base || Base.prototype.base;
+					this.base = ancestor;
+					var returnValue = method.apply(this, arguments);
+					this.base = previous;
+					return returnValue;
+				};
+				// point to the underlying method
+				value.valueOf = function(type) {
+					return (type == "object") ? value : method;
+				};
+				value.toString = Base.toString;
+			}
+			this[source] = value;
+		} else if (source) { // extending with an object literal
+			var extend = Base.prototype.extend;
+			// if this object has a customised extend method then use it
+			if (!Base._prototyping && typeof this != "function") {
+				extend = this.extend || extend;
+			}
+			var proto = {toSource: null};
+			// do the "toString" and other methods manually
+			var hidden = ["constructor", "toString", "valueOf"];
+			// if we are prototyping then include the constructor
+			var i = Base._prototyping ? 0 : 1;
+			while (key = hidden[i++]) {
+				if (source[key] != proto[key]) {
+					extend.call(this, key, source[key]);
+
+				}
+			}
+			// copy each of the source object's properties to this object
+			for (var key in source) {
+				if (!proto[key]) extend.call(this, key, source[key]);
+			}
+		}
+		return this;
+	}
+};
+
+// initialise
+Base = Base.extend({
+	constructor: function() {
+		this.extend(arguments[0]);
+	}
+}, {
+	ancestor: Object,
+	version: "1.1",
+	
+	forEach: function(object, block, context) {
+		for (var key in object) {
+			if (this.prototype[key] === undefined) {
+				block.call(context, object[key], key, object);
+			}
+		}
+	},
+		
+	implement: function() {
+		for (var i = 0; i < arguments.length; i++) {
+			if (typeof arguments[i] == "function") {
+				// if it's a function, call it
+				arguments[i](this.prototype);
+			} else {
+				// add the interface using the extend method
+				this.prototype.extend(arguments[i]);
+			}
+		}
+		return this;
+	},
+	
+	toString: function() {
+		return String(this.valueOf());
+	}
+});/**
+ * Detect browser support for specific features
+ */
+wysihtml5.browser = (function() {
+  var userAgent   = navigator.userAgent,
+      testElement = document.createElement("div"),
+      // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
+      isIE        = userAgent.indexOf("MSIE")         !== -1 && userAgent.indexOf("Opera") === -1,
+      isGecko     = userAgent.indexOf("Gecko")        !== -1 && userAgent.indexOf("KHTML") === -1,
+      isWebKit    = userAgent.indexOf("AppleWebKit/") !== -1,
+      isChrome    = userAgent.indexOf("Chrome/")      !== -1,
+      isOpera     = userAgent.indexOf("Opera/")       !== -1;
+  
+  function iosVersion(userAgent) {
+    return ((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [, 0])[1];
+  }
+  
+  return {
+    // Static variable needed, publicly accessible, to be able override it in unit tests
+    USER_AGENT: userAgent,
+    
+    /**
+     * Exclude browsers that are not capable of displaying and handling
+     * contentEditable as desired:
+     *    - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable
+     *    - IE < 8 create invalid markup and crash randomly from time to time
+     *
+     * @return {Boolean}
+     */
+    supported: function() {
+      var userAgent                   = this.USER_AGENT.toLowerCase(),
+          // Essential for making html elements editable
+          hasContentEditableSupport   = "contentEditable" in testElement,
+          // Following methods are needed in order to interact with the contentEditable area
+          hasEditingApiSupport        = document.execCommand && document.queryCommandSupported && document.queryCommandState,
+          // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+
+          hasQuerySelectorSupport     = document.querySelector && document.querySelectorAll,
+          // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05)
+          isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
+      
+      return hasContentEditableSupport
+        && hasEditingApiSupport
+        && hasQuerySelectorSupport
+        && !isIncompatibleMobileBrowser;
+    },
+    
+    isTouchDevice: function() {
+      return this.supportsEvent("touchmove");
+    },
+    
+    isIos: function() {
+      var userAgent = this.USER_AGENT.toLowerCase();
+      return userAgent.indexOf("webkit") !== -1 && userAgent.indexOf("mobile") !== -1;
+    },
+    
+    /**
+     * Whether the browser supports sandboxed iframes
+     * Currently only IE 6+ offers such feature <iframe security="restricted">
+     *
+     * http://msdn.microsoft.com/en-us/library/ms534622(v=vs.85).aspx
+     * http://blogs.msdn.com/b/ie/archive/2008/01/18/using-frames-more-securely.aspx
+     *
+     * HTML5 sandboxed iframes are still buggy and their DOM is not reachable from the outside (except when using postMessage)
+     */
+    supportsSandboxedIframes: function() {
+      return isIE;
+    },
+
+    /**
+     * IE6+7 throw a mixed content warning when the src of an iframe
+     * is empty/unset or about:blank
+     * window.querySelector is implemented as of IE8
+     */
+    throwsMixedContentWarningWhenIframeSrcIsEmpty: function() {
+      return !("querySelector" in document);
+    },
+
+    /**
+     * Whether the caret is correctly displayed in contentEditable elements
+     * Firefox sometimes shows a huge caret in the beginning after focusing
+     */
+    displaysCaretInEmptyContentEditableCorrectly: function() {
+      return !isGecko;
+    },
+
+    /**
+     * Opera and IE are the only browsers who offer the css value
+     * in the original unit, thx to the currentStyle object
+     * All other browsers provide the computed style in px via window.getComputedStyle
+     */
+    hasCurrentStyleProperty: function() {
+      return "currentStyle" in testElement;
+    },
+
+    /**
+     * Whether the browser inserts a <br> when pressing enter in a contentEditable element
+     */
+    insertsLineBreaksOnReturn: function() {
+      return isGecko;
+    },
+
+    supportsPlaceholderAttributeOn: function(element) {
+      return "placeholder" in element;
+    },
+
+    supportsEvent: function(eventName) {
+      return "on" + eventName in testElement || (function() {
+        testElement.setAttribute("on" + eventName, "return;");
+        return typeof(testElement["on" + eventName]) === "function";
+      })();
+    },
+
+    /**
+     * Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe
+     */
+    supportsEventsInIframeCorrectly: function() {
+      return !isOpera;
+    },
+
+    /**
+     * Chrome & Safari only fire the ondrop/ondragend/... events when the ondragover event is cancelled
+     * with event.preventDefault
+     * Firefox 3.6 fires those events anyway, but the mozilla doc says that the dragover/dragenter event needs
+     * to be cancelled
+     */
+    firesOnDropOnlyWhenOnDragOverIsCancelled: function() {
+      return isWebKit || isGecko;
+    },
+    
+    /**
+     * Whether the browser supports the event.dataTransfer property in a proper way
+     */
+    supportsDataTransfer: function() {
+      try {
+        // Firefox doesn't support dataTransfer in a safe way, it doesn't strip script code in the html payload (like Chrome does)
+        return isWebKit && (window.Clipboard || window.DataTransfer).prototype.getData;
+      } catch(e) {
+        return false;
+      }
+    },
+
+    /**
+     * Everything below IE9 doesn't know how to treat HTML5 tags
+     *
+     * @param {Object} context The document object on which to check HTML5 support
+     *
+     * @example
+     *    wysihtml5.browser.supportsHTML5Tags(document);
+     */
+    supportsHTML5Tags: function(context) {
+      var element = context.createElement("div"),
+          html5   = "<article>foo</article>";
+      element.innerHTML = html5;
+      return element.innerHTML.toLowerCase() === html5;
+    },
+
+    /**
+     * Checks whether a document supports a certain queryCommand
+     * In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree
+     * in oder to report correct results
+     *
+     * @param {Object} doc Document object on which to check for a query command
+     * @param {String} command The query command to check for
+     * @return {Boolean}
+     *
+     * @example
+     *    wysihtml5.browser.supportsCommand(document, "bold");
+     */
+    supportsCommand: (function() {
+      // Following commands are supported but contain bugs in some browsers
+      var buggyCommands = {
+        // formatBlock fails with some tags (eg. <blockquote>)
+        "formatBlock":          isIE,
+         // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
+         // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
+         // IE and Opera act a bit different here as they convert the entire content of the current block element into a list
+        "insertUnorderedList":  isIE || isOpera,
+        "insertOrderedList":    isIE || isOpera
+      };
+      
+      // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
+      var supported = {
+        "insertHTML": isGecko
+      };
+
+      return function(doc, command) {
+        var isBuggy = buggyCommands[command];
+        if (!isBuggy) {
+          // Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled
+          try {
+            return doc.queryCommandSupported(command);
+          } catch(e1) {}
+
+          try {
+            return doc.queryCommandEnabled(command);
+          } catch(e2) {
+            return !!supported[command];
+          }
+        }
+        return false;
+      };
+    })(),
+
+    /**
+     * IE: URLs starting with:
+     *    www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://,
+     *    nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url:
+     * will automatically be auto-linked when either the user inserts them via copy&paste or presses the
+     * space bar when the caret is directly after such an url.
+     * This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll
+     * (related blog post on msdn
+     * http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx).
+     */
+    doesAutoLinkingInContentEditable: function() {
+      return isIE;
+    },
+
+    /**
+     * As stated above, IE auto links urls typed into contentEditable elements
+     * Since IE9 it's possible to prevent this behavior
+     */
+    canDisableAutoLinking: function() {
+      return this.supportsCommand(document, "AutoUrlDetect");
+    },
+
+    /**
+     * IE leaves an empty paragraph in the contentEditable element after clearing it
+     * Chrome/Safari sometimes an empty <div>
+     */
+    clearsContentEditableCorrectly: function() {
+      return isGecko || isOpera || isWebKit;
+    },
+
+    /**
+     * IE gives wrong results for getAttribute
+     */
+    supportsGetAttributeCorrectly: function() {
+      var td = document.createElement("td");
+      return td.getAttribute("rowspan") != "1";
+    },
+
+    /**
+     * When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them.
+     * Chrome and Safari both don't support this
+     */
+    canSelectImagesInContentEditable: function() {
+      return isGecko || isIE || isOpera;
+    },
+
+    /**
+     * When the caret is in an empty list (<ul><li>|</li></ul>) which is the first child in an contentEditable container
+     * pressing backspace doesn't remove the entire list as done in other browsers
+     */
+    clearsListsInContentEditableCorrectly: function() {
+      return isGecko || isIE || isWebKit;
+    },
+
+    /**
+     * All browsers except Safari and Chrome automatically scroll the range/caret position into view
+     */
+    autoScrollsToCaret: function() {
+      return !isWebKit;
+    },
+
+    /**
+     * Check whether the browser automatically closes tags that don't need to be opened
+     */
+    autoClosesUnclosedTags: function() {
+      var clonedTestElement = testElement.cloneNode(false),
+          returnValue,
+          innerHTML;
+
+      clonedTestElement.innerHTML = "<p><div></div>";
+      innerHTML                   = clonedTestElement.innerHTML.toLowerCase();
+      returnValue                 = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>";
+
+      // Cache result by overwriting current function
+      this.autoClosesUnclosedTags = function() { return returnValue; };
+
+      return returnValue;
+    },
+
+    /**
+     * Whether the browser supports the native document.getElementsByClassName which returns live NodeLists
+     */
+    supportsNativeGetElementsByClassName: function() {
+      return String(document.getElementsByClassName).indexOf("[native code]") !== -1;
+    },
+
+    /**
+     * As of now (19.04.2011) only supported by Firefox 4 and Chrome
+     * See https://developer.mozilla.org/en/DOM/Selection/modify
+     */
+    supportsSelectionModify: function() {
+      return "getSelection" in window && "modify" in window.getSelection();
+    },
+    
+    /**
+     * Whether the browser supports the classList object for fast className manipulation
+     * See https://developer.mozilla.org/en/DOM/element.classList
+     */
+    supportsClassList: function() {
+      return "classList" in testElement;
+    },
+    
+    /**
+     * Opera needs a white space after a <br> in order to position the caret correctly
+     */
+    needsSpaceAfterLineBreak: function() {
+      return isOpera;
+    },
+    
+    /**
+     * Whether the browser supports the speech api on the given element
+     * See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
+     *
+     * @example
+     *    var input = document.createElement("input");
+     *    if (wysihtml5.browser.supportsSpeechApiOn(input)) {
+     *      // ...
+     *    }
+     */
+    supportsSpeechApiOn: function(input) {
+      var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [, 0];
+      return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input);
+    },
+    
+    /**
+     * IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest
+     * See https://connect.microsoft.com/ie/feedback/details/650112
+     * or try the POC http://tifftiff.de/ie9_crash/
+     */
+    crashesWhenDefineProperty: function(property) {
+      return isIE && (property === "XMLHttpRequest" || property === "XDomainRequest");
+    },
+    
+    /**
+     * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element
+     */
+    doesAsyncFocus: function() {
+      return isIE;
+    },
+    
+    /**
+     * In IE it's impssible for the user and for the selection library to set the caret after an <img> when it's the lastChild in the document
+     */
+    hasProblemsSettingCaretAfterImg: function() {
+      return isIE;
+    },
+    
+    hasUndoInContextMenu: function() {
+      return isGecko || isChrome || isOpera;
+    }
+  };
+})();wysihtml5.lang.array = function(arr) {
+  return {
+    /**
+     * Check whether a given object exists in an array
+     *
+     * @example
+     *    wysihtml5.lang.array([1, 2]).contains(1);
+     *    // => true
+     */
+    contains: function(needle) {
+      if (arr.indexOf) {
+        return arr.indexOf(needle) !== -1;
+      } else {
+        for (var i=0, length=arr.length; i<length; i++) {
+          if (arr[i] === needle) { return true; }
+        }
+        return false;
+      }
+    },
+    
+    /**
+     * Substract one array from another
+     *
+     * @example
+     *    wysihtml5.lang.array([1, 2, 3, 4]).without([3, 4]);
+     *    // => [1, 2]
+     */
+    without: function(arrayToSubstract) {
+      arrayToSubstract = wysihtml5.lang.array(arrayToSubstract);
+      var newArr  = [],
+          i       = 0,
+          length  = arr.length;
+      for (; i<length; i++) {
+        if (!arrayToSubstract.contains(arr[i])) {
+          newArr.push(arr[i]);
+        }
+      }
+      return newArr;
+    },
+    
+    /**
+     * Return a clean native array
+     * 
+     * Following will convert a Live NodeList to a proper Array
+     * @example
+     *    var childNodes = wysihtml5.lang.array(document.body.childNodes).get();
+     */
+    get: function() {
+      var i        = 0,
+          length   = arr.length,
+          newArray = [];
+      for (; i<length; i++) {
+        newArray.push(arr[i]);
+      }
+      return newArray;
+    }
+  };
+};wysihtml5.lang.Dispatcher = Base.extend(
+  /** @scope wysihtml5.lang.Dialog.prototype */ {
+  observe: function(eventName, handler) {
+    this.events = this.events || {};
+    this.events[eventName] = this.events[eventName] || [];
+    this.events[eventName].push(handler);
+    return this;
+  },
+
+  on: function() {
+    return this.observe.apply(this, wysihtml5.lang.array(arguments).get());
+  },
+
+  fire: function(eventName, payload) {
+    this.events = this.events || {};
+    var handlers = this.events[eventName] || [],
+        i        = 0;
+    for (; i<handlers.length; i++) {
+      handlers[i].call(this, payload);
+    }
+    return this;
+  },
+
+  stopObserving: function(eventName, handler) {
+    this.events = this.events || {};
+    var i = 0,
+        handlers,
+        newHandlers;
+    if (eventName) {
+      handlers    = this.events[eventName] || [],
+      newHandlers = [];
+      for (; i<handlers.length; i++) {
+        if (handlers[i] !== handler && handler) {
+          newHandlers.push(handlers[i]);
+        }
+      }
+      this.events[eventName] = newHandlers;
+    } else {
+      // Clean up all events
+      this.events = {};
+    }
+    return this;
+  }
+});wysihtml5.lang.object = function(obj) {
+  return {
+    /**
+     * @example
+     *    wysihtml5.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get();
+     *    // => { foo: 1, bar: 2, baz: 3 }
+     */
+    merge: function(otherObj) {
+      for (var i in otherObj) {
+        obj[i] = otherObj[i];
+      }
+      return this;
+    },
+    
+    get: function() {
+      return obj;
+    },
+    
+    /**
+     * @example
+     *    wysihtml5.lang.object({ foo: 1 }).clone();
+     *    // => { foo: 1 }
+     */
+    clone: function() {
+      var newObj = {},
+          i;
+      for (i in obj) {
+        newObj[i] = obj[i];
+      }
+      return newObj;
+    },
+    
+    /**
+     * @example
+     *    wysihtml5.lang.object([]).isArray();
+     *    // => true
+     */
+    isArray: function() {
+      return Object.prototype.toString.call(obj) === "[object Array]";
+    }
+  };
+};(function() {
+  var WHITE_SPACE_START = /^\s+/,
+      WHITE_SPACE_END   = /\s+$/;
+  wysihtml5.lang.string = function(str) {
+    str = String(str);
+    return {
+      /**
+       * @example
+       *    wysihtml5.lang.string("   foo   ").trim();
+       *    // => "foo"
+       */
+      trim: function() {
+        return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, "");
+      },
+      
+      /**
+       * @example
+       *    wysihtml5.lang.string("Hello #{name}").interpolate({ name: "Christopher" });
+       *    // => "Hello Christopher"
+       */
+      interpolate: function(vars) {
+        for (var i in vars) {
+          str = this.replace("#{" + i + "}").by(vars[i]);
+        }
+        return str;
+      },
+      
+      /**
+       * @example
+       *    wysihtml5.lang.string("Hello Tom").replace("Tom").with("Hans");
+       *    // => "Hello Hans"
+       */
+      replace: function(search) {
+        return {
+          by: function(replace) {
+            return str.split(search).join(replace);
+          }
+        }
+      }
+    };
+  };
+})();/**
+ * Find urls in descendant text nodes of an element and auto-links them
+ * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
+ *
+ * @param {Element} element Container element in which to search for urls
+ *
+ * @example
+ *    <div id="text-container">Please click here: www.google.com</div>
+ *    <script>wysihtml5.dom.autoLink(document.getElementById("text-container"));</script>
+ */
+(function(wysihtml5) {
+  var /**
+       * Don't auto-link urls that are contained in the following elements:
+       */
+      IGNORE_URLS_IN        = wysihtml5.lang.array(["CODE", "PRE", "A", "SCRIPT", "HEAD", "TITLE", "STYLE"]),
+      /**
+       * revision 1:
+       *    /(\S+\.{1}[^\s\,\.\!]+)/g
+       *
+       * revision 2:
+       *    /(\b(((https?|ftp):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;\[\]]*[-A-Z0-9+&@#\/%=~_|])/gim
+       *
+       * put this in the beginning if you don't wan't to match within a word
+       *    (^|[\>\(\{\[\s\>])
+       */
+      URL_REG_EXP           = /((https?:\/\/|www\.)[^\s<]{3,})/gi,
+      TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i,
+      MAX_DISPLAY_LENGTH    = 100,
+      BRACKETS              = { ")": "(", "]": "[", "}": "{" };
+  
+  function autoLink(element) {
+    if (_hasParentThatShouldBeIgnored(element)) {
+      return element;
+    }
+
+    if (element === element.ownerDocument.documentElement) {
+      element = element.ownerDocument.body;
+    }
+
+    return _parseNode(element);
+  }
+  
+  /**
+   * This is basically a rebuild of
+   * the rails auto_link_urls text helper
+   */
+  function _convertUrlsToLinks(str) {
+    return str.replace(URL_REG_EXP, function(match, url) {
+      var punctuation = (url.match(TRAILING_CHAR_REG_EXP) || [])[1] || "",
+          opening     = BRACKETS[punctuation];
+      url = url.replace(TRAILING_CHAR_REG_EXP, "");
+
+      if (url.split(opening).length > url.split(punctuation).length) {
+        url = url + punctuation;
+        punctuation = "";
+      }
+      var realUrl    = url,
+          displayUrl = url;
+      if (url.length > MAX_DISPLAY_LENGTH) {
+        displayUrl = displayUrl.substr(0, MAX_DISPLAY_LENGTH) + "...";
+      }
+      // Add http prefix if necessary
+      if (realUrl.substr(0, 4) === "www.") {
+        realUrl = "http://" + realUrl;
+      }
+      
+      return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation;
+    });
+  }
+  
+  /**
+   * Creates or (if already cached) returns a temp element
+   * for the given document object
+   */
+  function _getTempElement(context) {
+    var tempElement = context._wysihtml5_tempElement;
+    if (!tempElement) {
+      tempElement = context._wysihtml5_tempElement = context.createElement("div");
+    }
+    return tempElement;
+  }
+  
+  /**
+   * Replaces the original text nodes with the newly auto-linked dom tree
+   */
+  function _wrapMatchesInNode(textNode) {
+    var parentNode  = textNode.parentNode,
+        tempElement = _getTempElement(parentNode.ownerDocument);
+    
+    // We need to insert an empty/temporary <span /> to fix IE quirks
+    // Elsewise IE would strip white space in the beginning
+    tempElement.innerHTML = "<span></span>" + _convertUrlsToLinks(textNode.data);
+    tempElement.removeChild(tempElement.firstChild);
+    
+    while (tempElement.firstChild) {
+      // inserts tempElement.firstChild before textNode
+      parentNode.insertBefore(tempElement.firstChild, textNode);
+    }
+    parentNode.removeChild(textNode);
+  }
+  
+  function _hasParentThatShouldBeIgnored(node) {
+    var nodeName;
+    while (node.parentNode) {
+      node = node.parentNode;
+      nodeName = node.nodeName;
+      if (IGNORE_URLS_IN.contains(nodeName)) {
+        return true;
+      } else if (nodeName === "body") {
+        return false;
+      }
+    }
+    return false;
+  }
+  
+  function _parseNode(element) {
+    if (IGNORE_URLS_IN.contains(element.nodeName)) {
+      return;
+    }
+    
+    if (element.nodeType === wysihtml5.TEXT_NODE && element.data.match(URL_REG_EXP)) {
+      _wrapMatchesInNode(element);
+      return;
+    }
+    
+    var childNodes        = wysihtml5.lang.array(element.childNodes).get(),
+        childNodesLength  = childNodes.length,
+        i                 = 0;
+    
+    for (; i<childNodesLength; i++) {
+      _parseNode(childNodes[i]);
+    }
+    
+    return element;
+  }
+  
+  wysihtml5.dom.autoLink = autoLink;
+  
+  // Reveal url reg exp to the outside
+  wysihtml5.dom.autoLink.URL_REG_EXP = URL_REG_EXP;
+})(wysihtml5);(function(wysihtml5) {
+  var supportsClassList = wysihtml5.browser.supportsClassList(),
+      api               = wysihtml5.dom;
+  
+  api.addClass = function(element, className) {
+    if (supportsClassList) {
+      return element.classList.add(className);
+    }
+    if (api.hasClass(element, className)) {
+      return;
+    }
+    element.className += " " + className;
+  };
+  
+  api.removeClass = function(element, className) {
+    if (supportsClassList) {
+      return element.classList.remove(className);
+    }
+    
+    element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ");
+  };
+  
+  api.hasClass = function(element, className) {
+    if (supportsClassList) {
+      return element.classList.contains(className);
+    }
+    
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  };
+})(wysihtml5);
+wysihtml5.dom.contains = (function() {
+  var documentElement = document.documentElement;
+  if (documentElement.contains) {
+    return function(container, element) {
+      if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
+        element = element.parentNode;
+      }
+      return container !== element && container.contains(element);
+    };
+  } else if (documentElement.compareDocumentPosition) {
+    return function(container, element) {
+      // https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition
+      return !!(container.compareDocumentPosition(element) & 16);
+    };
+  }
+})();/**
+ * Converts an HTML fragment/element into a unordered/ordered list
+ *
+ * @param {Element} element The element which should be turned into a list
+ * @param {String} listType The list type in which to convert the tree (either "ul" or "ol")
+ * @return {Element} The created list
+ *
+ * @example
+ *    <!-- Assume the following dom: -->
+ *    <span id="pseudo-list">
+ *      eminem<br>
+ *      dr. dre
+ *      <div>50 Cent</div>
+ *    </span>
+ *
+ *    <script>
+ *      wysihtml5.dom.convertToList(document.getElementById("pseudo-list"), "ul");
+ *    </script>
+ *
+ *    <!-- Will result in: -->
+ *    <ul>
+ *      <li>eminem</li>
+ *      <li>dr. dre</li>
+ *      <li>50 Cent</li>
+ *    </ul>
+ */
+wysihtml5.dom.convertToList = (function() {
+  function _createListItem(doc, list) {
+    var listItem = doc.createElement("li");
+    list.appendChild(listItem);
+    return listItem;
+  }
+  
+  function _createList(doc, type) {
+    return doc.createElement(type);
+  }
+  
+  function convertToList(element, listType) {
+    if (element.nodeName === "UL" || element.nodeName === "OL" || element.nodeName === "MENU") {
+      // Already a list
+      return element;
+    }
+    
+    var doc               = element.ownerDocument,
+        list              = _createList(doc, listType),
+        childNodes        = wysihtml5.lang.array(element.childNodes).get(),
+        childNodesLength  = childNodes.length,
+        childNode,
+        isBlockElement,
+        isLineBreak,
+        currentListItem,
+        i                 = 0;
+    for (; i<childNodesLength; i++) {
+      currentListItem = currentListItem || _createListItem(doc, list);
+      childNode       = childNodes[i];
+      isBlockElement  = wysihtml5.dom.getStyle("display").from(childNode) === "block";
+      isLineBreak     = childNode.nodeName === "BR";
+      
+      if (isBlockElement) {
+        // Append blockElement to current <li> if empty, otherwise create a new one
+        currentListItem = currentListItem.firstChild ? _createListItem(doc, list) : currentListItem;
+        currentListItem.appendChild(childNode);
+        currentListItem = null;
+        continue;
+      }
+      
+      if (isLineBreak) {
+        // Only create a new list item in the next iteration when the current one has already content
+        currentListItem = currentListItem.firstChild ? null : currentListItem;
+        continue;
+      }
+      
+      currentListItem.appendChild(childNode);
+    }
+    
+    element.parentNode.replaceChild(list, element);
+    return list;
+  }
+  
+  return convertToList;
+})();/**
+ * Copy a set of attributes from one element to another
+ *
+ * @param {Array} attributesToCopy List of attributes which should be copied
+ * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
+ *    copy the attributes from., this again returns an object which provides a method named "to" which can be invoked 
+ *    with the element where to copy the attributes to (see example)
+ *
+ * @example
+ *    var textarea    = document.querySelector("textarea"),
+ *        div         = document.querySelector("div[contenteditable=true]"),
+ *        anotherDiv  = document.querySelector("div.preview");
+ *    wysihtml5.dom.copyAttributes(["spellcheck", "value", "placeholder"]).from(textarea).to(div).andTo(anotherDiv);
+ *
+ */
+wysihtml5.dom.copyAttributes = function(attributesToCopy) {
+  return {
+    from: function(elementToCopyFrom) {
+      return {
+        to: function(elementToCopyTo) {
+          var attribute,
+              i         = 0,
+              length    = attributesToCopy.length;
+          for (; i<length; i++) {
+            attribute = attributesToCopy[i];
+            if (elementToCopyFrom[attribute]) {
+              elementToCopyTo[attribute] = elementToCopyFrom[attribute];
+            }
+          }
+          return { andTo: arguments.callee };
+        }
+      };
+    }
+  };
+};/**
+ * Copy a set of styles from one element to another
+ * Please note that this only works properly across browsers when the element from which to copy the styles
+ * is in the dom
+ *
+ * Interesting article on how to copy styles
+ *
+ * @param {Array} stylesToCopy List of styles which should be copied
+ * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
+ *    copy the styles from., this again returns an object which provides a method named "to" which can be invoked 
+ *    with the element where to copy the styles to (see example)
+ *
+ * @example
+ *    var textarea    = document.querySelector("textarea"),
+ *        div         = document.querySelector("div[contenteditable=true]"),
+ *        anotherDiv  = document.querySelector("div.preview");
+ *    wysihtml5.dom.copyStyles(["overflow-y", "width", "height"]).from(textarea).to(div).andTo(anotherDiv);
+ *
+ */
+(function(dom) {
+  
+  /**
+   * Mozilla, WebKit and Opera recalculate the computed width when box-sizing: boder-box; is set
+   * So if an element has "width: 200px; -moz-box-sizing: border-box; border: 1px;" then 
+   * its computed css width will be 198px
+   */
+  var BOX_SIZING_PROPERTIES = ["-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing"];
+  
+  var shouldIgnoreBoxSizingBorderBox = function(element) {
+    if (hasBoxSizingBorderBox(element)) {
+       return parseInt(dom.getStyle("width").from(element), 10) < element.offsetWidth;
+    }
+    return false;
+  };
+  
+  var hasBoxSizingBorderBox = function(element) {
+    var i       = 0,
+        length  = BOX_SIZING_PROPERTIES.length;
+    for (; i<length; i++) {
+      if (dom.getStyle(BOX_SIZING_PROPERTIES[i]).from(element) === "border-box") {
+        return BOX_SIZING_PROPERTIES[i];
+      }
+    }
+  };
+  
+  dom.copyStyles = function(stylesToCopy) {
+    return {
+      from: function(element) {
+        if (shouldIgnoreBoxSizingBorderBox(element)) {
+          stylesToCopy = wysihtml5.lang.array(stylesToCopy).without(BOX_SIZING_PROPERTIES);
+        }
+        
+        var cssText = "",
+            length  = stylesToCopy.length,
+            i       = 0,
+            property;
+        for (; i<length; i++) {
+          property = stylesToCopy[i];
+          cssText += property + ":" + dom.getStyle(property).from(element) + ";";
+        }
+        
+        return {
+          to: function(element) {
+            dom.setStyles(cssText).on(element);
+            return { andTo: arguments.callee };
+          }
+        };
+      }
+    };
+  };
+})(wysihtml5.dom);/**
+ * Event Delegation
+ *
+ * @example
+ *    wysihtml5.dom.delegate(document.body, "a", "click", function() {
+ *      // foo
+ *    });
+ */
+(function(wysihtml5) {
+  
+  wysihtml5.dom.delegate = function(container, selector, eventName, handler) {
+    return wysihtml5.dom.observe(container, eventName, function(event) {
+      var target    = event.target,
+          match     = wysihtml5.lang.array(container.querySelectorAll(selector));
+      
+      while (target && target !== container) {
+        if (match.contains(target)) {
+          handler.call(target, event);
+          break;
+        }
+        target = target.parentNode;
+      }
+    });
+  };
+  
+})(wysihtml5);/**
+ * Returns the given html wrapped in a div element
+ *
+ * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly
+ * when inserted via innerHTML
+ * 
+ * @param {String} html The html which should be wrapped in a dom element
+ * @param {Obejct} [context] Document object of the context the html belongs to
+ *
+ * @example
+ *    wysihtml5.dom.getAsDom("<article>foo</article>");
+ */
+wysihtml5.dom.getAsDom = (function() {
+  
+  var _innerHTMLShiv = function(html, context) {
+    var tempElement = context.createElement("div");
+    tempElement.style.display = "none";
+    context.body.appendChild(tempElement);
+    // IE throws an exception when trying to insert <frameset></frameset> via innerHTML
+    try { tempElement.innerHTML = html; } catch(e) {}
+    context.body.removeChild(tempElement);
+    return tempElement;
+  };
+  
+  /**
+   * Make sure IE supports HTML5 tags, which is accomplished by simply creating one instance of each element
+   */
+  var _ensureHTML5Compatibility = function(context) {
+    if (context._wysihtml5_supportsHTML5Tags) {
+      return;
+    }
+    for (var i=0, length=HTML5_ELEMENTS.length; i<length; i++) {
+      context.createElement(HTML5_ELEMENTS[i]);
+    }
+    context._wysihtml5_supportsHTML5Tags = true;
+  };
+  
+  
+  /**
+   * List of html5 tags
+   * taken from http://simon.html5.org/html5-elements
+   */
+  var HTML5_ELEMENTS = [
+    "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption",
+    "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress",
+    "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr"
+  ];
+  
+  return function(html, context) {
+    context = context || document;
+    var tempElement;
+    if (typeof(html) === "object" && html.nodeType) {
+      tempElement = context.createElement("div");
+      tempElement.appendChild(html);
+    } else if (wysihtml5.browser.supportsHTML5Tags(context)) {
+      tempElement = context.createElement("div");
+      tempElement.innerHTML = html;
+    } else {
+      _ensureHTML5Compatibility(context);
+      tempElement = _innerHTMLShiv(html, context);
+    }
+    return tempElement;
+  };
+})();/**
+ * Walks the dom tree from the given node up until it finds a match
+ * Designed for optimal performance.
+ *
+ * @param {Element} node The from which to check the parent nodes
+ * @param {Object} matchingSet Object to match against (possible properties: nodeName, className, classRegExp)
+ * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50)
+ * @return {null|Element} Returns the first element that matched the desiredNodeName(s)
+ * @example
+ *    var listElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: ["MENU", "UL", "OL"] });
+ *    // ... or ...
+ *    var unorderedListElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: "UL" });
+ *    // ... or ...
+ *    var coloredElement = wysihtml5.dom.getParentElement(myTextNode, { nodeName: "SPAN", className: "wysiwyg-color-red", classRegExp: /wysiwyg-color-[a-z]/g });
+ */
+wysihtml5.dom.getParentElement = (function() {
+  
+  function _isSameNodeName(nodeName, desiredNodeNames) {
+    if (!desiredNodeNames || !desiredNodeNames.length) {
+      return true;
+    }
+    
+    if (typeof(desiredNodeNames) === "string") {
+      return nodeName === desiredNodeNames;
+    } else {
+      return wysihtml5.lang.array(desiredNodeNames).contains(nodeName);
+    }
+  }
+  
+  function _isElement(node) {
+    return node.nodeType === wysihtml5.ELEMENT_NODE;
+  }
+  
+  function _hasClassName(element, className, classRegExp) {
+    var classNames = (element.className || "").match(classRegExp) || [];
+    if (!className) {
+      return !!classNames.length;
+    }
+    return classNames[classNames.length - 1] === className;
+  }
+  
+  function _getParentElementWithNodeName(node, nodeName, levels) {
+    while (levels-- && node && node.nodeName !== "BODY") {
+      if (_isSameNodeName(node.nodeName, nodeName)) {
+        return node;
+      }
+      node = node.parentNode;
+    }
+    return null;
+  }
+  
+  function _getParentElementWithNodeNameAndClassName(node, nodeName, className, classRegExp, levels) {
+    while (levels-- && node && node.nodeName !== "BODY") {
+      if (_isElement(node) &&
+          _isSameNodeName(node.nodeName, nodeName) &&
+          _hasClassName(node, className, classRegExp)) {
+        return node;
+      }
+      node = node.parentNode;
+    }
+    return null;
+  }
+  
+  return function(node, matchingSet, levels) {
+    levels = levels || 50; // Go max 50 nodes upwards from current node
+    if (matchingSet.className || matchingSet.classRegExp) {
+      return _getParentElementWithNodeNameAndClassName(
+        node, matchingSet.nodeName, matchingSet.className, matchingSet.classRegExp, levels
+      );
+    } else {
+      return _getParentElementWithNodeName(
+        node, matchingSet.nodeName, levels
+      );
+    }
+  };
+})();
+/**
+ * Get element's style for a specific css property
+ *
+ * @param {Element} element The element on which to retrieve the style
+ * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...)
+ *
+ * @example
+ *    wysihtml5.dom.getStyle("display").from(document.body);
+ *    // => "block"
+ */
+wysihtml5.dom.getStyle = (function() {
+  var stylePropertyMapping = {
+        "float": ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat"
+      },
+      REG_EXP_CAMELIZE = /\-[a-z]/g;
+  
+  function camelize(str) {
+    return str.replace(REG_EXP_CAMELIZE, function(match) {
+      return match.charAt(1).toUpperCase();
+    });
+  }
+  
+  return function(property) {
+    return {
+      from: function(element) {
+        if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
+          return;
+        }
+        
+        var doc               = element.ownerDocument,
+            camelizedProperty = stylePropertyMapping[property] || camelize(property),
+            style             = element.style,
+            currentStyle      = element.currentStyle,
+            styleValue        = style[camelizedProperty];
+        if (styleValue) {
+          return styleValue;
+        }
+        
+        // currentStyle is no standard and only supported by Opera and IE but it has one important advantage over the standard-compliant
+        // window.getComputedStyle, since it returns css property values in their original unit:
+        // If you set an elements width to "50%", window.getComputedStyle will give you it's current width in px while currentStyle
+        // gives you the original "50%".
+        // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio
+        if (currentStyle) {
+          try {
+                return currentStyle[camelizedProperty];
+          } catch(e) {
+            //ie will occasionally fail for unknown reasons. swallowing exception
+          }
+        }
+
+        var win                 = doc.defaultView || doc.parentWindow,
+            needsOverflowReset  = (property === "height" || property === "width") && element.nodeName === "TEXTAREA",
+            originalOverflow,
+            returnValue;
+
+        if (win.getComputedStyle) {
+          // Chrome and Safari both calculate a wrong width and height for textareas when they have scroll bars
+          // therfore we remove and restore the scrollbar and calculate the value in between
+          if (needsOverflowReset) {
+            originalOverflow = style.overflow;
+            style.overflow = "hidden";
+          }
+          returnValue = win.getComputedStyle(element, null).getPropertyValue(property);
+          if (needsOverflowReset) {
+            style.overflow = originalOverflow || "";
+          }
+          return returnValue;
+        }
+      }
+    };
+  };
+})();/**
+ * High performant way to check whether an element with a specific tag name is in the given document
+ * Optimized for being heavily executed
+ * Unleashes the power of live node lists
+ *
+ * @param {Object} doc The document object of the context where to check
+ * @param {String} tagName Upper cased tag name
+ * @example
+ *    wysihtml5.dom.hasElementWithTagName(document, "IMG");
+ */
+wysihtml5.dom.hasElementWithTagName = (function() {
+  var LIVE_CACHE          = {},
+      DOCUMENT_IDENTIFIER = 1;
+  
+  function _getDocumentIdentifier(doc) {
+    return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++);
+  }
+  
+  return function(doc, tagName) {
+    var key         = _getDocumentIdentifier(doc) + ":" + tagName,
+        cacheEntry  = LIVE_CACHE[key];
+    if (!cacheEntry) {
+      cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tagName);
+    }
+    
+    return cacheEntry.length > 0;
+  };
+})();/**
+ * High performant way to check whether an element with a specific class name is in the given document
+ * Optimized for being heavily executed
+ * Unleashes the power of live node lists
+ *
+ * @param {Object} doc The document object of the context where to check
+ * @param {String} tagName Upper cased tag name
+ * @example
+ *    wysihtml5.dom.hasElementWithClassName(document, "foobar");
+ */
+(function(wysihtml5) {
+  var LIVE_CACHE          = {},
+      DOCUMENT_IDENTIFIER = 1;
+
+  function _getDocumentIdentifier(doc) {
+    return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++);
+  }
+  
+  wysihtml5.dom.hasElementWithClassName = function(doc, className) {
+    // getElementsByClassName is not supported by IE<9
+    // but is sometimes mocked via library code (which then doesn't return live node lists)
+    if (!wysihtml5.browser.supportsNativeGetElementsByClassName()) {
+      return !!doc.querySelector("." + className);
+    }
+
+    var key         = _getDocumentIdentifier(doc) + ":" + className,
+        cacheEntry  = LIVE_CACHE[key];
+    if (!cacheEntry) {
+      cacheEntry = LIVE_CACHE[key] = doc.getElementsByClassName(className);
+    }
+
+    return cacheEntry.length > 0;
+  };
+})(wysihtml5);
+wysihtml5.dom.insert = function(elementToInsert) {
+  return {
+    after: function(element) {
+      element.parentNode.insertBefore(elementToInsert, element.nextSibling);
+    },
+    
+    before: function(element) {
+      element.parentNode.insertBefore(elementToInsert, element);
+    },
+    
+    into: function(element) {
+      element.appendChild(elementToInsert);
+    }
+  };
+};wysihtml5.dom.insertCSS = function(rules) {
+  rules = rules.join("\n");
+  
+  return {
+    into: function(doc) {
+      var head         = doc.head || doc.getElementsByTagName("head")[0],
+          styleElement = doc.createElement("style");
+
+      styleElement.type = "text/css";
+
+      if (styleElement.styleSheet) {
+        styleElement.styleSheet.cssText = rules;
+      } else {
+        styleElement.appendChild(doc.createTextNode(rules));
+      }
+
+      if (head) {
+        head.appendChild(styleElement);
+      }
+    }
+  };
+};/**
+ * Method to set dom events
+ *
+ * @example
+ *    wysihtml5.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... });
+ */
+wysihtml5.dom.observe = function(element, eventNames, handler) {
+  eventNames = typeof(eventNames) === "string" ? [eventNames] : eventNames;
+  
+  var handlerWrapper,
+      eventName,
+      i       = 0,
+      length  = eventNames.length;
+  
+  for (; i<length; i++) {
+    eventName = eventNames[i];
+    if (element.addEventListener) {
+      element.addEventListener(eventName, handler, false);
+    } else {
+      handlerWrapper = function(event) {
+        if (!("target" in event)) {
+          event.target = event.srcElement;
+        }
+        event.preventDefault = event.preventDefault || function() {
+          this.returnValue = false;
+        };
+        event.stopPropagation = event.stopPropagation || function() {
+          this.cancelBubble = true;
+        };
+        handler.call(element, event);
+      };
+      element.attachEvent("on" + eventName, handlerWrapper);
+    }
+  }
+  
+  return {
+    stop: function() {
+      var eventName,
+          i       = 0,
+          length  = eventNames.length;
+      for (; i<length; i++) {
+        eventName = eventNames[i];
+        if (element.removeEventListener) {
+          element.removeEventListener(eventName, handler, false);
+        } else {
+          element.detachEvent("on" + eventName, handlerWrapper);
+        }
+      }
+    }
+  };
+};
+/**
+ * HTML Sanitizer
+ * Rewrites the HTML based on given rules
+ *
+ * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized
+ * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will
+ *    be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the
+ *    desired substitution.
+ * @param {Object} context Document object in which to parse the html, needed to sandbox the parsing
+ *
+ * @return {Element|String} Depends on the elementOrHtml parameter. When html then the sanitized html as string elsewise the element.
+ *
+ * @example
+ *    var userHTML = '<div id="foo" onclick="alert(1);"><p><font color="red">foo</font><script>alert(1);</script></p></div>';
+ *    wysihtml5.dom.parse(userHTML, {
+ *      tags {
+ *        p:      "div",      // Rename p tags to div tags
+ *        font:   "span"      // Rename font tags to span tags
+ *        div:    true,       // Keep them, also possible (same result when passing: "div" or true)
+ *        script: undefined   // Remove script elements
+ *      }
+ *    });
+ *    // => <div><div><span>foo bar</span></div></div>
+ *
+ *    var userHTML = '<table><tbody><tr><td>I'm a table!</td></tr></tbody></table>';
+ *    wysihtml5.dom.parse(userHTML);
+ *    // => '<span><span><span><span>I'm a table!</span></span></span></span>'
+ *
+ *    var userHTML = '<div>foobar<br>foobar</div>';
+ *    wysihtml5.dom.parse(userHTML, {
+ *      tags: {
+ *        div: undefined,
+ *        br:  true
+ *      }
+ *    });
+ *    // => ''
+ *
+ *    var userHTML = '<div class="red">foo</div><div class="pink">bar</div>';
+ *    wysihtml5.dom.parse(userHTML, {
+ *      classes: {
+ *        red:    1,
+ *        green:  1
+ *      },
+ *      tags: {
+ *        div: {
+ *          rename_tag:     "p"
+ *        }
+ *      }
+ *    });
+ *    // => '<p class="red">foo</p><p>bar</p>'
+ */
+wysihtml5.dom.parse = (function() {
+  
+  /**
+   * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML
+   * new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the
+   * node isn't closed
+   *
+   * Therefore we've to use the browser's ordinary HTML parser invoked by setting innerHTML.
+   */
+  var NODE_TYPE_MAPPING = {
+        "1": _handleElement,
+        "3": _handleText
+      },
+      // Rename unknown tags to this
+      DEFAULT_NODE_NAME   = "span",
+      WHITE_SPACE_REG_EXP = /\s+/,
+      defaultRules        = { tags: {}, classes: {} },
+      currentRules        = {};
+  
+  /**
+   * Iterates over all childs of the element, recreates them, appends them into a document fragment
+   * which later replaces the entire body content
+   */
+  function parse(elementOrHtml, rules, context, cleanUp) {
+    wysihtml5.lang.object(currentRules).merge(defaultRules).merge(rules).get();
+    
+    context           = context || elementOrHtml.ownerDocument || document;
+    var fragment      = context.createDocumentFragment(),
+        isString      = typeof(elementOrHtml) === "string",
+        element,
+        newNode,
+        firstChild;
+    
+    if (isString) {
+      element = wysihtml5.dom.getAsDom(elementOrHtml, context);
+    } else {
+      element = elementOrHtml;
+    }
+    
+    while (element.firstChild) {
+      firstChild  = element.firstChild;
+      element.removeChild(firstChild);
+      newNode = _convert(firstChild, cleanUp);
+      if (newNode) {
+        fragment.appendChild(newNode);
+      }
+    }
+    
+    // Clear element contents
+    element.innerHTML = "";
+    
+    // Insert new DOM tree
+    element.appendChild(fragment);
+    
+    return isString ? wysihtml5.quirks.getCorrectInnerHTML(element) : element;
+  }
+  
+  function _convert(oldNode, cleanUp) {
+    var oldNodeType     = oldNode.nodeType,
+        oldChilds       = oldNode.childNodes,
+        oldChildsLength = oldChilds.length,
+        newNode,
+        method          = NODE_TYPE_MAPPING[oldNodeType],
+        i               = 0;
+    
+    newNode = method && method(oldNode);
+    
+    if (!newNode) {
+      return null;
+    }
+    
+    for (i=0; i<oldChildsLength; i++) {
+      newChild = _convert(oldChilds[i], cleanUp);
+      if (newChild) {
+        newNode.appendChild(newChild);
+      }
+    }
+    
+    // Cleanup senseless <span> elements
+    if (cleanUp &&
+        newNode.childNodes.length <= 1 &&
+        newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME &&
+        !newNode.attributes.length) {
+      return newNode.firstChild;
+    }
+    
+    return newNode;
+  }
+  
+  function _handleElement(oldNode) {
+    var rule,
+        newNode,
+        endTag,
+        tagRules    = currentRules.tags,
+        nodeName    = oldNode.nodeName.toLowerCase(),
+        scopeName   = oldNode.scopeName;
+    
+    /**
+     * We already parsed that element
+     * ignore it! (yes, this sometimes happens in IE8 when the html is invalid)
+     */
+    if (oldNode._wysihtml5) {
+      return null;
+    }
+    oldNode._wysihtml5 = 1;
+    
+    if (oldNode.className === "wysihtml5-temp") {
+      return null;
+    }
+    
+    /**
+     * IE is the only browser who doesn't include the namespace in the
+     * nodeName, that's why we have to prepend it by ourselves
+     * scopeName is a proprietary IE feature
+     * read more here http://msdn.microsoft.com/en-us/library/ms534388(v=vs.85).aspx
+     */
+    if (scopeName && scopeName != "HTML") {
+      nodeName = scopeName + ":" + nodeName;
+    }
+    
+    /**
+     * Repair node
+     * IE is a bit bitchy when it comes to invalid nested markup which includes unclosed tags
+     * A <p> doesn't need to be closed according HTML4-5 spec, we simply replace it with a <div> to preserve its content and layout
+     */
+    if ("outerHTML" in oldNode) {
+      if (!wysihtml5.browser.autoClosesUnclosedTags() &&
+          oldNode.nodeName === "P" &&
+          oldNode.outerHTML.slice(-4).toLowerCase() !== "</p>") {
+        nodeName = "div";
+      }
+    }
+    
+    if (nodeName in tagRules) {
+      rule = tagRules[nodeName];
+      if (!rule || rule.remove) {
+        return null;
+      }
+      
+      rule = typeof(rule) === "string" ? { rename_tag: rule } : rule;
+    } else if (oldNode.firstChild) {
+      rule = { rename_tag: DEFAULT_NODE_NAME };
+    } else {
+      // Remove empty unknown elements
+      return null;
+    }
+    
+    newNode = oldNode.ownerDocument.createElement(rule.rename_tag || nodeName);
+    _handleAttributes(oldNode, newNode, rule);
+    
+    oldNode = null;
+    return newNode;
+  }
+  
+  function _handleAttributes(oldNode, newNode, rule) {
+    var attributes          = {},                         // fresh new set of attributes to set on newNode
+        setClass            = rule.set_class,             // classes to set
+        addClass            = rule.add_class,             // add classes based on existing attributes
+        setAttributes       = rule.set_attributes,        // attributes to set on the current node
+        checkAttributes     = rule.check_attributes,      // check/convert values of attributes
+        allowedClasses      = currentRules.classes,
+        i                   = 0,
+        classes             = [],
+        newClasses          = [],
+        newUniqueClasses    = [],
+        oldClasses          = [],
+        classesLength,
+        newClassesLength,
+        currentClass,
+        newClass,
+        attributeName,
+        newAttributeValue,
+        method;
+    
+    if (setAttributes) {
+      attributes = wysihtml5.lang.object(setAttributes).clone();
+    }
+    
+    if (checkAttributes) {
+      for (attributeName in checkAttributes) {
+        method = attributeCheckMethods[checkAttributes[attributeName]];
+        if (!method) {
+          continue;
+        }
+        newAttributeValue = method(_getAttribute(oldNode, attributeName));
+        if (typeof(newAttributeValue) === "string") {
+          attributes[attributeName] = newAttributeValue;
+        }
+      }
+    }
+    
+    if (setClass) {
+      classes.push(setClass);
+    }
+    
+    if (addClass) {
+      for (attributeName in addClass) {
+        method = addClassMethods[addClass[attributeName]];
+        if (!method) {
+          continue;
+        }
+        newClass = method(_getAttribute(oldNode, attributeName));
+        if (typeof(newClass) === "string") {
+          classes.push(newClass);
+        }
+      }
+    }
+    
+    // make sure that wysihtml5 temp class doesn't get stripped out
+    allowedClasses["_wysihtml5-temp-placeholder"] = 1;
+    
+    // add old classes last
+    oldClasses = oldNode.getAttribute("class");
+    if (oldClasses) {
+      classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
+    }
+    classesLength = classes.length;
+    for (; i<classesLength; i++) {
+      currentClass = classes[i];
+      if (allowedClasses[currentClass]) {
+        newClasses.push(currentClass);
+      }
+    }
+    
+    // remove duplicate entries and preserve class specificity
+    newClassesLength = newClasses.length;
+    while (newClassesLength--) {
+      currentClass = newClasses[newClassesLength];
+      if (!wysihtml5.lang.array(newUniqueClasses).contains(currentClass)) {
+        newUniqueClasses.unshift(currentClass);
+      }
+    }
+    
+    if (newUniqueClasses.length) {
+      attributes["class"] = newUniqueClasses.join(" ");
+    }
+    
+    // set attributes on newNode
+    for (attributeName in attributes) {
+      // Setting attributes can cause a js error in IE under certain circumstances
+      // eg. on a <img> under https when it's new attribute value is non-https
+      // TODO: Investigate this further and check for smarter handling
+      try {
+        newNode.setAttribute(attributeName, attributes[attributeName]);
+      } catch(e) {}
+    }
+    
+    // IE8 sometimes loses the width/height attributes when those are set before the "src"
+    // so we make sure to set them again
+    if (attributes.src) {
+      if (typeof(attributes.width) !== "undefined") {
+        newNode.setAttribute("width", attributes.width);
+      }
+      if (typeof(attributes.height) !== "undefined") {
+        newNode.setAttribute("height", attributes.height);
+      }
+    }
+  }
+  
+  /**
+   * IE gives wrong results for hasAttribute/getAttribute, for example:
+   *    var td = document.createElement("td");
+   *    td.getAttribute("rowspan"); // => "1" in IE
+   *
+   * Therefore we have to check the element's outerHTML for the attribute
+   */
+  var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
+  function _getAttribute(node, attributeName) {
+    attributeName = attributeName.toLowerCase();
+    var nodeName = node.nodeName;
+    if (nodeName == "IMG" && attributeName == "src" && _isLoadedImage(node) === true) {
+      // Get 'src' attribute value via object property since this will always contain the
+      // full absolute url (http://...)
+      // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
+      // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
+      return node.src;
+    } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
+      // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
+      var outerHTML      = node.outerHTML.toLowerCase(),
+          // TODO: This might not work for attributes without value: <input disabled>
+          hasAttribute   = outerHTML.indexOf(" " + attributeName +  "=") != -1;
+      
+      return hasAttribute ? node.getAttribute(attributeName) : null;
+    } else{
+      return node.getAttribute(attributeName);
+    }
+  }
+  
+  /**
+   * Check whether the given node is a proper loaded image
+   * FIXME: Returns undefined when unknown (Chrome, Safari)
+   */
+  function _isLoadedImage(node) {
+    try {
+      return node.complete && !node.mozMatchesSelector(":-moz-broken");
+    } catch(e) {
+      if (node.complete && node.readyState === "complete") {
+        return true;
+      }
+    }
+  }
+  
+  function _handleText(oldNode) {
+    return oldNode.ownerDocument.createTextNode(oldNode.data);
+  }
+  
+  
+  // ------------ attribute checks ------------ \\
+  var attributeCheckMethods = {
+    url: (function() {
+      var REG_EXP = /^https?:\/\//i;
+      return function(attributeValue) {
+        if (!attributeValue || !attributeValue.match(REG_EXP)) {
+          return null;
+        }
+        return attributeValue.replace(REG_EXP, function(match) {
+          return match.toLowerCase();
+        });
+      };
+    })(),
+    
+    alt: (function() {
+      var REG_EXP = /[^ a-z0-9_\-]/gi;
+      return function(attributeValue) {
+        if (!attributeValue) {
+          return "";
+        }
+        return attributeValue.replace(REG_EXP, "");
+      };
+    })(),
+    
+    numbers: (function() {
+      var REG_EXP = /\D/g;
+      return function(attributeValue) {
+        attributeValue = (attributeValue || "").replace(REG_EXP, "");
+        return attributeValue || null;
+      };
+    })()
+  };
+  
+  // ------------ class converter (converts an html attribute to a class name) ------------ \\
+  var addClassMethods = {
+    align_img: (function() {
+      var mapping = {
+        left:   "wysiwyg-float-left",
+        right:  "wysiwyg-float-right"
+      };
+      return function(attributeValue) {
+        return mapping[String(attributeValue).toLowerCase()];
+      };
+    })(),
+    
+    align_text: (function() {
+      var mapping = {
+        left:     "wysiwyg-text-align-left",
+        right:    "wysiwyg-text-align-right",
+        center:   "wysiwyg-text-align-center",
+        justify:  "wysiwyg-text-align-justify"
+      };
+      return function(attributeValue) {
+        return mapping[String(attributeValue).toLowerCase()];
+      };
+    })(),
+    
+    clear_br: (function() {
+      var mapping = {
+        left:   "wysiwyg-clear-left",
+        right:  "wysiwyg-clear-right",
+        both:   "wysiwyg-clear-both",
+        all:    "wysiwyg-clear-both"
+      };
+      return function(attributeValue) {
+        return mapping[String(attributeValue).toLowerCase()];
+      };
+    })(),
+    
+    size_font: (function() {
+      var mapping = {
+        "1": "wysiwyg-font-size-xx-small",
+        "2": "wysiwyg-font-size-small",
+        "3": "wysiwyg-font-size-medium",
+        "4": "wysiwyg-font-size-large",
+        "5": "wysiwyg-font-size-x-large",
+        "6": "wysiwyg-font-size-xx-large",
+        "7": "wysiwyg-font-size-xx-large",
+        "-": "wysiwyg-font-size-smaller",
+        "+": "wysiwyg-font-size-larger"
+      };
+      return function(attributeValue) {
+        return mapping[String(attributeValue).charAt(0)];
+      };
+    })()
+  };
+  
+  return parse;
+})();/**
+ * Checks for empty text node childs and removes them
+ *
+ * @param {Element} node The element in which to cleanup
+ * @example
+ *    wysihtml5.dom.removeEmptyTextNodes(element);
+ */
+wysihtml5.dom.removeEmptyTextNodes = function(node) {
+  var childNode,
+      childNodes        = wysihtml5.lang.array(node.childNodes).get(),
+      childNodesLength  = childNodes.length,
+      i                 = 0;
+  for (; i<childNodesLength; i++) {
+    childNode = childNodes[i];
+    if (childNode.nodeType === wysihtml5.TEXT_NODE && childNode.data === "") {
+      childNode.parentNode.removeChild(childNode);
+    }
+  }
+};
+/**
+ * Renames an element (eg. a <div> to a <p>) and keeps its childs
+ *
+ * @param {Element} element The list element which should be renamed
+ * @param {Element} newNodeName The desired tag name
+ *
+ * @example
+ *    <!-- Assume the following dom: -->
+ *    <ul id="list">
+ *      <li>eminem</li>
+ *      <li>dr. dre</li>
+ *      <li>50 Cent</li>
+ *    </ul>
+ *
+ *    <script>
+ *      wysihtml5.dom.renameElement(document.getElementById("list"), "ol");
+ *    </script>
+ *
+ *    <!-- Will result in: -->
+ *    <ol>
+ *      <li>eminem</li>
+ *      <li>dr. dre</li>
+ *      <li>50 Cent</li>
+ *    </ol>
+ */
+wysihtml5.dom.renameElement = function(element, newNodeName) {
+  var newElement = element.ownerDocument.createElement(newNodeName),
+      firstChild;
+  while (firstChild = element.firstChild) {
+    newElement.appendChild(firstChild);
+  }
+  wysihtml5.dom.copyAttributes(["align", "className"]).from(element).to(newElement);
+  element.parentNode.replaceChild(newElement, element);
+  return newElement;
+};/**
+ * Takes an element, removes it and replaces it with it's childs
+ * 
+ * @param {Object} node The node which to replace with it's child nodes
+ * @example
+ *    <div id="foo">
+ *      <span>hello</span>
+ *    </div>
+ *    <script>
+ *      // Remove #foo and replace with it's children
+ *      wysihtml5.dom.replaceWithChildNodes(document.getElementById("foo"));
+ *    </script>
+ */
+wysihtml5.dom.replaceWithChildNodes = function(node) {
+  if (!node.parentNode) {
+    return;
+  }
+  
+  if (!node.firstChild) {
+    node.parentNode.removeChild(node);
+    return;
+  }
+  
+  var fragment = node.ownerDocument.createDocumentFragment();
+  while (node.firstChild) {
+    fragment.appendChild(node.firstChild);
+  }
+  node.parentNode.replaceChild(fragment, node);
+  node = fragment = null;
+};
+/**
+ * Unwraps an unordered/ordered list
+ *
+ * @param {Element} element The list element which should be unwrapped
+ *
+ * @example
+ *    <!-- Assume the following dom: -->
+ *    <ul id="list">
+ *      <li>eminem</li>
+ *      <li>dr. dre</li>
+ *      <li>50 Cent</li>
+ *    </ul>
+ *
+ *    <script>
+ *      wysihtml5.dom.resolveList(document.getElementById("list"));
+ *    </script>
+ *
+ *    <!-- Will result in: -->
+ *    eminem<br>
+ *    dr. dre<br>
+ *    50 Cent<br>
+ */
+(function(dom) {
+  function _isBlockElement(node) {
+    return dom.getStyle("display").from(node) === "block";
+  }
+  
+  function _isLineBreak(node) {
+    return node.nodeName === "BR";
+  }
+  
+  function _appendLineBreak(element) {
+    var lineBreak = element.ownerDocument.createElement("br");
+    element.appendChild(lineBreak);
+  }
+  
+  function resolveList(list) {
+    if (list.nodeName !== "MENU" && list.nodeName !== "UL" && list.nodeName !== "OL") {
+      return;
+    }
+    
+    var doc             = list.ownerDocument,
+        fragment        = doc.createDocumentFragment(),
+        previousSibling = list.previousSibling,
+        firstChild,
+        lastChild,
+        isLastChild,
+        shouldAppendLineBreak,
+        listItem;
+    
+    if (previousSibling && !_isBlockElement(previousSibling)) {
+      _appendLineBreak(fragment);
+    }
+    
+    while (listItem = list.firstChild) {
+      lastChild = listItem.lastChild;
+      while (firstChild = listItem.firstChild) {
+        isLastChild           = firstChild === lastChild;
+        // This needs to be done before appending it to the fragment, as it otherwise will loose style information
+        shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
+        fragment.appendChild(firstChild);
+        if (shouldAppendLineBreak) {
+          _appendLineBreak(fragment);
+        }
+      }
+      
+      listItem.parentNode.removeChild(listItem);
+    }
+    list.parentNode.replaceChild(fragment, list);
+  }
+  
+  dom.resolveList = resolveList;
+})(wysihtml5.dom);/**
+ * Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way
+ *
+ * Browser Compatibility:
+ *  - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted"
+ *  - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...)
+ *
+ * Please note that this class can't benefit from the HTML5 sandbox attribute for the following reasons:
+ *    - sandboxing doesn't work correctly with inlined content (src="javascript:'<html>...</html>'")
+ *    - sandboxing of physical documents causes that the dom isn't accessible anymore from the outside (iframe.contentWindow, ...)
+ *    - setting the "allow-same-origin" flag would fix that, but then still javascript and dom events refuse to fire
+ *    - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe
+ *      can do anything as if the sandbox attribute wasn't set
+ *
+ * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready
+ * @param {Object} [config] Optional parameters
+ *
+ * @example
+ *    new wysihtml5.dom.Sandbox(function(sandbox) {
+ *      sandbox.getWindow().document.body.innerHTML = '<img src=foo.gif onerror="alert(document.cookie)">';
+ *    });
+ */
+(function(wysihtml5) {
+  var /**
+       * Default configuration
+       */
+      doc                 = document,
+      /**
+       * Properties to unset/protect on the window object
+       */
+      windowProperties    = [
+        "parent", "top", "opener", "frameElement", "frames",
+        "localStorage", "globalStorage", "sessionStorage", "indexedDB"
+      ],
+      /**
+       * Properties on the window object which are set to an empty function
+       */
+      windowProperties2   = [
+        "open", "close", "openDialog", "showModalDialog",
+        "alert", "confirm", "prompt",
+        "openDatabase", "postMessage",
+        "XMLHttpRequest", "XDomainRequest"
+      ],
+      /**
+       * Properties to unset/proetect on the document object
+       */
+      documentProperties  = [
+        "referrer",
+        "write", "open", "close"
+      ];
+  
+  wysihtml5.dom.Sandbox = Base.extend(
+    /** @scope wysihtml5.dom.Sandbox.prototype */ {
+
+    constructor: function(readyCallback, config) {
+      this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
+      this.config   = wysihtml5.lang.object({}).merge(config).get();
+      this.iframe   = this._createIframe();
+    },
+    
+    insertInto: function(element) {
+      if (typeof(element) === "string") {
+        element = doc.getElementById(element);
+      }
+      
+      element.appendChild(this.iframe);
+    },
+
+    getIframe: function() {
+      return this.iframe;
+    },
+
+    getWindow: function() {
+      this._readyError();
+    },
+
+    getDocument: function() {
+      this._readyError();
+    },
+
+    destroy: function() {
+      var iframe = this.getIframe();
+      iframe.parentNode.removeChild(iframe);
+    },
+
+    _readyError: function() {
+      throw new Error("wysihtml5.Sandbox: Sandbox iframe isn't loaded yet");
+    },
+
+    /**
+     * Creates the sandbox iframe
+     *
+     * Some important notes:
+     *  - We can't use HTML5 sandbox for now:
+     *    setting it causes that the iframe's dom can't be accessed from the outside
+     *    Therefore we need to set the "allow-same-origin" flag which enables accessing the iframe's dom
+     *    But then there's another problem, DOM events (focus, blur, change, keypress, ...) aren't fired.
+     *    In order to make this happen we need to set the "allow-scripts" flag.
+     *    A combination of allow-scripts and allow-same-origin is almost the same as setting no sandbox attribute at all.
+     *  - Chrome & Safari, doesn't seem to support sandboxing correctly when the iframe's html is inlined (no physical document)
+     *  - IE needs to have the security="restricted" attribute set before the iframe is 
+     *    inserted into the dom tree
+     *  - Believe it or not but in IE "security" in document.createElement("iframe") is false, even
+     *    though it supports it
+     *  - When an iframe has security="restricted", in IE eval() & execScript() don't work anymore
+     *  - IE doesn't fire the onload event when the content is inlined in the src attribute, therefore we rely
+     *    on the onreadystatechange event
+     */
+    _createIframe: function() {
+      var that   = this,
+          iframe = doc.createElement("iframe");
+      iframe.className = "wysihtml5-sandbox";
+      wysihtml5.dom.setAttributes({
+        "security":           "restricted",
+        "allowtransparency":  "true",
+        "frameborder":        0,
+        "width":              0,
+        "height":             0,
+        "marginwidth":        0,
+        "marginheight":       0
+      }).on(iframe);
+
+      // Setting the src like this prevents ssl warnings in IE6
+      if (wysihtml5.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()) {
+        iframe.src = "javascript:'<html></html>'";
+      }
+
+      iframe.onload = function() {
+        iframe.onreadystatechange = iframe.onload = null;
+        that._onLoadIframe(iframe);
+      };
+
+      iframe.onreadystatechange = function() {
+        if (/loaded|complete/.test(iframe.readyState)) {
+          iframe.onreadystatechange = iframe.onload = null;
+          that._onLoadIframe(iframe);
+        }
+      };
+
+      return iframe;
+    },
+
+    /**
+     * Callback for when the iframe has finished loading
+     */
+    _onLoadIframe: function(iframe) {
+      // don't resume when the iframe got unloaded (eg. by removing it from the dom)
+      if (!wysihtml5.dom.contains(doc.documentElement, iframe)) {
+        return;
+      }
+
+      var that           = this,
+          iframeWindow   = iframe.contentWindow,
+          iframeDocument = iframe.contentWindow.document,
+          charset        = doc.characterSet || doc.charset || "utf-8",
+          sandboxHtml    = this._getHtml({
+            charset:      charset,
+            stylesheets:  this.config.stylesheets
+          });
+
+      // Create the basic dom tree including proper DOCTYPE and charset
+      iframeDocument.open("text/html", "replace");
+      iframeDocument.write(sandboxHtml);
+      iframeDocument.close();
+
+      this.getWindow = function() { return iframe.contentWindow; };
+      this.getDocument = function() { return iframe.contentWindow.document; };
+
+      // Catch js errors and pass them to the parent's onerror event
+      // addEventListener("error") doesn't work properly in some browsers
+      // TODO: apparently this doesn't work in IE9!
+      iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
+        throw new Error("wysihtml5.Sandbox: " + errorMessage, fileName, lineNumber);
+      };
+
+      if (!wysihtml5.browser.supportsSandboxedIframes()) {
+        // Unset a bunch of sensitive variables
+        // Please note: This isn't hack safe!  
+        // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information
+        // IE is secure though, which is the most important thing, since IE is the only browser, who
+        // takes over scripts & styles into contentEditable elements when copied from external websites
+        // or applications (Microsoft Word, ...)
+        var i, length;
+        for (i=0, length=windowProperties.length; i<length; i++) {
+          this._unset(iframeWindow, windowProperties[i]);
+        }
+        for (i=0, length=windowProperties2.length; i<length; i++) {
+          this._unset(iframeWindow, windowProperties2[i], wysihtml5.EMPTY_FUNCTION);
+        }
+        for (i=0, length=documentProperties.length; i<length; i++) {
+          this._unset(iframeDocument, documentProperties[i]);
+        }
+        // This doesn't work in Safari 5 
+        // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit
+        this._unset(iframeDocument, "cookie", "", true);
+      }
+
+      this.loaded = true;
+
+      // Trigger the callback
+      setTimeout(function() { that.callback(that); }, 0);
+    },
+
+    _getHtml: function(templateVars) {
+      var stylesheets = templateVars.stylesheets,
+          html        = "",
+          i           = 0,
+          length;
+      stylesheets = typeof(stylesheets) === "string" ? [stylesheets] : stylesheets;
+      if (stylesheets) {
+        length = stylesheets.length;
+        for (; i<length; i++) {
+          html += '<link rel="stylesheet" href="' + stylesheets[i] + '">';
+        }
+      }
+      templateVars.stylesheets = html;
+
+      return wysihtml5.lang.string(
+        '<!DOCTYPE html><html><head>'
+        + '<meta charset="#{charset}">#{stylesheets}</head>'
+        + '<body></body></html>'
+      ).interpolate(templateVars);
+    },
+
+    /**
+     * Method to unset/override existing variables
+     * @example
+     *    // Make cookie unreadable and unwritable
+     *    this._unset(document, "cookie", "", true);
+     */
+    _unset: function(object, property, value, setter) {
+      try { object[property] = value; } catch(e) {}
+
+      try { object.__defineGetter__(property, function() { return value; }); } catch(e) {}
+      if (setter) {
+        try { object.__defineSetter__(property, function() {}); } catch(e) {}
+      }
+
+      if (!wysihtml5.browser.crashesWhenDefineProperty(property)) {
+        try {
+          var config = {
+            get: function() { return value; }
+          };
+          if (setter) {
+            config.set = function() {};
+          }
+          Object.defineProperty(object, property, config);
+        } catch(e) {}
+      }
+    }
+  });
+})(wysihtml5);
+(function() {
+  var mapping = {
+    "className": "class"
+  };
+  wysihtml5.dom.setAttributes = function(attributes) {
+    return {
+      on: function(element) {
+        for (var i in attributes) {
+          element.setAttribute(mapping[i] || i, attributes[i]);
+        }
+      }
+    }
+  };
+})();wysihtml5.dom.setStyles = function(styles) {
+  return {
+    on: function(element) {
+      var style = element.style;
+      if (typeof(styles) === "string") {
+        style.cssText += ";" + styles;
+        return;
+      }
+      for (var i in styles) {
+        if (i === "float") {
+          style.cssFloat = styles[i];
+          style.styleFloat = styles[i];
+        } else {
+          style[i] = styles[i];
+        }
+      }
+    }
+  };
+};/**
+ * Simulate HTML5 placeholder attribute
+ *
+ * Needed since
+ *    - div[contentEditable] elements don't support it
+ *    - older browsers (such as IE8 and Firefox 3.6) don't support it at all
+ *
+ * @param {Object} parent Instance of main wysihtml5.Editor class
+ * @param {Element} view Instance of wysihtml5.views.* class
+ * @param {String} placeholderText
+ *
+ * @example
+ *    wysihtml.dom.simulatePlaceholder(this, composer, "Foobar");
+ */
+(function(dom) {
+  dom.simulatePlaceholder = function(editor, view, placeholderText) {
+    var CLASS_NAME = "placeholder",
+        unset = function() {
+          if (view.hasPlaceholderSet()) {
+            view.clear();
+          }
+          dom.removeClass(view.element, CLASS_NAME);
+        },
+        set = function() {
+          if (view.isEmpty()) {
+            view.setValue(placeholderText);
+            dom.addClass(view.element, CLASS_NAME);
+          }
+        };
+
+    editor
+      .observe("set_placeholder", set)
+      .observe("unset_placeholder", unset)
+      .observe("focus:composer", unset)
+      .observe("paste:composer", unset)
+      .observe("blur:composer", set);
+
+    set();
+  };
+})(wysihtml5.dom);
+(function(dom) {
+  var documentElement = document.documentElement;
+  if ("textContent" in documentElement) {
+    dom.setTextContent = function(element, text) {
+      element.textContent = text;
+    };
+
+    dom.getTextContent = function(element) {
+      return element.textContent;
+    };
+  } else if ("innerText" in documentElement) {
+    dom.setTextContent = function(element, text) {
+      element.innerText = text;
+    };
+
+    dom.getTextContent = function(element) {
+      return element.innerText;
+    };
+  } else {
+    dom.setTextContent = function(element, text) {
+      element.nodeValue = text;
+    };
+
+    dom.getTextContent = function(element) {
+      return element.nodeValue;
+    };
+  }
+})(wysihtml5.dom);
+
+/**
+ * Fix most common html formatting misbehaviors of browsers implementation when inserting
+ * content via copy & paste contentEditable
+ *
+ * @author Christopher Blum
+ */
+wysihtml5.quirks.cleanPastedHTML = (function() {
+  // TODO: We probably need more rules here
+  var defaultRules = {
+    // When pasting underlined links <a> into a contentEditable, IE thinks, it has to insert <u> to keep the styling
+    "a u": wysihtml5.dom.replaceWithChildNodes
+  };
+  
+  function cleanPastedHTML(elementOrHtml, rules, context) {
+    rules   = rules || defaultRules;
+    context = context || elementOrHtml.ownerDocument || document;
+    
+    var element,
+        isString = typeof(elementOrHtml) === "string",
+        method,
+        matches,
+        matchesLength,
+        i,
+        j = 0;
+    if (isString) {
+      element = wysihtml5.dom.getAsDom(elementOrHtml, context);
+    } else {
+      element = elementOrHtml;
+    }
+    
+    for (i in rules) {
+      matches       = element.querySelectorAll(i);
+      method        = rules[i];
+      matchesLength = matches.length;
+      for (; j<matchesLength; j++) {
+        method(matches[j]);
+      }
+    }
+    
+    matches = elementOrHtml = rules = null;
+    
+    return isString ? element.innerHTML : element;
+  }
+  
+  return cleanPastedHTML;
+})();/**
+ * IE and Opera leave an empty paragraph in the contentEditable element after clearing it
+ *
+ * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
+ * @exaple
+ *    wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
+ */
+(function(wysihtml5) {
+  var dom = wysihtml5.dom;
+  
+  wysihtml5.quirks.ensureProperClearing = (function() {
+    var clearIfNecessary = function(event) {
+      var element = this;
+      setTimeout(function() {
+        var innerHTML = element.innerHTML.toLowerCase();
+        if (innerHTML == "<p>&nbsp;</p>" ||
+            innerHTML == "<p>&nbsp;</p><p>&nbsp;</p>") {
+          element.innerHTML = "";
+        }
+      }, 0);
+    };
+
+    return function(composer) {
+      dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
+    };
+  })();
+
+
+
+  /**
+   * In Opera when the caret is in the first and only item of a list (<ul><li>|</li></ul>) and the list is the first child of the contentEditable element, it's impossible to delete the list by hitting backspace
+   *
+   * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
+   * @exaple
+   *    wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
+   */
+  wysihtml5.quirks.ensureProperClearingOfLists = (function() {
+    var ELEMENTS_THAT_CONTAIN_LI = ["OL", "UL", "MENU"];
+
+    var clearIfNecessary = function(element, contentEditableElement) {
+      if (!contentEditableElement.firstChild || !wysihtml5.lang.array(ELEMENTS_THAT_CONTAIN_LI).contains(contentEditableElement.firstChild.nodeName)) {
+        return;
+      }
+
+      var list = dom.getParentElement(element, { nodeName: ELEMENTS_THAT_CONTAIN_LI });
+      if (!list) {
+        return;
+      }
+
+      var listIsFirstChildOfContentEditable = list == contentEditableElement.firstChild;
+      if (!listIsFirstChildOfContentEditable) {
+        return;
+      }
+
+      var hasOnlyOneListItem = list.childNodes.length <= 1;
+      if (!hasOnlyOneListItem) {
+        return;
+      }
+
+      var onlyListItemIsEmpty = list.firstChild ? list.firstChild.innerHTML === "" : true;
+      if (!onlyListItemIsEmpty) {
+        return;
+      }
+
+      list.parentNode.removeChild(list);
+    };
+
+    return function(composer) {
+      dom.observe(composer.element, "keydown", function(event) {
+        if (event.keyCode !== wysihtml5.BACKSPACE_KEY) {
+          return;
+        }
+
+        var element = composer.selection.getSelectedNode();
+        clearIfNecessary(element, composer.element);
+      });
+    };
+  })();
+
+})(wysihtml5);
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
+//
+// In Firefox this:
+//      var d = document.createElement("div");
+//      d.innerHTML ='<a href="~"></a>';
+//      d.innerHTML;
+// will result in:
+//      <a href="%7E"></a>
+// which is wrong
+(function(wysihtml5) {
+  var TILDE_ESCAPED = "%7E";
+  wysihtml5.quirks.getCorrectInnerHTML = function(element) {
+    var innerHTML = element.innerHTML;
+    if (innerHTML.indexOf(TILDE_ESCAPED) === -1) {
+      return innerHTML;
+    }
+    
+    var elementsWithTilde = element.querySelectorAll("[href*='~'], [src*='~']"),
+        url,
+        urlToSearch,
+        length,
+        i;
+    for (i=0, length=elementsWithTilde.length; i<length; i++) {
+      url         = elementsWithTilde[i].href || elementsWithTilde[i].src;
+      urlToSearch = wysihtml5.lang.string(url).replace("~").by(TILDE_ESCAPED);
+      innerHTML   = wysihtml5.lang.string(innerHTML).replace(urlToSearch).by(url);
+    }
+    return innerHTML;
+  };
+})(wysihtml5);/**
+ * Some browsers don't insert line breaks when hitting return in a contentEditable element
+ *    - Opera & IE insert new <p> on return
+ *    - Chrome & Safari insert new <div> on return
+ *    - Firefox inserts <br> on return (yippie!)
+ *
+ * @param {Element} element
+ *
+ * @example
+ *    wysihtml5.quirks.insertLineBreakOnReturn(element);
+ */
+(function(wysihtml5) {
+  var dom                                           = wysihtml5.dom,
+      USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS  = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"],
+      LIST_TAGS                                     = ["UL", "OL", "MENU"];
+  
+  wysihtml5.quirks.insertLineBreakOnReturn = function(composer) {
+    function unwrap(selectedNode) {
+      var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2);
+      if (!parentElement) {
+        return;
+      }
+
+      var invisibleSpace = document.createTextNode(wysihtml5.INVISIBLE_SPACE);
+      dom.insert(invisibleSpace).before(parentElement);
+      dom.replaceWithChildNodes(parentElement);
+      composer.selection.selectNode(invisibleSpace);
+    }
+
+    function keyDown(event) {
+      var keyCode = event.keyCode;
+      if (event.shiftKey || (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY)) {
+        return;
+      }
+
+      var element         = event.target,
+          selectedNode    = composer.selection.getSelectedNode(),
+          blockElement    = dom.getParentElement(selectedNode, { nodeName: USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS }, 4);
+      if (blockElement) {
+        // Some browsers create <p> elements after leaving a list
+        // check after keydown of backspace and return whether a <p> got inserted and unwrap it
+        if (blockElement.nodeName === "LI" && (keyCode === wysihtml5.ENTER_KEY || keyCode === wysihtml5.BACKSPACE_KEY)) {
+          setTimeout(function() {
+            var selectedNode = composer.selection.getSelectedNode(),
+                list,
+                div;
+            if (!selectedNode) {
+              return;
+            }
+
+            list = dom.getParentElement(selectedNode, {
+              nodeName: LIST_TAGS
+            }, 2);
+
+            if (list) {
+              return;
+            }
+
+            unwrap(selectedNode);
+          }, 0);
+        } else if (blockElement.nodeName.match(/H[1-6]/) && keyCode === wysihtml5.ENTER_KEY) {
+          setTimeout(function() {
+            unwrap(composer.selection.getSelectedNode());
+          }, 0);
+        } 
+        return;
+      }
+
+      if (keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
+        composer.commands.exec("insertLineBreak");
+        event.preventDefault();
+      }
+    }
+    
+    // keypress doesn't fire when you hit backspace
+    dom.observe(composer.element.ownerDocument, "keydown", keyDown);
+  };
+})(wysihtml5);/**
+ * Force rerendering of a given element
+ * Needed to fix display misbehaviors of IE
+ *
+ * @param {Element} element The element object which needs to be rerendered
+ * @example
+ *    wysihtml5.quirks.redraw(document.body);
+ */
+(function(wysihtml5) {
+  var CLASS_NAME = "wysihtml5-quirks-redraw";
+  
+  wysihtml5.quirks.redraw = function(element) {
+    wysihtml5.dom.addClass(element, CLASS_NAME);
+    wysihtml5.dom.removeClass(element, CLASS_NAME);
+    
+    // Following hack is needed for firefox to make sure that image resize handles are properly removed
+    try {
+      var doc = element.ownerDocument;
+      doc.execCommand("italic", false, null);
+      doc.execCommand("italic", false, null);
+    } catch(e) {}
+  };
+})(wysihtml5);/**
+ * Selection API
+ *
+ * @example
+ *    var selection = new wysihtml5.Selection(editor);
+ */
+(function(wysihtml5) {
+  var dom = wysihtml5.dom;
+  
+  function _getCumulativeOffsetTop(element) {
+    var top = 0;
+    if (element.parentNode) {
+      do {
+        top += element.offsetTop || 0;
+        element = element.offsetParent;
+      } while (element);
+    }
+    return top;
+  }
+  
+  wysihtml5.Selection = Base.extend(
+    /** @scope wysihtml5.Selection.prototype */ {
+    constructor: function(editor) {
+      // Make sure that our external range library is initialized
+      window.rangy.init();
+      
+      this.editor   = editor;
+      this.composer = editor.composer;
+      this.doc      = this.composer.doc;
+    },
+    
+    /**
+     * Get the current selection as a bookmark to be able to later restore it
+     *
+     * @return {Object} An object that represents the current selection
+     */
+    getBookmark: function() {
+      var range = this.getRange();
+      return range && range.cloneRange();
+    },
+
+    /**
+     * Restore a selection retrieved via wysihtml5.Selection.prototype.getBookmark
+     *
+     * @param {Object} bookmark An object that represents the current selection
+     */
+    setBookmark: function(bookmark) {
+      if (!bookmark) {
+        return;
+      }
+
+      this.setSelection(bookmark);
+    },
+
+    /**
+     * Set the caret in front of the given node
+     *
+     * @param {Object} node The element or text node where to position the caret in front of
+     * @example
+     *    selection.setBefore(myElement);
+     */
+    setBefore: function(node) {
+      var range = rangy.createRange(this.doc);
+      range.setStartBefore(node);
+      range.setEndBefore(node);
+      return this.setSelection(range);
+    },
+
+    /**
+     * Set the caret after the given node
+     *
+     * @param {Object} node The element or text node where to position the caret in front of
+     * @example
+     *    selection.setBefore(myElement);
+     */
+    setAfter: function(node) {
+      var range = rangy.createRange(this.doc);
+      range.setStartAfter(node);
+      range.setEndAfter(node);
+      return this.setSelection(range);
+    },
+
+    /**
+     * Ability to select/mark nodes
+     *
+     * @param {Element} node The node/element to select
+     * @example
+     *    selection.selectNode(document.getElementById("my-image"));
+     */
+    selectNode: function(node) {
+      var range           = rangy.createRange(this.doc),
+          isElement       = node.nodeType === wysihtml5.ELEMENT_NODE,
+          canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"),
+          content         = isElement ? node.innerHTML : node.data,
+          isEmpty         = (content === "" || content === wysihtml5.INVISIBLE_SPACE),
+          displayStyle    = dom.getStyle("display").from(node),
+          isBlockElement  = (displayStyle === "block" || displayStyle === "list-item");
+
+      if (isEmpty && isElement && canHaveHTML) {
+        // Make sure that caret is visible in node by inserting a zero width no breaking space
+        try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {}
+      }
+
+      if (canHaveHTML) {
+        range.selectNodeContents(node);
+      } else {
+        range.selectNode(node);
+      }
+
+      if (canHaveHTML && isEmpty && isElement) {
+        range.collapse(isBlockElement);
+      } else if (canHaveHTML && isEmpty) {
+        range.setStartAfter(node);
+        range.setEndAfter(node);
+      }
+
+      this.setSelection(range);
+    },
+
+    /**
+     * Get the node which contains the selection
+     *
+     * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange"
+     * @return {Object} The node that contains the caret
+     * @example
+     *    var nodeThatContainsCaret = selection.getSelectedNode();
+     */
+    getSelectedNode: function(controlRange) {
+      var selection,
+          range;
+
+      if (controlRange && this.doc.selection && this.doc.selection.type === "Control") {
+        range = this.doc.selection.createRange();
+        if (range && range.length) {
+          return range.item(0);
+        }
+      }
+
+      selection = this.getSelection(this.doc);
+      if (selection.focusNode === selection.anchorNode) {
+        return selection.focusNode;
+      } else {
+        range = this.getRange(this.doc);
+        return range ? range.commonAncestorContainer : this.doc.body;
+      }
+    },
+
+    executeAndRestore: function(method, restoreScrollPosition) {
+      var body                  = this.doc.body,
+          oldScrollTop          = restoreScrollPosition && body.scrollTop,
+          oldScrollLeft         = restoreScrollPosition && body.scrollLeft,
+          className             = "_wysihtml5-temp-placeholder",
+          placeholderHTML       = '<span class="' + className + '">' + wysihtml5.INVISIBLE_SPACE + '</span>',
+          range                 = this.getRange(this.doc),
+          newRange;
+      
+      // Nothing selected, execute and say goodbye
+      if (!range) {
+        method(body, body);
+        return;
+      }
+      
+      var node = range.createContextualFragment(placeholderHTML);
+      range.insertNode(node);
+      
+      // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder
+      try {
+        method(range.startContainer, range.endContainer);
+      } catch(e3) {
+        setTimeout(function() { throw e3; }, 0);
+      }
+      
+      caretPlaceholder = this.doc.querySelector("." + className);
+      if (caretPlaceholder) {
+        newRange = rangy.createRange(this.doc);
+        newRange.selectNode(caretPlaceholder);
+        newRange.deleteContents();
+        this.setSelection(newRange);
+      } else {
+        // fallback for when all hell breaks loose
+        body.focus();
+      }
+
+      if (restoreScrollPosition) {
+        body.scrollTop  = oldScrollTop;
+        body.scrollLeft = oldScrollLeft;
+      }
+
+      // Remove it again, just to make sure that the placeholder is definitely out of the dom tree
+      try {
+        caretPlaceholder.parentNode.removeChild(caretPlaceholder);
+      } catch(e4) {}
+    },
+
+    /**
+     * Different approach of preserving the selection (doesn't modify the dom)
+     * Takes all text nodes in the selection and saves the selection position in the first and last one
+     */
+    executeAndRestoreSimple: function(method) {
+      var range = this.getRange(),
+          body  = this.doc.body,
+          newRange,
+          firstNode,
+          lastNode,
+          textNodes,
+          rangeBackup;
+
+      // Nothing selected, execute and say goodbye
+      if (!range) {
+        method(body, body);
+        return;
+      }
+
+      textNodes = range.getNodes([3]);
+      firstNode = textNodes[0] || range.startContainer;
+      lastNode  = textNodes[textNodes.length - 1] || range.endContainer;
+
+      rangeBackup = {
+        collapsed:      range.collapsed,
+        startContainer: firstNode,
+        startOffset:    firstNode === range.startContainer ? range.startOffset : 0,
+        endContainer:   lastNode,
+        endOffset:      lastNode === range.endContainer ? range.endOffset : lastNode.length
+      };
+
+      try {
+        method(range.startContainer, range.endContainer);
+      } catch(e) {
+        setTimeout(function() { throw e; }, 0);
+      }
+
+      newRange = rangy.createRange(this.doc);
+      try { newRange.setStart(rangeBackup.startContainer, rangeBackup.startOffset); } catch(e1) {}
+      try { newRange.setEnd(rangeBackup.endContainer, rangeBackup.endOffset); } catch(e2) {}
+      try { this.setSelection(newRange); } catch(e3) {}
+    },
+
+    /**
+     * Insert html at the caret position and move the cursor after the inserted html
+     *
+     * @param {String} html HTML string to insert
+     * @example
+     *    selection.insertHTML("<p>foobar</p>");
+     */
+    insertHTML: function(html) {
+      var range     = rangy.createRange(this.doc),
+          node      = range.createContextualFragment(html),
+          lastChild = node.lastChild;
+      this.insertNode(node);
+      if (lastChild) {
+        this.setAfter(lastChild);
+      }
+    },
+
+    /**
+     * Insert a node at the caret position and move the cursor behind it
+     *
+     * @param {Object} node HTML string to insert
+     * @example
+     *    selection.insertNode(document.createTextNode("foobar"));
+     */
+    insertNode: function(node) {
+      var range = this.getRange();
+      if (range) {
+        range.insertNode(node);
+      }
+    },
+
+    /**
+     * Wraps current selection with the given node
+     *
+     * @param {Object} node The node to surround the selected elements with
+     */
+    surround: function(node) {
+      var range = this.getRange();
+      if (!range) {
+        return;
+      }
+
+      try {
+        // This only works when the range boundaries are not overlapping other elements
+        range.surroundContents(node);
+        this.selectNode(node);
+      } catch(e) {
+        // fallback
+        node.appendChild(range.extractContents());
+        range.insertNode(node);
+      }
+    },
+
+    /**
+     * Scroll the current caret position into the view
+     * FIXME: This is a bit hacky, there might be a smarter way of doing this
+     *
+     * @example
+     *    selection.scrollIntoView();
+     */
+    scrollIntoView: function() {
+      var doc           = this.doc,
+          hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight,
+          tempElement   = doc._wysihtml5ScrollIntoViewElement = doc._wysihtml5ScrollIntoViewElement || (function() {
+            var element = doc.createElement("span");
+            // The element needs content in order to be able to calculate it's position properly
+            element.innerHTML = wysihtml5.INVISIBLE_SPACE;
+            return element;
+          })(),
+          offsetTop;
+
+      if (hasScrollBars) {
+        this.insertNode(tempElement);
+        offsetTop = _getCumulativeOffsetTop(tempElement);
+        tempElement.parentNode.removeChild(tempElement);
+        if (offsetTop > doc.body.scrollTop) {
+          doc.body.scrollTop = offsetTop;
+        }
+      }
+    },
+
+    /**
+     * Select line where the caret is in
+     */
+    selectLine: function() {
+      if (wysihtml5.browser.supportsSelectionModify()) {
+        this._selectLine_W3C();
+      } else if (this.doc.selection) {
+        this._selectLine_MSIE();
+      }
+    },
+
+    /**
+     * See https://developer.mozilla.org/en/DOM/Selection/modify
+     */
+    _selectLine_W3C: function() {
+      var win       = this.doc.defaultView,
+          selection = win.getSelection();
+      selection.modify("extend", "left", "lineboundary");
+      selection.modify("extend", "right", "lineboundary");
+    },
+
+    _selectLine_MSIE: function() {
+      var range       = this.doc.selection.createRange(),
+          rangeTop    = range.boundingTop,
+          rangeHeight = range.boundingHeight,
+          scrollWidth = this.doc.body.scrollWidth,
+          rangeBottom,
+          rangeEnd,
+          measureNode,
+          i,
+          j;
+
+      if (!range.moveToPoint) {
+        return;
+      }
+
+      if (rangeTop === 0) {
+        // Don't know why, but when the selection ends at the end of a line
+        // range.boundingTop is 0
+        measureNode = this.doc.createElement("span");
+        this.insertNode(measureNode);
+        rangeTop = measureNode.offsetTop;
+        measureNode.parentNode.removeChild(measureNode);
+      }
+
+      rangeTop += 1;
+
+      for (i=-10; i<scrollWidth; i+=2) {
+        try {
+          range.moveToPoint(i, rangeTop);
+          break;
+        } catch(e1) {}
+      }
+
+      // Investigate the following in order to handle multi line selections
+      // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0);
+      rangeBottom = rangeTop;
+      rangeEnd = this.doc.selection.createRange();
+      for (j=scrollWidth; j>=0; j--) {
+        try {
+          rangeEnd.moveToPoint(j, rangeBottom);
+          break;
+        } catch(e2) {}
+      }
+
+      range.setEndPoint("EndToEnd", rangeEnd);
+      range.select();
+    },
+
+    getText: function() {
+      var selection = this.getSelection();
+      return selection ? selection.toString() : "";
+    },
+
+    getNodes: function(nodeType, filter) {
+      var range = this.getRange();
+      if (range) {
+        return range.getNodes([nodeType], filter);
+      } else {
+        return [];
+      }
+    },
+    
+    getRange: function() {
+      var selection = this.getSelection();
+      return selection && selection.rangeCount && selection.getRangeAt(0);
+    },
+
+    getSelection: function() {
+      return rangy.getSelection(this.doc.defaultView || this.doc.parentWindow);
+    },
+
+    setSelection: function(range) {
+      var win       = this.doc.defaultView || this.doc.parentWindow,
+          selection = rangy.getSelection(win);
+      return selection.setSingleRange(range);
+    }
+  });
+  
+})(wysihtml5);
+/**
+ * Inspired by the rangy CSS Applier module written by Tim Down and licensed under the MIT license.
+ * http://code.google.com/p/rangy/
+ *
+ * changed in order to be able ...
+ *    - to use custom tags
+ *    - to detect and replace similar css classes via reg exp
+ */
+(function(wysihtml5, rangy) {
+  var defaultTagName = "span";
+  
+  var REG_EXP_WHITE_SPACE = /\s+/g;
+  
+  function hasClass(el, cssClass, regExp) {
+    if (!el.className) {
+      return false;
+    }
+    
+    var matchingClassNames = el.className.match(regExp) || [];
+    return matchingClassNames[matchingClassNames.length - 1] === cssClass;
+  }
+
+  function addClass(el, cssClass, regExp) {
+    if (el.className) {
+      removeClass(el, regExp);
+      el.className += " " + cssClass;
+    } else {
+      el.className = cssClass;
+    }
+  }
+
+  function removeClass(el, regExp) {
+    if (el.className) {
+      el.className = el.className.replace(regExp, "");
+    }
+  }
+  
+  function hasSameClasses(el1, el2) {
+    return el1.className.replace(REG_EXP_WHITE_SPACE, " ") == el2.className.replace(REG_EXP_WHITE_SPACE, " ");
+  }
+
+  function replaceWithOwnChildren(el) {
+    var parent = el.parentNode;
+    while (el.firstChild) {
+      parent.insertBefore(el.firstChild, el);
+    }
+    parent.removeChild(el);
+  }
+
+  function elementsHaveSameNonClassAttributes(el1, el2) {
+    if (el1.attributes.length != el2.attributes.length) {
+      return false;
+    }
+    for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
+      attr1 = el1.attributes[i];
+      name = attr1.name;
+      if (name != "class") {
+        attr2 = el2.attributes.getNamedItem(name);
+        if (attr1.specified != attr2.specified) {
+          return false;
+        }
+        if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  function isSplitPoint(node, offset) {
+    if (rangy.dom.isCharacterDataNode(node)) {
+      if (offset == 0) {
+        return !!node.previousSibling;
+      } else if (offset == node.length) {
+        return !!node.nextSibling;
+      } else {
+        return true;
+      }
+    }
+
+    return offset > 0 && offset < node.childNodes.length;
+  }
+
+  function splitNodeAt(node, descendantNode, descendantOffset) {
+    var newNode;
+    if (rangy.dom.isCharacterDataNode(descendantNode)) {
+      if (descendantOffset == 0) {
+        descendantOffset = rangy.dom.getNodeIndex(descendantNode);
+        descendantNode = descendantNode.parentNode;
+      } else if (descendantOffset == descendantNode.length) {
+        descendantOffset = rangy.dom.getNodeIndex(descendantNode) + 1;
+        descendantNode = descendantNode.parentNode;
+      } else {
+        newNode = rangy.dom.splitDataNode(descendantNode, descendantOffset);
+      }
+    }
+    if (!newNode) {
+      newNode = descendantNode.cloneNode(false);
+      if (newNode.id) {
+        newNode.removeAttribute("id");
+      }
+      var child;
+      while ((child = descendantNode.childNodes[descendantOffset])) {
+        newNode.appendChild(child);
+      }
+      rangy.dom.insertAfter(newNode, descendantNode);
+    }
+    return (descendantNode == node) ? newNode : splitNodeAt(node, newNode.parentNode, rangy.dom.getNodeIndex(newNode));
+  }
+  
+  function Merge(firstNode) {
+    this.isElementMerge = (firstNode.nodeType == wysihtml5.ELEMENT_NODE);
+    this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
+    this.textNodes = [this.firstTextNode];
+  }
+
+  Merge.prototype = {
+    doMerge: function() {
+      var textBits = [], textNode, parent, text;
+      for (var i = 0, len = this.textNodes.length; i < len; ++i) {
+        textNode = this.textNodes[i];
+        parent = textNode.parentNode;
+        textBits[i] = textNode.data;
+        if (i) {
+          parent.removeChild(textNode);
+          if (!parent.hasChildNodes()) {
+            parent.parentNode.removeChild(parent);
+          }
+        }
+      }
+      this.firstTextNode.data = text = textBits.join("");
+      return text;
+    },
+
+    getLength: function() {
+      var i = this.textNodes.length, len = 0;
+      while (i--) {
+        len += this.textNodes[i].length;
+      }
+      return len;
+    },
+
+    toString: function() {
+      var textBits = [];
+      for (var i = 0, len = this.textNodes.length; i < len; ++i) {
+        textBits[i] = "'" + this.textNodes[i].data + "'";
+      }
+      return "[Merge(" + textBits.join(",") + ")]";
+    }
+  };
+
+  function HTMLApplier(tagNames, cssClass, similarClassRegExp, normalize) {
+    this.tagNames = tagNames || [defaultTagName];
+    this.cssClass = cssClass || "";
+    this.similarClassRegExp = similarClassRegExp;
+    this.normalize = normalize;
+    this.applyToAnyTagName = false;
+  }
+
+  HTMLApplier.prototype = {
+    getAncestorWithClass: function(node) {
+      var cssClassMatch;
+      while (node) {
+        cssClassMatch = this.cssClass ? hasClass(node, this.cssClass, this.similarClassRegExp) : true;
+        if (node.nodeType == wysihtml5.ELEMENT_NODE && rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssClassMatch) {
+          return node;
+        }
+        node = node.parentNode;
+      }
+      return false;
+    },
+
+    // Normalizes nodes after applying a CSS class to a Range.
+    postApply: function(textNodes, range) {
+      var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
+
+      var merges = [], currentMerge;
+
+      var rangeStartNode = firstNode, rangeEndNode = lastNode;
+      var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
+
+      var textNode, precedingTextNode;
+
+      for (var i = 0, len = textNodes.length; i < len; ++i) {
+        textNode = textNodes[i];
+        precedingTextNode = this.getAdjacentMergeableTextNode(textNode.parentNode, false);
+        if (precedingTextNode) {
+          if (!currentMerge) {
+            currentMerge = new Merge(precedingTextNode);
+            merges.push(currentMerge);
+          }
+          currentMerge.textNodes.push(textNode);
+          if (textNode === firstNode) {
+            rangeStartNode = currentMerge.firstTextNode;
+            rangeStartOffset = rangeStartNode.length;
+          }
+          if (textNode === lastNode) {
+            rangeEndNode = currentMerge.firstTextNode;
+            rangeEndOffset = currentMerge.getLength();
+          }
+        } else {
+          currentMerge = null;
+        }
+      }
+
+      // Test whether the first node after the range needs merging
+      var nextTextNode = this.getAdjacentMergeableTextNode(lastNode.parentNode, true);
+      if (nextTextNode) {
+        if (!currentMerge) {
+          currentMerge = new Merge(lastNode);
+          merges.push(currentMerge);
+        }
+        currentMerge.textNodes.push(nextTextNode);
+      }
+
+      // Do the merges
+      if (merges.length) {
+        for (i = 0, len = merges.length; i < len; ++i) {
+          merges[i].doMerge();
+        }
+        // Set the range boundaries
+        range.setStart(rangeStartNode, rangeStartOffset);
+        range.setEnd(rangeEndNode, rangeEndOffset);
+      }
+    },
+    
+    getAdjacentMergeableTextNode: function(node, forward) {
+        var isTextNode = (node.nodeType == wysihtml5.TEXT_NODE);
+        var el = isTextNode ? node.parentNode : node;
+        var adjacentNode;
+        var propName = forward ? "nextSibling" : "previousSibling";
+        if (isTextNode) {
+          // Can merge if the node's previous/next sibling is a text node
+          adjacentNode = node[propName];
+          if (adjacentNode && adjacentNode.nodeType == wysihtml5.TEXT_NODE) {
+            return adjacentNode;
+          }
+        } else {
+          // Compare element with its sibling
+          adjacentNode = el[propName];
+          if (adjacentNode && this.areElementsMergeable(node, adjacentNode)) {
+            return adjacentNode[forward ? "firstChild" : "lastChild"];
+          }
+        }
+        return null;
+    },
+    
+    areElementsMergeable: function(el1, el2) {
+      return rangy.dom.arrayContains(this.tagNames, (el1.tagName || "").toLowerCase())
+        && rangy.dom.arrayContains(this.tagNames, (el2.tagName || "").toLowerCase())
+        && hasSameClasses(el1, el2)
+        && elementsHaveSameNonClassAttributes(el1, el2);
+    },
+
+    createContainer: function(doc) {
+      var el = doc.createElement(this.tagNames[0]);
+      if (this.cssClass) {
+        el.className = this.cssClass;
+      }
+      return el;
+    },
+
+    applyToTextNode: function(textNode) {
+      var parent = textNode.parentNode;
+      if (parent.childNodes.length == 1 && rangy.dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) {
+        if (this.cssClass) {
+          addClass(parent, this.cssClass, this.similarClassRegExp);
+        }
+      } else {
+        var el = this.createContainer(rangy.dom.getDocument(textNode));
+        textNode.parentNode.insertBefore(el, textNode);
+        el.appendChild(textNode);
+      }
+    },
+
+    isRemovable: function(el) {
+      return rangy.dom.arrayContains(this.tagNames, el.tagName.toLowerCase()) && wysihtml5.lang.string(el.className).trim() == this.cssClass;
+    },
+
+    undoToTextNode: function(textNode, range, ancestorWithClass) {
+      if (!range.containsNode(ancestorWithClass)) {
+        // Split out the portion of the ancestor from which we can remove the CSS class
+        var ancestorRange = range.cloneRange();
+        ancestorRange.selectNode(ancestorWithClass);
+
+        if (ancestorRange.isPointInRange(range.endContainer, range.endOffset) && isSplitPoint(range.endContainer, range.endOffset)) {
+          splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset);
+          range.setEndAfter(ancestorWithClass);
+        }
+        if (ancestorRange.isPointInRange(range.startContainer, range.startOffset) && isSplitPoint(range.startContainer, range.startOffset)) {
+          ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset);
+        }
+      }
+      
+      if (this.similarClassRegExp) {
+        removeClass(ancestorWithClass, this.similarClassRegExp);
+      }
+      if (this.isRemovable(ancestorWithClass)) {
+        replaceWithOwnChildren(ancestorWithClass);
+      }
+    },
+
+    applyToRange: function(range) {
+        var textNodes = range.getNodes([wysihtml5.TEXT_NODE]);
+        if (!textNodes.length) {
+          try {
+            var node = this.createContainer(range.endContainer.ownerDocument);
+            range.surroundContents(node);
+            this.selectNode(range, node);
+            return;
+          } catch(e) {}
+        }
+        
+        range.splitBoundaries();
+        textNodes = range.getNodes([wysihtml5.TEXT_NODE]);
+        
+        if (textNodes.length) {
+          var textNode;
+
+          for (var i = 0, len = textNodes.length; i < len; ++i) {
+            textNode = textNodes[i];
+            if (!this.getAncestorWithClass(textNode)) {
+              this.applyToTextNode(textNode);
+            }
+          }
+          
+          range.setStart(textNodes[0], 0);
+          textNode = textNodes[textNodes.length - 1];
+          range.setEnd(textNode, textNode.length);
+          
+          if (this.normalize) {
+            this.postApply(textNodes, range);
+          }
+        }
+    },
+
+    undoToRange: function(range) {
+      var textNodes = range.getNodes([wysihtml5.TEXT_NODE]), textNode, ancestorWithClass;
+      if (textNodes.length) {
+        range.splitBoundaries();
+        textNodes = range.getNodes([wysihtml5.TEXT_NODE]);
+      } else {
+        var doc = range.endContainer.ownerDocument,
+            node = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
+        range.insertNode(node);
+        range.selectNode(node);
+        textNodes = [node];
+      }
+      
+      for (var i = 0, len = textNodes.length; i < len; ++i) {
+        textNode = textNodes[i];
+        ancestorWithClass = this.getAncestorWithClass(textNode);
+        if (ancestorWithClass) {
+          this.undoToTextNode(textNode, range, ancestorWithClass);
+        }
+      }
+      
+      if (len == 1) {
+        this.selectNode(range, textNodes[0]);
+      } else {
+        range.setStart(textNodes[0], 0);
+        textNode = textNodes[textNodes.length - 1];
+        range.setEnd(textNode, textNode.length);
+
+        if (this.normalize) {
+          this.postApply(textNodes, range);
+        }
+      }
+    },
+    
+    selectNode: function(range, node) {
+      var isElement       = node.nodeType === wysihtml5.ELEMENT_NODE,
+          canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : true,
+          content         = isElement ? node.innerHTML : node.data,
+          isEmpty         = (content === "" || content === wysihtml5.INVISIBLE_SPACE);
+
+      if (isEmpty && isElement && canHaveHTML) {
+        // Make sure that caret is visible in node by inserting a zero width no breaking space
+        try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {}
+      }
+      range.selectNodeContents(node);
+      if (isEmpty && isElement) {
+        range.collapse(false);
+      } else if (isEmpty) {
+        range.setStartAfter(node);
+        range.setEndAfter(node);
+      }
+    },
+    
+    getTextSelectedByRange: function(textNode, range) {
+      var textRange = range.cloneRange();
+      textRange.selectNodeContents(textNode);
+
+      var intersectionRange = textRange.intersection(range);
+      var text = intersectionRange ? intersectionRange.toString() : "";
+      textRange.detach();
+
+      return text;
+    },
+
+    isAppliedToRange: function(range) {
+      var ancestors = [],
+          ancestor,
+          textNodes = range.getNodes([wysihtml5.TEXT_NODE]);
+      if (!textNodes.length) {
+        ancestor = this.getAncestorWithClass(range.startContainer);
+        return ancestor ? [ancestor] : false;
+      }
+      
+      for (var i = 0, len = textNodes.length, selectedText; i < len; ++i) {
+        selectedText = this.getTextSelectedByRange(textNodes[i], range);
+        ancestor = this.getAncestorWithClass(textNodes[i]);
+        if (selectedText != "" && !ancestor) {
+          return false;
+        } else {
+          ancestors.push(ancestor);
+        }
+      }
+      return ancestors;
+    },
+
+    toggleRange: function(range) {
+      if (this.isAppliedToRange(range)) {
+        this.undoToRange(range);
+      } else {
+        this.applyToRange(range);
+      }
+    }
+  };
+
+  wysihtml5.selection.HTMLApplier = HTMLApplier;
+  
+})(wysihtml5, rangy);/**
+ * Rich Text Query/Formatting Commands
+ * 
+ * @example
+ *    var commands = new wysihtml5.Commands(editor);
+ */
+wysihtml5.Commands = Base.extend(
+  /** @scope wysihtml5.Commands.prototype */ {
+  constructor: function(editor) {
+    this.editor   = editor;
+    this.composer = editor.composer;
+    this.doc      = this.composer.doc;
+  },
+  
+  /**
+   * Check whether the browser supports the given command
+   *
+   * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
+   * @example
+   *    commands.supports("createLink");
+   */
+  support: function(command) {
+    return wysihtml5.browser.supportsCommand(this.doc, command);
+  },
+  
+  /**
+   * Check whether the browser supports the given command
+   *
+   * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList")
+   * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...)
+   * @example
+   *    commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg");
+   */
+  exec: function(command, value) {
+    var obj     = wysihtml5.commands[command],
+        method  = obj && obj.exec;
+    
+    this.editor.fire("beforecommand:composer");
+    
+    if (method) {
+      return method.call(obj, this.composer, command, value);
+    } else {
+      try {
+        // try/catch for buggy firefox
+        return this.doc.execCommand(command, false, value);
+      } catch(e) {}
+    }
+    
+    this.editor.fire("aftercommand:composer");
+  },
+  
+  /**
+   * Check whether the current command is active
+   * If the caret is within a bold text, then calling this with command "bold" should return true
+   *
+   * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
+   * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src)
+   * @return {Boolean} Whether the command is active
+   * @example
+   *    var isCurrentSelectionBold = commands.state("bold");
+   */
+  state: function(command, commandValue) {
+    var obj     = wysihtml5.commands[command],
+        method  = obj && obj.state;
+    if (method) {
+      return method.call(obj, this.composer, command, commandValue);
+    } else {
+      try {
+        // try/catch for buggy firefox
+        return this.doc.queryCommandState(command);
+      } catch(e) {
+        return false;
+      }
+    }
+  },
+  
+  /**
+   * Get the current command's value
+   *
+   * @param {String} command The command string which to check (eg. "formatBlock")
+   * @return {String} The command value
+   * @example
+   *    var currentBlockElement = commands.value("formatBlock");
+   */
+  value: function(command) {
+    var obj     = wysihtml5.commands[command],
+        method  = obj && obj.value;
+    if (method) {
+      return method.call(obj, this.composer, command);
+    } else {
+      try {
+        // try/catch for buggy firefox
+        return this.doc.queryCommandValue(command);
+      } catch(e) {
+        return null;
+      }
+    }
+  }
+});(function(wysihtml5) {
+  var undef;
+  
+  wysihtml5.commands.bold = {
+    exec: function(composer, command) {
+      return wysihtml5.commands.formatInline.exec(composer, command, "b");
+    },
+
+    state: function(composer, command, color) {
+      // element.ownerDocument.queryCommandState("bold") results:
+      // firefox: only <b>
+      // chrome:  <b>, <strong>, <h1>, <h2>, ...
+      // ie:      <b>, <strong>
+      // opera:   <b>, <strong>
+      return wysihtml5.commands.formatInline.state(composer, command, "b");
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);
+
+(function(wysihtml5) {
+  var undef,
+      NODE_NAME = "A",
+      dom       = wysihtml5.dom;
+  
+  function _removeFormat(composer, anchors) {
+    var length  = anchors.length,
+        i       = 0,
+        anchor,
+        codeElement,
+        textContent;
+    for (; i<length; i++) {
+      anchor      = anchors[i];
+      codeElement = dom.getParentElement(anchor, { nodeName: "code" });
+      textContent = dom.getTextContent(anchor);
+
+      // if <a> contains url-like text content, rename it to <code> to prevent re-autolinking
+      // else replace <a> with its childNodes
+      if (textContent.match(dom.autoLink.URL_REG_EXP) && !codeElement) {
+        // <code> element is used to prevent later auto-linking of the content
+        codeElement = dom.renameElement(anchor, "code");
+      } else {
+        dom.replaceWithChildNodes(anchor);
+      }
+    }
+  }
+
+  function _format(composer, attributes) {
+    var doc             = composer.doc,
+        tempClass       = "_wysihtml5-temp-" + (+new Date()),
+        tempClassRegExp = /non-matching-class/g,
+        i               = 0,
+        length,
+        anchors,
+        anchor,
+        hasElementChild,
+        isEmpty,
+        elementToSetCaretAfter,
+        textContent,
+        whiteSpace,
+        j;
+    wysihtml5.commands.formatInline.exec(composer, undef, NODE_NAME, tempClass, tempClassRegExp);
+    anchors = doc.querySelectorAll(NODE_NAME + "." + tempClass);
+    length  = anchors.length;
+    for (; i<length; i++) {
+      anchor = anchors[i];
+      anchor.removeAttribute("class");
+      for (j in attributes) {
+        anchor.setAttribute(j, attributes[j]);
+      }
+    }
+
+    elementToSetCaretAfter = anchor;
+    if (length === 1) {
+      textContent = dom.getTextContent(anchor);
+      hasElementChild = !!anchor.querySelector("*");
+      isEmpty = textContent === "" || textContent === wysihtml5.INVISIBLE_SPACE;
+      if (!hasElementChild && isEmpty) {
+        dom.setTextContent(anchor, anchor.href);
+        whiteSpace = doc.createTextNode(" ");
+        composer.selection.setAfter(anchor);
+        composer.selection.insertNode(whiteSpace);
+        elementToSetCaretAfter = whiteSpace;
+      }
+    }
+    composer.selection.setAfter(elementToSetCaretAfter);
+  }
+  
+  wysihtml5.commands.createLink = {
+    /**
+     * TODO: Use HTMLApplier or formatInline here
+     *
+     * Turns selection into a link
+     * If selection is already a link, it removes the link and wraps it with a <code> element
+     * The <code> element is needed to avoid auto linking
+     * 
+     * @example
+     *    // either ...
+     *    wysihtml5.commands.createLink.exec(composer, "createLink", "http://www.google.de");
+     *    // ... or ...
+     *    wysihtml5.commands.createLink.exec(composer, "createLink", { href: "http://www.google.de", target: "_blank" });
+     */
+    exec: function(composer, command, value) {
+      var anchors = this.state(composer, command);
+      if (anchors) {
+        // Selection contains links
+        composer.selection.executeAndRestore(function() {
+          _removeFormat(composer, anchors);
+        });
+      } else {
+        // Create links
+        value = typeof(value) === "object" ? value : { href: value };
+        _format(composer, value);
+      }
+    },
+
+    state: function(composer, command) {
+      return wysihtml5.commands.formatInline.state(composer, command, "A");
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);/**
+ * document.execCommand("fontSize") will create either inline styles (firefox, chrome) or use font tags
+ * which we don't want
+ * Instead we set a css class
+ */
+(function(wysihtml5) {
+  var undef,
+      REG_EXP = /wysiwyg-font-size-[a-z]+/g;
+  
+  wysihtml5.commands.fontSize = {
+    exec: function(composer, command, size) {
+      return wysihtml5.commands.formatInline.exec(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
+    },
+
+    state: function(composer, command, size) {
+      return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);
+/**
+ * document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
+ * which we don't want
+ * Instead we set a css class
+ */
+(function(wysihtml5) {
+  var undef,
+      REG_EXP = /wysiwyg-color-[a-z]+/g;
+  
+  wysihtml5.commands.foreColor = {
+    exec: function(composer, command, color) {
+      return wysihtml5.commands.formatInline.exec(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
+    },
+
+    state: function(composer, command, color) {
+      return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var undef,
+      dom                     = wysihtml5.dom,
+      DEFAULT_NODE_NAME       = "DIV",
+      // Following elements are grouped
+      // when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
+      // instead of creating a H4 within a H1 which would result in semantically invalid html
+      BLOCK_ELEMENTS_GROUP    = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", DEFAULT_NODE_NAME];
+  
+  /**
+   * Remove similiar classes (based on classRegExp)
+   * and add the desired class name
+   */
+  function _addClass(element, className, classRegExp) {
+    if (element.className) {
+      _removeClass(element, classRegExp);
+      element.className += " " + className;
+    } else {
+      element.className = className;
+    }
+  }
+
+  function _removeClass(element, classRegExp) {
+    element.className = element.className.replace(classRegExp, "");
+  }
+
+  /**
+   * Check whether given node is a text node and whether it's empty
+   */
+  function _isBlankTextNode(node) {
+    return node.nodeType === wysihtml5.TEXT_NODE && !wysihtml5.lang.string(node.data).trim();
+  }
+
+  /**
+   * Returns previous sibling node that is not a blank text node
+   */
+  function _getPreviousSiblingThatIsNotBlank(node) {
+    var previousSibling = node.previousSibling;
+    while (previousSibling && _isBlankTextNode(previousSibling)) {
+      previousSibling = previousSibling.previousSibling;
+    }
+    return previousSibling;
+  }
+
+  /**
+   * Returns next sibling node that is not a blank text node
+   */
+  function _getNextSiblingThatIsNotBlank(node) {
+    var nextSibling = node.nextSibling;
+    while (nextSibling && _isBlankTextNode(nextSibling)) {
+      nextSibling = nextSibling.nextSibling;
+    }
+    return nextSibling;
+  }
+
+  /**
+   * Adds line breaks before and after the given node if the previous and next siblings
+   * aren't already causing a visual line break (block element or <br>)
+   */
+  function _addLineBreakBeforeAndAfter(node) {
+    var doc             = node.ownerDocument,
+        nextSibling     = _getNextSiblingThatIsNotBlank(node),
+        previousSibling = _getPreviousSiblingThatIsNotBlank(node);
+
+    if (nextSibling && !_isLineBreakOrBlockElement(nextSibling)) {
+      node.parentNode.insertBefore(doc.createElement("br"), nextSibling);
+    }
+    if (previousSibling && !_isLineBreakOrBlockElement(previousSibling)) {
+      node.parentNode.insertBefore(doc.createElement("br"), node);
+    }
+  }
+
+  /**
+   * Removes line breaks before and after the given node
+   */
+  function _removeLineBreakBeforeAndAfter(node) {
+    var nextSibling     = _getNextSiblingThatIsNotBlank(node),
+        previousSibling = _getPreviousSiblingThatIsNotBlank(node);
+
+    if (nextSibling && _isLineBreak(nextSibling)) {
+      nextSibling.parentNode.removeChild(nextSibling);
+    }
+    if (previousSibling && _isLineBreak(previousSibling)) {
+      previousSibling.parentNode.removeChild(previousSibling);
+    }
+  }
+
+  function _removeLastChildIfLineBreak(node) {
+    var lastChild = node.lastChild;
+    if (lastChild && _isLineBreak(lastChild)) {
+      lastChild.parentNode.removeChild(lastChild);
+    }
+  }
+
+  function _isLineBreak(node) {
+    return node.nodeName === "BR";
+  }
+
+  /**
+   * Checks whether the elment causes a visual line break
+   * (<br> or block elements)
+   */
+  function _isLineBreakOrBlockElement(element) {
+    if (_isLineBreak(element)) {
+      return true;
+    }
+
+    if (dom.getStyle("display").from(element) === "block") {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Execute native query command
+   * and if necessary modify the inserted node's className
+   */
+  function _execCommand(doc, command, nodeName, className) {
+    if (className) {
+      var eventListener = dom.observe(doc, "DOMNodeInserted", function(event) {
+        var target = event.target,
+            displayStyle;
+        if (target.nodeType !== wysihtml5.ELEMENT_NODE) {
+          return;
+        }
+        displayStyle = dom.getStyle("display").from(target);
+        if (displayStyle.substr(0, 6) !== "inline") {
+          // Make sure that only block elements receive the given class
+          target.className += " " + className;
+        }
+      });
+    }
+    doc.execCommand(command, false, nodeName);
+    if (eventListener) {
+      eventListener.stop();
+    }
+  }
+
+  function _selectLineAndWrap(composer, element) {
+    composer.selection.selectLine();
+    composer.selection.surround(element);
+    _removeLineBreakBeforeAndAfter(element);
+    _removeLastChildIfLineBreak(element);
+    composer.selection.selectNode(element);
+  }
+
+  function _hasClasses(element) {
+    return !!wysihtml5.lang.string(element.className).trim();
+  }
+  
+  wysihtml5.commands.formatBlock = {
+    exec: function(composer, command, nodeName, className, classRegExp) {
+      var doc          = composer.doc,
+          blockElement = this.state(composer, command, nodeName, className, classRegExp),
+          selectedNode;
+
+      nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
+
+      if (blockElement) {
+        composer.selection.executeAndRestoreSimple(function() {
+          if (classRegExp) {
+            _removeClass(blockElement, classRegExp);
+          }
+          var hasClasses = _hasClasses(blockElement);
+          if (!hasClasses && blockElement.nodeName === (nodeName || DEFAULT_NODE_NAME)) {
+            // Insert a line break afterwards and beforewards when there are siblings
+            // that are not of type line break or block element
+            _addLineBreakBeforeAndAfter(blockElement);
+            dom.replaceWithChildNodes(blockElement);
+          } else if (hasClasses) {
+            // Make sure that styling is kept by renaming the element to <div> and copying over the class name
+            dom.renameElement(blockElement, DEFAULT_NODE_NAME);
+          }
+        });
+        return;
+      }
+
+      // Find similiar block element and rename it (<h2 class="foo"></h2>  =>  <h1 class="foo"></h1>)
+      if (nodeName === null || wysihtml5.lang.array(BLOCK_ELEMENTS_GROUP).contains(nodeName)) {
+        selectedNode = composer.selection.getSelectedNode();
+        blockElement = dom.getParentElement(selectedNode, {
+          nodeName: BLOCK_ELEMENTS_GROUP
+        });
+
+        if (blockElement) {
+          composer.selection.executeAndRestoreSimple(function() {
+            // Rename current block element to new block element and add class
+            if (nodeName) {
+              blockElement = dom.renameElement(blockElement, nodeName);
+            }
+            if (className) {
+              _addClass(blockElement, className, classRegExp);
+            }
+          });
+          return;
+        }
+      }
+
+      if (composer.commands.support(command)) {
+        _execCommand(doc, command, nodeName || DEFAULT_NODE_NAME, className);
+        return;
+      }
+
+      blockElement = doc.createElement(nodeName || DEFAULT_NODE_NAME);
+      if (className) {
+        blockElement.className = className;
+      }
+      _selectLineAndWrap(composer, blockElement);
+    },
+
+    state: function(composer, command, nodeName, className, classRegExp) {
+      nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
+      var selectedNode = composer.selection.getSelectedNode();
+      return dom.getParentElement(selectedNode, {
+        nodeName:     nodeName,
+        className:    className,
+        classRegExp:  classRegExp
+      });
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);/**
+ * formatInline scenarios for tag "B" (| = caret, |foo| = selected text)
+ *
+ *   #1 caret in unformatted text:
+ *      abcdefg|
+ *   output:
+ *      abcdefg<b>|</b>
+ *   
+ *   #2 unformatted text selected:
+ *      abc|deg|h
+ *   output:
+ *      abc<b>|deg|</b>h
+ *   
+ *   #3 unformatted text selected across boundaries:
+ *      ab|c <span>defg|h</span>
+ *   output:
+ *      ab<b>|c </b><span><b>defg</b>|h</span>
+ *
+ *   #4 formatted text entirely selected
+ *      <b>|abc|</b>
+ *   output:
+ *      |abc|
+ *
+ *   #5 formatted text partially selected
+ *      <b>ab|c|</b>
+ *   output:
+ *      <b>ab</b>|c|
+ *
+ *   #6 formatted text selected across boundaries
+ *      <span>ab|c</span> <b>de|fgh</b>
+ *   output:
+ *      <span>ab|c</span> de|<b>fgh</b>
+ */
+(function(wysihtml5) {
+  var undef,
+      // Treat <b> as <strong> and vice versa
+      ALIAS_MAPPING = {
+        "strong": "b",
+        "em":     "i",
+        "b":      "strong",
+        "i":      "em"
+      },
+      htmlApplier = {};
+  
+  function _getTagNames(tagName) {
+    var alias = ALIAS_MAPPING[tagName];
+    return alias ? [tagName.toLowerCase(), alias.toLowerCase()] : [tagName.toLowerCase()];
+  }
+  
+  function _getApplier(tagName, className, classRegExp) {
+    var identifier = tagName + ":" + className;
+    if (!htmlApplier[identifier]) {
+      htmlApplier[identifier] = new wysihtml5.selection.HTMLApplier(_getTagNames(tagName), className, classRegExp, true);
+    }
+    return htmlApplier[identifier];
+  }
+  
+  wysihtml5.commands.formatInline = {
+    exec: function(composer, command, tagName, className, classRegExp) {
+      var range = composer.selection.getRange();
+      if (!range) {
+        return false;
+      }
+      _getApplier(tagName, className, classRegExp).toggleRange(range);
+      composer.selection.setSelection(range);
+    },
+
+    state: function(composer, command, tagName, className, classRegExp) {
+      var doc           = composer.doc,
+          aliasTagName  = ALIAS_MAPPING[tagName] || tagName,
+          range;
+
+      // Check whether the document contains a node with the desired tagName
+      if (!wysihtml5.dom.hasElementWithTagName(doc, tagName) &&
+          !wysihtml5.dom.hasElementWithTagName(doc, aliasTagName)) {
+        return false;
+      }
+
+       // Check whether the document contains a node with the desired className
+      if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) {
+         return false;
+      }
+
+      range = composer.selection.getRange();
+      if (!range) {
+        return false;
+      }
+
+      return _getApplier(tagName, className, classRegExp).isAppliedToRange(range);
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var undef;
+  
+  wysihtml5.commands.insertHTML = {
+    exec: function(composer, command, html) {
+      if (composer.commands.support(command)) {
+        composer.doc.execCommand(command, false, html);
+      } else {
+        composer.selection.insertHTML(html);
+      }
+    },
+
+    state: function() {
+      return false;
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var NODE_NAME = "IMG";
+  
+  wysihtml5.commands.insertImage = {
+    /**
+     * Inserts an <img>
+     * If selection is already an image link, it removes it
+     * 
+     * @example
+     *    // either ...
+     *    wysihtml5.commands.insertImage.exec(composer, "insertImage", "http://www.google.de/logo.jpg");
+     *    // ... or ...
+     *    wysihtml5.commands.insertImage.exec(composer, "insertImage", { src: "http://www.google.de/logo.jpg", title: "foo" });
+     */
+    exec: function(composer, command, value) {
+      value = typeof(value) === "object" ? value : { src: value };
+
+      var doc     = composer.doc,
+          image   = this.state(composer),
+          textNode,
+          i,
+          parent;
+
+      if (image) {
+        // Image already selected, set the caret before it and delete it
+        composer.selection.setBefore(image);
+        parent = image.parentNode;
+        parent.removeChild(image);
+
+        // and it's parent <a> too if it hasn't got any other relevant child nodes
+        wysihtml5.dom.removeEmptyTextNodes(parent);
+        if (parent.nodeName === "A" && !parent.firstChild) {
+          composer.selection.setAfter(parent);
+          parent.parentNode.removeChild(parent);
+        }
+
+        // firefox and ie sometimes don't remove the image handles, even though the image got removed
+        wysihtml5.quirks.redraw(composer.element);
+        return;
+      }
+
+      image = doc.createElement(NODE_NAME);
+
+      for (i in value) {
+        image[i] = value[i];
+      }
+
+      composer.selection.insertNode(image);
+      if (wysihtml5.browser.hasProblemsSettingCaretAfterImg()) {
+        textNode = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
+        composer.selection.insertNode(textNode);
+        composer.selection.setAfter(textNode);
+      } else {
+        composer.selection.setAfter(image);
+      }
+    },
+
+    state: function(composer) {
+      var doc = composer.doc,
+          selectedNode,
+          text,
+          imagesInSelection;
+
+      if (!wysihtml5.dom.hasElementWithTagName(doc, NODE_NAME)) {
+        return false;
+      }
+
+      selectedNode = composer.selection.getSelectedNode();
+      if (!selectedNode) {
+        return false;
+      }
+
+      if (selectedNode.nodeName === NODE_NAME) {
+        // This works perfectly in IE
+        return selectedNode;
+      }
+
+      if (selectedNode.nodeType !== wysihtml5.ELEMENT_NODE) {
+        return false;
+      }
+
+      text = composer.selection.getText();
+      text = wysihtml5.lang.string(text).trim();
+      if (text) {
+        return false;
+      }
+
+      imagesInSelection = composer.selection.getNodes(wysihtml5.ELEMENT_NODE, function(node) {
+        return node.nodeName === "IMG";
+      });
+
+      if (imagesInSelection.length !== 1) {
+        return false;
+      }
+
+      return imagesInSelection[0];
+    },
+
+    value: function(composer) {
+      var image = this.state(composer);
+      return image && image.src;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var undef,
+      LINE_BREAK = "<br>" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
+  
+  wysihtml5.commands.insertLineBreak = {
+    exec: function(composer, command) {
+      if (composer.commands.support(command)) {
+        composer.doc.execCommand(command, false, null);
+        if (!wysihtml5.browser.autoScrollsToCaret()) {
+          composer.selection.scrollIntoView();
+        }
+      } else {
+        composer.commands.exec("insertHTML", LINE_BREAK);
+      }
+    },
+
+    state: function() {
+      return false;
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var undef;
+  
+  wysihtml5.commands.insertOrderedList = {
+    exec: function(composer, command) {
+      var doc = composer.doc,
+          selectedNode,
+          isEmpty,
+          tempElement,
+          list;
+
+      if (composer.commands.support(command)) {
+        doc.execCommand(command, false, null);
+      } else {
+        selectedNode = composer.selection.getSelectedNode();
+        list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: ["UL", "OL"] }, 4);
+        if (!list) {
+          tempElement = doc.createElement("span");
+          composer.selection.surround(tempElement);
+          isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE;
+          composer.selection.executeAndRestoreSimple(function() {
+            list = wysihtml5.dom.convertToList(tempElement, "ol");
+          });
+
+          if (isEmpty) {
+            composer.selection.selectNode(list.querySelector("li"));
+          }
+          return;
+        }
+
+        composer.selection.executeAndRestoreSimple(function() {
+          if (list.nodeName === "OL") {
+            // Unwrap list
+            // <ol><li>foo</li><li>bar</li></ol>
+            // becomes:
+            // foo<br>bar<br>
+            wysihtml5.dom.resolveList(list);
+          } else if (list.nodeName === "UL" || list.nodeName === "MENU") {
+            // Turn an unordered list into an ordered list
+            // <ul><li>foo</li><li>bar</li></ul>
+            // becomes:
+            // <ol><li>foo</li><li>bar</li></ol>
+            wysihtml5.dom.renameElement(list, "ol");
+          }
+        });
+      }
+    },
+
+    state: function(composer, command) {
+      try {
+        return composer.doc.queryCommandState(command);
+      } catch(e) {
+        return false;
+      }
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var undef;
+  
+  wysihtml5.commands.insertUnorderedList = {
+    exec: function(composer, command) {
+      var doc = composer.doc,
+          selectedNode,
+          isEmpty,
+          tempElement,
+          list;
+
+      if (composer.commands.support(command)) {
+        doc.execCommand(command, false, null);
+      } else {
+        selectedNode = composer.selection.getSelectedNode();
+        list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: ["UL", "OL"] });
+
+        if (!list) {
+          tempElement = doc.createElement("span");
+          composer.selection.surround(tempElement);
+          isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE;
+          composer.selection.executeAndRestoreSimple(function() {
+            list = wysihtml5.dom.convertToList(tempElement, "ul");
+          });
+
+          if (isEmpty) {
+            composer.selection.selectNode(list.querySelector("li"));
+          }
+          return;
+        }
+
+        composer.selection.executeAndRestoreSimple(function() {
+          if (list.nodeName === "UL") {
+            // Unwrap list
+            // <ul><li>foo</li><li>bar</li></ul>
+            // becomes:
+            // foo<br>bar<br>
+            wysihtml5.dom.resolveList(list);
+          } else if (list.nodeName === "OL" || list.nodeName === "MENU") {
+            // Turn an ordered list into an unordered list
+            // <ol><li>foo</li><li>bar</li></ol>
+            // becomes:
+            // <ul><li>foo</li><li>bar</li></ul>
+            wysihtml5.dom.renameElement(list, "ul");
+          }
+        });
+      }
+    },
+
+    state: function(composer, command) {
+      try {
+        return composer.doc.queryCommandState(command);
+      } catch(e) {
+        return false;
+      }
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var undef;
+  
+  wysihtml5.commands.italic = {
+    exec: function(composer, command) {
+      return wysihtml5.commands.formatInline.exec(composer, command, "i");
+    },
+
+    state: function(composer, command, color) {
+      // element.ownerDocument.queryCommandState("italic") results:
+      // firefox: only <i>
+      // chrome:  <i>, <em>, <blockquote>, ...
+      // ie:      <i>, <em>
+      // opera:   only <i>
+      return wysihtml5.commands.formatInline.state(composer, command, "i");
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var undef,
+      CLASS_NAME  = "wysiwyg-text-align-center",
+      REG_EXP     = /wysiwyg-text-align-[a-z]+/g;
+  
+  wysihtml5.commands.justifyCenter = {
+    exec: function(composer, command) {
+      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+    },
+
+    state: function(composer, command) {
+      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var undef,
+      CLASS_NAME  = "wysiwyg-text-align-left",
+      REG_EXP     = /wysiwyg-text-align-[a-z]+/g;
+  
+  wysihtml5.commands.justifyLeft = {
+    exec: function(composer, command) {
+      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+    },
+
+    state: function(composer, command) {
+      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var undef,
+      CLASS_NAME  = "wysiwyg-text-align-right",
+      REG_EXP     = /wysiwyg-text-align-[a-z]+/g;
+  
+  wysihtml5.commands.justifyRight = {
+    exec: function(composer, command) {
+      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+    },
+
+    state: function(composer, command) {
+      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);(function(wysihtml5) {
+  var undef,
+      REG_EXP     = /wysiwyg-text-decoration-underline/g,
+      CLASS_NAME  = "wysiwyg-text-decoration-underline";
+  
+  wysihtml5.commands.underline = {
+    exec: function(composer, command) {
+      return wysihtml5.commands.formatInline.exec(composer, command, "span", CLASS_NAME, REG_EXP);
+    },
+
+    state: function(composer, command) {
+      return wysihtml5.commands.formatInline.state(composer, command, "span", CLASS_NAME, REG_EXP);
+    },
+
+    value: function() {
+      return undef;
+    }
+  };
+})(wysihtml5);/**
+ * Undo Manager for wysihtml5
+ * slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface
+ */
+(function(wysihtml5) {
+  var Z_KEY               = 90,
+      Y_KEY               = 89,
+      BACKSPACE_KEY       = 8,
+      DELETE_KEY          = 46,
+      MAX_HISTORY_ENTRIES = 40,
+      UNDO_HTML           = '<span id="_wysihtml5-undo" class="_wysihtml5-temp">' + wysihtml5.INVISIBLE_SPACE + '</span>',
+      REDO_HTML           = '<span id="_wysihtml5-redo" class="_wysihtml5-temp">' + wysihtml5.INVISIBLE_SPACE + '</span>',
+      dom                 = wysihtml5.dom;
+  
+  function cleanTempElements(doc) {
+    var tempElement;
+    while (tempElement = doc.querySelector("._wysihtml5-temp")) {
+      tempElement.parentNode.removeChild(tempElement);
+    }
+  }
+  
+  wysihtml5.UndoManager = wysihtml5.lang.Dispatcher.extend(
+    /** @scope wysihtml5.UndoManager.prototype */ {
+    constructor: function(editor) {
+      this.editor = editor;
+      this.composer = editor.composer;
+      this.element = this.composer.element;
+      this.history = [this.composer.getValue()];
+      this.position = 1;
+      
+      // Undo manager currently only supported in browsers who have the insertHTML command (not IE)
+      if (this.composer.commands.support("insertHTML")) {
+        this._observe();
+      }
+    },
+    
+    _observe: function() {
+      var that      = this,
+          doc       = this.composer.sandbox.getDocument(),
+          lastKey;
+          
+      // Catch CTRL+Z and CTRL+Y
+      dom.observe(this.element, "keydown", function(event) {
+        if (event.altKey || (!event.ctrlKey && !event.metaKey)) {
+          return;
+        }
+        
+        var keyCode = event.keyCode,
+            isUndo = keyCode === Z_KEY && !event.shiftKey,
+            isRedo = (keyCode === Z_KEY && event.shiftKey) || (keyCode === Y_KEY);
+        
+        if (isUndo) {
+          that.undo();
+          event.preventDefault();
+        } else if (isRedo) {
+          that.redo();
+          event.preventDefault();
+        }
+      });
+      
+      // Catch delete and backspace
+      dom.observe(this.element, "keydown", function(event) {
+        var keyCode = event.keyCode;
+        if (keyCode === lastKey) {
+          return;
+        }
+        
+        lastKey = keyCode;
+        
+        if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) {
+          that.transact();
+        }
+      });
+      
+      // Now this is very hacky:
+      // These days browsers don't offer a undo/redo event which we could hook into
+      // to be notified when the user hits undo/redo in the contextmenu.
+      // Therefore we simply insert two elements as soon as the contextmenu gets opened.
+      // The last element being inserted will be immediately be removed again by a exexCommand("undo")
+      //  => When the second element appears in the dom tree then we know the user clicked "redo" in the context menu
+      //  => When the first element disappears from the dom tree then we know the user clicked "undo" in the context menu
+      if (wysihtml5.browser.hasUndoInContextMenu()) {
+        var interval, observed, cleanUp = function() {
+          cleanTempElements(doc);
+          clearInterval(interval);
+        };
+        
+        dom.observe(this.element, "contextmenu", function() {
+          cleanUp();
+          that.composer.selection.executeAndRestoreSimple(function() {
+            if (that.element.lastChild) {
+              that.composer.selection.setAfter(that.element.lastChild);
+            }
+
+            // enable undo button in context menu
+            doc.execCommand("insertHTML", false, UNDO_HTML);
+            // enable redo button in context menu
+            doc.execCommand("insertHTML", false, REDO_HTML);
+            doc.execCommand("undo", false, null);
+          });
+
+          interval = setInterval(function() {
+            if (doc.getElementById("_wysihtml5-redo")) {
+              cleanUp();
+              that.redo();
+            } else if (!doc.getElementById("_wysihtml5-undo")) {
+              cleanUp();
+              that.undo();
+            }
+          }, 400);
+
+          if (!observed) {
+            observed = true;
+            dom.observe(document, "mousedown", cleanUp);
+            dom.observe(doc, ["mousedown", "paste", "cut", "copy"], cleanUp);
+          }
+        });
+      }
+      
+      this.editor
+        .observe("newword:composer", function() {
+          that.transact();
+        })
+        
+        .observe("beforecommand:composer", function() {
+          that.transact();
+        });
+    },
+    
+    transact: function() {
+      var previousHtml  = this.history[this.position - 1],
+          currentHtml   = this.composer.getValue();
+      
+      if (currentHtml == previousHtml) {
+        return;
+      }
+      
+      var length = this.history.length = this.position;
+      if (length > MAX_HISTORY_ENTRIES) {
+        this.history.shift();
+        this.position--;
+      }
+      
+      this.position++;
+      this.history.push(currentHtml);
+    },
+    
+    undo: function() {
+      this.transact();
+      
+      if (this.position <= 1) {
+        return;
+      }
+      
+      this.set(this.history[--this.position - 1]);
+      this.editor.fire("undo:composer");
+    },
+    
+    redo: function() {
+      if (this.position >= this.history.length) {
+        return;
+      }
+      
+      this.set(this.history[++this.position - 1]);
+      this.editor.fire("redo:composer");
+    },
+    
+    set: function(html) {
+      this.composer.setValue(html);
+      this.editor.focus(true);
+    }
+  });
+})(wysihtml5);
+/**
+ * TODO: the following methods still need unit test coverage
+ */
+wysihtml5.views.View = Base.extend(
+  /** @scope wysihtml5.views.View.prototype */ {
+  constructor: function(parent, textareaElement, config) {
+    this.parent   = parent;
+    this.element  = textareaElement;
+    this.config   = config;
+    
+    this._observeViewChange();
+  },
+  
+  _observeViewChange: function() {
+    var that = this;
+    this.parent.observe("beforeload", function() {
+      that.parent.observe("change_view", function(view) {
+        if (view === that.name) {
+          that.parent.currentView = that;
+          that.show();
+          // Using tiny delay here to make sure that the placeholder is set before focusing
+          setTimeout(function() { that.focus(); }, 0);
+        } else {
+          that.hide();
+        }
+      });
+    });
+  },
+  
+  focus: function() {
+    if (this.element.ownerDocument.querySelector(":focus") === this.element) {
+      return;
+    }
+    
+    try { this.element.focus(); } catch(e) {}
+  },
+  
+  hide: function() {
+    this.element.style.display = "none";
+  },
+  
+  show: function() {
+    this.element.style.display = "";
+  },
+  
+  disable: function() {
+    this.element.setAttribute("disabled", "disabled");
+  },
+  
+  enable: function() {
+    this.element.removeAttribute("disabled");
+  }
+});(function(wysihtml5) {
+  var dom       = wysihtml5.dom,
+      browser   = wysihtml5.browser;
+  
+  wysihtml5.views.Composer = wysihtml5.views.View.extend(
+    /** @scope wysihtml5.views.Composer.prototype */ {
+    name: "composer",
+
+    // Needed for firefox in order to display a proper caret in an empty contentEditable
+    CARET_HACK: "<br>",
+
+    constructor: function(parent, textareaElement, config) {
+      this.base(parent, textareaElement, config);
+      this.textarea = this.parent.textarea;
+      this._initSandbox();
+    },
+
+    clear: function() {
+      this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : this.CARET_HACK;
+    },
+
+    getValue: function(parse) {
+      var value = this.isEmpty() ? "" : wysihtml5.quirks.getCorrectInnerHTML(this.element);
+      
+      if (parse) {
+        value = this.parent.parse(value);
+      }
+
+      // Replace all "zero width no breaking space" chars
+      // which are used as hacks to enable some functionalities
+      // Also remove all CARET hacks that somehow got left
+      value = wysihtml5.lang.string(value).replace(wysihtml5.INVISIBLE_SPACE).by("");
+
+      return value;
+    },
+
+    setValue: function(html, parse) {
+      if (parse) {
+        html = this.parent.parse(html);
+      }
+      this.element.innerHTML = html;
+    },
+
+    show: function() {
+      this.iframe.style.display = this._displayStyle || "";
+
+      // Firefox needs this, otherwise contentEditable becomes uneditable
+      this.disable();
+      this.enable();
+    },
+
+    hide: function() {
+      this._displayStyle = dom.getStyle("display").from(this.iframe);
+      if (this._displayStyle === "none") {
+        this._displayStyle = null;
+      }
+      this.iframe.style.display = "none";
+    },
+
+    disable: function() {
+      this.element.removeAttribute("contentEditable");
+      this.base();
+    },
+
+    enable: function() {
+      this.element.setAttribute("contentEditable", "true");
+      this.base();
+    },
+
+    focus: function(setToEnd) {
+      // IE 8 fires the focus event after .focus()
+      // This is needed by our simulate_placeholder.js to work
+      // therefore we clear it ourselves this time
+      if (wysihtml5.browser.doesAsyncFocus() && this.hasPlaceholderSet()) {
+        this.clear();
+      }
+      
+      this.base();
+      
+      var lastChild = this.element.lastChild;
+      if (setToEnd && lastChild) {
+        if (lastChild.nodeName === "BR") {
+          this.selection.setBefore(this.element.lastChild);
+        } else {
+          this.selection.setAfter(this.element.lastChild);
+        }
+      }
+    },
+
+    getTextContent: function() {
+      return dom.getTextContent(this.element);
+    },
+
+    hasPlaceholderSet: function() {
+      return this.getTextContent() == this.textarea.element.getAttribute("placeholder");
+    },
+
+    isEmpty: function() {
+      var innerHTML               = this.element.innerHTML,
+          elementsWithVisualValue = "blockquote, ul, ol, img, embed, object, table, iframe, svg, video, audio, button, input, select, textarea";
+      return innerHTML === ""              || 
+             innerHTML === this.CARET_HACK ||
+             this.hasPlaceholderSet()      ||
+             (this.getTextContent() === "" && !this.element.querySelector(elementsWithVisualValue));
+    },
+
+    _initSandbox: function() {
+      var that = this;
+      
+      this.sandbox = new dom.Sandbox(function() {
+        that._create();
+      }, {
+        stylesheets:  this.config.stylesheets
+      });
+      this.iframe  = this.sandbox.getIframe();
+
+      // Create hidden field which tells the server after submit, that the user used an wysiwyg editor
+      var hiddenField = document.createElement("input");
+      hiddenField.type   = "hidden";
+      hiddenField.name   = "_wysihtml5_mode";
+      hiddenField.value  = 1;
+
+      // Store reference to current wysihtml5 instance on the textarea element
+      var textareaElement = this.textarea.element;
+      dom.insert(this.iframe).after(textareaElement);
+      dom.insert(hiddenField).after(textareaElement);
+    },
+
+    _create: function() {
+      var that = this;
+      
+      this.doc                = this.sandbox.getDocument();
+      this.element            = this.doc.body;
+      this.textarea           = this.parent.textarea;
+      this.element.innerHTML  = this.textarea.getValue(true);
+      this.enable();
+      
+      // Make sure our selection handler is ready
+      this.selection = new wysihtml5.Selection(this.parent);
+      
+      // Make sure commands dispatcher is ready
+      this.commands  = new wysihtml5.Commands(this.parent);
+
+      dom.copyAttributes([
+        "className", "spellcheck", "title", "lang", "dir", "accessKey"
+      ]).from(this.textarea.element).to(this.element);
+      
+      dom.addClass(this.element, this.config.composerClassName);
+
+      // Make the editor look like the original textarea, by syncing styles
+      if (this.config.style) {
+        this.style();
+      }
+
+      this.observe();
+
+      var name = this.config.name;
+      if (name) {
+        dom.addClass(this.element, name);
+        dom.addClass(this.iframe, name);
+      }
+
+      // Simulate html5 placeholder attribute on contentEditable element
+      var placeholderText = typeof(this.config.placeholder) === "string"
+        ? this.config.placeholder
+        : this.textarea.element.getAttribute("placeholder");
+      if (placeholderText) {
+        dom.simulatePlaceholder(this.parent, this, placeholderText);
+      }
+      
+      // Make sure that the browser avoids using inline styles whenever possible
+      this.commands.exec("styleWithCSS", false);
+
+      this._initAutoLinking();
+      this._initObjectResizing();
+      this._initUndoManager();
+
+      // Simulate html5 autofocus on contentEditable element
+      if (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) {
+        setTimeout(function() { that.focus(); }, 100);
+      }
+
+      wysihtml5.quirks.insertLineBreakOnReturn(this);
+
+      // IE sometimes leaves a single paragraph, which can't be removed by the user
+      if (!browser.clearsContentEditableCorrectly()) {
+        wysihtml5.quirks.ensureProperClearing(this);
+      }
+
+      if (!browser.clearsListsInContentEditableCorrectly()) {
+        wysihtml5.quirks.ensureProperClearingOfLists(this);
+      }
+
+      // Set up a sync that makes sure that textarea and editor have the same content
+      if (this.initSync && this.config.sync) {
+        this.initSync();
+      }
+
+      // Okay hide the textarea, we are ready to go
+      this.textarea.hide();
+
+      // Fire global (before-)load event
+      this.parent.fire("beforeload").fire("load");
+    },
+
+    _initAutoLinking: function() {
+      var that                           = this,
+          supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(),
+          supportsAutoLinking            = browser.doesAutoLinkingInContentEditable();
+      if (supportsDisablingOfAutoLinking) {
+        this.commands.exec("autoUrlDetect", false);
+      }
+
+      if (!this.config.autoLink) {
+        return;
+      }
+
+      // Only do the auto linking by ourselves when the browser doesn't support auto linking
+      // OR when he supports auto linking but we were able to turn it off (IE9+)
+      if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
+        this.parent.observe("newword:composer", function() {
+          that.selection.executeAndRestore(function(startContainer, endContainer) {
+            dom.autoLink(endContainer.parentNode);
+          });
+        });
+      }
+
+      // Assuming we have the following:
+      //  <a href="http://www.google.de">http://www.google.de</a>
+      // If a user now changes the url in the innerHTML we want to make sure that
+      // it's synchronized with the href attribute (as long as the innerHTML is still a url)
+      var // Use a live NodeList to check whether there are any links in the document
+          links           = this.sandbox.getDocument().getElementsByTagName("a"),
+          // The autoLink helper method reveals a reg exp to detect correct urls
+          urlRegExp       = dom.autoLink.URL_REG_EXP,
+          getTextContent  = function(element) {
+            var textContent = wysihtml5.lang.string(dom.getTextContent(element)).trim();
+            if (textContent.substr(0, 4) === "www.") {
+              textContent = "http://" + textContent;
+            }
+            return textContent;
+          };
+
+      dom.observe(this.element, "keydown", function(event) {
+        if (!links.length) {
+          return;
+        }
+
+        var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument),
+            link         = dom.getParentElement(selectedNode, { nodeName: "A" }, 4),
+            textContent;
+
+        if (!link) {
+          return;
+        }
+
+        textContent = getTextContent(link);
+        // keydown is fired before the actual content is changed
+        // therefore we set a timeout to change the href
+        setTimeout(function() {
+          var newTextContent = getTextContent(link);
+          if (newTextContent === textContent) {
+            return;
+          }
+
+          // Only set href when new href looks like a valid url
+          if (newTextContent.match(urlRegExp)) {
+            link.setAttribute("href", newTextContent);
+          }
+        }, 0);
+      });
+    },
+
+    _initObjectResizing: function() {
+      var properties        = ["width", "height"],
+          propertiesLength  = properties.length,
+          element           = this.element;
+      
+      this.commands.exec("enableObjectResizing", this.config.allowObjectResizing);
+      
+      if (this.config.allowObjectResizing) {
+         // IE sets inline styles after resizing objects
+         // The following lines make sure that the width/height css properties
+         // are copied over to the width/height attributes
+        if (browser.supportsEvent("resizeend")) {
+          dom.observe(element, "resizeend", function(event) {
+            var target = event.target || event.srcElement,
+                style  = target.style,
+                i      = 0,
+                property;
+            for(; i<propertiesLength; i++) {
+              property = properties[i];
+              if (style[property]) {
+                target.setAttribute(property, parseInt(style[property], 10));
+                style[property] = "";
+              }
+            }
+            // After resizing IE sometimes forgets to remove the old resize handles
+            wysihtml5.quirks.redraw(element);
+          });
+        }
+      } else {
+        if (browser.supportsEvent("resizestart")) {
+          dom.observe(element, "resizestart", function(event) { event.preventDefault(); });
+        }
+      }
+    },
+    
+    _initUndoManager: function() {
+      new wysihtml5.UndoManager(this.parent);
+    }
+  });
+})(wysihtml5);(function(wysihtml5) {
+  var dom             = wysihtml5.dom,
+      doc             = document,
+      win             = window,
+      HOST_TEMPLATE   = doc.createElement("div"),
+      /**
+       * Styles to copy from textarea to the composer element
+       */
+      TEXT_FORMATTING = [
+        "background-color",
+        "color", "cursor",
+        "font-family", "font-size", "font-style", "font-variant", "font-weight",
+        "line-height", "letter-spacing",
+        "text-align", "text-decoration", "text-indent", "text-rendering",
+        "word-break", "word-wrap", "word-spacing"
+      ],
+      /**
+       * Styles to copy from textarea to the iframe
+       */
+      BOX_FORMATTING = [
+        "background-color",
+        "border-collapse",
+        "border-bottom-color", "border-bottom-style", "border-bottom-width",
+        "border-left-color", "border-left-style", "border-left-width",
+        "border-right-color", "border-right-style", "border-right-width",
+        "border-top-color", "border-top-style", "border-top-width",
+        "clear", "display", "float",
+        "margin-bottom", "margin-left", "margin-right", "margin-top",
+        "outline-color", "outline-offset", "outline-width", "outline-style",
+        "padding-left", "padding-right", "padding-top", "padding-bottom",
+        "position", "top", "left", "right", "bottom", "z-index",
+        "vertical-align", "text-align",
+        "-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing",
+        "-webkit-box-shadow", "-moz-box-shadow", "-ms-box-shadow","box-shadow",
+        "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius",
+        "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius",
+        "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius",
+        "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius",
+        "width", "height"
+      ],
+      /**
+       * Styles to sync while the window gets resized
+       */
+      RESIZE_STYLE = [
+        "width", "height",
+        "top", "left", "right", "bottom"
+      ],
+      ADDITIONAL_CSS_RULES = [
+        "html             { height: 100%; }",
+        "body             { min-height: 100%; padding: 0; margin: 0; margin-top: -1px; padding-top: 1px; }",
+        "._wysihtml5-temp { display: none; }",
+        wysihtml5.browser.isGecko ?
+          "body.placeholder { color: graytext !important; }" : 
+          "body.placeholder { color: #a9a9a9 !important; }",
+        "body[disabled]   { background-color: #eee !important; color: #999 !important; cursor: default !important; }",
+        // Ensure that user see's broken images and can delete them
+        "img:-moz-broken  { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"
+      ];
+  
+  /**
+   * With "setActive" IE offers a smart way of focusing elements without scrolling them into view:
+   * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx
+   *
+   * Other browsers need a more hacky way: (pssst don't tell my mama)
+   * In order to prevent the element being scrolled into view when focusing it, we simply
+   * move it out of the scrollable area, focus it, and reset it's position
+   */
+  var focusWithoutScrolling = function(element) {
+    if (element.setActive) {
+      // Following line could cause a js error when the textarea is invisible
+      // See https://github.com/xing/wysihtml5/issues/9
+      try { element.setActive(); } catch(e) {}
+    } else {
+      var elementStyle = element.style,
+          originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop,
+          originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft,
+          originalStyles = {
+            position:         elementStyle.position,
+            top:              elementStyle.top,
+            left:             elementStyle.left,
+            WebkitUserSelect: elementStyle.WebkitUserSelect
+          };
+      
+      dom.setStyles({
+        position:         "absolute",
+        top:              "-99999px",
+        left:             "-99999px",
+        // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother
+        WebkitUserSelect: "none"
+      }).on(element);
+      
+      element.focus();
+      
+      dom.setStyles(originalStyles).on(element);
+      
+      if (win.scrollTo) {
+        // Some browser extensions unset this method to prevent annoyances
+        // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100
+        // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1
+        win.scrollTo(originalScrollLeft, originalScrollTop);
+      }
+    }
+  };
+  
+  
+  wysihtml5.views.Composer.prototype.style = function() {
+    var that                  = this,
+        originalActiveElement = doc.querySelector(":focus"),
+        textareaElement       = this.textarea.element,
+        hasPlaceholder        = textareaElement.hasAttribute("placeholder"),
+        originalPlaceholder   = hasPlaceholder && textareaElement.getAttribute("placeholder");
+    this.focusStylesHost      = this.focusStylesHost  || HOST_TEMPLATE.cloneNode(false);
+    this.blurStylesHost       = this.blurStylesHost   || HOST_TEMPLATE.cloneNode(false);
+  
+    // Remove placeholder before copying (as the placeholder has an affect on the computed style)
+    if (hasPlaceholder) {
+      textareaElement.removeAttribute("placeholder");
+    }
+  
+    if (textareaElement === originalActiveElement) {
+      textareaElement.blur();
+    }
+  
+    // --------- iframe styles (has to be set before editor styles, otherwise IE9 sets wrong fontFamily on blurStylesHost) ---------
+    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.iframe).andTo(this.blurStylesHost);
+  
+    // --------- editor styles ---------
+    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.element).andTo(this.blurStylesHost);
+  
+    // --------- apply standard rules ---------
+    dom.insertCSS(ADDITIONAL_CSS_RULES).into(this.element.ownerDocument);
+  
+    // --------- :focus styles ---------
+    focusWithoutScrolling(textareaElement);
+    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.focusStylesHost);
+    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.focusStylesHost);
+  
+    // Make sure that we don't change the display style of the iframe when copying styles oblur/onfocus
+    // this is needed for when the change_view event is fired where the iframe is hidden and then
+    // the blur event fires and re-displays it
+    var boxFormattingStyles = wysihtml5.lang.array(BOX_FORMATTING).without(["display"]);
+  
+    // --------- restore focus ---------
+    if (originalActiveElement) {
+      originalActiveElement.focus();
+    } else {
+      textareaElement.blur();
+    }
+  
+    // --------- restore placeholder ---------
+    if (hasPlaceholder) {
+      textareaElement.setAttribute("placeholder", originalPlaceholder);
+    }
+  
+    // When copying styles, we only get the computed style which is never returned in percent unit
+    // Therefore we've to recalculate style onresize
+    if (!wysihtml5.browser.hasCurrentStyleProperty()) {
+      dom.observe(win, "resize", function() {
+        var originalDisplayStyle = dom.getStyle("display").from(textareaElement);
+        textareaElement.style.display = "";
+        dom.copyStyles(RESIZE_STYLE)
+          .from(textareaElement)
+          .to(that.iframe)
+          .andTo(that.focusStylesHost)
+          .andTo(that.blurStylesHost);
+        textareaElement.style.display = originalDisplayStyle;
+      });
+    }
+  
+    // --------- Sync focus/blur styles ---------
+    this.parent.observe("focus:composer", function() {
+      dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.iframe);
+      dom.copyStyles(TEXT_FORMATTING)     .from(that.focusStylesHost).to(that.element);
+    });
+
+    this.parent.observe("blur:composer", function() {
+      dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.iframe);
+      dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element);
+    });
+  
+    return this;
+  };
+})(wysihtml5);/**
+ * Taking care of events
+ *  - Simulating 'change' event on contentEditable element
+ *  - Handling drag & drop logic
+ *  - Catch paste events
+ *  - Dispatch proprietary newword:composer event
+ *  - Keyboard shortcuts
+ */
+(function(wysihtml5) {
+  var dom       = wysihtml5.dom,
+      browser   = wysihtml5.browser,
+      /**
+       * Map keyCodes to query commands
+       */
+      shortcuts = {
+        "66": "bold",     // B
+        "73": "italic",   // I
+        "85": "underline" // U
+      };
+  
+  wysihtml5.views.Composer.prototype.observe = function() {
+    var that                = this,
+        state               = this.getValue(),
+        iframe              = this.sandbox.getIframe(),
+        element             = this.element,
+        focusBlurElement    = browser.supportsEventsInIframeCorrectly() ? element : this.sandbox.getWindow(),
+        // Firefox < 3.5 doesn't support the drop event, instead it supports a so called "dragdrop" event which behaves almost the same
+        pasteEvents         = browser.supportsEvent("drop") ? ["drop", "paste"] : ["dragdrop", "paste"];
+
+    // --------- destroy:composer event ---------
+    dom.observe(iframe, "DOMNodeRemoved", function() {
+      clearInterval(domNodeRemovedInterval);
+      that.parent.fire("destroy:composer");
+    });
+
+    // DOMNodeRemoved event is not supported in IE 8
+    var domNodeRemovedInterval = setInterval(function() {
+      if (!dom.contains(document.documentElement, iframe)) {
+        clearInterval(domNodeRemovedInterval);
+        that.parent.fire("destroy:composer");
+      }
+    }, 250);
+
+
+    // --------- Focus & blur logic ---------
+    dom.observe(focusBlurElement, "focus", function() {
+      that.parent.fire("focus").fire("focus:composer");
+
+      // Delay storing of state until all focus handler are fired
+      // especially the one which resets the placeholder
+      setTimeout(function() { state = that.getValue(); }, 0);
+    });
+
+    dom.observe(focusBlurElement, "blur", function() {
+      if (state !== that.getValue()) {
+        that.parent.fire("change").fire("change:composer");
+      }
+      that.parent.fire("blur").fire("blur:composer");
+    });
+    
+    if (wysihtml5.browser.isIos()) {
+      // When on iPad/iPhone/IPod after clicking outside of editor, the editor loses focus
+      // but the UI still acts as if the editor has focus (blinking caret and onscreen keyboard visible)
+      // We prevent that by focusing a temporary input element which immediately loses focus
+      dom.observe(element, "blur", function() {
+        var input = element.ownerDocument.createElement("input"),
+            originalScrollTop = document.documentElement.scrollTop || document.body.scrollTop,
+            originalScrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
+        try {
+          that.selection.insertNode(input);
+        } catch(e) {
+          element.appendChild(input);
+        }
+        input.focus();
+        input.parentNode.removeChild(input);
+        
+        window.scrollTo(originalScrollLeft, originalScrollTop);
+      });
+    }
+
+    // --------- Drag & Drop logic ---------
+    dom.observe(element, "dragenter", function() {
+      that.parent.fire("unset_placeholder");
+    });
+
+    if (browser.firesOnDropOnlyWhenOnDragOverIsCancelled()) {
+      dom.observe(element, ["dragover", "dragenter"], function(event) {
+        event.preventDefault();
+      });
+    }
+
+    dom.observe(element, pasteEvents, function(event) {
+      var dataTransfer = event.dataTransfer,
+          data;
+
+      if (dataTransfer && browser.supportsDataTransfer()) {
+        data = dataTransfer.getData("text/html") || dataTransfer.getData("text/plain");
+      }
+      if (data) {
+        element.focus();
+        that.commands.exec("insertHTML", data);
+        that.parent.fire("paste").fire("paste:composer");
+        event.stopPropagation();
+        event.preventDefault();
+      } else {
+        setTimeout(function() {
+          that.parent.fire("paste").fire("paste:composer");
+        }, 0);
+      }
+    });
+
+    // --------- neword event ---------
+    dom.observe(element, "keyup", function(event) {
+      var keyCode = event.keyCode;
+      if (keyCode === wysihtml5.SPACE_KEY || keyCode === wysihtml5.ENTER_KEY) {
+        that.parent.fire("newword:composer");
+      }
+    });
+
+    this.parent.observe("paste:composer", function() {
+      setTimeout(function() { that.parent.fire("newword:composer"); }, 0);
+    });
+
+    // --------- Make sure that images are selected when clicking on them ---------
+    if (!browser.canSelectImagesInContentEditable()) {
+      dom.observe(element, "mousedown", function(event) {
+        var target = event.target;
+        if (target.nodeName === "IMG") {
+          that.selection.selectNode(target);
+          event.preventDefault();
+        }
+      });
+    }
+    
+    // --------- Shortcut logic ---------
+    dom.observe(element, "keydown", function(event) {
+      var keyCode  = event.keyCode,
+          command  = shortcuts[keyCode];
+      if ((event.ctrlKey || event.metaKey) && command) {
+        that.commands.exec(command);
+        event.preventDefault();
+      }
+    });
+
+    // --------- Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor ---------
+    dom.observe(element, "keydown", function(event) {
+      var target  = that.selection.getSelectedNode(true),
+          keyCode = event.keyCode,
+          parent;
+      if (target && target.nodeName === "IMG" && (keyCode === wysihtml5.BACKSPACE_KEY || keyCode === wysihtml5.DELETE_KEY)) { // 8 => backspace, 46 => delete
+        parent = target.parentNode;
+        // delete the <img>
+        parent.removeChild(target);
+        // and it's parent <a> too if it hasn't got any other child nodes
+        if (parent.nodeName === "A" && !parent.firstChild) {
+          parent.parentNode.removeChild(parent);
+        }
+
+        setTimeout(function() { wysihtml5.quirks.redraw(element); }, 0);
+        event.preventDefault();
+      }
+    });
+
+    // --------- Show url in tooltip when hovering links or images ---------
+    var titlePrefixes = {
+      IMG: "Image: ",
+      A:   "Link: "
+    };
+    
+    dom.observe(element, "mouseover", function(event) {
+      var target   = event.target,
+          nodeName = target.nodeName,
+          title;
+      if (nodeName !== "A" && nodeName !== "IMG") {
+        return;
+      }
+
+      title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src"));
+      target.setAttribute("title", title);
+    });
+  };
+})(wysihtml5);/**
+ * Class that takes care that the value of the composer and the textarea is always in sync
+ */
+(function(wysihtml5) {
+  var INTERVAL = 400;
+  
+  wysihtml5.views.Synchronizer = Base.extend(
+    /** @scope wysihtml5.views.Synchronizer.prototype */ {
+
+    constructor: function(editor, textarea, composer) {
+      this.editor   = editor;
+      this.textarea = textarea;
+      this.composer = composer;
+
+      this._observe();
+    },
+
+    /**
+     * Sync html from composer to textarea
+     * Takes care of placeholders
+     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the textarea
+     */
+    fromComposerToTextarea: function(shouldParseHtml) {
+      this.textarea.setValue(wysihtml5.lang.string(this.composer.getValue()).trim(), shouldParseHtml);
+    },
+
+    /**
+     * Sync value of textarea to composer
+     * Takes care of placeholders
+     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer
+     */
+    fromTextareaToComposer: function(shouldParseHtml) {
+      var textareaValue = this.textarea.getValue();
+      if (textareaValue) {
+        this.composer.setValue(textareaValue, shouldParseHtml);
+      } else {
+        this.composer.clear();
+        this.editor.fire("set_placeholder");
+      }
+    },
+
+    /**
+     * Invoke syncing based on view state
+     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer/textarea
+     */
+    sync: function(shouldParseHtml) {
+      if (this.editor.currentView.name === "textarea") {
+        this.fromTextareaToComposer(shouldParseHtml);
+      } else {
+        this.fromComposerToTextarea(shouldParseHtml);
+      }
+    },
+
+    /**
+     * Initializes interval-based syncing
+     * also makes sure that on-submit the composer's content is synced with the textarea
+     * immediately when the form gets submitted
+     */
+    _observe: function() {
+      var interval,
+          that          = this,
+          form          = this.textarea.element.form,
+          startInterval = function() {
+            interval = setInterval(function() { that.fromComposerToTextarea(); }, INTERVAL);
+          },
+          stopInterval  = function() {
+            clearInterval(interval);
+            interval = null;
+          };
+
+      startInterval();
+
+      if (form) {
+        // If the textarea is in a form make sure that after onreset and onsubmit the composer
+        // has the correct state
+        wysihtml5.dom.observe(form, "submit", function() {
+          that.sync(true);
+        });
+        wysihtml5.dom.observe(form, "reset", function() {
+          setTimeout(function() { that.fromTextareaToComposer(); }, 0);
+        });
+      }
+
+      this.editor.observe("change_view", function(view) {
+        if (view === "composer" && !interval) {
+          that.fromTextareaToComposer(true);
+          startInterval();
+        } else if (view === "textarea") {
+          that.fromComposerToTextarea(true);
+          stopInterval();
+        }
+      });
+
+      this.editor.observe("destroy:composer", stopInterval);
+    }
+  });
+})(wysihtml5);
+wysihtml5.views.Textarea = wysihtml5.views.View.extend(
+  /** @scope wysihtml5.views.Textarea.prototype */ {
+  name: "textarea",
+  
+  constructor: function(parent, textareaElement, config) {
+    this.base(parent, textareaElement, config);
+    
+    this._observe();
+  },
+  
+  clear: function() {
+    this.element.value = "";
+  },
+  
+  getValue: function(parse) {
+    var value = this.isEmpty() ? "" : this.element.value;
+    if (parse) {
+      value = this.parent.parse(value);
+    }
+    return value;
+  },
+  
+  setValue: function(html, parse) {
+    if (parse) {
+      html = this.parent.parse(html);
+    }
+    this.element.value = html;
+  },
+  
+  hasPlaceholderSet: function() {
+    var supportsPlaceholder = wysihtml5.browser.supportsPlaceholderAttributeOn(this.element),
+        placeholderText     = this.element.getAttribute("placeholder") || null,
+        value               = this.element.value,
+        isEmpty             = !value;
+    return (supportsPlaceholder && isEmpty) || (value === placeholderText);
+  },
+  
+  isEmpty: function() {
+    return !wysihtml5.lang.string(this.element.value).trim() || this.hasPlaceholderSet();
+  },
+  
+  _observe: function() {
+    var element = this.element,
+        parent  = this.parent,
+        eventMapping = {
+          focusin:  "focus",
+          focusout: "blur"
+        },
+        /**
+         * Calling focus() or blur() on an element doesn't synchronously trigger the attached focus/blur events
+         * This is the case for focusin and focusout, so let's use them whenever possible, kkthxbai
+         */
+        events = wysihtml5.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"];
+    
+    parent.observe("beforeload", function() {
+      wysihtml5.dom.observe(element, events, function(event) {
+        var eventName = eventMapping[event.type] || event.type;
+        parent.fire(eventName).fire(eventName + ":textarea");
+      });
+      
+      wysihtml5.dom.observe(element, ["paste", "drop"], function() {
+        setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0);
+      });
+    });
+  }
+});/**
+ * Toolbar Dialog
+ *
+ * @param {Element} link The toolbar link which causes the dialog to show up
+ * @param {Element} container The dialog container
+ *
+ * @example
+ *    <!-- Toolbar link -->
+ *    <a data-wysihtml5-command="insertImage">insert an image</a>
+ *
+ *    <!-- Dialog -->
+ *    <div data-wysihtml5-dialog="insertImage" style="display: none;">
+ *      <label>
+ *        URL: <input data-wysihtml5-dialog-field="src" value="http://">
+ *      </label>
+ *      <label>
+ *        Alternative text: <input data-wysihtml5-dialog-field="alt" value="">
+ *      </label>
+ *    </div>
+ *
+ *    <script>
+ *      var dialog = new wysihtml5.toolbar.Dialog(
+ *        document.querySelector("[data-wysihtml5-command='insertImage']"),
+ *        document.querySelector("[data-wysihtml5-dialog='insertImage']")
+ *      );
+ *      dialog.observe("save", function(attributes) {
+ *        // do something
+ *      });
+ *    </script>
+ */
+(function(wysihtml5) {
+  var dom                     = wysihtml5.dom,
+      CLASS_NAME_OPENED       = "wysihtml5-command-dialog-opened",
+      SELECTOR_FORM_ELEMENTS  = "input, select, textarea",
+      SELECTOR_FIELDS         = "[data-wysihtml5-dialog-field]",
+      ATTRIBUTE_FIELDS        = "data-wysihtml5-dialog-field";
+      
+  
+  wysihtml5.toolbar.Dialog = wysihtml5.lang.Dispatcher.extend(
+    /** @scope wysihtml5.toolbar.Dialog.prototype */ {
+    constructor: function(link, container) {
+      this.link       = link;
+      this.container  = container;
+    },
+
+    _observe: function() {
+      if (this._observed) {
+        return;
+      }
+      
+      var that = this,
+          callbackWrapper = function(event) {
+            var attributes = that._serialize();
+            if (attributes == that.elementToChange) {
+              that.fire("edit", attributes);
+            } else {
+              that.fire("save", attributes);
+            }
+            that.hide();
+            event.preventDefault();
+            event.stopPropagation();
+          };
+
+      dom.observe(that.link, "click", function(event) {
+        if (dom.hasClass(that.link, CLASS_NAME_OPENED)) {
+          setTimeout(function() { that.hide(); }, 0);
+        }
+      });
+
+      dom.observe(this.container, "keydown", function(event) {
+        var keyCode = event.keyCode;
+        if (keyCode === wysihtml5.ENTER_KEY) {
+          callbackWrapper(event);
+        }
+        if (keyCode === wysihtml5.ESCAPE_KEY) {
+          that.hide();
+        }
+      });
+
+      dom.delegate(this.container, "[data-wysihtml5-dialog-action=save]", "click", callbackWrapper);
+
+      dom.delegate(this.container, "[data-wysihtml5-dialog-action=cancel]", "click", function(event) {
+        that.fire("cancel");
+        that.hide();
+        event.preventDefault();
+        event.stopPropagation();
+      });
+
+      var formElements  = this.container.querySelectorAll(SELECTOR_FORM_ELEMENTS),
+          i             = 0,
+          length        = formElements.length,
+          _clearInterval = function() { clearInterval(that.interval); };
+      for (; i<length; i++) {
+        dom.observe(formElements[i], "change", _clearInterval);
+      }
+
+      this._observed = true;
+    },
+
+    /**
+     * Grabs all fields in the dialog and puts them in key=>value style in an object which
+     * then gets returned
+     */
+    _serialize: function() {
+      var data    = this.elementToChange || {},
+          fields  = this.container.querySelectorAll(SELECTOR_FIELDS),
+          length  = fields.length,
+          i       = 0;
+      for (; i<length; i++) {
+        data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
+      }
+      return data;
+    },
+
+    /**
+     * Takes the attributes of the "elementToChange"
+     * and inserts them in their corresponding dialog input fields
+     * 
+     * Assume the "elementToChange" looks like this:
+     *    <a href="http://www.google.com" target="_blank">foo</a>
+     *
+     * and we have the following dialog:
+     *    <input type="text" data-wysihtml5-dialog-field="href" value="">
+     *    <input type="text" data-wysihtml5-dialog-field="target" value="">
+     * 
+     * after calling _interpolate() the dialog will look like this
+     *    <input type="text" data-wysihtml5-dialog-field="href" value="http://www.google.com">
+     *    <input type="text" data-wysihtml5-dialog-field="target" value="_blank">
+     *
+     * Basically it adopted the attribute values into the corresponding input fields
+     *
+     */
+    _interpolate: function(avoidHiddenFields) {
+      var field,
+          fieldName,
+          newValue,
+          focusedElement = document.querySelector(":focus"),
+          fields         = this.container.querySelectorAll(SELECTOR_FIELDS),
+          length         = fields.length,
+          i              = 0;
+      for (; i<length; i++) {
+        field = fields[i];
+        
+        // Never change elements where the user is currently typing in
+        if (field === focusedElement) {
+          continue;
+        }
+        
+        // Don't update hidden fields
+        // See https://github.com/xing/wysihtml5/pull/14
+        if (avoidHiddenFields && field.type === "hidden") {
+          continue;
+        }
+        
+        fieldName = field.getAttribute(ATTRIBUTE_FIELDS);
+        newValue  = this.elementToChange ? (this.elementToChange[fieldName] || "") : field.defaultValue;
+        field.value = newValue;
+      }
+    },
+
+    /**
+     * Show the dialog element
+     */
+    show: function(elementToChange) {
+      var that        = this,
+          firstField  = this.container.querySelector(SELECTOR_FORM_ELEMENTS);
+      this.elementToChange = elementToChange;
+      this._observe();
+      this._interpolate();
+      if (elementToChange) {
+        this.interval = setInterval(function() { that._interpolate(true); }, 500);
+      }
+      dom.addClass(this.link, CLASS_NAME_OPENED);
+      this.container.style.display = "";
+      this.fire("show");
+      if (firstField && !elementToChange) {
+        try {
+          firstField.focus();
+        } catch(e) {}
+      }
+    },
+
+    /**
+     * Hide the dialog element
+     */
+    hide: function() {
+      clearInterval(this.interval);
+      this.elementToChange = null;
+      dom.removeClass(this.link, CLASS_NAME_OPENED);
+      this.container.style.display = "none";
+      this.fire("hide");
+    }
+  });
+})(wysihtml5);
+/**
+ * Converts speech-to-text and inserts this into the editor
+ * As of now (2011/03/25) this only is supported in Chrome >= 11
+ *
+ * Note that it sends the recorded audio to the google speech recognition api:
+ * http://stackoverflow.com/questions/4361826/does-chrome-have-buil-in-speech-recognition-for-input-type-text-x-webkit-speec
+ *
+ * Current HTML5 draft can be found here
+ * http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/api-draft.html
+ * 
+ * "Accessing Google Speech API Chrome 11"
+ * http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
+ */
+(function(wysihtml5) {
+  var dom = wysihtml5.dom;
+  
+  var linkStyles = {
+    position: "relative"
+  };
+  
+  var wrapperStyles = {
+    left:     0,
+    margin:   0,
+    opacity:  0,
+    overflow: "hidden",
+    padding:  0,
+    position: "absolute",
+    top:      0,
+    zIndex:   1
+  };
+  
+  var inputStyles = {
+    cursor:     "inherit",
+    fontSize:   "50px",
+    height:     "50px",
+    marginTop:  "-25px",
+    outline:    0,
+    padding:    0,
+    position:   "absolute",
+    right:      "-4px",
+    top:        "50%"
+  };
+  
+  var inputAttributes = {
+    "x-webkit-speech": "",
+    "speech":          ""
+  };
+  
+  wysihtml5.toolbar.Speech = function(parent, link) {
+    var input = document.createElement("input");
+    if (!wysihtml5.browser.supportsSpeechApiOn(input)) {
+      link.style.display = "none";
+      return;
+    }
+    
+    var wrapper = document.createElement("div");
+    
+    wysihtml5.lang.object(wrapperStyles).merge({
+      width:  link.offsetWidth  + "px",
+      height: link.offsetHeight + "px"
+    });
+    
+    dom.insert(input).into(wrapper);
+    dom.insert(wrapper).into(link);
+    
+    dom.setStyles(inputStyles).on(input);
+    dom.setAttributes(inputAttributes).on(input)
+    
+    dom.setStyles(wrapperStyles).on(wrapper);
+    dom.setStyles(linkStyles).on(link);
+    
+    var eventName = "onwebkitspeechchange" in input ? "webkitspeechchange" : "speechchange";
+    dom.observe(input, eventName, function() {
+      parent.execCommand("insertText", input.value);
+      input.value = "";
+    });
+    
+    dom.observe(input, "click", function(event) {
+      if (dom.hasClass(link, "wysihtml5-command-disabled")) {
+        event.preventDefault();
+      }
+      
+      event.stopPropagation();
+    });
+  };
+})(wysihtml5);/**
+ * Toolbar
+ *
+ * @param {Object} parent Reference to instance of Editor instance
+ * @param {Element} container Reference to the toolbar container element
+ *
+ * @example
+ *    <div id="toolbar">
+ *      <a data-wysihtml5-command="createLink">insert link</a>
+ *      <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1">insert h1</a>
+ *    </div>
+ *
+ *    <script>
+ *      var toolbar = new wysihtml5.toolbar.Toolbar(editor, document.getElementById("toolbar"));
+ *    </script>
+ */
+(function(wysihtml5) {
+  var CLASS_NAME_COMMAND_DISABLED   = "wysihtml5-command-disabled",
+      CLASS_NAME_COMMANDS_DISABLED  = "wysihtml5-commands-disabled",
+      CLASS_NAME_COMMAND_ACTIVE     = "wysihtml5-command-active",
+      dom                           = wysihtml5.dom;
+  
+  wysihtml5.toolbar.Toolbar = Base.extend(
+    /** @scope wysihtml5.toolbar.Toolbar.prototype */ {
+    constructor: function(editor, container) {
+      this.editor     = editor;
+      this.container  = typeof(container) === "string" ? document.getElementById(container) : container;
+      this.composer   = editor.composer;
+
+      this._getLinks("command");
+      this._getLinks("action");
+
+      this._observe();
+      this.show();
+      
+      var speechInputLinks  = this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"),
+          length            = speechInputLinks.length,
+          i                 = 0;
+      for (; i<length; i++) {
+        new wysihtml5.toolbar.Speech(this, speechInputLinks[i]);
+      }
+    },
+
+    _getLinks: function(type) {
+      var links   = this[type + "Links"] = wysihtml5.lang.array(this.container.querySelectorAll("a[data-wysihtml5-" + type + "]")).get(),
+          length  = links.length,
+          i       = 0,
+          mapping = this[type + "Mapping"] = {},
+          link,
+          name,
+          value,
+          dialog;
+      for (; i<length; i++) {
+        link    = links[i];
+        name    = link.getAttribute("data-wysihtml5-" + type);
+        value   = link.getAttribute("data-wysihtml5-" + type + "-value");
+        dialog  = this._getDialog(link, name);
+        
+        mapping[name + ":" + value] = {
+          link:   link,
+          name:   name,
+          value:  value,
+          dialog: dialog,
+          state:  false
+        };
+      }
+    },
+
+    _getDialog: function(link, command) {
+      var that          = this,
+          dialogElement = this.container.querySelector("[data-wysihtml5-dialog='" + command + "']"),
+          dialog,
+          caretBookmark;
+      
+      if (dialogElement) {
+        dialog = new wysihtml5.toolbar.Dialog(link, dialogElement);
+
+        dialog.observe("show", function() {
+          caretBookmark = that.composer.selection.getBookmark();
+
+          that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
+        });
+
+        dialog.observe("save", function(attributes) {
+          if (caretBookmark) {
+            that.composer.selection.setBookmark(caretBookmark);
+          }
+          that._execCommand(command, attributes);
+          
+          that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
+        });
+
+        dialog.observe("cancel", function() {
+          that.editor.focus(false);
+          that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
+        });
+      }
+      return dialog;
+    },
+
+    /**
+     * @example
+     *    var toolbar = new wysihtml5.Toolbar();
+     *    // Insert a <blockquote> element or wrap current selection in <blockquote>
+     *    toolbar.execCommand("formatBlock", "blockquote");
+     */
+    execCommand: function(command, commandValue) {
+      if (this.commandsDisabled) {
+        return;
+      }
+
+      var commandObj = this.commandMapping[command + ":" + commandValue];
+
+      // Show dialog when available
+      if (commandObj && commandObj.dialog && !commandObj.state) {
+        commandObj.dialog.show();
+      } else {
+        this._execCommand(command, commandValue);
+      }
+    },
+
+    _execCommand: function(command, commandValue) {
+      // Make sure that composer is focussed (false => don't move caret to the end)
+      this.editor.focus(false);
+
+      this.composer.commands.exec(command, commandValue);
+      this._updateLinkStates();
+    },
+
+    execAction: function(action) {
+      var editor = this.editor;
+      switch(action) {
+        case "change_view":
+          if (editor.currentView === editor.textarea) {
+            editor.fire("change_view", "composer");
+          } else {
+            editor.fire("change_view", "textarea");
+          }
+          break;
+      }
+    },
+
+    _observe: function() {
+      var that      = this,
+          editor    = this.editor,
+          container = this.container,
+          links     = this.commandLinks.concat(this.actionLinks),
+          length    = links.length,
+          i         = 0;
+      
+      for (; i<length; i++) {
+        // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied
+        // (you know, a:link { ... } doesn't match anchors with missing href attribute)
+        dom.setAttributes({
+          href:         "javascript:;",
+          unselectable: "on"
+        }).on(links[i]);
+      }
+
+      // Needed for opera
+      dom.delegate(container, "[data-wysihtml5-command]", "mousedown", function(event) { event.preventDefault(); });
+      
+      dom.delegate(container, "[data-wysihtml5-command]", "click", function(event) {
+        var link          = this,
+            command       = link.getAttribute("data-wysihtml5-command"),
+            commandValue  = link.getAttribute("data-wysihtml5-command-value");
+        that.execCommand(command, commandValue);
+        event.preventDefault();
+      });
+
+      dom.delegate(container, "[data-wysihtml5-action]", "click", function(event) {
+        var action = this.getAttribute("data-wysihtml5-action");
+        that.execAction(action);
+        event.preventDefault();
+      });
+
+      editor.observe("focus:composer", function() {
+        that.bookmark = null;
+        clearInterval(that.interval);
+        that.interval = setInterval(function() { that._updateLinkStates(); }, 500);
+      });
+
+      editor.observe("blur:composer", function() {
+        clearInterval(that.interval);
+      });
+
+      editor.observe("destroy:composer", function() {
+        clearInterval(that.interval);
+      });
+
+      editor.observe("change_view", function(currentView) {
+        // Set timeout needed in order to let the blur event fire first
+        setTimeout(function() {
+          that.commandsDisabled = (currentView !== "composer");
+          that._updateLinkStates();
+          if (that.commandsDisabled) {
+            dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED);
+          } else {
+            dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED);
+          }
+        }, 0);
+      });
+    },
+
+    _updateLinkStates: function() {
+      var element           = this.composer.element,
+          commandMapping    = this.commandMapping,
+          i,
+          state,
+          command;
+      // every millisecond counts... this is executed quite often
+      for (i in commandMapping) {
+        command = commandMapping[i];
+        if (this.commandsDisabled) {
+          state = false;
+          dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
+          if (command.dialog) {
+            command.dialog.hide();
+          }
+        } else {
+          state = this.composer.commands.state(command.name, command.value);
+          if (wysihtml5.lang.object(state).isArray()) {
+            // Grab first and only object/element in state array, otherwise convert state into boolean
+            // to avoid showing a dialog for multiple selected elements which may have different attributes
+            // eg. when two links with different href are selected, the state will be an array consisting of both link elements
+            // but the dialog interface can only update one
+            state = state.length === 1 ? state[0] : true;
+          }
+          dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED);
+        }
+
+        if (command.state === state) {
+          continue;
+        }
+
+        command.state = state;
+        if (state) {
+          dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
+          if (command.dialog) {
+            if (typeof(state) === "object") {
+              command.dialog.show(state);
+            } else {
+              command.dialog.hide();
+            }
+          }
+        } else {
+          dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
+          if (command.dialog) {
+            command.dialog.hide();
+          }
+        }
+      }
+    },
+
+    show: function() {
+      this.container.style.display = "";
+    },
+
+    hide: function() {
+      this.container.style.display = "none";
+    }
+  });
+  
+})(wysihtml5);
+/**
+ * WYSIHTML5 Editor
+ *
+ * @param {Element} textareaElement Reference to the textarea which should be turned into a rich text interface
+ * @param {Object} [config] See defaultConfig object below for explanation of each individual config option
+ *
+ * @events
+ *    load
+ *    beforeload (for internal use only)
+ *    focus
+ *    focus:composer
+ *    focus:textarea
+ *    blur
+ *    blur:composer
+ *    blur:textarea
+ *    change
+ *    change:composer
+ *    change:textarea
+ *    paste
+ *    paste:composer
+ *    paste:textarea
+ *    newword:composer
+ *    destroy:composer
+ *    undo:composer
+ *    redo:composer
+ *    beforecommand:composer
+ *    aftercommand:composer
+ *    change_view
+ */
+(function(wysihtml5) {
+  var undef;
+  
+  var defaultConfig = {
+    // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body 
+    name:                 undef,
+    // Whether the editor should look like the textarea (by adopting styles)
+    style:                true,
+    // Id of the toolbar element, pass falsey value if you don't want any toolbar logic
+    toolbar:              undef,
+    // Whether urls, entered by the user should automatically become clickable-links
+    autoLink:             true,
+    // Object which includes parser rules to apply when html gets inserted via copy & paste
+    // See parser_rules/*.js for examples
+    parserRules:          { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} },
+    // Parser method to use when the user inserts content via copy & paste
+    parser:               wysihtml5.dom.parse,
+    // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
+    composerClassName:    "wysihtml5-editor",
+    // Class name to add to the body when the wysihtml5 editor is supported
+    bodyClassName:        "wysihtml5-supported",
+    // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
+    stylesheets:          [],
+    // Placeholder text to use, defaults to the placeholder attribute on the textarea element
+    placeholderText:      undef,
+    // Whether the composer should allow the user to manually resize images, tables etc.
+    allowObjectResizing:  true,
+    // Whether the rich text editor should be rendered on touch devices (wysihtml5 >= 0.3.0 comes with basic support for iOS 5)
+    supportTouchDevices:  true
+  };
+  
+  wysihtml5.Editor = wysihtml5.lang.Dispatcher.extend(
+    /** @scope wysihtml5.Editor.prototype */ {
+    constructor: function(textareaElement, config) {
+      this.textareaElement  = typeof(textareaElement) === "string" ? document.getElementById(textareaElement) : textareaElement;
+      this.config           = wysihtml5.lang.object({}).merge(defaultConfig).merge(config).get();
+      this.textarea         = new wysihtml5.views.Textarea(this, this.textareaElement, this.config);
+      this.currentView      = this.textarea;
+      this._isCompatible    = wysihtml5.browser.supported();
+      
+      // Sort out unsupported/unwanted browsers here
+      if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml5.browser.isTouchDevice())) {
+        var that = this;
+        setTimeout(function() { that.fire("beforeload").fire("load"); }, 0);
+        return;
+      }
+      
+      // Add class name to body, to indicate that the editor is supported
+      wysihtml5.dom.addClass(document.body, this.config.bodyClassName);
+      
+      this.composer = new wysihtml5.views.Composer(this, this.textareaElement, this.config);
+      this.currentView = this.composer;
+      
+      if (typeof(this.config.parser) === "function") {
+        this._initParser();
+      }
+      
+      this.observe("beforeload", function() {
+        this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer);
+        if (this.config.toolbar) {
+          this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar);
+        }
+      });
+      
+      try {
+        console.log("Heya! This page is using wysihtml5 for rich text editing. Check out https://github.com/xing/wysihtml5");
+      } catch(e) {}
+    },
+    
+    isCompatible: function() {
+      return this._isCompatible;
+    },
+
+    clear: function() {
+      this.currentView.clear();
+      return this;
+    },
+
+    getValue: function(parse) {
+      return this.currentView.getValue(parse);
+    },
+
+    setValue: function(html, parse) {
+      if (!html) {
+        return this.clear();
+      }
+      this.currentView.setValue(html, parse);
+      return this;
+    },
+
+    focus: function(setToEnd) {
+      this.currentView.focus(setToEnd);
+      return this;
+    },
+
+    /**
+     * Deactivate editor (make it readonly)
+     */
+    disable: function() {
+      this.currentView.disable();
+      return this;
+    },
+    
+    /**
+     * Activate editor
+     */
+    enable: function() {
+      this.currentView.enable();
+      return this;
+    },
+    
+    isEmpty: function() {
+      return this.currentView.isEmpty();
+    },
+    
+    hasPlaceholderSet: function() {
+      return this.currentView.hasPlaceholderSet();
+    },
+    
+    parse: function(htmlOrElement) {
+      var returnValue = this.config.parser(htmlOrElement, this.config.parserRules, this.composer.sandbox.getDocument(), true);
+      if (typeof(htmlOrElement) === "object") {
+        wysihtml5.quirks.redraw(htmlOrElement);
+      }
+      return returnValue;
+    },
+    
+    /**
+     * Prepare html parser logic
+     *  - Observes for paste and drop
+     */
+    _initParser: function() {
+      this.observe("paste:composer", function() {
+        var keepScrollPosition  = true,
+            that                = this;
+        that.composer.selection.executeAndRestore(function() {
+          wysihtml5.quirks.cleanPastedHTML(that.composer.element);
+          that.parse(that.composer.element);
+        }, keepScrollPosition);
+      });
+      
+      this.observe("paste:textarea", function() {
+        var value   = this.textarea.getValue(),
+            newValue;
+        newValue = this.parse(value);
+        this.textarea.setValue(newValue);
+      });
+    }
+  });
+})(wysihtml5);
diff --git a/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0_rc2.min.js b/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0_rc2.min.js
new file mode 100644
index 0000000..cf752cf
--- /dev/null
+++ b/src/inputs/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0_rc2.min.js
@@ -0,0 +1,260 @@
+/*
+ wysihtml5 v0.3.0_rc2
+ https://github.com/xing/wysihtml5
+
+ Author: Christopher Blum (https://github.com/tiff)
+
+ Copyright (C) 2012 XING AG
+ Licensed under the MIT license (MIT)
+
+ Rangy, a cross-browser JavaScript range and selection library
+ http://code.google.com/p/rangy/
+
+ Copyright 2011, Tim Down
+ Licensed under the MIT license.
+ Version: 1.2.2
+ Build date: 13 November 2011
+*/
+var wysihtml5={version:"0.3.0_rc2",commands:{},dom:{},quirks:{},toolbar:{},lang:{},selection:{},views:{},INVISIBLE_SPACE:"\ufeff",EMPTY_FUNCTION:function(){},ELEMENT_NODE:1,TEXT_NODE:3,BACKSPACE_KEY:8,ENTER_KEY:13,ESCAPE_KEY:27,SPACE_KEY:32,DELETE_KEY:46};
+window.rangy=function(){function b(a,b){var c=typeof a[b];return c==k||!!(c==g&&a[b])||"unknown"==c}function c(a,b){return!!(typeof a[b]==g&&a[b])}function a(a,b){return typeof a[b]!=j}function d(a){return function(b,c){for(var d=c.length;d--;)if(!a(b,c[d]))return!1;return!0}}function e(a){return a&&m(a,r)&&x(a,p)}function f(a){window.alert("Rangy not supported in your browser. Reason: "+a);o.initialized=!0;o.supported=!1}function h(){if(!o.initialized){var a,d=!1,g=!1;b(document,"createRange")&&
+(a=document.createRange(),m(a,n)&&x(a,q)&&(d=!0),a.detach());if((a=c(document,"body")?document.body:document.getElementsByTagName("body")[0])&&b(a,"createTextRange"))a=a.createTextRange(),e(a)&&(g=!0);!d&&!g&&f("Neither Range nor TextRange are implemented");o.initialized=!0;o.features={implementsDomRange:d,implementsTextRange:g};d=w.concat(z);g=0;for(a=d.length;g<a;++g)try{d[g](o)}catch(j){c(window,"console")&&b(window.console,"log")&&window.console.log("Init listener threw an exception. Continuing.",
+j)}}}function i(a){this.name=a;this.supported=this.initialized=!1}var g="object",k="function",j="undefined",q="startContainer,startOffset,endContainer,endOffset,collapsed,commonAncestorContainer,START_TO_START,START_TO_END,END_TO_START,END_TO_END".split(","),n="setStart,setStartBefore,setStartAfter,setEnd,setEndBefore,setEndAfter,collapse,selectNode,selectNodeContents,compareBoundaryPoints,deleteContents,extractContents,cloneContents,insertNode,surroundContents,cloneRange,toString,detach".split(","),
+p="boundingHeight,boundingLeft,boundingTop,boundingWidth,htmlText,text".split(","),r="collapse,compareEndPoints,duplicate,getBookmark,moveToBookmark,moveToElementText,parentElement,pasteHTML,select,setEndPoint,getBoundingClientRect".split(","),m=d(b),s=d(c),x=d(a),o={version:"1.2.2",initialized:!1,supported:!0,util:{isHostMethod:b,isHostObject:c,isHostProperty:a,areHostMethods:m,areHostObjects:s,areHostProperties:x,isTextRange:e},features:{},modules:{},config:{alertOnWarn:!1,preferTextRange:!1}};
+o.fail=f;o.warn=function(a){a="Rangy warning: "+a;o.config.alertOnWarn?window.alert(a):typeof window.console!=j&&typeof window.console.log!=j&&window.console.log(a)};({}).hasOwnProperty?o.util.extend=function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c])}:f("hasOwnProperty not supported");var z=[],w=[];o.init=h;o.addInitListener=function(a){o.initialized?a(o):z.push(a)};var y=[];o.addCreateMissingNativeApiListener=function(a){y.push(a)};o.createMissingNativeApi=function(a){a=a||window;h();
+for(var b=0,c=y.length;b<c;++b)y[b](a)};i.prototype.fail=function(a){this.initialized=!0;this.supported=!1;throw Error("Module '"+this.name+"' failed to load: "+a);};i.prototype.warn=function(a){o.warn("Module "+this.name+": "+a)};i.prototype.createError=function(a){return Error("Error in Rangy "+this.name+" module: "+a)};o.createModule=function(a,b){var c=new i(a);o.modules[a]=c;w.push(function(a){b(a,c);c.initialized=!0;c.supported=!0})};o.requireModules=function(a){for(var b=0,c=a.length,d,g;b<
+c;++b){g=a[b];d=o.modules[g];if(!d||!(d instanceof i))throw Error("Module '"+g+"' not found");if(!d.supported)throw Error("Module '"+g+"' not supported");}};var A=!1,s=function(){A||(A=!0,o.initialized||h())};if(typeof window==j)f("No window found");else if(typeof document==j)f("No document found");else return b(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",s,!1),b(window,"addEventListener")?window.addEventListener("load",s,!1):b(window,"attachEvent")?window.attachEvent("onload",
+s):f("Window does not have required addEventListener or attachEvent method"),o}();
+rangy.createModule("DomUtil",function(b,c){function a(a){for(var b=0;a=a.previousSibling;)b++;return b}function d(a,b){var c=[],d;for(d=a;d;d=d.parentNode)c.push(d);for(d=b;d;d=d.parentNode)if(m(c,d))return d;return null}function e(a,b,c){for(c=c?a:a.parentNode;c;){a=c.parentNode;if(a===b)return c;c=a}return null}function f(a){a=a.nodeType;return 3==a||4==a||8==a}function h(a,b){var c=b.nextSibling,d=b.parentNode;c?d.insertBefore(a,c):d.appendChild(a);return a}function i(a){if(9==a.nodeType)return a;
+if(typeof a.ownerDocument!=n)return a.ownerDocument;if(typeof a.document!=n)return a.document;if(a.parentNode)return i(a.parentNode);throw Error("getDocument: no document found for node");}function g(a){return!a?"[No node]":f(a)?'"'+a.data+'"':1==a.nodeType?"<"+a.nodeName+(a.id?' id="'+a.id+'"':"")+">["+a.childNodes.length+"]":a.nodeName}function k(a){this._next=this.root=a}function j(a,b){this.node=a;this.offset=b}function q(a){this.code=this[a];this.codeName=a;this.message="DOMException: "+this.codeName}
+var n="undefined",p=b.util;p.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||c.fail("document missing a Node creation method");p.isHostMethod(document,"getElementsByTagName")||c.fail("document missing getElementsByTagName method");var r=document.createElement("div");p.areHostMethods(r,["insertBefore","appendChild","cloneNode"])||c.fail("Incomplete Element implementation");p.isHostProperty(r,"innerHTML")||c.fail("Element is missing innerHTML property");r=document.createTextNode("test");
+p.areHostMethods(r,["splitText","deleteData","insertData","appendData","cloneNode"])||c.fail("Incomplete Text Node implementation");var m=function(a,b){for(var c=a.length;c--;)if(a[c]===b)return!0;return!1};k.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var a=this._current=this._next,b;if(this._current){b=a.firstChild;if(!b)for(b=null;a!==this.root&&!(b=a.nextSibling);)a=a.parentNode;this._next=b}return this._current},detach:function(){this._current=this._next=this.root=
+null}};j.prototype={equals:function(a){return this.node===a.node&this.offset==a.offset},inspect:function(){return"[DomPosition("+g(this.node)+":"+this.offset+")]"}};q.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11};q.prototype.toString=function(){return this.message};b.dom={arrayContains:m,isHtmlNamespace:function(a){var b;return typeof a.namespaceURI==n||null===(b=a.namespaceURI)||"http://www.w3.org/1999/xhtml"==
+b},parentElement:function(a){a=a.parentNode;return 1==a.nodeType?a:null},getNodeIndex:a,getNodeLength:function(a){var b;return f(a)?a.length:(b=a.childNodes)?b.length:0},getCommonAncestor:d,isAncestorOf:function(a,b,c){for(b=c?b:b.parentNode;b;){if(b===a)return!0;b=b.parentNode}return!1},getClosestAncestorIn:e,isCharacterDataNode:f,insertAfter:h,splitDataNode:function(a,b){var c=a.cloneNode(!1);c.deleteData(0,b);a.deleteData(b,a.length-b);h(c,a);return c},getDocument:i,getWindow:function(a){a=i(a);
+if(typeof a.defaultView!=n)return a.defaultView;if(typeof a.parentWindow!=n)return a.parentWindow;throw Error("Cannot get a window object for node");},getIframeWindow:function(a){if(typeof a.contentWindow!=n)return a.contentWindow;if(typeof a.contentDocument!=n)return a.contentDocument.defaultView;throw Error("getIframeWindow: No Window object found for iframe element");},getIframeDocument:function(a){if(typeof a.contentDocument!=n)return a.contentDocument;if(typeof a.contentWindow!=n)return a.contentWindow.document;
+throw Error("getIframeWindow: No Document object found for iframe element");},getBody:function(a){return p.isHostObject(a,"body")?a.body:a.getElementsByTagName("body")[0]},getRootContainer:function(a){for(var b;b=a.parentNode;)a=b;return a},comparePoints:function(b,c,g,j){var k;if(b==g)return c===j?0:c<j?-1:1;if(k=e(g,b,!0))return c<=a(k)?-1:1;if(k=e(b,g,!0))return a(k)<j?-1:1;c=d(b,g);b=b===c?c:e(b,c,!0);g=g===c?c:e(g,c,!0);if(b===g)throw Error("comparePoints got to case 4 and childA and childB are the same!");
+for(c=c.firstChild;c;){if(c===b)return-1;if(c===g)return 1;c=c.nextSibling}throw Error("Should not be here!");},inspectNode:g,fragmentFromNodeChildren:function(a){for(var b=i(a).createDocumentFragment(),c;c=a.firstChild;)b.appendChild(c);return b},createIterator:function(a){return new k(a)},DomPosition:j};b.DOMException=q});
+rangy.createModule("DomRange",function(b){function c(a,b){return 3!=a.nodeType&&(l.isAncestorOf(a,b.startContainer,!0)||l.isAncestorOf(a,b.endContainer,!0))}function a(a){return l.getDocument(a.startContainer)}function d(a,b,c){if(b=a._listeners[b])for(var d=0,g=b.length;d<g;++d)b[d].call(a,{target:a,args:c})}function e(a){return new u(a.parentNode,l.getNodeIndex(a))}function f(a){return new u(a.parentNode,l.getNodeIndex(a)+1)}function h(a,b,c){var d=11==a.nodeType?a.firstChild:a;l.isCharacterDataNode(b)?
+c==b.length?l.insertAfter(a,b):b.parentNode.insertBefore(a,0==c?b:l.splitDataNode(b,c)):c>=b.childNodes.length?b.appendChild(a):b.insertBefore(a,b.childNodes[c]);return d}function i(b){for(var c,d,g=a(b.range).createDocumentFragment();d=b.next();){c=b.isPartiallySelectedSubtree();d=d.cloneNode(!c);c&&(c=b.getSubtreeIterator(),d.appendChild(i(c)),c.detach(!0));if(10==d.nodeType)throw new B("HIERARCHY_REQUEST_ERR");g.appendChild(d)}return g}function g(a,b,c){for(var d,e,c=c||{stop:!1};d=a.next();)if(a.isPartiallySelectedSubtree())if(!1===
+b(d)){c.stop=!0;break}else{if(d=a.getSubtreeIterator(),g(d,b,c),d.detach(!0),c.stop)break}else for(d=l.createIterator(d);e=d.next();)if(!1===b(e)){c.stop=!0;return}}function k(a){for(var b;a.next();)a.isPartiallySelectedSubtree()?(b=a.getSubtreeIterator(),k(b),b.detach(!0)):a.remove()}function j(b){for(var c,d=a(b.range).createDocumentFragment(),g;c=b.next();){b.isPartiallySelectedSubtree()?(c=c.cloneNode(!1),g=b.getSubtreeIterator(),c.appendChild(j(g)),g.detach(!0)):b.remove();if(10==c.nodeType)throw new B("HIERARCHY_REQUEST_ERR");
+d.appendChild(c)}return d}function q(a,b,c){var d=!(!b||!b.length),e,j=!!c;d&&(e=RegExp("^("+b.join("|")+")$"));var k=[];g(new p(a,!1),function(a){(!d||e.test(a.nodeType))&&(!j||c(a))&&k.push(a)});return k}function n(a){return"["+("undefined"==typeof a.getName?"Range":a.getName())+"("+l.inspectNode(a.startContainer)+":"+a.startOffset+", "+l.inspectNode(a.endContainer)+":"+a.endOffset+")]"}function p(a,b){this.range=a;this.clonePartiallySelectedTextNodes=b;if(!a.collapsed){this.sc=a.startContainer;
+this.so=a.startOffset;this.ec=a.endContainer;this.eo=a.endOffset;var c=a.commonAncestorContainer;this.sc===this.ec&&l.isCharacterDataNode(this.sc)?(this.isSingleCharacterDataNode=!0,this._first=this._last=this._next=this.sc):(this._first=this._next=this.sc===c&&!l.isCharacterDataNode(this.sc)?this.sc.childNodes[this.so]:l.getClosestAncestorIn(this.sc,c,!0),this._last=this.ec===c&&!l.isCharacterDataNode(this.ec)?this.ec.childNodes[this.eo-1]:l.getClosestAncestorIn(this.ec,c,!0))}}function r(a){this.code=
+this[a];this.codeName=a;this.message="RangeException: "+this.codeName}function m(a,b,c){this.nodes=q(a,b,c);this._next=this.nodes[0];this._position=0}function s(a){return function(b,c){for(var d,g=c?b:b.parentNode;g;){d=g.nodeType;if(l.arrayContains(a,d))return g;g=g.parentNode}return null}}function x(a,b){if($(a,b))throw new r("INVALID_NODE_TYPE_ERR");}function o(a){if(!a.startContainer)throw new B("INVALID_STATE_ERR");}function z(a,b){if(!l.arrayContains(b,a.nodeType))throw new r("INVALID_NODE_TYPE_ERR");
+}function w(a,b){if(0>b||b>(l.isCharacterDataNode(a)?a.length:a.childNodes.length))throw new B("INDEX_SIZE_ERR");}function y(a,b){if(O(a,!0)!==O(b,!0))throw new B("WRONG_DOCUMENT_ERR");}function A(a){if(aa(a,!0))throw new B("NO_MODIFICATION_ALLOWED_ERR");}function t(a,b){if(!a)throw new B(b);}function v(a){o(a);if(!l.arrayContains(G,a.startContainer.nodeType)&&!O(a.startContainer,!0)||!l.arrayContains(G,a.endContainer.nodeType)&&!O(a.endContainer,!0)||!(a.startOffset<=(l.isCharacterDataNode(a.startContainer)?
+a.startContainer.length:a.startContainer.childNodes.length))||!(a.endOffset<=(l.isCharacterDataNode(a.endContainer)?a.endContainer.length:a.endContainer.childNodes.length)))throw Error("Range error: Range is no longer valid after DOM mutation ("+a.inspect()+")");}function D(){}function K(a){a.START_TO_START=Q;a.START_TO_END=U;a.END_TO_END=ba;a.END_TO_START=V;a.NODE_BEFORE=W;a.NODE_AFTER=X;a.NODE_BEFORE_AND_AFTER=Y;a.NODE_INSIDE=R}function F(a){K(a);K(a.prototype)}function E(a,b){return function(){v(this);
+var c=this.startContainer,d=this.startOffset,e=this.commonAncestorContainer,j=new p(this,!0);c!==e&&(c=l.getClosestAncestorIn(c,e,!0),d=f(c),c=d.node,d=d.offset);g(j,A);j.reset();e=a(j);j.detach();b(this,c,d,c,d);return e}}function I(a,d,g){function h(a,b){return function(c){o(this);z(c,L);z(M(c),G);c=(a?e:f)(c);(b?i:n)(this,c.node,c.offset)}}function i(a,b,c){var g=a.endContainer,e=a.endOffset;if(b!==a.startContainer||c!==a.startOffset){if(M(b)!=M(g)||1==l.comparePoints(b,c,g,e))g=b,e=c;d(a,b,c,
+g,e)}}function n(a,b,c){var g=a.startContainer,e=a.startOffset;if(b!==a.endContainer||c!==a.endOffset){if(M(b)!=M(g)||-1==l.comparePoints(b,c,g,e))g=b,e=c;d(a,g,e,b,c)}}a.prototype=new D;b.util.extend(a.prototype,{setStart:function(a,b){o(this);x(a,!0);w(a,b);i(this,a,b)},setEnd:function(a,b){o(this);x(a,!0);w(a,b);n(this,a,b)},setStartBefore:h(!0,!0),setStartAfter:h(!1,!0),setEndBefore:h(!0,!1),setEndAfter:h(!1,!1),collapse:function(a){v(this);a?d(this,this.startContainer,this.startOffset,this.startContainer,
+this.startOffset):d(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(a){o(this);x(a,!0);d(this,a,0,a,l.getNodeLength(a))},selectNode:function(a){o(this);x(a,!1);z(a,L);var b=e(a),a=f(a);d(this,b.node,b.offset,a.node,a.offset)},extractContents:E(j,d),deleteContents:E(k,d),canSurroundContents:function(){v(this);A(this.startContainer);A(this.endContainer);var a=new p(this,!0),b=a._first&&c(a._first,this)||a._last&&c(a._last,this);a.detach();return!b},
+detach:function(){g(this)},splitBoundaries:function(){v(this);var a=this.startContainer,b=this.startOffset,c=this.endContainer,g=this.endOffset,e=a===c;l.isCharacterDataNode(c)&&0<g&&g<c.length&&l.splitDataNode(c,g);l.isCharacterDataNode(a)&&0<b&&b<a.length&&(a=l.splitDataNode(a,b),e?(g-=b,c=a):c==a.parentNode&&g>=l.getNodeIndex(a)&&g++,b=0);d(this,a,b,c,g)},normalizeBoundaries:function(){v(this);var a=this.startContainer,b=this.startOffset,c=this.endContainer,g=this.endOffset,e=function(a){var b=
+a.nextSibling;b&&b.nodeType==a.nodeType&&(c=a,g=a.length,a.appendData(b.data),b.parentNode.removeChild(b))},j=function(d){var e=d.previousSibling;if(e&&e.nodeType==d.nodeType){a=d;var j=d.length;b=e.length;d.insertData(0,e.data);e.parentNode.removeChild(e);a==c?(g+=b,c=a):c==d.parentNode&&(e=l.getNodeIndex(d),g==e?(c=d,g=j):g>e&&g--)}},k=!0;l.isCharacterDataNode(c)?c.length==g&&e(c):(0<g&&(k=c.childNodes[g-1])&&l.isCharacterDataNode(k)&&e(k),k=!this.collapsed);k?l.isCharacterDataNode(a)?0==b&&j(a):
+b<a.childNodes.length&&(e=a.childNodes[b])&&l.isCharacterDataNode(e)&&j(e):(a=c,b=g);d(this,a,b,c,g)},collapseToPoint:function(a,b){o(this);x(a,!0);w(a,b);(a!==this.startContainer||b!==this.startOffset||a!==this.endContainer||b!==this.endOffset)&&d(this,a,b,a,b)}});F(a)}function N(a){a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset;a.commonAncestorContainer=a.collapsed?a.startContainer:l.getCommonAncestor(a.startContainer,a.endContainer)}function J(a,b,c,g,e){var j=a.startContainer!==
+b||a.startOffset!==c,k=a.endContainer!==g||a.endOffset!==e;a.startContainer=b;a.startOffset=c;a.endContainer=g;a.endOffset=e;N(a);d(a,"boundarychange",{startMoved:j,endMoved:k})}function C(a){this.startContainer=a;this.startOffset=0;this.endContainer=a;this.endOffset=0;this._listeners={boundarychange:[],detach:[]};N(this)}b.requireModules(["DomUtil"]);var l=b.dom,u=l.DomPosition,B=b.DOMException;p.prototype={_current:null,_next:null,_first:null,_last:null,isSingleCharacterDataNode:!1,reset:function(){this._current=
+null;this._next=this._first},hasNext:function(){return!!this._next},next:function(){var a=this._current=this._next;a&&(this._next=a!==this._last?a.nextSibling:null,l.isCharacterDataNode(a)&&this.clonePartiallySelectedTextNodes&&(a===this.ec&&(a=a.cloneNode(!0)).deleteData(this.eo,a.length-this.eo),this._current===this.sc&&(a=a.cloneNode(!0)).deleteData(0,this.so)));return a},remove:function(){var a=this._current,b,c;l.isCharacterDataNode(a)&&(a===this.sc||a===this.ec)?(b=a===this.sc?this.so:0,c=a===
+this.ec?this.eo:a.length,b!=c&&a.deleteData(b,c-b)):a.parentNode&&a.parentNode.removeChild(a)},isPartiallySelectedSubtree:function(){return c(this._current,this.range)},getSubtreeIterator:function(){var b;if(this.isSingleCharacterDataNode)b=this.range.cloneRange(),b.collapse();else{b=new C(a(this.range));var c=this._current,d=c,g=0,e=c,j=l.getNodeLength(c);l.isAncestorOf(c,this.sc,!0)&&(d=this.sc,g=this.so);l.isAncestorOf(c,this.ec,!0)&&(e=this.ec,j=this.eo);J(b,d,g,e,j)}return new p(b,this.clonePartiallySelectedTextNodes)},
+detach:function(a){a&&this.range.detach();this.range=this._current=this._next=this._first=this._last=this.sc=this.so=this.ec=this.eo=null}};r.prototype={BAD_BOUNDARYPOINTS_ERR:1,INVALID_NODE_TYPE_ERR:2};r.prototype.toString=function(){return this.message};m.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){this._current=this._next;this._next=this.nodes[++this._position];return this._current},detach:function(){this._current=this._next=this.nodes=null}};var L=[1,3,4,5,
+7,8,10],G=[2,9,11],P=[1,3,4,5,7,8,10,11],H=[1,3,4,5,7,8],M=l.getRootContainer,O=s([9,11]),aa=s([5,6,10,12]),$=s([6,10,12]),Z=document.createElement("style"),S=!1;try{Z.innerHTML="<b>x</b>",S=3==Z.firstChild.nodeType}catch(ca){}b.features.htmlParsingConforms=S;var T="startContainer,startOffset,endContainer,endOffset,collapsed,commonAncestorContainer".split(","),Q=0,U=1,ba=2,V=3,W=0,X=1,Y=2,R=3;D.prototype={attachListener:function(a,b){this._listeners[a].push(b)},compareBoundaryPoints:function(a,b){v(this);
+y(this.startContainer,b.startContainer);var c=a==V||a==Q?"start":"end",d=a==U||a==Q?"start":"end";return l.comparePoints(this[c+"Container"],this[c+"Offset"],b[d+"Container"],b[d+"Offset"])},insertNode:function(a){v(this);z(a,P);A(this.startContainer);if(l.isAncestorOf(a,this.startContainer,!0))throw new B("HIERARCHY_REQUEST_ERR");this.setStartBefore(h(a,this.startContainer,this.startOffset))},cloneContents:function(){v(this);var b,c;if(this.collapsed)return a(this).createDocumentFragment();if(this.startContainer===
+this.endContainer&&l.isCharacterDataNode(this.startContainer))return b=this.startContainer.cloneNode(!0),b.data=b.data.slice(this.startOffset,this.endOffset),c=a(this).createDocumentFragment(),c.appendChild(b),c;c=new p(this,!0);b=i(c);c.detach();return b},canSurroundContents:function(){v(this);A(this.startContainer);A(this.endContainer);var a=new p(this,!0),b=a._first&&c(a._first,this)||a._last&&c(a._last,this);a.detach();return!b},surroundContents:function(a){z(a,H);if(!this.canSurroundContents())throw new r("BAD_BOUNDARYPOINTS_ERR");
+var b=this.extractContents();if(a.hasChildNodes())for(;a.lastChild;)a.removeChild(a.lastChild);h(a,this.startContainer,this.startOffset);a.appendChild(b);this.selectNode(a)},cloneRange:function(){v(this);for(var b=new C(a(this)),c=T.length,d;c--;)d=T[c],b[d]=this[d];return b},toString:function(){v(this);var a=this.startContainer;if(a===this.endContainer&&l.isCharacterDataNode(a))return 3==a.nodeType||4==a.nodeType?a.data.slice(this.startOffset,this.endOffset):"";var b=[],a=new p(this,!0);g(a,function(a){(3==
+a.nodeType||4==a.nodeType)&&b.push(a.data)});a.detach();return b.join("")},compareNode:function(a){v(this);var b=a.parentNode,c=l.getNodeIndex(a);if(!b)throw new B("NOT_FOUND_ERR");a=this.comparePoint(b,c);b=this.comparePoint(b,c+1);return 0>a?0<b?Y:W:0<b?X:R},comparePoint:function(a,b){v(this);t(a,"HIERARCHY_REQUEST_ERR");y(a,this.startContainer);return 0>l.comparePoints(a,b,this.startContainer,this.startOffset)?-1:0<l.comparePoints(a,b,this.endContainer,this.endOffset)?1:0},createContextualFragment:S?
+function(a){var b=this.startContainer,c=l.getDocument(b);if(!b)throw new B("INVALID_STATE_ERR");var d=null;1==b.nodeType?d=b:l.isCharacterDataNode(b)&&(d=l.parentElement(b));d=null===d||"HTML"==d.nodeName&&l.isHtmlNamespace(l.getDocument(d).documentElement)&&l.isHtmlNamespace(d)?c.createElement("body"):d.cloneNode(!1);d.innerHTML=a;return l.fragmentFromNodeChildren(d)}:function(b){o(this);var c=a(this).createElement("body");c.innerHTML=b;return l.fragmentFromNodeChildren(c)},toHtml:function(){v(this);
+var b=a(this).createElement("div");b.appendChild(this.cloneContents());return b.innerHTML},intersectsNode:function(b,c){v(this);t(b,"NOT_FOUND_ERR");if(l.getDocument(b)!==a(this))return!1;var d=b.parentNode,g=l.getNodeIndex(b);t(d,"NOT_FOUND_ERR");var e=l.comparePoints(d,g,this.endContainer,this.endOffset),d=l.comparePoints(d,g+1,this.startContainer,this.startOffset);return c?0>=e&&0<=d:0>e&&0<d},isPointInRange:function(a,b){v(this);t(a,"HIERARCHY_REQUEST_ERR");y(a,this.startContainer);return 0<=
+l.comparePoints(a,b,this.startContainer,this.startOffset)&&0>=l.comparePoints(a,b,this.endContainer,this.endOffset)},intersectsRange:function(b,c){v(this);if(a(b)!=a(this))throw new B("WRONG_DOCUMENT_ERR");var d=l.comparePoints(this.startContainer,this.startOffset,b.endContainer,b.endOffset),g=l.comparePoints(this.endContainer,this.endOffset,b.startContainer,b.startOffset);return c?0>=d&&0<=g:0>d&&0<g},intersection:function(a){if(this.intersectsRange(a)){var b=l.comparePoints(this.startContainer,
+this.startOffset,a.startContainer,a.startOffset),c=l.comparePoints(this.endContainer,this.endOffset,a.endContainer,a.endOffset),d=this.cloneRange();-1==b&&d.setStart(a.startContainer,a.startOffset);1==c&&d.setEnd(a.endContainer,a.endOffset);return d}return null},union:function(a){if(this.intersectsRange(a,!0)){var b=this.cloneRange();-1==l.comparePoints(a.startContainer,a.startOffset,this.startContainer,this.startOffset)&&b.setStart(a.startContainer,a.startOffset);1==l.comparePoints(a.endContainer,
+a.endOffset,this.endContainer,this.endOffset)&&b.setEnd(a.endContainer,a.endOffset);return b}throw new r("Ranges do not intersect");},containsNode:function(a,b){return b?this.intersectsNode(a,!1):this.compareNode(a)==R},containsNodeContents:function(a){return 0<=this.comparePoint(a,0)&&0>=this.comparePoint(a,l.getNodeLength(a))},containsRange:function(a){return this.intersection(a).equals(a)},containsNodeText:function(a){var b=this.cloneRange();b.selectNode(a);var c=b.getNodes([3]);return 0<c.length?
+(b.setStart(c[0],0),a=c.pop(),b.setEnd(a,a.length),a=this.containsRange(b),b.detach(),a):this.containsNodeContents(a)},createNodeIterator:function(a,b){v(this);return new m(this,a,b)},getNodes:function(a,b){v(this);return q(this,a,b)},getDocument:function(){return a(this)},collapseBefore:function(a){o(this);this.setEndBefore(a);this.collapse(!1)},collapseAfter:function(a){o(this);this.setStartAfter(a);this.collapse(!0)},getName:function(){return"DomRange"},equals:function(a){return C.rangesEqual(this,
+a)},inspect:function(){return n(this)}};I(C,J,function(a){o(a);a.startContainer=a.startOffset=a.endContainer=a.endOffset=null;a.collapsed=a.commonAncestorContainer=null;d(a,"detach",null);a._listeners=null});b.rangePrototype=D.prototype;C.rangeProperties=T;C.RangeIterator=p;C.copyComparisonConstants=F;C.createPrototypeRange=I;C.inspect=n;C.getRangeDocument=a;C.rangesEqual=function(a,b){return a.startContainer===b.startContainer&&a.startOffset===b.startOffset&&a.endContainer===b.endContainer&&a.endOffset===
+b.endOffset};b.DomRange=C;b.RangeException=r});
+rangy.createModule("WrappedRange",function(b){function c(a,b,c,d){var h=a.duplicate();h.collapse(c);var i=h.parentElement();e.isAncestorOf(b,i,!0)||(i=b);if(!i.canHaveHTML)return new f(i.parentNode,e.getNodeIndex(i));var b=e.getDocument(i).createElement("span"),r,m=c?"StartToStart":"StartToEnd";do i.insertBefore(b,b.previousSibling),h.moveToElementText(b);while(0<(r=h.compareEndPoints(m,a))&&b.previousSibling);m=b.nextSibling;if(-1==r&&m&&e.isCharacterDataNode(m)){h.setEndPoint(c?"EndToStart":"EndToEnd",
+a);if(/[\r\n]/.test(m.data)){i=h.duplicate();c=i.text.replace(/\r\n/g,"\r").length;for(c=i.moveStart("character",c);-1==i.compareEndPoints("StartToEnd",i);)c++,i.moveStart("character",1)}else c=h.text.length;i=new f(m,c)}else m=(d||!c)&&b.previousSibling,i=(c=(d||c)&&b.nextSibling)&&e.isCharacterDataNode(c)?new f(c,0):m&&e.isCharacterDataNode(m)?new f(m,m.length):new f(i,e.getNodeIndex(b));b.parentNode.removeChild(b);return i}function a(a,b){var c,d,f=a.offset,h=e.getDocument(a.node),i=h.body.createTextRange(),
+m=e.isCharacterDataNode(a.node);m?(c=a.node,d=c.parentNode):(c=a.node.childNodes,c=f<c.length?c[f]:null,d=a.node);h=h.createElement("span");h.innerHTML="&#feff;";c?d.insertBefore(h,c):d.appendChild(h);i.moveToElementText(h);i.collapse(!b);d.removeChild(h);if(m)i[b?"moveStart":"moveEnd"]("character",f);return i}b.requireModules(["DomUtil","DomRange"]);var d,e=b.dom,f=e.DomPosition,h=b.DomRange;if(b.features.implementsDomRange&&(!b.features.implementsTextRange||!b.config.preferTextRange))(function(){function a(b){for(var c=
+j.length,d;c--;)d=j[c],b[d]=b.nativeRange[d]}var c,j=h.rangeProperties,f;d=function(b){if(!b)throw Error("Range must be specified");this.nativeRange=b;a(this)};h.createPrototypeRange(d,function(a,b,c,d,g){var e=a.endContainer!==d||a.endOffset!=g;if(a.startContainer!==b||a.startOffset!=c||e)a.setEnd(d,g),a.setStart(b,c)},function(a){a.nativeRange.detach();a.detached=!0;for(var b=j.length,c;b--;)c=j[b],a[c]=null});c=d.prototype;c.selectNode=function(b){this.nativeRange.selectNode(b);a(this)};c.deleteContents=
+function(){this.nativeRange.deleteContents();a(this)};c.extractContents=function(){var b=this.nativeRange.extractContents();a(this);return b};c.cloneContents=function(){return this.nativeRange.cloneContents()};c.surroundContents=function(b){this.nativeRange.surroundContents(b);a(this)};c.collapse=function(b){this.nativeRange.collapse(b);a(this)};c.cloneRange=function(){return new d(this.nativeRange.cloneRange())};c.refresh=function(){a(this)};c.toString=function(){return this.nativeRange.toString()};
+var i=document.createTextNode("test");e.getBody(document).appendChild(i);var p=document.createRange();p.setStart(i,0);p.setEnd(i,0);try{p.setStart(i,1),c.setStart=function(b,c){this.nativeRange.setStart(b,c);a(this)},c.setEnd=function(b,c){this.nativeRange.setEnd(b,c);a(this)},f=function(b){return function(c){this.nativeRange[b](c);a(this)}}}catch(r){c.setStart=function(b,c){try{this.nativeRange.setStart(b,c)}catch(d){this.nativeRange.setEnd(b,c),this.nativeRange.setStart(b,c)}a(this)},c.setEnd=function(b,
+c){try{this.nativeRange.setEnd(b,c)}catch(d){this.nativeRange.setStart(b,c),this.nativeRange.setEnd(b,c)}a(this)},f=function(b,c){return function(d){try{this.nativeRange[b](d)}catch(e){this.nativeRange[c](d),this.nativeRange[b](d)}a(this)}}}c.setStartBefore=f("setStartBefore","setEndBefore");c.setStartAfter=f("setStartAfter","setEndAfter");c.setEndBefore=f("setEndBefore","setStartBefore");c.setEndAfter=f("setEndAfter","setStartAfter");p.selectNodeContents(i);c.selectNodeContents=p.startContainer==
+i&&p.endContainer==i&&0==p.startOffset&&p.endOffset==i.length?function(b){this.nativeRange.selectNodeContents(b);a(this)}:function(a){this.setStart(a,0);this.setEnd(a,h.getEndOffset(a))};p.selectNodeContents(i);p.setEnd(i,3);f=document.createRange();f.selectNodeContents(i);f.setEnd(i,4);f.setStart(i,2);c.compareBoundaryPoints=-1==p.compareBoundaryPoints(p.START_TO_END,f)&1==p.compareBoundaryPoints(p.END_TO_START,f)?function(a,b){b=b.nativeRange||b;a==b.START_TO_END?a=b.END_TO_START:a==b.END_TO_START&&
+(a=b.START_TO_END);return this.nativeRange.compareBoundaryPoints(a,b)}:function(a,b){return this.nativeRange.compareBoundaryPoints(a,b.nativeRange||b)};b.util.isHostMethod(p,"createContextualFragment")&&(c.createContextualFragment=function(a){return this.nativeRange.createContextualFragment(a)});e.getBody(document).removeChild(i);p.detach();f.detach()})(),b.createNativeRange=function(a){return(a||document).createRange()};else if(b.features.implementsTextRange){d=function(a){this.textRange=a;this.refresh()};
+d.prototype=new h(document);d.prototype.refresh=function(){var a,b,d=this.textRange;a=d.parentElement();var f=d.duplicate();f.collapse(!0);b=f.parentElement();f=d.duplicate();f.collapse(!1);d=f.parentElement();b=b==d?b:e.getCommonAncestor(b,d);b=b==a?b:e.getCommonAncestor(a,b);0==this.textRange.compareEndPoints("StartToEnd",this.textRange)?b=a=c(this.textRange,b,!0,!0):(a=c(this.textRange,b,!0,!1),b=c(this.textRange,b,!1,!1));this.setStart(a.node,a.offset);this.setEnd(b.node,b.offset)};h.copyComparisonConstants(d);
+var i=function(){return this}();"undefined"==typeof i.Range&&(i.Range=d);b.createNativeRange=function(a){return(a||document).body.createTextRange()}}b.features.implementsTextRange&&(d.rangeToTextRange=function(b){if(b.collapsed)return a(new f(b.startContainer,b.startOffset),!0);var c=a(new f(b.startContainer,b.startOffset),!0),d=a(new f(b.endContainer,b.endOffset),!1),b=e.getDocument(b.startContainer).body.createTextRange();b.setEndPoint("StartToStart",c);b.setEndPoint("EndToEnd",d);return b});d.prototype.getName=
+function(){return"WrappedRange"};b.WrappedRange=d;b.createRange=function(a){return new d(b.createNativeRange(a||document))};b.createRangyRange=function(a){return new h(a||document)};b.createIframeRange=function(a){return b.createRange(e.getIframeDocument(a))};b.createIframeRangyRange=function(a){return b.createRangyRange(e.getIframeDocument(a))};b.addCreateMissingNativeApiListener(function(a){a=a.document;if(typeof a.createRange=="undefined")a.createRange=function(){return b.createRange(this)};a=
+a=null})});
+rangy.createModule("WrappedSelection",function(b,c){function a(a){return(a||window).getSelection()}function d(a){return(a||window).document.selection}function e(a,b,c){var d=c?"end":"start",c=c?"start":"end";a.anchorNode=b[d+"Container"];a.anchorOffset=b[d+"Offset"];a.focusNode=b[c+"Container"];a.focusOffset=b[c+"Offset"]}function f(a){a.anchorNode=a.focusNode=null;a.anchorOffset=a.focusOffset=0;a.rangeCount=0;a.isCollapsed=!0;a._ranges.length=0}function h(a){var c;a instanceof x?(c=a._selectionNativeRange,c||
+(c=b.createNativeRange(m.getDocument(a.startContainer)),c.setEnd(a.endContainer,a.endOffset),c.setStart(a.startContainer,a.startOffset),a._selectionNativeRange=c,a.attachListener("detach",function(){this._selectionNativeRange=null}))):a instanceof o?c=a.nativeRange:b.features.implementsDomRange&&a instanceof m.getWindow(a.startContainer).Range&&(c=a);return c}function i(a){var b=a.getNodes(),c;a:if(!b.length||1!=b[0].nodeType)c=!1;else{c=1;for(var d=b.length;c<d;++c)if(!m.isAncestorOf(b[0],b[c])){c=
+!1;break a}c=!0}if(!c)throw Error("getSingleElementFromRange: range "+a.inspect()+" did not consist of a single element");return b[0]}function g(a,b){var c=new o(b);a._ranges=[c];e(a,c,!1);a.rangeCount=1;a.isCollapsed=c.collapsed}function k(a){a._ranges.length=0;if("None"==a.docSelection.type)f(a);else{var c=a.docSelection.createRange();if(c&&"undefined"!=typeof c.text)g(a,c);else{a.rangeCount=c.length;for(var d,j=m.getDocument(c.item(0)),k=0;k<a.rangeCount;++k)d=b.createRange(j),d.selectNode(c.item(k)),
+a._ranges.push(d);a.isCollapsed=1==a.rangeCount&&a._ranges[0].collapsed;e(a,a._ranges[a.rangeCount-1],!1)}}}function j(a,b){for(var c=a.docSelection.createRange(),d=i(b),e=m.getDocument(c.item(0)),e=m.getBody(e).createControlRange(),g=0,j=c.length;g<j;++g)e.add(c.item(g));try{e.add(d)}catch(f){throw Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");}e.select();k(a)}function q(a,b,c){this.nativeSelection=a;this.docSelection=b;this._ranges=
+[];this.win=c;this.refresh()}function n(a,b){for(var c=m.getDocument(b[0].startContainer),c=m.getBody(c).createControlRange(),d=0,e;d<rangeCount;++d){e=i(b[d]);try{c.add(e)}catch(g){throw Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");}}c.select();k(a)}function p(a,b){if(a.anchorNode&&m.getDocument(a.anchorNode)!==m.getDocument(b))throw new z("WRONG_DOCUMENT_ERR");}function r(a){var b=[],c=new w(a.anchorNode,a.anchorOffset),
+d=new w(a.focusNode,a.focusOffset),e="function"==typeof a.getName?a.getName():"Selection";if("undefined"!=typeof a.rangeCount)for(var g=0,j=a.rangeCount;g<j;++g)b[g]=x.inspect(a.getRangeAt(g));return"["+e+"(Ranges: "+b.join(", ")+")(anchor: "+c.inspect()+", focus: "+d.inspect()+"]"}b.requireModules(["DomUtil","DomRange","WrappedRange"]);b.config.checkSelectionRanges=!0;var m=b.dom,s=b.util,x=b.DomRange,o=b.WrappedRange,z=b.DOMException,w=m.DomPosition,y,A,t=b.util.isHostMethod(window,"getSelection"),
+v=b.util.isHostObject(document,"selection"),D=v&&(!t||b.config.preferTextRange);D?(y=d,b.isSelectionValid=function(a){var a=(a||window).document,b=a.selection;return"None"!=b.type||m.getDocument(b.createRange().parentElement())==a}):t?(y=a,b.isSelectionValid=function(){return!0}):c.fail("Neither document.selection or window.getSelection() detected.");b.getNativeSelection=y;var t=y(),K=b.createNativeRange(document),F=m.getBody(document),E=s.areHostObjects(t,s.areHostProperties(t,["anchorOffset","focusOffset"]));
+b.features.selectionHasAnchorAndFocus=E;var I=s.isHostMethod(t,"extend");b.features.selectionHasExtend=I;var N="number"==typeof t.rangeCount;b.features.selectionHasRangeCount=N;var J=!1,C=!0;s.areHostMethods(t,["addRange","getRangeAt","removeAllRanges"])&&"number"==typeof t.rangeCount&&b.features.implementsDomRange&&function(){var a=document.createElement("iframe");F.appendChild(a);var b=m.getIframeDocument(a);b.open();b.write("<html><head></head><body>12</body></html>");b.close();var c=m.getIframeWindow(a).getSelection(),
+d=b.documentElement.lastChild.firstChild,b=b.createRange();b.setStart(d,1);b.collapse(true);c.addRange(b);C=c.rangeCount==1;c.removeAllRanges();var e=b.cloneRange();b.setStart(d,0);e.setEnd(d,2);c.addRange(b);c.addRange(e);J=c.rangeCount==2;b.detach();e.detach();F.removeChild(a)}();b.features.selectionSupportsMultipleRanges=J;b.features.collapsedNonEditableSelectionsSupported=C;var l=!1,u;F&&s.isHostMethod(F,"createControlRange")&&(u=F.createControlRange(),s.areHostProperties(u,["item","add"])&&(l=
+!0));b.features.implementsControlRange=l;A=E?function(a){return a.anchorNode===a.focusNode&&a.anchorOffset===a.focusOffset}:function(a){return a.rangeCount?a.getRangeAt(a.rangeCount-1).collapsed:false};var B;s.isHostMethod(t,"getRangeAt")?B=function(a,b){try{return a.getRangeAt(b)}catch(c){return null}}:E&&(B=function(a){var c=m.getDocument(a.anchorNode),c=b.createRange(c);c.setStart(a.anchorNode,a.anchorOffset);c.setEnd(a.focusNode,a.focusOffset);if(c.collapsed!==this.isCollapsed){c.setStart(a.focusNode,
+a.focusOffset);c.setEnd(a.anchorNode,a.anchorOffset)}return c});b.getSelection=function(a){var a=a||window,b=a._rangySelection,c=y(a),e=v?d(a):null;if(b){b.nativeSelection=c;b.docSelection=e;b.refresh(a)}else{b=new q(c,e,a);a._rangySelection=b}return b};b.getIframeSelection=function(a){return b.getSelection(m.getIframeWindow(a))};u=q.prototype;if(!D&&E&&s.areHostMethods(t,["removeAllRanges","addRange"])){u.removeAllRanges=function(){this.nativeSelection.removeAllRanges();f(this)};var L=function(a,
+c){var d=x.getRangeDocument(c),d=b.createRange(d);d.collapseToPoint(c.endContainer,c.endOffset);a.nativeSelection.addRange(h(d));a.nativeSelection.extend(c.startContainer,c.startOffset);a.refresh()};u.addRange=N?function(a,c){if(l&&v&&this.docSelection.type=="Control")j(this,a);else if(c&&I)L(this,a);else{var d;if(J)d=this.rangeCount;else{this.removeAllRanges();d=0}this.nativeSelection.addRange(h(a));this.rangeCount=this.nativeSelection.rangeCount;if(this.rangeCount==d+1){if(b.config.checkSelectionRanges)(d=
+B(this.nativeSelection,this.rangeCount-1))&&!x.rangesEqual(d,a)&&(a=new o(d));this._ranges[this.rangeCount-1]=a;e(this,a,H(this.nativeSelection));this.isCollapsed=A(this)}else this.refresh()}}:function(a,b){if(b&&I)L(this,a);else{this.nativeSelection.addRange(h(a));this.refresh()}};u.setRanges=function(a){if(l&&a.length>1)n(this,a);else{this.removeAllRanges();for(var b=0,c=a.length;b<c;++b)this.addRange(a[b])}}}else if(s.isHostMethod(t,"empty")&&s.isHostMethod(K,"select")&&l&&D)u.removeAllRanges=
+function(){try{this.docSelection.empty();if(this.docSelection.type!="None"){var a;if(this.anchorNode)a=m.getDocument(this.anchorNode);else if(this.docSelection.type=="Control"){var b=this.docSelection.createRange();b.length&&(a=m.getDocument(b.item(0)).body.createTextRange())}if(a){a.body.createTextRange().select();this.docSelection.empty()}}}catch(c){}f(this)},u.addRange=function(a){if(this.docSelection.type=="Control")j(this,a);else{o.rangeToTextRange(a).select();this._ranges[0]=a;this.rangeCount=
+1;this.isCollapsed=this._ranges[0].collapsed;e(this,a,false)}},u.setRanges=function(a){this.removeAllRanges();var b=a.length;b>1?n(this,a):b&&this.addRange(a[0])};else return c.fail("No means of selecting a Range or TextRange was found"),!1;u.getRangeAt=function(a){if(a<0||a>=this.rangeCount)throw new z("INDEX_SIZE_ERR");return this._ranges[a]};var G;if(D)G=function(a){var c;if(b.isSelectionValid(a.win))c=a.docSelection.createRange();else{c=m.getBody(a.win.document).createTextRange();c.collapse(true)}a.docSelection.type==
+"Control"?k(a):c&&typeof c.text!="undefined"?g(a,c):f(a)};else if(s.isHostMethod(t,"getRangeAt")&&"number"==typeof t.rangeCount)G=function(a){if(l&&v&&a.docSelection.type=="Control")k(a);else{a._ranges.length=a.rangeCount=a.nativeSelection.rangeCount;if(a.rangeCount){for(var c=0,d=a.rangeCount;c<d;++c)a._ranges[c]=new b.WrappedRange(a.nativeSelection.getRangeAt(c));e(a,a._ranges[a.rangeCount-1],H(a.nativeSelection));a.isCollapsed=A(a)}else f(a)}};else if(E&&"boolean"==typeof t.isCollapsed&&"boolean"==
+typeof K.collapsed&&b.features.implementsDomRange)G=function(a){var b;b=a.nativeSelection;if(b.anchorNode){b=B(b,0);a._ranges=[b];a.rangeCount=1;b=a.nativeSelection;a.anchorNode=b.anchorNode;a.anchorOffset=b.anchorOffset;a.focusNode=b.focusNode;a.focusOffset=b.focusOffset;a.isCollapsed=A(a)}else f(a)};else return c.fail("No means of obtaining a Range or TextRange from the user's selection was found"),!1;u.refresh=function(a){var b=a?this._ranges.slice(0):null;G(this);if(a){a=b.length;if(a!=this._ranges.length)return false;
+for(;a--;)if(!x.rangesEqual(b[a],this._ranges[a]))return false;return true}};var P=function(a,b){var c=a.getAllRanges(),d=false;a.removeAllRanges();for(var e=0,g=c.length;e<g;++e)d||b!==c[e]?a.addRange(c[e]):d=true;a.rangeCount||f(a)};u.removeRange=l?function(a){if(this.docSelection.type=="Control"){for(var b=this.docSelection.createRange(),a=i(a),c=m.getDocument(b.item(0)),c=m.getBody(c).createControlRange(),d,e=false,g=0,j=b.length;g<j;++g){d=b.item(g);d!==a||e?c.add(b.item(g)):e=true}c.select();
+k(this)}else P(this,a)}:function(a){P(this,a)};var H;!D&&E&&b.features.implementsDomRange?(H=function(a){var b=false;a.anchorNode&&(b=m.comparePoints(a.anchorNode,a.anchorOffset,a.focusNode,a.focusOffset)==1);return b},u.isBackwards=function(){return H(this)}):H=u.isBackwards=function(){return false};u.toString=function(){for(var a=[],b=0,c=this.rangeCount;b<c;++b)a[b]=""+this._ranges[b];return a.join("")};u.collapse=function(a,c){p(this,a);var d=b.createRange(m.getDocument(a));d.collapseToPoint(a,
+c);this.removeAllRanges();this.addRange(d);this.isCollapsed=true};u.collapseToStart=function(){if(this.rangeCount){var a=this._ranges[0];this.collapse(a.startContainer,a.startOffset)}else throw new z("INVALID_STATE_ERR");};u.collapseToEnd=function(){if(this.rangeCount){var a=this._ranges[this.rangeCount-1];this.collapse(a.endContainer,a.endOffset)}else throw new z("INVALID_STATE_ERR");};u.selectAllChildren=function(a){p(this,a);var c=b.createRange(m.getDocument(a));c.selectNodeContents(a);this.removeAllRanges();
+this.addRange(c)};u.deleteFromDocument=function(){if(l&&v&&this.docSelection.type=="Control"){for(var a=this.docSelection.createRange(),b;a.length;){b=a.item(0);a.remove(b);b.parentNode.removeChild(b)}this.refresh()}else if(this.rangeCount){a=this.getAllRanges();this.removeAllRanges();b=0;for(var c=a.length;b<c;++b)a[b].deleteContents();this.addRange(a[c-1])}};u.getAllRanges=function(){return this._ranges.slice(0)};u.setSingleRange=function(a){this.setRanges([a])};u.containsNode=function(a,b){for(var c=
+0,d=this._ranges.length;c<d;++c)if(this._ranges[c].containsNode(a,b))return true;return false};u.toHtml=function(){var a="";if(this.rangeCount){for(var a=x.getRangeDocument(this._ranges[0]).createElement("div"),b=0,c=this._ranges.length;b<c;++b)a.appendChild(this._ranges[b].cloneContents());a=a.innerHTML}return a};u.getName=function(){return"WrappedSelection"};u.inspect=function(){return r(this)};u.detach=function(){this.win=this.anchorNode=this.focusNode=this.win._rangySelection=null};q.inspect=
+r;b.Selection=q;b.selectionPrototype=u;b.addCreateMissingNativeApiListener(function(a){if(typeof a.getSelection=="undefined")a.getSelection=function(){return b.getSelection(this)};a=null})});var Base=function(){};
+Base.extend=function(b,c){var a=Base.prototype.extend;Base._prototyping=!0;var d=new this;a.call(d,b);d.base=function(){};delete Base._prototyping;var e=d.constructor,f=d.constructor=function(){if(!Base._prototyping)if(this._constructing||this.constructor==f)this._constructing=!0,e.apply(this,arguments),delete this._constructing;else if(null!=arguments[0])return(arguments[0].extend||a).call(arguments[0],d)};f.ancestor=this;f.extend=this.extend;f.forEach=this.forEach;f.implement=this.implement;f.prototype=
+d;f.toString=this.toString;f.valueOf=function(a){return"object"==a?f:e.valueOf()};a.call(f,c);"function"==typeof f.init&&f.init();return f};
+Base.prototype={extend:function(b,c){if(1<arguments.length){var a=this[b];if(a&&"function"==typeof c&&(!a.valueOf||a.valueOf()!=c.valueOf())&&/\bbase\b/.test(c)){var d=c.valueOf(),c=function(){var b=this.base||Base.prototype.base;this.base=a;var c=d.apply(this,arguments);this.base=b;return c};c.valueOf=function(a){return"object"==a?c:d};c.toString=Base.toString}this[b]=c}else if(b){var e=Base.prototype.extend;!Base._prototyping&&"function"!=typeof this&&(e=this.extend||e);for(var f={toSource:null},
+h=["constructor","toString","valueOf"],i=Base._prototyping?0:1;g=h[i++];)b[g]!=f[g]&&e.call(this,g,b[g]);for(var g in b)f[g]||e.call(this,g,b[g])}return this}};
+Base=Base.extend({constructor:function(b){this.extend(b)}},{ancestor:Object,version:"1.1",forEach:function(b,c,a){for(var d in b)void 0===this.prototype[d]&&c.call(a,b[d],d,b)},implement:function(){for(var b=0;b<arguments.length;b++)if("function"==typeof arguments[b])arguments[b](this.prototype);else this.prototype.extend(arguments[b]);return this},toString:function(){return""+this.valueOf()}});
+wysihtml5.browser=function(){var b=navigator.userAgent,c=document.createElement("div"),a=-1!==b.indexOf("MSIE")&&-1===b.indexOf("Opera"),d=-1!==b.indexOf("Gecko")&&-1===b.indexOf("KHTML"),e=-1!==b.indexOf("AppleWebKit/"),f=-1!==b.indexOf("Chrome/"),h=-1!==b.indexOf("Opera/");return{USER_AGENT:b,supported:function(){var a=this.USER_AGENT.toLowerCase(),b="contentEditable"in c,d=document.execCommand&&document.queryCommandSupported&&document.queryCommandState,e=document.querySelector&&document.querySelectorAll,
+a=this.isIos()&&5>(/ipad|iphone|ipod/.test(a)&&a.match(/ os (\d+).+? like mac os x/)||[,0])[1]||-1!==a.indexOf("opera mobi")||-1!==a.indexOf("hpwos/");return b&&d&&e&&!a},isTouchDevice:function(){return this.supportsEvent("touchmove")},isIos:function(){var a=this.USER_AGENT.toLowerCase();return-1!==a.indexOf("webkit")&&-1!==a.indexOf("mobile")},supportsSandboxedIframes:function(){return a},throwsMixedContentWarningWhenIframeSrcIsEmpty:function(){return!("querySelector"in document)},displaysCaretInEmptyContentEditableCorrectly:function(){return!d},
+hasCurrentStyleProperty:function(){return"currentStyle"in c},insertsLineBreaksOnReturn:function(){return d},supportsPlaceholderAttributeOn:function(a){return"placeholder"in a},supportsEvent:function(a){var b;if(!(b="on"+a in c))c.setAttribute("on"+a,"return;"),b="function"===typeof c["on"+a];return b},supportsEventsInIframeCorrectly:function(){return!h},firesOnDropOnlyWhenOnDragOverIsCancelled:function(){return e||d},supportsDataTransfer:function(){try{return e&&(window.Clipboard||window.DataTransfer).prototype.getData}catch(a){return!1}},
+supportsHTML5Tags:function(a){a=a.createElement("div");a.innerHTML="<article>foo</article>";return"<article>foo</article>"===a.innerHTML.toLowerCase()},supportsCommand:function(){var b={formatBlock:a,insertUnorderedList:a||h,insertOrderedList:a||h},c={insertHTML:d};return function(a,d){if(!b[d]){try{return a.queryCommandSupported(d)}catch(e){}try{return a.queryCommandEnabled(d)}catch(f){return!!c[d]}}return!1}}(),doesAutoLinkingInContentEditable:function(){return a},canDisableAutoLinking:function(){return this.supportsCommand(document,
+"AutoUrlDetect")},clearsContentEditableCorrectly:function(){return d||h||e},supportsGetAttributeCorrectly:function(){return"1"!=document.createElement("td").getAttribute("rowspan")},canSelectImagesInContentEditable:function(){return d||a||h},clearsListsInContentEditableCorrectly:function(){return d||a||e},autoScrollsToCaret:function(){return!e},autoClosesUnclosedTags:function(){var a=c.cloneNode(!1),b;a.innerHTML="<p><div></div>";a=a.innerHTML.toLowerCase();b="<p></p><div></div>"===a||"<p><div></div></p>"===
+a;this.autoClosesUnclosedTags=function(){return b};return b},supportsNativeGetElementsByClassName:function(){return-1!==(""+document.getElementsByClassName).indexOf("[native code]")},supportsSelectionModify:function(){return"getSelection"in window&&"modify"in window.getSelection()},supportsClassList:function(){return"classList"in c},needsSpaceAfterLineBreak:function(){return h},supportsSpeechApiOn:function(a){return 11<=(b.match(/Chrome\/(\d+)/)||[,0])[1]&&("onwebkitspeechchange"in a||"speech"in a)},
+crashesWhenDefineProperty:function(b){return a&&("XMLHttpRequest"===b||"XDomainRequest"===b)},doesAsyncFocus:function(){return a},hasProblemsSettingCaretAfterImg:function(){return a},hasUndoInContextMenu:function(){return d||f||h}}}();
+wysihtml5.lang.array=function(b){return{contains:function(c){if(b.indexOf)return-1!==b.indexOf(c);for(var a=0,d=b.length;a<d;a++)if(b[a]===c)return!0;return!1},without:function(c){for(var c=wysihtml5.lang.array(c),a=[],d=0,e=b.length;d<e;d++)c.contains(b[d])||a.push(b[d]);return a},get:function(){for(var c=0,a=b.length,d=[];c<a;c++)d.push(b[c]);return d}}};
+wysihtml5.lang.Dispatcher=Base.extend({observe:function(b,c){this.events=this.events||{};this.events[b]=this.events[b]||[];this.events[b].push(c);return this},on:function(){return this.observe.apply(this,wysihtml5.lang.array(arguments).get())},fire:function(b,c){this.events=this.events||{};for(var a=this.events[b]||[],d=0;d<a.length;d++)a[d].call(this,c);return this},stopObserving:function(b,c){this.events=this.events||{};var a=0,d,e;if(b){d=this.events[b]||[];for(e=[];a<d.length;a++)d[a]!==c&&c&&
+e.push(d[a]);this.events[b]=e}else this.events={};return this}});wysihtml5.lang.object=function(b){return{merge:function(c){for(var a in c)b[a]=c[a];return this},get:function(){return b},clone:function(){var c={},a;for(a in b)c[a]=b[a];return c},isArray:function(){return"[object Array]"===Object.prototype.toString.call(b)}}};
+(function(){var b=/^\s+/,c=/\s+$/;wysihtml5.lang.string=function(a){a=""+a;return{trim:function(){return a.replace(b,"").replace(c,"")},interpolate:function(b){for(var c in b)a=this.replace("#{"+c+"}").by(b[c]);return a},replace:function(b){return{by:function(c){return a.split(b).join(c)}}}}}})();
+(function(b){function c(a){return a.replace(e,function(a,b){var c=(b.match(f)||[])[1]||"",d=i[c],b=b.replace(f,"");b.split(d).length>b.split(c).length&&(b+=c,c="");var e=d=b;b.length>h&&(e=e.substr(0,h)+"...");"www."===d.substr(0,4)&&(d="http://"+d);return'<a href="'+d+'">'+e+"</a>"+c})}function a(g){if(!d.contains(g.nodeName))if(g.nodeType===b.TEXT_NODE&&g.data.match(e)){var f=g.parentNode,j;j=f.ownerDocument;var h=j._wysihtml5_tempElement;h||(h=j._wysihtml5_tempElement=j.createElement("div"));j=
+h;j.innerHTML="<span></span>"+c(g.data);for(j.removeChild(j.firstChild);j.firstChild;)f.insertBefore(j.firstChild,g);f.removeChild(g)}else{f=b.lang.array(g.childNodes).get();j=f.length;for(h=0;h<j;h++)a(f[h]);return g}}var d=b.lang.array("CODE,PRE,A,SCRIPT,HEAD,TITLE,STYLE".split(",")),e=/((https?:\/\/|www\.)[^\s<]{3,})/gi,f=/([^\w\/\-](,?))$/i,h=100,i={")":"(","]":"[","}":"{"};b.dom.autoLink=function(b){var c;a:{c=b;for(var e;c.parentNode;){c=c.parentNode;e=c.nodeName;if(d.contains(e)){c=!0;break a}if("body"===
+e)break}c=!1}if(c)return b;b===b.ownerDocument.documentElement&&(b=b.ownerDocument.body);return a(b)};b.dom.autoLink.URL_REG_EXP=e})(wysihtml5);
+(function(b){var c=b.browser.supportsClassList(),a=b.dom;a.addClass=function(b,e){if(c)return b.classList.add(e);a.hasClass(b,e)||(b.className+=" "+e)};a.removeClass=function(a,b){if(c)return a.classList.remove(b);a.className=a.className.replace(RegExp("(^|\\s+)"+b+"(\\s+|$)")," ")};a.hasClass=function(a,b){if(c)return a.classList.contains(b);var f=a.className;return 0<f.length&&(f==b||RegExp("(^|\\s)"+b+"(\\s|$)").test(f))}})(wysihtml5);
+wysihtml5.dom.contains=function(){var b=document.documentElement;if(b.contains)return function(b,a){a.nodeType!==wysihtml5.ELEMENT_NODE&&(a=a.parentNode);return b!==a&&b.contains(a)};if(b.compareDocumentPosition)return function(b,a){return!!(b.compareDocumentPosition(a)&16)}}();
+wysihtml5.dom.convertToList=function(){function b(b,a){var d=b.createElement("li");a.appendChild(d);return d}return function(c,a){if("UL"===c.nodeName||"OL"===c.nodeName||"MENU"===c.nodeName)return c;for(var d=c.ownerDocument,e=d.createElement(a),f=wysihtml5.lang.array(c.childNodes).get(),h=f.length,i,g,k,j,q=0;q<h;q++)j=j||b(d,e),i=f[q],g="block"===wysihtml5.dom.getStyle("display").from(i),k="BR"===i.nodeName,g?(j=j.firstChild?b(d,e):j,j.appendChild(i),j=null):k?j=j.firstChild?null:j:j.appendChild(i);
+c.parentNode.replaceChild(e,c);return e}}();wysihtml5.dom.copyAttributes=function(b){return{from:function(c){return{to:function(a){for(var d,e=0,f=b.length;e<f;e++)d=b[e],c[d]&&(a[d]=c[d]);return{andTo:arguments.callee}}}}}};
+(function(b){var c=["-webkit-box-sizing","-moz-box-sizing","-ms-box-sizing","box-sizing"],a=function(a){var e;a:for(var f=0,h=c.length;f<h;f++)if("border-box"===b.getStyle(c[f]).from(a)){e=c[f];break a}return e?parseInt(b.getStyle("width").from(a),10)<a.offsetWidth:!1};b.copyStyles=function(d){return{from:function(e){a(e)&&(d=wysihtml5.lang.array(d).without(c));for(var f="",h=d.length,i=0,g;i<h;i++)g=d[i],f+=g+":"+b.getStyle(g).from(e)+";";return{to:function(a){b.setStyles(f).on(a);return{andTo:arguments.callee}}}}}}})(wysihtml5.dom);
+(function(b){b.dom.delegate=function(c,a,d,e){return b.dom.observe(c,d,function(d){for(var h=d.target,i=b.lang.array(c.querySelectorAll(a));h&&h!==c;){if(i.contains(h)){e.call(h,d);break}h=h.parentNode}})}})(wysihtml5);
+wysihtml5.dom.getAsDom=function(){var b="abbr,article,aside,audio,bdi,canvas,command,datalist,details,figcaption,figure,footer,header,hgroup,keygen,mark,meter,nav,output,progress,rp,rt,ruby,svg,section,source,summary,time,track,video,wbr".split(",");return function(c,a){var a=a||document,d;if("object"===typeof c&&c.nodeType)d=a.createElement("div"),d.appendChild(c);else if(wysihtml5.browser.supportsHTML5Tags(a))d=a.createElement("div"),d.innerHTML=c;else{d=a;if(!d._wysihtml5_supportsHTML5Tags){for(var e=
+0,f=b.length;e<f;e++)d.createElement(b[e]);d._wysihtml5_supportsHTML5Tags=!0}d=a;e=d.createElement("div");e.style.display="none";d.body.appendChild(e);try{e.innerHTML=c}catch(h){}d.body.removeChild(e);d=e}return d}}();
+wysihtml5.dom.getParentElement=function(){function b(b,a){return!a||!a.length?!0:"string"===typeof a?b===a:wysihtml5.lang.array(a).contains(b)}return function(c,a,d){d=d||50;if(a.className||a.classRegExp){a:{for(var e=a.nodeName,f=a.className,a=a.classRegExp;d--&&c&&"BODY"!==c.nodeName;){var h;if(h=c.nodeType===wysihtml5.ELEMENT_NODE)if(h=b(c.nodeName,e)){h=f;var i=(c.className||"").match(a)||[];h=!h?!!i.length:i[i.length-1]===h}if(h)break a;c=c.parentNode}c=null}return c}a:{e=a.nodeName;for(f=d;f--&&
+c&&"BODY"!==c.nodeName;){if(b(c.nodeName,e))break a;c=c.parentNode}c=null}return c}}();
+wysihtml5.dom.getStyle=function(){function b(b){return b.replace(a,function(a){return a.charAt(1).toUpperCase()})}var c={"float":"styleFloat"in document.createElement("div").style?"styleFloat":"cssFloat"},a=/\-[a-z]/g;return function(a){return{from:function(e){if(e.nodeType===wysihtml5.ELEMENT_NODE){var f=e.ownerDocument,h=c[a]||b(a),i=e.style,g=e.currentStyle,k=i[h];if(k)return k;if(g)try{return g[h]}catch(j){}var h=f.defaultView||f.parentWindow,f=("height"===a||"width"===a)&&"TEXTAREA"===e.nodeName,
+q;if(h.getComputedStyle)return f&&(q=i.overflow,i.overflow="hidden"),e=h.getComputedStyle(e,null).getPropertyValue(a),f&&(i.overflow=q||""),e}}}}}();wysihtml5.dom.hasElementWithTagName=function(){var b={},c=1;return function(a,d){var e=(a._wysihtml5_identifier||(a._wysihtml5_identifier=c++))+":"+d,f=b[e];f||(f=b[e]=a.getElementsByTagName(d));return 0<f.length}}();
+(function(b){var c={},a=1;b.dom.hasElementWithClassName=function(d,e){if(!b.browser.supportsNativeGetElementsByClassName())return!!d.querySelector("."+e);var f=(d._wysihtml5_identifier||(d._wysihtml5_identifier=a++))+":"+e,h=c[f];h||(h=c[f]=d.getElementsByClassName(e));return 0<h.length}})(wysihtml5);wysihtml5.dom.insert=function(b){return{after:function(c){c.parentNode.insertBefore(b,c.nextSibling)},before:function(c){c.parentNode.insertBefore(b,c)},into:function(c){c.appendChild(b)}}};
+wysihtml5.dom.insertCSS=function(b){b=b.join("\n");return{into:function(c){var a=c.head||c.getElementsByTagName("head")[0],d=c.createElement("style");d.type="text/css";d.styleSheet?d.styleSheet.cssText=b:d.appendChild(c.createTextNode(b));a&&a.appendChild(d)}}};
+wysihtml5.dom.observe=function(b,c,a){for(var c="string"===typeof c?[c]:c,d,e,f=0,h=c.length;f<h;f++)e=c[f],b.addEventListener?b.addEventListener(e,a,!1):(d=function(c){"target"in c||(c.target=c.srcElement);c.preventDefault=c.preventDefault||function(){this.returnValue=false};c.stopPropagation=c.stopPropagation||function(){this.cancelBubble=true};a.call(b,c)},b.attachEvent("on"+e,d));return{stop:function(){for(var e,g=0,f=c.length;g<f;g++)e=c[g],b.removeEventListener?b.removeEventListener(e,a,!1):
+b.detachEvent("on"+e,d)}}};
+wysihtml5.dom.parse=function(){function b(c,e){var g=c.childNodes,f=g.length,k;k=a[c.nodeType];var h=0;k=k&&k(c);if(!k)return null;for(h=0;h<f;h++)(newChild=b(g[h],e))&&k.appendChild(newChild);return e&&1>=k.childNodes.length&&k.nodeName.toLowerCase()===d&&!k.attributes.length?k.firstChild:k}function c(a,b){var b=b.toLowerCase(),c;if(c="IMG"==a.nodeName)if(c="src"==b){var d;try{d=a.complete&&!a.mozMatchesSelector(":-moz-broken")}catch(e){a.complete&&"complete"===a.readyState&&(d=!0)}c=!0===d}return c?
+a.src:i&&"outerHTML"in a?-1!=a.outerHTML.toLowerCase().indexOf(" "+b+"=")?a.getAttribute(b):null:a.getAttribute(b)}var a={1:function(a){var b,f,i=h.tags;f=a.nodeName.toLowerCase();b=a.scopeName;if(a._wysihtml5)return null;a._wysihtml5=1;if("wysihtml5-temp"===a.className)return null;b&&"HTML"!=b&&(f=b+":"+f);"outerHTML"in a&&!wysihtml5.browser.autoClosesUnclosedTags()&&"P"===a.nodeName&&"</p>"!==a.outerHTML.slice(-4).toLowerCase()&&(f="div");if(f in i){b=i[f];if(!b||b.remove)return null;b="string"===
+typeof b?{rename_tag:b}:b}else if(a.firstChild)b={rename_tag:d};else return null;f=a.ownerDocument.createElement(b.rename_tag||f);var i={},r=b.set_class,m=b.add_class,s=b.set_attributes,x=b.check_attributes,o=h.classes,z=0,w=[];b=[];var y=[],A=[],t;s&&(i=wysihtml5.lang.object(s).clone());if(x)for(t in x)if(s=g[x[t]])s=s(c(a,t)),"string"===typeof s&&(i[t]=s);r&&w.push(r);if(m)for(t in m)if(s=k[m[t]])r=s(c(a,t)),"string"===typeof r&&w.push(r);o["_wysihtml5-temp-placeholder"]=1;(A=a.getAttribute("class"))&&
+(w=w.concat(A.split(e)));for(m=w.length;z<m;z++)a=w[z],o[a]&&b.push(a);for(o=b.length;o--;)a=b[o],wysihtml5.lang.array(y).contains(a)||y.unshift(a);y.length&&(i["class"]=y.join(" "));for(t in i)try{f.setAttribute(t,i[t])}catch(v){}i.src&&("undefined"!==typeof i.width&&f.setAttribute("width",i.width),"undefined"!==typeof i.height&&f.setAttribute("height",i.height));return f},3:function(a){return a.ownerDocument.createTextNode(a.data)}},d="span",e=/\s+/,f={tags:{},classes:{}},h={},i=!wysihtml5.browser.supportsGetAttributeCorrectly(),
+g={url:function(){var a=/^https?:\/\//i;return function(b){return!b||!b.match(a)?null:b.replace(a,function(a){return a.toLowerCase()})}}(),alt:function(){var a=/[^ a-z0-9_\-]/gi;return function(b){return!b?"":b.replace(a,"")}}(),numbers:function(){var a=/\D/g;return function(b){return(b=(b||"").replace(a,""))||null}}()},k={align_img:function(){var a={left:"wysiwyg-float-left",right:"wysiwyg-float-right"};return function(b){return a[(""+b).toLowerCase()]}}(),align_text:function(){var a={left:"wysiwyg-text-align-left",
+right:"wysiwyg-text-align-right",center:"wysiwyg-text-align-center",justify:"wysiwyg-text-align-justify"};return function(b){return a[(""+b).toLowerCase()]}}(),clear_br:function(){var a={left:"wysiwyg-clear-left",right:"wysiwyg-clear-right",both:"wysiwyg-clear-both",all:"wysiwyg-clear-both"};return function(b){return a[(""+b).toLowerCase()]}}(),size_font:function(){var a={1:"wysiwyg-font-size-xx-small",2:"wysiwyg-font-size-small",3:"wysiwyg-font-size-medium",4:"wysiwyg-font-size-large",5:"wysiwyg-font-size-x-large",
+6:"wysiwyg-font-size-xx-large",7:"wysiwyg-font-size-xx-large","-":"wysiwyg-font-size-smaller","+":"wysiwyg-font-size-larger"};return function(b){return a[(""+b).charAt(0)]}}()};return function(a,c,d,e){wysihtml5.lang.object(h).merge(f).merge(c).get();for(var d=d||a.ownerDocument||document,c=d.createDocumentFragment(),g="string"===typeof a,a=g?wysihtml5.dom.getAsDom(a,d):a;a.firstChild;)d=a.firstChild,a.removeChild(d),(d=b(d,e))&&c.appendChild(d);a.innerHTML="";a.appendChild(c);return g?wysihtml5.quirks.getCorrectInnerHTML(a):
+a}}();wysihtml5.dom.removeEmptyTextNodes=function(b){for(var c=wysihtml5.lang.array(b.childNodes).get(),a=c.length,d=0;d<a;d++)b=c[d],b.nodeType===wysihtml5.TEXT_NODE&&""===b.data&&b.parentNode.removeChild(b)};wysihtml5.dom.renameElement=function(b,c){for(var a=b.ownerDocument.createElement(c),d;d=b.firstChild;)a.appendChild(d);wysihtml5.dom.copyAttributes(["align","className"]).from(b).to(a);b.parentNode.replaceChild(a,b);return a};
+wysihtml5.dom.replaceWithChildNodes=function(b){if(b.parentNode)if(b.firstChild){for(var c=b.ownerDocument.createDocumentFragment();b.firstChild;)c.appendChild(b.firstChild);b.parentNode.replaceChild(c,b)}else b.parentNode.removeChild(b)};
+(function(b){function c(a){var b=a.ownerDocument.createElement("br");a.appendChild(b)}b.resolveList=function(a){if(!("MENU"!==a.nodeName&&"UL"!==a.nodeName&&"OL"!==a.nodeName)){var d=a.ownerDocument.createDocumentFragment(),e=a.previousSibling,f,h,i;for(e&&"block"!==b.getStyle("display").from(e)&&c(d);i=a.firstChild;){for(f=i.lastChild;e=i.firstChild;)h=(h=e===f)&&"block"!==b.getStyle("display").from(e)&&"BR"!==e.nodeName,d.appendChild(e),h&&c(d);i.parentNode.removeChild(i)}a.parentNode.replaceChild(d,
+a)}}})(wysihtml5.dom);
+(function(b){var c=document,a="parent,top,opener,frameElement,frames,localStorage,globalStorage,sessionStorage,indexedDB".split(","),d="open,close,openDialog,showModalDialog,alert,confirm,prompt,openDatabase,postMessage,XMLHttpRequest,XDomainRequest".split(","),e=["referrer","write","open","close"];b.dom.Sandbox=Base.extend({constructor:function(a,c){this.callback=a||b.EMPTY_FUNCTION;this.config=b.lang.object({}).merge(c).get();this.iframe=this._createIframe()},insertInto:function(a){"string"===typeof a&&
+(a=c.getElementById(a));a.appendChild(this.iframe)},getIframe:function(){return this.iframe},getWindow:function(){this._readyError()},getDocument:function(){this._readyError()},destroy:function(){var a=this.getIframe();a.parentNode.removeChild(a)},_readyError:function(){throw Error("wysihtml5.Sandbox: Sandbox iframe isn't loaded yet");},_createIframe:function(){var a=this,d=c.createElement("iframe");d.className="wysihtml5-sandbox";b.dom.setAttributes({security:"restricted",allowtransparency:"true",
+frameborder:0,width:0,height:0,marginwidth:0,marginheight:0}).on(d);b.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()&&(d.src="javascript:'<html></html>'");d.onload=function(){d.onreadystatechange=d.onload=null;a._onLoadIframe(d)};d.onreadystatechange=function(){if(/loaded|complete/.test(d.readyState)){d.onreadystatechange=d.onload=null;a._onLoadIframe(d)}};return d},_onLoadIframe:function(f){if(b.dom.contains(c.documentElement,f)){var h=this,i=f.contentWindow,g=f.contentWindow.document,k=
+this._getHtml({charset:c.characterSet||c.charset||"utf-8",stylesheets:this.config.stylesheets});g.open("text/html","replace");g.write(k);g.close();this.getWindow=function(){return f.contentWindow};this.getDocument=function(){return f.contentWindow.document};i.onerror=function(a,b,c){throw Error("wysihtml5.Sandbox: "+a,b,c);};if(!b.browser.supportsSandboxedIframes()){var j,k=0;for(j=a.length;k<j;k++)this._unset(i,a[k]);k=0;for(j=d.length;k<j;k++)this._unset(i,d[k],b.EMPTY_FUNCTION);k=0;for(j=e.length;k<
+j;k++)this._unset(g,e[k]);this._unset(g,"cookie","",!0)}this.loaded=!0;setTimeout(function(){h.callback(h)},0)}},_getHtml:function(a){var c=a.stylesheets,d="",e=0,k;if(c="string"===typeof c?[c]:c)for(k=c.length;e<k;e++)d+='<link rel="stylesheet" href="'+c[e]+'">';a.stylesheets=d;return b.lang.string('<!DOCTYPE html><html><head><meta charset="#{charset}">#{stylesheets}</head><body></body></html>').interpolate(a)},_unset:function(a,c,d,e){try{a[c]=d}catch(k){}try{a.__defineGetter__(c,function(){return d})}catch(j){}if(e)try{a.__defineSetter__(c,
+function(){})}catch(q){}if(!b.browser.crashesWhenDefineProperty(c))try{var n={get:function(){return d}};e&&(n.set=function(){});Object.defineProperty(a,c,n)}catch(p){}}})})(wysihtml5);(function(){var b={className:"class"};wysihtml5.dom.setAttributes=function(c){return{on:function(a){for(var d in c)a.setAttribute(b[d]||d,c[d])}}}})();
+wysihtml5.dom.setStyles=function(b){return{on:function(c){c=c.style;if("string"===typeof b)c.cssText+=";"+b;else for(var a in b)"float"===a?(c.cssFloat=b[a],c.styleFloat=b[a]):c[a]=b[a]}}};
+(function(b){b.simulatePlaceholder=function(c,a,d){var e=function(){a.hasPlaceholderSet()&&a.clear();b.removeClass(a.element,"placeholder")},f=function(){a.isEmpty()&&(a.setValue(d),b.addClass(a.element,"placeholder"))};c.observe("set_placeholder",f).observe("unset_placeholder",e).observe("focus:composer",e).observe("paste:composer",e).observe("blur:composer",f);f()}})(wysihtml5.dom);
+(function(b){var c=document.documentElement;"textContent"in c?(b.setTextContent=function(a,b){a.textContent=b},b.getTextContent=function(a){return a.textContent}):"innerText"in c?(b.setTextContent=function(a,b){a.innerText=b},b.getTextContent=function(a){return a.innerText}):(b.setTextContent=function(a,b){a.nodeValue=b},b.getTextContent=function(a){return a.nodeValue})})(wysihtml5.dom);
+wysihtml5.quirks.cleanPastedHTML=function(){var b={"a u":wysihtml5.dom.replaceWithChildNodes};return function(c,a,d){var a=a||b,d=d||c.ownerDocument||document,e="string"===typeof c,f,h,i,g=0,c=e?wysihtml5.dom.getAsDom(c,d):c;for(i in a){f=c.querySelectorAll(i);d=a[i];for(h=f.length;g<h;g++)d(f[g])}return e?c.innerHTML:c}}();
+(function(b){var c=b.dom;b.quirks.ensureProperClearing=function(){var a=function(){var a=this;setTimeout(function(){var b=a.innerHTML.toLowerCase();if("<p>&nbsp;</p>"==b||"<p>&nbsp;</p><p>&nbsp;</p>"==b)a.innerHTML=""},0)};return function(b){c.observe(b.element,["cut","keydown"],a)}}();b.quirks.ensureProperClearingOfLists=function(){var a=["OL","UL","MENU"];return function(d){c.observe(d.element,"keydown",function(e){if(e.keyCode===b.BACKSPACE_KEY){var f=d.selection.getSelectedNode(),e=d.element;
+e.firstChild&&b.lang.array(a).contains(e.firstChild.nodeName)&&(f=c.getParentElement(f,{nodeName:a}))&&f==e.firstChild&&1>=f.childNodes.length&&(f.firstChild?""===f.firstChild.innerHTML:1)&&f.parentNode.removeChild(f)}})}}()})(wysihtml5);
+(function(b){b.quirks.getCorrectInnerHTML=function(c){var a=c.innerHTML;if(-1===a.indexOf("%7E"))return a;var c=c.querySelectorAll("[href*='~'], [src*='~']"),d,e,f,h;h=0;for(f=c.length;h<f;h++)d=c[h].href||c[h].src,e=b.lang.string(d).replace("~").by("%7E"),a=b.lang.string(a).replace(e).by(d);return a}})(wysihtml5);
+(function(b){var c=b.dom,a="LI,P,H1,H2,H3,H4,H5,H6".split(","),d=["UL","OL","MENU"];b.quirks.insertLineBreakOnReturn=function(e){function f(a){if(a=c.getParentElement(a,{nodeName:["P","DIV"]},2)){var d=document.createTextNode(b.INVISIBLE_SPACE);c.insert(d).before(a);c.replaceWithChildNodes(a);e.selection.selectNode(d)}}c.observe(e.element.ownerDocument,"keydown",function(h){var i=h.keyCode;if(!(h.shiftKey||i!==b.ENTER_KEY&&i!==b.BACKSPACE_KEY)){var g=e.selection.getSelectedNode();(g=c.getParentElement(g,
+{nodeName:a},4))?"LI"===g.nodeName&&(i===b.ENTER_KEY||i===b.BACKSPACE_KEY)?setTimeout(function(){var a=e.selection.getSelectedNode(),b;a&&((b=c.getParentElement(a,{nodeName:d},2))||f(a))},0):g.nodeName.match(/H[1-6]/)&&i===b.ENTER_KEY&&setTimeout(function(){f(e.selection.getSelectedNode())},0):i===b.ENTER_KEY&&!b.browser.insertsLineBreaksOnReturn()&&(e.commands.exec("insertLineBreak"),h.preventDefault())}})}})(wysihtml5);
+(function(b){b.quirks.redraw=function(c){b.dom.addClass(c,"wysihtml5-quirks-redraw");b.dom.removeClass(c,"wysihtml5-quirks-redraw");try{var a=c.ownerDocument;a.execCommand("italic",!1,null);a.execCommand("italic",!1,null)}catch(d){}}})(wysihtml5);
+(function(b){var c=b.dom;b.Selection=Base.extend({constructor:function(a){window.rangy.init();this.editor=a;this.composer=a.composer;this.doc=this.composer.doc},getBookmark:function(){var a=this.getRange();return a&&a.cloneRange()},setBookmark:function(a){a&&this.setSelection(a)},setBefore:function(a){var b=rangy.createRange(this.doc);b.setStartBefore(a);b.setEndBefore(a);return this.setSelection(b)},setAfter:function(a){var b=rangy.createRange(this.doc);b.setStartAfter(a);b.setEndAfter(a);return this.setSelection(b)},
+selectNode:function(a){var d=rangy.createRange(this.doc),e=a.nodeType===b.ELEMENT_NODE,f="canHaveHTML"in a?a.canHaveHTML:"IMG"!==a.nodeName,h=e?a.innerHTML:a.data,h=""===h||h===b.INVISIBLE_SPACE,i=c.getStyle("display").from(a),i="block"===i||"list-item"===i;if(h&&e&&f)try{a.innerHTML=b.INVISIBLE_SPACE}catch(g){}f?d.selectNodeContents(a):d.selectNode(a);f&&h&&e?d.collapse(i):f&&h&&(d.setStartAfter(a),d.setEndAfter(a));this.setSelection(d)},getSelectedNode:function(a){if(a&&this.doc.selection&&"Control"===
+this.doc.selection.type&&(a=this.doc.selection.createRange())&&a.length)return a.item(0);a=this.getSelection(this.doc);return a.focusNode===a.anchorNode?a.focusNode:(a=this.getRange(this.doc))?a.commonAncestorContainer:this.doc.body},executeAndRestore:function(a,c){var e=this.doc.body,f=c&&e.scrollTop,h=c&&e.scrollLeft,i='<span class="_wysihtml5-temp-placeholder">'+b.INVISIBLE_SPACE+"</span>",g=this.getRange(this.doc);if(g){i=g.createContextualFragment(i);g.insertNode(i);try{a(g.startContainer,g.endContainer)}catch(k){setTimeout(function(){throw k;
+},0)}(caretPlaceholder=this.doc.querySelector("._wysihtml5-temp-placeholder"))?(g=rangy.createRange(this.doc),g.selectNode(caretPlaceholder),g.deleteContents(),this.setSelection(g)):e.focus();c&&(e.scrollTop=f,e.scrollLeft=h);try{caretPlaceholder.parentNode.removeChild(caretPlaceholder)}catch(j){}}else a(e,e)},executeAndRestoreSimple:function(a){var b,c,f=this.getRange(),h=this.doc.body,i;if(f){b=f.getNodes([3]);h=b[0]||f.startContainer;i=b[b.length-1]||f.endContainer;b=h===f.startContainer?f.startOffset:
+0;c=i===f.endContainer?f.endOffset:i.length;try{a(f.startContainer,f.endContainer)}catch(g){setTimeout(function(){throw g;},0)}a=rangy.createRange(this.doc);try{a.setStart(h,b)}catch(k){}try{a.setEnd(i,c)}catch(j){}try{this.setSelection(a)}catch(q){}}else a(h,h)},insertHTML:function(a){var a=rangy.createRange(this.doc).createContextualFragment(a),b=a.lastChild;this.insertNode(a);b&&this.setAfter(b)},insertNode:function(a){var b=this.getRange();b&&b.insertNode(a)},surround:function(a){var b=this.getRange();
+if(b)try{b.surroundContents(a),this.selectNode(a)}catch(c){a.appendChild(b.extractContents()),b.insertNode(a)}},scrollIntoView:function(){var a=this.doc,c=a.documentElement.scrollHeight>a.documentElement.offsetHeight,e;if(!(e=a._wysihtml5ScrollIntoViewElement))e=a.createElement("span"),e.innerHTML=b.INVISIBLE_SPACE;e=a._wysihtml5ScrollIntoViewElement=e;if(c){this.insertNode(e);var c=e,f=0;if(c.parentNode){do f+=c.offsetTop||0,c=c.offsetParent;while(c)}c=f;e.parentNode.removeChild(e);c>a.body.scrollTop&&
+(a.body.scrollTop=c)}},selectLine:function(){b.browser.supportsSelectionModify()?this._selectLine_W3C():this.doc.selection&&this._selectLine_MSIE()},_selectLine_W3C:function(){var a=this.doc.defaultView.getSelection();a.modify("extend","left","lineboundary");a.modify("extend","right","lineboundary")},_selectLine_MSIE:function(){var a=this.doc.selection.createRange(),b=a.boundingTop,c=this.doc.body.scrollWidth,f;if(a.moveToPoint){0===b&&(f=this.doc.createElement("span"),this.insertNode(f),b=f.offsetTop,
+f.parentNode.removeChild(f));b+=1;for(f=-10;f<c;f+=2)try{a.moveToPoint(f,b);break}catch(h){}for(f=this.doc.selection.createRange();0<=c;c--)try{f.moveToPoint(c,b);break}catch(i){}a.setEndPoint("EndToEnd",f);a.select()}},getText:function(){var a=this.getSelection();return a?a.toString():""},getNodes:function(a,b){var c=this.getRange();return c?c.getNodes([a],b):[]},getRange:function(){var a=this.getSelection();return a&&a.rangeCount&&a.getRangeAt(0)},getSelection:function(){return rangy.getSelection(this.doc.defaultView||
+this.doc.parentWindow)},setSelection:function(a){return rangy.getSelection(this.doc.defaultView||this.doc.parentWindow).setSingleRange(a)}})})(wysihtml5);
+(function(b,c){function a(a,b){return c.dom.isCharacterDataNode(a)?0==b?!!a.previousSibling:b==a.length?!!a.nextSibling:!0:0<b&&b<a.childNodes.length}function d(a,b,e){var f;c.dom.isCharacterDataNode(b)&&(0==e?(e=c.dom.getNodeIndex(b),b=b.parentNode):e==b.length?(e=c.dom.getNodeIndex(b)+1,b=b.parentNode):f=c.dom.splitDataNode(b,e));if(!f){f=b.cloneNode(!1);f.id&&f.removeAttribute("id");for(var h;h=b.childNodes[e];)f.appendChild(h);c.dom.insertAfter(f,b)}return b==a?f:d(a,f.parentNode,c.dom.getNodeIndex(f))}
+function e(a){this.firstTextNode=(this.isElementMerge=a.nodeType==b.ELEMENT_NODE)?a.lastChild:a;this.textNodes=[this.firstTextNode]}function f(a,b,c,d){this.tagNames=a||[h];this.cssClass=b||"";this.similarClassRegExp=c;this.normalize=d;this.applyToAnyTagName=!1}var h="span",i=/\s+/g;e.prototype={doMerge:function(){for(var a=[],b,c,d=0,e=this.textNodes.length;d<e;++d)b=this.textNodes[d],c=b.parentNode,a[d]=b.data,d&&(c.removeChild(b),c.hasChildNodes()||c.parentNode.removeChild(c));return this.firstTextNode.data=
+a=a.join("")},getLength:function(){for(var a=this.textNodes.length,b=0;a--;)b+=this.textNodes[a].length;return b},toString:function(){for(var a=[],b=0,c=this.textNodes.length;b<c;++b)a[b]="'"+this.textNodes[b].data+"'";return"[Merge("+a.join(",")+")]"}};f.prototype={getAncestorWithClass:function(a){for(var d;a;){if(this.cssClass)if(d=this.cssClass,a.className){var e=a.className.match(this.similarClassRegExp)||[];d=e[e.length-1]===d}else d=!1;else d=!0;if(a.nodeType==b.ELEMENT_NODE&&c.dom.arrayContains(this.tagNames,
+a.tagName.toLowerCase())&&d)return a;a=a.parentNode}return!1},postApply:function(a,b){for(var c=a[0],d=a[a.length-1],f=[],h,i=c,m=d,s=0,x=d.length,o,z,w=0,y=a.length;w<y;++w)if(o=a[w],z=this.getAdjacentMergeableTextNode(o.parentNode,!1)){if(h||(h=new e(z),f.push(h)),h.textNodes.push(o),o===c&&(i=h.firstTextNode,s=i.length),o===d)m=h.firstTextNode,x=h.getLength()}else h=null;if(c=this.getAdjacentMergeableTextNode(d.parentNode,!0))h||(h=new e(d),f.push(h)),h.textNodes.push(c);if(f.length){w=0;for(y=
+f.length;w<y;++w)f[w].doMerge();b.setStart(i,s);b.setEnd(m,x)}},getAdjacentMergeableTextNode:function(a,c){var d=a.nodeType==b.TEXT_NODE,e=d?a.parentNode:a,f=c?"nextSibling":"previousSibling";if(d){if((d=a[f])&&d.nodeType==b.TEXT_NODE)return d}else if((d=e[f])&&this.areElementsMergeable(a,d))return d[c?"firstChild":"lastChild"];return null},areElementsMergeable:function(a,b){var d;if(d=c.dom.arrayContains(this.tagNames,(a.tagName||"").toLowerCase()))if(d=c.dom.arrayContains(this.tagNames,(b.tagName||
+"").toLowerCase()))if(d=a.className.replace(i," ")==b.className.replace(i," "))a:if(a.attributes.length!=b.attributes.length)d=!1;else{d=0;for(var e=a.attributes.length,f,h;d<e;++d)if(f=a.attributes[d],h=f.name,"class"!=h&&(h=b.attributes.getNamedItem(h),f.specified!=h.specified||f.specified&&f.nodeValue!==h.nodeValue)){d=!1;break a}d=!0}return d},createContainer:function(a){a=a.createElement(this.tagNames[0]);this.cssClass&&(a.className=this.cssClass);return a},applyToTextNode:function(a){var b=
+a.parentNode;1==b.childNodes.length&&c.dom.arrayContains(this.tagNames,b.tagName.toLowerCase())?this.cssClass&&(a=this.cssClass,b.className?(b.className&&(b.className=b.className.replace(this.similarClassRegExp,"")),b.className+=" "+a):b.className=a):(b=this.createContainer(c.dom.getDocument(a)),a.parentNode.insertBefore(b,a),b.appendChild(a))},isRemovable:function(a){return c.dom.arrayContains(this.tagNames,a.tagName.toLowerCase())&&b.lang.string(a.className).trim()==this.cssClass},undoToTextNode:function(b,
+c,e){c.containsNode(e)||(b=c.cloneRange(),b.selectNode(e),b.isPointInRange(c.endContainer,c.endOffset)&&a(c.endContainer,c.endOffset)&&(d(e,c.endContainer,c.endOffset),c.setEndAfter(e)),b.isPointInRange(c.startContainer,c.startOffset)&&a(c.startContainer,c.startOffset)&&(e=d(e,c.startContainer,c.startOffset)));this.similarClassRegExp&&e.className&&(e.className=e.className.replace(this.similarClassRegExp,""));if(this.isRemovable(e)){c=e;for(e=c.parentNode;c.firstChild;)e.insertBefore(c.firstChild,
+c);e.removeChild(c)}},applyToRange:function(a){var c=a.getNodes([b.TEXT_NODE]);if(!c.length)try{var d=this.createContainer(a.endContainer.ownerDocument);a.surroundContents(d);this.selectNode(a,d);return}catch(e){}a.splitBoundaries();c=a.getNodes([b.TEXT_NODE]);if(c.length){for(var f=0,h=c.length;f<h;++f)d=c[f],this.getAncestorWithClass(d)||this.applyToTextNode(d);a.setStart(c[0],0);d=c[c.length-1];a.setEnd(d,d.length);this.normalize&&this.postApply(c,a)}},undoToRange:function(a){var c=a.getNodes([b.TEXT_NODE]),
+d,e;c.length?(a.splitBoundaries(),c=a.getNodes([b.TEXT_NODE])):(c=a.endContainer.ownerDocument.createTextNode(b.INVISIBLE_SPACE),a.insertNode(c),a.selectNode(c),c=[c]);for(var f=0,h=c.length;f<h;++f)d=c[f],(e=this.getAncestorWithClass(d))&&this.undoToTextNode(d,a,e);1==h?this.selectNode(a,c[0]):(a.setStart(c[0],0),d=c[c.length-1],a.setEnd(d,d.length),this.normalize&&this.postApply(c,a))},selectNode:function(a,c){var d=c.nodeType===b.ELEMENT_NODE,e="canHaveHTML"in c?c.canHaveHTML:!0,f=d?c.innerHTML:
+c.data;if((f=""===f||f===b.INVISIBLE_SPACE)&&d&&e)try{c.innerHTML=b.INVISIBLE_SPACE}catch(h){}a.selectNodeContents(c);f&&d?a.collapse(!1):f&&(a.setStartAfter(c),a.setEndAfter(c))},getTextSelectedByRange:function(a,b){var c=b.cloneRange();c.selectNodeContents(a);var d=c.intersection(b),d=d?d.toString():"";c.detach();return d},isAppliedToRange:function(a){var c=[],d,e=a.getNodes([b.TEXT_NODE]);if(!e.length)return(d=this.getAncestorWithClass(a.startContainer))?[d]:!1;for(var f=0,h=e.length,i;f<h;++f){i=
+this.getTextSelectedByRange(e[f],a);d=this.getAncestorWithClass(e[f]);if(""!=i&&!d)return!1;c.push(d)}return c},toggleRange:function(a){this.isAppliedToRange(a)?this.undoToRange(a):this.applyToRange(a)}};b.selection.HTMLApplier=f})(wysihtml5,rangy);
+wysihtml5.Commands=Base.extend({constructor:function(b){this.editor=b;this.composer=b.composer;this.doc=this.composer.doc},support:function(b){return wysihtml5.browser.supportsCommand(this.doc,b)},exec:function(b,c){var a=wysihtml5.commands[b],d=a&&a.exec;this.editor.fire("beforecommand:composer");if(d)return d.call(a,this.composer,b,c);try{return this.doc.execCommand(b,!1,c)}catch(e){}this.editor.fire("aftercommand:composer")},state:function(b,c){var a=wysihtml5.commands[b],d=a&&a.state;if(d)return d.call(a,
+this.composer,b,c);try{return this.doc.queryCommandState(b)}catch(e){return!1}},value:function(b){var c=wysihtml5.commands[b],a=c&&c.value;if(a)return a.call(c,this.composer,b);try{return this.doc.queryCommandValue(b)}catch(d){return null}}});(function(b){b.commands.bold={exec:function(c,a){return b.commands.formatInline.exec(c,a,"b")},state:function(c,a){return b.commands.formatInline.state(c,a,"b")},value:function(){}}})(wysihtml5);
+(function(b){function c(c,h){var i=c.doc,g="_wysihtml5-temp-"+ +new Date,k=0,j,q,n;b.commands.formatInline.exec(c,a,d,g,/non-matching-class/g);j=i.querySelectorAll(d+"."+g);for(g=j.length;k<g;k++)for(n in q=j[k],q.removeAttribute("class"),h)q.setAttribute(n,h[n]);k=q;1===g&&(n=e.getTextContent(q),g=!!q.querySelector("*"),n=""===n||n===b.INVISIBLE_SPACE,!g&&n&&(e.setTextContent(q,q.href),i=i.createTextNode(" "),c.selection.setAfter(q),c.selection.insertNode(i),k=i));c.selection.setAfter(k)}var a,d=
+"A",e=b.dom;b.commands.createLink={exec:function(a,b,d){var g=this.state(a,b);g?a.selection.executeAndRestore(function(){for(var a=g.length,b=0,c,d,f;b<a;b++)c=g[b],d=e.getParentElement(c,{nodeName:"code"}),f=e.getTextContent(c),f.match(e.autoLink.URL_REG_EXP)&&!d?e.renameElement(c,"code"):e.replaceWithChildNodes(c)}):(d="object"===typeof d?d:{href:d},c(a,d))},state:function(a,c){return b.commands.formatInline.state(a,c,"A")},value:function(){return a}}})(wysihtml5);
+(function(b){var c=/wysiwyg-font-size-[a-z]+/g;b.commands.fontSize={exec:function(a,d,e){return b.commands.formatInline.exec(a,d,"span","wysiwyg-font-size-"+e,c)},state:function(a,d,e){return b.commands.formatInline.state(a,d,"span","wysiwyg-font-size-"+e,c)},value:function(){}}})(wysihtml5);
+(function(b){var c=/wysiwyg-color-[a-z]+/g;b.commands.foreColor={exec:function(a,d,e){return b.commands.formatInline.exec(a,d,"span","wysiwyg-color-"+e,c)},state:function(a,d,e){return b.commands.formatInline.state(a,d,"span","wysiwyg-color-"+e,c)},value:function(){}}})(wysihtml5);
+(function(b){function c(a){for(a=a.previousSibling;a&&a.nodeType===b.TEXT_NODE&&!b.lang.string(a.data).trim();)a=a.previousSibling;return a}function a(a){for(a=a.nextSibling;a&&a.nodeType===b.TEXT_NODE&&!b.lang.string(a.data).trim();)a=a.nextSibling;return a}function d(a){return"BR"===a.nodeName||"block"===h.getStyle("display").from(a)?!0:!1}function e(a,c,d,e){if(e)var f=h.observe(a,"DOMNodeInserted",function(a){var a=a.target,c;a.nodeType===b.ELEMENT_NODE&&(c=h.getStyle("display").from(a),"inline"!==
+c.substr(0,6)&&(a.className+=" "+e))});a.execCommand(c,!1,d);f&&f.stop()}function f(b,d){b.selection.selectLine();b.selection.surround(d);var e=a(d),f=c(d);e&&"BR"===e.nodeName&&e.parentNode.removeChild(e);f&&"BR"===f.nodeName&&f.parentNode.removeChild(f);(e=d.lastChild)&&"BR"===e.nodeName&&e.parentNode.removeChild(e);b.selection.selectNode(d)}var h=b.dom,i="H1,H2,H3,H4,H5,H6,P,BLOCKQUOTE,DIV".split(",");b.commands.formatBlock={exec:function(g,k,j,q,n){var p=g.doc,r=this.state(g,k,j,q,n),m,j="string"===
+typeof j?j.toUpperCase():j;if(r)g.selection.executeAndRestoreSimple(function(){n&&(r.className=r.className.replace(n,""));var e=!!b.lang.string(r.className).trim();if(!e&&r.nodeName===(j||"DIV")){var e=r,f=e.ownerDocument,g=a(e),i=c(e);g&&!d(g)&&e.parentNode.insertBefore(f.createElement("br"),g);i&&!d(i)&&e.parentNode.insertBefore(f.createElement("br"),e);h.replaceWithChildNodes(r)}else e&&h.renameElement(r,"DIV")});else{if(null===j||b.lang.array(i).contains(j))if(m=g.selection.getSelectedNode(),
+r=h.getParentElement(m,{nodeName:i})){g.selection.executeAndRestoreSimple(function(){j&&(r=h.renameElement(r,j));if(q){var a=r;a.className?(a.className=a.className.replace(n,""),a.className+=" "+q):a.className=q}});return}g.commands.support(k)?e(p,k,j||"DIV",q):(r=p.createElement(j||"DIV"),q&&(r.className=q),f(g,r))}},state:function(a,b,c,d,e){c="string"===typeof c?c.toUpperCase():c;a=a.selection.getSelectedNode();return h.getParentElement(a,{nodeName:c,className:d,classRegExp:e})},value:function(){}}})(wysihtml5);
+(function(b){function c(c,f,h){var i=c+":"+f;if(!d[i]){var g=d,k=b.selection.HTMLApplier,j=a[c],c=j?[c.toLowerCase(),j.toLowerCase()]:[c.toLowerCase()];g[i]=new k(c,f,h,!0)}return d[i]}var a={strong:"b",em:"i",b:"strong",i:"em"},d={};b.commands.formatInline={exec:function(a,b,d,i,g){b=a.selection.getRange();if(!b)return!1;c(d,i,g).toggleRange(b);a.selection.setSelection(b)},state:function(d,f,h,i,g){var f=d.doc,k=a[h]||h;if(!b.dom.hasElementWithTagName(f,h)&&!b.dom.hasElementWithTagName(f,k)||i&&
+!b.dom.hasElementWithClassName(f,i))return!1;d=d.selection.getRange();return!d?!1:c(h,i,g).isAppliedToRange(d)},value:function(){}}})(wysihtml5);(function(b){b.commands.insertHTML={exec:function(b,a,d){b.commands.support(a)?b.doc.execCommand(a,!1,d):b.selection.insertHTML(d)},state:function(){return!1},value:function(){}}})(wysihtml5);
+(function(b){b.commands.insertImage={exec:function(c,a,d){var d="object"===typeof d?d:{src:d},e=c.doc,a=this.state(c),f;if(a)c.selection.setBefore(a),d=a.parentNode,d.removeChild(a),b.dom.removeEmptyTextNodes(d),"A"===d.nodeName&&!d.firstChild&&(c.selection.setAfter(d),d.parentNode.removeChild(d)),b.quirks.redraw(c.element);else{a=e.createElement("IMG");for(f in d)a[f]=d[f];c.selection.insertNode(a);b.browser.hasProblemsSettingCaretAfterImg()?(d=e.createTextNode(b.INVISIBLE_SPACE),c.selection.insertNode(d),
+c.selection.setAfter(d)):c.selection.setAfter(a)}},state:function(c){var a;if(!b.dom.hasElementWithTagName(c.doc,"IMG"))return!1;a=c.selection.getSelectedNode();if(!a)return!1;if("IMG"===a.nodeName)return a;if(a.nodeType!==b.ELEMENT_NODE)return!1;a=c.selection.getText();if(a=b.lang.string(a).trim())return!1;c=c.selection.getNodes(b.ELEMENT_NODE,function(a){return"IMG"===a.nodeName});return 1!==c.length?!1:c[0]},value:function(b){return(b=this.state(b))&&b.src}}})(wysihtml5);
+(function(b){var c="<br>"+(b.browser.needsSpaceAfterLineBreak()?" ":"");b.commands.insertLineBreak={exec:function(a,d){a.commands.support(d)?(a.doc.execCommand(d,!1,null),b.browser.autoScrollsToCaret()||a.selection.scrollIntoView()):a.commands.exec("insertHTML",c)},state:function(){return!1},value:function(){}}})(wysihtml5);
+(function(b){b.commands.insertOrderedList={exec:function(c,a){var d=c.doc,e,f,h;c.commands.support(a)?d.execCommand(a,!1,null):(e=c.selection.getSelectedNode(),(h=b.dom.getParentElement(e,{nodeName:["UL","OL"]},4))?c.selection.executeAndRestoreSimple(function(){"OL"===h.nodeName?b.dom.resolveList(h):("UL"===h.nodeName||"MENU"===h.nodeName)&&b.dom.renameElement(h,"ol")}):(f=d.createElement("span"),c.selection.surround(f),d=""===f.innerHTML||f.innerHTML===b.INVISIBLE_SPACE,c.selection.executeAndRestoreSimple(function(){h=
+b.dom.convertToList(f,"ol")}),d&&c.selection.selectNode(h.querySelector("li"))))},state:function(b,a){try{return b.doc.queryCommandState(a)}catch(d){return!1}},value:function(){}}})(wysihtml5);
+(function(b){b.commands.insertUnorderedList={exec:function(c,a){var d=c.doc,e,f,h;c.commands.support(a)?d.execCommand(a,!1,null):(e=c.selection.getSelectedNode(),(h=b.dom.getParentElement(e,{nodeName:["UL","OL"]}))?c.selection.executeAndRestoreSimple(function(){"UL"===h.nodeName?b.dom.resolveList(h):("OL"===h.nodeName||"MENU"===h.nodeName)&&b.dom.renameElement(h,"ul")}):(f=d.createElement("span"),c.selection.surround(f),d=""===f.innerHTML||f.innerHTML===b.INVISIBLE_SPACE,c.selection.executeAndRestoreSimple(function(){h=
+b.dom.convertToList(f,"ul")}),d&&c.selection.selectNode(h.querySelector("li"))))},state:function(b,a){try{return b.doc.queryCommandState(a)}catch(d){return!1}},value:function(){}}})(wysihtml5);(function(b){b.commands.italic={exec:function(c,a){return b.commands.formatInline.exec(c,a,"i")},state:function(c,a){return b.commands.formatInline.state(c,a,"i")},value:function(){}}})(wysihtml5);
+(function(b){var c=/wysiwyg-text-align-[a-z]+/g;b.commands.justifyCenter={exec:function(a){return b.commands.formatBlock.exec(a,"formatBlock",null,"wysiwyg-text-align-center",c)},state:function(a){return b.commands.formatBlock.state(a,"formatBlock",null,"wysiwyg-text-align-center",c)},value:function(){}}})(wysihtml5);
+(function(b){var c=/wysiwyg-text-align-[a-z]+/g;b.commands.justifyLeft={exec:function(a){return b.commands.formatBlock.exec(a,"formatBlock",null,"wysiwyg-text-align-left",c)},state:function(a){return b.commands.formatBlock.state(a,"formatBlock",null,"wysiwyg-text-align-left",c)},value:function(){}}})(wysihtml5);
+(function(b){var c=/wysiwyg-text-align-[a-z]+/g;b.commands.justifyRight={exec:function(a){return b.commands.formatBlock.exec(a,"formatBlock",null,"wysiwyg-text-align-right",c)},state:function(a){return b.commands.formatBlock.state(a,"formatBlock",null,"wysiwyg-text-align-right",c)},value:function(){}}})(wysihtml5);
+(function(b){var c=/wysiwyg-text-decoration-underline/g;b.commands.underline={exec:function(a,d){return b.commands.formatInline.exec(a,d,"span","wysiwyg-text-decoration-underline",c)},state:function(a,d){return b.commands.formatInline.state(a,d,"span","wysiwyg-text-decoration-underline",c)},value:function(){}}})(wysihtml5);
+(function(b){var c='<span id="_wysihtml5-undo" class="_wysihtml5-temp">'+b.INVISIBLE_SPACE+"</span>",a='<span id="_wysihtml5-redo" class="_wysihtml5-temp">'+b.INVISIBLE_SPACE+"</span>",d=b.dom;b.UndoManager=b.lang.Dispatcher.extend({constructor:function(a){this.editor=a;this.composer=a.composer;this.element=this.composer.element;this.history=[this.composer.getValue()];this.position=1;this.composer.commands.support("insertHTML")&&this._observe()},_observe:function(){var e=this,f=this.composer.sandbox.getDocument(),
+h;d.observe(this.element,"keydown",function(a){if(!(a.altKey||!a.ctrlKey&&!a.metaKey)){var b=a.keyCode,c=90===b&&a.shiftKey||89===b;90===b&&!a.shiftKey?(e.undo(),a.preventDefault()):c&&(e.redo(),a.preventDefault())}});d.observe(this.element,"keydown",function(a){a=a.keyCode;a!==h&&(h=a,(8===a||46===a)&&e.transact())});if(b.browser.hasUndoInContextMenu()){var i,g,k=function(){for(var a;a=f.querySelector("._wysihtml5-temp");)a.parentNode.removeChild(a);clearInterval(i)};d.observe(this.element,"contextmenu",
+function(){k();e.composer.selection.executeAndRestoreSimple(function(){e.element.lastChild&&e.composer.selection.setAfter(e.element.lastChild);f.execCommand("insertHTML",!1,c);f.execCommand("insertHTML",!1,a);f.execCommand("undo",!1,null)});i=setInterval(function(){f.getElementById("_wysihtml5-redo")?(k(),e.redo()):f.getElementById("_wysihtml5-undo")||(k(),e.undo())},400);g||(g=!0,d.observe(document,"mousedown",k),d.observe(f,["mousedown","paste","cut","copy"],k))})}this.editor.observe("newword:composer",
+function(){e.transact()}).observe("beforecommand:composer",function(){e.transact()})},transact:function(){var a=this.history[this.position-1],b=this.composer.getValue();if(b!=a){if(40<(this.history.length=this.position))this.history.shift(),this.position--;this.position++;this.history.push(b)}},undo:function(){this.transact();1>=this.position||(this.set(this.history[--this.position-1]),this.editor.fire("undo:composer"))},redo:function(){this.position>=this.history.length||(this.set(this.history[++this.position-
+1]),this.editor.fire("redo:composer"))},set:function(a){this.composer.setValue(a);this.editor.focus(!0)}})})(wysihtml5);
+wysihtml5.views.View=Base.extend({constructor:function(b,c,a){this.parent=b;this.element=c;this.config=a;this._observeViewChange()},_observeViewChange:function(){var b=this;this.parent.observe("beforeload",function(){b.parent.observe("change_view",function(c){c===b.name?(b.parent.currentView=b,b.show(),setTimeout(function(){b.focus()},0)):b.hide()})})},focus:function(){if(this.element.ownerDocument.querySelector(":focus")!==this.element)try{this.element.focus()}catch(b){}},hide:function(){this.element.style.display=
+"none"},show:function(){this.element.style.display=""},disable:function(){this.element.setAttribute("disabled","disabled")},enable:function(){this.element.removeAttribute("disabled")}});
+(function(b){var c=b.dom,a=b.browser;b.views.Composer=b.views.View.extend({name:"composer",CARET_HACK:"<br>",constructor:function(a,b,c){this.base(a,b,c);this.textarea=this.parent.textarea;this._initSandbox()},clear:function(){this.element.innerHTML=a.displaysCaretInEmptyContentEditableCorrectly()?"":this.CARET_HACK},getValue:function(a){var c=this.isEmpty()?"":b.quirks.getCorrectInnerHTML(this.element);a&&(c=this.parent.parse(c));return c=b.lang.string(c).replace(b.INVISIBLE_SPACE).by("")},setValue:function(a,
+b){b&&(a=this.parent.parse(a));this.element.innerHTML=a},show:function(){this.iframe.style.display=this._displayStyle||"";this.disable();this.enable()},hide:function(){this._displayStyle=c.getStyle("display").from(this.iframe);"none"===this._displayStyle&&(this._displayStyle=null);this.iframe.style.display="none"},disable:function(){this.element.removeAttribute("contentEditable");this.base()},enable:function(){this.element.setAttribute("contentEditable","true");this.base()},focus:function(a){b.browser.doesAsyncFocus()&&
+this.hasPlaceholderSet()&&this.clear();this.base();var c=this.element.lastChild;a&&c&&("BR"===c.nodeName?this.selection.setBefore(this.element.lastChild):this.selection.setAfter(this.element.lastChild))},getTextContent:function(){return c.getTextContent(this.element)},hasPlaceholderSet:function(){return this.getTextContent()==this.textarea.element.getAttribute("placeholder")},isEmpty:function(){var a=this.element.innerHTML;return""===a||a===this.CARET_HACK||this.hasPlaceholderSet()||""===this.getTextContent()&&
+!this.element.querySelector("blockquote, ul, ol, img, embed, object, table, iframe, svg, video, audio, button, input, select, textarea")},_initSandbox:function(){var a=this;this.sandbox=new c.Sandbox(function(){a._create()},{stylesheets:this.config.stylesheets});this.iframe=this.sandbox.getIframe();var b=document.createElement("input");b.type="hidden";b.name="_wysihtml5_mode";b.value=1;var f=this.textarea.element;c.insert(this.iframe).after(f);c.insert(b).after(f)},_create:function(){var d=this;this.doc=
+this.sandbox.getDocument();this.element=this.doc.body;this.textarea=this.parent.textarea;this.element.innerHTML=this.textarea.getValue(!0);this.enable();this.selection=new b.Selection(this.parent);this.commands=new b.Commands(this.parent);c.copyAttributes("className,spellcheck,title,lang,dir,accessKey".split(",")).from(this.textarea.element).to(this.element);c.addClass(this.element,this.config.composerClassName);this.config.style&&this.style();this.observe();var e=this.config.name;e&&(c.addClass(this.element,
+e),c.addClass(this.iframe,e));(e="string"===typeof this.config.placeholder?this.config.placeholder:this.textarea.element.getAttribute("placeholder"))&&c.simulatePlaceholder(this.parent,this,e);this.commands.exec("styleWithCSS",!1);this._initAutoLinking();this._initObjectResizing();this._initUndoManager();(this.textarea.element.hasAttribute("autofocus")||document.querySelector(":focus")==this.textarea.element)&&setTimeout(function(){d.focus()},100);b.quirks.insertLineBreakOnReturn(this);a.clearsContentEditableCorrectly()||
+b.quirks.ensureProperClearing(this);a.clearsListsInContentEditableCorrectly()||b.quirks.ensureProperClearingOfLists(this);this.initSync&&this.config.sync&&this.initSync();this.textarea.hide();this.parent.fire("beforeload").fire("load")},_initAutoLinking:function(){var d=this,e=a.canDisableAutoLinking(),f=a.doesAutoLinkingInContentEditable();e&&this.commands.exec("autoUrlDetect",!1);if(this.config.autoLink){(!f||f&&e)&&this.parent.observe("newword:composer",function(){d.selection.executeAndRestore(function(a,
+b){c.autoLink(b.parentNode)})});var h=this.sandbox.getDocument().getElementsByTagName("a"),i=c.autoLink.URL_REG_EXP,g=function(a){a=b.lang.string(c.getTextContent(a)).trim();"www."===a.substr(0,4)&&(a="http://"+a);return a};c.observe(this.element,"keydown",function(a){if(h.length){var a=d.selection.getSelectedNode(a.target.ownerDocument),b=c.getParentElement(a,{nodeName:"A"},4),e;b&&(e=g(b),setTimeout(function(){var a=g(b);a!==e&&a.match(i)&&b.setAttribute("href",a)},0))}})}},_initObjectResizing:function(){var d=
+["width","height"],e=d.length,f=this.element;this.commands.exec("enableObjectResizing",this.config.allowObjectResizing);this.config.allowObjectResizing?a.supportsEvent("resizeend")&&c.observe(f,"resizeend",function(a){for(var a=a.target||a.srcElement,c=a.style,g=0,k;g<e;g++)k=d[g],c[k]&&(a.setAttribute(k,parseInt(c[k],10)),c[k]="");b.quirks.redraw(f)}):a.supportsEvent("resizestart")&&c.observe(f,"resizestart",function(a){a.preventDefault()})},_initUndoManager:function(){new b.UndoManager(this.parent)}})})(wysihtml5);
+(function(b){var c=b.dom,a=document,d=window,e=a.createElement("div"),f="background-color,color,cursor,font-family,font-size,font-style,font-variant,font-weight,line-height,letter-spacing,text-align,text-decoration,text-indent,text-rendering,word-break,word-wrap,word-spacing".split(","),h="background-color,border-collapse,border-bottom-color,border-bottom-style,border-bottom-width,border-left-color,border-left-style,border-left-width,border-right-color,border-right-style,border-right-width,border-top-color,border-top-style,border-top-width,clear,display,float,margin-bottom,margin-left,margin-right,margin-top,outline-color,outline-offset,outline-width,outline-style,padding-left,padding-right,padding-top,padding-bottom,position,top,left,right,bottom,z-index,vertical-align,text-align,-webkit-box-sizing,-moz-box-sizing,-ms-box-sizing,box-sizing,-webkit-box-shadow,-moz-box-shadow,-ms-box-shadow,box-shadow,-webkit-border-top-right-radius,-moz-border-radius-topright,border-top-right-radius,-webkit-border-bottom-right-radius,-moz-border-radius-bottomright,border-bottom-right-radius,-webkit-border-bottom-left-radius,-moz-border-radius-bottomleft,border-bottom-left-radius,-webkit-border-top-left-radius,-moz-border-radius-topleft,border-top-left-radius,width,height".split(","),
+i="width,height,top,left,right,bottom".split(","),g=["html             { height: 100%; }","body             { min-height: 100%; padding: 0; margin: 0; margin-top: -1px; padding-top: 1px; }","._wysihtml5-temp { display: none; }",b.browser.isGecko?"body.placeholder { color: graytext !important; }":"body.placeholder { color: #a9a9a9 !important; }","body[disabled]   { background-color: #eee !important; color: #999 !important; cursor: default !important; }","img:-moz-broken  { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"],
+k=function(b){if(b.setActive)try{b.setActive()}catch(e){}else{var f=b.style,g=a.documentElement.scrollTop||a.body.scrollTop,h=a.documentElement.scrollLeft||a.body.scrollLeft,f={position:f.position,top:f.top,left:f.left,WebkitUserSelect:f.WebkitUserSelect};c.setStyles({position:"absolute",top:"-99999px",left:"-99999px",WebkitUserSelect:"none"}).on(b);b.focus();c.setStyles(f).on(b);d.scrollTo&&d.scrollTo(h,g)}};b.views.Composer.prototype.style=function(){var j=this,q=a.querySelector(":focus"),n=this.textarea.element,
+p=n.hasAttribute("placeholder"),r=p&&n.getAttribute("placeholder");this.focusStylesHost=this.focusStylesHost||e.cloneNode(!1);this.blurStylesHost=this.blurStylesHost||e.cloneNode(!1);p&&n.removeAttribute("placeholder");n===q&&n.blur();c.copyStyles(h).from(n).to(this.iframe).andTo(this.blurStylesHost);c.copyStyles(f).from(n).to(this.element).andTo(this.blurStylesHost);c.insertCSS(g).into(this.element.ownerDocument);k(n);c.copyStyles(h).from(n).to(this.focusStylesHost);c.copyStyles(f).from(n).to(this.focusStylesHost);
+var m=b.lang.array(h).without(["display"]);q?q.focus():n.blur();p&&n.setAttribute("placeholder",r);b.browser.hasCurrentStyleProperty()||c.observe(d,"resize",function(){var a=c.getStyle("display").from(n);n.style.display="";c.copyStyles(i).from(n).to(j.iframe).andTo(j.focusStylesHost).andTo(j.blurStylesHost);n.style.display=a});this.parent.observe("focus:composer",function(){c.copyStyles(m).from(j.focusStylesHost).to(j.iframe);c.copyStyles(f).from(j.focusStylesHost).to(j.element)});this.parent.observe("blur:composer",
+function(){c.copyStyles(m).from(j.blurStylesHost).to(j.iframe);c.copyStyles(f).from(j.blurStylesHost).to(j.element)});return this}})(wysihtml5);
+(function(b){var c=b.dom,a=b.browser,d={66:"bold",73:"italic",85:"underline"};b.views.Composer.prototype.observe=function(){var e=this,f=this.getValue(),h=this.sandbox.getIframe(),i=this.element,g=a.supportsEventsInIframeCorrectly()?i:this.sandbox.getWindow(),k=a.supportsEvent("drop")?["drop","paste"]:["dragdrop","paste"];c.observe(h,"DOMNodeRemoved",function(){clearInterval(j);e.parent.fire("destroy:composer")});var j=setInterval(function(){c.contains(document.documentElement,h)||(clearInterval(j),
+e.parent.fire("destroy:composer"))},250);c.observe(g,"focus",function(){e.parent.fire("focus").fire("focus:composer");setTimeout(function(){f=e.getValue()},0)});c.observe(g,"blur",function(){f!==e.getValue()&&e.parent.fire("change").fire("change:composer");e.parent.fire("blur").fire("blur:composer")});b.browser.isIos()&&c.observe(i,"blur",function(){var a=i.ownerDocument.createElement("input"),b=document.documentElement.scrollTop||document.body.scrollTop,c=document.documentElement.scrollLeft||document.body.scrollLeft;
+try{e.selection.insertNode(a)}catch(d){i.appendChild(a)}a.focus();a.parentNode.removeChild(a);window.scrollTo(c,b)});c.observe(i,"dragenter",function(){e.parent.fire("unset_placeholder")});a.firesOnDropOnlyWhenOnDragOverIsCancelled()&&c.observe(i,["dragover","dragenter"],function(a){a.preventDefault()});c.observe(i,k,function(b){var c=b.dataTransfer,d;c&&a.supportsDataTransfer()&&(d=c.getData("text/html")||c.getData("text/plain"));d?(i.focus(),e.commands.exec("insertHTML",d),e.parent.fire("paste").fire("paste:composer"),
+b.stopPropagation(),b.preventDefault()):setTimeout(function(){e.parent.fire("paste").fire("paste:composer")},0)});c.observe(i,"keyup",function(a){a=a.keyCode;(a===b.SPACE_KEY||a===b.ENTER_KEY)&&e.parent.fire("newword:composer")});this.parent.observe("paste:composer",function(){setTimeout(function(){e.parent.fire("newword:composer")},0)});a.canSelectImagesInContentEditable()||c.observe(i,"mousedown",function(a){var b=a.target;"IMG"===b.nodeName&&(e.selection.selectNode(b),a.preventDefault())});c.observe(i,
+"keydown",function(a){var b=d[a.keyCode];if((a.ctrlKey||a.metaKey)&&b)e.commands.exec(b),a.preventDefault()});c.observe(i,"keydown",function(a){var c=e.selection.getSelectedNode(!0),d=a.keyCode;if(c&&"IMG"===c.nodeName&&(d===b.BACKSPACE_KEY||d===b.DELETE_KEY))d=c.parentNode,d.removeChild(c),"A"===d.nodeName&&!d.firstChild&&d.parentNode.removeChild(d),setTimeout(function(){b.quirks.redraw(i)},0),a.preventDefault()});var q={IMG:"Image: ",A:"Link: "};c.observe(i,"mouseover",function(a){var a=a.target,
+b=a.nodeName;"A"!==b&&"IMG"!==b||(b=q[b]+(a.getAttribute("href")||a.getAttribute("src")),a.setAttribute("title",b))})}})(wysihtml5);
+(function(b){b.views.Synchronizer=Base.extend({constructor:function(b,a,d){this.editor=b;this.textarea=a;this.composer=d;this._observe()},fromComposerToTextarea:function(c){this.textarea.setValue(b.lang.string(this.composer.getValue()).trim(),c)},fromTextareaToComposer:function(b){var a=this.textarea.getValue();a?this.composer.setValue(a,b):(this.composer.clear(),this.editor.fire("set_placeholder"))},sync:function(b){"textarea"===this.editor.currentView.name?this.fromTextareaToComposer(b):this.fromComposerToTextarea(b)},
+_observe:function(){var c,a=this,d=this.textarea.element.form,e=function(){c=setInterval(function(){a.fromComposerToTextarea()},400)},f=function(){clearInterval(c);c=null};e();d&&(b.dom.observe(d,"submit",function(){a.sync(!0)}),b.dom.observe(d,"reset",function(){setTimeout(function(){a.fromTextareaToComposer()},0)}));this.editor.observe("change_view",function(b){if(b==="composer"&&!c){a.fromTextareaToComposer(true);e()}else if(b==="textarea"){a.fromComposerToTextarea(true);f()}});this.editor.observe("destroy:composer",
+f)}})})(wysihtml5);
+wysihtml5.views.Textarea=wysihtml5.views.View.extend({name:"textarea",constructor:function(b,c,a){this.base(b,c,a);this._observe()},clear:function(){this.element.value=""},getValue:function(b){var c=this.isEmpty()?"":this.element.value;b&&(c=this.parent.parse(c));return c},setValue:function(b,c){c&&(b=this.parent.parse(b));this.element.value=b},hasPlaceholderSet:function(){var b=wysihtml5.browser.supportsPlaceholderAttributeOn(this.element),c=this.element.getAttribute("placeholder")||null,a=this.element.value;
+return b&&!a||a===c},isEmpty:function(){return!wysihtml5.lang.string(this.element.value).trim()||this.hasPlaceholderSet()},_observe:function(){var b=this.element,c=this.parent,a={focusin:"focus",focusout:"blur"},d=wysihtml5.browser.supportsEvent("focusin")?["focusin","focusout","change"]:["focus","blur","change"];c.observe("beforeload",function(){wysihtml5.dom.observe(b,d,function(b){b=a[b.type]||b.type;c.fire(b).fire(b+":textarea")});wysihtml5.dom.observe(b,["paste","drop"],function(){setTimeout(function(){c.fire("paste").fire("paste:textarea")},
+0)})})}});
+(function(b){var c=b.dom;b.toolbar.Dialog=b.lang.Dispatcher.extend({constructor:function(a,b){this.link=a;this.container=b},_observe:function(){if(!this._observed){var a=this,d=function(b){var c=a._serialize();c==a.elementToChange?a.fire("edit",c):a.fire("save",c);a.hide();b.preventDefault();b.stopPropagation()};c.observe(a.link,"click",function(){c.hasClass(a.link,"wysihtml5-command-dialog-opened")&&setTimeout(function(){a.hide()},0)});c.observe(this.container,"keydown",function(c){var e=c.keyCode;
+e===b.ENTER_KEY&&d(c);e===b.ESCAPE_KEY&&a.hide()});c.delegate(this.container,"[data-wysihtml5-dialog-action=save]","click",d);c.delegate(this.container,"[data-wysihtml5-dialog-action=cancel]","click",function(b){a.fire("cancel");a.hide();b.preventDefault();b.stopPropagation()});for(var e=this.container.querySelectorAll("input, select, textarea"),f=0,h=e.length,i=function(){clearInterval(a.interval)};f<h;f++)c.observe(e[f],"change",i);this._observed=!0}},_serialize:function(){for(var a=this.elementToChange||
+{},b=this.container.querySelectorAll("[data-wysihtml5-dialog-field]"),c=b.length,f=0;f<c;f++)a[b[f].getAttribute("data-wysihtml5-dialog-field")]=b[f].value;return a},_interpolate:function(a){for(var b,c,f=document.querySelector(":focus"),h=this.container.querySelectorAll("[data-wysihtml5-dialog-field]"),i=h.length,g=0;g<i;g++)b=h[g],b!==f&&!(a&&"hidden"===b.type)&&(c=b.getAttribute("data-wysihtml5-dialog-field"),c=this.elementToChange?this.elementToChange[c]||"":b.defaultValue,b.value=c)},show:function(a){var b=
+this,e=this.container.querySelector("input, select, textarea");this.elementToChange=a;this._observe();this._interpolate();a&&(this.interval=setInterval(function(){b._interpolate(!0)},500));c.addClass(this.link,"wysihtml5-command-dialog-opened");this.container.style.display="";this.fire("show");if(e&&!a)try{e.focus()}catch(f){}},hide:function(){clearInterval(this.interval);this.elementToChange=null;c.removeClass(this.link,"wysihtml5-command-dialog-opened");this.container.style.display="none";this.fire("hide")}})})(wysihtml5);
+(function(b){var c=b.dom,a={position:"relative"},d={left:0,margin:0,opacity:0,overflow:"hidden",padding:0,position:"absolute",top:0,zIndex:1},e={cursor:"inherit",fontSize:"50px",height:"50px",marginTop:"-25px",outline:0,padding:0,position:"absolute",right:"-4px",top:"50%"},f={"x-webkit-speech":"",speech:""};b.toolbar.Speech=function(h,i){var g=document.createElement("input");if(b.browser.supportsSpeechApiOn(g)){var k=document.createElement("div");b.lang.object(d).merge({width:i.offsetWidth+"px",height:i.offsetHeight+
+"px"});c.insert(g).into(k);c.insert(k).into(i);c.setStyles(e).on(g);c.setAttributes(f).on(g);c.setStyles(d).on(k);c.setStyles(a).on(i);c.observe(g,"onwebkitspeechchange"in g?"webkitspeechchange":"speechchange",function(){h.execCommand("insertText",g.value);g.value=""});c.observe(g,"click",function(a){c.hasClass(i,"wysihtml5-command-disabled")&&a.preventDefault();a.stopPropagation()})}else i.style.display="none"}})(wysihtml5);
+(function(b){var c=b.dom;b.toolbar.Toolbar=Base.extend({constructor:function(a,c){this.editor=a;this.container="string"===typeof c?document.getElementById(c):c;this.composer=a.composer;this._getLinks("command");this._getLinks("action");this._observe();this.show();for(var e=this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"),f=e.length,h=0;h<f;h++)new b.toolbar.Speech(this,e[h])},_getLinks:function(a){for(var c=this[a+"Links"]=b.lang.array(this.container.querySelectorAll("a[data-wysihtml5-"+
+a+"]")).get(),e=c.length,f=0,h=this[a+"Mapping"]={},i,g,k,j;f<e;f++)i=c[f],g=i.getAttribute("data-wysihtml5-"+a),k=i.getAttribute("data-wysihtml5-"+a+"-value"),j=this._getDialog(i,g),h[g+":"+k]={link:i,name:g,value:k,dialog:j,state:!1}},_getDialog:function(a,c){var e=this,f=this.container.querySelector("[data-wysihtml5-dialog='"+c+"']"),h,i;f&&(h=new b.toolbar.Dialog(a,f),h.observe("show",function(){i=e.composer.selection.getBookmark();e.editor.fire("show:dialog",{command:c,dialogContainer:f,commandLink:a})}),
+h.observe("save",function(b){i&&e.composer.selection.setBookmark(i);e._execCommand(c,b);e.editor.fire("save:dialog",{command:c,dialogContainer:f,commandLink:a})}),h.observe("cancel",function(){e.editor.focus(!1);e.editor.fire("cancel:dialog",{command:c,dialogContainer:f,commandLink:a})}));return h},execCommand:function(a,b){if(!this.commandsDisabled){var c=this.commandMapping[a+":"+b];c&&c.dialog&&!c.state?c.dialog.show():this._execCommand(a,b)}},_execCommand:function(a,b){this.editor.focus(!1);this.composer.commands.exec(a,
+b);this._updateLinkStates()},execAction:function(a){var b=this.editor;switch(a){case "change_view":b.currentView===b.textarea?b.fire("change_view","composer"):b.fire("change_view","textarea")}},_observe:function(){for(var a=this,b=this.editor,e=this.container,f=this.commandLinks.concat(this.actionLinks),h=f.length,i=0;i<h;i++)c.setAttributes({href:"javascript:;",unselectable:"on"}).on(f[i]);c.delegate(e,"[data-wysihtml5-command]","mousedown",function(a){a.preventDefault()});c.delegate(e,"[data-wysihtml5-command]",
+"click",function(b){var c=this.getAttribute("data-wysihtml5-command"),d=this.getAttribute("data-wysihtml5-command-value");a.execCommand(c,d);b.preventDefault()});c.delegate(e,"[data-wysihtml5-action]","click",function(b){var c=this.getAttribute("data-wysihtml5-action");a.execAction(c);b.preventDefault()});b.observe("focus:composer",function(){a.bookmark=null;clearInterval(a.interval);a.interval=setInterval(function(){a._updateLinkStates()},500)});b.observe("blur:composer",function(){clearInterval(a.interval)});
+b.observe("destroy:composer",function(){clearInterval(a.interval)});b.observe("change_view",function(b){setTimeout(function(){a.commandsDisabled="composer"!==b;a._updateLinkStates();a.commandsDisabled?c.addClass(e,"wysihtml5-commands-disabled"):c.removeClass(e,"wysihtml5-commands-disabled")},0)})},_updateLinkStates:function(){var a=this.commandMapping,d,e,f;for(d in a)if(f=a[d],this.commandsDisabled?(e=!1,c.removeClass(f.link,"wysihtml5-command-active"),f.dialog&&f.dialog.hide()):(e=this.composer.commands.state(f.name,
+f.value),b.lang.object(e).isArray()&&(e=1===e.length?e[0]:!0),c.removeClass(f.link,"wysihtml5-command-disabled")),f.state!==e)(f.state=e)?(c.addClass(f.link,"wysihtml5-command-active"),f.dialog&&("object"===typeof e?f.dialog.show(e):f.dialog.hide())):(c.removeClass(f.link,"wysihtml5-command-active"),f.dialog&&f.dialog.hide())},show:function(){this.container.style.display=""},hide:function(){this.container.style.display="none"}})})(wysihtml5);
+(function(b){var c={name:void 0,style:!0,toolbar:void 0,autoLink:!0,parserRules:{tags:{br:{},span:{},div:{},p:{}},classes:{}},parser:b.dom.parse,composerClassName:"wysihtml5-editor",bodyClassName:"wysihtml5-supported",stylesheets:[],placeholderText:void 0,allowObjectResizing:!0,supportTouchDevices:!0};b.Editor=b.lang.Dispatcher.extend({constructor:function(a,d){this.textareaElement="string"===typeof a?document.getElementById(a):a;this.config=b.lang.object({}).merge(c).merge(d).get();this.currentView=
+this.textarea=new b.views.Textarea(this,this.textareaElement,this.config);this._isCompatible=b.browser.supported();if(!this._isCompatible||!this.config.supportTouchDevices&&b.browser.isTouchDevice()){var e=this;setTimeout(function(){e.fire("beforeload").fire("load")},0)}else{b.dom.addClass(document.body,this.config.bodyClassName);this.currentView=this.composer=new b.views.Composer(this,this.textareaElement,this.config);"function"===typeof this.config.parser&&this._initParser();this.observe("beforeload",
+function(){this.synchronizer=new b.views.Synchronizer(this,this.textarea,this.composer);this.config.toolbar&&(this.toolbar=new b.toolbar.Toolbar(this,this.config.toolbar))});try{console.log("Heya! This page is using wysihtml5 for rich text editing. Check out https://github.com/xing/wysihtml5")}catch(f){}}},isCompatible:function(){return this._isCompatible},clear:function(){this.currentView.clear();return this},getValue:function(a){return this.currentView.getValue(a)},setValue:function(a,b){if(!a)return this.clear();
+this.currentView.setValue(a,b);return this},focus:function(a){this.currentView.focus(a);return this},disable:function(){this.currentView.disable();return this},enable:function(){this.currentView.enable();return this},isEmpty:function(){return this.currentView.isEmpty()},hasPlaceholderSet:function(){return this.currentView.hasPlaceholderSet()},parse:function(a){var c=this.config.parser(a,this.config.parserRules,this.composer.sandbox.getDocument(),!0);"object"===typeof a&&b.quirks.redraw(a);return c},
+_initParser:function(){this.observe("paste:composer",function(){var a=this;a.composer.selection.executeAndRestore(function(){b.quirks.cleanPastedHTML(a.composer.element);a.parse(a.composer.element)},!0)});this.observe("paste:textarea",function(){this.textarea.setValue(this.parse(this.textarea.getValue()))})}})})(wysihtml5);