liquid_feedback_frontend

diff static/wysihtml/wysihtml.js @ 1309:32cc544d5a5b

Cumulative patch for upcoming frontend version 4
author bsw/jbe
date Sun Jul 15 14:07:29 2018 +0200 (2018-07-15)
parents
children
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/static/wysihtml/wysihtml.js	Sun Jul 15 14:07:29 2018 +0200
     1.3 @@ -0,0 +1,16102 @@
     1.4 +/**
     1.5 + * @license wysihtml v0.6.0-beta1
     1.6 + * https://github.com/Voog/wysihtml
     1.7 + *
     1.8 + * Author: Christopher Blum (https://github.com/tiff)
     1.9 + * Secondary author of extended features: Oliver Pulges (https://github.com/pulges)
    1.10 + *
    1.11 + * Copyright (C) 2012 XING AG
    1.12 + * Licensed under the MIT license (MIT)
    1.13 + *
    1.14 + */
    1.15 +var wysihtml = {
    1.16 +  version: '0.6.0-beta1',
    1.17 +
    1.18 +  // namespaces
    1.19 +  commands:   {},
    1.20 +  dom:        {},
    1.21 +  quirks:     {},
    1.22 +  toolbar:    {},
    1.23 +  lang:       {},
    1.24 +  selection:  {},
    1.25 +  views:      {},
    1.26 +
    1.27 +  editorExtenders: [],
    1.28 +  extendEditor: function(extender) {
    1.29 +    this.editorExtenders.push(extender);
    1.30 +  },
    1.31 +
    1.32 +  INVISIBLE_SPACE: '\uFEFF',
    1.33 +  INVISIBLE_SPACE_REG_EXP: /\uFEFF/g,
    1.34 +
    1.35 +  VOID_ELEMENTS: 'area, base, br, col, embed, hr, img, input, keygen, link, meta, param, source, track, wbr',
    1.36 +  PERMITTED_PHRASING_CONTENT_ONLY: 'h1, h2, h3, h4, h5, h6, p, pre',
    1.37 +
    1.38 +  EMPTY_FUNCTION: function() {},
    1.39 +
    1.40 +  ELEMENT_NODE: 1,
    1.41 +  TEXT_NODE:    3,
    1.42 +
    1.43 +  BACKSPACE_KEY:  8,
    1.44 +  ENTER_KEY:      13,
    1.45 +  ESCAPE_KEY:     27,
    1.46 +  SPACE_KEY:      32,
    1.47 +  TAB_KEY:        9,
    1.48 +  DELETE_KEY:     46
    1.49 +};
    1.50 +
    1.51 +wysihtml.polyfills = function(win, doc) {
    1.52 +
    1.53 +  var methods = {
    1.54 +
    1.55 +    // Safary has a bug of not restoring selection after node.normalize correctly.
    1.56 +    // Detects the misbegaviour and patches it
    1.57 +    normalizeHasCaretError: function() {
    1.58 +      if ("createRange" in doc && "getSelection" in win) {
    1.59 +        var originalTarget,
    1.60 +            scrollTop = window.pageYOffset,
    1.61 +            scrollLeft = window.pageXOffset,
    1.62 +            e = doc.createElement('div'),
    1.63 +            t1 = doc.createTextNode('a'),
    1.64 +            t2 = doc.createTextNode('a'),
    1.65 +            t3 = doc.createTextNode('a'),
    1.66 +            r = doc.createRange(),
    1.67 +            s, ret;
    1.68 +
    1.69 +        if (document.activeElement) {
    1.70 +          if (document.activeElement.nodeType === 1 && ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].indexOf(document.activeElement.nodeName) > -1) {
    1.71 +            originalTarget = {
    1.72 +              type: 'form',
    1.73 +              node: document.activeElement,
    1.74 +              start: document.activeElement.selectionStart,
    1.75 +              end: document.activeElement.selectionEnd,
    1.76 +            };
    1.77 +          } else {
    1.78 +            s = win.getSelection();
    1.79 +            if (s && s.anchorNode) {
    1.80 +              originalTarget = {
    1.81 +                type: 'range',
    1.82 +                anchorNode: s.anchorNode,
    1.83 +                anchorOffset: s.anchorOffset,
    1.84 +                focusNode: s.focusNode,
    1.85 +                focusOffset: s.focusOffset
    1.86 +              };
    1.87 +            }
    1.88 +          }
    1.89 +        }
    1.90 +
    1.91 +        e.setAttribute('contenteditable', 'true');
    1.92 +        e.appendChild(t1);
    1.93 +        e.appendChild(t2);
    1.94 +        e.appendChild(t3);
    1.95 +        doc.body.appendChild(e);
    1.96 +        r.setStart(t2, 1);
    1.97 +        r.setEnd(t2, 1);
    1.98 +
    1.99 +        s = win.getSelection();
   1.100 +        s.removeAllRanges();
   1.101 +        s.addRange(r);
   1.102 +        e.normalize();
   1.103 +        s = win.getSelection();
   1.104 +
   1.105 +        ret = (e.childNodes.length !== 1 || s.anchorNode !== e.firstChild || s.anchorOffset !== 2);
   1.106 +        e.parentNode.removeChild(e);
   1.107 +        s.removeAllRanges();
   1.108 +
   1.109 +        if (originalTarget) {
   1.110 +          if (originalTarget.type === 'form') {
   1.111 +            // The selection parameters are not present for all form elements
   1.112 +            if (typeof originalTarget.start !== 'undefined' && typeof originalTarget.end !== 'undefined') {
   1.113 +              originalTarget.node.setSelectionRange(originalTarget.start, originalTarget.end);
   1.114 +            }
   1.115 +            originalTarget.node.focus();
   1.116 +          } else if (originalTarget.type === 'range') {
   1.117 +            r = doc.createRange();
   1.118 +            r.setStart(originalTarget.anchorNode, originalTarget.anchorOffset);
   1.119 +            r.setEnd(originalTarget.focusNode, originalTarget.focusOffset);
   1.120 +            s.addRange(r);
   1.121 +          }
   1.122 +        }
   1.123 +
   1.124 +        if (scrollTop !== window.pageYOffset || scrollLeft !== window.pageXOffset) {
   1.125 +          win.scrollTo(scrollLeft, scrollTop);
   1.126 +        }
   1.127 +
   1.128 +        return ret;
   1.129 +      }
   1.130 +    },
   1.131 +
   1.132 +    apply: function() {
   1.133 +      // closest, matches, and remove polyfill
   1.134 +      // https://github.com/jonathantneal/closest
   1.135 +      (function (ELEMENT) {
   1.136 +        ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector || function matches(selector) {
   1.137 +          var
   1.138 +          element = this,
   1.139 +          elements = (element.document || element.ownerDocument).querySelectorAll(selector),
   1.140 +          index = 0;
   1.141 +
   1.142 +          while (elements[index] && elements[index] !== element) {
   1.143 +            ++index;
   1.144 +          }
   1.145 +
   1.146 +          return elements[index] ? true : false;
   1.147 +        };
   1.148 +
   1.149 +        ELEMENT.closest = ELEMENT.closest || function closest(selector) {
   1.150 +          var element = this;
   1.151 +
   1.152 +          while (element) {
   1.153 +            if (element.matches(selector)) {
   1.154 +              break;
   1.155 +            }
   1.156 +
   1.157 +            element = element.parentElement;
   1.158 +          }
   1.159 +
   1.160 +          return element;
   1.161 +        };
   1.162 +
   1.163 +        ELEMENT.remove = ELEMENT.remove || function remove() {
   1.164 +          if (this.parentNode) {
   1.165 +            this.parentNode.removeChild(this);
   1.166 +          }
   1.167 +        };
   1.168 +
   1.169 +      }(win.Element.prototype));
   1.170 +
   1.171 +      if (!('classList' in doc.documentElement) && win.Object.defineProperty && typeof win.HTMLElement !== 'undefined') {
   1.172 +        win.Object.defineProperty(win.HTMLElement.prototype, 'classList', {
   1.173 +          get: function() {
   1.174 +            var self = this;
   1.175 +            function update(fn) {
   1.176 +              return function(value) {
   1.177 +                var classes = self.className.split(/\s+/),
   1.178 +                    index = classes.indexOf(value);
   1.179 +
   1.180 +                fn(classes, index, value);
   1.181 +                self.className = classes.join(' ');
   1.182 +              };
   1.183 +            }
   1.184 +
   1.185 +            var ret = {
   1.186 +                add: update(function(classes, index, value) {
   1.187 +                  ~index || classes.push(value);
   1.188 +                }),
   1.189 +
   1.190 +                remove: update(function(classes, index) {
   1.191 +                  ~index && classes.splice(index, 1);
   1.192 +                }),
   1.193 +
   1.194 +                toggle: update(function(classes, index, value) {
   1.195 +                  ~index ? classes.splice(index, 1) : classes.push(value);
   1.196 +                }),
   1.197 +
   1.198 +                contains: function(value) {
   1.199 +                  return !!~self.className.split(/\s+/).indexOf(value);
   1.200 +                },
   1.201 +
   1.202 +                item: function(i) {
   1.203 +                  return self.className.split(/\s+/)[i] || null;
   1.204 +                }
   1.205 +              };
   1.206 +
   1.207 +            win.Object.defineProperty(ret, 'length', {
   1.208 +              get: function() {
   1.209 +                return self.className.split(/\s+/).length;
   1.210 +              }
   1.211 +            });
   1.212 +
   1.213 +            return ret;
   1.214 +          }
   1.215 +        });
   1.216 +      }
   1.217 +
   1.218 +      var getTextNodes = function(node){
   1.219 +        var all = [];
   1.220 +        for (node=node.firstChild;node;node=node.nextSibling){
   1.221 +          if (node.nodeType == 3) {
   1.222 +              all.push(node);
   1.223 +          } else {
   1.224 +            all = all.concat(getTextNodes(node));
   1.225 +          }
   1.226 +        }
   1.227 +        return all;
   1.228 +      };
   1.229 +
   1.230 +      var isInDom = function(node) {
   1.231 +        var doc = node.ownerDocument,
   1.232 +            n = node;
   1.233 +
   1.234 +        do {
   1.235 +          if (n === doc) {
   1.236 +            return true;
   1.237 +          }
   1.238 +          n = n.parentNode;
   1.239 +        } while(n);
   1.240 +
   1.241 +        return false;
   1.242 +      };
   1.243 +
   1.244 +      var normalizeFix = function() {
   1.245 +        var f = win.Node.prototype.normalize;
   1.246 +        var nf = function() {
   1.247 +          var texts = getTextNodes(this),
   1.248 +              s = this.ownerDocument.defaultView.getSelection(),
   1.249 +              anode = s.anchorNode,
   1.250 +              aoffset = s.anchorOffset,
   1.251 +              aelement = anode && anode.nodeType === 1 && anode.childNodes.length > 0 ? anode.childNodes[aoffset] : undefined,
   1.252 +              fnode = s.focusNode,
   1.253 +              foffset = s.focusOffset,
   1.254 +              felement = fnode && fnode.nodeType === 1 && foffset > 0 ? fnode.childNodes[foffset -1] : undefined,
   1.255 +              r = this.ownerDocument.createRange(),
   1.256 +              prevTxt = texts.shift(),
   1.257 +              curText = prevTxt ? texts.shift() : null;
   1.258 +
   1.259 +          if (felement && felement.nodeType === 3) {
   1.260 +            fnode = felement;
   1.261 +            foffset = felement.nodeValue.length;
   1.262 +            felement = undefined;
   1.263 +          }
   1.264 +
   1.265 +          if (aelement && aelement.nodeType === 3) {
   1.266 +            anode = aelement;
   1.267 +            aoffset = 0;
   1.268 +            aelement = undefined;
   1.269 +          }
   1.270 +
   1.271 +          if ((anode === fnode && foffset < aoffset) || (anode !== fnode && (anode.compareDocumentPosition(fnode) & win.Node.DOCUMENT_POSITION_PRECEDING) && !(anode.compareDocumentPosition(fnode) & win.Node.DOCUMENT_POSITION_CONTAINS))) {
   1.272 +            fnode = [anode, anode = fnode][0];
   1.273 +            foffset = [aoffset, aoffset = foffset][0];
   1.274 +          }
   1.275 +
   1.276 +          while(prevTxt && curText) {
   1.277 +            if (curText.previousSibling && curText.previousSibling === prevTxt) {
   1.278 +              if (anode === curText) {
   1.279 +                anode = prevTxt;
   1.280 +                aoffset = prevTxt.nodeValue.length +  aoffset;
   1.281 +              }
   1.282 +              if (fnode === curText) {
   1.283 +                fnode = prevTxt;
   1.284 +                foffset = prevTxt.nodeValue.length +  foffset;
   1.285 +              }
   1.286 +              prevTxt.nodeValue = prevTxt.nodeValue + curText.nodeValue;
   1.287 +              curText.parentNode.removeChild(curText);
   1.288 +              curText = texts.shift();
   1.289 +            } else {
   1.290 +              prevTxt = curText;
   1.291 +              curText = texts.shift();
   1.292 +            }
   1.293 +          }
   1.294 +
   1.295 +          if (felement) {
   1.296 +            foffset = Array.prototype.indexOf.call(felement.parentNode.childNodes, felement) + 1;
   1.297 +          }
   1.298 +
   1.299 +          if (aelement) {
   1.300 +            aoffset = Array.prototype.indexOf.call(aelement.parentNode.childNodes, aelement);
   1.301 +          }
   1.302 +
   1.303 +          if (isInDom(this) && anode && anode.parentNode && fnode && fnode.parentNode) {
   1.304 +            r.setStart(anode, aoffset);
   1.305 +            r.setEnd(fnode, foffset);
   1.306 +            s.removeAllRanges();
   1.307 +            s.addRange(r);
   1.308 +          }
   1.309 +        };
   1.310 +        win.Node.prototype.normalize = nf;
   1.311 +      };
   1.312 +
   1.313 +      var F = function() {
   1.314 +        win.removeEventListener("load", F);
   1.315 +        if ("Node" in win && "normalize" in win.Node.prototype && methods.normalizeHasCaretError()) {
   1.316 +          normalizeFix();
   1.317 +        }
   1.318 +      };
   1.319 +
   1.320 +      if (doc.readyState !== "complete") {
   1.321 +        win.addEventListener("load", F);
   1.322 +      } else {
   1.323 +        F();
   1.324 +      }
   1.325 +
   1.326 +      // CustomEvent for ie9 and up
   1.327 +      function nativeCustomEventSupported() {
   1.328 +        try {
   1.329 +          var p = new win.CustomEvent('cat', {detail: {foo: 'bar'}});
   1.330 +          return  'cat' === p.type && 'bar' === p.detail.foo;
   1.331 +        } catch (e) {}
   1.332 +        return false;
   1.333 +      }
   1.334 +
   1.335 +      // Polyfills CustomEvent object for IE9 and up
   1.336 +      (function() {
   1.337 +        if (!nativeCustomEventSupported() && "CustomEvent" in win) {
   1.338 +          function CustomEvent(event, params) {
   1.339 +            params = params || {bubbles: false, cancelable: false, detail: undefined};
   1.340 +            var evt = doc.createEvent('CustomEvent');
   1.341 +            evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
   1.342 +            return evt;
   1.343 +          }
   1.344 +          CustomEvent.prototype = win.Event.prototype;
   1.345 +          win.CustomEvent = CustomEvent;
   1.346 +        }
   1.347 +      })();
   1.348 +    }
   1.349 +  }
   1.350 +
   1.351 +  return methods;
   1.352 +};
   1.353 +
   1.354 +wysihtml.polyfills(window, document).apply();
   1.355 +
   1.356 +/*
   1.357 +	Base.js, version 1.1a
   1.358 +	Copyright 2006-2010, Dean Edwards
   1.359 +	License: http://www.opensource.org/licenses/mit-license.php
   1.360 +*/
   1.361 +
   1.362 +var Base = function() {
   1.363 +	// dummy
   1.364 +};
   1.365 +
   1.366 +Base.extend = function(_instance, _static) { // subclass
   1.367 +	var extend = Base.prototype.extend;
   1.368 +	
   1.369 +	// build the prototype
   1.370 +	Base._prototyping = true;
   1.371 +	var proto = new this;
   1.372 +	extend.call(proto, _instance);
   1.373 +  proto.base = function() {
   1.374 +    // call this method from any other method to invoke that method's ancestor
   1.375 +  };
   1.376 +	delete Base._prototyping;
   1.377 +	
   1.378 +	// create the wrapper for the constructor function
   1.379 +	//var constructor = proto.constructor.valueOf(); //-dean
   1.380 +	var constructor = proto.constructor;
   1.381 +	var klass = proto.constructor = function() {
   1.382 +		if (!Base._prototyping) {
   1.383 +			if (this._constructing || this.constructor == klass) { // instantiation
   1.384 +				this._constructing = true;
   1.385 +				constructor.apply(this, arguments);
   1.386 +				delete this._constructing;
   1.387 +			} else if (arguments[0] != null) { // casting
   1.388 +				return (arguments[0].extend || extend).call(arguments[0], proto);
   1.389 +			}
   1.390 +		}
   1.391 +	};
   1.392 +	
   1.393 +	// build the class interface
   1.394 +	klass.ancestor = this;
   1.395 +	klass.extend = this.extend;
   1.396 +	klass.forEach = this.forEach;
   1.397 +	klass.implement = this.implement;
   1.398 +	klass.prototype = proto;
   1.399 +	klass.toString = this.toString;
   1.400 +	klass.valueOf = function(type) {
   1.401 +		//return (type == "object") ? klass : constructor; //-dean
   1.402 +		return (type == "object") ? klass : constructor.valueOf();
   1.403 +	};
   1.404 +	extend.call(klass, _static);
   1.405 +	// class initialisation
   1.406 +	if (typeof klass.init == "function") klass.init();
   1.407 +	return klass;
   1.408 +};
   1.409 +
   1.410 +Base.prototype = {	
   1.411 +	extend: function(source, value) {
   1.412 +		if (arguments.length > 1) { // extending with a name/value pair
   1.413 +			var ancestor = this[source];
   1.414 +			if (ancestor && (typeof value == "function") && // overriding a method?
   1.415 +				// the valueOf() comparison is to avoid circular references
   1.416 +				(!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
   1.417 +				/\bbase\b/.test(value)) {
   1.418 +				// get the underlying method
   1.419 +				var method = value.valueOf();
   1.420 +				// override
   1.421 +				value = function() {
   1.422 +					var previous = this.base || Base.prototype.base;
   1.423 +					this.base = ancestor;
   1.424 +					var returnValue = method.apply(this, arguments);
   1.425 +					this.base = previous;
   1.426 +					return returnValue;
   1.427 +				};
   1.428 +				// point to the underlying method
   1.429 +				value.valueOf = function(type) {
   1.430 +					return (type == "object") ? value : method;
   1.431 +				};
   1.432 +				value.toString = Base.toString;
   1.433 +			}
   1.434 +			this[source] = value;
   1.435 +		} else if (source) { // extending with an object literal
   1.436 +			var extend = Base.prototype.extend;
   1.437 +			// if this object has a customised extend method then use it
   1.438 +			if (!Base._prototyping && typeof this != "function") {
   1.439 +				extend = this.extend || extend;
   1.440 +			}
   1.441 +			var proto = {toSource: null};
   1.442 +			// do the "toString" and other methods manually
   1.443 +			var hidden = ["constructor", "toString", "valueOf"];
   1.444 +			// if we are prototyping then include the constructor
   1.445 +			var i = Base._prototyping ? 0 : 1;
   1.446 +			while (key = hidden[i++]) {
   1.447 +				if (source[key] != proto[key]) {
   1.448 +					extend.call(this, key, source[key]);
   1.449 +
   1.450 +				}
   1.451 +			}
   1.452 +			// copy each of the source object's properties to this object
   1.453 +			for (var key in source) {
   1.454 +				if (!proto[key]) extend.call(this, key, source[key]);
   1.455 +			}
   1.456 +		}
   1.457 +		return this;
   1.458 +	}
   1.459 +};
   1.460 +
   1.461 +// initialise
   1.462 +Base = Base.extend({
   1.463 +	constructor: function() {
   1.464 +		this.extend(arguments[0]);
   1.465 +	}
   1.466 +}, {
   1.467 +	ancestor: Object,
   1.468 +	version: "1.1",
   1.469 +	
   1.470 +	forEach: function(object, block, context) {
   1.471 +		for (var key in object) {
   1.472 +			if (this.prototype[key] === undefined) {
   1.473 +				block.call(context, object[key], key, object);
   1.474 +			}
   1.475 +		}
   1.476 +	},
   1.477 +		
   1.478 +	implement: function() {
   1.479 +		for (var i = 0; i < arguments.length; i++) {
   1.480 +			if (typeof arguments[i] == "function") {
   1.481 +				// if it's a function, call it
   1.482 +				arguments[i](this.prototype);
   1.483 +			} else {
   1.484 +				// add the interface using the extend method
   1.485 +				this.prototype.extend(arguments[i]);
   1.486 +			}
   1.487 +		}
   1.488 +		return this;
   1.489 +	},
   1.490 +	
   1.491 +	toString: function() {
   1.492 +		return String(this.valueOf());
   1.493 +	}
   1.494 +});
   1.495 +/**
   1.496 + * Rangy, a cross-browser JavaScript range and selection library
   1.497 + * https://github.com/timdown/rangy
   1.498 + *
   1.499 + * Copyright 2015, Tim Down
   1.500 + * Licensed under the MIT license.
   1.501 + * Version: 1.3.1-dev
   1.502 + * Build date: 20 May 2015
   1.503 + *
   1.504 + * NOTE: UMD wrapper removed manually for bundling (Oliver Pulges)
   1.505 + */
   1.506 +var rangy;
   1.507 +
   1.508 +(function() {
   1.509 +    var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";
   1.510 +
   1.511 +    // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START
   1.512 +    // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.
   1.513 +    var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
   1.514 +        "commonAncestorContainer"];
   1.515 +
   1.516 +    // Minimal set of methods required for DOM Level 2 Range compliance
   1.517 +    var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
   1.518 +        "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
   1.519 +        "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];
   1.520 +
   1.521 +    var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];
   1.522 +
   1.523 +    // Subset of TextRange's full set of methods that we're interested in
   1.524 +    var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select",
   1.525 +        "setEndPoint", "getBoundingClientRect"];
   1.526 +
   1.527 +    /*----------------------------------------------------------------------------------------------------------------*/
   1.528 +
   1.529 +    // Trio of functions taken from Peter Michaux's article:
   1.530 +    // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
   1.531 +    function isHostMethod(o, p) {
   1.532 +        var t = typeof o[p];
   1.533 +        return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
   1.534 +    }
   1.535 +
   1.536 +    function isHostObject(o, p) {
   1.537 +        return !!(typeof o[p] == OBJECT && o[p]);
   1.538 +    }
   1.539 +
   1.540 +    function isHostProperty(o, p) {
   1.541 +        return typeof o[p] != UNDEFINED;
   1.542 +    }
   1.543 +
   1.544 +    // Creates a convenience function to save verbose repeated calls to tests functions
   1.545 +    function createMultiplePropertyTest(testFunc) {
   1.546 +        return function(o, props) {
   1.547 +            var i = props.length;
   1.548 +            while (i--) {
   1.549 +                if (!testFunc(o, props[i])) {
   1.550 +                    return false;
   1.551 +                }
   1.552 +            }
   1.553 +            return true;
   1.554 +        };
   1.555 +    }
   1.556 +
   1.557 +    // Next trio of functions are a convenience to save verbose repeated calls to previous two functions
   1.558 +    var areHostMethods = createMultiplePropertyTest(isHostMethod);
   1.559 +    var areHostObjects = createMultiplePropertyTest(isHostObject);
   1.560 +    var areHostProperties = createMultiplePropertyTest(isHostProperty);
   1.561 +
   1.562 +    function isTextRange(range) {
   1.563 +        return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
   1.564 +    }
   1.565 +
   1.566 +    function getBody(doc) {
   1.567 +        return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
   1.568 +    }
   1.569 +
   1.570 +    var forEach = [].forEach ?
   1.571 +        function(arr, func) {
   1.572 +            arr.forEach(func);
   1.573 +        } :
   1.574 +        function(arr, func) {
   1.575 +            for (var i = 0, len = arr.length; i < len; ++i) {
   1.576 +                func(arr[i], i);
   1.577 +            }
   1.578 +        };
   1.579 +
   1.580 +    var modules = {};
   1.581 +
   1.582 +    var isBrowser = (typeof window != UNDEFINED && typeof document != UNDEFINED);
   1.583 +
   1.584 +    var util = {
   1.585 +        isHostMethod: isHostMethod,
   1.586 +        isHostObject: isHostObject,
   1.587 +        isHostProperty: isHostProperty,
   1.588 +        areHostMethods: areHostMethods,
   1.589 +        areHostObjects: areHostObjects,
   1.590 +        areHostProperties: areHostProperties,
   1.591 +        isTextRange: isTextRange,
   1.592 +        getBody: getBody,
   1.593 +        forEach: forEach
   1.594 +    };
   1.595 +
   1.596 +    var api = {
   1.597 +        version: "1.3.1-dev",
   1.598 +        initialized: false,
   1.599 +        isBrowser: isBrowser,
   1.600 +        supported: true,
   1.601 +        util: util,
   1.602 +        features: {},
   1.603 +        modules: modules,
   1.604 +        config: {
   1.605 +            alertOnFail: false,
   1.606 +            alertOnWarn: false,
   1.607 +            preferTextRange: false,
   1.608 +            autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize
   1.609 +        }
   1.610 +    };
   1.611 +
   1.612 +    function consoleLog(msg) {
   1.613 +        if (typeof console != UNDEFINED && isHostMethod(console, "log")) {
   1.614 +            console.log(msg);
   1.615 +        }
   1.616 +    }
   1.617 +
   1.618 +    function alertOrLog(msg, shouldAlert) {
   1.619 +        if (isBrowser && shouldAlert) {
   1.620 +            alert(msg);
   1.621 +        } else  {
   1.622 +            consoleLog(msg);
   1.623 +        }
   1.624 +    }
   1.625 +
   1.626 +    function fail(reason) {
   1.627 +        api.initialized = true;
   1.628 +        api.supported = false;
   1.629 +        alertOrLog("Rangy is not supported in this environment. Reason: " + reason, api.config.alertOnFail);
   1.630 +    }
   1.631 +
   1.632 +    api.fail = fail;
   1.633 +
   1.634 +    function warn(msg) {
   1.635 +        alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn);
   1.636 +    }
   1.637 +
   1.638 +    api.warn = warn;
   1.639 +
   1.640 +    // Add utility extend() method
   1.641 +    var extend;
   1.642 +    if ({}.hasOwnProperty) {
   1.643 +        util.extend = extend = function(obj, props, deep) {
   1.644 +            var o, p;
   1.645 +            for (var i in props) {
   1.646 +                if (props.hasOwnProperty(i)) {
   1.647 +                    o = obj[i];
   1.648 +                    p = props[i];
   1.649 +                    if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
   1.650 +                        extend(o, p, true);
   1.651 +                    }
   1.652 +                    obj[i] = p;
   1.653 +                }
   1.654 +            }
   1.655 +            // Special case for toString, which does not show up in for...in loops in IE <= 8
   1.656 +            if (props.hasOwnProperty("toString")) {
   1.657 +                obj.toString = props.toString;
   1.658 +            }
   1.659 +            return obj;
   1.660 +        };
   1.661 +
   1.662 +        util.createOptions = function(optionsParam, defaults) {
   1.663 +            var options = {};
   1.664 +            extend(options, defaults);
   1.665 +            if (optionsParam) {
   1.666 +                extend(options, optionsParam);
   1.667 +            }
   1.668 +            return options;
   1.669 +        };
   1.670 +    } else {
   1.671 +        fail("hasOwnProperty not supported");
   1.672 +    }
   1.673 +
   1.674 +    // Test whether we're in a browser and bail out if not
   1.675 +    if (!isBrowser) {
   1.676 +        fail("Rangy can only run in a browser");
   1.677 +    }
   1.678 +
   1.679 +    // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
   1.680 +    (function() {
   1.681 +        var toArray;
   1.682 +
   1.683 +        if (isBrowser) {
   1.684 +            var el = document.createElement("div");
   1.685 +            el.appendChild(document.createElement("span"));
   1.686 +            var slice = [].slice;
   1.687 +            try {
   1.688 +                if (slice.call(el.childNodes, 0)[0].nodeType == 1) {
   1.689 +                    toArray = function(arrayLike) {
   1.690 +                        return slice.call(arrayLike, 0);
   1.691 +                    };
   1.692 +                }
   1.693 +            } catch (e) {}
   1.694 +        }
   1.695 +
   1.696 +        if (!toArray) {
   1.697 +            toArray = function(arrayLike) {
   1.698 +                var arr = [];
   1.699 +                for (var i = 0, len = arrayLike.length; i < len; ++i) {
   1.700 +                    arr[i] = arrayLike[i];
   1.701 +                }
   1.702 +                return arr;
   1.703 +            };
   1.704 +        }
   1.705 +
   1.706 +        util.toArray = toArray;
   1.707 +    })();
   1.708 +
   1.709 +    // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or
   1.710 +    // normalization of event properties
   1.711 +    var addListener;
   1.712 +    if (isBrowser) {
   1.713 +        if (isHostMethod(document, "addEventListener")) {
   1.714 +            addListener = function(obj, eventType, listener) {
   1.715 +                obj.addEventListener(eventType, listener, false);
   1.716 +            };
   1.717 +        } else if (isHostMethod(document, "attachEvent")) {
   1.718 +            addListener = function(obj, eventType, listener) {
   1.719 +                obj.attachEvent("on" + eventType, listener);
   1.720 +            };
   1.721 +        } else {
   1.722 +            fail("Document does not have required addEventListener or attachEvent method");
   1.723 +        }
   1.724 +
   1.725 +        util.addListener = addListener;
   1.726 +    }
   1.727 +
   1.728 +    var initListeners = [];
   1.729 +
   1.730 +    function getErrorDesc(ex) {
   1.731 +        return ex.message || ex.description || String(ex);
   1.732 +    }
   1.733 +
   1.734 +    // Initialization
   1.735 +    function init() {
   1.736 +        if (!isBrowser || api.initialized) {
   1.737 +            return;
   1.738 +        }
   1.739 +        var testRange;
   1.740 +        var implementsDomRange = false, implementsTextRange = false;
   1.741 +
   1.742 +        // First, perform basic feature tests
   1.743 +
   1.744 +        if (isHostMethod(document, "createRange")) {
   1.745 +            testRange = document.createRange();
   1.746 +            if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
   1.747 +                implementsDomRange = true;
   1.748 +            }
   1.749 +        }
   1.750 +
   1.751 +        var body = getBody(document);
   1.752 +        if (!body || body.nodeName.toLowerCase() != "body") {
   1.753 +            fail("No body element found");
   1.754 +            return;
   1.755 +        }
   1.756 +
   1.757 +        if (body && isHostMethod(body, "createTextRange")) {
   1.758 +            testRange = body.createTextRange();
   1.759 +            if (isTextRange(testRange)) {
   1.760 +                implementsTextRange = true;
   1.761 +            }
   1.762 +        }
   1.763 +
   1.764 +        if (!implementsDomRange && !implementsTextRange) {
   1.765 +            fail("Neither Range nor TextRange are available");
   1.766 +            return;
   1.767 +        }
   1.768 +
   1.769 +        api.initialized = true;
   1.770 +        api.features = {
   1.771 +            implementsDomRange: implementsDomRange,
   1.772 +            implementsTextRange: implementsTextRange
   1.773 +        };
   1.774 +
   1.775 +        // Initialize modules
   1.776 +        var module, errorMessage;
   1.777 +        for (var moduleName in modules) {
   1.778 +            if ( (module = modules[moduleName]) instanceof Module ) {
   1.779 +                module.init(module, api);
   1.780 +            }
   1.781 +        }
   1.782 +
   1.783 +        // Call init listeners
   1.784 +        for (var i = 0, len = initListeners.length; i < len; ++i) {
   1.785 +            try {
   1.786 +                initListeners[i](api);
   1.787 +            } catch (ex) {
   1.788 +                errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex);
   1.789 +                consoleLog(errorMessage);
   1.790 +            }
   1.791 +        }
   1.792 +    }
   1.793 +
   1.794 +    function deprecationNotice(deprecated, replacement, module) {
   1.795 +        if (module) {
   1.796 +            deprecated += " in module " + module.name;
   1.797 +        }
   1.798 +        api.warn("DEPRECATED: " + deprecated + " is deprecated. Please use " +
   1.799 +        replacement + " instead.");
   1.800 +    }
   1.801 +
   1.802 +    function createAliasForDeprecatedMethod(owner, deprecated, replacement, module) {
   1.803 +        owner[deprecated] = function() {
   1.804 +            deprecationNotice(deprecated, replacement, module);
   1.805 +            return owner[replacement].apply(owner, util.toArray(arguments));
   1.806 +        };
   1.807 +    }
   1.808 +
   1.809 +    util.deprecationNotice = deprecationNotice;
   1.810 +    util.createAliasForDeprecatedMethod = createAliasForDeprecatedMethod;
   1.811 +
   1.812 +    // Allow external scripts to initialize this library in case it's loaded after the document has loaded
   1.813 +    api.init = init;
   1.814 +
   1.815 +    // Execute listener immediately if already initialized
   1.816 +    api.addInitListener = function(listener) {
   1.817 +        if (api.initialized) {
   1.818 +            listener(api);
   1.819 +        } else {
   1.820 +            initListeners.push(listener);
   1.821 +        }
   1.822 +    };
   1.823 +
   1.824 +    var shimListeners = [];
   1.825 +
   1.826 +    api.addShimListener = function(listener) {
   1.827 +        shimListeners.push(listener);
   1.828 +    };
   1.829 +
   1.830 +    function shim(win) {
   1.831 +        win = win || window;
   1.832 +        init();
   1.833 +
   1.834 +        // Notify listeners
   1.835 +        for (var i = 0, len = shimListeners.length; i < len; ++i) {
   1.836 +            shimListeners[i](win);
   1.837 +        }
   1.838 +    }
   1.839 +
   1.840 +    if (isBrowser) {
   1.841 +        api.shim = api.createMissingNativeApi = shim;
   1.842 +        createAliasForDeprecatedMethod(api, "createMissingNativeApi", "shim");
   1.843 +    }
   1.844 +
   1.845 +    function Module(name, dependencies, initializer) {
   1.846 +        this.name = name;
   1.847 +        this.dependencies = dependencies;
   1.848 +        this.initialized = false;
   1.849 +        this.supported = false;
   1.850 +        this.initializer = initializer;
   1.851 +    }
   1.852 +
   1.853 +    Module.prototype = {
   1.854 +        init: function() {
   1.855 +            var requiredModuleNames = this.dependencies || [];
   1.856 +            for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) {
   1.857 +                moduleName = requiredModuleNames[i];
   1.858 +
   1.859 +                requiredModule = modules[moduleName];
   1.860 +                if (!requiredModule || !(requiredModule instanceof Module)) {
   1.861 +                    throw new Error("required module '" + moduleName + "' not found");
   1.862 +                }
   1.863 +
   1.864 +                requiredModule.init();
   1.865 +
   1.866 +                if (!requiredModule.supported) {
   1.867 +                    throw new Error("required module '" + moduleName + "' not supported");
   1.868 +                }
   1.869 +            }
   1.870 +
   1.871 +            // Now run initializer
   1.872 +            this.initializer(this);
   1.873 +        },
   1.874 +
   1.875 +        fail: function(reason) {
   1.876 +            this.initialized = true;
   1.877 +            this.supported = false;
   1.878 +            throw new Error(reason);
   1.879 +        },
   1.880 +
   1.881 +        warn: function(msg) {
   1.882 +            api.warn("Module " + this.name + ": " + msg);
   1.883 +        },
   1.884 +
   1.885 +        deprecationNotice: function(deprecated, replacement) {
   1.886 +            api.warn("DEPRECATED: " + deprecated + " in module " + this.name + " is deprecated. Please use " +
   1.887 +                replacement + " instead");
   1.888 +        },
   1.889 +
   1.890 +        createError: function(msg) {
   1.891 +            return new Error("Error in Rangy " + this.name + " module: " + msg);
   1.892 +        }
   1.893 +    };
   1.894 +
   1.895 +    function createModule(name, dependencies, initFunc) {
   1.896 +        var newModule = new Module(name, dependencies, function(module) {
   1.897 +            if (!module.initialized) {
   1.898 +                module.initialized = true;
   1.899 +                try {
   1.900 +                    initFunc(api, module);
   1.901 +                    module.supported = true;
   1.902 +                } catch (ex) {
   1.903 +                    var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);
   1.904 +                    consoleLog(errorMessage);
   1.905 +                    if (ex.stack) {
   1.906 +                        consoleLog(ex.stack);
   1.907 +                    }
   1.908 +                }
   1.909 +            }
   1.910 +        });
   1.911 +        modules[name] = newModule;
   1.912 +        return newModule;
   1.913 +    }
   1.914 +
   1.915 +    api.createModule = function(name) {
   1.916 +        // Allow 2 or 3 arguments (second argument is an optional array of dependencies)
   1.917 +        var initFunc, dependencies;
   1.918 +        if (arguments.length == 2) {
   1.919 +            initFunc = arguments[1];
   1.920 +            dependencies = [];
   1.921 +        } else {
   1.922 +            initFunc = arguments[2];
   1.923 +            dependencies = arguments[1];
   1.924 +        }
   1.925 +
   1.926 +        var module = createModule(name, dependencies, initFunc);
   1.927 +
   1.928 +        // Initialize the module immediately if the core is already initialized
   1.929 +        if (api.initialized && api.supported) {
   1.930 +            module.init();
   1.931 +        }
   1.932 +    };
   1.933 +
   1.934 +    api.createCoreModule = function(name, dependencies, initFunc) {
   1.935 +        createModule(name, dependencies, initFunc);
   1.936 +    };
   1.937 +
   1.938 +    /*----------------------------------------------------------------------------------------------------------------*/
   1.939 +
   1.940 +    // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately
   1.941 +
   1.942 +    function RangePrototype() {}
   1.943 +    api.RangePrototype = RangePrototype;
   1.944 +    api.rangePrototype = new RangePrototype();
   1.945 +
   1.946 +    function SelectionPrototype() {}
   1.947 +    api.selectionPrototype = new SelectionPrototype();
   1.948 +
   1.949 +    /*----------------------------------------------------------------------------------------------------------------*/
   1.950 +
   1.951 +    // DOM utility methods used by Rangy
   1.952 +    api.createCoreModule("DomUtil", [], function(api, module) {
   1.953 +        var UNDEF = "undefined";
   1.954 +        var util = api.util;
   1.955 +        var getBody = util.getBody;
   1.956 +
   1.957 +        // Perform feature tests
   1.958 +        if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
   1.959 +            module.fail("document missing a Node creation method");
   1.960 +        }
   1.961 +
   1.962 +        if (!util.isHostMethod(document, "getElementsByTagName")) {
   1.963 +            module.fail("document missing getElementsByTagName method");
   1.964 +        }
   1.965 +
   1.966 +        var el = document.createElement("div");
   1.967 +        if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
   1.968 +                !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
   1.969 +            module.fail("Incomplete Element implementation");
   1.970 +        }
   1.971 +
   1.972 +        // innerHTML is required for Range's createContextualFragment method
   1.973 +        if (!util.isHostProperty(el, "innerHTML")) {
   1.974 +            module.fail("Element is missing innerHTML property");
   1.975 +        }
   1.976 +
   1.977 +        var textNode = document.createTextNode("test");
   1.978 +        if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
   1.979 +                !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
   1.980 +                !util.areHostProperties(textNode, ["data"]))) {
   1.981 +            module.fail("Incomplete Text Node implementation");
   1.982 +        }
   1.983 +
   1.984 +        /*----------------------------------------------------------------------------------------------------------------*/
   1.985 +
   1.986 +        // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
   1.987 +        // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
   1.988 +        // contains just the document as a single element and the value searched for is the document.
   1.989 +        var arrayContains = /*Array.prototype.indexOf ?
   1.990 +            function(arr, val) {
   1.991 +                return arr.indexOf(val) > -1;
   1.992 +            }:*/
   1.993 +
   1.994 +            function(arr, val) {
   1.995 +                var i = arr.length;
   1.996 +                while (i--) {
   1.997 +                    if (arr[i] === val) {
   1.998 +                        return true;
   1.999 +                    }
  1.1000 +                }
  1.1001 +                return false;
  1.1002 +            };
  1.1003 +
  1.1004 +        // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
  1.1005 +        function isHtmlNamespace(node) {
  1.1006 +            var ns;
  1.1007 +            return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
  1.1008 +        }
  1.1009 +
  1.1010 +        function parentElement(node) {
  1.1011 +            var parent = node.parentNode;
  1.1012 +            return (parent.nodeType == 1) ? parent : null;
  1.1013 +        }
  1.1014 +
  1.1015 +        function getNodeIndex(node) {
  1.1016 +            var i = 0;
  1.1017 +            while( (node = node.previousSibling) ) {
  1.1018 +                ++i;
  1.1019 +            }
  1.1020 +            return i;
  1.1021 +        }
  1.1022 +
  1.1023 +        function getNodeLength(node) {
  1.1024 +            switch (node.nodeType) {
  1.1025 +                case 7:
  1.1026 +                case 10:
  1.1027 +                    return 0;
  1.1028 +                case 3:
  1.1029 +                case 8:
  1.1030 +                    return node.length;
  1.1031 +                default:
  1.1032 +                    return node.childNodes.length;
  1.1033 +            }
  1.1034 +        }
  1.1035 +
  1.1036 +        function getCommonAncestor(node1, node2) {
  1.1037 +            var ancestors = [], n;
  1.1038 +            for (n = node1; n; n = n.parentNode) {
  1.1039 +                ancestors.push(n);
  1.1040 +            }
  1.1041 +
  1.1042 +            for (n = node2; n; n = n.parentNode) {
  1.1043 +                if (arrayContains(ancestors, n)) {
  1.1044 +                    return n;
  1.1045 +                }
  1.1046 +            }
  1.1047 +
  1.1048 +            return null;
  1.1049 +        }
  1.1050 +
  1.1051 +        function isAncestorOf(ancestor, descendant, selfIsAncestor) {
  1.1052 +            var n = selfIsAncestor ? descendant : descendant.parentNode;
  1.1053 +            while (n) {
  1.1054 +                if (n === ancestor) {
  1.1055 +                    return true;
  1.1056 +                } else {
  1.1057 +                    n = n.parentNode;
  1.1058 +                }
  1.1059 +            }
  1.1060 +            return false;
  1.1061 +        }
  1.1062 +
  1.1063 +        function isOrIsAncestorOf(ancestor, descendant) {
  1.1064 +            return isAncestorOf(ancestor, descendant, true);
  1.1065 +        }
  1.1066 +
  1.1067 +        function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
  1.1068 +            var p, n = selfIsAncestor ? node : node.parentNode;
  1.1069 +            while (n) {
  1.1070 +                p = n.parentNode;
  1.1071 +                if (p === ancestor) {
  1.1072 +                    return n;
  1.1073 +                }
  1.1074 +                n = p;
  1.1075 +            }
  1.1076 +            return null;
  1.1077 +        }
  1.1078 +
  1.1079 +        function isCharacterDataNode(node) {
  1.1080 +            var t = node.nodeType;
  1.1081 +            return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
  1.1082 +        }
  1.1083 +
  1.1084 +        function isTextOrCommentNode(node) {
  1.1085 +            if (!node) {
  1.1086 +                return false;
  1.1087 +            }
  1.1088 +            var t = node.nodeType;
  1.1089 +            return t == 3 || t == 8 ; // Text or Comment
  1.1090 +        }
  1.1091 +
  1.1092 +        function insertAfter(node, precedingNode) {
  1.1093 +            var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
  1.1094 +            if (nextNode) {
  1.1095 +                parent.insertBefore(node, nextNode);
  1.1096 +            } else {
  1.1097 +                parent.appendChild(node);
  1.1098 +            }
  1.1099 +            return node;
  1.1100 +        }
  1.1101 +
  1.1102 +        // Note that we cannot use splitText() because it is bugridden in IE 9.
  1.1103 +        function splitDataNode(node, index, positionsToPreserve) {
  1.1104 +            var newNode = node.cloneNode(false);
  1.1105 +            newNode.deleteData(0, index);
  1.1106 +            node.deleteData(index, node.length - index);
  1.1107 +            insertAfter(newNode, node);
  1.1108 +
  1.1109 +            // Preserve positions
  1.1110 +            if (positionsToPreserve) {
  1.1111 +                for (var i = 0, position; position = positionsToPreserve[i++]; ) {
  1.1112 +                    // Handle case where position was inside the portion of node after the split point
  1.1113 +                    if (position.node == node && position.offset > index) {
  1.1114 +                        position.node = newNode;
  1.1115 +                        position.offset -= index;
  1.1116 +                    }
  1.1117 +                    // Handle the case where the position is a node offset within node's parent
  1.1118 +                    else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {
  1.1119 +                        ++position.offset;
  1.1120 +                    }
  1.1121 +                }
  1.1122 +            }
  1.1123 +            return newNode;
  1.1124 +        }
  1.1125 +
  1.1126 +        function getDocument(node) {
  1.1127 +            if (node.nodeType == 9) {
  1.1128 +                return node;
  1.1129 +            } else if (typeof node.ownerDocument != UNDEF) {
  1.1130 +                return node.ownerDocument;
  1.1131 +            } else if (typeof node.document != UNDEF) {
  1.1132 +                return node.document;
  1.1133 +            } else if (node.parentNode) {
  1.1134 +                return getDocument(node.parentNode);
  1.1135 +            } else {
  1.1136 +                throw module.createError("getDocument: no document found for node");
  1.1137 +            }
  1.1138 +        }
  1.1139 +
  1.1140 +        function getWindow(node) {
  1.1141 +            var doc = getDocument(node);
  1.1142 +            if (typeof doc.defaultView != UNDEF) {
  1.1143 +                return doc.defaultView;
  1.1144 +            } else if (typeof doc.parentWindow != UNDEF) {
  1.1145 +                return doc.parentWindow;
  1.1146 +            } else {
  1.1147 +                throw module.createError("Cannot get a window object for node");
  1.1148 +            }
  1.1149 +        }
  1.1150 +
  1.1151 +        function getIframeDocument(iframeEl) {
  1.1152 +            if (typeof iframeEl.contentDocument != UNDEF) {
  1.1153 +                return iframeEl.contentDocument;
  1.1154 +            } else if (typeof iframeEl.contentWindow != UNDEF) {
  1.1155 +                return iframeEl.contentWindow.document;
  1.1156 +            } else {
  1.1157 +                throw module.createError("getIframeDocument: No Document object found for iframe element");
  1.1158 +            }
  1.1159 +        }
  1.1160 +
  1.1161 +        function getIframeWindow(iframeEl) {
  1.1162 +            if (typeof iframeEl.contentWindow != UNDEF) {
  1.1163 +                return iframeEl.contentWindow;
  1.1164 +            } else if (typeof iframeEl.contentDocument != UNDEF) {
  1.1165 +                return iframeEl.contentDocument.defaultView;
  1.1166 +            } else {
  1.1167 +                throw module.createError("getIframeWindow: No Window object found for iframe element");
  1.1168 +            }
  1.1169 +        }
  1.1170 +
  1.1171 +        // This looks bad. Is it worth it?
  1.1172 +        function isWindow(obj) {
  1.1173 +            return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document");
  1.1174 +        }
  1.1175 +
  1.1176 +        function getContentDocument(obj, module, methodName) {
  1.1177 +            var doc;
  1.1178 +
  1.1179 +            if (!obj) {
  1.1180 +                doc = document;
  1.1181 +            }
  1.1182 +
  1.1183 +            // Test if a DOM node has been passed and obtain a document object for it if so
  1.1184 +            else if (util.isHostProperty(obj, "nodeType")) {
  1.1185 +                doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") ?
  1.1186 +                    getIframeDocument(obj) : getDocument(obj);
  1.1187 +            }
  1.1188 +
  1.1189 +            // Test if the doc parameter appears to be a Window object
  1.1190 +            else if (isWindow(obj)) {
  1.1191 +                doc = obj.document;
  1.1192 +            }
  1.1193 +
  1.1194 +            if (!doc) {
  1.1195 +                throw module.createError(methodName + "(): Parameter must be a Window object or DOM node");
  1.1196 +            }
  1.1197 +
  1.1198 +            return doc;
  1.1199 +        }
  1.1200 +
  1.1201 +        function getRootContainer(node) {
  1.1202 +            var parent;
  1.1203 +            while ( (parent = node.parentNode) ) {
  1.1204 +                node = parent;
  1.1205 +            }
  1.1206 +            return node;
  1.1207 +        }
  1.1208 +
  1.1209 +        function comparePoints(nodeA, offsetA, nodeB, offsetB) {
  1.1210 +            // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
  1.1211 +            var nodeC, root, childA, childB, n;
  1.1212 +            if (nodeA == nodeB) {
  1.1213 +                // Case 1: nodes are the same
  1.1214 +                return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
  1.1215 +            } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
  1.1216 +                // Case 2: node C (container B or an ancestor) is a child node of A
  1.1217 +                return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
  1.1218 +            } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
  1.1219 +                // Case 3: node C (container A or an ancestor) is a child node of B
  1.1220 +                return getNodeIndex(nodeC) < offsetB  ? -1 : 1;
  1.1221 +            } else {
  1.1222 +                root = getCommonAncestor(nodeA, nodeB);
  1.1223 +                if (!root) {
  1.1224 +                    throw new Error("comparePoints error: nodes have no common ancestor");
  1.1225 +                }
  1.1226 +
  1.1227 +                // Case 4: containers are siblings or descendants of siblings
  1.1228 +                childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
  1.1229 +                childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
  1.1230 +
  1.1231 +                if (childA === childB) {
  1.1232 +                    // This shouldn't be possible
  1.1233 +                    throw module.createError("comparePoints got to case 4 and childA and childB are the same!");
  1.1234 +                } else {
  1.1235 +                    n = root.firstChild;
  1.1236 +                    while (n) {
  1.1237 +                        if (n === childA) {
  1.1238 +                            return -1;
  1.1239 +                        } else if (n === childB) {
  1.1240 +                            return 1;
  1.1241 +                        }
  1.1242 +                        n = n.nextSibling;
  1.1243 +                    }
  1.1244 +                }
  1.1245 +            }
  1.1246 +        }
  1.1247 +
  1.1248 +        /*----------------------------------------------------------------------------------------------------------------*/
  1.1249 +
  1.1250 +        // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
  1.1251 +        var crashyTextNodes = false;
  1.1252 +
  1.1253 +        function isBrokenNode(node) {
  1.1254 +            var n;
  1.1255 +            try {
  1.1256 +                n = node.parentNode;
  1.1257 +                return false;
  1.1258 +            } catch (e) {
  1.1259 +                return true;
  1.1260 +            }
  1.1261 +        }
  1.1262 +
  1.1263 +        (function() {
  1.1264 +            var el = document.createElement("b");
  1.1265 +            el.innerHTML = "1";
  1.1266 +            var textNode = el.firstChild;
  1.1267 +            el.innerHTML = "<br />";
  1.1268 +            crashyTextNodes = isBrokenNode(textNode);
  1.1269 +
  1.1270 +            api.features.crashyTextNodes = crashyTextNodes;
  1.1271 +        })();
  1.1272 +
  1.1273 +        /*----------------------------------------------------------------------------------------------------------------*/
  1.1274 +
  1.1275 +        function inspectNode(node) {
  1.1276 +            if (!node) {
  1.1277 +                return "[No node]";
  1.1278 +            }
  1.1279 +            if (crashyTextNodes && isBrokenNode(node)) {
  1.1280 +                return "[Broken node]";
  1.1281 +            }
  1.1282 +            if (isCharacterDataNode(node)) {
  1.1283 +                return '"' + node.data + '"';
  1.1284 +            }
  1.1285 +            if (node.nodeType == 1) {
  1.1286 +                var idAttr = node.id ? ' id="' + node.id + '"' : "";
  1.1287 +                return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]";
  1.1288 +            }
  1.1289 +            return node.nodeName;
  1.1290 +        }
  1.1291 +
  1.1292 +        function fragmentFromNodeChildren(node) {
  1.1293 +            var fragment = getDocument(node).createDocumentFragment(), child;
  1.1294 +            while ( (child = node.firstChild) ) {
  1.1295 +                fragment.appendChild(child);
  1.1296 +            }
  1.1297 +            return fragment;
  1.1298 +        }
  1.1299 +
  1.1300 +        var getComputedStyleProperty;
  1.1301 +        if (typeof window.getComputedStyle != UNDEF) {
  1.1302 +            getComputedStyleProperty = function(el, propName) {
  1.1303 +                return getWindow(el).getComputedStyle(el, null)[propName];
  1.1304 +            };
  1.1305 +        } else if (typeof document.documentElement.currentStyle != UNDEF) {
  1.1306 +            getComputedStyleProperty = function(el, propName) {
  1.1307 +                return el.currentStyle ? el.currentStyle[propName] : "";
  1.1308 +            };
  1.1309 +        } else {
  1.1310 +            module.fail("No means of obtaining computed style properties found");
  1.1311 +        }
  1.1312 +
  1.1313 +        function createTestElement(doc, html, contentEditable) {
  1.1314 +            var body = getBody(doc);
  1.1315 +            var el = doc.createElement("div");
  1.1316 +            el.contentEditable = "" + !!contentEditable;
  1.1317 +            if (html) {
  1.1318 +                el.innerHTML = html;
  1.1319 +            }
  1.1320 +
  1.1321 +            // Insert the test element at the start of the body to prevent scrolling to the bottom in iOS (issue #292)
  1.1322 +            var bodyFirstChild = body.firstChild;
  1.1323 +            if (bodyFirstChild) {
  1.1324 +                body.insertBefore(el, bodyFirstChild);
  1.1325 +            } else {
  1.1326 +                body.appendChild(el);
  1.1327 +            }
  1.1328 +
  1.1329 +            return el;
  1.1330 +        }
  1.1331 +
  1.1332 +        function removeNode(node) {
  1.1333 +            return node.parentNode.removeChild(node);
  1.1334 +        }
  1.1335 +
  1.1336 +        function NodeIterator(root) {
  1.1337 +            this.root = root;
  1.1338 +            this._next = root;
  1.1339 +        }
  1.1340 +
  1.1341 +        NodeIterator.prototype = {
  1.1342 +            _current: null,
  1.1343 +
  1.1344 +            hasNext: function() {
  1.1345 +                return !!this._next;
  1.1346 +            },
  1.1347 +
  1.1348 +            next: function() {
  1.1349 +                var n = this._current = this._next;
  1.1350 +                var child, next;
  1.1351 +                if (this._current) {
  1.1352 +                    child = n.firstChild;
  1.1353 +                    if (child) {
  1.1354 +                        this._next = child;
  1.1355 +                    } else {
  1.1356 +                        next = null;
  1.1357 +                        while ((n !== this.root) && !(next = n.nextSibling)) {
  1.1358 +                            n = n.parentNode;
  1.1359 +                        }
  1.1360 +                        this._next = next;
  1.1361 +                    }
  1.1362 +                }
  1.1363 +                return this._current;
  1.1364 +            },
  1.1365 +
  1.1366 +            detach: function() {
  1.1367 +                this._current = this._next = this.root = null;
  1.1368 +            }
  1.1369 +        };
  1.1370 +
  1.1371 +        function createIterator(root) {
  1.1372 +            return new NodeIterator(root);
  1.1373 +        }
  1.1374 +
  1.1375 +        function DomPosition(node, offset) {
  1.1376 +            this.node = node;
  1.1377 +            this.offset = offset;
  1.1378 +        }
  1.1379 +
  1.1380 +        DomPosition.prototype = {
  1.1381 +            equals: function(pos) {
  1.1382 +                return !!pos && this.node === pos.node && this.offset == pos.offset;
  1.1383 +            },
  1.1384 +
  1.1385 +            inspect: function() {
  1.1386 +                return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
  1.1387 +            },
  1.1388 +
  1.1389 +            toString: function() {
  1.1390 +                return this.inspect();
  1.1391 +            }
  1.1392 +        };
  1.1393 +
  1.1394 +        function DOMException(codeName) {
  1.1395 +            this.code = this[codeName];
  1.1396 +            this.codeName = codeName;
  1.1397 +            this.message = "DOMException: " + this.codeName;
  1.1398 +        }
  1.1399 +
  1.1400 +        DOMException.prototype = {
  1.1401 +            INDEX_SIZE_ERR: 1,
  1.1402 +            HIERARCHY_REQUEST_ERR: 3,
  1.1403 +            WRONG_DOCUMENT_ERR: 4,
  1.1404 +            NO_MODIFICATION_ALLOWED_ERR: 7,
  1.1405 +            NOT_FOUND_ERR: 8,
  1.1406 +            NOT_SUPPORTED_ERR: 9,
  1.1407 +            INVALID_STATE_ERR: 11,
  1.1408 +            INVALID_NODE_TYPE_ERR: 24
  1.1409 +        };
  1.1410 +
  1.1411 +        DOMException.prototype.toString = function() {
  1.1412 +            return this.message;
  1.1413 +        };
  1.1414 +
  1.1415 +        api.dom = {
  1.1416 +            arrayContains: arrayContains,
  1.1417 +            isHtmlNamespace: isHtmlNamespace,
  1.1418 +            parentElement: parentElement,
  1.1419 +            getNodeIndex: getNodeIndex,
  1.1420 +            getNodeLength: getNodeLength,
  1.1421 +            getCommonAncestor: getCommonAncestor,
  1.1422 +            isAncestorOf: isAncestorOf,
  1.1423 +            isOrIsAncestorOf: isOrIsAncestorOf,
  1.1424 +            getClosestAncestorIn: getClosestAncestorIn,
  1.1425 +            isCharacterDataNode: isCharacterDataNode,
  1.1426 +            isTextOrCommentNode: isTextOrCommentNode,
  1.1427 +            insertAfter: insertAfter,
  1.1428 +            splitDataNode: splitDataNode,
  1.1429 +            getDocument: getDocument,
  1.1430 +            getWindow: getWindow,
  1.1431 +            getIframeWindow: getIframeWindow,
  1.1432 +            getIframeDocument: getIframeDocument,
  1.1433 +            getBody: getBody,
  1.1434 +            isWindow: isWindow,
  1.1435 +            getContentDocument: getContentDocument,
  1.1436 +            getRootContainer: getRootContainer,
  1.1437 +            comparePoints: comparePoints,
  1.1438 +            isBrokenNode: isBrokenNode,
  1.1439 +            inspectNode: inspectNode,
  1.1440 +            getComputedStyleProperty: getComputedStyleProperty,
  1.1441 +            createTestElement: createTestElement,
  1.1442 +            removeNode: removeNode,
  1.1443 +            fragmentFromNodeChildren: fragmentFromNodeChildren,
  1.1444 +            createIterator: createIterator,
  1.1445 +            DomPosition: DomPosition
  1.1446 +        };
  1.1447 +
  1.1448 +        api.DOMException = DOMException;
  1.1449 +    });
  1.1450 +
  1.1451 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.1452 +
  1.1453 +    // Pure JavaScript implementation of DOM Range
  1.1454 +    api.createCoreModule("DomRange", ["DomUtil"], function(api, module) {
  1.1455 +        var dom = api.dom;
  1.1456 +        var util = api.util;
  1.1457 +        var DomPosition = dom.DomPosition;
  1.1458 +        var DOMException = api.DOMException;
  1.1459 +
  1.1460 +        var isCharacterDataNode = dom.isCharacterDataNode;
  1.1461 +        var getNodeIndex = dom.getNodeIndex;
  1.1462 +        var isOrIsAncestorOf = dom.isOrIsAncestorOf;
  1.1463 +        var getDocument = dom.getDocument;
  1.1464 +        var comparePoints = dom.comparePoints;
  1.1465 +        var splitDataNode = dom.splitDataNode;
  1.1466 +        var getClosestAncestorIn = dom.getClosestAncestorIn;
  1.1467 +        var getNodeLength = dom.getNodeLength;
  1.1468 +        var arrayContains = dom.arrayContains;
  1.1469 +        var getRootContainer = dom.getRootContainer;
  1.1470 +        var crashyTextNodes = api.features.crashyTextNodes;
  1.1471 +
  1.1472 +        var removeNode = dom.removeNode;
  1.1473 +
  1.1474 +        /*----------------------------------------------------------------------------------------------------------------*/
  1.1475 +
  1.1476 +        // Utility functions
  1.1477 +
  1.1478 +        function isNonTextPartiallySelected(node, range) {
  1.1479 +            return (node.nodeType != 3) &&
  1.1480 +                   (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));
  1.1481 +        }
  1.1482 +
  1.1483 +        function getRangeDocument(range) {
  1.1484 +            return range.document || getDocument(range.startContainer);
  1.1485 +        }
  1.1486 +
  1.1487 +        function getRangeRoot(range) {
  1.1488 +            return getRootContainer(range.startContainer);
  1.1489 +        }
  1.1490 +
  1.1491 +        function getBoundaryBeforeNode(node) {
  1.1492 +            return new DomPosition(node.parentNode, getNodeIndex(node));
  1.1493 +        }
  1.1494 +
  1.1495 +        function getBoundaryAfterNode(node) {
  1.1496 +            return new DomPosition(node.parentNode, getNodeIndex(node) + 1);
  1.1497 +        }
  1.1498 +
  1.1499 +        function insertNodeAtPosition(node, n, o) {
  1.1500 +            var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
  1.1501 +            if (isCharacterDataNode(n)) {
  1.1502 +                if (o == n.length) {
  1.1503 +                    dom.insertAfter(node, n);
  1.1504 +                } else {
  1.1505 +                    n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o));
  1.1506 +                }
  1.1507 +            } else if (o >= n.childNodes.length) {
  1.1508 +                n.appendChild(node);
  1.1509 +            } else {
  1.1510 +                n.insertBefore(node, n.childNodes[o]);
  1.1511 +            }
  1.1512 +            return firstNodeInserted;
  1.1513 +        }
  1.1514 +
  1.1515 +        function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {
  1.1516 +            assertRangeValid(rangeA);
  1.1517 +            assertRangeValid(rangeB);
  1.1518 +
  1.1519 +            if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {
  1.1520 +                throw new DOMException("WRONG_DOCUMENT_ERR");
  1.1521 +            }
  1.1522 +
  1.1523 +            var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),
  1.1524 +                endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);
  1.1525 +
  1.1526 +            return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
  1.1527 +        }
  1.1528 +
  1.1529 +        function cloneSubtree(iterator) {
  1.1530 +            var partiallySelected;
  1.1531 +            for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
  1.1532 +                partiallySelected = iterator.isPartiallySelectedSubtree();
  1.1533 +                node = node.cloneNode(!partiallySelected);
  1.1534 +                if (partiallySelected) {
  1.1535 +                    subIterator = iterator.getSubtreeIterator();
  1.1536 +                    node.appendChild(cloneSubtree(subIterator));
  1.1537 +                    subIterator.detach();
  1.1538 +                }
  1.1539 +
  1.1540 +                if (node.nodeType == 10) { // DocumentType
  1.1541 +                    throw new DOMException("HIERARCHY_REQUEST_ERR");
  1.1542 +                }
  1.1543 +                frag.appendChild(node);
  1.1544 +            }
  1.1545 +            return frag;
  1.1546 +        }
  1.1547 +
  1.1548 +        function iterateSubtree(rangeIterator, func, iteratorState) {
  1.1549 +            var it, n;
  1.1550 +            iteratorState = iteratorState || { stop: false };
  1.1551 +            for (var node, subRangeIterator; node = rangeIterator.next(); ) {
  1.1552 +                if (rangeIterator.isPartiallySelectedSubtree()) {
  1.1553 +                    if (func(node) === false) {
  1.1554 +                        iteratorState.stop = true;
  1.1555 +                        return;
  1.1556 +                    } else {
  1.1557 +                        // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of
  1.1558 +                        // the node selected by the Range.
  1.1559 +                        subRangeIterator = rangeIterator.getSubtreeIterator();
  1.1560 +                        iterateSubtree(subRangeIterator, func, iteratorState);
  1.1561 +                        subRangeIterator.detach();
  1.1562 +                        if (iteratorState.stop) {
  1.1563 +                            return;
  1.1564 +                        }
  1.1565 +                    }
  1.1566 +                } else {
  1.1567 +                    // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
  1.1568 +                    // descendants
  1.1569 +                    it = dom.createIterator(node);
  1.1570 +                    while ( (n = it.next()) ) {
  1.1571 +                        if (func(n) === false) {
  1.1572 +                            iteratorState.stop = true;
  1.1573 +                            return;
  1.1574 +                        }
  1.1575 +                    }
  1.1576 +                }
  1.1577 +            }
  1.1578 +        }
  1.1579 +
  1.1580 +        function deleteSubtree(iterator) {
  1.1581 +            var subIterator;
  1.1582 +            while (iterator.next()) {
  1.1583 +                if (iterator.isPartiallySelectedSubtree()) {
  1.1584 +                    subIterator = iterator.getSubtreeIterator();
  1.1585 +                    deleteSubtree(subIterator);
  1.1586 +                    subIterator.detach();
  1.1587 +                } else {
  1.1588 +                    iterator.remove();
  1.1589 +                }
  1.1590 +            }
  1.1591 +        }
  1.1592 +
  1.1593 +        function extractSubtree(iterator) {
  1.1594 +            for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
  1.1595 +
  1.1596 +                if (iterator.isPartiallySelectedSubtree()) {
  1.1597 +                    node = node.cloneNode(false);
  1.1598 +                    subIterator = iterator.getSubtreeIterator();
  1.1599 +                    node.appendChild(extractSubtree(subIterator));
  1.1600 +                    subIterator.detach();
  1.1601 +                } else {
  1.1602 +                    iterator.remove();
  1.1603 +                }
  1.1604 +                if (node.nodeType == 10) { // DocumentType
  1.1605 +                    throw new DOMException("HIERARCHY_REQUEST_ERR");
  1.1606 +                }
  1.1607 +                frag.appendChild(node);
  1.1608 +            }
  1.1609 +            return frag;
  1.1610 +        }
  1.1611 +
  1.1612 +        function getNodesInRange(range, nodeTypes, filter) {
  1.1613 +            var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
  1.1614 +            var filterExists = !!filter;
  1.1615 +            if (filterNodeTypes) {
  1.1616 +                regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
  1.1617 +            }
  1.1618 +
  1.1619 +            var nodes = [];
  1.1620 +            iterateSubtree(new RangeIterator(range, false), function(node) {
  1.1621 +                if (filterNodeTypes && !regex.test(node.nodeType)) {
  1.1622 +                    return;
  1.1623 +                }
  1.1624 +                if (filterExists && !filter(node)) {
  1.1625 +                    return;
  1.1626 +                }
  1.1627 +                // Don't include a boundary container if it is a character data node and the range does not contain any
  1.1628 +                // of its character data. See issue 190.
  1.1629 +                var sc = range.startContainer;
  1.1630 +                if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) {
  1.1631 +                    return;
  1.1632 +                }
  1.1633 +
  1.1634 +                var ec = range.endContainer;
  1.1635 +                if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) {
  1.1636 +                    return;
  1.1637 +                }
  1.1638 +
  1.1639 +                nodes.push(node);
  1.1640 +            });
  1.1641 +            return nodes;
  1.1642 +        }
  1.1643 +
  1.1644 +        function inspect(range) {
  1.1645 +            var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
  1.1646 +            return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
  1.1647 +                    dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
  1.1648 +        }
  1.1649 +
  1.1650 +        /*----------------------------------------------------------------------------------------------------------------*/
  1.1651 +
  1.1652 +        // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
  1.1653 +
  1.1654 +        function RangeIterator(range, clonePartiallySelectedTextNodes) {
  1.1655 +            this.range = range;
  1.1656 +            this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
  1.1657 +
  1.1658 +
  1.1659 +            if (!range.collapsed) {
  1.1660 +                this.sc = range.startContainer;
  1.1661 +                this.so = range.startOffset;
  1.1662 +                this.ec = range.endContainer;
  1.1663 +                this.eo = range.endOffset;
  1.1664 +                var root = range.commonAncestorContainer;
  1.1665 +
  1.1666 +                if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
  1.1667 +                    this.isSingleCharacterDataNode = true;
  1.1668 +                    this._first = this._last = this._next = this.sc;
  1.1669 +                } else {
  1.1670 +                    this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?
  1.1671 +                        this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
  1.1672 +                    this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?
  1.1673 +                        this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
  1.1674 +                }
  1.1675 +            }
  1.1676 +        }
  1.1677 +
  1.1678 +        RangeIterator.prototype = {
  1.1679 +            _current: null,
  1.1680 +            _next: null,
  1.1681 +            _first: null,
  1.1682 +            _last: null,
  1.1683 +            isSingleCharacterDataNode: false,
  1.1684 +
  1.1685 +            reset: function() {
  1.1686 +                this._current = null;
  1.1687 +                this._next = this._first;
  1.1688 +            },
  1.1689 +
  1.1690 +            hasNext: function() {
  1.1691 +                return !!this._next;
  1.1692 +            },
  1.1693 +
  1.1694 +            next: function() {
  1.1695 +                // Move to next node
  1.1696 +                var current = this._current = this._next;
  1.1697 +                if (current) {
  1.1698 +                    this._next = (current !== this._last) ? current.nextSibling : null;
  1.1699 +
  1.1700 +                    // Check for partially selected text nodes
  1.1701 +                    if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
  1.1702 +                        if (current === this.ec) {
  1.1703 +                            (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
  1.1704 +                        }
  1.1705 +                        if (this._current === this.sc) {
  1.1706 +                            (current = current.cloneNode(true)).deleteData(0, this.so);
  1.1707 +                        }
  1.1708 +                    }
  1.1709 +                }
  1.1710 +
  1.1711 +                return current;
  1.1712 +            },
  1.1713 +
  1.1714 +            remove: function() {
  1.1715 +                var current = this._current, start, end;
  1.1716 +
  1.1717 +                if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
  1.1718 +                    start = (current === this.sc) ? this.so : 0;
  1.1719 +                    end = (current === this.ec) ? this.eo : current.length;
  1.1720 +                    if (start != end) {
  1.1721 +                        current.deleteData(start, end - start);
  1.1722 +                    }
  1.1723 +                } else {
  1.1724 +                    if (current.parentNode) {
  1.1725 +                        removeNode(current);
  1.1726 +                    } else {
  1.1727 +                    }
  1.1728 +                }
  1.1729 +            },
  1.1730 +
  1.1731 +            // Checks if the current node is partially selected
  1.1732 +            isPartiallySelectedSubtree: function() {
  1.1733 +                var current = this._current;
  1.1734 +                return isNonTextPartiallySelected(current, this.range);
  1.1735 +            },
  1.1736 +
  1.1737 +            getSubtreeIterator: function() {
  1.1738 +                var subRange;
  1.1739 +                if (this.isSingleCharacterDataNode) {
  1.1740 +                    subRange = this.range.cloneRange();
  1.1741 +                    subRange.collapse(false);
  1.1742 +                } else {
  1.1743 +                    subRange = new Range(getRangeDocument(this.range));
  1.1744 +                    var current = this._current;
  1.1745 +                    var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current);
  1.1746 +
  1.1747 +                    if (isOrIsAncestorOf(current, this.sc)) {
  1.1748 +                        startContainer = this.sc;
  1.1749 +                        startOffset = this.so;
  1.1750 +                    }
  1.1751 +                    if (isOrIsAncestorOf(current, this.ec)) {
  1.1752 +                        endContainer = this.ec;
  1.1753 +                        endOffset = this.eo;
  1.1754 +                    }
  1.1755 +
  1.1756 +                    updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
  1.1757 +                }
  1.1758 +                return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
  1.1759 +            },
  1.1760 +
  1.1761 +            detach: function() {
  1.1762 +                this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
  1.1763 +            }
  1.1764 +        };
  1.1765 +
  1.1766 +        /*----------------------------------------------------------------------------------------------------------------*/
  1.1767 +
  1.1768 +        var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
  1.1769 +        var rootContainerNodeTypes = [2, 9, 11];
  1.1770 +        var readonlyNodeTypes = [5, 6, 10, 12];
  1.1771 +        var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
  1.1772 +        var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
  1.1773 +
  1.1774 +        function createAncestorFinder(nodeTypes) {
  1.1775 +            return function(node, selfIsAncestor) {
  1.1776 +                var t, n = selfIsAncestor ? node : node.parentNode;
  1.1777 +                while (n) {
  1.1778 +                    t = n.nodeType;
  1.1779 +                    if (arrayContains(nodeTypes, t)) {
  1.1780 +                        return n;
  1.1781 +                    }
  1.1782 +                    n = n.parentNode;
  1.1783 +                }
  1.1784 +                return null;
  1.1785 +            };
  1.1786 +        }
  1.1787 +
  1.1788 +        var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
  1.1789 +        var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
  1.1790 +        var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
  1.1791 +
  1.1792 +        function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
  1.1793 +            if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
  1.1794 +                throw new DOMException("INVALID_NODE_TYPE_ERR");
  1.1795 +            }
  1.1796 +        }
  1.1797 +
  1.1798 +        function assertValidNodeType(node, invalidTypes) {
  1.1799 +            if (!arrayContains(invalidTypes, node.nodeType)) {
  1.1800 +                throw new DOMException("INVALID_NODE_TYPE_ERR");
  1.1801 +            }
  1.1802 +        }
  1.1803 +
  1.1804 +        function assertValidOffset(node, offset) {
  1.1805 +            if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
  1.1806 +                throw new DOMException("INDEX_SIZE_ERR");
  1.1807 +            }
  1.1808 +        }
  1.1809 +
  1.1810 +        function assertSameDocumentOrFragment(node1, node2) {
  1.1811 +            if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
  1.1812 +                throw new DOMException("WRONG_DOCUMENT_ERR");
  1.1813 +            }
  1.1814 +        }
  1.1815 +
  1.1816 +        function assertNodeNotReadOnly(node) {
  1.1817 +            if (getReadonlyAncestor(node, true)) {
  1.1818 +                throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
  1.1819 +            }
  1.1820 +        }
  1.1821 +
  1.1822 +        function assertNode(node, codeName) {
  1.1823 +            if (!node) {
  1.1824 +                throw new DOMException(codeName);
  1.1825 +            }
  1.1826 +        }
  1.1827 +
  1.1828 +        function isValidOffset(node, offset) {
  1.1829 +            return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);
  1.1830 +        }
  1.1831 +
  1.1832 +        function isRangeValid(range) {
  1.1833 +            return (!!range.startContainer && !!range.endContainer &&
  1.1834 +                    !(crashyTextNodes && (dom.isBrokenNode(range.startContainer) || dom.isBrokenNode(range.endContainer))) &&
  1.1835 +                    getRootContainer(range.startContainer) == getRootContainer(range.endContainer) &&
  1.1836 +                    isValidOffset(range.startContainer, range.startOffset) &&
  1.1837 +                    isValidOffset(range.endContainer, range.endOffset));
  1.1838 +        }
  1.1839 +
  1.1840 +        function assertRangeValid(range) {
  1.1841 +            if (!isRangeValid(range)) {
  1.1842 +                throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: (" + range.inspect() + ")");
  1.1843 +            }
  1.1844 +        }
  1.1845 +
  1.1846 +        /*----------------------------------------------------------------------------------------------------------------*/
  1.1847 +
  1.1848 +        // Test the browser's innerHTML support to decide how to implement createContextualFragment
  1.1849 +        var styleEl = document.createElement("style");
  1.1850 +        var htmlParsingConforms = false;
  1.1851 +        try {
  1.1852 +            styleEl.innerHTML = "<b>x</b>";
  1.1853 +            htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
  1.1854 +        } catch (e) {
  1.1855 +            // IE 6 and 7 throw
  1.1856 +        }
  1.1857 +
  1.1858 +        api.features.htmlParsingConforms = htmlParsingConforms;
  1.1859 +
  1.1860 +        var createContextualFragment = htmlParsingConforms ?
  1.1861 +
  1.1862 +            // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
  1.1863 +            // discussion and base code for this implementation at issue 67.
  1.1864 +            // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
  1.1865 +            // Thanks to Aleks Williams.
  1.1866 +            function(fragmentStr) {
  1.1867 +                // "Let node the context object's start's node."
  1.1868 +                var node = this.startContainer;
  1.1869 +                var doc = getDocument(node);
  1.1870 +
  1.1871 +                // "If the context object's start's node is null, raise an INVALID_STATE_ERR
  1.1872 +                // exception and abort these steps."
  1.1873 +                if (!node) {
  1.1874 +                    throw new DOMException("INVALID_STATE_ERR");
  1.1875 +                }
  1.1876 +
  1.1877 +                // "Let element be as follows, depending on node's interface:"
  1.1878 +                // Document, Document Fragment: null
  1.1879 +                var el = null;
  1.1880 +
  1.1881 +                // "Element: node"
  1.1882 +                if (node.nodeType == 1) {
  1.1883 +                    el = node;
  1.1884 +
  1.1885 +                // "Text, Comment: node's parentElement"
  1.1886 +                } else if (isCharacterDataNode(node)) {
  1.1887 +                    el = dom.parentElement(node);
  1.1888 +                }
  1.1889 +
  1.1890 +                // "If either element is null or element's ownerDocument is an HTML document
  1.1891 +                // and element's local name is "html" and element's namespace is the HTML
  1.1892 +                // namespace"
  1.1893 +                if (el === null || (
  1.1894 +                    el.nodeName == "HTML" &&
  1.1895 +                    dom.isHtmlNamespace(getDocument(el).documentElement) &&
  1.1896 +                    dom.isHtmlNamespace(el)
  1.1897 +                )) {
  1.1898 +
  1.1899 +                // "let element be a new Element with "body" as its local name and the HTML
  1.1900 +                // namespace as its namespace.""
  1.1901 +                    el = doc.createElement("body");
  1.1902 +                } else {
  1.1903 +                    el = el.cloneNode(false);
  1.1904 +                }
  1.1905 +
  1.1906 +                // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
  1.1907 +                // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
  1.1908 +                // "In either case, the algorithm must be invoked with fragment as the input
  1.1909 +                // and element as the context element."
  1.1910 +                el.innerHTML = fragmentStr;
  1.1911 +
  1.1912 +                // "If this raises an exception, then abort these steps. Otherwise, let new
  1.1913 +                // children be the nodes returned."
  1.1914 +
  1.1915 +                // "Let fragment be a new DocumentFragment."
  1.1916 +                // "Append all new children to fragment."
  1.1917 +                // "Return fragment."
  1.1918 +                return dom.fragmentFromNodeChildren(el);
  1.1919 +            } :
  1.1920 +
  1.1921 +            // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
  1.1922 +            // previous versions of Rangy used (with the exception of using a body element rather than a div)
  1.1923 +            function(fragmentStr) {
  1.1924 +                var doc = getRangeDocument(this);
  1.1925 +                var el = doc.createElement("body");
  1.1926 +                el.innerHTML = fragmentStr;
  1.1927 +
  1.1928 +                return dom.fragmentFromNodeChildren(el);
  1.1929 +            };
  1.1930 +
  1.1931 +        function splitRangeBoundaries(range, positionsToPreserve) {
  1.1932 +            assertRangeValid(range);
  1.1933 +
  1.1934 +            var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
  1.1935 +            var startEndSame = (sc === ec);
  1.1936 +
  1.1937 +            if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
  1.1938 +                splitDataNode(ec, eo, positionsToPreserve);
  1.1939 +            }
  1.1940 +
  1.1941 +            if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
  1.1942 +                sc = splitDataNode(sc, so, positionsToPreserve);
  1.1943 +                if (startEndSame) {
  1.1944 +                    eo -= so;
  1.1945 +                    ec = sc;
  1.1946 +                } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
  1.1947 +                    eo++;
  1.1948 +                }
  1.1949 +                so = 0;
  1.1950 +            }
  1.1951 +            range.setStartAndEnd(sc, so, ec, eo);
  1.1952 +        }
  1.1953 +
  1.1954 +        function rangeToHtml(range) {
  1.1955 +            assertRangeValid(range);
  1.1956 +            var container = range.commonAncestorContainer.parentNode.cloneNode(false);
  1.1957 +            container.appendChild( range.cloneContents() );
  1.1958 +            return container.innerHTML;
  1.1959 +        }
  1.1960 +
  1.1961 +        /*----------------------------------------------------------------------------------------------------------------*/
  1.1962 +
  1.1963 +        var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
  1.1964 +            "commonAncestorContainer"];
  1.1965 +
  1.1966 +        var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
  1.1967 +        var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
  1.1968 +
  1.1969 +        util.extend(api.rangePrototype, {
  1.1970 +            compareBoundaryPoints: function(how, range) {
  1.1971 +                assertRangeValid(this);
  1.1972 +                assertSameDocumentOrFragment(this.startContainer, range.startContainer);
  1.1973 +
  1.1974 +                var nodeA, offsetA, nodeB, offsetB;
  1.1975 +                var prefixA = (how == e2s || how == s2s) ? "start" : "end";
  1.1976 +                var prefixB = (how == s2e || how == s2s) ? "start" : "end";
  1.1977 +                nodeA = this[prefixA + "Container"];
  1.1978 +                offsetA = this[prefixA + "Offset"];
  1.1979 +                nodeB = range[prefixB + "Container"];
  1.1980 +                offsetB = range[prefixB + "Offset"];
  1.1981 +                return comparePoints(nodeA, offsetA, nodeB, offsetB);
  1.1982 +            },
  1.1983 +
  1.1984 +            insertNode: function(node) {
  1.1985 +                assertRangeValid(this);
  1.1986 +                assertValidNodeType(node, insertableNodeTypes);
  1.1987 +                assertNodeNotReadOnly(this.startContainer);
  1.1988 +
  1.1989 +                if (isOrIsAncestorOf(node, this.startContainer)) {
  1.1990 +                    throw new DOMException("HIERARCHY_REQUEST_ERR");
  1.1991 +                }
  1.1992 +
  1.1993 +                // No check for whether the container of the start of the Range is of a type that does not allow
  1.1994 +                // children of the type of node: the browser's DOM implementation should do this for us when we attempt
  1.1995 +                // to add the node
  1.1996 +
  1.1997 +                var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
  1.1998 +                this.setStartBefore(firstNodeInserted);
  1.1999 +            },
  1.2000 +
  1.2001 +            cloneContents: function() {
  1.2002 +                assertRangeValid(this);
  1.2003 +
  1.2004 +                var clone, frag;
  1.2005 +                if (this.collapsed) {
  1.2006 +                    return getRangeDocument(this).createDocumentFragment();
  1.2007 +                } else {
  1.2008 +                    if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {
  1.2009 +                        clone = this.startContainer.cloneNode(true);
  1.2010 +                        clone.data = clone.data.slice(this.startOffset, this.endOffset);
  1.2011 +                        frag = getRangeDocument(this).createDocumentFragment();
  1.2012 +                        frag.appendChild(clone);
  1.2013 +                        return frag;
  1.2014 +                    } else {
  1.2015 +                        var iterator = new RangeIterator(this, true);
  1.2016 +                        clone = cloneSubtree(iterator);
  1.2017 +                        iterator.detach();
  1.2018 +                    }
  1.2019 +                    return clone;
  1.2020 +                }
  1.2021 +            },
  1.2022 +
  1.2023 +            canSurroundContents: function() {
  1.2024 +                assertRangeValid(this);
  1.2025 +                assertNodeNotReadOnly(this.startContainer);
  1.2026 +                assertNodeNotReadOnly(this.endContainer);
  1.2027 +
  1.2028 +                // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
  1.2029 +                // no non-text nodes.
  1.2030 +                var iterator = new RangeIterator(this, true);
  1.2031 +                var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
  1.2032 +                        (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
  1.2033 +                iterator.detach();
  1.2034 +                return !boundariesInvalid;
  1.2035 +            },
  1.2036 +
  1.2037 +            surroundContents: function(node) {
  1.2038 +                assertValidNodeType(node, surroundNodeTypes);
  1.2039 +
  1.2040 +                if (!this.canSurroundContents()) {
  1.2041 +                    throw new DOMException("INVALID_STATE_ERR");
  1.2042 +                }
  1.2043 +
  1.2044 +                // Extract the contents
  1.2045 +                var content = this.extractContents();
  1.2046 +
  1.2047 +                // Clear the children of the node
  1.2048 +                if (node.hasChildNodes()) {
  1.2049 +                    while (node.lastChild) {
  1.2050 +                        node.removeChild(node.lastChild);
  1.2051 +                    }
  1.2052 +                }
  1.2053 +
  1.2054 +                // Insert the new node and add the extracted contents
  1.2055 +                insertNodeAtPosition(node, this.startContainer, this.startOffset);
  1.2056 +                node.appendChild(content);
  1.2057 +
  1.2058 +                this.selectNode(node);
  1.2059 +            },
  1.2060 +
  1.2061 +            cloneRange: function() {
  1.2062 +                assertRangeValid(this);
  1.2063 +                var range = new Range(getRangeDocument(this));
  1.2064 +                var i = rangeProperties.length, prop;
  1.2065 +                while (i--) {
  1.2066 +                    prop = rangeProperties[i];
  1.2067 +                    range[prop] = this[prop];
  1.2068 +                }
  1.2069 +                return range;
  1.2070 +            },
  1.2071 +
  1.2072 +            toString: function() {
  1.2073 +                assertRangeValid(this);
  1.2074 +                var sc = this.startContainer;
  1.2075 +                if (sc === this.endContainer && isCharacterDataNode(sc)) {
  1.2076 +                    return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
  1.2077 +                } else {
  1.2078 +                    var textParts = [], iterator = new RangeIterator(this, true);
  1.2079 +                    iterateSubtree(iterator, function(node) {
  1.2080 +                        // Accept only text or CDATA nodes, not comments
  1.2081 +                        if (node.nodeType == 3 || node.nodeType == 4) {
  1.2082 +                            textParts.push(node.data);
  1.2083 +                        }
  1.2084 +                    });
  1.2085 +                    iterator.detach();
  1.2086 +                    return textParts.join("");
  1.2087 +                }
  1.2088 +            },
  1.2089 +
  1.2090 +            // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
  1.2091 +            // been removed from Mozilla.
  1.2092 +
  1.2093 +            compareNode: function(node) {
  1.2094 +                assertRangeValid(this);
  1.2095 +
  1.2096 +                var parent = node.parentNode;
  1.2097 +                var nodeIndex = getNodeIndex(node);
  1.2098 +
  1.2099 +                if (!parent) {
  1.2100 +                    throw new DOMException("NOT_FOUND_ERR");
  1.2101 +                }
  1.2102 +
  1.2103 +                var startComparison = this.comparePoint(parent, nodeIndex),
  1.2104 +                    endComparison = this.comparePoint(parent, nodeIndex + 1);
  1.2105 +
  1.2106 +                if (startComparison < 0) { // Node starts before
  1.2107 +                    return (endComparison > 0) ? n_b_a : n_b;
  1.2108 +                } else {
  1.2109 +                    return (endComparison > 0) ? n_a : n_i;
  1.2110 +                }
  1.2111 +            },
  1.2112 +
  1.2113 +            comparePoint: function(node, offset) {
  1.2114 +                assertRangeValid(this);
  1.2115 +                assertNode(node, "HIERARCHY_REQUEST_ERR");
  1.2116 +                assertSameDocumentOrFragment(node, this.startContainer);
  1.2117 +
  1.2118 +                if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
  1.2119 +                    return -1;
  1.2120 +                } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
  1.2121 +                    return 1;
  1.2122 +                }
  1.2123 +                return 0;
  1.2124 +            },
  1.2125 +
  1.2126 +            createContextualFragment: createContextualFragment,
  1.2127 +
  1.2128 +            toHtml: function() {
  1.2129 +                return rangeToHtml(this);
  1.2130 +            },
  1.2131 +
  1.2132 +            // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
  1.2133 +            // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
  1.2134 +            intersectsNode: function(node, touchingIsIntersecting) {
  1.2135 +                assertRangeValid(this);
  1.2136 +                if (getRootContainer(node) != getRangeRoot(this)) {
  1.2137 +                    return false;
  1.2138 +                }
  1.2139 +
  1.2140 +                var parent = node.parentNode, offset = getNodeIndex(node);
  1.2141 +                if (!parent) {
  1.2142 +                    return true;
  1.2143 +                }
  1.2144 +
  1.2145 +                var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),
  1.2146 +                    endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
  1.2147 +
  1.2148 +                return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
  1.2149 +            },
  1.2150 +
  1.2151 +            isPointInRange: function(node, offset) {
  1.2152 +                assertRangeValid(this);
  1.2153 +                assertNode(node, "HIERARCHY_REQUEST_ERR");
  1.2154 +                assertSameDocumentOrFragment(node, this.startContainer);
  1.2155 +
  1.2156 +                return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
  1.2157 +                       (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
  1.2158 +            },
  1.2159 +
  1.2160 +            // The methods below are non-standard and invented by me.
  1.2161 +
  1.2162 +            // Sharing a boundary start-to-end or end-to-start does not count as intersection.
  1.2163 +            intersectsRange: function(range) {
  1.2164 +                return rangesIntersect(this, range, false);
  1.2165 +            },
  1.2166 +
  1.2167 +            // Sharing a boundary start-to-end or end-to-start does count as intersection.
  1.2168 +            intersectsOrTouchesRange: function(range) {
  1.2169 +                return rangesIntersect(this, range, true);
  1.2170 +            },
  1.2171 +
  1.2172 +            intersection: function(range) {
  1.2173 +                if (this.intersectsRange(range)) {
  1.2174 +                    var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
  1.2175 +                        endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
  1.2176 +
  1.2177 +                    var intersectionRange = this.cloneRange();
  1.2178 +                    if (startComparison == -1) {
  1.2179 +                        intersectionRange.setStart(range.startContainer, range.startOffset);
  1.2180 +                    }
  1.2181 +                    if (endComparison == 1) {
  1.2182 +                        intersectionRange.setEnd(range.endContainer, range.endOffset);
  1.2183 +                    }
  1.2184 +                    return intersectionRange;
  1.2185 +                }
  1.2186 +                return null;
  1.2187 +            },
  1.2188 +
  1.2189 +            union: function(range) {
  1.2190 +                if (this.intersectsOrTouchesRange(range)) {
  1.2191 +                    var unionRange = this.cloneRange();
  1.2192 +                    if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
  1.2193 +                        unionRange.setStart(range.startContainer, range.startOffset);
  1.2194 +                    }
  1.2195 +                    if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
  1.2196 +                        unionRange.setEnd(range.endContainer, range.endOffset);
  1.2197 +                    }
  1.2198 +                    return unionRange;
  1.2199 +                } else {
  1.2200 +                    throw new DOMException("Ranges do not intersect");
  1.2201 +                }
  1.2202 +            },
  1.2203 +
  1.2204 +            containsNode: function(node, allowPartial) {
  1.2205 +                if (allowPartial) {
  1.2206 +                    return this.intersectsNode(node, false);
  1.2207 +                } else {
  1.2208 +                    return this.compareNode(node) == n_i;
  1.2209 +                }
  1.2210 +            },
  1.2211 +
  1.2212 +            containsNodeContents: function(node) {
  1.2213 +                return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;
  1.2214 +            },
  1.2215 +
  1.2216 +            containsRange: function(range) {
  1.2217 +                var intersection = this.intersection(range);
  1.2218 +                return intersection !== null && range.equals(intersection);
  1.2219 +            },
  1.2220 +
  1.2221 +            containsNodeText: function(node) {
  1.2222 +                var nodeRange = this.cloneRange();
  1.2223 +                nodeRange.selectNode(node);
  1.2224 +                var textNodes = nodeRange.getNodes([3]);
  1.2225 +                if (textNodes.length > 0) {
  1.2226 +                    nodeRange.setStart(textNodes[0], 0);
  1.2227 +                    var lastTextNode = textNodes.pop();
  1.2228 +                    nodeRange.setEnd(lastTextNode, lastTextNode.length);
  1.2229 +                    return this.containsRange(nodeRange);
  1.2230 +                } else {
  1.2231 +                    return this.containsNodeContents(node);
  1.2232 +                }
  1.2233 +            },
  1.2234 +
  1.2235 +            getNodes: function(nodeTypes, filter) {
  1.2236 +                assertRangeValid(this);
  1.2237 +                return getNodesInRange(this, nodeTypes, filter);
  1.2238 +            },
  1.2239 +
  1.2240 +            getDocument: function() {
  1.2241 +                return getRangeDocument(this);
  1.2242 +            },
  1.2243 +
  1.2244 +            collapseBefore: function(node) {
  1.2245 +                this.setEndBefore(node);
  1.2246 +                this.collapse(false);
  1.2247 +            },
  1.2248 +
  1.2249 +            collapseAfter: function(node) {
  1.2250 +                this.setStartAfter(node);
  1.2251 +                this.collapse(true);
  1.2252 +            },
  1.2253 +
  1.2254 +            getBookmark: function(containerNode) {
  1.2255 +                var doc = getRangeDocument(this);
  1.2256 +                var preSelectionRange = api.createRange(doc);
  1.2257 +                containerNode = containerNode || dom.getBody(doc);
  1.2258 +                preSelectionRange.selectNodeContents(containerNode);
  1.2259 +                var range = this.intersection(preSelectionRange);
  1.2260 +                var start = 0, end = 0;
  1.2261 +                if (range) {
  1.2262 +                    preSelectionRange.setEnd(range.startContainer, range.startOffset);
  1.2263 +                    start = preSelectionRange.toString().length;
  1.2264 +                    end = start + range.toString().length;
  1.2265 +                }
  1.2266 +
  1.2267 +                return {
  1.2268 +                    start: start,
  1.2269 +                    end: end,
  1.2270 +                    containerNode: containerNode
  1.2271 +                };
  1.2272 +            },
  1.2273 +
  1.2274 +            moveToBookmark: function(bookmark) {
  1.2275 +                var containerNode = bookmark.containerNode;
  1.2276 +                var charIndex = 0;
  1.2277 +                this.setStart(containerNode, 0);
  1.2278 +                this.collapse(true);
  1.2279 +                var nodeStack = [containerNode], node, foundStart = false, stop = false;
  1.2280 +                var nextCharIndex, i, childNodes;
  1.2281 +
  1.2282 +                while (!stop && (node = nodeStack.pop())) {
  1.2283 +                    if (node.nodeType == 3) {
  1.2284 +                        nextCharIndex = charIndex + node.length;
  1.2285 +                        if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {
  1.2286 +                            this.setStart(node, bookmark.start - charIndex);
  1.2287 +                            foundStart = true;
  1.2288 +                        }
  1.2289 +                        if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {
  1.2290 +                            this.setEnd(node, bookmark.end - charIndex);
  1.2291 +                            stop = true;
  1.2292 +                        }
  1.2293 +                        charIndex = nextCharIndex;
  1.2294 +                    } else {
  1.2295 +                        childNodes = node.childNodes;
  1.2296 +                        i = childNodes.length;
  1.2297 +                        while (i--) {
  1.2298 +                            nodeStack.push(childNodes[i]);
  1.2299 +                        }
  1.2300 +                    }
  1.2301 +                }
  1.2302 +            },
  1.2303 +
  1.2304 +            getName: function() {
  1.2305 +                return "DomRange";
  1.2306 +            },
  1.2307 +
  1.2308 +            equals: function(range) {
  1.2309 +                return Range.rangesEqual(this, range);
  1.2310 +            },
  1.2311 +
  1.2312 +            isValid: function() {
  1.2313 +                return isRangeValid(this);
  1.2314 +            },
  1.2315 +
  1.2316 +            inspect: function() {
  1.2317 +                return inspect(this);
  1.2318 +            },
  1.2319 +
  1.2320 +            detach: function() {
  1.2321 +                // In DOM4, detach() is now a no-op.
  1.2322 +            }
  1.2323 +        });
  1.2324 +
  1.2325 +        function copyComparisonConstantsToObject(obj) {
  1.2326 +            obj.START_TO_START = s2s;
  1.2327 +            obj.START_TO_END = s2e;
  1.2328 +            obj.END_TO_END = e2e;
  1.2329 +            obj.END_TO_START = e2s;
  1.2330 +
  1.2331 +            obj.NODE_BEFORE = n_b;
  1.2332 +            obj.NODE_AFTER = n_a;
  1.2333 +            obj.NODE_BEFORE_AND_AFTER = n_b_a;
  1.2334 +            obj.NODE_INSIDE = n_i;
  1.2335 +        }
  1.2336 +
  1.2337 +        function copyComparisonConstants(constructor) {
  1.2338 +            copyComparisonConstantsToObject(constructor);
  1.2339 +            copyComparisonConstantsToObject(constructor.prototype);
  1.2340 +        }
  1.2341 +
  1.2342 +        function createRangeContentRemover(remover, boundaryUpdater) {
  1.2343 +            return function() {
  1.2344 +                assertRangeValid(this);
  1.2345 +
  1.2346 +                var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
  1.2347 +
  1.2348 +                var iterator = new RangeIterator(this, true);
  1.2349 +
  1.2350 +                // Work out where to position the range after content removal
  1.2351 +                var node, boundary;
  1.2352 +                if (sc !== root) {
  1.2353 +                    node = getClosestAncestorIn(sc, root, true);
  1.2354 +                    boundary = getBoundaryAfterNode(node);
  1.2355 +                    sc = boundary.node;
  1.2356 +                    so = boundary.offset;
  1.2357 +                }
  1.2358 +
  1.2359 +                // Check none of the range is read-only
  1.2360 +                iterateSubtree(iterator, assertNodeNotReadOnly);
  1.2361 +
  1.2362 +                iterator.reset();
  1.2363 +
  1.2364 +                // Remove the content
  1.2365 +                var returnValue = remover(iterator);
  1.2366 +                iterator.detach();
  1.2367 +
  1.2368 +                // Move to the new position
  1.2369 +                boundaryUpdater(this, sc, so, sc, so);
  1.2370 +
  1.2371 +                return returnValue;
  1.2372 +            };
  1.2373 +        }
  1.2374 +
  1.2375 +        function createPrototypeRange(constructor, boundaryUpdater) {
  1.2376 +            function createBeforeAfterNodeSetter(isBefore, isStart) {
  1.2377 +                return function(node) {
  1.2378 +                    assertValidNodeType(node, beforeAfterNodeTypes);
  1.2379 +                    assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
  1.2380 +
  1.2381 +                    var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
  1.2382 +                    (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
  1.2383 +                };
  1.2384 +            }
  1.2385 +
  1.2386 +            function setRangeStart(range, node, offset) {
  1.2387 +                var ec = range.endContainer, eo = range.endOffset;
  1.2388 +                if (node !== range.startContainer || offset !== range.startOffset) {
  1.2389 +                    // Check the root containers of the range and the new boundary, and also check whether the new boundary
  1.2390 +                    // is after the current end. In either case, collapse the range to the new position
  1.2391 +                    if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) {
  1.2392 +                        ec = node;
  1.2393 +                        eo = offset;
  1.2394 +                    }
  1.2395 +                    boundaryUpdater(range, node, offset, ec, eo);
  1.2396 +                }
  1.2397 +            }
  1.2398 +
  1.2399 +            function setRangeEnd(range, node, offset) {
  1.2400 +                var sc = range.startContainer, so = range.startOffset;
  1.2401 +                if (node !== range.endContainer || offset !== range.endOffset) {
  1.2402 +                    // Check the root containers of the range and the new boundary, and also check whether the new boundary
  1.2403 +                    // is after the current end. In either case, collapse the range to the new position
  1.2404 +                    if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {
  1.2405 +                        sc = node;
  1.2406 +                        so = offset;
  1.2407 +                    }
  1.2408 +                    boundaryUpdater(range, sc, so, node, offset);
  1.2409 +                }
  1.2410 +            }
  1.2411 +
  1.2412 +            // Set up inheritance
  1.2413 +            var F = function() {};
  1.2414 +            F.prototype = api.rangePrototype;
  1.2415 +            constructor.prototype = new F();
  1.2416 +
  1.2417 +            util.extend(constructor.prototype, {
  1.2418 +                setStart: function(node, offset) {
  1.2419 +                    assertNoDocTypeNotationEntityAncestor(node, true);
  1.2420 +                    assertValidOffset(node, offset);
  1.2421 +
  1.2422 +                    setRangeStart(this, node, offset);
  1.2423 +                },
  1.2424 +
  1.2425 +                setEnd: function(node, offset) {
  1.2426 +                    assertNoDocTypeNotationEntityAncestor(node, true);
  1.2427 +                    assertValidOffset(node, offset);
  1.2428 +
  1.2429 +                    setRangeEnd(this, node, offset);
  1.2430 +                },
  1.2431 +
  1.2432 +                /**
  1.2433 +                 * Convenience method to set a range's start and end boundaries. Overloaded as follows:
  1.2434 +                 * - Two parameters (node, offset) creates a collapsed range at that position
  1.2435 +                 * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
  1.2436 +                 *   startOffset and ending at endOffset
  1.2437 +                 * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
  1.2438 +                 *   startNode and ending at endOffset in endNode
  1.2439 +                 */
  1.2440 +                setStartAndEnd: function() {
  1.2441 +                    var args = arguments;
  1.2442 +                    var sc = args[0], so = args[1], ec = sc, eo = so;
  1.2443 +
  1.2444 +                    switch (args.length) {
  1.2445 +                        case 3:
  1.2446 +                            eo = args[2];
  1.2447 +                            break;
  1.2448 +                        case 4:
  1.2449 +                            ec = args[2];
  1.2450 +                            eo = args[3];
  1.2451 +                            break;
  1.2452 +                    }
  1.2453 +
  1.2454 +                    boundaryUpdater(this, sc, so, ec, eo);
  1.2455 +                },
  1.2456 +
  1.2457 +                setBoundary: function(node, offset, isStart) {
  1.2458 +                    this["set" + (isStart ? "Start" : "End")](node, offset);
  1.2459 +                },
  1.2460 +
  1.2461 +                setStartBefore: createBeforeAfterNodeSetter(true, true),
  1.2462 +                setStartAfter: createBeforeAfterNodeSetter(false, true),
  1.2463 +                setEndBefore: createBeforeAfterNodeSetter(true, false),
  1.2464 +                setEndAfter: createBeforeAfterNodeSetter(false, false),
  1.2465 +
  1.2466 +                collapse: function(isStart) {
  1.2467 +                    assertRangeValid(this);
  1.2468 +                    if (isStart) {
  1.2469 +                        boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
  1.2470 +                    } else {
  1.2471 +                        boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
  1.2472 +                    }
  1.2473 +                },
  1.2474 +
  1.2475 +                selectNodeContents: function(node) {
  1.2476 +                    assertNoDocTypeNotationEntityAncestor(node, true);
  1.2477 +
  1.2478 +                    boundaryUpdater(this, node, 0, node, getNodeLength(node));
  1.2479 +                },
  1.2480 +
  1.2481 +                selectNode: function(node) {
  1.2482 +                    assertNoDocTypeNotationEntityAncestor(node, false);
  1.2483 +                    assertValidNodeType(node, beforeAfterNodeTypes);
  1.2484 +
  1.2485 +                    var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
  1.2486 +                    boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
  1.2487 +                },
  1.2488 +
  1.2489 +                extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
  1.2490 +
  1.2491 +                deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
  1.2492 +
  1.2493 +                canSurroundContents: function() {
  1.2494 +                    assertRangeValid(this);
  1.2495 +                    assertNodeNotReadOnly(this.startContainer);
  1.2496 +                    assertNodeNotReadOnly(this.endContainer);
  1.2497 +
  1.2498 +                    // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
  1.2499 +                    // no non-text nodes.
  1.2500 +                    var iterator = new RangeIterator(this, true);
  1.2501 +                    var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) ||
  1.2502 +                            (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
  1.2503 +                    iterator.detach();
  1.2504 +                    return !boundariesInvalid;
  1.2505 +                },
  1.2506 +
  1.2507 +                splitBoundaries: function() {
  1.2508 +                    splitRangeBoundaries(this);
  1.2509 +                },
  1.2510 +
  1.2511 +                splitBoundariesPreservingPositions: function(positionsToPreserve) {
  1.2512 +                    splitRangeBoundaries(this, positionsToPreserve);
  1.2513 +                },
  1.2514 +
  1.2515 +                normalizeBoundaries: function() {
  1.2516 +                    assertRangeValid(this);
  1.2517 +
  1.2518 +                    var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
  1.2519 +
  1.2520 +                    var mergeForward = function(node) {
  1.2521 +                        var sibling = node.nextSibling;
  1.2522 +                        if (sibling && sibling.nodeType == node.nodeType) {
  1.2523 +                            ec = node;
  1.2524 +                            eo = node.length;
  1.2525 +                            node.appendData(sibling.data);
  1.2526 +                            removeNode(sibling);
  1.2527 +                        }
  1.2528 +                    };
  1.2529 +
  1.2530 +                    var mergeBackward = function(node) {
  1.2531 +                        var sibling = node.previousSibling;
  1.2532 +                        if (sibling && sibling.nodeType == node.nodeType) {
  1.2533 +                            sc = node;
  1.2534 +                            var nodeLength = node.length;
  1.2535 +                            so = sibling.length;
  1.2536 +                            node.insertData(0, sibling.data);
  1.2537 +                            removeNode(sibling);
  1.2538 +                            if (sc == ec) {
  1.2539 +                                eo += so;
  1.2540 +                                ec = sc;
  1.2541 +                            } else if (ec == node.parentNode) {
  1.2542 +                                var nodeIndex = getNodeIndex(node);
  1.2543 +                                if (eo == nodeIndex) {
  1.2544 +                                    ec = node;
  1.2545 +                                    eo = nodeLength;
  1.2546 +                                } else if (eo > nodeIndex) {
  1.2547 +                                    eo--;
  1.2548 +                                }
  1.2549 +                            }
  1.2550 +                        }
  1.2551 +                    };
  1.2552 +
  1.2553 +                    var normalizeStart = true;
  1.2554 +                    var sibling;
  1.2555 +
  1.2556 +                    if (isCharacterDataNode(ec)) {
  1.2557 +                        if (eo == ec.length) {
  1.2558 +                            mergeForward(ec);
  1.2559 +                        } else if (eo == 0) {
  1.2560 +                            sibling = ec.previousSibling;
  1.2561 +                            if (sibling && sibling.nodeType == ec.nodeType) {
  1.2562 +                                eo = sibling.length;
  1.2563 +                                if (sc == ec) {
  1.2564 +                                    normalizeStart = false;
  1.2565 +                                }
  1.2566 +                                sibling.appendData(ec.data);
  1.2567 +                                removeNode(ec);
  1.2568 +                                ec = sibling;
  1.2569 +                            }
  1.2570 +                        }
  1.2571 +                    } else {
  1.2572 +                        if (eo > 0) {
  1.2573 +                            var endNode = ec.childNodes[eo - 1];
  1.2574 +                            if (endNode && isCharacterDataNode(endNode)) {
  1.2575 +                                mergeForward(endNode);
  1.2576 +                            }
  1.2577 +                        }
  1.2578 +                        normalizeStart = !this.collapsed;
  1.2579 +                    }
  1.2580 +
  1.2581 +                    if (normalizeStart) {
  1.2582 +                        if (isCharacterDataNode(sc)) {
  1.2583 +                            if (so == 0) {
  1.2584 +                                mergeBackward(sc);
  1.2585 +                            } else if (so == sc.length) {
  1.2586 +                                sibling = sc.nextSibling;
  1.2587 +                                if (sibling && sibling.nodeType == sc.nodeType) {
  1.2588 +                                    if (ec == sibling) {
  1.2589 +                                        ec = sc;
  1.2590 +                                        eo += sc.length;
  1.2591 +                                    }
  1.2592 +                                    sc.appendData(sibling.data);
  1.2593 +                                    removeNode(sibling);
  1.2594 +                                }
  1.2595 +                            }
  1.2596 +                        } else {
  1.2597 +                            if (so < sc.childNodes.length) {
  1.2598 +                                var startNode = sc.childNodes[so];
  1.2599 +                                if (startNode && isCharacterDataNode(startNode)) {
  1.2600 +                                    mergeBackward(startNode);
  1.2601 +                                }
  1.2602 +                            }
  1.2603 +                        }
  1.2604 +                    } else {
  1.2605 +                        sc = ec;
  1.2606 +                        so = eo;
  1.2607 +                    }
  1.2608 +
  1.2609 +                    boundaryUpdater(this, sc, so, ec, eo);
  1.2610 +                },
  1.2611 +
  1.2612 +                collapseToPoint: function(node, offset) {
  1.2613 +                    assertNoDocTypeNotationEntityAncestor(node, true);
  1.2614 +                    assertValidOffset(node, offset);
  1.2615 +                    this.setStartAndEnd(node, offset);
  1.2616 +                }
  1.2617 +            });
  1.2618 +
  1.2619 +            copyComparisonConstants(constructor);
  1.2620 +        }
  1.2621 +
  1.2622 +        /*----------------------------------------------------------------------------------------------------------------*/
  1.2623 +
  1.2624 +        // Updates commonAncestorContainer and collapsed after boundary change
  1.2625 +        function updateCollapsedAndCommonAncestor(range) {
  1.2626 +            range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
  1.2627 +            range.commonAncestorContainer = range.collapsed ?
  1.2628 +                range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
  1.2629 +        }
  1.2630 +
  1.2631 +        function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
  1.2632 +            range.startContainer = startContainer;
  1.2633 +            range.startOffset = startOffset;
  1.2634 +            range.endContainer = endContainer;
  1.2635 +            range.endOffset = endOffset;
  1.2636 +            range.document = dom.getDocument(startContainer);
  1.2637 +
  1.2638 +            updateCollapsedAndCommonAncestor(range);
  1.2639 +        }
  1.2640 +
  1.2641 +        function Range(doc) {
  1.2642 +            this.startContainer = doc;
  1.2643 +            this.startOffset = 0;
  1.2644 +            this.endContainer = doc;
  1.2645 +            this.endOffset = 0;
  1.2646 +            this.document = doc;
  1.2647 +            updateCollapsedAndCommonAncestor(this);
  1.2648 +        }
  1.2649 +
  1.2650 +        createPrototypeRange(Range, updateBoundaries);
  1.2651 +
  1.2652 +        util.extend(Range, {
  1.2653 +            rangeProperties: rangeProperties,
  1.2654 +            RangeIterator: RangeIterator,
  1.2655 +            copyComparisonConstants: copyComparisonConstants,
  1.2656 +            createPrototypeRange: createPrototypeRange,
  1.2657 +            inspect: inspect,
  1.2658 +            toHtml: rangeToHtml,
  1.2659 +            getRangeDocument: getRangeDocument,
  1.2660 +            rangesEqual: function(r1, r2) {
  1.2661 +                return r1.startContainer === r2.startContainer &&
  1.2662 +                    r1.startOffset === r2.startOffset &&
  1.2663 +                    r1.endContainer === r2.endContainer &&
  1.2664 +                    r1.endOffset === r2.endOffset;
  1.2665 +            }
  1.2666 +        });
  1.2667 +
  1.2668 +        api.DomRange = Range;
  1.2669 +    });
  1.2670 +
  1.2671 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.2672 +
  1.2673 +    // Wrappers for the browser's native DOM Range and/or TextRange implementation
  1.2674 +    api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) {
  1.2675 +        var WrappedRange, WrappedTextRange;
  1.2676 +        var dom = api.dom;
  1.2677 +        var util = api.util;
  1.2678 +        var DomPosition = dom.DomPosition;
  1.2679 +        var DomRange = api.DomRange;
  1.2680 +        var getBody = dom.getBody;
  1.2681 +        var getContentDocument = dom.getContentDocument;
  1.2682 +        var isCharacterDataNode = dom.isCharacterDataNode;
  1.2683 +
  1.2684 +
  1.2685 +        /*----------------------------------------------------------------------------------------------------------------*/
  1.2686 +
  1.2687 +        if (api.features.implementsDomRange) {
  1.2688 +            // This is a wrapper around the browser's native DOM Range. It has two aims:
  1.2689 +            // - Provide workarounds for specific browser bugs
  1.2690 +            // - provide convenient extensions, which are inherited from Rangy's DomRange
  1.2691 +
  1.2692 +            (function() {
  1.2693 +                var rangeProto;
  1.2694 +                var rangeProperties = DomRange.rangeProperties;
  1.2695 +
  1.2696 +                function updateRangeProperties(range) {
  1.2697 +                    var i = rangeProperties.length, prop;
  1.2698 +                    while (i--) {
  1.2699 +                        prop = rangeProperties[i];
  1.2700 +                        range[prop] = range.nativeRange[prop];
  1.2701 +                    }
  1.2702 +                    // Fix for broken collapsed property in IE 9.
  1.2703 +                    range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
  1.2704 +                }
  1.2705 +
  1.2706 +                function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
  1.2707 +                    var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
  1.2708 +                    var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
  1.2709 +                    var nativeRangeDifferent = !range.equals(range.nativeRange);
  1.2710 +
  1.2711 +                    // Always set both boundaries for the benefit of IE9 (see issue 35)
  1.2712 +                    if (startMoved || endMoved || nativeRangeDifferent) {
  1.2713 +                        range.setEnd(endContainer, endOffset);
  1.2714 +                        range.setStart(startContainer, startOffset);
  1.2715 +                    }
  1.2716 +                }
  1.2717 +
  1.2718 +                var createBeforeAfterNodeSetter;
  1.2719 +
  1.2720 +                WrappedRange = function(range) {
  1.2721 +                    if (!range) {
  1.2722 +                        throw module.createError("WrappedRange: Range must be specified");
  1.2723 +                    }
  1.2724 +                    this.nativeRange = range;
  1.2725 +                    updateRangeProperties(this);
  1.2726 +                };
  1.2727 +
  1.2728 +                DomRange.createPrototypeRange(WrappedRange, updateNativeRange);
  1.2729 +
  1.2730 +                rangeProto = WrappedRange.prototype;
  1.2731 +
  1.2732 +                rangeProto.selectNode = function(node) {
  1.2733 +                    this.nativeRange.selectNode(node);
  1.2734 +                    updateRangeProperties(this);
  1.2735 +                };
  1.2736 +
  1.2737 +                rangeProto.cloneContents = function() {
  1.2738 +                    return this.nativeRange.cloneContents();
  1.2739 +                };
  1.2740 +
  1.2741 +                // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
  1.2742 +                // insertNode() is never delegated to the native range.
  1.2743 +
  1.2744 +                rangeProto.surroundContents = function(node) {
  1.2745 +                    this.nativeRange.surroundContents(node);
  1.2746 +                    updateRangeProperties(this);
  1.2747 +                };
  1.2748 +
  1.2749 +                rangeProto.collapse = function(isStart) {
  1.2750 +                    this.nativeRange.collapse(isStart);
  1.2751 +                    updateRangeProperties(this);
  1.2752 +                };
  1.2753 +
  1.2754 +                rangeProto.cloneRange = function() {
  1.2755 +                    return new WrappedRange(this.nativeRange.cloneRange());
  1.2756 +                };
  1.2757 +
  1.2758 +                rangeProto.refresh = function() {
  1.2759 +                    updateRangeProperties(this);
  1.2760 +                };
  1.2761 +
  1.2762 +                rangeProto.toString = function() {
  1.2763 +                    return this.nativeRange.toString();
  1.2764 +                };
  1.2765 +
  1.2766 +                // Create test range and node for feature detection
  1.2767 +
  1.2768 +                var testTextNode = document.createTextNode("test");
  1.2769 +                getBody(document).appendChild(testTextNode);
  1.2770 +                var range = document.createRange();
  1.2771 +
  1.2772 +                /*--------------------------------------------------------------------------------------------------------*/
  1.2773 +
  1.2774 +                // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
  1.2775 +                // correct for it
  1.2776 +
  1.2777 +                range.setStart(testTextNode, 0);
  1.2778 +                range.setEnd(testTextNode, 0);
  1.2779 +
  1.2780 +                try {
  1.2781 +                    range.setStart(testTextNode, 1);
  1.2782 +
  1.2783 +                    rangeProto.setStart = function(node, offset) {
  1.2784 +                        this.nativeRange.setStart(node, offset);
  1.2785 +                        updateRangeProperties(this);
  1.2786 +                    };
  1.2787 +
  1.2788 +                    rangeProto.setEnd = function(node, offset) {
  1.2789 +                        this.nativeRange.setEnd(node, offset);
  1.2790 +                        updateRangeProperties(this);
  1.2791 +                    };
  1.2792 +
  1.2793 +                    createBeforeAfterNodeSetter = function(name) {
  1.2794 +                        return function(node) {
  1.2795 +                            this.nativeRange[name](node);
  1.2796 +                            updateRangeProperties(this);
  1.2797 +                        };
  1.2798 +                    };
  1.2799 +
  1.2800 +                } catch(ex) {
  1.2801 +
  1.2802 +                    rangeProto.setStart = function(node, offset) {
  1.2803 +                        try {
  1.2804 +                            this.nativeRange.setStart(node, offset);
  1.2805 +                        } catch (ex) {
  1.2806 +                            this.nativeRange.setEnd(node, offset);
  1.2807 +                            this.nativeRange.setStart(node, offset);
  1.2808 +                        }
  1.2809 +                        updateRangeProperties(this);
  1.2810 +                    };
  1.2811 +
  1.2812 +                    rangeProto.setEnd = function(node, offset) {
  1.2813 +                        try {
  1.2814 +                            this.nativeRange.setEnd(node, offset);
  1.2815 +                        } catch (ex) {
  1.2816 +                            this.nativeRange.setStart(node, offset);
  1.2817 +                            this.nativeRange.setEnd(node, offset);
  1.2818 +                        }
  1.2819 +                        updateRangeProperties(this);
  1.2820 +                    };
  1.2821 +
  1.2822 +                    createBeforeAfterNodeSetter = function(name, oppositeName) {
  1.2823 +                        return function(node) {
  1.2824 +                            try {
  1.2825 +                                this.nativeRange[name](node);
  1.2826 +                            } catch (ex) {
  1.2827 +                                this.nativeRange[oppositeName](node);
  1.2828 +                                this.nativeRange[name](node);
  1.2829 +                            }
  1.2830 +                            updateRangeProperties(this);
  1.2831 +                        };
  1.2832 +                    };
  1.2833 +                }
  1.2834 +
  1.2835 +                rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
  1.2836 +                rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
  1.2837 +                rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
  1.2838 +                rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
  1.2839 +
  1.2840 +                /*--------------------------------------------------------------------------------------------------------*/
  1.2841 +
  1.2842 +                // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing
  1.2843 +                // whether the native implementation can be trusted
  1.2844 +                rangeProto.selectNodeContents = function(node) {
  1.2845 +                    this.setStartAndEnd(node, 0, dom.getNodeLength(node));
  1.2846 +                };
  1.2847 +
  1.2848 +                /*--------------------------------------------------------------------------------------------------------*/
  1.2849 +
  1.2850 +                // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for
  1.2851 +                // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
  1.2852 +
  1.2853 +                range.selectNodeContents(testTextNode);
  1.2854 +                range.setEnd(testTextNode, 3);
  1.2855 +
  1.2856 +                var range2 = document.createRange();
  1.2857 +                range2.selectNodeContents(testTextNode);
  1.2858 +                range2.setEnd(testTextNode, 4);
  1.2859 +                range2.setStart(testTextNode, 2);
  1.2860 +
  1.2861 +                if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&
  1.2862 +                        range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
  1.2863 +                    // This is the wrong way round, so correct for it
  1.2864 +
  1.2865 +                    rangeProto.compareBoundaryPoints = function(type, range) {
  1.2866 +                        range = range.nativeRange || range;
  1.2867 +                        if (type == range.START_TO_END) {
  1.2868 +                            type = range.END_TO_START;
  1.2869 +                        } else if (type == range.END_TO_START) {
  1.2870 +                            type = range.START_TO_END;
  1.2871 +                        }
  1.2872 +                        return this.nativeRange.compareBoundaryPoints(type, range);
  1.2873 +                    };
  1.2874 +                } else {
  1.2875 +                    rangeProto.compareBoundaryPoints = function(type, range) {
  1.2876 +                        return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
  1.2877 +                    };
  1.2878 +                }
  1.2879 +
  1.2880 +                /*--------------------------------------------------------------------------------------------------------*/
  1.2881 +
  1.2882 +                // Test for IE deleteContents() and extractContents() bug and correct it. See issue 107.
  1.2883 +
  1.2884 +                var el = document.createElement("div");
  1.2885 +                el.innerHTML = "123";
  1.2886 +                var textNode = el.firstChild;
  1.2887 +                var body = getBody(document);
  1.2888 +                body.appendChild(el);
  1.2889 +
  1.2890 +                range.setStart(textNode, 1);
  1.2891 +                range.setEnd(textNode, 2);
  1.2892 +                range.deleteContents();
  1.2893 +
  1.2894 +                if (textNode.data == "13") {
  1.2895 +                    // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
  1.2896 +                    // extractContents()
  1.2897 +                    rangeProto.deleteContents = function() {
  1.2898 +                        this.nativeRange.deleteContents();
  1.2899 +                        updateRangeProperties(this);
  1.2900 +                    };
  1.2901 +
  1.2902 +                    rangeProto.extractContents = function() {
  1.2903 +                        var frag = this.nativeRange.extractContents();
  1.2904 +                        updateRangeProperties(this);
  1.2905 +                        return frag;
  1.2906 +                    };
  1.2907 +                } else {
  1.2908 +                }
  1.2909 +
  1.2910 +                body.removeChild(el);
  1.2911 +                body = null;
  1.2912 +
  1.2913 +                /*--------------------------------------------------------------------------------------------------------*/
  1.2914 +
  1.2915 +                // Test for existence of createContextualFragment and delegate to it if it exists
  1.2916 +                if (util.isHostMethod(range, "createContextualFragment")) {
  1.2917 +                    rangeProto.createContextualFragment = function(fragmentStr) {
  1.2918 +                        return this.nativeRange.createContextualFragment(fragmentStr);
  1.2919 +                    };
  1.2920 +                }
  1.2921 +
  1.2922 +                /*--------------------------------------------------------------------------------------------------------*/
  1.2923 +
  1.2924 +                // Clean up
  1.2925 +                getBody(document).removeChild(testTextNode);
  1.2926 +
  1.2927 +                rangeProto.getName = function() {
  1.2928 +                    return "WrappedRange";
  1.2929 +                };
  1.2930 +
  1.2931 +                api.WrappedRange = WrappedRange;
  1.2932 +
  1.2933 +                api.createNativeRange = function(doc) {
  1.2934 +                    doc = getContentDocument(doc, module, "createNativeRange");
  1.2935 +                    return doc.createRange();
  1.2936 +                };
  1.2937 +            })();
  1.2938 +        }
  1.2939 +
  1.2940 +        if (api.features.implementsTextRange) {
  1.2941 +            /*
  1.2942 +            This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
  1.2943 +            method. For example, in the following (where pipes denote the selection boundaries):
  1.2944 +
  1.2945 +            <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
  1.2946 +
  1.2947 +            var range = document.selection.createRange();
  1.2948 +            alert(range.parentElement().id); // Should alert "ul" but alerts "b"
  1.2949 +
  1.2950 +            This method returns the common ancestor node of the following:
  1.2951 +            - the parentElement() of the textRange
  1.2952 +            - the parentElement() of the textRange after calling collapse(true)
  1.2953 +            - the parentElement() of the textRange after calling collapse(false)
  1.2954 +            */
  1.2955 +            var getTextRangeContainerElement = function(textRange) {
  1.2956 +                var parentEl = textRange.parentElement();
  1.2957 +                var range = textRange.duplicate();
  1.2958 +                range.collapse(true);
  1.2959 +                var startEl = range.parentElement();
  1.2960 +                range = textRange.duplicate();
  1.2961 +                range.collapse(false);
  1.2962 +                var endEl = range.parentElement();
  1.2963 +                var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
  1.2964 +
  1.2965 +                return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
  1.2966 +            };
  1.2967 +
  1.2968 +            var textRangeIsCollapsed = function(textRange) {
  1.2969 +                return textRange.compareEndPoints("StartToEnd", textRange) == 0;
  1.2970 +            };
  1.2971 +
  1.2972 +            // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started
  1.2973 +            // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/)
  1.2974 +            // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange
  1.2975 +            // bugs, handling for inputs and images, plus optimizations.
  1.2976 +            var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
  1.2977 +                var workingRange = textRange.duplicate();
  1.2978 +                workingRange.collapse(isStart);
  1.2979 +                var containerElement = workingRange.parentElement();
  1.2980 +
  1.2981 +                // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
  1.2982 +                // check for that
  1.2983 +                if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
  1.2984 +                    containerElement = wholeRangeContainerElement;
  1.2985 +                }
  1.2986 +
  1.2987 +
  1.2988 +                // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
  1.2989 +                // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
  1.2990 +                if (!containerElement.canHaveHTML) {
  1.2991 +                    var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
  1.2992 +                    return {
  1.2993 +                        boundaryPosition: pos,
  1.2994 +                        nodeInfo: {
  1.2995 +                            nodeIndex: pos.offset,
  1.2996 +                            containerElement: pos.node
  1.2997 +                        }
  1.2998 +                    };
  1.2999 +                }
  1.3000 +
  1.3001 +                var workingNode = dom.getDocument(containerElement).createElement("span");
  1.3002 +
  1.3003 +                // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
  1.3004 +                // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
  1.3005 +                if (workingNode.parentNode) {
  1.3006 +                    dom.removeNode(workingNode);
  1.3007 +                }
  1.3008 +
  1.3009 +                var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
  1.3010 +                var previousNode, nextNode, boundaryPosition, boundaryNode;
  1.3011 +                var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;
  1.3012 +                var childNodeCount = containerElement.childNodes.length;
  1.3013 +                var end = childNodeCount;
  1.3014 +
  1.3015 +                // Check end first. Code within the loop assumes that the endth child node of the container is definitely
  1.3016 +                // after the range boundary.
  1.3017 +                var nodeIndex = end;
  1.3018 +
  1.3019 +                while (true) {
  1.3020 +                    if (nodeIndex == childNodeCount) {
  1.3021 +                        containerElement.appendChild(workingNode);
  1.3022 +                    } else {
  1.3023 +                        containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
  1.3024 +                    }
  1.3025 +                    workingRange.moveToElementText(workingNode);
  1.3026 +                    comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
  1.3027 +                    if (comparison == 0 || start == end) {
  1.3028 +                        break;
  1.3029 +                    } else if (comparison == -1) {
  1.3030 +                        if (end == start + 1) {
  1.3031 +                            // We know the endth child node is after the range boundary, so we must be done.
  1.3032 +                            break;
  1.3033 +                        } else {
  1.3034 +                            start = nodeIndex;
  1.3035 +                        }
  1.3036 +                    } else {
  1.3037 +                        end = (end == start + 1) ? start : nodeIndex;
  1.3038 +                    }
  1.3039 +                    nodeIndex = Math.floor((start + end) / 2);
  1.3040 +                    containerElement.removeChild(workingNode);
  1.3041 +                }
  1.3042 +
  1.3043 +
  1.3044 +                // We've now reached or gone past the boundary of the text range we're interested in
  1.3045 +                // so have identified the node we want
  1.3046 +                boundaryNode = workingNode.nextSibling;
  1.3047 +
  1.3048 +                if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) {
  1.3049 +                    // This is a character data node (text, comment, cdata). The working range is collapsed at the start of
  1.3050 +                    // the node containing the text range's boundary, so we move the end of the working range to the
  1.3051 +                    // boundary point and measure the length of its text to get the boundary's offset within the node.
  1.3052 +                    workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
  1.3053 +
  1.3054 +                    var offset;
  1.3055 +
  1.3056 +                    if (/[\r\n]/.test(boundaryNode.data)) {
  1.3057 +                        /*
  1.3058 +                        For the particular case of a boundary within a text node containing rendered line breaks (within a
  1.3059 +                        <pre> element, for example), we need a slightly complicated approach to get the boundary's offset in
  1.3060 +                        IE. The facts:
  1.3061 +
  1.3062 +                        - Each line break is represented as \r in the text node's data/nodeValue properties
  1.3063 +                        - Each line break is represented as \r\n in the TextRange's 'text' property
  1.3064 +                        - The 'text' property of the TextRange does not contain trailing line breaks
  1.3065 +
  1.3066 +                        To get round the problem presented by the final fact above, we can use the fact that TextRange's
  1.3067 +                        moveStart() and moveEnd() methods return the actual number of characters moved, which is not
  1.3068 +                        necessarily the same as the number of characters it was instructed to move. The simplest approach is
  1.3069 +                        to use this to store the characters moved when moving both the start and end of the range to the
  1.3070 +                        start of the document body and subtracting the start offset from the end offset (the
  1.3071 +                        "move-negative-gazillion" method). However, this is extremely slow when the document is large and
  1.3072 +                        the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to
  1.3073 +                        the end of the document) has the same problem.
  1.3074 +
  1.3075 +                        Another approach that works is to use moveStart() to move the start boundary of the range up to the
  1.3076 +                        end boundary one character at a time and incrementing a counter with the value returned by the
  1.3077 +                        moveStart() call. However, the check for whether the start boundary has reached the end boundary is
  1.3078 +                        expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected
  1.3079 +                        by the location of the range within the document).
  1.3080 +
  1.3081 +                        The approach used below is a hybrid of the two methods above. It uses the fact that a string
  1.3082 +                        containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot
  1.3083 +                        be longer than the text of the TextRange, so the start of the range is moved that length initially
  1.3084 +                        and then a character at a time to make up for any trailing line breaks not contained in the 'text'
  1.3085 +                        property. This has good performance in most situations compared to the previous two methods.
  1.3086 +                        */
  1.3087 +                        var tempRange = workingRange.duplicate();
  1.3088 +                        var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
  1.3089 +
  1.3090 +                        offset = tempRange.moveStart("character", rangeLength);
  1.3091 +                        while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
  1.3092 +                            offset++;
  1.3093 +                            tempRange.moveStart("character", 1);
  1.3094 +                        }
  1.3095 +                    } else {
  1.3096 +                        offset = workingRange.text.length;
  1.3097 +                    }
  1.3098 +                    boundaryPosition = new DomPosition(boundaryNode, offset);
  1.3099 +                } else {
  1.3100 +
  1.3101 +                    // If the boundary immediately follows a character data node and this is the end boundary, we should favour
  1.3102 +                    // a position within that, and likewise for a start boundary preceding a character data node
  1.3103 +                    previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
  1.3104 +                    nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
  1.3105 +                    if (nextNode && isCharacterDataNode(nextNode)) {
  1.3106 +                        boundaryPosition = new DomPosition(nextNode, 0);
  1.3107 +                    } else if (previousNode && isCharacterDataNode(previousNode)) {
  1.3108 +                        boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
  1.3109 +                    } else {
  1.3110 +                        boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
  1.3111 +                    }
  1.3112 +                }
  1.3113 +
  1.3114 +                // Clean up
  1.3115 +                dom.removeNode(workingNode);
  1.3116 +
  1.3117 +                return {
  1.3118 +                    boundaryPosition: boundaryPosition,
  1.3119 +                    nodeInfo: {
  1.3120 +                        nodeIndex: nodeIndex,
  1.3121 +                        containerElement: containerElement
  1.3122 +                    }
  1.3123 +                };
  1.3124 +            };
  1.3125 +
  1.3126 +            // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that
  1.3127 +            // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
  1.3128 +            // (http://code.google.com/p/ierange/)
  1.3129 +            var createBoundaryTextRange = function(boundaryPosition, isStart) {
  1.3130 +                var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
  1.3131 +                var doc = dom.getDocument(boundaryPosition.node);
  1.3132 +                var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
  1.3133 +                var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
  1.3134 +
  1.3135 +                if (nodeIsDataNode) {
  1.3136 +                    boundaryNode = boundaryPosition.node;
  1.3137 +                    boundaryParent = boundaryNode.parentNode;
  1.3138 +                } else {
  1.3139 +                    childNodes = boundaryPosition.node.childNodes;
  1.3140 +                    boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
  1.3141 +                    boundaryParent = boundaryPosition.node;
  1.3142 +                }
  1.3143 +
  1.3144 +                // Position the range immediately before the node containing the boundary
  1.3145 +                workingNode = doc.createElement("span");
  1.3146 +
  1.3147 +                // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within
  1.3148 +                // the element rather than immediately before or after it
  1.3149 +                workingNode.innerHTML = "&#feff;";
  1.3150 +
  1.3151 +                // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
  1.3152 +                // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
  1.3153 +                if (boundaryNode) {
  1.3154 +                    boundaryParent.insertBefore(workingNode, boundaryNode);
  1.3155 +                } else {
  1.3156 +                    boundaryParent.appendChild(workingNode);
  1.3157 +                }
  1.3158 +
  1.3159 +                workingRange.moveToElementText(workingNode);
  1.3160 +                workingRange.collapse(!isStart);
  1.3161 +
  1.3162 +                // Clean up
  1.3163 +                boundaryParent.removeChild(workingNode);
  1.3164 +
  1.3165 +                // Move the working range to the text offset, if required
  1.3166 +                if (nodeIsDataNode) {
  1.3167 +                    workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
  1.3168 +                }
  1.3169 +
  1.3170 +                return workingRange;
  1.3171 +            };
  1.3172 +
  1.3173 +            /*------------------------------------------------------------------------------------------------------------*/
  1.3174 +
  1.3175 +            // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
  1.3176 +            // prototype
  1.3177 +
  1.3178 +            WrappedTextRange = function(textRange) {
  1.3179 +                this.textRange = textRange;
  1.3180 +                this.refresh();
  1.3181 +            };
  1.3182 +
  1.3183 +            WrappedTextRange.prototype = new DomRange(document);
  1.3184 +
  1.3185 +            WrappedTextRange.prototype.refresh = function() {
  1.3186 +                var start, end, startBoundary;
  1.3187 +
  1.3188 +                // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
  1.3189 +                var rangeContainerElement = getTextRangeContainerElement(this.textRange);
  1.3190 +
  1.3191 +                if (textRangeIsCollapsed(this.textRange)) {
  1.3192 +                    end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
  1.3193 +                        true).boundaryPosition;
  1.3194 +                } else {
  1.3195 +                    startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
  1.3196 +                    start = startBoundary.boundaryPosition;
  1.3197 +
  1.3198 +                    // An optimization used here is that if the start and end boundaries have the same parent element, the
  1.3199 +                    // search scope for the end boundary can be limited to exclude the portion of the element that precedes
  1.3200 +                    // the start boundary
  1.3201 +                    end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
  1.3202 +                        startBoundary.nodeInfo).boundaryPosition;
  1.3203 +                }
  1.3204 +
  1.3205 +                this.setStart(start.node, start.offset);
  1.3206 +                this.setEnd(end.node, end.offset);
  1.3207 +            };
  1.3208 +
  1.3209 +            WrappedTextRange.prototype.getName = function() {
  1.3210 +                return "WrappedTextRange";
  1.3211 +            };
  1.3212 +
  1.3213 +            DomRange.copyComparisonConstants(WrappedTextRange);
  1.3214 +
  1.3215 +            var rangeToTextRange = function(range) {
  1.3216 +                if (range.collapsed) {
  1.3217 +                    return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
  1.3218 +                } else {
  1.3219 +                    var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
  1.3220 +                    var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
  1.3221 +                    var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
  1.3222 +                    textRange.setEndPoint("StartToStart", startRange);
  1.3223 +                    textRange.setEndPoint("EndToEnd", endRange);
  1.3224 +                    return textRange;
  1.3225 +                }
  1.3226 +            };
  1.3227 +
  1.3228 +            WrappedTextRange.rangeToTextRange = rangeToTextRange;
  1.3229 +
  1.3230 +            WrappedTextRange.prototype.toTextRange = function() {
  1.3231 +                return rangeToTextRange(this);
  1.3232 +            };
  1.3233 +
  1.3234 +            api.WrappedTextRange = WrappedTextRange;
  1.3235 +
  1.3236 +            // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
  1.3237 +            // implementation to use by default.
  1.3238 +            if (!api.features.implementsDomRange || api.config.preferTextRange) {
  1.3239 +                // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
  1.3240 +                var globalObj = (function(f) { return f("return this;")(); })(Function);
  1.3241 +                if (typeof globalObj.Range == "undefined") {
  1.3242 +                    globalObj.Range = WrappedTextRange;
  1.3243 +                }
  1.3244 +
  1.3245 +                api.createNativeRange = function(doc) {
  1.3246 +                    doc = getContentDocument(doc, module, "createNativeRange");
  1.3247 +                    return getBody(doc).createTextRange();
  1.3248 +                };
  1.3249 +
  1.3250 +                api.WrappedRange = WrappedTextRange;
  1.3251 +            }
  1.3252 +        }
  1.3253 +
  1.3254 +        api.createRange = function(doc) {
  1.3255 +            doc = getContentDocument(doc, module, "createRange");
  1.3256 +            return new api.WrappedRange(api.createNativeRange(doc));
  1.3257 +        };
  1.3258 +
  1.3259 +        api.createRangyRange = function(doc) {
  1.3260 +            doc = getContentDocument(doc, module, "createRangyRange");
  1.3261 +            return new DomRange(doc);
  1.3262 +        };
  1.3263 +
  1.3264 +        util.createAliasForDeprecatedMethod(api, "createIframeRange", "createRange");
  1.3265 +        util.createAliasForDeprecatedMethod(api, "createIframeRangyRange", "createRangyRange");
  1.3266 +
  1.3267 +        api.addShimListener(function(win) {
  1.3268 +            var doc = win.document;
  1.3269 +            if (typeof doc.createRange == "undefined") {
  1.3270 +                doc.createRange = function() {
  1.3271 +                    return api.createRange(doc);
  1.3272 +                };
  1.3273 +            }
  1.3274 +            doc = win = null;
  1.3275 +        });
  1.3276 +    });
  1.3277 +
  1.3278 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.3279 +
  1.3280 +    // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
  1.3281 +    // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
  1.3282 +    api.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
  1.3283 +        api.config.checkSelectionRanges = true;
  1.3284 +
  1.3285 +        var BOOLEAN = "boolean";
  1.3286 +        var NUMBER = "number";
  1.3287 +        var dom = api.dom;
  1.3288 +        var util = api.util;
  1.3289 +        var isHostMethod = util.isHostMethod;
  1.3290 +        var DomRange = api.DomRange;
  1.3291 +        var WrappedRange = api.WrappedRange;
  1.3292 +        var DOMException = api.DOMException;
  1.3293 +        var DomPosition = dom.DomPosition;
  1.3294 +        var getNativeSelection;
  1.3295 +        var selectionIsCollapsed;
  1.3296 +        var features = api.features;
  1.3297 +        var CONTROL = "Control";
  1.3298 +        var getDocument = dom.getDocument;
  1.3299 +        var getBody = dom.getBody;
  1.3300 +        var rangesEqual = DomRange.rangesEqual;
  1.3301 +
  1.3302 +
  1.3303 +        // Utility function to support direction parameters in the API that may be a string ("backward", "backwards",
  1.3304 +        // "forward" or "forwards") or a Boolean (true for backwards).
  1.3305 +        function isDirectionBackward(dir) {
  1.3306 +            return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
  1.3307 +        }
  1.3308 +
  1.3309 +        function getWindow(win, methodName) {
  1.3310 +            if (!win) {
  1.3311 +                return window;
  1.3312 +            } else if (dom.isWindow(win)) {
  1.3313 +                return win;
  1.3314 +            } else if (win instanceof WrappedSelection) {
  1.3315 +                return win.win;
  1.3316 +            } else {
  1.3317 +                var doc = dom.getContentDocument(win, module, methodName);
  1.3318 +                return dom.getWindow(doc);
  1.3319 +            }
  1.3320 +        }
  1.3321 +
  1.3322 +        function getWinSelection(winParam) {
  1.3323 +            return getWindow(winParam, "getWinSelection").getSelection();
  1.3324 +        }
  1.3325 +
  1.3326 +        function getDocSelection(winParam) {
  1.3327 +            return getWindow(winParam, "getDocSelection").document.selection;
  1.3328 +        }
  1.3329 +
  1.3330 +        function winSelectionIsBackward(sel) {
  1.3331 +            var backward = false;
  1.3332 +            if (sel.anchorNode) {
  1.3333 +                backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
  1.3334 +            }
  1.3335 +            return backward;
  1.3336 +        }
  1.3337 +
  1.3338 +        // Test for the Range/TextRange and Selection features required
  1.3339 +        // Test for ability to retrieve selection
  1.3340 +        var implementsWinGetSelection = isHostMethod(window, "getSelection"),
  1.3341 +            implementsDocSelection = util.isHostObject(document, "selection");
  1.3342 +
  1.3343 +        features.implementsWinGetSelection = implementsWinGetSelection;
  1.3344 +        features.implementsDocSelection = implementsDocSelection;
  1.3345 +
  1.3346 +        var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
  1.3347 +
  1.3348 +        if (useDocumentSelection) {
  1.3349 +            getNativeSelection = getDocSelection;
  1.3350 +            api.isSelectionValid = function(winParam) {
  1.3351 +                var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
  1.3352 +
  1.3353 +                // Check whether the selection TextRange is actually contained within the correct document
  1.3354 +                return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
  1.3355 +            };
  1.3356 +        } else if (implementsWinGetSelection) {
  1.3357 +            getNativeSelection = getWinSelection;
  1.3358 +            api.isSelectionValid = function() {
  1.3359 +                return true;
  1.3360 +            };
  1.3361 +        } else {
  1.3362 +            module.fail("Neither document.selection or window.getSelection() detected.");
  1.3363 +            return false;
  1.3364 +        }
  1.3365 +
  1.3366 +        api.getNativeSelection = getNativeSelection;
  1.3367 +
  1.3368 +        var testSelection = getNativeSelection();
  1.3369 +
  1.3370 +        // In Firefox, the selection is null in an iframe with display: none. See issue #138.
  1.3371 +        if (!testSelection) {
  1.3372 +            module.fail("Native selection was null (possibly issue 138?)");
  1.3373 +            return false;
  1.3374 +        }
  1.3375 +
  1.3376 +        var testRange = api.createNativeRange(document);
  1.3377 +        var body = getBody(document);
  1.3378 +
  1.3379 +        // Obtaining a range from a selection
  1.3380 +        var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
  1.3381 +            ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
  1.3382 +
  1.3383 +        features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
  1.3384 +
  1.3385 +        // Test for existence of native selection extend() method
  1.3386 +        var selectionHasExtend = isHostMethod(testSelection, "extend");
  1.3387 +        features.selectionHasExtend = selectionHasExtend;
  1.3388 +
  1.3389 +        // Test if rangeCount exists
  1.3390 +        var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);
  1.3391 +        features.selectionHasRangeCount = selectionHasRangeCount;
  1.3392 +
  1.3393 +        var selectionSupportsMultipleRanges = false;
  1.3394 +        var collapsedNonEditableSelectionsSupported = true;
  1.3395 +
  1.3396 +        var addRangeBackwardToNative = selectionHasExtend ?
  1.3397 +            function(nativeSelection, range) {
  1.3398 +                var doc = DomRange.getRangeDocument(range);
  1.3399 +                var endRange = api.createRange(doc);
  1.3400 +                endRange.collapseToPoint(range.endContainer, range.endOffset);
  1.3401 +                nativeSelection.addRange(getNativeRange(endRange));
  1.3402 +                nativeSelection.extend(range.startContainer, range.startOffset);
  1.3403 +            } : null;
  1.3404 +
  1.3405 +        if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
  1.3406 +                typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
  1.3407 +
  1.3408 +            (function() {
  1.3409 +                // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
  1.3410 +                // performed on the current document's selection. See issue 109.
  1.3411 +
  1.3412 +                // Note also that if a selection previously existed, it is wiped and later restored by these tests. This
  1.3413 +                // will result in the selection direction begin reversed if the original selection was backwards and the
  1.3414 +                // browser does not support setting backwards selections (Internet Explorer, I'm looking at you).
  1.3415 +                var sel = window.getSelection();
  1.3416 +                if (sel) {
  1.3417 +                    // Store the current selection
  1.3418 +                    var originalSelectionRangeCount = sel.rangeCount;
  1.3419 +                    var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);
  1.3420 +                    var originalSelectionRanges = [];
  1.3421 +                    var originalSelectionBackward = winSelectionIsBackward(sel);
  1.3422 +                    for (var i = 0; i < originalSelectionRangeCount; ++i) {
  1.3423 +                        originalSelectionRanges[i] = sel.getRangeAt(i);
  1.3424 +                    }
  1.3425 +
  1.3426 +                    // Create some test elements
  1.3427 +                    var testEl = dom.createTestElement(document, "", false);
  1.3428 +                    var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
  1.3429 +
  1.3430 +                    // Test whether the native selection will allow a collapsed selection within a non-editable element
  1.3431 +                    var r1 = document.createRange();
  1.3432 +
  1.3433 +                    r1.setStart(textNode, 1);
  1.3434 +                    r1.collapse(true);
  1.3435 +                    sel.removeAllRanges();
  1.3436 +                    sel.addRange(r1);
  1.3437 +                    collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
  1.3438 +                    sel.removeAllRanges();
  1.3439 +
  1.3440 +                    // Test whether the native selection is capable of supporting multiple ranges.
  1.3441 +                    if (!selectionHasMultipleRanges) {
  1.3442 +                        // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a
  1.3443 +                        // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's
  1.3444 +                        // nothing we can do about this while retaining the feature test so we have to resort to a browser
  1.3445 +                        // sniff. I'm not happy about it. See
  1.3446 +                        // https://code.google.com/p/chromium/issues/detail?id=399791
  1.3447 +                        var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /);
  1.3448 +                        if (chromeMatch && parseInt(chromeMatch[1]) >= 36) {
  1.3449 +                            selectionSupportsMultipleRanges = false;
  1.3450 +                        } else {
  1.3451 +                            var r2 = r1.cloneRange();
  1.3452 +                            r1.setStart(textNode, 0);
  1.3453 +                            r2.setEnd(textNode, 3);
  1.3454 +                            r2.setStart(textNode, 2);
  1.3455 +                            sel.addRange(r1);
  1.3456 +                            sel.addRange(r2);
  1.3457 +                            selectionSupportsMultipleRanges = (sel.rangeCount == 2);
  1.3458 +                        }
  1.3459 +                    }
  1.3460 +
  1.3461 +                    // Clean up
  1.3462 +                    dom.removeNode(testEl);
  1.3463 +                    sel.removeAllRanges();
  1.3464 +
  1.3465 +                    for (i = 0; i < originalSelectionRangeCount; ++i) {
  1.3466 +                        if (i == 0 && originalSelectionBackward) {
  1.3467 +                            if (addRangeBackwardToNative) {
  1.3468 +                                addRangeBackwardToNative(sel, originalSelectionRanges[i]);
  1.3469 +                            } else {
  1.3470 +                                api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend");
  1.3471 +                                sel.addRange(originalSelectionRanges[i]);
  1.3472 +                            }
  1.3473 +                        } else {
  1.3474 +                            sel.addRange(originalSelectionRanges[i]);
  1.3475 +                        }
  1.3476 +                    }
  1.3477 +                }
  1.3478 +            })();
  1.3479 +        }
  1.3480 +
  1.3481 +        features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
  1.3482 +        features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
  1.3483 +
  1.3484 +        // ControlRanges
  1.3485 +        var implementsControlRange = false, testControlRange;
  1.3486 +
  1.3487 +        if (body && isHostMethod(body, "createControlRange")) {
  1.3488 +            testControlRange = body.createControlRange();
  1.3489 +            if (util.areHostProperties(testControlRange, ["item", "add"])) {
  1.3490 +                implementsControlRange = true;
  1.3491 +            }
  1.3492 +        }
  1.3493 +        features.implementsControlRange = implementsControlRange;
  1.3494 +
  1.3495 +        // Selection collapsedness
  1.3496 +        if (selectionHasAnchorAndFocus) {
  1.3497 +            selectionIsCollapsed = function(sel) {
  1.3498 +                return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
  1.3499 +            };
  1.3500 +        } else {
  1.3501 +            selectionIsCollapsed = function(sel) {
  1.3502 +                return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
  1.3503 +            };
  1.3504 +        }
  1.3505 +
  1.3506 +        function updateAnchorAndFocusFromRange(sel, range, backward) {
  1.3507 +            var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
  1.3508 +            sel.anchorNode = range[anchorPrefix + "Container"];
  1.3509 +            sel.anchorOffset = range[anchorPrefix + "Offset"];
  1.3510 +            sel.focusNode = range[focusPrefix + "Container"];
  1.3511 +            sel.focusOffset = range[focusPrefix + "Offset"];
  1.3512 +        }
  1.3513 +
  1.3514 +        function updateAnchorAndFocusFromNativeSelection(sel) {
  1.3515 +            var nativeSel = sel.nativeSelection;
  1.3516 +            sel.anchorNode = nativeSel.anchorNode;
  1.3517 +            sel.anchorOffset = nativeSel.anchorOffset;
  1.3518 +            sel.focusNode = nativeSel.focusNode;
  1.3519 +            sel.focusOffset = nativeSel.focusOffset;
  1.3520 +        }
  1.3521 +
  1.3522 +        function updateEmptySelection(sel) {
  1.3523 +            sel.anchorNode = sel.focusNode = null;
  1.3524 +            sel.anchorOffset = sel.focusOffset = 0;
  1.3525 +            sel.rangeCount = 0;
  1.3526 +            sel.isCollapsed = true;
  1.3527 +            sel._ranges.length = 0;
  1.3528 +        }
  1.3529 +
  1.3530 +        function getNativeRange(range) {
  1.3531 +            var nativeRange;
  1.3532 +            if (range instanceof DomRange) {
  1.3533 +                nativeRange = api.createNativeRange(range.getDocument());
  1.3534 +                nativeRange.setEnd(range.endContainer, range.endOffset);
  1.3535 +                nativeRange.setStart(range.startContainer, range.startOffset);
  1.3536 +            } else if (range instanceof WrappedRange) {
  1.3537 +                nativeRange = range.nativeRange;
  1.3538 +            } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
  1.3539 +                nativeRange = range;
  1.3540 +            }
  1.3541 +            return nativeRange;
  1.3542 +        }
  1.3543 +
  1.3544 +        function rangeContainsSingleElement(rangeNodes) {
  1.3545 +            if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
  1.3546 +                return false;
  1.3547 +            }
  1.3548 +            for (var i = 1, len = rangeNodes.length; i < len; ++i) {
  1.3549 +                if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
  1.3550 +                    return false;
  1.3551 +                }
  1.3552 +            }
  1.3553 +            return true;
  1.3554 +        }
  1.3555 +
  1.3556 +        function getSingleElementFromRange(range) {
  1.3557 +            var nodes = range.getNodes();
  1.3558 +            if (!rangeContainsSingleElement(nodes)) {
  1.3559 +                throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
  1.3560 +            }
  1.3561 +            return nodes[0];
  1.3562 +        }
  1.3563 +
  1.3564 +        // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
  1.3565 +        function isTextRange(range) {
  1.3566 +            return !!range && typeof range.text != "undefined";
  1.3567 +        }
  1.3568 +
  1.3569 +        function updateFromTextRange(sel, range) {
  1.3570 +            // Create a Range from the selected TextRange
  1.3571 +            var wrappedRange = new WrappedRange(range);
  1.3572 +            sel._ranges = [wrappedRange];
  1.3573 +
  1.3574 +            updateAnchorAndFocusFromRange(sel, wrappedRange, false);
  1.3575 +            sel.rangeCount = 1;
  1.3576 +            sel.isCollapsed = wrappedRange.collapsed;
  1.3577 +        }
  1.3578 +
  1.3579 +        function updateControlSelection(sel) {
  1.3580 +            // Update the wrapped selection based on what's now in the native selection
  1.3581 +            sel._ranges.length = 0;
  1.3582 +            if (sel.docSelection.type == "None") {
  1.3583 +                updateEmptySelection(sel);
  1.3584 +            } else {
  1.3585 +                var controlRange = sel.docSelection.createRange();
  1.3586 +                if (isTextRange(controlRange)) {
  1.3587 +                    // This case (where the selection type is "Control" and calling createRange() on the selection returns
  1.3588 +                    // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
  1.3589 +                    // ControlRange have been removed from the ControlRange and removed from the document.
  1.3590 +                    updateFromTextRange(sel, controlRange);
  1.3591 +                } else {
  1.3592 +                    sel.rangeCount = controlRange.length;
  1.3593 +                    var range, doc = getDocument(controlRange.item(0));
  1.3594 +                    for (var i = 0; i < sel.rangeCount; ++i) {
  1.3595 +                        range = api.createRange(doc);
  1.3596 +                        range.selectNode(controlRange.item(i));
  1.3597 +                        sel._ranges.push(range);
  1.3598 +                    }
  1.3599 +                    sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
  1.3600 +                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
  1.3601 +                }
  1.3602 +            }
  1.3603 +        }
  1.3604 +
  1.3605 +        function addRangeToControlSelection(sel, range) {
  1.3606 +            var controlRange = sel.docSelection.createRange();
  1.3607 +            var rangeElement = getSingleElementFromRange(range);
  1.3608 +
  1.3609 +            // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
  1.3610 +            // contained by the supplied range
  1.3611 +            var doc = getDocument(controlRange.item(0));
  1.3612 +            var newControlRange = getBody(doc).createControlRange();
  1.3613 +            for (var i = 0, len = controlRange.length; i < len; ++i) {
  1.3614 +                newControlRange.add(controlRange.item(i));
  1.3615 +            }
  1.3616 +            try {
  1.3617 +                newControlRange.add(rangeElement);
  1.3618 +            } catch (ex) {
  1.3619 +                throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
  1.3620 +            }
  1.3621 +            newControlRange.select();
  1.3622 +
  1.3623 +            // Update the wrapped selection based on what's now in the native selection
  1.3624 +            updateControlSelection(sel);
  1.3625 +        }
  1.3626 +
  1.3627 +        var getSelectionRangeAt;
  1.3628 +
  1.3629 +        if (isHostMethod(testSelection, "getRangeAt")) {
  1.3630 +            // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
  1.3631 +            // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
  1.3632 +            // lesson to us all, especially me.
  1.3633 +            getSelectionRangeAt = function(sel, index) {
  1.3634 +                try {
  1.3635 +                    return sel.getRangeAt(index);
  1.3636 +                } catch (ex) {
  1.3637 +                    return null;
  1.3638 +                }
  1.3639 +            };
  1.3640 +        } else if (selectionHasAnchorAndFocus) {
  1.3641 +            getSelectionRangeAt = function(sel) {
  1.3642 +                var doc = getDocument(sel.anchorNode);
  1.3643 +                var range = api.createRange(doc);
  1.3644 +                range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
  1.3645 +
  1.3646 +                // Handle the case when the selection was selected backwards (from the end to the start in the
  1.3647 +                // document)
  1.3648 +                if (range.collapsed !== this.isCollapsed) {
  1.3649 +                    range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
  1.3650 +                }
  1.3651 +
  1.3652 +                return range;
  1.3653 +            };
  1.3654 +        }
  1.3655 +
  1.3656 +        function WrappedSelection(selection, docSelection, win) {
  1.3657 +            this.nativeSelection = selection;
  1.3658 +            this.docSelection = docSelection;
  1.3659 +            this._ranges = [];
  1.3660 +            this.win = win;
  1.3661 +            this.refresh();
  1.3662 +        }
  1.3663 +
  1.3664 +        WrappedSelection.prototype = api.selectionPrototype;
  1.3665 +
  1.3666 +        function deleteProperties(sel) {
  1.3667 +            sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
  1.3668 +            sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
  1.3669 +            sel.detached = true;
  1.3670 +        }
  1.3671 +
  1.3672 +        var cachedRangySelections = [];
  1.3673 +
  1.3674 +        function actOnCachedSelection(win, action) {
  1.3675 +            var i = cachedRangySelections.length, cached, sel;
  1.3676 +            while (i--) {
  1.3677 +                cached = cachedRangySelections[i];
  1.3678 +                sel = cached.selection;
  1.3679 +                if (action == "deleteAll") {
  1.3680 +                    deleteProperties(sel);
  1.3681 +                } else if (cached.win == win) {
  1.3682 +                    if (action == "delete") {
  1.3683 +                        cachedRangySelections.splice(i, 1);
  1.3684 +                        return true;
  1.3685 +                    } else {
  1.3686 +                        return sel;
  1.3687 +                    }
  1.3688 +                }
  1.3689 +            }
  1.3690 +            if (action == "deleteAll") {
  1.3691 +                cachedRangySelections.length = 0;
  1.3692 +            }
  1.3693 +            return null;
  1.3694 +        }
  1.3695 +
  1.3696 +        var getSelection = function(win) {
  1.3697 +            // Check if the parameter is a Rangy Selection object
  1.3698 +            if (win && win instanceof WrappedSelection) {
  1.3699 +                win.refresh();
  1.3700 +                return win;
  1.3701 +            }
  1.3702 +
  1.3703 +            win = getWindow(win, "getNativeSelection");
  1.3704 +
  1.3705 +            var sel = actOnCachedSelection(win);
  1.3706 +            var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
  1.3707 +            if (sel) {
  1.3708 +                sel.nativeSelection = nativeSel;
  1.3709 +                sel.docSelection = docSel;
  1.3710 +                sel.refresh();
  1.3711 +            } else {
  1.3712 +                sel = new WrappedSelection(nativeSel, docSel, win);
  1.3713 +                cachedRangySelections.push( { win: win, selection: sel } );
  1.3714 +            }
  1.3715 +            return sel;
  1.3716 +        };
  1.3717 +
  1.3718 +        api.getSelection = getSelection;
  1.3719 +
  1.3720 +        util.createAliasForDeprecatedMethod(api, "getIframeSelection", "getSelection");
  1.3721 +
  1.3722 +        var selProto = WrappedSelection.prototype;
  1.3723 +
  1.3724 +        function createControlSelection(sel, ranges) {
  1.3725 +            // Ensure that the selection becomes of type "Control"
  1.3726 +            var doc = getDocument(ranges[0].startContainer);
  1.3727 +            var controlRange = getBody(doc).createControlRange();
  1.3728 +            for (var i = 0, el, len = ranges.length; i < len; ++i) {
  1.3729 +                el = getSingleElementFromRange(ranges[i]);
  1.3730 +                try {
  1.3731 +                    controlRange.add(el);
  1.3732 +                } catch (ex) {
  1.3733 +                    throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)");
  1.3734 +                }
  1.3735 +            }
  1.3736 +            controlRange.select();
  1.3737 +
  1.3738 +            // Update the wrapped selection based on what's now in the native selection
  1.3739 +            updateControlSelection(sel);
  1.3740 +        }
  1.3741 +
  1.3742 +        // Selecting a range
  1.3743 +        if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
  1.3744 +            selProto.removeAllRanges = function() {
  1.3745 +                this.nativeSelection.removeAllRanges();
  1.3746 +                updateEmptySelection(this);
  1.3747 +            };
  1.3748 +
  1.3749 +            var addRangeBackward = function(sel, range) {
  1.3750 +                addRangeBackwardToNative(sel.nativeSelection, range);
  1.3751 +                sel.refresh();
  1.3752 +            };
  1.3753 +
  1.3754 +            if (selectionHasRangeCount) {
  1.3755 +                selProto.addRange = function(range, direction) {
  1.3756 +                    if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
  1.3757 +                        addRangeToControlSelection(this, range);
  1.3758 +                    } else {
  1.3759 +                        if (isDirectionBackward(direction) && selectionHasExtend) {
  1.3760 +                            addRangeBackward(this, range);
  1.3761 +                        } else {
  1.3762 +                            var previousRangeCount;
  1.3763 +                            if (selectionSupportsMultipleRanges) {
  1.3764 +                                previousRangeCount = this.rangeCount;
  1.3765 +                            } else {
  1.3766 +                                this.removeAllRanges();
  1.3767 +                                previousRangeCount = 0;
  1.3768 +                            }
  1.3769 +                            // Clone the native range so that changing the selected range does not affect the selection.
  1.3770 +                            // This is contrary to the spec but is the only way to achieve consistency between browsers. See
  1.3771 +                            // issue 80.
  1.3772 +                            var clonedNativeRange = getNativeRange(range).cloneRange();
  1.3773 +                            try {
  1.3774 +                                this.nativeSelection.addRange(clonedNativeRange);
  1.3775 +                            } catch (ex) {
  1.3776 +                            }
  1.3777 +
  1.3778 +                            // Check whether adding the range was successful
  1.3779 +                            this.rangeCount = this.nativeSelection.rangeCount;
  1.3780 +
  1.3781 +                            if (this.rangeCount == previousRangeCount + 1) {
  1.3782 +                                // The range was added successfully
  1.3783 +
  1.3784 +                                // Check whether the range that we added to the selection is reflected in the last range extracted from
  1.3785 +                                // the selection
  1.3786 +                                if (api.config.checkSelectionRanges) {
  1.3787 +                                    var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
  1.3788 +                                    if (nativeRange && !rangesEqual(nativeRange, range)) {
  1.3789 +                                        // Happens in WebKit with, for example, a selection placed at the start of a text node
  1.3790 +                                        range = new WrappedRange(nativeRange);
  1.3791 +                                    }
  1.3792 +                                }
  1.3793 +                                this._ranges[this.rangeCount - 1] = range;
  1.3794 +                                updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
  1.3795 +                                this.isCollapsed = selectionIsCollapsed(this);
  1.3796 +                            } else {
  1.3797 +                                // The range was not added successfully. The simplest thing is to refresh
  1.3798 +                                this.refresh();
  1.3799 +                            }
  1.3800 +                        }
  1.3801 +                    }
  1.3802 +                };
  1.3803 +            } else {
  1.3804 +                selProto.addRange = function(range, direction) {
  1.3805 +                    if (isDirectionBackward(direction) && selectionHasExtend) {
  1.3806 +                        addRangeBackward(this, range);
  1.3807 +                    } else {
  1.3808 +                        this.nativeSelection.addRange(getNativeRange(range));
  1.3809 +                        this.refresh();
  1.3810 +                    }
  1.3811 +                };
  1.3812 +            }
  1.3813 +
  1.3814 +            selProto.setRanges = function(ranges) {
  1.3815 +                if (implementsControlRange && implementsDocSelection && ranges.length > 1) {
  1.3816 +                    createControlSelection(this, ranges);
  1.3817 +                } else {
  1.3818 +                    this.removeAllRanges();
  1.3819 +                    for (var i = 0, len = ranges.length; i < len; ++i) {
  1.3820 +                        this.addRange(ranges[i]);
  1.3821 +                    }
  1.3822 +                }
  1.3823 +            };
  1.3824 +        } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
  1.3825 +                   implementsControlRange && useDocumentSelection) {
  1.3826 +
  1.3827 +            selProto.removeAllRanges = function() {
  1.3828 +                // Added try/catch as fix for issue #21
  1.3829 +                try {
  1.3830 +                    this.docSelection.empty();
  1.3831 +
  1.3832 +                    // Check for empty() not working (issue #24)
  1.3833 +                    if (this.docSelection.type != "None") {
  1.3834 +                        // Work around failure to empty a control selection by instead selecting a TextRange and then
  1.3835 +                        // calling empty()
  1.3836 +                        var doc;
  1.3837 +                        if (this.anchorNode) {
  1.3838 +                            doc = getDocument(this.anchorNode);
  1.3839 +                        } else if (this.docSelection.type == CONTROL) {
  1.3840 +                            var controlRange = this.docSelection.createRange();
  1.3841 +                            if (controlRange.length) {
  1.3842 +                                doc = getDocument( controlRange.item(0) );
  1.3843 +                            }
  1.3844 +                        }
  1.3845 +                        if (doc) {
  1.3846 +                            var textRange = getBody(doc).createTextRange();
  1.3847 +                            textRange.select();
  1.3848 +                            this.docSelection.empty();
  1.3849 +                        }
  1.3850 +                    }
  1.3851 +                } catch(ex) {}
  1.3852 +                updateEmptySelection(this);
  1.3853 +            };
  1.3854 +
  1.3855 +            selProto.addRange = function(range) {
  1.3856 +                if (this.docSelection.type == CONTROL) {
  1.3857 +                    addRangeToControlSelection(this, range);
  1.3858 +                } else {
  1.3859 +                    api.WrappedTextRange.rangeToTextRange(range).select();
  1.3860 +                    this._ranges[0] = range;
  1.3861 +                    this.rangeCount = 1;
  1.3862 +                    this.isCollapsed = this._ranges[0].collapsed;
  1.3863 +                    updateAnchorAndFocusFromRange(this, range, false);
  1.3864 +                }
  1.3865 +            };
  1.3866 +
  1.3867 +            selProto.setRanges = function(ranges) {
  1.3868 +                this.removeAllRanges();
  1.3869 +                var rangeCount = ranges.length;
  1.3870 +                if (rangeCount > 1) {
  1.3871 +                    createControlSelection(this, ranges);
  1.3872 +                } else if (rangeCount) {
  1.3873 +                    this.addRange(ranges[0]);
  1.3874 +                }
  1.3875 +            };
  1.3876 +        } else {
  1.3877 +            module.fail("No means of selecting a Range or TextRange was found");
  1.3878 +            return false;
  1.3879 +        }
  1.3880 +
  1.3881 +        selProto.getRangeAt = function(index) {
  1.3882 +            if (index < 0 || index >= this.rangeCount) {
  1.3883 +                throw new DOMException("INDEX_SIZE_ERR");
  1.3884 +            } else {
  1.3885 +                // Clone the range to preserve selection-range independence. See issue 80.
  1.3886 +                return this._ranges[index].cloneRange();
  1.3887 +            }
  1.3888 +        };
  1.3889 +
  1.3890 +        var refreshSelection;
  1.3891 +
  1.3892 +        if (useDocumentSelection) {
  1.3893 +            refreshSelection = function(sel) {
  1.3894 +                var range;
  1.3895 +                if (api.isSelectionValid(sel.win)) {
  1.3896 +                    range = sel.docSelection.createRange();
  1.3897 +                } else {
  1.3898 +                    range = getBody(sel.win.document).createTextRange();
  1.3899 +                    range.collapse(true);
  1.3900 +                }
  1.3901 +
  1.3902 +                if (sel.docSelection.type == CONTROL) {
  1.3903 +                    updateControlSelection(sel);
  1.3904 +                } else if (isTextRange(range)) {
  1.3905 +                    updateFromTextRange(sel, range);
  1.3906 +                } else {
  1.3907 +                    updateEmptySelection(sel);
  1.3908 +                }
  1.3909 +            };
  1.3910 +        } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) {
  1.3911 +            refreshSelection = function(sel) {
  1.3912 +                if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
  1.3913 +                    updateControlSelection(sel);
  1.3914 +                } else {
  1.3915 +                    sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
  1.3916 +                    if (sel.rangeCount) {
  1.3917 +                        for (var i = 0, len = sel.rangeCount; i < len; ++i) {
  1.3918 +                            sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
  1.3919 +                        }
  1.3920 +                        updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
  1.3921 +                        sel.isCollapsed = selectionIsCollapsed(sel);
  1.3922 +                    } else {
  1.3923 +                        updateEmptySelection(sel);
  1.3924 +                    }
  1.3925 +                }
  1.3926 +            };
  1.3927 +        } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
  1.3928 +            refreshSelection = function(sel) {
  1.3929 +                var range, nativeSel = sel.nativeSelection;
  1.3930 +                if (nativeSel.anchorNode) {
  1.3931 +                    range = getSelectionRangeAt(nativeSel, 0);
  1.3932 +                    sel._ranges = [range];
  1.3933 +                    sel.rangeCount = 1;
  1.3934 +                    updateAnchorAndFocusFromNativeSelection(sel);
  1.3935 +                    sel.isCollapsed = selectionIsCollapsed(sel);
  1.3936 +                } else {
  1.3937 +                    updateEmptySelection(sel);
  1.3938 +                }
  1.3939 +            };
  1.3940 +        } else {
  1.3941 +            module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
  1.3942 +            return false;
  1.3943 +        }
  1.3944 +
  1.3945 +        selProto.refresh = function(checkForChanges) {
  1.3946 +            var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
  1.3947 +            var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
  1.3948 +
  1.3949 +            refreshSelection(this);
  1.3950 +            if (checkForChanges) {
  1.3951 +                // Check the range count first
  1.3952 +                var i = oldRanges.length;
  1.3953 +                if (i != this._ranges.length) {
  1.3954 +                    return true;
  1.3955 +                }
  1.3956 +
  1.3957 +                // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
  1.3958 +                // ranges after this
  1.3959 +                if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
  1.3960 +                    return true;
  1.3961 +                }
  1.3962 +
  1.3963 +                // Finally, compare each range in turn
  1.3964 +                while (i--) {
  1.3965 +                    if (!rangesEqual(oldRanges[i], this._ranges[i])) {
  1.3966 +                        return true;
  1.3967 +                    }
  1.3968 +                }
  1.3969 +                return false;
  1.3970 +            }
  1.3971 +        };
  1.3972 +
  1.3973 +        // Removal of a single range
  1.3974 +        var removeRangeManually = function(sel, range) {
  1.3975 +            var ranges = sel.getAllRanges();
  1.3976 +            sel.removeAllRanges();
  1.3977 +            for (var i = 0, len = ranges.length; i < len; ++i) {
  1.3978 +                if (!rangesEqual(range, ranges[i])) {
  1.3979 +                    sel.addRange(ranges[i]);
  1.3980 +                }
  1.3981 +            }
  1.3982 +            if (!sel.rangeCount) {
  1.3983 +                updateEmptySelection(sel);
  1.3984 +            }
  1.3985 +        };
  1.3986 +
  1.3987 +        if (implementsControlRange && implementsDocSelection) {
  1.3988 +            selProto.removeRange = function(range) {
  1.3989 +                if (this.docSelection.type == CONTROL) {
  1.3990 +                    var controlRange = this.docSelection.createRange();
  1.3991 +                    var rangeElement = getSingleElementFromRange(range);
  1.3992 +
  1.3993 +                    // Create a new ControlRange containing all the elements in the selected ControlRange minus the
  1.3994 +                    // element contained by the supplied range
  1.3995 +                    var doc = getDocument(controlRange.item(0));
  1.3996 +                    var newControlRange = getBody(doc).createControlRange();
  1.3997 +                    var el, removed = false;
  1.3998 +                    for (var i = 0, len = controlRange.length; i < len; ++i) {
  1.3999 +                        el = controlRange.item(i);
  1.4000 +                        if (el !== rangeElement || removed) {
  1.4001 +                            newControlRange.add(controlRange.item(i));
  1.4002 +                        } else {
  1.4003 +                            removed = true;
  1.4004 +                        }
  1.4005 +                    }
  1.4006 +                    newControlRange.select();
  1.4007 +
  1.4008 +                    // Update the wrapped selection based on what's now in the native selection
  1.4009 +                    updateControlSelection(this);
  1.4010 +                } else {
  1.4011 +                    removeRangeManually(this, range);
  1.4012 +                }
  1.4013 +            };
  1.4014 +        } else {
  1.4015 +            selProto.removeRange = function(range) {
  1.4016 +                removeRangeManually(this, range);
  1.4017 +            };
  1.4018 +        }
  1.4019 +
  1.4020 +        // Detecting if a selection is backward
  1.4021 +        var selectionIsBackward;
  1.4022 +        if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
  1.4023 +            selectionIsBackward = winSelectionIsBackward;
  1.4024 +
  1.4025 +            selProto.isBackward = function() {
  1.4026 +                return selectionIsBackward(this);
  1.4027 +            };
  1.4028 +        } else {
  1.4029 +            selectionIsBackward = selProto.isBackward = function() {
  1.4030 +                return false;
  1.4031 +            };
  1.4032 +        }
  1.4033 +
  1.4034 +        // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
  1.4035 +        selProto.isBackwards = selProto.isBackward;
  1.4036 +
  1.4037 +        // Selection stringifier
  1.4038 +        // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
  1.4039 +        // The current spec does not yet define this method.
  1.4040 +        selProto.toString = function() {
  1.4041 +            var rangeTexts = [];
  1.4042 +            for (var i = 0, len = this.rangeCount; i < len; ++i) {
  1.4043 +                rangeTexts[i] = "" + this._ranges[i];
  1.4044 +            }
  1.4045 +            return rangeTexts.join("");
  1.4046 +        };
  1.4047 +
  1.4048 +        function assertNodeInSameDocument(sel, node) {
  1.4049 +            if (sel.win.document != getDocument(node)) {
  1.4050 +                throw new DOMException("WRONG_DOCUMENT_ERR");
  1.4051 +            }
  1.4052 +        }
  1.4053 +
  1.4054 +        // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
  1.4055 +        selProto.collapse = function(node, offset) {
  1.4056 +            assertNodeInSameDocument(this, node);
  1.4057 +            var range = api.createRange(node);
  1.4058 +            range.collapseToPoint(node, offset);
  1.4059 +            this.setSingleRange(range);
  1.4060 +            this.isCollapsed = true;
  1.4061 +        };
  1.4062 +
  1.4063 +        selProto.collapseToStart = function() {
  1.4064 +            if (this.rangeCount) {
  1.4065 +                var range = this._ranges[0];
  1.4066 +                this.collapse(range.startContainer, range.startOffset);
  1.4067 +            } else {
  1.4068 +                throw new DOMException("INVALID_STATE_ERR");
  1.4069 +            }
  1.4070 +        };
  1.4071 +
  1.4072 +        selProto.collapseToEnd = function() {
  1.4073 +            if (this.rangeCount) {
  1.4074 +                var range = this._ranges[this.rangeCount - 1];
  1.4075 +                this.collapse(range.endContainer, range.endOffset);
  1.4076 +            } else {
  1.4077 +                throw new DOMException("INVALID_STATE_ERR");
  1.4078 +            }
  1.4079 +        };
  1.4080 +
  1.4081 +        // The spec is very specific on how selectAllChildren should be implemented and not all browsers implement it as
  1.4082 +        // specified so the native implementation is never used by Rangy.
  1.4083 +        selProto.selectAllChildren = function(node) {
  1.4084 +            assertNodeInSameDocument(this, node);
  1.4085 +            var range = api.createRange(node);
  1.4086 +            range.selectNodeContents(node);
  1.4087 +            this.setSingleRange(range);
  1.4088 +        };
  1.4089 +
  1.4090 +        selProto.deleteFromDocument = function() {
  1.4091 +            // Sepcial behaviour required for IE's control selections
  1.4092 +            if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
  1.4093 +                var controlRange = this.docSelection.createRange();
  1.4094 +                var element;
  1.4095 +                while (controlRange.length) {
  1.4096 +                    element = controlRange.item(0);
  1.4097 +                    controlRange.remove(element);
  1.4098 +                    dom.removeNode(element);
  1.4099 +                }
  1.4100 +                this.refresh();
  1.4101 +            } else if (this.rangeCount) {
  1.4102 +                var ranges = this.getAllRanges();
  1.4103 +                if (ranges.length) {
  1.4104 +                    this.removeAllRanges();
  1.4105 +                    for (var i = 0, len = ranges.length; i < len; ++i) {
  1.4106 +                        ranges[i].deleteContents();
  1.4107 +                    }
  1.4108 +                    // The spec says nothing about what the selection should contain after calling deleteContents on each
  1.4109 +                    // range. Firefox moves the selection to where the final selected range was, so we emulate that
  1.4110 +                    this.addRange(ranges[len - 1]);
  1.4111 +                }
  1.4112 +            }
  1.4113 +        };
  1.4114 +
  1.4115 +        // The following are non-standard extensions
  1.4116 +        selProto.eachRange = function(func, returnValue) {
  1.4117 +            for (var i = 0, len = this._ranges.length; i < len; ++i) {
  1.4118 +                if ( func( this.getRangeAt(i) ) ) {
  1.4119 +                    return returnValue;
  1.4120 +                }
  1.4121 +            }
  1.4122 +        };
  1.4123 +
  1.4124 +        selProto.getAllRanges = function() {
  1.4125 +            var ranges = [];
  1.4126 +            this.eachRange(function(range) {
  1.4127 +                ranges.push(range);
  1.4128 +            });
  1.4129 +            return ranges;
  1.4130 +        };
  1.4131 +
  1.4132 +        selProto.setSingleRange = function(range, direction) {
  1.4133 +            this.removeAllRanges();
  1.4134 +            this.addRange(range, direction);
  1.4135 +        };
  1.4136 +
  1.4137 +        selProto.callMethodOnEachRange = function(methodName, params) {
  1.4138 +            var results = [];
  1.4139 +            this.eachRange( function(range) {
  1.4140 +                results.push( range[methodName].apply(range, params || []) );
  1.4141 +            } );
  1.4142 +            return results;
  1.4143 +        };
  1.4144 +
  1.4145 +        function createStartOrEndSetter(isStart) {
  1.4146 +            return function(node, offset) {
  1.4147 +                var range;
  1.4148 +                if (this.rangeCount) {
  1.4149 +                    range = this.getRangeAt(0);
  1.4150 +                    range["set" + (isStart ? "Start" : "End")](node, offset);
  1.4151 +                } else {
  1.4152 +                    range = api.createRange(this.win.document);
  1.4153 +                    range.setStartAndEnd(node, offset);
  1.4154 +                }
  1.4155 +                this.setSingleRange(range, this.isBackward());
  1.4156 +            };
  1.4157 +        }
  1.4158 +
  1.4159 +        selProto.setStart = createStartOrEndSetter(true);
  1.4160 +        selProto.setEnd = createStartOrEndSetter(false);
  1.4161 +
  1.4162 +        // Add select() method to Range prototype. Any existing selection will be removed.
  1.4163 +        api.rangePrototype.select = function(direction) {
  1.4164 +            getSelection( this.getDocument() ).setSingleRange(this, direction);
  1.4165 +        };
  1.4166 +
  1.4167 +        selProto.changeEachRange = function(func) {
  1.4168 +            var ranges = [];
  1.4169 +            var backward = this.isBackward();
  1.4170 +
  1.4171 +            this.eachRange(function(range) {
  1.4172 +                func(range);
  1.4173 +                ranges.push(range);
  1.4174 +            });
  1.4175 +
  1.4176 +            this.removeAllRanges();
  1.4177 +            if (backward && ranges.length == 1) {
  1.4178 +                this.addRange(ranges[0], "backward");
  1.4179 +            } else {
  1.4180 +                this.setRanges(ranges);
  1.4181 +            }
  1.4182 +        };
  1.4183 +
  1.4184 +        selProto.containsNode = function(node, allowPartial) {
  1.4185 +            return this.eachRange( function(range) {
  1.4186 +                return range.containsNode(node, allowPartial);
  1.4187 +            }, true ) || false;
  1.4188 +        };
  1.4189 +
  1.4190 +        selProto.getBookmark = function(containerNode) {
  1.4191 +            return {
  1.4192 +                backward: this.isBackward(),
  1.4193 +                rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
  1.4194 +            };
  1.4195 +        };
  1.4196 +
  1.4197 +        selProto.moveToBookmark = function(bookmark) {
  1.4198 +            var selRanges = [];
  1.4199 +            for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
  1.4200 +                range = api.createRange(this.win);
  1.4201 +                range.moveToBookmark(rangeBookmark);
  1.4202 +                selRanges.push(range);
  1.4203 +            }
  1.4204 +            if (bookmark.backward) {
  1.4205 +                this.setSingleRange(selRanges[0], "backward");
  1.4206 +            } else {
  1.4207 +                this.setRanges(selRanges);
  1.4208 +            }
  1.4209 +        };
  1.4210 +
  1.4211 +        selProto.saveRanges = function() {
  1.4212 +            return {
  1.4213 +                backward: this.isBackward(),
  1.4214 +                ranges: this.callMethodOnEachRange("cloneRange")
  1.4215 +            };
  1.4216 +        };
  1.4217 +
  1.4218 +        selProto.restoreRanges = function(selRanges) {
  1.4219 +            this.removeAllRanges();
  1.4220 +            for (var i = 0, range; range = selRanges.ranges[i]; ++i) {
  1.4221 +                this.addRange(range, (selRanges.backward && i == 0));
  1.4222 +            }
  1.4223 +        };
  1.4224 +
  1.4225 +        selProto.toHtml = function() {
  1.4226 +            var rangeHtmls = [];
  1.4227 +            this.eachRange(function(range) {
  1.4228 +                rangeHtmls.push( DomRange.toHtml(range) );
  1.4229 +            });
  1.4230 +            return rangeHtmls.join("");
  1.4231 +        };
  1.4232 +
  1.4233 +        if (features.implementsTextRange) {
  1.4234 +            selProto.getNativeTextRange = function() {
  1.4235 +                var sel, textRange;
  1.4236 +                if ( (sel = this.docSelection) ) {
  1.4237 +                    var range = sel.createRange();
  1.4238 +                    if (isTextRange(range)) {
  1.4239 +                        return range;
  1.4240 +                    } else {
  1.4241 +                        throw module.createError("getNativeTextRange: selection is a control selection");
  1.4242 +                    }
  1.4243 +                } else if (this.rangeCount > 0) {
  1.4244 +                    return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) );
  1.4245 +                } else {
  1.4246 +                    throw module.createError("getNativeTextRange: selection contains no range");
  1.4247 +                }
  1.4248 +            };
  1.4249 +        }
  1.4250 +
  1.4251 +        function inspect(sel) {
  1.4252 +            var rangeInspects = [];
  1.4253 +            var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
  1.4254 +            var focus = new DomPosition(sel.focusNode, sel.focusOffset);
  1.4255 +            var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
  1.4256 +
  1.4257 +            if (typeof sel.rangeCount != "undefined") {
  1.4258 +                for (var i = 0, len = sel.rangeCount; i < len; ++i) {
  1.4259 +                    rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
  1.4260 +                }
  1.4261 +            }
  1.4262 +            return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
  1.4263 +                    ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
  1.4264 +        }
  1.4265 +
  1.4266 +        selProto.getName = function() {
  1.4267 +            return "WrappedSelection";
  1.4268 +        };
  1.4269 +
  1.4270 +        selProto.inspect = function() {
  1.4271 +            return inspect(this);
  1.4272 +        };
  1.4273 +
  1.4274 +        selProto.detach = function() {
  1.4275 +            actOnCachedSelection(this.win, "delete");
  1.4276 +            deleteProperties(this);
  1.4277 +        };
  1.4278 +
  1.4279 +        WrappedSelection.detachAll = function() {
  1.4280 +            actOnCachedSelection(null, "deleteAll");
  1.4281 +        };
  1.4282 +
  1.4283 +        WrappedSelection.inspect = inspect;
  1.4284 +        WrappedSelection.isDirectionBackward = isDirectionBackward;
  1.4285 +
  1.4286 +        api.Selection = WrappedSelection;
  1.4287 +
  1.4288 +        api.selectionPrototype = selProto;
  1.4289 +
  1.4290 +        api.addShimListener(function(win) {
  1.4291 +            if (typeof win.getSelection == "undefined") {
  1.4292 +                win.getSelection = function() {
  1.4293 +                    return getSelection(win);
  1.4294 +                };
  1.4295 +            }
  1.4296 +            win = null;
  1.4297 +        });
  1.4298 +    });
  1.4299 +    
  1.4300 +
  1.4301 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.4302 +
  1.4303 +    // Wait for document to load before initializing
  1.4304 +    var docReady = false;
  1.4305 +
  1.4306 +    var loadHandler = function(e) {
  1.4307 +        if (!docReady) {
  1.4308 +            docReady = true;
  1.4309 +            if (!api.initialized && api.config.autoInitialize) {
  1.4310 +                init();
  1.4311 +            }
  1.4312 +        }
  1.4313 +    };
  1.4314 +
  1.4315 +    if (isBrowser) {
  1.4316 +        // Test whether the document has already been loaded and initialize immediately if so
  1.4317 +        if (document.readyState == "complete") {
  1.4318 +            loadHandler();
  1.4319 +        } else {
  1.4320 +            if (isHostMethod(document, "addEventListener")) {
  1.4321 +                document.addEventListener("DOMContentLoaded", loadHandler, false);
  1.4322 +            }
  1.4323 +
  1.4324 +            // Add a fallback in case the DOMContentLoaded event isn't supported
  1.4325 +            addListener(window, "load", loadHandler);
  1.4326 +        }
  1.4327 +    }
  1.4328 +
  1.4329 +    rangy = api;
  1.4330 +})();
  1.4331 +
  1.4332 +/**
  1.4333 + * Selection save and restore module for Rangy.
  1.4334 + * Saves and restores user selections using marker invisible elements in the DOM.
  1.4335 + *
  1.4336 + * Part of Rangy, a cross-browser JavaScript range and selection library
  1.4337 + * https://github.com/timdown/rangy
  1.4338 + *
  1.4339 + * Depends on Rangy core.
  1.4340 + *
  1.4341 + * Copyright 2015, Tim Down
  1.4342 + * Licensed under the MIT license.
  1.4343 + * Version: 1.3.1-dev
  1.4344 + * Build date: 20 May 2015
  1.4345 + *
  1.4346 +* NOTE: UMD wrapper removed manually for bundling (Oliver Pulges)
  1.4347 +*/
  1.4348 +rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
  1.4349 +    var dom = api.dom;
  1.4350 +    var removeNode = dom.removeNode;
  1.4351 +    var isDirectionBackward = api.Selection.isDirectionBackward;
  1.4352 +    var markerTextChar = "\ufeff";
  1.4353 +
  1.4354 +    function gEBI(id, doc) {
  1.4355 +        return (doc || document).getElementById(id);
  1.4356 +    }
  1.4357 +
  1.4358 +    function insertRangeBoundaryMarker(range, atStart) {
  1.4359 +        var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
  1.4360 +        var markerEl;
  1.4361 +        var doc = dom.getDocument(range.startContainer);
  1.4362 +
  1.4363 +        // Clone the Range and collapse to the appropriate boundary point
  1.4364 +        var boundaryRange = range.cloneRange();
  1.4365 +        boundaryRange.collapse(atStart);
  1.4366 +
  1.4367 +        // Create the marker element containing a single invisible character using DOM methods and insert it
  1.4368 +        markerEl = doc.createElement("span");
  1.4369 +        markerEl.id = markerId;
  1.4370 +        markerEl.style.lineHeight = "0";
  1.4371 +        markerEl.style.display = "none";
  1.4372 +        markerEl.className = "rangySelectionBoundary";
  1.4373 +        markerEl.appendChild(doc.createTextNode(markerTextChar));
  1.4374 +
  1.4375 +        boundaryRange.insertNode(markerEl);
  1.4376 +        return markerEl;
  1.4377 +    }
  1.4378 +
  1.4379 +    function setRangeBoundary(doc, range, markerId, atStart) {
  1.4380 +        var markerEl = gEBI(markerId, doc);
  1.4381 +        if (markerEl) {
  1.4382 +            range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
  1.4383 +            removeNode(markerEl);
  1.4384 +        } else {
  1.4385 +            module.warn("Marker element has been removed. Cannot restore selection.");
  1.4386 +        }
  1.4387 +    }
  1.4388 +
  1.4389 +    function compareRanges(r1, r2) {
  1.4390 +        return r2.compareBoundaryPoints(r1.START_TO_START, r1);
  1.4391 +    }
  1.4392 +
  1.4393 +    function saveRange(range, direction) {
  1.4394 +        var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
  1.4395 +        var backward = isDirectionBackward(direction);
  1.4396 +
  1.4397 +        if (range.collapsed) {
  1.4398 +            endEl = insertRangeBoundaryMarker(range, false);
  1.4399 +            return {
  1.4400 +                document: doc,
  1.4401 +                markerId: endEl.id,
  1.4402 +                collapsed: true
  1.4403 +            };
  1.4404 +        } else {
  1.4405 +            endEl = insertRangeBoundaryMarker(range, false);
  1.4406 +            startEl = insertRangeBoundaryMarker(range, true);
  1.4407 +
  1.4408 +            return {
  1.4409 +                document: doc,
  1.4410 +                startMarkerId: startEl.id,
  1.4411 +                endMarkerId: endEl.id,
  1.4412 +                collapsed: false,
  1.4413 +                backward: backward,
  1.4414 +                toString: function() {
  1.4415 +                    return "original text: '" + text + "', new text: '" + range.toString() + "'";
  1.4416 +                }
  1.4417 +            };
  1.4418 +        }
  1.4419 +    }
  1.4420 +
  1.4421 +    function restoreRange(rangeInfo, normalize) {
  1.4422 +        var doc = rangeInfo.document;
  1.4423 +        if (typeof normalize == "undefined") {
  1.4424 +            normalize = true;
  1.4425 +        }
  1.4426 +        var range = api.createRange(doc);
  1.4427 +        if (rangeInfo.collapsed) {
  1.4428 +            var markerEl = gEBI(rangeInfo.markerId, doc);
  1.4429 +            if (markerEl) {
  1.4430 +                markerEl.style.display = "inline";
  1.4431 +                var previousNode = markerEl.previousSibling;
  1.4432 +
  1.4433 +                // Workaround for issue 17
  1.4434 +                if (previousNode && previousNode.nodeType == 3) {
  1.4435 +                    removeNode(markerEl);
  1.4436 +                    range.collapseToPoint(previousNode, previousNode.length);
  1.4437 +                } else {
  1.4438 +                    range.collapseBefore(markerEl);
  1.4439 +                    removeNode(markerEl);
  1.4440 +                }
  1.4441 +            } else {
  1.4442 +                module.warn("Marker element has been removed. Cannot restore selection.");
  1.4443 +            }
  1.4444 +        } else {
  1.4445 +            setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
  1.4446 +            setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
  1.4447 +        }
  1.4448 +
  1.4449 +        if (normalize) {
  1.4450 +            range.normalizeBoundaries();
  1.4451 +        }
  1.4452 +
  1.4453 +        return range;
  1.4454 +    }
  1.4455 +
  1.4456 +    function saveRanges(ranges, direction) {
  1.4457 +        var rangeInfos = [], range, doc;
  1.4458 +        var backward = isDirectionBackward(direction);
  1.4459 +
  1.4460 +        // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
  1.4461 +        ranges = ranges.slice(0);
  1.4462 +        ranges.sort(compareRanges);
  1.4463 +
  1.4464 +        for (var i = 0, len = ranges.length; i < len; ++i) {
  1.4465 +            rangeInfos[i] = saveRange(ranges[i], backward);
  1.4466 +        }
  1.4467 +
  1.4468 +        // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
  1.4469 +        // between its markers
  1.4470 +        for (i = len - 1; i >= 0; --i) {
  1.4471 +            range = ranges[i];
  1.4472 +            doc = api.DomRange.getRangeDocument(range);
  1.4473 +            if (range.collapsed) {
  1.4474 +                range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
  1.4475 +            } else {
  1.4476 +                range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
  1.4477 +                range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
  1.4478 +            }
  1.4479 +        }
  1.4480 +
  1.4481 +        return rangeInfos;
  1.4482 +    }
  1.4483 +
  1.4484 +    function saveSelection(win) {
  1.4485 +        if (!api.isSelectionValid(win)) {
  1.4486 +            module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
  1.4487 +            return null;
  1.4488 +        }
  1.4489 +        var sel = api.getSelection(win);
  1.4490 +        var ranges = sel.getAllRanges();
  1.4491 +        var backward = (ranges.length == 1 && sel.isBackward());
  1.4492 +
  1.4493 +        var rangeInfos = saveRanges(ranges, backward);
  1.4494 +
  1.4495 +        // Ensure current selection is unaffected
  1.4496 +        if (backward) {
  1.4497 +            sel.setSingleRange(ranges[0], backward);
  1.4498 +        } else {
  1.4499 +            sel.setRanges(ranges);
  1.4500 +        }
  1.4501 +
  1.4502 +        return {
  1.4503 +            win: win,
  1.4504 +            rangeInfos: rangeInfos,
  1.4505 +            restored: false
  1.4506 +        };
  1.4507 +    }
  1.4508 +
  1.4509 +    function restoreRanges(rangeInfos) {
  1.4510 +        var ranges = [];
  1.4511 +
  1.4512 +        // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
  1.4513 +        // normalization affecting previously restored ranges.
  1.4514 +        var rangeCount = rangeInfos.length;
  1.4515 +
  1.4516 +        for (var i = rangeCount - 1; i >= 0; i--) {
  1.4517 +            ranges[i] = restoreRange(rangeInfos[i], true);
  1.4518 +        }
  1.4519 +
  1.4520 +        return ranges;
  1.4521 +    }
  1.4522 +
  1.4523 +    function restoreSelection(savedSelection, preserveDirection) {
  1.4524 +        if (!savedSelection.restored) {
  1.4525 +            var rangeInfos = savedSelection.rangeInfos;
  1.4526 +            var sel = api.getSelection(savedSelection.win);
  1.4527 +            var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
  1.4528 +
  1.4529 +            if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
  1.4530 +                sel.removeAllRanges();
  1.4531 +                sel.addRange(ranges[0], true);
  1.4532 +            } else {
  1.4533 +                sel.setRanges(ranges);
  1.4534 +            }
  1.4535 +
  1.4536 +            savedSelection.restored = true;
  1.4537 +        }
  1.4538 +    }
  1.4539 +
  1.4540 +    function removeMarkerElement(doc, markerId) {
  1.4541 +        var markerEl = gEBI(markerId, doc);
  1.4542 +        if (markerEl) {
  1.4543 +            removeNode(markerEl);
  1.4544 +        }
  1.4545 +    }
  1.4546 +
  1.4547 +    function removeMarkers(savedSelection) {
  1.4548 +        var rangeInfos = savedSelection.rangeInfos;
  1.4549 +        for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
  1.4550 +            rangeInfo = rangeInfos[i];
  1.4551 +            if (rangeInfo.collapsed) {
  1.4552 +                removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
  1.4553 +            } else {
  1.4554 +                removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
  1.4555 +                removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
  1.4556 +            }
  1.4557 +        }
  1.4558 +    }
  1.4559 +
  1.4560 +    api.util.extend(api, {
  1.4561 +        saveRange: saveRange,
  1.4562 +        restoreRange: restoreRange,
  1.4563 +        saveRanges: saveRanges,
  1.4564 +        restoreRanges: restoreRanges,
  1.4565 +        saveSelection: saveSelection,
  1.4566 +        restoreSelection: restoreSelection,
  1.4567 +        removeMarkerElement: removeMarkerElement,
  1.4568 +        removeMarkers: removeMarkers
  1.4569 +    });
  1.4570 +});
  1.4571 +
  1.4572 +/**
  1.4573 + * Text range module for Rangy.
  1.4574 + * Text-based manipulation and searching of ranges and selections.
  1.4575 + *
  1.4576 + * Features
  1.4577 + *
  1.4578 + * - Ability to move range boundaries by character or word offsets
  1.4579 + * - Customizable word tokenizer
  1.4580 + * - Ignores text nodes inside <script> or <style> elements or those hidden by CSS display and visibility properties
  1.4581 + * - Range findText method to search for text or regex within the page or within a range. Flags for whole words and case
  1.4582 + *   sensitivity
  1.4583 + * - Selection and range save/restore as text offsets within a node
  1.4584 + * - Methods to return visible text within a range or selection
  1.4585 + * - innerText method for elements
  1.4586 + *
  1.4587 + * References
  1.4588 + *
  1.4589 + * https://www.w3.org/Bugs/Public/show_bug.cgi?id=13145
  1.4590 + * http://aryeh.name/spec/innertext/innertext.html
  1.4591 + * http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html
  1.4592 + *
  1.4593 + * Part of Rangy, a cross-browser JavaScript range and selection library
  1.4594 + * https://github.com/timdown/rangy
  1.4595 + *
  1.4596 + * Depends on Rangy core.
  1.4597 + *
  1.4598 + * Copyright 2015, Tim Down
  1.4599 + * Licensed under the MIT license.
  1.4600 + * Version: 1.3.1-dev
  1.4601 + * Build date: 20 May 2015
  1.4602 + */
  1.4603 +
  1.4604 +/**
  1.4605 + * Problem: handling of trailing spaces before line breaks is handled inconsistently between browsers.
  1.4606 + *
  1.4607 + * First, a <br>: this is relatively simple. For the following HTML:
  1.4608 + *
  1.4609 + * 1 <br>2
  1.4610 + *
  1.4611 + * - IE and WebKit render the space, include it in the selection (i.e. when the content is selected and pasted into a
  1.4612 + *   textarea, the space is present) and allow the caret to be placed after it.
  1.4613 + * - Firefox does not acknowledge the space in the selection but it is possible to place the caret after it.
  1.4614 + * - Opera does not render the space but has two separate caret positions on either side of the space (left and right
  1.4615 + *   arrow keys show this) and includes the space in the selection.
  1.4616 + *
  1.4617 + * The other case is the line break or breaks implied by block elements. For the following HTML:
  1.4618 + *
  1.4619 + * <p>1 </p><p>2<p>
  1.4620 + *
  1.4621 + * - WebKit does not acknowledge the space in any way
  1.4622 + * - Firefox, IE and Opera as per <br>
  1.4623 + *
  1.4624 + * One more case is trailing spaces before line breaks in elements with white-space: pre-line. For the following HTML:
  1.4625 + *
  1.4626 + * <p style="white-space: pre-line">1
  1.4627 + * 2</p>
  1.4628 + *
  1.4629 + * - Firefox and WebKit include the space in caret positions
  1.4630 + * - IE does not support pre-line up to and including version 9
  1.4631 + * - Opera ignores the space
  1.4632 + * - Trailing space only renders if there is a non-collapsed character in the line
  1.4633 + *
  1.4634 + * Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be
  1.4635 + * feature-tested
  1.4636 + *
  1.4637 + * NOTE: UMD wrapper removed manually for bundling (Oliver Pulges)
  1.4638 +*/
  1.4639 +rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) {
  1.4640 +    var UNDEF = "undefined";
  1.4641 +    var CHARACTER = "character", WORD = "word";
  1.4642 +    var dom = api.dom, util = api.util;
  1.4643 +    var extend = util.extend;
  1.4644 +    var createOptions = util.createOptions;
  1.4645 +    var getBody = dom.getBody;
  1.4646 +
  1.4647 +
  1.4648 +    var spacesRegex = /^[ \t\f\r\n]+$/;
  1.4649 +    var spacesMinusLineBreaksRegex = /^[ \t\f\r]+$/;
  1.4650 +    var allWhiteSpaceRegex = /^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/;
  1.4651 +    var nonLineBreakWhiteSpaceRegex = /^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/;
  1.4652 +    var lineBreakRegex = /^[\n-\r\u0085\u2028\u2029]$/;
  1.4653 +
  1.4654 +    var defaultLanguage = "en";
  1.4655 +
  1.4656 +    var isDirectionBackward = api.Selection.isDirectionBackward;
  1.4657 +
  1.4658 +    // Properties representing whether trailing spaces inside blocks are completely collapsed (as they are in WebKit,
  1.4659 +    // but not other browsers). Also test whether trailing spaces before <br> elements are collapsed.
  1.4660 +    var trailingSpaceInBlockCollapses = false;
  1.4661 +    var trailingSpaceBeforeBrCollapses = false;
  1.4662 +    var trailingSpaceBeforeBlockCollapses = false;
  1.4663 +    var trailingSpaceBeforeLineBreakInPreLineCollapses = true;
  1.4664 +
  1.4665 +    (function() {
  1.4666 +        var el = dom.createTestElement(document, "<p>1 </p><p></p>", true);
  1.4667 +        var p = el.firstChild;
  1.4668 +        var sel = api.getSelection();
  1.4669 +        sel.collapse(p.lastChild, 2);
  1.4670 +        sel.setStart(p.firstChild, 0);
  1.4671 +        trailingSpaceInBlockCollapses = ("" + sel).length == 1;
  1.4672 +
  1.4673 +        el.innerHTML = "1 <br />";
  1.4674 +        sel.collapse(el, 2);
  1.4675 +        sel.setStart(el.firstChild, 0);
  1.4676 +        trailingSpaceBeforeBrCollapses = ("" + sel).length == 1;
  1.4677 +
  1.4678 +        el.innerHTML = "1 <p>1</p>";
  1.4679 +        sel.collapse(el, 2);
  1.4680 +        sel.setStart(el.firstChild, 0);
  1.4681 +        trailingSpaceBeforeBlockCollapses = ("" + sel).length == 1;
  1.4682 +
  1.4683 +        dom.removeNode(el);
  1.4684 +        sel.removeAllRanges();
  1.4685 +    })();
  1.4686 +
  1.4687 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.4688 +
  1.4689 +    // This function must create word and non-word tokens for the whole of the text supplied to it
  1.4690 +    function defaultTokenizer(chars, wordOptions) {
  1.4691 +        var word = chars.join(""), result, tokenRanges = [];
  1.4692 +
  1.4693 +        function createTokenRange(start, end, isWord) {
  1.4694 +            tokenRanges.push( { start: start, end: end, isWord: isWord } );
  1.4695 +        }
  1.4696 +
  1.4697 +        // Match words and mark characters
  1.4698 +        var lastWordEnd = 0, wordStart, wordEnd;
  1.4699 +        while ( (result = wordOptions.wordRegex.exec(word)) ) {
  1.4700 +            wordStart = result.index;
  1.4701 +            wordEnd = wordStart + result[0].length;
  1.4702 +
  1.4703 +            // Create token for non-word characters preceding this word
  1.4704 +            if (wordStart > lastWordEnd) {
  1.4705 +                createTokenRange(lastWordEnd, wordStart, false);
  1.4706 +            }
  1.4707 +
  1.4708 +            // Get trailing space characters for word
  1.4709 +            if (wordOptions.includeTrailingSpace) {
  1.4710 +                while ( nonLineBreakWhiteSpaceRegex.test(chars[wordEnd]) ) {
  1.4711 +                    ++wordEnd;
  1.4712 +                }
  1.4713 +            }
  1.4714 +            createTokenRange(wordStart, wordEnd, true);
  1.4715 +            lastWordEnd = wordEnd;
  1.4716 +        }
  1.4717 +
  1.4718 +        // Create token for trailing non-word characters, if any exist
  1.4719 +        if (lastWordEnd < chars.length) {
  1.4720 +            createTokenRange(lastWordEnd, chars.length, false);
  1.4721 +        }
  1.4722 +
  1.4723 +        return tokenRanges;
  1.4724 +    }
  1.4725 +
  1.4726 +    function convertCharRangeToToken(chars, tokenRange) {
  1.4727 +        var tokenChars = chars.slice(tokenRange.start, tokenRange.end);
  1.4728 +        var token = {
  1.4729 +            isWord: tokenRange.isWord,
  1.4730 +            chars: tokenChars,
  1.4731 +            toString: function() {
  1.4732 +                return tokenChars.join("");
  1.4733 +            }
  1.4734 +        };
  1.4735 +        for (var i = 0, len = tokenChars.length; i < len; ++i) {
  1.4736 +            tokenChars[i].token = token;
  1.4737 +        }
  1.4738 +        return token;
  1.4739 +    }
  1.4740 +
  1.4741 +    function tokenize(chars, wordOptions, tokenizer) {
  1.4742 +        var tokenRanges = tokenizer(chars, wordOptions);
  1.4743 +        var tokens = [];
  1.4744 +        for (var i = 0, tokenRange; tokenRange = tokenRanges[i++]; ) {
  1.4745 +            tokens.push( convertCharRangeToToken(chars, tokenRange) );
  1.4746 +        }
  1.4747 +        return tokens;
  1.4748 +    }
  1.4749 +
  1.4750 +    var defaultCharacterOptions = {
  1.4751 +        includeBlockContentTrailingSpace: true,
  1.4752 +        includeSpaceBeforeBr: true,
  1.4753 +        includeSpaceBeforeBlock: true,
  1.4754 +        includePreLineTrailingSpace: true,
  1.4755 +        ignoreCharacters: ""
  1.4756 +    };
  1.4757 +
  1.4758 +    function normalizeIgnoredCharacters(ignoredCharacters) {
  1.4759 +        // Check if character is ignored
  1.4760 +        var ignoredChars = ignoredCharacters || "";
  1.4761 +
  1.4762 +        // Normalize ignored characters into a string consisting of characters in ascending order of character code
  1.4763 +        var ignoredCharsArray = (typeof ignoredChars == "string") ? ignoredChars.split("") : ignoredChars;
  1.4764 +        ignoredCharsArray.sort(function(char1, char2) {
  1.4765 +            return char1.charCodeAt(0) - char2.charCodeAt(0);
  1.4766 +        });
  1.4767 +
  1.4768 +        /// Convert back to a string and remove duplicates
  1.4769 +        return ignoredCharsArray.join("").replace(/(.)\1+/g, "$1");
  1.4770 +    }
  1.4771 +
  1.4772 +    var defaultCaretCharacterOptions = {
  1.4773 +        includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses,
  1.4774 +        includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses,
  1.4775 +        includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses,
  1.4776 +        includePreLineTrailingSpace: true
  1.4777 +    };
  1.4778 +
  1.4779 +    var defaultWordOptions = {
  1.4780 +        "en": {
  1.4781 +            wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi,
  1.4782 +            includeTrailingSpace: false,
  1.4783 +            tokenizer: defaultTokenizer
  1.4784 +        }
  1.4785 +    };
  1.4786 +
  1.4787 +    var defaultFindOptions = {
  1.4788 +        caseSensitive: false,
  1.4789 +        withinRange: null,
  1.4790 +        wholeWordsOnly: false,
  1.4791 +        wrap: false,
  1.4792 +        direction: "forward",
  1.4793 +        wordOptions: null,
  1.4794 +        characterOptions: null
  1.4795 +    };
  1.4796 +
  1.4797 +    var defaultMoveOptions = {
  1.4798 +        wordOptions: null,
  1.4799 +        characterOptions: null
  1.4800 +    };
  1.4801 +
  1.4802 +    var defaultExpandOptions = {
  1.4803 +        wordOptions: null,
  1.4804 +        characterOptions: null,
  1.4805 +        trim: false,
  1.4806 +        trimStart: true,
  1.4807 +        trimEnd: true
  1.4808 +    };
  1.4809 +
  1.4810 +    var defaultWordIteratorOptions = {
  1.4811 +        wordOptions: null,
  1.4812 +        characterOptions: null,
  1.4813 +        direction: "forward"
  1.4814 +    };
  1.4815 +
  1.4816 +    function createWordOptions(options) {
  1.4817 +        var lang, defaults;
  1.4818 +        if (!options) {
  1.4819 +            return defaultWordOptions[defaultLanguage];
  1.4820 +        } else {
  1.4821 +            lang = options.language || defaultLanguage;
  1.4822 +            defaults = {};
  1.4823 +            extend(defaults, defaultWordOptions[lang] || defaultWordOptions[defaultLanguage]);
  1.4824 +            extend(defaults, options);
  1.4825 +            return defaults;
  1.4826 +        }
  1.4827 +    }
  1.4828 +
  1.4829 +    function createNestedOptions(optionsParam, defaults) {
  1.4830 +        var options = createOptions(optionsParam, defaults);
  1.4831 +        if (defaults.hasOwnProperty("wordOptions")) {
  1.4832 +            options.wordOptions = createWordOptions(options.wordOptions);
  1.4833 +        }
  1.4834 +        if (defaults.hasOwnProperty("characterOptions")) {
  1.4835 +            options.characterOptions = createOptions(options.characterOptions, defaultCharacterOptions);
  1.4836 +        }
  1.4837 +        return options;
  1.4838 +    }
  1.4839 +
  1.4840 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.4841 +
  1.4842 +    /* DOM utility functions */
  1.4843 +    var getComputedStyleProperty = dom.getComputedStyleProperty;
  1.4844 +
  1.4845 +    // Create cachable versions of DOM functions
  1.4846 +
  1.4847 +    // Test for old IE's incorrect display properties
  1.4848 +    var tableCssDisplayBlock;
  1.4849 +    (function() {
  1.4850 +        var table = document.createElement("table");
  1.4851 +        var body = getBody(document);
  1.4852 +        body.appendChild(table);
  1.4853 +        tableCssDisplayBlock = (getComputedStyleProperty(table, "display") == "block");
  1.4854 +        body.removeChild(table);
  1.4855 +    })();
  1.4856 +
  1.4857 +    var defaultDisplayValueForTag = {
  1.4858 +        table: "table",
  1.4859 +        caption: "table-caption",
  1.4860 +        colgroup: "table-column-group",
  1.4861 +        col: "table-column",
  1.4862 +        thead: "table-header-group",
  1.4863 +        tbody: "table-row-group",
  1.4864 +        tfoot: "table-footer-group",
  1.4865 +        tr: "table-row",
  1.4866 +        td: "table-cell",
  1.4867 +        th: "table-cell"
  1.4868 +    };
  1.4869 +
  1.4870 +    // Corrects IE's "block" value for table-related elements
  1.4871 +    function getComputedDisplay(el, win) {
  1.4872 +        var display = getComputedStyleProperty(el, "display", win);
  1.4873 +        var tagName = el.tagName.toLowerCase();
  1.4874 +        return (display == "block" &&
  1.4875 +                tableCssDisplayBlock &&
  1.4876 +                defaultDisplayValueForTag.hasOwnProperty(tagName)) ?
  1.4877 +            defaultDisplayValueForTag[tagName] : display;
  1.4878 +    }
  1.4879 +
  1.4880 +    function isHidden(node) {
  1.4881 +        var ancestors = getAncestorsAndSelf(node);
  1.4882 +        for (var i = 0, len = ancestors.length; i < len; ++i) {
  1.4883 +            if (ancestors[i].nodeType == 1 && getComputedDisplay(ancestors[i]) == "none") {
  1.4884 +                return true;
  1.4885 +            }
  1.4886 +        }
  1.4887 +
  1.4888 +        return false;
  1.4889 +    }
  1.4890 +
  1.4891 +    function isVisibilityHiddenTextNode(textNode) {
  1.4892 +        var el;
  1.4893 +        return textNode.nodeType == 3 &&
  1.4894 +            (el = textNode.parentNode) &&
  1.4895 +            getComputedStyleProperty(el, "visibility") == "hidden";
  1.4896 +    }
  1.4897 +
  1.4898 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.4899 +
  1.4900 +
  1.4901 +    // "A block node is either an Element whose "display" property does not have
  1.4902 +    // resolved value "inline" or "inline-block" or "inline-table" or "none", or a
  1.4903 +    // Document, or a DocumentFragment."
  1.4904 +    function isBlockNode(node) {
  1.4905 +        return node &&
  1.4906 +            ((node.nodeType == 1 && !/^(inline(-block|-table)?|none)$/.test(getComputedDisplay(node))) ||
  1.4907 +            node.nodeType == 9 || node.nodeType == 11);
  1.4908 +    }
  1.4909 +
  1.4910 +    function getLastDescendantOrSelf(node) {
  1.4911 +        var lastChild = node.lastChild;
  1.4912 +        return lastChild ? getLastDescendantOrSelf(lastChild) : node;
  1.4913 +    }
  1.4914 +
  1.4915 +    function containsPositions(node) {
  1.4916 +        return dom.isCharacterDataNode(node) ||
  1.4917 +            !/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(node.nodeName);
  1.4918 +    }
  1.4919 +
  1.4920 +    function getAncestors(node) {
  1.4921 +        var ancestors = [];
  1.4922 +        while (node.parentNode) {
  1.4923 +            ancestors.unshift(node.parentNode);
  1.4924 +            node = node.parentNode;
  1.4925 +        }
  1.4926 +        return ancestors;
  1.4927 +    }
  1.4928 +
  1.4929 +    function getAncestorsAndSelf(node) {
  1.4930 +        return getAncestors(node).concat([node]);
  1.4931 +    }
  1.4932 +
  1.4933 +    function nextNodeDescendants(node) {
  1.4934 +        while (node && !node.nextSibling) {
  1.4935 +            node = node.parentNode;
  1.4936 +        }
  1.4937 +        if (!node) {
  1.4938 +            return null;
  1.4939 +        }
  1.4940 +        return node.nextSibling;
  1.4941 +    }
  1.4942 +
  1.4943 +    function nextNode(node, excludeChildren) {
  1.4944 +        if (!excludeChildren && node.hasChildNodes()) {
  1.4945 +            return node.firstChild;
  1.4946 +        }
  1.4947 +        return nextNodeDescendants(node);
  1.4948 +    }
  1.4949 +
  1.4950 +    function previousNode(node) {
  1.4951 +        var previous = node.previousSibling;
  1.4952 +        if (previous) {
  1.4953 +            node = previous;
  1.4954 +            while (node.hasChildNodes()) {
  1.4955 +                node = node.lastChild;
  1.4956 +            }
  1.4957 +            return node;
  1.4958 +        }
  1.4959 +        var parent = node.parentNode;
  1.4960 +        if (parent && parent.nodeType == 1) {
  1.4961 +            return parent;
  1.4962 +        }
  1.4963 +        return null;
  1.4964 +    }
  1.4965 +
  1.4966 +    // Adpated from Aryeh's code.
  1.4967 +    // "A whitespace node is either a Text node whose data is the empty string; or
  1.4968 +    // a Text node whose data consists only of one or more tabs (0x0009), line
  1.4969 +    // feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
  1.4970 +    // parent is an Element whose resolved value for "white-space" is "normal" or
  1.4971 +    // "nowrap"; or a Text node whose data consists only of one or more tabs
  1.4972 +    // (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
  1.4973 +    // parent is an Element whose resolved value for "white-space" is "pre-line"."
  1.4974 +    function isWhitespaceNode(node) {
  1.4975 +        if (!node || node.nodeType != 3) {
  1.4976 +            return false;
  1.4977 +        }
  1.4978 +        var text = node.data;
  1.4979 +        if (text === "") {
  1.4980 +            return true;
  1.4981 +        }
  1.4982 +        var parent = node.parentNode;
  1.4983 +        if (!parent || parent.nodeType != 1) {
  1.4984 +            return false;
  1.4985 +        }
  1.4986 +        var computedWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
  1.4987 +
  1.4988 +        return (/^[\t\n\r ]+$/.test(text) && /^(normal|nowrap)$/.test(computedWhiteSpace)) ||
  1.4989 +            (/^[\t\r ]+$/.test(text) && computedWhiteSpace == "pre-line");
  1.4990 +    }
  1.4991 +
  1.4992 +    // Adpated from Aryeh's code.
  1.4993 +    // "node is a collapsed whitespace node if the following algorithm returns
  1.4994 +    // true:"
  1.4995 +    function isCollapsedWhitespaceNode(node) {
  1.4996 +        // "If node's data is the empty string, return true."
  1.4997 +        if (node.data === "") {
  1.4998 +            return true;
  1.4999 +        }
  1.5000 +
  1.5001 +        // "If node is not a whitespace node, return false."
  1.5002 +        if (!isWhitespaceNode(node)) {
  1.5003 +            return false;
  1.5004 +        }
  1.5005 +
  1.5006 +        // "Let ancestor be node's parent."
  1.5007 +        var ancestor = node.parentNode;
  1.5008 +
  1.5009 +        // "If ancestor is null, return true."
  1.5010 +        if (!ancestor) {
  1.5011 +            return true;
  1.5012 +        }
  1.5013 +
  1.5014 +        // "If the "display" property of some ancestor of node has resolved value "none", return true."
  1.5015 +        if (isHidden(node)) {
  1.5016 +            return true;
  1.5017 +        }
  1.5018 +
  1.5019 +        return false;
  1.5020 +    }
  1.5021 +
  1.5022 +    function isCollapsedNode(node) {
  1.5023 +        var type = node.nodeType;
  1.5024 +        return type == 7 /* PROCESSING_INSTRUCTION */ ||
  1.5025 +            type == 8 /* COMMENT */ ||
  1.5026 +            isHidden(node) ||
  1.5027 +            /^(script|style)$/i.test(node.nodeName) ||
  1.5028 +            isVisibilityHiddenTextNode(node) ||
  1.5029 +            isCollapsedWhitespaceNode(node);
  1.5030 +    }
  1.5031 +
  1.5032 +    function isIgnoredNode(node, win) {
  1.5033 +        var type = node.nodeType;
  1.5034 +        return type == 7 /* PROCESSING_INSTRUCTION */ ||
  1.5035 +            type == 8 /* COMMENT */ ||
  1.5036 +            (type == 1 && getComputedDisplay(node, win) == "none");
  1.5037 +    }
  1.5038 +
  1.5039 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.5040 +
  1.5041 +    // Possibly overengineered caching system to prevent repeated DOM calls slowing everything down
  1.5042 +
  1.5043 +    function Cache() {
  1.5044 +        this.store = {};
  1.5045 +    }
  1.5046 +
  1.5047 +    Cache.prototype = {
  1.5048 +        get: function(key) {
  1.5049 +            return this.store.hasOwnProperty(key) ? this.store[key] : null;
  1.5050 +        },
  1.5051 +
  1.5052 +        set: function(key, value) {
  1.5053 +            return this.store[key] = value;
  1.5054 +        }
  1.5055 +    };
  1.5056 +
  1.5057 +    var cachedCount = 0, uncachedCount = 0;
  1.5058 +
  1.5059 +    function createCachingGetter(methodName, func, objProperty) {
  1.5060 +        return function(args) {
  1.5061 +            var cache = this.cache;
  1.5062 +            if (cache.hasOwnProperty(methodName)) {
  1.5063 +                cachedCount++;
  1.5064 +                return cache[methodName];
  1.5065 +            } else {
  1.5066 +                uncachedCount++;
  1.5067 +                var value = func.call(this, objProperty ? this[objProperty] : this, args);
  1.5068 +                cache[methodName] = value;
  1.5069 +                return value;
  1.5070 +            }
  1.5071 +        };
  1.5072 +    }
  1.5073 +
  1.5074 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.5075 +
  1.5076 +    function NodeWrapper(node, session) {
  1.5077 +        this.node = node;
  1.5078 +        this.session = session;
  1.5079 +        this.cache = new Cache();
  1.5080 +        this.positions = new Cache();
  1.5081 +    }
  1.5082 +
  1.5083 +    var nodeProto = {
  1.5084 +        getPosition: function(offset) {
  1.5085 +            var positions = this.positions;
  1.5086 +            return positions.get(offset) || positions.set(offset, new Position(this, offset));
  1.5087 +        },
  1.5088 +
  1.5089 +        toString: function() {
  1.5090 +            return "[NodeWrapper(" + dom.inspectNode(this.node) + ")]";
  1.5091 +        }
  1.5092 +    };
  1.5093 +
  1.5094 +    NodeWrapper.prototype = nodeProto;
  1.5095 +
  1.5096 +    var EMPTY = "EMPTY",
  1.5097 +        NON_SPACE = "NON_SPACE",
  1.5098 +        UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE",
  1.5099 +        COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE",
  1.5100 +        TRAILING_SPACE_BEFORE_BLOCK = "TRAILING_SPACE_BEFORE_BLOCK",
  1.5101 +        TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK",
  1.5102 +        TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR",
  1.5103 +        PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK",
  1.5104 +        TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR",
  1.5105 +        INCLUDED_TRAILING_LINE_BREAK_AFTER_BR = "INCLUDED_TRAILING_LINE_BREAK_AFTER_BR";
  1.5106 +
  1.5107 +    extend(nodeProto, {
  1.5108 +        isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"),
  1.5109 +        getNodeIndex: createCachingGetter("nodeIndex", dom.getNodeIndex, "node"),
  1.5110 +        getLength: createCachingGetter("nodeLength", dom.getNodeLength, "node"),
  1.5111 +        containsPositions: createCachingGetter("containsPositions", containsPositions, "node"),
  1.5112 +        isWhitespace: createCachingGetter("isWhitespace", isWhitespaceNode, "node"),
  1.5113 +        isCollapsedWhitespace: createCachingGetter("isCollapsedWhitespace", isCollapsedWhitespaceNode, "node"),
  1.5114 +        getComputedDisplay: createCachingGetter("computedDisplay", getComputedDisplay, "node"),
  1.5115 +        isCollapsed: createCachingGetter("collapsed", isCollapsedNode, "node"),
  1.5116 +        isIgnored: createCachingGetter("ignored", isIgnoredNode, "node"),
  1.5117 +        next: createCachingGetter("nextPos", nextNode, "node"),
  1.5118 +        previous: createCachingGetter("previous", previousNode, "node"),
  1.5119 +
  1.5120 +        getTextNodeInfo: createCachingGetter("textNodeInfo", function(textNode) {
  1.5121 +            var spaceRegex = null, collapseSpaces = false;
  1.5122 +            var cssWhitespace = getComputedStyleProperty(textNode.parentNode, "whiteSpace");
  1.5123 +            var preLine = (cssWhitespace == "pre-line");
  1.5124 +            if (preLine) {
  1.5125 +                spaceRegex = spacesMinusLineBreaksRegex;
  1.5126 +                collapseSpaces = true;
  1.5127 +            } else if (cssWhitespace == "normal" || cssWhitespace == "nowrap") {
  1.5128 +                spaceRegex = spacesRegex;
  1.5129 +                collapseSpaces = true;
  1.5130 +            }
  1.5131 +
  1.5132 +            return {
  1.5133 +                node: textNode,
  1.5134 +                text: textNode.data,
  1.5135 +                spaceRegex: spaceRegex,
  1.5136 +                collapseSpaces: collapseSpaces,
  1.5137 +                preLine: preLine
  1.5138 +            };
  1.5139 +        }, "node"),
  1.5140 +
  1.5141 +        hasInnerText: createCachingGetter("hasInnerText", function(el, backward) {
  1.5142 +            var session = this.session;
  1.5143 +            var posAfterEl = session.getPosition(el.parentNode, this.getNodeIndex() + 1);
  1.5144 +            var firstPosInEl = session.getPosition(el, 0);
  1.5145 +
  1.5146 +            var pos = backward ? posAfterEl : firstPosInEl;
  1.5147 +            var endPos = backward ? firstPosInEl : posAfterEl;
  1.5148 +
  1.5149 +            /*
  1.5150 +             <body><p>X  </p><p>Y</p></body>
  1.5151 +
  1.5152 +             Positions:
  1.5153 +
  1.5154 +             body:0:""
  1.5155 +             p:0:""
  1.5156 +             text:0:""
  1.5157 +             text:1:"X"
  1.5158 +             text:2:TRAILING_SPACE_IN_BLOCK
  1.5159 +             text:3:COLLAPSED_SPACE
  1.5160 +             p:1:""
  1.5161 +             body:1:"\n"
  1.5162 +             p:0:""
  1.5163 +             text:0:""
  1.5164 +             text:1:"Y"
  1.5165 +
  1.5166 +             A character is a TRAILING_SPACE_IN_BLOCK iff:
  1.5167 +
  1.5168 +             - There is no uncollapsed character after it within the visible containing block element
  1.5169 +
  1.5170 +             A character is a TRAILING_SPACE_BEFORE_BR iff:
  1.5171 +
  1.5172 +             - There is no uncollapsed character after it preceding a <br> element
  1.5173 +
  1.5174 +             An element has inner text iff
  1.5175 +
  1.5176 +             - It is not hidden
  1.5177 +             - It contains an uncollapsed character
  1.5178 +
  1.5179 +             All trailing spaces (pre-line, before <br>, end of block) require definite non-empty characters to render.
  1.5180 +             */
  1.5181 +
  1.5182 +            while (pos !== endPos) {
  1.5183 +                pos.prepopulateChar();
  1.5184 +                if (pos.isDefinitelyNonEmpty()) {
  1.5185 +                    return true;
  1.5186 +                }
  1.5187 +                pos = backward ? pos.previousVisible() : pos.nextVisible();
  1.5188 +            }
  1.5189 +
  1.5190 +            return false;
  1.5191 +        }, "node"),
  1.5192 +
  1.5193 +        isRenderedBlock: createCachingGetter("isRenderedBlock", function(el) {
  1.5194 +            // Ensure that a block element containing a <br> is considered to have inner text
  1.5195 +            var brs = el.getElementsByTagName("br");
  1.5196 +            for (var i = 0, len = brs.length; i < len; ++i) {
  1.5197 +                if (!isCollapsedNode(brs[i])) {
  1.5198 +                    return true;
  1.5199 +                }
  1.5200 +            }
  1.5201 +            return this.hasInnerText();
  1.5202 +        }, "node"),
  1.5203 +
  1.5204 +        getTrailingSpace: createCachingGetter("trailingSpace", function(el) {
  1.5205 +            if (el.tagName.toLowerCase() == "br") {
  1.5206 +                return "";
  1.5207 +            } else {
  1.5208 +                switch (this.getComputedDisplay()) {
  1.5209 +                    case "inline":
  1.5210 +                        var child = el.lastChild;
  1.5211 +                        while (child) {
  1.5212 +                            if (!isIgnoredNode(child)) {
  1.5213 +                                return (child.nodeType == 1) ? this.session.getNodeWrapper(child).getTrailingSpace() : "";
  1.5214 +                            }
  1.5215 +                            child = child.previousSibling;
  1.5216 +                        }
  1.5217 +                        break;
  1.5218 +                    case "inline-block":
  1.5219 +                    case "inline-table":
  1.5220 +                    case "none":
  1.5221 +                    case "table-column":
  1.5222 +                    case "table-column-group":
  1.5223 +                        break;
  1.5224 +                    case "table-cell":
  1.5225 +                        return "\t";
  1.5226 +                    default:
  1.5227 +                        return this.isRenderedBlock(true) ? "\n" : "";
  1.5228 +                }
  1.5229 +            }
  1.5230 +            return "";
  1.5231 +        }, "node"),
  1.5232 +
  1.5233 +        getLeadingSpace: createCachingGetter("leadingSpace", function(el) {
  1.5234 +            switch (this.getComputedDisplay()) {
  1.5235 +                case "inline":
  1.5236 +                case "inline-block":
  1.5237 +                case "inline-table":
  1.5238 +                case "none":
  1.5239 +                case "table-column":
  1.5240 +                case "table-column-group":
  1.5241 +                case "table-cell":
  1.5242 +                    break;
  1.5243 +                default:
  1.5244 +                    return this.isRenderedBlock(false) ? "\n" : "";
  1.5245 +            }
  1.5246 +            return "";
  1.5247 +        }, "node")
  1.5248 +    });
  1.5249 +
  1.5250 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.5251 +
  1.5252 +    function Position(nodeWrapper, offset) {
  1.5253 +        this.offset = offset;
  1.5254 +        this.nodeWrapper = nodeWrapper;
  1.5255 +        this.node = nodeWrapper.node;
  1.5256 +        this.session = nodeWrapper.session;
  1.5257 +        this.cache = new Cache();
  1.5258 +    }
  1.5259 +
  1.5260 +    function inspectPosition() {
  1.5261 +        return "[Position(" + dom.inspectNode(this.node) + ":" + this.offset + ")]";
  1.5262 +    }
  1.5263 +
  1.5264 +    var positionProto = {
  1.5265 +        character: "",
  1.5266 +        characterType: EMPTY,
  1.5267 +        isBr: false,
  1.5268 +
  1.5269 +        /*
  1.5270 +        This method:
  1.5271 +        - Fully populates positions that have characters that can be determined independently of any other characters.
  1.5272 +        - Populates most types of space positions with a provisional character. The character is finalized later.
  1.5273 +         */
  1.5274 +        prepopulateChar: function() {
  1.5275 +            var pos = this;
  1.5276 +            if (!pos.prepopulatedChar) {
  1.5277 +                var node = pos.node, offset = pos.offset;
  1.5278 +                var visibleChar = "", charType = EMPTY;
  1.5279 +                var finalizedChar = false;
  1.5280 +                if (offset > 0) {
  1.5281 +                    if (node.nodeType == 3) {
  1.5282 +                        var text = node.data;
  1.5283 +                        var textChar = text.charAt(offset - 1);
  1.5284 +
  1.5285 +                        var nodeInfo = pos.nodeWrapper.getTextNodeInfo();
  1.5286 +                        var spaceRegex = nodeInfo.spaceRegex;
  1.5287 +                        if (nodeInfo.collapseSpaces) {
  1.5288 +                            if (spaceRegex.test(textChar)) {
  1.5289 +                                // "If the character at position is from set, append a single space (U+0020) to newdata and advance
  1.5290 +                                // position until the character at position is not from set."
  1.5291 +
  1.5292 +                                // We also need to check for the case where we're in a pre-line and we have a space preceding a
  1.5293 +                                // line break, because such spaces are collapsed in some browsers
  1.5294 +                                if (offset > 1 && spaceRegex.test(text.charAt(offset - 2))) {
  1.5295 +                                } else if (nodeInfo.preLine && text.charAt(offset) === "\n") {
  1.5296 +                                    visibleChar = " ";
  1.5297 +                                    charType = PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK;
  1.5298 +                                } else {
  1.5299 +                                    visibleChar = " ";
  1.5300 +                                    //pos.checkForFollowingLineBreak = true;
  1.5301 +                                    charType = COLLAPSIBLE_SPACE;
  1.5302 +                                }
  1.5303 +                            } else {
  1.5304 +                                visibleChar = textChar;
  1.5305 +                                charType = NON_SPACE;
  1.5306 +                                finalizedChar = true;
  1.5307 +                            }
  1.5308 +                        } else {
  1.5309 +                            visibleChar = textChar;
  1.5310 +                            charType = UNCOLLAPSIBLE_SPACE;
  1.5311 +                            finalizedChar = true;
  1.5312 +                        }
  1.5313 +                    } else {
  1.5314 +                        var nodePassed = node.childNodes[offset - 1];
  1.5315 +                        if (nodePassed && nodePassed.nodeType == 1 && !isCollapsedNode(nodePassed)) {
  1.5316 +                            if (nodePassed.tagName.toLowerCase() == "br") {
  1.5317 +                                visibleChar = "\n";
  1.5318 +                                pos.isBr = true;
  1.5319 +                                charType = COLLAPSIBLE_SPACE;
  1.5320 +                                finalizedChar = false;
  1.5321 +                            } else {
  1.5322 +                                pos.checkForTrailingSpace = true;
  1.5323 +                            }
  1.5324 +                        }
  1.5325 +
  1.5326 +                        // Check the leading space of the next node for the case when a block element follows an inline
  1.5327 +                        // element or text node. In that case, there is an implied line break between the two nodes.
  1.5328 +                        if (!visibleChar) {
  1.5329 +                            var nextNode = node.childNodes[offset];
  1.5330 +                            if (nextNode && nextNode.nodeType == 1 && !isCollapsedNode(nextNode)) {
  1.5331 +                                pos.checkForLeadingSpace = true;
  1.5332 +                            }
  1.5333 +                        }
  1.5334 +                    }
  1.5335 +                }
  1.5336 +
  1.5337 +                pos.prepopulatedChar = true;
  1.5338 +                pos.character = visibleChar;
  1.5339 +                pos.characterType = charType;
  1.5340 +                pos.isCharInvariant = finalizedChar;
  1.5341 +            }
  1.5342 +        },
  1.5343 +
  1.5344 +        isDefinitelyNonEmpty: function() {
  1.5345 +            var charType = this.characterType;
  1.5346 +            return charType == NON_SPACE || charType == UNCOLLAPSIBLE_SPACE;
  1.5347 +        },
  1.5348 +
  1.5349 +        // Resolve leading and trailing spaces, which may involve prepopulating other positions
  1.5350 +        resolveLeadingAndTrailingSpaces: function() {
  1.5351 +            if (!this.prepopulatedChar) {
  1.5352 +                this.prepopulateChar();
  1.5353 +            }
  1.5354 +            if (this.checkForTrailingSpace) {
  1.5355 +                var trailingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset - 1]).getTrailingSpace();
  1.5356 +                if (trailingSpace) {
  1.5357 +                    this.isTrailingSpace = true;
  1.5358 +                    this.character = trailingSpace;
  1.5359 +                    this.characterType = COLLAPSIBLE_SPACE;
  1.5360 +                }
  1.5361 +                this.checkForTrailingSpace = false;
  1.5362 +            }
  1.5363 +            if (this.checkForLeadingSpace) {
  1.5364 +                var leadingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace();
  1.5365 +                if (leadingSpace) {
  1.5366 +                    this.isLeadingSpace = true;
  1.5367 +                    this.character = leadingSpace;
  1.5368 +                    this.characterType = COLLAPSIBLE_SPACE;
  1.5369 +                }
  1.5370 +                this.checkForLeadingSpace = false;
  1.5371 +            }
  1.5372 +        },
  1.5373 +
  1.5374 +        getPrecedingUncollapsedPosition: function(characterOptions) {
  1.5375 +            var pos = this, character;
  1.5376 +            while ( (pos = pos.previousVisible()) ) {
  1.5377 +                character = pos.getCharacter(characterOptions);
  1.5378 +                if (character !== "") {
  1.5379 +                    return pos;
  1.5380 +                }
  1.5381 +            }
  1.5382 +
  1.5383 +            return null;
  1.5384 +        },
  1.5385 +
  1.5386 +        getCharacter: function(characterOptions) {
  1.5387 +            this.resolveLeadingAndTrailingSpaces();
  1.5388 +
  1.5389 +            var thisChar = this.character, returnChar;
  1.5390 +
  1.5391 +            // Check if character is ignored
  1.5392 +            var ignoredChars = normalizeIgnoredCharacters(characterOptions.ignoreCharacters);
  1.5393 +            var isIgnoredCharacter = (thisChar !== "" && ignoredChars.indexOf(thisChar) > -1);
  1.5394 +
  1.5395 +            // Check if this position's  character is invariant (i.e. not dependent on character options) and return it
  1.5396 +            // if so
  1.5397 +            if (this.isCharInvariant) {
  1.5398 +                returnChar = isIgnoredCharacter ? "" : thisChar;
  1.5399 +                return returnChar;
  1.5400 +            }
  1.5401 +
  1.5402 +            var cacheKey = ["character", characterOptions.includeSpaceBeforeBr, characterOptions.includeBlockContentTrailingSpace, characterOptions.includePreLineTrailingSpace, ignoredChars].join("_");
  1.5403 +            var cachedChar = this.cache.get(cacheKey);
  1.5404 +            if (cachedChar !== null) {
  1.5405 +                return cachedChar;
  1.5406 +            }
  1.5407 +
  1.5408 +            // We need to actually get the character now
  1.5409 +            var character = "";
  1.5410 +            var collapsible = (this.characterType == COLLAPSIBLE_SPACE);
  1.5411 +
  1.5412 +            var nextPos, previousPos;
  1.5413 +            var gotPreviousPos = false;
  1.5414 +            var pos = this;
  1.5415 +
  1.5416 +            function getPreviousPos() {
  1.5417 +                if (!gotPreviousPos) {
  1.5418 +                    previousPos = pos.getPrecedingUncollapsedPosition(characterOptions);
  1.5419 +                    gotPreviousPos = true;
  1.5420 +                }
  1.5421 +                return previousPos;
  1.5422 +            }
  1.5423 +
  1.5424 +            // Disallow a collapsible space that is followed by a line break or is the last character
  1.5425 +            if (collapsible) {
  1.5426 +                // Allow a trailing space that we've previously determined should be included
  1.5427 +                if (this.type == INCLUDED_TRAILING_LINE_BREAK_AFTER_BR) {
  1.5428 +                    character = "\n";
  1.5429 +                }
  1.5430 +                // Disallow a collapsible space that follows a trailing space or line break, or is the first character,
  1.5431 +                // or follows a collapsible included space
  1.5432 +                else if (thisChar == " " &&
  1.5433 +                        (!getPreviousPos() || previousPos.isTrailingSpace || previousPos.character == "\n" || (previousPos.character == " " && previousPos.characterType == COLLAPSIBLE_SPACE))) {
  1.5434 +                }
  1.5435 +                // Allow a leading line break unless it follows a line break
  1.5436 +                else if (thisChar == "\n" && this.isLeadingSpace) {
  1.5437 +                    if (getPreviousPos() && previousPos.character != "\n") {
  1.5438 +                        character = "\n";
  1.5439 +                    } else {
  1.5440 +                    }
  1.5441 +                } else {
  1.5442 +                    nextPos = this.nextUncollapsed();
  1.5443 +                    if (nextPos) {
  1.5444 +                        if (nextPos.isBr) {
  1.5445 +                            this.type = TRAILING_SPACE_BEFORE_BR;
  1.5446 +                        } else if (nextPos.isTrailingSpace && nextPos.character == "\n") {
  1.5447 +                            this.type = TRAILING_SPACE_IN_BLOCK;
  1.5448 +                        } else if (nextPos.isLeadingSpace && nextPos.character == "\n") {
  1.5449 +                            this.type = TRAILING_SPACE_BEFORE_BLOCK;
  1.5450 +                        }
  1.5451 +
  1.5452 +                        if (nextPos.character == "\n") {
  1.5453 +                            if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) {
  1.5454 +                            } else if (this.type == TRAILING_SPACE_BEFORE_BLOCK && !characterOptions.includeSpaceBeforeBlock) {
  1.5455 +                            } else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) {
  1.5456 +                            } else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) {
  1.5457 +                            } else if (thisChar == "\n") {
  1.5458 +                                if (nextPos.isTrailingSpace) {
  1.5459 +                                    if (this.isTrailingSpace) {
  1.5460 +                                    } else if (this.isBr) {
  1.5461 +                                        nextPos.type = TRAILING_LINE_BREAK_AFTER_BR;
  1.5462 +
  1.5463 +                                        if (getPreviousPos() && previousPos.isLeadingSpace && !previousPos.isTrailingSpace && previousPos.character == "\n") {
  1.5464 +                                            nextPos.character = "";
  1.5465 +                                        } else {
  1.5466 +                                            nextPos.type = INCLUDED_TRAILING_LINE_BREAK_AFTER_BR;
  1.5467 +                                        }
  1.5468 +                                    }
  1.5469 +                                } else {
  1.5470 +                                    character = "\n";
  1.5471 +                                }
  1.5472 +                            } else if (thisChar == " ") {
  1.5473 +                                character = " ";
  1.5474 +                            } else {
  1.5475 +                            }
  1.5476 +                        } else {
  1.5477 +                            character = thisChar;
  1.5478 +                        }
  1.5479 +                    } else {
  1.5480 +                    }
  1.5481 +                }
  1.5482 +            }
  1.5483 +
  1.5484 +            if (ignoredChars.indexOf(character) > -1) {
  1.5485 +                character = "";
  1.5486 +            }
  1.5487 +
  1.5488 +
  1.5489 +            this.cache.set(cacheKey, character);
  1.5490 +
  1.5491 +            return character;
  1.5492 +        },
  1.5493 +
  1.5494 +        equals: function(pos) {
  1.5495 +            return !!pos && this.node === pos.node && this.offset === pos.offset;
  1.5496 +        },
  1.5497 +
  1.5498 +        inspect: inspectPosition,
  1.5499 +
  1.5500 +        toString: function() {
  1.5501 +            return this.character;
  1.5502 +        }
  1.5503 +    };
  1.5504 +
  1.5505 +    Position.prototype = positionProto;
  1.5506 +
  1.5507 +    extend(positionProto, {
  1.5508 +        next: createCachingGetter("nextPos", function(pos) {
  1.5509 +            var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
  1.5510 +            if (!node) {
  1.5511 +                return null;
  1.5512 +            }
  1.5513 +            var nextNode, nextOffset, child;
  1.5514 +            if (offset == nodeWrapper.getLength()) {
  1.5515 +                // Move onto the next node
  1.5516 +                nextNode = node.parentNode;
  1.5517 +                nextOffset = nextNode ? nodeWrapper.getNodeIndex() + 1 : 0;
  1.5518 +            } else {
  1.5519 +                if (nodeWrapper.isCharacterDataNode()) {
  1.5520 +                    nextNode = node;
  1.5521 +                    nextOffset = offset + 1;
  1.5522 +                } else {
  1.5523 +                    child = node.childNodes[offset];
  1.5524 +                    // Go into the children next, if children there are
  1.5525 +                    if (session.getNodeWrapper(child).containsPositions()) {
  1.5526 +                        nextNode = child;
  1.5527 +                        nextOffset = 0;
  1.5528 +                    } else {
  1.5529 +                        nextNode = node;
  1.5530 +                        nextOffset = offset + 1;
  1.5531 +                    }
  1.5532 +                }
  1.5533 +            }
  1.5534 +
  1.5535 +            return nextNode ? session.getPosition(nextNode, nextOffset) : null;
  1.5536 +        }),
  1.5537 +
  1.5538 +        previous: createCachingGetter("previous", function(pos) {
  1.5539 +            var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
  1.5540 +            var previousNode, previousOffset, child;
  1.5541 +            if (offset == 0) {
  1.5542 +                previousNode = node.parentNode;
  1.5543 +                previousOffset = previousNode ? nodeWrapper.getNodeIndex() : 0;
  1.5544 +            } else {
  1.5545 +                if (nodeWrapper.isCharacterDataNode()) {
  1.5546 +                    previousNode = node;
  1.5547 +                    previousOffset = offset - 1;
  1.5548 +                } else {
  1.5549 +                    child = node.childNodes[offset - 1];
  1.5550 +                    // Go into the children next, if children there are
  1.5551 +                    if (session.getNodeWrapper(child).containsPositions()) {
  1.5552 +                        previousNode = child;
  1.5553 +                        previousOffset = dom.getNodeLength(child);
  1.5554 +                    } else {
  1.5555 +                        previousNode = node;
  1.5556 +                        previousOffset = offset - 1;
  1.5557 +                    }
  1.5558 +                }
  1.5559 +            }
  1.5560 +            return previousNode ? session.getPosition(previousNode, previousOffset) : null;
  1.5561 +        }),
  1.5562 +
  1.5563 +        /*
  1.5564 +         Next and previous position moving functions that filter out
  1.5565 +
  1.5566 +         - Hidden (CSS visibility/display) elements
  1.5567 +         - Script and style elements
  1.5568 +         */
  1.5569 +        nextVisible: createCachingGetter("nextVisible", function(pos) {
  1.5570 +            var next = pos.next();
  1.5571 +            if (!next) {
  1.5572 +                return null;
  1.5573 +            }
  1.5574 +            var nodeWrapper = next.nodeWrapper, node = next.node;
  1.5575 +            var newPos = next;
  1.5576 +            if (nodeWrapper.isCollapsed()) {
  1.5577 +                // We're skipping this node and all its descendants
  1.5578 +                newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex() + 1);
  1.5579 +            }
  1.5580 +            return newPos;
  1.5581 +        }),
  1.5582 +
  1.5583 +        nextUncollapsed: createCachingGetter("nextUncollapsed", function(pos) {
  1.5584 +            var nextPos = pos;
  1.5585 +            while ( (nextPos = nextPos.nextVisible()) ) {
  1.5586 +                nextPos.resolveLeadingAndTrailingSpaces();
  1.5587 +                if (nextPos.character !== "") {
  1.5588 +                    return nextPos;
  1.5589 +                }
  1.5590 +            }
  1.5591 +            return null;
  1.5592 +        }),
  1.5593 +
  1.5594 +        previousVisible: createCachingGetter("previousVisible", function(pos) {
  1.5595 +            var previous = pos.previous();
  1.5596 +            if (!previous) {
  1.5597 +                return null;
  1.5598 +            }
  1.5599 +            var nodeWrapper = previous.nodeWrapper, node = previous.node;
  1.5600 +            var newPos = previous;
  1.5601 +            if (nodeWrapper.isCollapsed()) {
  1.5602 +                // We're skipping this node and all its descendants
  1.5603 +                newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex());
  1.5604 +            }
  1.5605 +            return newPos;
  1.5606 +        })
  1.5607 +    });
  1.5608 +
  1.5609 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.5610 +
  1.5611 +    var currentSession = null;
  1.5612 +
  1.5613 +    var Session = (function() {
  1.5614 +        function createWrapperCache(nodeProperty) {
  1.5615 +            var cache = new Cache();
  1.5616 +
  1.5617 +            return {
  1.5618 +                get: function(node) {
  1.5619 +                    var wrappersByProperty = cache.get(node[nodeProperty]);
  1.5620 +                    if (wrappersByProperty) {
  1.5621 +                        for (var i = 0, wrapper; wrapper = wrappersByProperty[i++]; ) {
  1.5622 +                            if (wrapper.node === node) {
  1.5623 +                                return wrapper;
  1.5624 +                            }
  1.5625 +                        }
  1.5626 +                    }
  1.5627 +                    return null;
  1.5628 +                },
  1.5629 +
  1.5630 +                set: function(nodeWrapper) {
  1.5631 +                    var property = nodeWrapper.node[nodeProperty];
  1.5632 +                    var wrappersByProperty = cache.get(property) || cache.set(property, []);
  1.5633 +                    wrappersByProperty.push(nodeWrapper);
  1.5634 +                }
  1.5635 +            };
  1.5636 +        }
  1.5637 +
  1.5638 +        var uniqueIDSupported = util.isHostProperty(document.documentElement, "uniqueID");
  1.5639 +
  1.5640 +        function Session() {
  1.5641 +            this.initCaches();
  1.5642 +        }
  1.5643 +
  1.5644 +        Session.prototype = {
  1.5645 +            initCaches: function() {
  1.5646 +                this.elementCache = uniqueIDSupported ? (function() {
  1.5647 +                    var elementsCache = new Cache();
  1.5648 +
  1.5649 +                    return {
  1.5650 +                        get: function(el) {
  1.5651 +                            return elementsCache.get(el.uniqueID);
  1.5652 +                        },
  1.5653 +
  1.5654 +                        set: function(elWrapper) {
  1.5655 +                            elementsCache.set(elWrapper.node.uniqueID, elWrapper);
  1.5656 +                        }
  1.5657 +                    };
  1.5658 +                })() : createWrapperCache("tagName");
  1.5659 +
  1.5660 +                // Store text nodes keyed by data, although we may need to truncate this
  1.5661 +                this.textNodeCache = createWrapperCache("data");
  1.5662 +                this.otherNodeCache = createWrapperCache("nodeName");
  1.5663 +            },
  1.5664 +
  1.5665 +            getNodeWrapper: function(node) {
  1.5666 +                var wrapperCache;
  1.5667 +                switch (node.nodeType) {
  1.5668 +                    case 1:
  1.5669 +                        wrapperCache = this.elementCache;
  1.5670 +                        break;
  1.5671 +                    case 3:
  1.5672 +                        wrapperCache = this.textNodeCache;
  1.5673 +                        break;
  1.5674 +                    default:
  1.5675 +                        wrapperCache = this.otherNodeCache;
  1.5676 +                        break;
  1.5677 +                }
  1.5678 +
  1.5679 +                var wrapper = wrapperCache.get(node);
  1.5680 +                if (!wrapper) {
  1.5681 +                    wrapper = new NodeWrapper(node, this);
  1.5682 +                    wrapperCache.set(wrapper);
  1.5683 +                }
  1.5684 +                return wrapper;
  1.5685 +            },
  1.5686 +
  1.5687 +            getPosition: function(node, offset) {
  1.5688 +                return this.getNodeWrapper(node).getPosition(offset);
  1.5689 +            },
  1.5690 +
  1.5691 +            getRangeBoundaryPosition: function(range, isStart) {
  1.5692 +                var prefix = isStart ? "start" : "end";
  1.5693 +                return this.getPosition(range[prefix + "Container"], range[prefix + "Offset"]);
  1.5694 +            },
  1.5695 +
  1.5696 +            detach: function() {
  1.5697 +                this.elementCache = this.textNodeCache = this.otherNodeCache = null;
  1.5698 +            }
  1.5699 +        };
  1.5700 +
  1.5701 +        return Session;
  1.5702 +    })();
  1.5703 +
  1.5704 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.5705 +
  1.5706 +    function startSession() {
  1.5707 +        endSession();
  1.5708 +        return (currentSession = new Session());
  1.5709 +    }
  1.5710 +
  1.5711 +    function getSession() {
  1.5712 +        return currentSession || startSession();
  1.5713 +    }
  1.5714 +
  1.5715 +    function endSession() {
  1.5716 +        if (currentSession) {
  1.5717 +            currentSession.detach();
  1.5718 +        }
  1.5719 +        currentSession = null;
  1.5720 +    }
  1.5721 +
  1.5722 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.5723 +
  1.5724 +    // Extensions to the rangy.dom utility object
  1.5725 +
  1.5726 +    extend(dom, {
  1.5727 +        nextNode: nextNode,
  1.5728 +        previousNode: previousNode
  1.5729 +    });
  1.5730 +
  1.5731 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.5732 +
  1.5733 +    function createCharacterIterator(startPos, backward, endPos, characterOptions) {
  1.5734 +
  1.5735 +        // Adjust the end position to ensure that it is actually reached
  1.5736 +        if (endPos) {
  1.5737 +            if (backward) {
  1.5738 +                if (isCollapsedNode(endPos.node)) {
  1.5739 +                    endPos = startPos.previousVisible();
  1.5740 +                }
  1.5741 +            } else {
  1.5742 +                if (isCollapsedNode(endPos.node)) {
  1.5743 +                    endPos = endPos.nextVisible();
  1.5744 +                }
  1.5745 +            }
  1.5746 +        }
  1.5747 +
  1.5748 +        var pos = startPos, finished = false;
  1.5749 +
  1.5750 +        function next() {
  1.5751 +            var charPos = null;
  1.5752 +            if (backward) {
  1.5753 +                charPos = pos;
  1.5754 +                if (!finished) {
  1.5755 +                    pos = pos.previousVisible();
  1.5756 +                    finished = !pos || (endPos && pos.equals(endPos));
  1.5757 +                }
  1.5758 +            } else {
  1.5759 +                if (!finished) {
  1.5760 +                    charPos = pos = pos.nextVisible();
  1.5761 +                    finished = !pos || (endPos && pos.equals(endPos));
  1.5762 +                }
  1.5763 +            }
  1.5764 +            if (finished) {
  1.5765 +                pos = null;
  1.5766 +            }
  1.5767 +            return charPos;
  1.5768 +        }
  1.5769 +
  1.5770 +        var previousTextPos, returnPreviousTextPos = false;
  1.5771 +
  1.5772 +        return {
  1.5773 +            next: function() {
  1.5774 +                if (returnPreviousTextPos) {
  1.5775 +                    returnPreviousTextPos = false;
  1.5776 +                    return previousTextPos;
  1.5777 +                } else {
  1.5778 +                    var pos, character;
  1.5779 +                    while ( (pos = next()) ) {
  1.5780 +                        character = pos.getCharacter(characterOptions);
  1.5781 +                        if (character) {
  1.5782 +                            previousTextPos = pos;
  1.5783 +                            return pos;
  1.5784 +                        }
  1.5785 +                    }
  1.5786 +                    return null;
  1.5787 +                }
  1.5788 +            },
  1.5789 +
  1.5790 +            rewind: function() {
  1.5791 +                if (previousTextPos) {
  1.5792 +                    returnPreviousTextPos = true;
  1.5793 +                } else {
  1.5794 +                    throw module.createError("createCharacterIterator: cannot rewind. Only one position can be rewound.");
  1.5795 +                }
  1.5796 +            },
  1.5797 +
  1.5798 +            dispose: function() {
  1.5799 +                startPos = endPos = null;
  1.5800 +            }
  1.5801 +        };
  1.5802 +    }
  1.5803 +
  1.5804 +    var arrayIndexOf = Array.prototype.indexOf ?
  1.5805 +        function(arr, val) {
  1.5806 +            return arr.indexOf(val);
  1.5807 +        } :
  1.5808 +        function(arr, val) {
  1.5809 +            for (var i = 0, len = arr.length; i < len; ++i) {
  1.5810 +                if (arr[i] === val) {
  1.5811 +                    return i;
  1.5812 +                }
  1.5813 +            }
  1.5814 +            return -1;
  1.5815 +        };
  1.5816 +
  1.5817 +    // Provides a pair of iterators over text positions, tokenized. Transparently requests more text when next()
  1.5818 +    // is called and there is no more tokenized text
  1.5819 +    function createTokenizedTextProvider(pos, characterOptions, wordOptions) {
  1.5820 +        var forwardIterator = createCharacterIterator(pos, false, null, characterOptions);
  1.5821 +        var backwardIterator = createCharacterIterator(pos, true, null, characterOptions);
  1.5822 +        var tokenizer = wordOptions.tokenizer;
  1.5823 +
  1.5824 +        // Consumes a word and the whitespace beyond it
  1.5825 +        function consumeWord(forward) {
  1.5826 +            var pos, textChar;
  1.5827 +            var newChars = [], it = forward ? forwardIterator : backwardIterator;
  1.5828 +
  1.5829 +            var passedWordBoundary = false, insideWord = false;
  1.5830 +
  1.5831 +            while ( (pos = it.next()) ) {
  1.5832 +                textChar = pos.character;
  1.5833 +
  1.5834 +
  1.5835 +                if (allWhiteSpaceRegex.test(textChar)) {
  1.5836 +                    if (insideWord) {
  1.5837 +                        insideWord = false;
  1.5838 +                        passedWordBoundary = true;
  1.5839 +                    }
  1.5840 +                } else {
  1.5841 +                    if (passedWordBoundary) {
  1.5842 +                        it.rewind();
  1.5843 +                        break;
  1.5844 +                    } else {
  1.5845 +                        insideWord = true;
  1.5846 +                    }
  1.5847 +                }
  1.5848 +                newChars.push(pos);
  1.5849 +            }
  1.5850 +
  1.5851 +
  1.5852 +            return newChars;
  1.5853 +        }
  1.5854 +
  1.5855 +        // Get initial word surrounding initial position and tokenize it
  1.5856 +        var forwardChars = consumeWord(true);
  1.5857 +        var backwardChars = consumeWord(false).reverse();
  1.5858 +        var tokens = tokenize(backwardChars.concat(forwardChars), wordOptions, tokenizer);
  1.5859 +
  1.5860 +        // Create initial token buffers
  1.5861 +        var forwardTokensBuffer = forwardChars.length ?
  1.5862 +            tokens.slice(arrayIndexOf(tokens, forwardChars[0].token)) : [];
  1.5863 +
  1.5864 +        var backwardTokensBuffer = backwardChars.length ?
  1.5865 +            tokens.slice(0, arrayIndexOf(tokens, backwardChars.pop().token) + 1) : [];
  1.5866 +
  1.5867 +        function inspectBuffer(buffer) {
  1.5868 +            var textPositions = ["[" + buffer.length + "]"];
  1.5869 +            for (var i = 0; i < buffer.length; ++i) {
  1.5870 +                textPositions.push("(word: " + buffer[i] + ", is word: " + buffer[i].isWord + ")");
  1.5871 +            }
  1.5872 +            return textPositions;
  1.5873 +        }
  1.5874 +
  1.5875 +
  1.5876 +        return {
  1.5877 +            nextEndToken: function() {
  1.5878 +                var lastToken, forwardChars;
  1.5879 +
  1.5880 +                // If we're down to the last token, consume character chunks until we have a word or run out of
  1.5881 +                // characters to consume
  1.5882 +                while ( forwardTokensBuffer.length == 1 &&
  1.5883 +                    !(lastToken = forwardTokensBuffer[0]).isWord &&
  1.5884 +                    (forwardChars = consumeWord(true)).length > 0) {
  1.5885 +
  1.5886 +                    // Merge trailing non-word into next word and tokenize
  1.5887 +                    forwardTokensBuffer = tokenize(lastToken.chars.concat(forwardChars), wordOptions, tokenizer);
  1.5888 +                }
  1.5889 +
  1.5890 +                return forwardTokensBuffer.shift();
  1.5891 +            },
  1.5892 +
  1.5893 +            previousStartToken: function() {
  1.5894 +                var lastToken, backwardChars;
  1.5895 +
  1.5896 +                // If we're down to the last token, consume character chunks until we have a word or run out of
  1.5897 +                // characters to consume
  1.5898 +                while ( backwardTokensBuffer.length == 1 &&
  1.5899 +                    !(lastToken = backwardTokensBuffer[0]).isWord &&
  1.5900 +                    (backwardChars = consumeWord(false)).length > 0) {
  1.5901 +
  1.5902 +                    // Merge leading non-word into next word and tokenize
  1.5903 +                    backwardTokensBuffer = tokenize(backwardChars.reverse().concat(lastToken.chars), wordOptions, tokenizer);
  1.5904 +                }
  1.5905 +
  1.5906 +                return backwardTokensBuffer.pop();
  1.5907 +            },
  1.5908 +
  1.5909 +            dispose: function() {
  1.5910 +                forwardIterator.dispose();
  1.5911 +                backwardIterator.dispose();
  1.5912 +                forwardTokensBuffer = backwardTokensBuffer = null;
  1.5913 +            }
  1.5914 +        };
  1.5915 +    }
  1.5916 +
  1.5917 +    function movePositionBy(pos, unit, count, characterOptions, wordOptions) {
  1.5918 +        var unitsMoved = 0, currentPos, newPos = pos, charIterator, nextPos, absCount = Math.abs(count), token;
  1.5919 +        if (count !== 0) {
  1.5920 +            var backward = (count < 0);
  1.5921 +
  1.5922 +            switch (unit) {
  1.5923 +                case CHARACTER:
  1.5924 +                    charIterator = createCharacterIterator(pos, backward, null, characterOptions);
  1.5925 +                    while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) {
  1.5926 +                        ++unitsMoved;
  1.5927 +                        newPos = currentPos;
  1.5928 +                    }
  1.5929 +                    nextPos = currentPos;
  1.5930 +                    charIterator.dispose();
  1.5931 +                    break;
  1.5932 +                case WORD:
  1.5933 +                    var tokenizedTextProvider = createTokenizedTextProvider(pos, characterOptions, wordOptions);
  1.5934 +                    var next = backward ? tokenizedTextProvider.previousStartToken : tokenizedTextProvider.nextEndToken;
  1.5935 +
  1.5936 +                    while ( (token = next()) && unitsMoved < absCount ) {
  1.5937 +                        if (token.isWord) {
  1.5938 +                            ++unitsMoved;
  1.5939 +                            newPos = backward ? token.chars[0] : token.chars[token.chars.length - 1];
  1.5940 +                        }
  1.5941 +                    }
  1.5942 +                    break;
  1.5943 +                default:
  1.5944 +                    throw new Error("movePositionBy: unit '" + unit + "' not implemented");
  1.5945 +            }
  1.5946 +
  1.5947 +            // Perform any necessary position tweaks
  1.5948 +            if (backward) {
  1.5949 +                newPos = newPos.previousVisible();
  1.5950 +                unitsMoved = -unitsMoved;
  1.5951 +            } else if (newPos && newPos.isLeadingSpace && !newPos.isTrailingSpace) {
  1.5952 +                // Tweak the position for the case of a leading space. The problem is that an uncollapsed leading space
  1.5953 +                // before a block element (for example, the line break between "1" and "2" in the following HTML:
  1.5954 +                // "1<p>2</p>") is considered to be attached to the position immediately before the block element, which
  1.5955 +                // corresponds with a different selection position in most browsers from the one we want (i.e. at the
  1.5956 +                // start of the contents of the block element). We get round this by advancing the position returned to
  1.5957 +                // the last possible equivalent visible position.
  1.5958 +                if (unit == WORD) {
  1.5959 +                    charIterator = createCharacterIterator(pos, false, null, characterOptions);
  1.5960 +                    nextPos = charIterator.next();
  1.5961 +                    charIterator.dispose();
  1.5962 +                }
  1.5963 +                if (nextPos) {
  1.5964 +                    newPos = nextPos.previousVisible();
  1.5965 +                }
  1.5966 +            }
  1.5967 +        }
  1.5968 +
  1.5969 +
  1.5970 +        return {
  1.5971 +            position: newPos,
  1.5972 +            unitsMoved: unitsMoved
  1.5973 +        };
  1.5974 +    }
  1.5975 +
  1.5976 +    function createRangeCharacterIterator(session, range, characterOptions, backward) {
  1.5977 +        var rangeStart = session.getRangeBoundaryPosition(range, true);
  1.5978 +        var rangeEnd = session.getRangeBoundaryPosition(range, false);
  1.5979 +        var itStart = backward ? rangeEnd : rangeStart;
  1.5980 +        var itEnd = backward ? rangeStart : rangeEnd;
  1.5981 +
  1.5982 +        return createCharacterIterator(itStart, !!backward, itEnd, characterOptions);
  1.5983 +    }
  1.5984 +
  1.5985 +    function getRangeCharacters(session, range, characterOptions) {
  1.5986 +
  1.5987 +        var chars = [], it = createRangeCharacterIterator(session, range, characterOptions), pos;
  1.5988 +        while ( (pos = it.next()) ) {
  1.5989 +            chars.push(pos);
  1.5990 +        }
  1.5991 +
  1.5992 +        it.dispose();
  1.5993 +        return chars;
  1.5994 +    }
  1.5995 +
  1.5996 +    function isWholeWord(startPos, endPos, wordOptions) {
  1.5997 +        var range = api.createRange(startPos.node);
  1.5998 +        range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset);
  1.5999 +        return !range.expand("word", { wordOptions: wordOptions });
  1.6000 +    }
  1.6001 +
  1.6002 +    function findTextFromPosition(initialPos, searchTerm, isRegex, searchScopeRange, findOptions) {
  1.6003 +        var backward = isDirectionBackward(findOptions.direction);
  1.6004 +        var it = createCharacterIterator(
  1.6005 +            initialPos,
  1.6006 +            backward,
  1.6007 +            initialPos.session.getRangeBoundaryPosition(searchScopeRange, backward),
  1.6008 +            findOptions.characterOptions
  1.6009 +        );
  1.6010 +        var text = "", chars = [], pos, currentChar, matchStartIndex, matchEndIndex;
  1.6011 +        var result, insideRegexMatch;
  1.6012 +        var returnValue = null;
  1.6013 +
  1.6014 +        function handleMatch(startIndex, endIndex) {
  1.6015 +            var startPos = chars[startIndex].previousVisible();
  1.6016 +            var endPos = chars[endIndex - 1];
  1.6017 +            var valid = (!findOptions.wholeWordsOnly || isWholeWord(startPos, endPos, findOptions.wordOptions));
  1.6018 +
  1.6019 +            return {
  1.6020 +                startPos: startPos,
  1.6021 +                endPos: endPos,
  1.6022 +                valid: valid
  1.6023 +            };
  1.6024 +        }
  1.6025 +
  1.6026 +        while ( (pos = it.next()) ) {
  1.6027 +            currentChar = pos.character;
  1.6028 +            if (!isRegex && !findOptions.caseSensitive) {
  1.6029 +                currentChar = currentChar.toLowerCase();
  1.6030 +            }
  1.6031 +
  1.6032 +            if (backward) {
  1.6033 +                chars.unshift(pos);
  1.6034 +                text = currentChar + text;
  1.6035 +            } else {
  1.6036 +                chars.push(pos);
  1.6037 +                text += currentChar;
  1.6038 +            }
  1.6039 +
  1.6040 +            if (isRegex) {
  1.6041 +                result = searchTerm.exec(text);
  1.6042 +                if (result) {
  1.6043 +                    matchStartIndex = result.index;
  1.6044 +                    matchEndIndex = matchStartIndex + result[0].length;
  1.6045 +                    if (insideRegexMatch) {
  1.6046 +                        // Check whether the match is now over
  1.6047 +                        if ((!backward && matchEndIndex < text.length) || (backward && matchStartIndex > 0)) {
  1.6048 +                            returnValue = handleMatch(matchStartIndex, matchEndIndex);
  1.6049 +                            break;
  1.6050 +                        }
  1.6051 +                    } else {
  1.6052 +                        insideRegexMatch = true;
  1.6053 +                    }
  1.6054 +                }
  1.6055 +            } else if ( (matchStartIndex = text.indexOf(searchTerm)) != -1 ) {
  1.6056 +                returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length);
  1.6057 +                break;
  1.6058 +            }
  1.6059 +        }
  1.6060 +
  1.6061 +        // Check whether regex match extends to the end of the range
  1.6062 +        if (insideRegexMatch) {
  1.6063 +            returnValue = handleMatch(matchStartIndex, matchEndIndex);
  1.6064 +        }
  1.6065 +        it.dispose();
  1.6066 +
  1.6067 +        return returnValue;
  1.6068 +    }
  1.6069 +
  1.6070 +    function createEntryPointFunction(func) {
  1.6071 +        return function() {
  1.6072 +            var sessionRunning = !!currentSession;
  1.6073 +            var session = getSession();
  1.6074 +            var args = [session].concat( util.toArray(arguments) );
  1.6075 +            var returnValue = func.apply(this, args);
  1.6076 +            if (!sessionRunning) {
  1.6077 +                endSession();
  1.6078 +            }
  1.6079 +            return returnValue;
  1.6080 +        };
  1.6081 +    }
  1.6082 +
  1.6083 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.6084 +
  1.6085 +    // Extensions to the Rangy Range object
  1.6086 +
  1.6087 +    function createRangeBoundaryMover(isStart, collapse) {
  1.6088 +        /*
  1.6089 +         Unit can be "character" or "word"
  1.6090 +         Options:
  1.6091 +
  1.6092 +         - includeTrailingSpace
  1.6093 +         - wordRegex
  1.6094 +         - tokenizer
  1.6095 +         - collapseSpaceBeforeLineBreak
  1.6096 +         */
  1.6097 +        return createEntryPointFunction(
  1.6098 +            function(session, unit, count, moveOptions) {
  1.6099 +                if (typeof count == UNDEF) {
  1.6100 +                    count = unit;
  1.6101 +                    unit = CHARACTER;
  1.6102 +                }
  1.6103 +                moveOptions = createNestedOptions(moveOptions, defaultMoveOptions);
  1.6104 +
  1.6105 +                var boundaryIsStart = isStart;
  1.6106 +                if (collapse) {
  1.6107 +                    boundaryIsStart = (count >= 0);
  1.6108 +                    this.collapse(!boundaryIsStart);
  1.6109 +                }
  1.6110 +                var moveResult = movePositionBy(session.getRangeBoundaryPosition(this, boundaryIsStart), unit, count, moveOptions.characterOptions, moveOptions.wordOptions);
  1.6111 +                var newPos = moveResult.position;
  1.6112 +                this[boundaryIsStart ? "setStart" : "setEnd"](newPos.node, newPos.offset);
  1.6113 +                return moveResult.unitsMoved;
  1.6114 +            }
  1.6115 +        );
  1.6116 +    }
  1.6117 +
  1.6118 +    function createRangeTrimmer(isStart) {
  1.6119 +        return createEntryPointFunction(
  1.6120 +            function(session, characterOptions) {
  1.6121 +                characterOptions = createOptions(characterOptions, defaultCharacterOptions);
  1.6122 +                var pos;
  1.6123 +                var it = createRangeCharacterIterator(session, this, characterOptions, !isStart);
  1.6124 +                var trimCharCount = 0;
  1.6125 +                while ( (pos = it.next()) && allWhiteSpaceRegex.test(pos.character) ) {
  1.6126 +                    ++trimCharCount;
  1.6127 +                }
  1.6128 +                it.dispose();
  1.6129 +                var trimmed = (trimCharCount > 0);
  1.6130 +                if (trimmed) {
  1.6131 +                    this[isStart ? "moveStart" : "moveEnd"](
  1.6132 +                        "character",
  1.6133 +                        isStart ? trimCharCount : -trimCharCount,
  1.6134 +                        { characterOptions: characterOptions }
  1.6135 +                    );
  1.6136 +                }
  1.6137 +                return trimmed;
  1.6138 +            }
  1.6139 +        );
  1.6140 +    }
  1.6141 +
  1.6142 +    extend(api.rangePrototype, {
  1.6143 +        moveStart: createRangeBoundaryMover(true, false),
  1.6144 +
  1.6145 +        moveEnd: createRangeBoundaryMover(false, false),
  1.6146 +
  1.6147 +        move: createRangeBoundaryMover(true, true),
  1.6148 +
  1.6149 +        trimStart: createRangeTrimmer(true),
  1.6150 +
  1.6151 +        trimEnd: createRangeTrimmer(false),
  1.6152 +
  1.6153 +        trim: createEntryPointFunction(
  1.6154 +            function(session, characterOptions) {
  1.6155 +                var startTrimmed = this.trimStart(characterOptions), endTrimmed = this.trimEnd(characterOptions);
  1.6156 +                return startTrimmed || endTrimmed;
  1.6157 +            }
  1.6158 +        ),
  1.6159 +
  1.6160 +        expand: createEntryPointFunction(
  1.6161 +            function(session, unit, expandOptions) {
  1.6162 +                var moved = false;
  1.6163 +                expandOptions = createNestedOptions(expandOptions, defaultExpandOptions);
  1.6164 +                var characterOptions = expandOptions.characterOptions;
  1.6165 +                if (!unit) {
  1.6166 +                    unit = CHARACTER;
  1.6167 +                }
  1.6168 +                if (unit == WORD) {
  1.6169 +                    var wordOptions = expandOptions.wordOptions;
  1.6170 +                    var startPos = session.getRangeBoundaryPosition(this, true);
  1.6171 +                    var endPos = session.getRangeBoundaryPosition(this, false);
  1.6172 +
  1.6173 +                    var startTokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions);
  1.6174 +                    var startToken = startTokenizedTextProvider.nextEndToken();
  1.6175 +                    var newStartPos = startToken.chars[0].previousVisible();
  1.6176 +                    var endToken, newEndPos;
  1.6177 +
  1.6178 +                    if (this.collapsed) {
  1.6179 +                        endToken = startToken;
  1.6180 +                    } else {
  1.6181 +                        var endTokenizedTextProvider = createTokenizedTextProvider(endPos, characterOptions, wordOptions);
  1.6182 +                        endToken = endTokenizedTextProvider.previousStartToken();
  1.6183 +                    }
  1.6184 +                    newEndPos = endToken.chars[endToken.chars.length - 1];
  1.6185 +
  1.6186 +                    if (!newStartPos.equals(startPos)) {
  1.6187 +                        this.setStart(newStartPos.node, newStartPos.offset);
  1.6188 +                        moved = true;
  1.6189 +                    }
  1.6190 +                    if (newEndPos && !newEndPos.equals(endPos)) {
  1.6191 +                        this.setEnd(newEndPos.node, newEndPos.offset);
  1.6192 +                        moved = true;
  1.6193 +                    }
  1.6194 +
  1.6195 +                    if (expandOptions.trim) {
  1.6196 +                        if (expandOptions.trimStart) {
  1.6197 +                            moved = this.trimStart(characterOptions) || moved;
  1.6198 +                        }
  1.6199 +                        if (expandOptions.trimEnd) {
  1.6200 +                            moved = this.trimEnd(characterOptions) || moved;
  1.6201 +                        }
  1.6202 +                    }
  1.6203 +
  1.6204 +                    return moved;
  1.6205 +                } else {
  1.6206 +                    return this.moveEnd(CHARACTER, 1, expandOptions);
  1.6207 +                }
  1.6208 +            }
  1.6209 +        ),
  1.6210 +
  1.6211 +        text: createEntryPointFunction(
  1.6212 +            function(session, characterOptions) {
  1.6213 +                return this.collapsed ?
  1.6214 +                    "" : getRangeCharacters(session, this, createOptions(characterOptions, defaultCharacterOptions)).join("");
  1.6215 +            }
  1.6216 +        ),
  1.6217 +
  1.6218 +        selectCharacters: createEntryPointFunction(
  1.6219 +            function(session, containerNode, startIndex, endIndex, characterOptions) {
  1.6220 +                var moveOptions = { characterOptions: characterOptions };
  1.6221 +                if (!containerNode) {
  1.6222 +                    containerNode = getBody( this.getDocument() );
  1.6223 +                }
  1.6224 +                this.selectNodeContents(containerNode);
  1.6225 +                this.collapse(true);
  1.6226 +                this.moveStart("character", startIndex, moveOptions);
  1.6227 +                this.collapse(true);
  1.6228 +                this.moveEnd("character", endIndex - startIndex, moveOptions);
  1.6229 +            }
  1.6230 +        ),
  1.6231 +
  1.6232 +        // Character indexes are relative to the start of node
  1.6233 +        toCharacterRange: createEntryPointFunction(
  1.6234 +            function(session, containerNode, characterOptions) {
  1.6235 +                if (!containerNode) {
  1.6236 +                    containerNode = getBody( this.getDocument() );
  1.6237 +                }
  1.6238 +                var parent = containerNode.parentNode, nodeIndex = dom.getNodeIndex(containerNode);
  1.6239 +                var rangeStartsBeforeNode = (dom.comparePoints(this.startContainer, this.endContainer, parent, nodeIndex) == -1);
  1.6240 +                var rangeBetween = this.cloneRange();
  1.6241 +                var startIndex, endIndex;
  1.6242 +                if (rangeStartsBeforeNode) {
  1.6243 +                    rangeBetween.setStartAndEnd(this.startContainer, this.startOffset, parent, nodeIndex);
  1.6244 +                    startIndex = -rangeBetween.text(characterOptions).length;
  1.6245 +                } else {
  1.6246 +                    rangeBetween.setStartAndEnd(parent, nodeIndex, this.startContainer, this.startOffset);
  1.6247 +                    startIndex = rangeBetween.text(characterOptions).length;
  1.6248 +                }
  1.6249 +                endIndex = startIndex + this.text(characterOptions).length;
  1.6250 +
  1.6251 +                return {
  1.6252 +                    start: startIndex,
  1.6253 +                    end: endIndex
  1.6254 +                };
  1.6255 +            }
  1.6256 +        ),
  1.6257 +
  1.6258 +        findText: createEntryPointFunction(
  1.6259 +            function(session, searchTermParam, findOptions) {
  1.6260 +                // Set up options
  1.6261 +                findOptions = createNestedOptions(findOptions, defaultFindOptions);
  1.6262 +
  1.6263 +                // Create word options if we're matching whole words only
  1.6264 +                if (findOptions.wholeWordsOnly) {
  1.6265 +                    // We don't ever want trailing spaces for search results
  1.6266 +                    findOptions.wordOptions.includeTrailingSpace = false;
  1.6267 +                }
  1.6268 +
  1.6269 +                var backward = isDirectionBackward(findOptions.direction);
  1.6270 +
  1.6271 +                // Create a range representing the search scope if none was provided
  1.6272 +                var searchScopeRange = findOptions.withinRange;
  1.6273 +                if (!searchScopeRange) {
  1.6274 +                    searchScopeRange = api.createRange();
  1.6275 +                    searchScopeRange.selectNodeContents(this.getDocument());
  1.6276 +                }
  1.6277 +
  1.6278 +                // Examine and prepare the search term
  1.6279 +                var searchTerm = searchTermParam, isRegex = false;
  1.6280 +                if (typeof searchTerm == "string") {
  1.6281 +                    if (!findOptions.caseSensitive) {
  1.6282 +                        searchTerm = searchTerm.toLowerCase();
  1.6283 +                    }
  1.6284 +                } else {
  1.6285 +                    isRegex = true;
  1.6286 +                }
  1.6287 +
  1.6288 +                var initialPos = session.getRangeBoundaryPosition(this, !backward);
  1.6289 +
  1.6290 +                // Adjust initial position if it lies outside the search scope
  1.6291 +                var comparison = searchScopeRange.comparePoint(initialPos.node, initialPos.offset);
  1.6292 +
  1.6293 +                if (comparison === -1) {
  1.6294 +                    initialPos = session.getRangeBoundaryPosition(searchScopeRange, true);
  1.6295 +                } else if (comparison === 1) {
  1.6296 +                    initialPos = session.getRangeBoundaryPosition(searchScopeRange, false);
  1.6297 +                }
  1.6298 +
  1.6299 +                var pos = initialPos;
  1.6300 +                var wrappedAround = false;
  1.6301 +
  1.6302 +                // Try to find a match and ignore invalid ones
  1.6303 +                var findResult;
  1.6304 +                while (true) {
  1.6305 +                    findResult = findTextFromPosition(pos, searchTerm, isRegex, searchScopeRange, findOptions);
  1.6306 +
  1.6307 +                    if (findResult) {
  1.6308 +                        if (findResult.valid) {
  1.6309 +                            this.setStartAndEnd(findResult.startPos.node, findResult.startPos.offset, findResult.endPos.node, findResult.endPos.offset);
  1.6310 +                            return true;
  1.6311 +                        } else {
  1.6312 +                            // We've found a match that is not a whole word, so we carry on searching from the point immediately
  1.6313 +                            // after the match
  1.6314 +                            pos = backward ? findResult.startPos : findResult.endPos;
  1.6315 +                        }
  1.6316 +                    } else if (findOptions.wrap && !wrappedAround) {
  1.6317 +                        // No result found but we're wrapping around and limiting the scope to the unsearched part of the range
  1.6318 +                        searchScopeRange = searchScopeRange.cloneRange();
  1.6319 +                        pos = session.getRangeBoundaryPosition(searchScopeRange, !backward);
  1.6320 +                        searchScopeRange.setBoundary(initialPos.node, initialPos.offset, backward);
  1.6321 +                        wrappedAround = true;
  1.6322 +                    } else {
  1.6323 +                        // Nothing found and we can't wrap around, so we're done
  1.6324 +                        return false;
  1.6325 +                    }
  1.6326 +                }
  1.6327 +            }
  1.6328 +        ),
  1.6329 +
  1.6330 +        pasteHtml: function(html) {
  1.6331 +            this.deleteContents();
  1.6332 +            if (html) {
  1.6333 +                var frag = this.createContextualFragment(html);
  1.6334 +                var lastChild = frag.lastChild;
  1.6335 +                this.insertNode(frag);
  1.6336 +                this.collapseAfter(lastChild);
  1.6337 +            }
  1.6338 +        }
  1.6339 +    });
  1.6340 +
  1.6341 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.6342 +
  1.6343 +    // Extensions to the Rangy Selection object
  1.6344 +
  1.6345 +    function createSelectionTrimmer(methodName) {
  1.6346 +        return createEntryPointFunction(
  1.6347 +            function(session, characterOptions) {
  1.6348 +                var trimmed = false;
  1.6349 +                this.changeEachRange(function(range) {
  1.6350 +                    trimmed = range[methodName](characterOptions) || trimmed;
  1.6351 +                });
  1.6352 +                return trimmed;
  1.6353 +            }
  1.6354 +        );
  1.6355 +    }
  1.6356 +
  1.6357 +    extend(api.selectionPrototype, {
  1.6358 +        expand: createEntryPointFunction(
  1.6359 +            function(session, unit, expandOptions) {
  1.6360 +                this.changeEachRange(function(range) {
  1.6361 +                    range.expand(unit, expandOptions);
  1.6362 +                });
  1.6363 +            }
  1.6364 +        ),
  1.6365 +
  1.6366 +        move: createEntryPointFunction(
  1.6367 +            function(session, unit, count, options) {
  1.6368 +                var unitsMoved = 0;
  1.6369 +                if (this.focusNode) {
  1.6370 +                    this.collapse(this.focusNode, this.focusOffset);
  1.6371 +                    var range = this.getRangeAt(0);
  1.6372 +                    if (!options) {
  1.6373 +                        options = {};
  1.6374 +                    }
  1.6375 +                    options.characterOptions = createOptions(options.characterOptions, defaultCaretCharacterOptions);
  1.6376 +                    unitsMoved = range.move(unit, count, options);
  1.6377 +                    this.setSingleRange(range);
  1.6378 +                }
  1.6379 +                return unitsMoved;
  1.6380 +            }
  1.6381 +        ),
  1.6382 +
  1.6383 +        trimStart: createSelectionTrimmer("trimStart"),
  1.6384 +        trimEnd: createSelectionTrimmer("trimEnd"),
  1.6385 +        trim: createSelectionTrimmer("trim"),
  1.6386 +
  1.6387 +        selectCharacters: createEntryPointFunction(
  1.6388 +            function(session, containerNode, startIndex, endIndex, direction, characterOptions) {
  1.6389 +                var range = api.createRange(containerNode);
  1.6390 +                range.selectCharacters(containerNode, startIndex, endIndex, characterOptions);
  1.6391 +                this.setSingleRange(range, direction);
  1.6392 +            }
  1.6393 +        ),
  1.6394 +
  1.6395 +        saveCharacterRanges: createEntryPointFunction(
  1.6396 +            function(session, containerNode, characterOptions) {
  1.6397 +                var ranges = this.getAllRanges(), rangeCount = ranges.length;
  1.6398 +                var rangeInfos = [];
  1.6399 +
  1.6400 +                var backward = rangeCount == 1 && this.isBackward();
  1.6401 +
  1.6402 +                for (var i = 0, len = ranges.length; i < len; ++i) {
  1.6403 +                    rangeInfos[i] = {
  1.6404 +                        characterRange: ranges[i].toCharacterRange(containerNode, characterOptions),
  1.6405 +                        backward: backward,
  1.6406 +                        characterOptions: characterOptions
  1.6407 +                    };
  1.6408 +                }
  1.6409 +
  1.6410 +                return rangeInfos;
  1.6411 +            }
  1.6412 +        ),
  1.6413 +
  1.6414 +        restoreCharacterRanges: createEntryPointFunction(
  1.6415 +            function(session, containerNode, saved) {
  1.6416 +                this.removeAllRanges();
  1.6417 +                for (var i = 0, len = saved.length, range, rangeInfo, characterRange; i < len; ++i) {
  1.6418 +                    rangeInfo = saved[i];
  1.6419 +                    characterRange = rangeInfo.characterRange;
  1.6420 +                    range = api.createRange(containerNode);
  1.6421 +                    range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions);
  1.6422 +                    this.addRange(range, rangeInfo.backward);
  1.6423 +                }
  1.6424 +            }
  1.6425 +        ),
  1.6426 +
  1.6427 +        text: createEntryPointFunction(
  1.6428 +            function(session, characterOptions) {
  1.6429 +                var rangeTexts = [];
  1.6430 +                for (var i = 0, len = this.rangeCount; i < len; ++i) {
  1.6431 +                    rangeTexts[i] = this.getRangeAt(i).text(characterOptions);
  1.6432 +                }
  1.6433 +                return rangeTexts.join("");
  1.6434 +            }
  1.6435 +        )
  1.6436 +    });
  1.6437 +
  1.6438 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.6439 +
  1.6440 +    // Extensions to the core rangy object
  1.6441 +
  1.6442 +    api.innerText = function(el, characterOptions) {
  1.6443 +        var range = api.createRange(el);
  1.6444 +        range.selectNodeContents(el);
  1.6445 +        var text = range.text(characterOptions);
  1.6446 +        return text;
  1.6447 +    };
  1.6448 +
  1.6449 +    api.createWordIterator = function(startNode, startOffset, iteratorOptions) {
  1.6450 +        var session = getSession();
  1.6451 +        iteratorOptions = createNestedOptions(iteratorOptions, defaultWordIteratorOptions);
  1.6452 +        var startPos = session.getPosition(startNode, startOffset);
  1.6453 +        var tokenizedTextProvider = createTokenizedTextProvider(startPos, iteratorOptions.characterOptions, iteratorOptions.wordOptions);
  1.6454 +        var backward = isDirectionBackward(iteratorOptions.direction);
  1.6455 +
  1.6456 +        return {
  1.6457 +            next: function() {
  1.6458 +                return backward ? tokenizedTextProvider.previousStartToken() : tokenizedTextProvider.nextEndToken();
  1.6459 +            },
  1.6460 +
  1.6461 +            dispose: function() {
  1.6462 +                tokenizedTextProvider.dispose();
  1.6463 +                this.next = function() {};
  1.6464 +            }
  1.6465 +        };
  1.6466 +    };
  1.6467 +
  1.6468 +    /*----------------------------------------------------------------------------------------------------------------*/
  1.6469 +
  1.6470 +    api.noMutation = function(func) {
  1.6471 +        var session = getSession();
  1.6472 +        func(session);
  1.6473 +        endSession();
  1.6474 +    };
  1.6475 +
  1.6476 +    api.noMutation.createEntryPointFunction = createEntryPointFunction;
  1.6477 +
  1.6478 +    api.textRange = {
  1.6479 +        isBlockNode: isBlockNode,
  1.6480 +        isCollapsedWhitespaceNode: isCollapsedWhitespaceNode,
  1.6481 +
  1.6482 +        createPosition: createEntryPointFunction(
  1.6483 +            function(session, node, offset) {
  1.6484 +                return session.getPosition(node, offset);
  1.6485 +            }
  1.6486 +        )
  1.6487 +    };
  1.6488 +});
  1.6489 +
  1.6490 +/**
  1.6491 + * Detect browser support for specific features
  1.6492 + */
  1.6493 +wysihtml.browser = (function() {
  1.6494 +  var userAgent   = navigator.userAgent,
  1.6495 +      testElement = document.createElement("div"),
  1.6496 +      // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
  1.6497 +      // We need to be extra careful about Microsoft as it shows increasing tendency of tainting its userAgent strings with false feathers
  1.6498 +      isGecko     = userAgent.indexOf("Gecko")        !== -1 && userAgent.indexOf("KHTML") === -1 && !isIE(),
  1.6499 +      isWebKit    = userAgent.indexOf("AppleWebKit/") !== -1 && !isIE(),
  1.6500 +      isChrome    = userAgent.indexOf("Chrome/")      !== -1 && !isIE(),
  1.6501 +      isOpera     = userAgent.indexOf("Opera/")       !== -1 && !isIE();
  1.6502 +
  1.6503 +  function iosVersion(userAgent) {
  1.6504 +    return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1];
  1.6505 +  }
  1.6506 +
  1.6507 +  function androidVersion(userAgent) {
  1.6508 +    return +(userAgent.match(/android (\d+)/) || [undefined, 0])[1];
  1.6509 +  }
  1.6510 +
  1.6511 +  function isIE(version, equation) {
  1.6512 +    var rv = -1,
  1.6513 +        re;
  1.6514 +
  1.6515 +    if (navigator.appName == 'Microsoft Internet Explorer') {
  1.6516 +      re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
  1.6517 +    } else if (navigator.appName == 'Netscape') {
  1.6518 +      if (navigator.userAgent.indexOf("Trident") > -1) {
  1.6519 +        re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})");
  1.6520 +      } else if ((/Edge\/(\d+)./i).test(navigator.userAgent)) {
  1.6521 +        re = /Edge\/(\d+)./i;
  1.6522 +      }
  1.6523 +    }
  1.6524 +
  1.6525 +    if (re && re.exec(navigator.userAgent) != null) {
  1.6526 +      rv = parseFloat(RegExp.$1);
  1.6527 +    }
  1.6528 +
  1.6529 +    if (rv === -1) { return false; }
  1.6530 +    if (!version) { return true; }
  1.6531 +    if (!equation) { return version === rv; }
  1.6532 +    if (equation === "<") { return version < rv; }
  1.6533 +    if (equation === ">") { return version > rv; }
  1.6534 +    if (equation === "<=") { return version <= rv; }
  1.6535 +    if (equation === ">=") { return version >= rv; }
  1.6536 +  }
  1.6537 +
  1.6538 +  return {
  1.6539 +    // Static variable needed, publicly accessible, to be able override it in unit tests
  1.6540 +    USER_AGENT: userAgent,
  1.6541 +
  1.6542 +    /**
  1.6543 +     * Exclude browsers that are not capable of displaying and handling
  1.6544 +     * contentEditable as desired:
  1.6545 +     *    - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable
  1.6546 +     *    - IE < 8 create invalid markup and crash randomly from time to time
  1.6547 +     *
  1.6548 +     * @return {Boolean}
  1.6549 +     */
  1.6550 +    supported: function() {
  1.6551 +      var userAgent                   = this.USER_AGENT.toLowerCase(),
  1.6552 +          // Essential for making html elements editable
  1.6553 +          hasContentEditableSupport   = "contentEditable" in testElement,
  1.6554 +          // Following methods are needed in order to interact with the contentEditable area
  1.6555 +          hasEditingApiSupport        = document.execCommand && document.queryCommandSupported && document.queryCommandState,
  1.6556 +          // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+
  1.6557 +          hasQuerySelectorSupport     = document.querySelector && document.querySelectorAll,
  1.6558 +          // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05)
  1.6559 +          isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
  1.6560 +      return hasContentEditableSupport
  1.6561 +        && hasEditingApiSupport
  1.6562 +        && hasQuerySelectorSupport
  1.6563 +        && !isIncompatibleMobileBrowser;
  1.6564 +    },
  1.6565 +
  1.6566 +    isTouchDevice: function() {
  1.6567 +      return this.supportsEvent("touchmove");
  1.6568 +    },
  1.6569 +
  1.6570 +    isIos: function() {
  1.6571 +      return (/ipad|iphone|ipod/i).test(this.USER_AGENT);
  1.6572 +    },
  1.6573 +
  1.6574 +    isAndroid: function() {
  1.6575 +      return this.USER_AGENT.indexOf("Android") !== -1;
  1.6576 +    },
  1.6577 +
  1.6578 +    /**
  1.6579 +     * Whether the browser supports sandboxed iframes
  1.6580 +     * Currently only IE 6+ offers such feature <iframe security="restricted">
  1.6581 +     *
  1.6582 +     * http://msdn.microsoft.com/en-us/library/ms534622(v=vs.85).aspx
  1.6583 +     * http://blogs.msdn.com/b/ie/archive/2008/01/18/using-frames-more-securely.aspx
  1.6584 +     *
  1.6585 +     * HTML5 sandboxed iframes are still buggy and their DOM is not reachable from the outside (except when using postMessage)
  1.6586 +     */
  1.6587 +    supportsSandboxedIframes: function() {
  1.6588 +      return isIE();
  1.6589 +    },
  1.6590 +
  1.6591 +    /**
  1.6592 +     * IE6+7 throw a mixed content warning when the src of an iframe
  1.6593 +     * is empty/unset or about:blank
  1.6594 +     * window.querySelector is implemented as of IE8
  1.6595 +     */
  1.6596 +    throwsMixedContentWarningWhenIframeSrcIsEmpty: function() {
  1.6597 +      return !("querySelector" in document);
  1.6598 +    },
  1.6599 +
  1.6600 +    /**
  1.6601 +     * Whether the caret is correctly displayed in contentEditable elements
  1.6602 +     * Firefox sometimes shows a huge caret in the beginning after focusing
  1.6603 +     */
  1.6604 +    displaysCaretInEmptyContentEditableCorrectly: function() {
  1.6605 +      return isIE(12, ">");
  1.6606 +    },
  1.6607 +
  1.6608 +    /**
  1.6609 +     * Opera and IE are the only browsers who offer the css value
  1.6610 +     * in the original unit, thx to the currentStyle object
  1.6611 +     * All other browsers provide the computed style in px via window.getComputedStyle
  1.6612 +     */
  1.6613 +    hasCurrentStyleProperty: function() {
  1.6614 +      return "currentStyle" in testElement;
  1.6615 +    },
  1.6616 +
  1.6617 +    /**
  1.6618 +     * Whether the browser inserts a <br> when pressing enter in a contentEditable element
  1.6619 +     */
  1.6620 +    insertsLineBreaksOnReturn: function() {
  1.6621 +      return isGecko;
  1.6622 +    },
  1.6623 +
  1.6624 +    supportsPlaceholderAttributeOn: function(element) {
  1.6625 +      return "placeholder" in element;
  1.6626 +    },
  1.6627 +
  1.6628 +    supportsEvent: function(eventName) {
  1.6629 +      return "on" + eventName in testElement || (function() {
  1.6630 +        testElement.setAttribute("on" + eventName, "return;");
  1.6631 +        return typeof(testElement["on" + eventName]) === "function";
  1.6632 +      })();
  1.6633 +    },
  1.6634 +
  1.6635 +    /**
  1.6636 +     * Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe
  1.6637 +     */
  1.6638 +    supportsEventsInIframeCorrectly: function() {
  1.6639 +      return !isOpera;
  1.6640 +    },
  1.6641 +
  1.6642 +    /**
  1.6643 +     * Everything below IE9 doesn't know how to treat HTML5 tags
  1.6644 +     *
  1.6645 +     * @param {Object} context The document object on which to check HTML5 support
  1.6646 +     *
  1.6647 +     * @example
  1.6648 +     *    wysihtml.browser.supportsHTML5Tags(document);
  1.6649 +     */
  1.6650 +    supportsHTML5Tags: function(context) {
  1.6651 +      var element = context.createElement("div"),
  1.6652 +          html5   = "<article>foo</article>";
  1.6653 +      element.innerHTML = html5;
  1.6654 +      return element.innerHTML.toLowerCase() === html5;
  1.6655 +    },
  1.6656 +
  1.6657 +    /**
  1.6658 +     * Checks whether a document supports a certain queryCommand
  1.6659 +     * In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree
  1.6660 +     * in oder to report correct results
  1.6661 +     *
  1.6662 +     * @param {Object} doc Document object on which to check for a query command
  1.6663 +     * @param {String} command The query command to check for
  1.6664 +     * @return {Boolean}
  1.6665 +     *
  1.6666 +     * @example
  1.6667 +     *    wysihtml.browser.supportsCommand(document, "bold");
  1.6668 +     */
  1.6669 +    supportsCommand: (function() {
  1.6670 +      // Following commands are supported but contain bugs in some browsers
  1.6671 +      // TODO: investigate if some of these bugs can be tested without altering selection on page, instead of targeting browsers and versions directly
  1.6672 +      var buggyCommands = {
  1.6673 +        // formatBlock fails with some tags (eg. <blockquote>)
  1.6674 +        "formatBlock":          isIE(10, "<="),
  1.6675 +         // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
  1.6676 +         // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
  1.6677 +         // IE and Opera act a bit different here as they convert the entire content of the current block element into a list
  1.6678 +        "insertUnorderedList":  isIE(),
  1.6679 +        "insertOrderedList":    isIE()
  1.6680 +      };
  1.6681 +
  1.6682 +      // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
  1.6683 +      var supported = {
  1.6684 +        "insertHTML": isGecko
  1.6685 +      };
  1.6686 +
  1.6687 +      return function(doc, command) {
  1.6688 +        var isBuggy = buggyCommands[command];
  1.6689 +        if (!isBuggy) {
  1.6690 +          // Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled
  1.6691 +          try {
  1.6692 +            return doc.queryCommandSupported(command);
  1.6693 +          } catch(e1) {}
  1.6694 +
  1.6695 +          try {
  1.6696 +            return doc.queryCommandEnabled(command);
  1.6697 +          } catch(e2) {
  1.6698 +            return !!supported[command];
  1.6699 +          }
  1.6700 +        }
  1.6701 +        return false;
  1.6702 +      };
  1.6703 +    })(),
  1.6704 +
  1.6705 +    /**
  1.6706 +     * IE: URLs starting with:
  1.6707 +     *    www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://,
  1.6708 +     *    nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url:
  1.6709 +     * will automatically be auto-linked when either the user inserts them via copy&paste or presses the
  1.6710 +     * space bar when the caret is directly after such an url.
  1.6711 +     * This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll
  1.6712 +     * (related blog post on msdn
  1.6713 +     * http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx).
  1.6714 +     */
  1.6715 +    doesAutoLinkingInContentEditable: function() {
  1.6716 +      return isIE();
  1.6717 +    },
  1.6718 +
  1.6719 +    /**
  1.6720 +     * As stated above, IE auto links urls typed into contentEditable elements
  1.6721 +     * Since IE9 it's possible to prevent this behavior
  1.6722 +     */
  1.6723 +    canDisableAutoLinking: function() {
  1.6724 +      return this.supportsCommand(document, "AutoUrlDetect");
  1.6725 +    },
  1.6726 +
  1.6727 +    /**
  1.6728 +     * IE leaves an empty paragraph in the contentEditable element after clearing it
  1.6729 +     * Chrome/Safari sometimes an empty <div>
  1.6730 +     */
  1.6731 +    clearsContentEditableCorrectly: function() {
  1.6732 +      return isGecko || isOpera || isWebKit;
  1.6733 +    },
  1.6734 +
  1.6735 +    /**
  1.6736 +     * IE gives wrong results for getAttribute
  1.6737 +     */
  1.6738 +    supportsGetAttributeCorrectly: function() {
  1.6739 +      var td = document.createElement("td");
  1.6740 +      return td.getAttribute("rowspan") != "1";
  1.6741 +    },
  1.6742 +
  1.6743 +    /**
  1.6744 +     * When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them.
  1.6745 +     * Chrome and Safari both don't support this
  1.6746 +     */
  1.6747 +    canSelectImagesInContentEditable: function() {
  1.6748 +      return isGecko || isIE() || isOpera;
  1.6749 +    },
  1.6750 +
  1.6751 +    /**
  1.6752 +     * All browsers except Safari and Chrome automatically scroll the range/caret position into view
  1.6753 +     */
  1.6754 +    autoScrollsToCaret: function() {
  1.6755 +      return !isWebKit;
  1.6756 +    },
  1.6757 +
  1.6758 +    /**
  1.6759 +     * Check whether the browser automatically closes tags that don't need to be opened
  1.6760 +     */
  1.6761 +    autoClosesUnclosedTags: function() {
  1.6762 +      var clonedTestElement = testElement.cloneNode(false),
  1.6763 +          returnValue,
  1.6764 +          innerHTML;
  1.6765 +
  1.6766 +      clonedTestElement.innerHTML = "<p><div></div>";
  1.6767 +      innerHTML                   = clonedTestElement.innerHTML.toLowerCase();
  1.6768 +      returnValue                 = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>";
  1.6769 +
  1.6770 +      // Cache result by overwriting current function
  1.6771 +      this.autoClosesUnclosedTags = function() { return returnValue; };
  1.6772 +
  1.6773 +      return returnValue;
  1.6774 +    },
  1.6775 +
  1.6776 +    /**
  1.6777 +     * Whether the browser supports the native document.getElementsByClassName which returns live NodeLists
  1.6778 +     */
  1.6779 +    supportsNativeGetElementsByClassName: function() {
  1.6780 +      return String(document.getElementsByClassName).indexOf("[native code]") !== -1;
  1.6781 +    },
  1.6782 +
  1.6783 +    /**
  1.6784 +     * As of now (19.04.2011) only supported by Firefox 4 and Chrome
  1.6785 +     * See https://developer.mozilla.org/en/DOM/Selection/modify
  1.6786 +     */
  1.6787 +    supportsSelectionModify: function() {
  1.6788 +      return "getSelection" in window && "modify" in window.getSelection();
  1.6789 +    },
  1.6790 +
  1.6791 +    /**
  1.6792 +     * Opera needs a white space after a <br> in order to position the caret correctly
  1.6793 +     */
  1.6794 +    needsSpaceAfterLineBreak: function() {
  1.6795 +      return isOpera;
  1.6796 +    },
  1.6797 +
  1.6798 +    /**
  1.6799 +     * Whether the browser supports the speech api on the given element
  1.6800 +     * See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
  1.6801 +     *
  1.6802 +     * @example
  1.6803 +     *    var input = document.createElement("input");
  1.6804 +     *    if (wysihtml.browser.supportsSpeechApiOn(input)) {
  1.6805 +     *      // ...
  1.6806 +     *    }
  1.6807 +     */
  1.6808 +    supportsSpeechApiOn: function(input) {
  1.6809 +      var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [undefined, 0];
  1.6810 +      return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input);
  1.6811 +    },
  1.6812 +
  1.6813 +    /**
  1.6814 +     * IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest
  1.6815 +     * See https://connect.microsoft.com/ie/feedback/details/650112
  1.6816 +     * or try the POC http://tifftiff.de/ie9_crash/
  1.6817 +     */
  1.6818 +    crashesWhenDefineProperty: function(property) {
  1.6819 +      return isIE(9) && (property === "XMLHttpRequest" || property === "XDomainRequest");
  1.6820 +    },
  1.6821 +
  1.6822 +    /**
  1.6823 +     * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element
  1.6824 +     */
  1.6825 +    doesAsyncFocus: function() {
  1.6826 +      return isIE(12, ">");
  1.6827 +    },
  1.6828 +
  1.6829 +    /**
  1.6830 +     * 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
  1.6831 +     */
  1.6832 +    hasProblemsSettingCaretAfterImg: function() {
  1.6833 +      return isIE();
  1.6834 +    },
  1.6835 +
  1.6836 +    /* In IE when deleting with caret at the begining of LI, List get broken into half instead of merging the LI with previous */
  1.6837 +    hasLiDeletingProblem: function() {
  1.6838 +      return isIE();
  1.6839 +    },
  1.6840 +
  1.6841 +    hasUndoInContextMenu: function() {
  1.6842 +      return isGecko || isChrome || isOpera;
  1.6843 +    },
  1.6844 +
  1.6845 +    /**
  1.6846 +     * Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode)
  1.6847 +     * is used (regardless if rangy or native)
  1.6848 +     * This especially happens when the caret is positioned right after a <br> because then
  1.6849 +     * insertNode() will insert the node right before the <br>
  1.6850 +     */
  1.6851 +    hasInsertNodeIssue: function() {
  1.6852 +      return isOpera;
  1.6853 +    },
  1.6854 +
  1.6855 +    /**
  1.6856 +     * IE 8+9 don't fire the focus event of the <body> when the iframe gets focused (even though the caret gets set into the <body>)
  1.6857 +     */
  1.6858 +    hasIframeFocusIssue: function() {
  1.6859 +      return isIE();
  1.6860 +    },
  1.6861 +
  1.6862 +    /**
  1.6863 +     * Chrome + Safari create invalid nested markup after paste
  1.6864 +     *
  1.6865 +     *  <p>
  1.6866 +     *    foo
  1.6867 +     *    <p>bar</p> <!-- BOO! -->
  1.6868 +     *  </p>
  1.6869 +     */
  1.6870 +    createsNestedInvalidMarkupAfterPaste: function() {
  1.6871 +      return isWebKit;
  1.6872 +    },
  1.6873 +
  1.6874 +    // In all webkit browsers there are some places where caret can not be placed at the end of blocks and directly before block level element
  1.6875 +    //   when startContainer is element.
  1.6876 +    hasCaretBlockElementIssue: function() {
  1.6877 +      return isWebKit;
  1.6878 +    },
  1.6879 +
  1.6880 +    supportsMutationEvents: function() {
  1.6881 +      return ("MutationEvent" in window);
  1.6882 +    },
  1.6883 +
  1.6884 +    /**
  1.6885 +      IE (at least up to 11) does not support clipboardData on event.
  1.6886 +      It is on window but cannot return text/html
  1.6887 +      Should actually check for clipboardData on paste event, but cannot in firefox
  1.6888 +    */
  1.6889 +    supportsModernPaste: function () {
  1.6890 +      return !isIE();
  1.6891 +    },
  1.6892 +
  1.6893 +    // Unifies the property names of element.style by returning the suitable property name for current browser
  1.6894 +    // Input property key must be the standard
  1.6895 +    fixStyleKey: function(key) {
  1.6896 +      if (key === "cssFloat") {
  1.6897 +        return ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat";
  1.6898 +      }
  1.6899 +      return key;
  1.6900 +    },
  1.6901 +
  1.6902 +    usesControlRanges: function() {
  1.6903 +      return document.body && "createControlRange" in document.body;
  1.6904 +    },
  1.6905 +
  1.6906 +    // Webkit browsers have an issue that when caret is at the end of link it is moved outside of link while inserting new characters,
  1.6907 +    // so all inserted content will be after link. Selection before inserion is reported to be in link though.
  1.6908 +    // This makes changing link texts from problematic to impossible (if link is just 1 characer long) for the user.
  1.6909 +    // TODO: needs to be tested better than just browser as it some day might get fixed
  1.6910 +    hasCaretAtLinkEndInsertionProblems: function() {
  1.6911 +      return isWebKit;
  1.6912 +    }
  1.6913 +  };
  1.6914 +})();
  1.6915 +
  1.6916 +wysihtml.lang.array = function(arr) {
  1.6917 +  return {
  1.6918 +    /**
  1.6919 +     * Check whether a given object exists in an array
  1.6920 +     *
  1.6921 +     * @example
  1.6922 +     *    wysihtml.lang.array([1, 2]).contains(1);
  1.6923 +     *    // => true
  1.6924 +     *
  1.6925 +     * Can be used to match array with array. If intersection is found true is returned
  1.6926 +     */
  1.6927 +    contains: function(needle) {
  1.6928 +      if (Array.isArray(needle)) {
  1.6929 +        for (var i = needle.length; i--;) {
  1.6930 +          if (wysihtml.lang.array(arr).indexOf(needle[i]) !== -1) {
  1.6931 +            return true;
  1.6932 +          }
  1.6933 +        }
  1.6934 +        return false;
  1.6935 +      } else {
  1.6936 +        return wysihtml.lang.array(arr).indexOf(needle) !== -1;
  1.6937 +      }
  1.6938 +    },
  1.6939 +
  1.6940 +    /**
  1.6941 +     * Check whether a given object exists in an array and return index
  1.6942 +     * If no elelemt found returns -1
  1.6943 +     *
  1.6944 +     * @example
  1.6945 +     *    wysihtml.lang.array([1, 2]).indexOf(2);
  1.6946 +     *    // => 1
  1.6947 +     */
  1.6948 +    indexOf: function(needle) {
  1.6949 +        if (arr.indexOf) {
  1.6950 +          return arr.indexOf(needle);
  1.6951 +        } else {
  1.6952 +          for (var i=0, length=arr.length; i<length; i++) {
  1.6953 +            if (arr[i] === needle) { return i; }
  1.6954 +          }
  1.6955 +          return -1;
  1.6956 +        }
  1.6957 +    },
  1.6958 +
  1.6959 +    /**
  1.6960 +     * Substract one array from another
  1.6961 +     *
  1.6962 +     * @example
  1.6963 +     *    wysihtml.lang.array([1, 2, 3, 4]).without([3, 4]);
  1.6964 +     *    // => [1, 2]
  1.6965 +     */
  1.6966 +    without: function(arrayToSubstract) {
  1.6967 +      arrayToSubstract = wysihtml.lang.array(arrayToSubstract);
  1.6968 +      var newArr  = [],
  1.6969 +          i       = 0,
  1.6970 +          length  = arr.length;
  1.6971 +      for (; i<length; i++) {
  1.6972 +        if (!arrayToSubstract.contains(arr[i])) {
  1.6973 +          newArr.push(arr[i]);
  1.6974 +        }
  1.6975 +      }
  1.6976 +      return newArr;
  1.6977 +    },
  1.6978 +
  1.6979 +    /**
  1.6980 +     * Return a clean native array
  1.6981 +     *
  1.6982 +     * Following will convert a Live NodeList to a proper Array
  1.6983 +     * @example
  1.6984 +     *    var childNodes = wysihtml.lang.array(document.body.childNodes).get();
  1.6985 +     */
  1.6986 +    get: function() {
  1.6987 +      var i        = 0,
  1.6988 +          length   = arr.length,
  1.6989 +          newArray = [];
  1.6990 +      for (; i<length; i++) {
  1.6991 +        newArray.push(arr[i]);
  1.6992 +      }
  1.6993 +      return newArray;
  1.6994 +    },
  1.6995 +
  1.6996 +    /**
  1.6997 +     * Creates a new array with the results of calling a provided function on every element in this array.
  1.6998 +     * optionally this can be provided as second argument
  1.6999 +     *
  1.7000 +     * @example
  1.7001 +     *    var childNodes = wysihtml.lang.array([1,2,3,4]).map(function (value, index, array) {
  1.7002 +            return value * 2;
  1.7003 +     *    });
  1.7004 +     *    // => [2,4,6,8]
  1.7005 +     */
  1.7006 +    map: function(callback, thisArg) {
  1.7007 +      if (Array.prototype.map) {
  1.7008 +        return arr.map(callback, thisArg);
  1.7009 +      } else {
  1.7010 +        var len = arr.length >>> 0,
  1.7011 +            A = new Array(len),
  1.7012 +            i = 0;
  1.7013 +        for (; i < len; i++) {
  1.7014 +           A[i] = callback.call(thisArg, arr[i], i, arr);
  1.7015 +        }
  1.7016 +        return A;
  1.7017 +      }
  1.7018 +    },
  1.7019 +
  1.7020 +    /* ReturnS new array without duplicate entries
  1.7021 +     *
  1.7022 +     * @example
  1.7023 +     *    var uniq = wysihtml.lang.array([1,2,3,2,1,4]).unique();
  1.7024 +     *    // => [1,2,3,4]
  1.7025 +     */
  1.7026 +    unique: function() {
  1.7027 +      var vals = [],
  1.7028 +          max = arr.length,
  1.7029 +          idx = 0;
  1.7030 +
  1.7031 +      while (idx < max) {
  1.7032 +        if (!wysihtml.lang.array(vals).contains(arr[idx])) {
  1.7033 +          vals.push(arr[idx]);
  1.7034 +        }
  1.7035 +        idx++;
  1.7036 +      }
  1.7037 +      return vals;
  1.7038 +    }
  1.7039 +
  1.7040 +  };
  1.7041 +};
  1.7042 +
  1.7043 +wysihtml.lang.Dispatcher = Base.extend(
  1.7044 +  /** @scope wysihtml.lang.Dialog.prototype */ {
  1.7045 +  on: function(eventName, handler) {
  1.7046 +    this.events = this.events || {};
  1.7047 +    this.events[eventName] = this.events[eventName] || [];
  1.7048 +    this.events[eventName].push(handler);
  1.7049 +    return this;
  1.7050 +  },
  1.7051 +
  1.7052 +  off: function(eventName, handler) {
  1.7053 +    this.events = this.events || {};
  1.7054 +    var i = 0,
  1.7055 +        handlers,
  1.7056 +        newHandlers;
  1.7057 +    if (eventName) {
  1.7058 +      handlers    = this.events[eventName] || [],
  1.7059 +      newHandlers = [];
  1.7060 +      for (; i<handlers.length; i++) {
  1.7061 +        if (handlers[i] !== handler && handler) {
  1.7062 +          newHandlers.push(handlers[i]);
  1.7063 +        }
  1.7064 +      }
  1.7065 +      this.events[eventName] = newHandlers;
  1.7066 +    } else {
  1.7067 +      // Clean up all events
  1.7068 +      this.events = {};
  1.7069 +    }
  1.7070 +    return this;
  1.7071 +  },
  1.7072 +
  1.7073 +  fire: function(eventName, payload) {
  1.7074 +    this.events = this.events || {};
  1.7075 +    var handlers = this.events[eventName] || [],
  1.7076 +        i        = 0;
  1.7077 +    for (; i<handlers.length; i++) {
  1.7078 +      handlers[i].call(this, payload);
  1.7079 +    }
  1.7080 +    return this;
  1.7081 +  },
  1.7082 +
  1.7083 +  // deprecated, use .on()
  1.7084 +  observe: function() {
  1.7085 +    return this.on.apply(this, arguments);
  1.7086 +  },
  1.7087 +
  1.7088 +  // deprecated, use .off()
  1.7089 +  stopObserving: function() {
  1.7090 +    return this.off.apply(this, arguments);
  1.7091 +  }
  1.7092 +});
  1.7093 +
  1.7094 +wysihtml.lang.object = function(obj) {
  1.7095 +  return {
  1.7096 +    /**
  1.7097 +     * @example
  1.7098 +     *    wysihtml.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get();
  1.7099 +     *    // => { foo: 1, bar: 2, baz: 3 }
  1.7100 +     */
  1.7101 +    merge: function(otherObj, deep) {
  1.7102 +      for (var i in otherObj) {
  1.7103 +        if (deep && wysihtml.lang.object(otherObj[i]).isPlainObject() && (typeof obj[i] === "undefined" || wysihtml.lang.object(obj[i]).isPlainObject())) {
  1.7104 +          if (typeof obj[i] === "undefined") {
  1.7105 +            obj[i] = wysihtml.lang.object(otherObj[i]).clone(true);
  1.7106 +          } else {
  1.7107 +            wysihtml.lang.object(obj[i]).merge(wysihtml.lang.object(otherObj[i]).clone(true));
  1.7108 +          }
  1.7109 +        } else {
  1.7110 +          obj[i] = wysihtml.lang.object(otherObj[i]).isPlainObject() ? wysihtml.lang.object(otherObj[i]).clone(true) : otherObj[i];
  1.7111 +        }
  1.7112 +      }
  1.7113 +      return this;
  1.7114 +    },
  1.7115 +
  1.7116 +    difference: function (otherObj) {
  1.7117 +      var diffObj = {};
  1.7118 +
  1.7119 +      // Get old values not in comparing object
  1.7120 +      for (var i in obj) {
  1.7121 +        if (obj.hasOwnProperty(i)) {
  1.7122 +          if (!otherObj.hasOwnProperty(i)) {
  1.7123 +            diffObj[i] = obj[i];
  1.7124 +          }
  1.7125 +        }
  1.7126 +      }
  1.7127 +
  1.7128 +      // Get new and different values in comparing object
  1.7129 +      for (var o in otherObj) {
  1.7130 +        if (otherObj.hasOwnProperty(o)) {
  1.7131 +          if (!obj.hasOwnProperty(o) || obj[o] !== otherObj[o]) {
  1.7132 +            diffObj[0] = obj[0];
  1.7133 +          }
  1.7134 +        }
  1.7135 +      }
  1.7136 +      return diffObj;
  1.7137 +    },
  1.7138 +
  1.7139 +    get: function() {
  1.7140 +      return obj;
  1.7141 +    },
  1.7142 +
  1.7143 +    /**
  1.7144 +     * @example
  1.7145 +     *    wysihtml.lang.object({ foo: 1 }).clone();
  1.7146 +     *    // => { foo: 1 }
  1.7147 +     *
  1.7148 +     *    v0.4.14 adds options for deep clone : wysihtml.lang.object({ foo: 1 }).clone(true);
  1.7149 +     */
  1.7150 +    clone: function(deep) {
  1.7151 +      var newObj = {},
  1.7152 +          i;
  1.7153 +
  1.7154 +      if (obj === null || !wysihtml.lang.object(obj).isPlainObject()) {
  1.7155 +        return obj;
  1.7156 +      }
  1.7157 +
  1.7158 +      for (i in obj) {
  1.7159 +        if(obj.hasOwnProperty(i)) {
  1.7160 +          if (deep) {
  1.7161 +            newObj[i] = wysihtml.lang.object(obj[i]).clone(deep);
  1.7162 +          } else {
  1.7163 +            newObj[i] = obj[i];
  1.7164 +          }
  1.7165 +        }
  1.7166 +      }
  1.7167 +      return newObj;
  1.7168 +    },
  1.7169 +
  1.7170 +    /**
  1.7171 +     * @example
  1.7172 +     *    wysihtml.lang.object([]).isArray();
  1.7173 +     *    // => true
  1.7174 +     */
  1.7175 +    isArray: function() {
  1.7176 +      return Object.prototype.toString.call(obj) === "[object Array]";
  1.7177 +    },
  1.7178 +
  1.7179 +    /**
  1.7180 +     * @example
  1.7181 +     *    wysihtml.lang.object(function() {}).isFunction();
  1.7182 +     *    // => true
  1.7183 +     */
  1.7184 +    isFunction: function() {
  1.7185 +      return Object.prototype.toString.call(obj) === '[object Function]';
  1.7186 +    },
  1.7187 +
  1.7188 +    isPlainObject: function () {
  1.7189 +      return obj && Object.prototype.toString.call(obj) === '[object Object]' && !(("Node" in window) ? obj instanceof Node : obj instanceof Element || obj instanceof Text);
  1.7190 +    },
  1.7191 +
  1.7192 +    /**
  1.7193 +     * @example
  1.7194 +     *    wysihtml.lang.object({}).isEmpty();
  1.7195 +     *    // => true
  1.7196 +     */
  1.7197 +    isEmpty: function() {
  1.7198 +      for (var i in obj) {
  1.7199 +        if (obj.hasOwnProperty(i)) {
  1.7200 +          return false;
  1.7201 +        }
  1.7202 +      }
  1.7203 +      return true;
  1.7204 +    }
  1.7205 +  };
  1.7206 +};
  1.7207 +
  1.7208 +(function() {
  1.7209 +  var WHITE_SPACE_START = /^\s+/,
  1.7210 +      WHITE_SPACE_END   = /\s+$/,
  1.7211 +      ENTITY_REG_EXP    = /[&<>\t"]/g,
  1.7212 +      ENTITY_MAP = {
  1.7213 +        '&': '&amp;',
  1.7214 +        '<': '&lt;',
  1.7215 +        '>': '&gt;',
  1.7216 +        '"': "&quot;",
  1.7217 +        '\t':"&nbsp; "
  1.7218 +      };
  1.7219 +  wysihtml.lang.string = function(str) {
  1.7220 +    str = String(str);
  1.7221 +    return {
  1.7222 +      /**
  1.7223 +       * @example
  1.7224 +       *    wysihtml.lang.string("   foo   ").trim();
  1.7225 +       *    // => "foo"
  1.7226 +       */
  1.7227 +      trim: function() {
  1.7228 +        return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, "");
  1.7229 +      },
  1.7230 +
  1.7231 +      /**
  1.7232 +       * @example
  1.7233 +       *    wysihtml.lang.string("Hello #{name}").interpolate({ name: "Christopher" });
  1.7234 +       *    // => "Hello Christopher"
  1.7235 +       */
  1.7236 +      interpolate: function(vars) {
  1.7237 +        for (var i in vars) {
  1.7238 +          str = this.replace("#{" + i + "}").by(vars[i]);
  1.7239 +        }
  1.7240 +        return str;
  1.7241 +      },
  1.7242 +
  1.7243 +      /**
  1.7244 +       * @example
  1.7245 +       *    wysihtml.lang.string("Hello Tom").replace("Tom").with("Hans");
  1.7246 +       *    // => "Hello Hans"
  1.7247 +       */
  1.7248 +      replace: function(search) {
  1.7249 +        return {
  1.7250 +          by: function(replace) {
  1.7251 +            return str.split(search).join(replace);
  1.7252 +          }
  1.7253 +        };
  1.7254 +      },
  1.7255 +
  1.7256 +      /**
  1.7257 +       * @example
  1.7258 +       *    wysihtml.lang.string("hello<br>").escapeHTML();
  1.7259 +       *    // => "hello&lt;br&gt;"
  1.7260 +       */
  1.7261 +      escapeHTML: function(linebreaks, convertSpaces) {
  1.7262 +        var html = str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
  1.7263 +        if (linebreaks) {
  1.7264 +          html = html.replace(/(?:\r\n|\r|\n)/g, '<br />');
  1.7265 +        }
  1.7266 +        if (convertSpaces) {
  1.7267 +          html = html.replace(/  /gi, "&nbsp; ");
  1.7268 +        }
  1.7269 +        return html;
  1.7270 +      }
  1.7271 +    };
  1.7272 +  };
  1.7273 +})();
  1.7274 +
  1.7275 +/**
  1.7276 + * Find urls in descendant text nodes of an element and auto-links them
  1.7277 + * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
  1.7278 + *
  1.7279 + * @param {Element} element Container element in which to search for urls
  1.7280 + *
  1.7281 + * @example
  1.7282 + *    <div id="text-container">Please click here: www.google.com</div>
  1.7283 + *    <script>wysihtml.dom.autoLink(document.getElementById("text-container"));</script>
  1.7284 + */
  1.7285 +(function(wysihtml) {
  1.7286 +  var /**
  1.7287 +       * Don't auto-link urls that are contained in the following elements:
  1.7288 +       */
  1.7289 +      IGNORE_URLS_IN        = wysihtml.lang.array(["CODE", "PRE", "A", "SCRIPT", "HEAD", "TITLE", "STYLE"]),
  1.7290 +      /**
  1.7291 +       * revision 1:
  1.7292 +       *    /(\S+\.{1}[^\s\,\.\!]+)/g
  1.7293 +       *
  1.7294 +       * revision 2:
  1.7295 +       *    /(\b(((https?|ftp):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;\[\]]*[-A-Z0-9+&@#\/%=~_|])/gim
  1.7296 +       *
  1.7297 +       * put this in the beginning if you don't wan't to match within a word
  1.7298 +       *    (^|[\>\(\{\[\s\>])
  1.7299 +       */
  1.7300 +      URL_REG_EXP           = /((https?:\/\/|www\.)[^\s<]{3,})/gi,
  1.7301 +      TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i,
  1.7302 +      MAX_DISPLAY_LENGTH    = 100,
  1.7303 +      BRACKETS              = { ")": "(", "]": "[", "}": "{" };
  1.7304 +
  1.7305 +  function autoLink(element, ignoreInClasses) {
  1.7306 +    if (_hasParentThatShouldBeIgnored(element, ignoreInClasses)) {
  1.7307 +      return element;
  1.7308 +    }
  1.7309 +
  1.7310 +    if (element === element.ownerDocument.documentElement) {
  1.7311 +      element = element.ownerDocument.body;
  1.7312 +    }
  1.7313 +
  1.7314 +    return _parseNode(element, ignoreInClasses);
  1.7315 +  }
  1.7316 +
  1.7317 +  /**
  1.7318 +   * This is basically a rebuild of
  1.7319 +   * the rails auto_link_urls text helper
  1.7320 +   */
  1.7321 +  function _convertUrlsToLinks(str) {
  1.7322 +    return str.replace(URL_REG_EXP, function(match, url) {
  1.7323 +      var punctuation = (url.match(TRAILING_CHAR_REG_EXP) || [])[1] || "",
  1.7324 +          opening     = BRACKETS[punctuation];
  1.7325 +      url = url.replace(TRAILING_CHAR_REG_EXP, "");
  1.7326 +
  1.7327 +      if (url.split(opening).length > url.split(punctuation).length) {
  1.7328 +        url = url + punctuation;
  1.7329 +        punctuation = "";
  1.7330 +      }
  1.7331 +      var realUrl    = url,
  1.7332 +          displayUrl = url;
  1.7333 +      if (url.length > MAX_DISPLAY_LENGTH) {
  1.7334 +        displayUrl = displayUrl.substr(0, MAX_DISPLAY_LENGTH) + "...";
  1.7335 +      }
  1.7336 +      // Add http prefix if necessary
  1.7337 +      if (realUrl.substr(0, 4) === "www.") {
  1.7338 +        realUrl = "http://" + realUrl;
  1.7339 +      }
  1.7340 +
  1.7341 +      return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation;
  1.7342 +    });
  1.7343 +  }
  1.7344 +
  1.7345 +  /**
  1.7346 +   * Creates or (if already cached) returns a temp element
  1.7347 +   * for the given document object
  1.7348 +   */
  1.7349 +  function _getTempElement(context) {
  1.7350 +    var tempElement = context._wysihtml_tempElement;
  1.7351 +    if (!tempElement) {
  1.7352 +      tempElement = context._wysihtml_tempElement = context.createElement("div");
  1.7353 +    }
  1.7354 +    return tempElement;
  1.7355 +  }
  1.7356 +
  1.7357 +  /**
  1.7358 +   * Replaces the original text nodes with the newly auto-linked dom tree
  1.7359 +   */
  1.7360 +  function _wrapMatchesInNode(textNode) {
  1.7361 +    var parentNode  = textNode.parentNode,
  1.7362 +        nodeValue   = wysihtml.lang.string(textNode.data).escapeHTML(),
  1.7363 +        tempElement = _getTempElement(parentNode.ownerDocument);
  1.7364 +
  1.7365 +    // We need to insert an empty/temporary <span /> to fix IE quirks
  1.7366 +    // Elsewise IE would strip white space in the beginning
  1.7367 +    tempElement.innerHTML = "<span></span>" + _convertUrlsToLinks(nodeValue);
  1.7368 +    tempElement.removeChild(tempElement.firstChild);
  1.7369 +
  1.7370 +    while (tempElement.firstChild) {
  1.7371 +      // inserts tempElement.firstChild before textNode
  1.7372 +      parentNode.insertBefore(tempElement.firstChild, textNode);
  1.7373 +    }
  1.7374 +    parentNode.removeChild(textNode);
  1.7375 +  }
  1.7376 +
  1.7377 +  function _hasParentThatShouldBeIgnored(node, ignoreInClasses) {
  1.7378 +    var nodeName;
  1.7379 +    while (node.parentNode) {
  1.7380 +      node = node.parentNode;
  1.7381 +      nodeName = node.nodeName;
  1.7382 +      if (node.className && wysihtml.lang.array(node.className.split(' ')).contains(ignoreInClasses)) {
  1.7383 +        return true;
  1.7384 +      }
  1.7385 +      if (IGNORE_URLS_IN.contains(nodeName)) {
  1.7386 +        return true;
  1.7387 +      } else if (nodeName === "body") {
  1.7388 +        return false;
  1.7389 +      }
  1.7390 +    }
  1.7391 +    return false;
  1.7392 +  }
  1.7393 +
  1.7394 +  function _parseNode(element, ignoreInClasses) {
  1.7395 +    if (IGNORE_URLS_IN.contains(element.nodeName)) {
  1.7396 +      return;
  1.7397 +    }
  1.7398 +
  1.7399 +    if (element.className && wysihtml.lang.array(element.className.split(' ')).contains(ignoreInClasses)) {
  1.7400 +      return;
  1.7401 +    }
  1.7402 +
  1.7403 +    if (element.nodeType === wysihtml.TEXT_NODE && element.data.match(URL_REG_EXP)) {
  1.7404 +      _wrapMatchesInNode(element);
  1.7405 +      return;
  1.7406 +    }
  1.7407 +
  1.7408 +    var childNodes        = wysihtml.lang.array(element.childNodes).get(),
  1.7409 +        childNodesLength  = childNodes.length,
  1.7410 +        i                 = 0;
  1.7411 +
  1.7412 +    for (; i<childNodesLength; i++) {
  1.7413 +      _parseNode(childNodes[i], ignoreInClasses);
  1.7414 +    }
  1.7415 +
  1.7416 +    return element;
  1.7417 +  }
  1.7418 +
  1.7419 +  wysihtml.dom.autoLink = autoLink;
  1.7420 +
  1.7421 +  // Reveal url reg exp to the outside
  1.7422 +  wysihtml.dom.autoLink.URL_REG_EXP = URL_REG_EXP;
  1.7423 +})(wysihtml);
  1.7424 +
  1.7425 +(function(wysihtml) {
  1.7426 +  var api = wysihtml.dom;
  1.7427 +
  1.7428 +  api.addClass = function(element, className) {
  1.7429 +    var classList = element.classList;
  1.7430 +    if (classList) {
  1.7431 +      return classList.add(className);
  1.7432 +    }
  1.7433 +    if (api.hasClass(element, className)) {
  1.7434 +      return;
  1.7435 +    }
  1.7436 +    element.className += " " + className;
  1.7437 +  };
  1.7438 +
  1.7439 +  api.removeClass = function(element, className) {
  1.7440 +    var classList = element.classList;
  1.7441 +    if (classList) {
  1.7442 +      return classList.remove(className);
  1.7443 +    }
  1.7444 +
  1.7445 +    element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ");
  1.7446 +  };
  1.7447 +
  1.7448 +  api.hasClass = function(element, className) {
  1.7449 +    var classList = element.classList;
  1.7450 +    if (classList) {
  1.7451 +      return classList.contains(className);
  1.7452 +    }
  1.7453 +
  1.7454 +    var elementClassName = element.className;
  1.7455 +    return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
  1.7456 +  };
  1.7457 +})(wysihtml);
  1.7458 +
  1.7459 +wysihtml.dom.compareDocumentPosition = (function() {
  1.7460 +  var documentElement = document.documentElement;
  1.7461 +  if (documentElement.compareDocumentPosition) {
  1.7462 +    return function(container, element) {
  1.7463 +      return container.compareDocumentPosition(element);
  1.7464 +    };
  1.7465 +  } else {
  1.7466 +    return function( container, element ) {
  1.7467 +      // implementation borrowed from https://github.com/tmpvar/jsdom/blob/681a8524b663281a0f58348c6129c8c184efc62c/lib/jsdom/level3/core.js // MIT license
  1.7468 +      var thisOwner, otherOwner;
  1.7469 +
  1.7470 +      if( container.nodeType === 9) // Node.DOCUMENT_NODE
  1.7471 +        thisOwner = container;
  1.7472 +      else
  1.7473 +        thisOwner = container.ownerDocument;
  1.7474 +
  1.7475 +      if( element.nodeType === 9) // Node.DOCUMENT_NODE
  1.7476 +        otherOwner = element;
  1.7477 +      else
  1.7478 +        otherOwner = element.ownerDocument;
  1.7479 +
  1.7480 +      if( container === element ) return 0;
  1.7481 +      if( container === element.ownerDocument ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
  1.7482 +      if( container.ownerDocument === element ) return 2 + 8;  //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
  1.7483 +      if( thisOwner !== otherOwner ) return 1; // Node.DOCUMENT_POSITION_DISCONNECTED;
  1.7484 +
  1.7485 +      // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child.
  1.7486 +      if( container.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && container.childNodes && wysihtml.lang.array(container.childNodes).indexOf( element ) !== -1)
  1.7487 +        return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
  1.7488 +
  1.7489 +      if( element.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && element.childNodes && wysihtml.lang.array(element.childNodes).indexOf( container ) !== -1)
  1.7490 +        return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
  1.7491 +
  1.7492 +      var point = container;
  1.7493 +      var parents = [ ];
  1.7494 +      var previous = null;
  1.7495 +      while( point ) {
  1.7496 +        if( point == element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
  1.7497 +        parents.push( point );
  1.7498 +        point = point.parentNode;
  1.7499 +      }
  1.7500 +      point = element;
  1.7501 +      previous = null;
  1.7502 +      while( point ) {
  1.7503 +        if( point == container ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
  1.7504 +        var location_index = wysihtml.lang.array(parents).indexOf( point );
  1.7505 +        if( location_index !== -1) {
  1.7506 +         var smallest_common_ancestor = parents[ location_index ];
  1.7507 +         var this_index = wysihtml.lang.array(smallest_common_ancestor.childNodes).indexOf( parents[location_index - 1]);//smallest_common_ancestor.childNodes.toArray().indexOf( parents[location_index - 1] );
  1.7508 +         var other_index = wysihtml.lang.array(smallest_common_ancestor.childNodes).indexOf( previous ); //smallest_common_ancestor.childNodes.toArray().indexOf( previous );
  1.7509 +         if( this_index > other_index ) {
  1.7510 +               return 2; //Node.DOCUMENT_POSITION_PRECEDING;
  1.7511 +         }
  1.7512 +         else {
  1.7513 +           return 4; //Node.DOCUMENT_POSITION_FOLLOWING;
  1.7514 +         }
  1.7515 +        }
  1.7516 +        previous = point;
  1.7517 +        point = point.parentNode;
  1.7518 +      }
  1.7519 +      return 1; //Node.DOCUMENT_POSITION_DISCONNECTED;
  1.7520 +    };
  1.7521 +  }
  1.7522 +})();
  1.7523 +
  1.7524 +wysihtml.dom.contains = (function() {
  1.7525 +  var documentElement = document.documentElement;
  1.7526 +  if (documentElement.contains) {
  1.7527 +    return function(container, element) {
  1.7528 +      if (element.nodeType !== wysihtml.ELEMENT_NODE) {
  1.7529 +        if (element.parentNode === container) {
  1.7530 +          return true;
  1.7531 +        }
  1.7532 +        element = element.parentNode;
  1.7533 +      }
  1.7534 +      return container !== element && container.contains(element);
  1.7535 +    };
  1.7536 +  } else if (documentElement.compareDocumentPosition) {
  1.7537 +    return function(container, element) {
  1.7538 +      // https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition
  1.7539 +      return !!(container.compareDocumentPosition(element) & 16);
  1.7540 +    };
  1.7541 +  }
  1.7542 +})();
  1.7543 +
  1.7544 +(function(wysihtml) {
  1.7545 +  var doc = document;
  1.7546 +  wysihtml.dom.ContentEditableArea = Base.extend({
  1.7547 +      getContentEditable: function() {
  1.7548 +        return this.element;
  1.7549 +      },
  1.7550 +
  1.7551 +      getWindow: function() {
  1.7552 +        return this.element.ownerDocument.defaultView || this.element.ownerDocument.parentWindow;
  1.7553 +      },
  1.7554 +
  1.7555 +      getDocument: function() {
  1.7556 +        return this.element.ownerDocument;
  1.7557 +      },
  1.7558 +
  1.7559 +      constructor: function(readyCallback, config, contentEditable) {
  1.7560 +        this.callback = readyCallback || wysihtml.EMPTY_FUNCTION;
  1.7561 +        this.config   = wysihtml.lang.object({}).merge(config).get();
  1.7562 +        if (!this.config.className) {
  1.7563 +          this.config.className = "wysihtml-sandbox";
  1.7564 +        }
  1.7565 +        if (contentEditable) {
  1.7566 +            this.element = this._bindElement(contentEditable);
  1.7567 +        } else {
  1.7568 +            this.element = this._createElement();
  1.7569 +        }
  1.7570 +      },
  1.7571 +
  1.7572 +      destroy: function() {
  1.7573 +
  1.7574 +      },
  1.7575 +
  1.7576 +      // creates a new contenteditable and initiates it
  1.7577 +      _createElement: function() {
  1.7578 +        var element = doc.createElement("div");
  1.7579 +        element.className = this.config.className;
  1.7580 +        this._loadElement(element);
  1.7581 +        return element;
  1.7582 +      },
  1.7583 +
  1.7584 +      // initiates an allready existent contenteditable
  1.7585 +      _bindElement: function(contentEditable) {
  1.7586 +        contentEditable.className = contentEditable.className ? contentEditable.className + " wysihtml-sandbox" : "wysihtml-sandbox";
  1.7587 +        this._loadElement(contentEditable, true);
  1.7588 +        return contentEditable;
  1.7589 +      },
  1.7590 +
  1.7591 +      _loadElement: function(element, contentExists) {
  1.7592 +        var that = this;
  1.7593 +
  1.7594 +        if (!contentExists) {
  1.7595 +            var innerHtml = this._getHtml();
  1.7596 +            element.innerHTML = innerHtml;
  1.7597 +        }
  1.7598 +
  1.7599 +        this.loaded = true;
  1.7600 +        // Trigger the callback
  1.7601 +        setTimeout(function() { that.callback(that); }, 0);
  1.7602 +      },
  1.7603 +
  1.7604 +      _getHtml: function(templateVars) {
  1.7605 +        return '';
  1.7606 +      }
  1.7607 +
  1.7608 +  });
  1.7609 +})(wysihtml);
  1.7610 +
  1.7611 +/**
  1.7612 + * Converts an HTML fragment/element into a unordered/ordered list
  1.7613 + *
  1.7614 + * @param {Element} element The element which should be turned into a list
  1.7615 + * @param {String} listType The list type in which to convert the tree (either "ul" or "ol")
  1.7616 + * @return {Element} The created list
  1.7617 + *
  1.7618 + * @example
  1.7619 + *    <!-- Assume the following dom: -->
  1.7620 + *    <span id="pseudo-list">
  1.7621 + *      eminem<br>
  1.7622 + *      dr. dre
  1.7623 + *      <div>50 Cent</div>
  1.7624 + *    </span>
  1.7625 + *
  1.7626 + *    <script>
  1.7627 + *      wysihtml.dom.convertToList(document.getElementById("pseudo-list"), "ul");
  1.7628 + *    </script>
  1.7629 + *
  1.7630 + *    <!-- Will result in: -->
  1.7631 + *    <ul>
  1.7632 + *      <li>eminem</li>
  1.7633 + *      <li>dr. dre</li>
  1.7634 + *      <li>50 Cent</li>
  1.7635 + *    </ul>
  1.7636 + */
  1.7637 +wysihtml.dom.convertToList = (function() {
  1.7638 +  function _createListItem(doc, list) {
  1.7639 +    var listItem = doc.createElement("li");
  1.7640 +    list.appendChild(listItem);
  1.7641 +    return listItem;
  1.7642 +  }
  1.7643 +
  1.7644 +  function _createList(doc, type) {
  1.7645 +    return doc.createElement(type);
  1.7646 +  }
  1.7647 +
  1.7648 +  function convertToList(element, listType, uneditableClass) {
  1.7649 +    if (element.nodeName === "UL" || element.nodeName === "OL" || element.nodeName === "MENU") {
  1.7650 +      // Already a list
  1.7651 +      return element;
  1.7652 +    }
  1.7653 +
  1.7654 +    var doc               = element.ownerDocument,
  1.7655 +        list              = _createList(doc, listType),
  1.7656 +        lineBreaks        = element.querySelectorAll("br"),
  1.7657 +        lineBreaksLength  = lineBreaks.length,
  1.7658 +        childNodes,
  1.7659 +        childNodesLength,
  1.7660 +        childNode,
  1.7661 +        lineBreak,
  1.7662 +        parentNode,
  1.7663 +        isBlockElement,
  1.7664 +        isLineBreak,
  1.7665 +        currentListItem,
  1.7666 +        i;
  1.7667 +
  1.7668 +    // First find <br> at the end of inline elements and move them behind them
  1.7669 +    for (i=0; i<lineBreaksLength; i++) {
  1.7670 +      lineBreak = lineBreaks[i];
  1.7671 +      while ((parentNode = lineBreak.parentNode) && parentNode !== element && parentNode.lastChild === lineBreak) {
  1.7672 +        if (wysihtml.dom.getStyle("display").from(parentNode) === "block") {
  1.7673 +          parentNode.removeChild(lineBreak);
  1.7674 +          break;
  1.7675 +        }
  1.7676 +        wysihtml.dom.insert(lineBreak).after(lineBreak.parentNode);
  1.7677 +      }
  1.7678 +    }
  1.7679 +
  1.7680 +    childNodes        = wysihtml.lang.array(element.childNodes).get();
  1.7681 +    childNodesLength  = childNodes.length;
  1.7682 +
  1.7683 +    for (i=0; i<childNodesLength; i++) {
  1.7684 +      currentListItem   = currentListItem || _createListItem(doc, list);
  1.7685 +      childNode         = childNodes[i];
  1.7686 +      isBlockElement    = wysihtml.dom.getStyle("display").from(childNode) === "block";
  1.7687 +      isLineBreak       = childNode.nodeName === "BR";
  1.7688 +
  1.7689 +      // consider uneditable as an inline element
  1.7690 +      if (isBlockElement && (!uneditableClass || !wysihtml.dom.hasClass(childNode, uneditableClass))) {
  1.7691 +        // Append blockElement to current <li> if empty, otherwise create a new one
  1.7692 +        currentListItem = currentListItem.firstChild ? _createListItem(doc, list) : currentListItem;
  1.7693 +        currentListItem.appendChild(childNode);
  1.7694 +        currentListItem = null;
  1.7695 +        continue;
  1.7696 +      }
  1.7697 +
  1.7698 +      if (isLineBreak) {
  1.7699 +        // Only create a new list item in the next iteration when the current one has already content
  1.7700 +        currentListItem = currentListItem.firstChild ? null : currentListItem;
  1.7701 +        continue;
  1.7702 +      }
  1.7703 +
  1.7704 +      currentListItem.appendChild(childNode);
  1.7705 +    }
  1.7706 +
  1.7707 +    if (childNodes.length === 0) {
  1.7708 +      _createListItem(doc, list);
  1.7709 +    }
  1.7710 +
  1.7711 +    element.parentNode.replaceChild(list, element);
  1.7712 +    return list;
  1.7713 +  }
  1.7714 +
  1.7715 +  return convertToList;
  1.7716 +})();
  1.7717 +
  1.7718 +/**
  1.7719 + * Copy a set of attributes from one element to another
  1.7720 + *
  1.7721 + * @param {Array} attributesToCopy List of attributes which should be copied
  1.7722 + * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
  1.7723 + *    copy the attributes from., this again returns an object which provides a method named "to" which can be invoked
  1.7724 + *    with the element where to copy the attributes to (see example)
  1.7725 + *
  1.7726 + * @example
  1.7727 + *    var textarea    = document.querySelector("textarea"),
  1.7728 + *        div         = document.querySelector("div[contenteditable=true]"),
  1.7729 + *        anotherDiv  = document.querySelector("div.preview");
  1.7730 + *    wysihtml.dom.copyAttributes(["spellcheck", "value", "placeholder"]).from(textarea).to(div).andTo(anotherDiv);
  1.7731 + *
  1.7732 + */
  1.7733 +wysihtml.dom.copyAttributes = function(attributesToCopy) {
  1.7734 +  return {
  1.7735 +    from: function(elementToCopyFrom) {
  1.7736 +      return {
  1.7737 +        to: function pasteElementAttributesTo(elementToCopyTo) {
  1.7738 +          var attribute,
  1.7739 +              i         = 0,
  1.7740 +              length    = attributesToCopy.length;
  1.7741 +          for (; i<length; i++) {
  1.7742 +            attribute = attributesToCopy[i];
  1.7743 +            if (typeof(elementToCopyFrom[attribute]) !== "undefined" && elementToCopyFrom[attribute] !== "") {
  1.7744 +              elementToCopyTo[attribute] = elementToCopyFrom[attribute];
  1.7745 +            }
  1.7746 +          }
  1.7747 +          return { andTo: pasteElementAttributesTo };
  1.7748 +        }
  1.7749 +      };
  1.7750 +    }
  1.7751 +  };
  1.7752 +};
  1.7753 +
  1.7754 +/**
  1.7755 + * Copy a set of styles from one element to another
  1.7756 + * Please note that this only works properly across browsers when the element from which to copy the styles
  1.7757 + * is in the dom
  1.7758 + *
  1.7759 + * Interesting article on how to copy styles
  1.7760 + *
  1.7761 + * @param {Array} stylesToCopy List of styles which should be copied
  1.7762 + * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
  1.7763 + *    copy the styles from., this again returns an object which provides a method named "to" which can be invoked
  1.7764 + *    with the element where to copy the styles to (see example)
  1.7765 + *
  1.7766 + * @example
  1.7767 + *    var textarea    = document.querySelector("textarea"),
  1.7768 + *        div         = document.querySelector("div[contenteditable=true]"),
  1.7769 + *        anotherDiv  = document.querySelector("div.preview");
  1.7770 + *    wysihtml.dom.copyStyles(["overflow-y", "width", "height"]).from(textarea).to(div).andTo(anotherDiv);
  1.7771 + *
  1.7772 + */
  1.7773 +(function(dom) {
  1.7774 +
  1.7775 +  /**
  1.7776 +   * Mozilla, WebKit and Opera recalculate the computed width when box-sizing: boder-box; is set
  1.7777 +   * So if an element has "width: 200px; -moz-box-sizing: border-box; border: 1px;" then
  1.7778 +   * its computed css width will be 198px
  1.7779 +   *
  1.7780 +   * See https://bugzilla.mozilla.org/show_bug.cgi?id=520992
  1.7781 +   */
  1.7782 +  var BOX_SIZING_PROPERTIES = ["-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing"];
  1.7783 +
  1.7784 +  var shouldIgnoreBoxSizingBorderBox = function(element) {
  1.7785 +    if (hasBoxSizingBorderBox(element)) {
  1.7786 +       return parseInt(dom.getStyle("width").from(element), 10) < element.offsetWidth;
  1.7787 +    }
  1.7788 +    return false;
  1.7789 +  };
  1.7790 +
  1.7791 +  var hasBoxSizingBorderBox = function(element) {
  1.7792 +    var i       = 0,
  1.7793 +        length  = BOX_SIZING_PROPERTIES.length;
  1.7794 +    for (; i<length; i++) {
  1.7795 +      if (dom.getStyle(BOX_SIZING_PROPERTIES[i]).from(element) === "border-box") {
  1.7796 +        return BOX_SIZING_PROPERTIES[i];
  1.7797 +      }
  1.7798 +    }
  1.7799 +  };
  1.7800 +
  1.7801 +  dom.copyStyles = function(stylesToCopy) {
  1.7802 +    return {
  1.7803 +      from: function(element) {
  1.7804 +        if (shouldIgnoreBoxSizingBorderBox(element)) {
  1.7805 +          stylesToCopy = wysihtml.lang.array(stylesToCopy).without(BOX_SIZING_PROPERTIES);
  1.7806 +        }
  1.7807 +
  1.7808 +        var cssText = "",
  1.7809 +            length  = stylesToCopy.length,
  1.7810 +            i       = 0,
  1.7811 +            property;
  1.7812 +        for (; i<length; i++) {
  1.7813 +          property = stylesToCopy[i];
  1.7814 +          cssText += property + ":" + dom.getStyle(property).from(element) + ";";
  1.7815 +        }
  1.7816 +
  1.7817 +        return {
  1.7818 +          to: function pasteStylesTo(element) {
  1.7819 +            dom.setStyles(cssText).on(element);
  1.7820 +            return { andTo: pasteStylesTo };
  1.7821 +          }
  1.7822 +        };
  1.7823 +      }
  1.7824 +    };
  1.7825 +  };
  1.7826 +})(wysihtml.dom);
  1.7827 +
  1.7828 +/**
  1.7829 + * Event Delegation
  1.7830 + *
  1.7831 + * @example
  1.7832 + *    wysihtml.dom.delegate(document.body, "a", "click", function() {
  1.7833 + *      // foo
  1.7834 + *    });
  1.7835 + */
  1.7836 +(function(wysihtml) {
  1.7837 +  wysihtml.dom.delegate = function(container, selector, eventName, handler) {
  1.7838 +    var callback = function(event) {
  1.7839 +      var target = event.target,
  1.7840 +          element = (target.nodeType === 3) ? target.parentNode : target, // IE has .contains only seeing elements not textnodes
  1.7841 +          matches  = container.querySelectorAll(selector);
  1.7842 +
  1.7843 +      for (var i = 0, max = matches.length; i < max; i++) {
  1.7844 +        if (matches[i].contains(element)) {
  1.7845 +          handler.call(matches[i], event);
  1.7846 +        }
  1.7847 +      }
  1.7848 +    };
  1.7849 +
  1.7850 +    container.addEventListener(eventName, callback, false);
  1.7851 +    return {
  1.7852 +      stop: function() {
  1.7853 +        container.removeEventListener(eventName, callback, false);
  1.7854 +      }
  1.7855 +    };
  1.7856 +  };
  1.7857 +})(wysihtml);
  1.7858 +
  1.7859 +// TODO: Refactor dom tree traversing here
  1.7860 +(function(wysihtml) {
  1.7861 +
  1.7862 +  // Finds parents of a node, returning the outermost node first in Array
  1.7863 +  // if contain node is given parents search is stopped at the container
  1.7864 +  function parents(node, container) {
  1.7865 +    var nodes = [node], n = node;
  1.7866 +
  1.7867 +    // iterate parents while parent exists and it is not container element
  1.7868 +    while((container && n && n !== container) || (!container && n)) {
  1.7869 +      nodes.unshift(n);
  1.7870 +      n = n.parentNode;
  1.7871 +    }
  1.7872 +    return nodes;
  1.7873 +  }
  1.7874 +
  1.7875 +  wysihtml.dom.domNode = function(node) {
  1.7876 +    var defaultNodeTypes = [wysihtml.ELEMENT_NODE, wysihtml.TEXT_NODE];
  1.7877 +
  1.7878 +    return {
  1.7879 +
  1.7880 +      is: {
  1.7881 +        emptyTextNode: function(ignoreWhitespace) {
  1.7882 +          var regx = ignoreWhitespace ? (/^\s*$/g) : (/^[\r\n]*$/g);
  1.7883 +          return node && node.nodeType === wysihtml.TEXT_NODE && (regx).test(node.data);
  1.7884 +        },
  1.7885 +
  1.7886 +        // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
  1.7887 +        rangyBookmark: function() {
  1.7888 +          return node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary');
  1.7889 +        },
  1.7890 +
  1.7891 +        visible: function() {
  1.7892 +          var isVisible = !(/^\s*$/g).test(wysihtml.dom.getTextContent(node));
  1.7893 +
  1.7894 +          if (!isVisible) {
  1.7895 +            if (node.nodeType === 1 && node.querySelector('img, br, hr, object, embed, canvas, input, textarea')) {
  1.7896 +              isVisible = true;
  1.7897 +            }
  1.7898 +          }
  1.7899 +          return isVisible;
  1.7900 +        },
  1.7901 +        lineBreak: function() {
  1.7902 +          return node && node.nodeType === 1 && node.nodeName === "BR";
  1.7903 +        },
  1.7904 +        block: function() {
  1.7905 +          return node && node.nodeType === 1 && node.ownerDocument.defaultView.getComputedStyle(node).display === "block";
  1.7906 +        },
  1.7907 +        // Void elements are elemens that can not have content
  1.7908 +        // In most cases browsers should solve the cases for you when you try to insert content into those,
  1.7909 +        //    but IE does not and it is not nice to do so anyway.
  1.7910 +        voidElement: function() {
  1.7911 +          return wysihtml.dom.domNode(node).test({
  1.7912 +            query: wysihtml.VOID_ELEMENTS
  1.7913 +          });
  1.7914 +        }
  1.7915 +      },
  1.7916 +
  1.7917 +      // var node = wysihtml.dom.domNode(element).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
  1.7918 +      prev: function(options) {
  1.7919 +        var prevNode = node.previousSibling,
  1.7920 +            types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
  1.7921 +        
  1.7922 +        if (!prevNode) {
  1.7923 +          return null;
  1.7924 +        }
  1.7925 +
  1.7926 +        if (
  1.7927 +          wysihtml.dom.domNode(prevNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass)
  1.7928 +          (!wysihtml.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check.
  1.7929 +          (options && options.ignoreBlankTexts && wysihtml.dom.domNode(prevNode).is.emptyTextNode(true)) // Blank text nodes bypassed if set
  1.7930 +        ) {
  1.7931 +          return wysihtml.dom.domNode(prevNode).prev(options);
  1.7932 +        }
  1.7933 +        
  1.7934 +        return prevNode;
  1.7935 +      },
  1.7936 +
  1.7937 +      // var node = wysihtml.dom.domNode(element).next({nodeTypes: [1,3], ignoreBlankTexts: true});
  1.7938 +      next: function(options) {
  1.7939 +        var nextNode = node.nextSibling,
  1.7940 +            types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
  1.7941 +        
  1.7942 +        if (!nextNode) {
  1.7943 +          return null;
  1.7944 +        }
  1.7945 +
  1.7946 +        if (
  1.7947 +          wysihtml.dom.domNode(nextNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass)
  1.7948 +          (!wysihtml.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check.
  1.7949 +          (options && options.ignoreBlankTexts && wysihtml.dom.domNode(nextNode).is.emptyTextNode(true)) // blank text nodes bypassed if set
  1.7950 +        ) {
  1.7951 +          return wysihtml.dom.domNode(nextNode).next(options);
  1.7952 +        }
  1.7953 +        
  1.7954 +        return nextNode;
  1.7955 +      },
  1.7956 +
  1.7957 +      // Finds the common acnestor container of two nodes
  1.7958 +      // If container given stops search at the container
  1.7959 +      // If no common ancestor found returns null
  1.7960 +      // var node = wysihtml.dom.domNode(element).commonAncestor(node2, container);
  1.7961 +      commonAncestor: function(node2, container) {
  1.7962 +        var parents1 = parents(node, container),
  1.7963 +            parents2 = parents(node2, container);
  1.7964 +
  1.7965 +        // Ensure we have found a common ancestor, which will be the first one if anything
  1.7966 +        if (parents1[0] != parents2[0]) {
  1.7967 +          return null;
  1.7968 +        }
  1.7969 +
  1.7970 +        // Traverse up the hierarchy of parents until we reach where they're no longer
  1.7971 +        // the same. Then return previous which was the common ancestor.
  1.7972 +        for (var i = 0; i < parents1.length; i++) {
  1.7973 +          if (parents1[i] != parents2[i]) {
  1.7974 +            return parents1[i - 1];
  1.7975 +          }
  1.7976 +        }
  1.7977 +
  1.7978 +        return null;
  1.7979 +      },
  1.7980 +
  1.7981 +      // Traverses a node for last children and their chidren (including itself), and finds the last node that has no children.
  1.7982 +      // Array of classes for forced last-leaves (ex: uneditable-container) can be defined (options = {leafClasses: [...]})
  1.7983 +      // Useful for finding the actually visible element before cursor
  1.7984 +      lastLeafNode: function(options) {
  1.7985 +        var lastChild;
  1.7986 +
  1.7987 +        // Returns non-element nodes
  1.7988 +        if (node.nodeType !== 1) {
  1.7989 +          return node;
  1.7990 +        }
  1.7991 +
  1.7992 +        // Returns if element is leaf
  1.7993 +        lastChild = node.lastChild;
  1.7994 +        if (!lastChild) {
  1.7995 +          return node;
  1.7996 +        }
  1.7997 +
  1.7998 +        // Returns if element is of of options.leafClasses leaf
  1.7999 +        if (options && options.leafClasses) {
  1.8000 +          for (var i = options.leafClasses.length; i--;) {
  1.8001 +            if (wysihtml.dom.hasClass(node, options.leafClasses[i])) {
  1.8002 +              return node;
  1.8003 +            }
  1.8004 +          }
  1.8005 +        }
  1.8006 +
  1.8007 +        return wysihtml.dom.domNode(lastChild).lastLeafNode(options);
  1.8008 +      },
  1.8009 +
  1.8010 +      // Splits element at childnode and extracts the childNode out of the element context
  1.8011 +      // Example:
  1.8012 +      //   var node = wysihtml.dom.domNode(node).escapeParent(parentNode);
  1.8013 +      escapeParent: function(element, newWrapper) {
  1.8014 +        var parent, split2, nodeWrap,
  1.8015 +            curNode = node;
  1.8016 +        
  1.8017 +        // Stop if node is not a descendant of element
  1.8018 +        if (!wysihtml.dom.contains(element, node)) {
  1.8019 +          throw new Error("Child is not a descendant of node.");
  1.8020 +        }
  1.8021 +
  1.8022 +        // Climb up the node tree untill node is reached
  1.8023 +        do {
  1.8024 +          // Get current parent of node
  1.8025 +          parent = curNode.parentNode;
  1.8026 +
  1.8027 +          // Move after nodes to new clone wrapper
  1.8028 +          split2 = parent.cloneNode(false);
  1.8029 +          while (parent.lastChild && parent.lastChild !== curNode) {
  1.8030 +            split2.insertBefore(parent.lastChild, split2.firstChild);
  1.8031 +          }
  1.8032 +
  1.8033 +          // Move node up a level. If parent is not yet the container to escape, clone the parent around node, so inner nodes are escaped out too
  1.8034 +          if (parent !== element) {
  1.8035 +            nodeWrap = parent.cloneNode(false);
  1.8036 +            nodeWrap.appendChild(curNode);
  1.8037 +            curNode = nodeWrap;
  1.8038 +          }
  1.8039 +          parent.parentNode.insertBefore(curNode, parent.nextSibling);
  1.8040 +
  1.8041 +          // Add after nodes (unless empty)
  1.8042 +          if (split2.innerHTML !== '') {
  1.8043 +            // if contents are empty insert without wrap
  1.8044 +            if ((/^\s+$/).test(split2.innerHTML)) {
  1.8045 +              while (split2.lastChild) {
  1.8046 +                parent.parentNode.insertBefore(split2.lastChild, curNode.nextSibling);
  1.8047 +              }
  1.8048 +            } else {
  1.8049 +              parent.parentNode.insertBefore(split2, curNode.nextSibling);
  1.8050 +            }
  1.8051 +          }
  1.8052 +
  1.8053 +          // If the node left behind before the split (parent) is now empty then remove
  1.8054 +          if (parent.innerHTML === '') {
  1.8055 +            parent.parentNode.removeChild(parent);
  1.8056 +          } else if ((/^\s+$/).test(parent.innerHTML)) {
  1.8057 +            while (parent.firstChild) {
  1.8058 +              parent.parentNode.insertBefore(parent.firstChild, parent);
  1.8059 +            }
  1.8060 +            parent.parentNode.removeChild(parent);
  1.8061 +          }
  1.8062 +
  1.8063 +        } while (parent && parent !== element);
  1.8064 +
  1.8065 +        if (newWrapper && curNode) {
  1.8066 +          curNode.parentNode.insertBefore(newWrapper, curNode);
  1.8067 +          newWrapper.appendChild(curNode);
  1.8068 +        }
  1.8069 +      },
  1.8070 +
  1.8071 +      transferContentTo: function(targetNode, removeOldWrapper) {
  1.8072 +        if (node.nodeType === 1) {
  1.8073 +          if (wysihtml.dom.domNode(targetNode).is.voidElement() || targetNode.nodeType === 3) {
  1.8074 +            while (node.lastChild) {
  1.8075 +              targetNode.parentNode.insertBefore(node.lastChild, targetNode.nextSibling);
  1.8076 +            }
  1.8077 +          } else {
  1.8078 +            while (node.firstChild) {
  1.8079 +              targetNode.appendChild(node.firstChild);
  1.8080 +            }
  1.8081 +          }
  1.8082 +          if (removeOldWrapper) {
  1.8083 +            node.parentNode.removeChild(node);
  1.8084 +          }
  1.8085 +        } else if (node.nodeType === 3 || node.nodeType === 8){
  1.8086 +          if (wysihtml.dom.domNode(targetNode).is.voidElement()) {
  1.8087 +            targetNode.parentNode.insertBefore(node, targetNode.nextSibling);
  1.8088 +          } else {
  1.8089 +            targetNode.appendChild(node);
  1.8090 +          }
  1.8091 +        }
  1.8092 +      },
  1.8093 +
  1.8094 +      /*
  1.8095 +        Tests a node against properties, and returns true if matches.
  1.8096 +        Tests on principle that all properties defined must have at least one match.
  1.8097 +        styleValue parameter works in context of styleProperty and has no effect otherwise.
  1.8098 +        Returns true if element matches and false if it does not.
  1.8099 +        
  1.8100 +        Properties for filtering element:
  1.8101 +        {
  1.8102 +          query: selector string,
  1.8103 +          nodeName: string (uppercase),
  1.8104 +          className: string,
  1.8105 +          classRegExp: regex,
  1.8106 +          styleProperty: string or [],
  1.8107 +          styleValue: string, [] or regex
  1.8108 +        }
  1.8109 +
  1.8110 +        Example:
  1.8111 +        var node = wysihtml.dom.domNode(element).test({})
  1.8112 +      */
  1.8113 +      test: function(properties) {
  1.8114 +        var prop;
  1.8115 +
  1.8116 +        // return false if properties object is not defined
  1.8117 +        if (!properties) {
  1.8118 +          return false;
  1.8119 +        }
  1.8120 +
  1.8121 +        // Only element nodes can be tested for these properties
  1.8122 +        if (node.nodeType !== 1) {
  1.8123 +          return false;
  1.8124 +        }
  1.8125 +
  1.8126 +        if (properties.query) {
  1.8127 +          if (!node.matches(properties.query)) {
  1.8128 +            return false;
  1.8129 +          }
  1.8130 +        }
  1.8131 +
  1.8132 +        if (properties.nodeName && node.nodeName.toLowerCase() !== properties.nodeName.toLowerCase()) {
  1.8133 +          return false;
  1.8134 +        }
  1.8135 +
  1.8136 +        if (properties.className && !node.classList.contains(properties.className)) {
  1.8137 +          return false;
  1.8138 +        }
  1.8139 +
  1.8140 +        // classRegExp check (useful for classname begins with logic)
  1.8141 +        if (properties.classRegExp) {
  1.8142 +          var matches = (node.className || "").match(properties.classRegExp) || [];
  1.8143 +          if (matches.length === 0) {
  1.8144 +            return false;
  1.8145 +          }
  1.8146 +        }
  1.8147 +
  1.8148 +        // styleProperty check
  1.8149 +        if (properties.styleProperty && properties.styleProperty.length > 0) {
  1.8150 +          var hasOneStyle = false,
  1.8151 +              styles = (Array.isArray(properties.styleProperty)) ? properties.styleProperty : [properties.styleProperty];
  1.8152 +          for (var j = 0, maxStyleP = styles.length; j < maxStyleP; j++) {
  1.8153 +            // Some old IE-s have different property name for cssFloat
  1.8154 +            prop = wysihtml.browser.fixStyleKey(styles[j]);
  1.8155 +            if (node.style[prop]) {
  1.8156 +              if (properties.styleValue) {
  1.8157 +                // Style value as additional parameter
  1.8158 +                if (properties.styleValue instanceof RegExp) {
  1.8159 +                  // style value as Regexp
  1.8160 +                  if (node.style[prop].trim().match(properties.styleValue).length > 0) {
  1.8161 +                    hasOneStyle = true;
  1.8162 +                    break;
  1.8163 +                  }
  1.8164 +                } else if (Array.isArray(properties.styleValue)) {
  1.8165 +                  // style value as array
  1.8166 +                  if (properties.styleValue.indexOf(node.style[prop].trim())) {
  1.8167 +                    hasOneStyle = true;
  1.8168 +                    break;
  1.8169 +                  }
  1.8170 +                } else {
  1.8171 +                  // style value as string
  1.8172 +                  if (properties.styleValue === node.style[prop].trim().replace(/, /g, ",")) {
  1.8173 +                    hasOneStyle = true;
  1.8174 +                    break;
  1.8175 +                  }
  1.8176 +                }
  1.8177 +              } else {
  1.8178 +                hasOneStyle = true;
  1.8179 +                break;
  1.8180 +              }
  1.8181 +            }
  1.8182 +            if (!hasOneStyle) {
  1.8183 +              return false;
  1.8184 +            }
  1.8185 +          }
  1.8186 +        }
  1.8187 +
  1.8188 +        if (properties.attribute) {
  1.8189 +          var attr = wysihtml.dom.getAttributes(node),
  1.8190 +              attrList = [],
  1.8191 +              hasOneAttribute = false;
  1.8192 +
  1.8193 +          if (Array.isArray(properties.attribute)) {
  1.8194 +            attrList = properties.attribute;
  1.8195 +          } else {
  1.8196 +            attrList[properties.attribute] = properties.attributeValue;
  1.8197 +          }
  1.8198 +
  1.8199 +          for (var a in attrList) {
  1.8200 +            if (attrList.hasOwnProperty(a)) {
  1.8201 +              if (typeof attrList[a] === "undefined") {
  1.8202 +                if (typeof attr[a] !== "undefined") {
  1.8203 +                  hasOneAttribute = true;
  1.8204 +                  break;
  1.8205 +                }
  1.8206 +              } else if (attr[a] === attrList[a]) {
  1.8207 +                hasOneAttribute = true;
  1.8208 +                break;
  1.8209 +              }
  1.8210 +            }
  1.8211 +          }
  1.8212 +
  1.8213 +          if (!hasOneAttribute) {
  1.8214 +            return false;
  1.8215 +          }
  1.8216 +
  1.8217 +        }
  1.8218 +
  1.8219 +        return true;
  1.8220 +      }
  1.8221 +
  1.8222 +    };
  1.8223 +  };
  1.8224 +})(wysihtml);
  1.8225 +
  1.8226 +/**
  1.8227 + * Returns the given html wrapped in a div element
  1.8228 + *
  1.8229 + * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly
  1.8230 + * when inserted via innerHTML
  1.8231 + *
  1.8232 + * @param {String} html The html which should be wrapped in a dom element
  1.8233 + * @param {Obejct} [context] Document object of the context the html belongs to
  1.8234 + *
  1.8235 + * @example
  1.8236 + *    wysihtml.dom.getAsDom("<article>foo</article>");
  1.8237 + */
  1.8238 +wysihtml.dom.getAsDom = (function() {
  1.8239 +
  1.8240 +  var _innerHTMLShiv = function(html, context) {
  1.8241 +    var tempElement = context.createElement("div");
  1.8242 +    tempElement.style.display = "none";
  1.8243 +    context.body.appendChild(tempElement);
  1.8244 +    // IE throws an exception when trying to insert <frameset></frameset> via innerHTML
  1.8245 +    try { tempElement.innerHTML = html; } catch(e) {}
  1.8246 +    context.body.removeChild(tempElement);
  1.8247 +    return tempElement;
  1.8248 +  };
  1.8249 +
  1.8250 +  /**
  1.8251 +   * Make sure IE supports HTML5 tags, which is accomplished by simply creating one instance of each element
  1.8252 +   */
  1.8253 +  var _ensureHTML5Compatibility = function(context) {
  1.8254 +    if (context._wysihtml_supportsHTML5Tags) {
  1.8255 +      return;
  1.8256 +    }
  1.8257 +    for (var i=0, length=HTML5_ELEMENTS.length; i<length; i++) {
  1.8258 +      context.createElement(HTML5_ELEMENTS[i]);
  1.8259 +    }
  1.8260 +    context._wysihtml_supportsHTML5Tags = true;
  1.8261 +  };
  1.8262 +
  1.8263 +
  1.8264 +  /**
  1.8265 +   * List of html5 tags
  1.8266 +   * taken from http://simon.html5.org/html5-elements
  1.8267 +   */
  1.8268 +  var HTML5_ELEMENTS = [
  1.8269 +    "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption",
  1.8270 +    "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress",
  1.8271 +    "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr"
  1.8272 +  ];
  1.8273 +
  1.8274 +  return function(html, context) {
  1.8275 +    context = context || document;
  1.8276 +    var tempElement;
  1.8277 +    if (typeof(html) === "object" && html.nodeType) {
  1.8278 +      tempElement = context.createElement("div");
  1.8279 +      tempElement.appendChild(html);
  1.8280 +    } else if (wysihtml.browser.supportsHTML5Tags(context)) {
  1.8281 +      tempElement = context.createElement("div");
  1.8282 +      tempElement.innerHTML = html;
  1.8283 +    } else {
  1.8284 +      _ensureHTML5Compatibility(context);
  1.8285 +      tempElement = _innerHTMLShiv(html, context);
  1.8286 +    }
  1.8287 +    return tempElement;
  1.8288 +  };
  1.8289 +})();
  1.8290 +
  1.8291 +/**
  1.8292 + * Get a set of attribute from one element
  1.8293 + *
  1.8294 + * IE gives wrong results for hasAttribute/getAttribute, for example:
  1.8295 + *    var td = document.createElement("td");
  1.8296 + *    td.getAttribute("rowspan"); // => "1" in IE
  1.8297 + *
  1.8298 + * Therefore we have to check the element's outerHTML for the attribute
  1.8299 +*/
  1.8300 +
  1.8301 +wysihtml.dom.getAttribute = function(node, attributeName) {
  1.8302 +  var HAS_GET_ATTRIBUTE_BUG = !wysihtml.browser.supportsGetAttributeCorrectly();
  1.8303 +  attributeName = attributeName.toLowerCase();
  1.8304 +  var nodeName = node.nodeName;
  1.8305 +  if (nodeName == "IMG" && attributeName == "src" && wysihtml.dom.isLoadedImage(node) === true) {
  1.8306 +    // Get 'src' attribute value via object property since this will always contain the
  1.8307 +    // full absolute url (http://...)
  1.8308 +    // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
  1.8309 +    // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
  1.8310 +    return node.src;
  1.8311 +  } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
  1.8312 +    // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
  1.8313 +    var outerHTML      = node.outerHTML.toLowerCase(),
  1.8314 +        // TODO: This might not work for attributes without value: <input disabled>
  1.8315 +        hasAttribute   = outerHTML.indexOf(" " + attributeName +  "=") != -1;
  1.8316 +
  1.8317 +    return hasAttribute ? node.getAttribute(attributeName) : null;
  1.8318 +  } else{
  1.8319 +    return node.getAttribute(attributeName);
  1.8320 +  }
  1.8321 +};
  1.8322 +
  1.8323 +/**
  1.8324 + * Get all attributes of an element
  1.8325 + *
  1.8326 + * IE gives wrong results for hasAttribute/getAttribute, for example:
  1.8327 + *    var td = document.createElement("td");
  1.8328 + *    td.getAttribute("rowspan"); // => "1" in IE
  1.8329 + *
  1.8330 + * Therefore we have to check the element's outerHTML for the attribute
  1.8331 +*/
  1.8332 +
  1.8333 +wysihtml.dom.getAttributes = function(node) {
  1.8334 +  var HAS_GET_ATTRIBUTE_BUG = !wysihtml.browser.supportsGetAttributeCorrectly(),
  1.8335 +      nodeName = node.nodeName,
  1.8336 +      attributes = [],
  1.8337 +      attr;
  1.8338 +
  1.8339 +  for (attr in node.attributes) {
  1.8340 +    if ((node.attributes.hasOwnProperty && node.attributes.hasOwnProperty(attr)) || (!node.attributes.hasOwnProperty && Object.prototype.hasOwnProperty.call(node.attributes, attr)))  {
  1.8341 +      if (node.attributes[attr].specified) {
  1.8342 +        if (nodeName == "IMG" && node.attributes[attr].name.toLowerCase() == "src" && wysihtml.dom.isLoadedImage(node) === true) {
  1.8343 +          attributes['src'] = node.src;
  1.8344 +        } else if (wysihtml.lang.array(['rowspan', 'colspan']).contains(node.attributes[attr].name.toLowerCase()) && HAS_GET_ATTRIBUTE_BUG) {
  1.8345 +          if (node.attributes[attr].value !== 1) {
  1.8346 +            attributes[node.attributes[attr].name] = node.attributes[attr].value;
  1.8347 +          }
  1.8348 +        } else {
  1.8349 +          attributes[node.attributes[attr].name] = node.attributes[attr].value;
  1.8350 +        }
  1.8351 +      }
  1.8352 +    }
  1.8353 +  }
  1.8354 +  return attributes;
  1.8355 +};
  1.8356 +
  1.8357 +/**
  1.8358 + * Walks the dom tree from the given node up until it finds a match
  1.8359 + *
  1.8360 + * @param {Element} node The from which to check the parent nodes
  1.8361 + * @param {Object} matchingSet Object to match against, Properties for filtering element:
  1.8362 + *   {
  1.8363 + *     query: selector string,
  1.8364 + *     classRegExp: regex,
  1.8365 + *     styleProperty: string or [],
  1.8366 + *     styleValue: string, [] or regex
  1.8367 + *   }
  1.8368 + * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50)
  1.8369 + * @param {Element} Optional, defines the container that limits the search
  1.8370 + *
  1.8371 + * @return {null|Element} Returns the first element that matched the desiredNodeName(s)
  1.8372 +*/
  1.8373 +
  1.8374 +wysihtml.dom.getParentElement = (function() {
  1.8375 +
  1.8376 +  return function(node, properties, levels, container) {
  1.8377 +    levels = levels || 50;
  1.8378 +    while (levels-- && node && node.nodeName !== "BODY" && (!container || node !== container)) {
  1.8379 +      if (wysihtml.dom.domNode(node).test(properties)) {
  1.8380 +        return node;
  1.8381 +      }
  1.8382 +      node = node.parentNode;
  1.8383 +    }
  1.8384 +    return null;
  1.8385 +  };
  1.8386 +
  1.8387 +})();
  1.8388 +
  1.8389 +/* 
  1.8390 + * Methods for fetching pasted html before it gets inserted into content
  1.8391 +**/
  1.8392 +
  1.8393 +/* Modern event.clipboardData driven approach.
  1.8394 + * Advantage is that it does not have to loose selection or modify dom to catch the data. 
  1.8395 + * IE does not support though.
  1.8396 +**/
  1.8397 +wysihtml.dom.getPastedHtml = function(event) {
  1.8398 +  var html;
  1.8399 +  if (wysihtml.browser.supportsModernPaste() && event.clipboardData) {
  1.8400 +    if (wysihtml.lang.array(event.clipboardData.types).contains('text/html')) {
  1.8401 +      html = event.clipboardData.getData('text/html');
  1.8402 +    } else if (wysihtml.lang.array(event.clipboardData.types).contains('text/plain')) {
  1.8403 +      html = wysihtml.lang.string(event.clipboardData.getData('text/plain')).escapeHTML(true, true);
  1.8404 +    }
  1.8405 +  }
  1.8406 +  return html;
  1.8407 +};
  1.8408 +
  1.8409 +/* Older temprorary contenteditable as paste source catcher method for fallbacks */
  1.8410 +wysihtml.dom.getPastedHtmlWithDiv = function (composer, f) {
  1.8411 +  var selBookmark = composer.selection.getBookmark(),
  1.8412 +      doc = composer.element.ownerDocument,
  1.8413 +      cleanerDiv = doc.createElement('DIV'),
  1.8414 +      scrollPos = composer.getScrollPos();
  1.8415 +  
  1.8416 +  doc.body.appendChild(cleanerDiv);
  1.8417 +
  1.8418 +  cleanerDiv.style.width = "1px";
  1.8419 +  cleanerDiv.style.height = "1px";
  1.8420 +  cleanerDiv.style.overflow = "hidden";
  1.8421 +  cleanerDiv.style.position = "absolute";
  1.8422 +  cleanerDiv.style.top = scrollPos.y + "px";
  1.8423 +  cleanerDiv.style.left = scrollPos.x + "px";
  1.8424 +
  1.8425 +  cleanerDiv.setAttribute('contenteditable', 'true');
  1.8426 +  cleanerDiv.focus();
  1.8427 +
  1.8428 +  setTimeout(function () {
  1.8429 +    var html;
  1.8430 +
  1.8431 +    composer.selection.setBookmark(selBookmark);
  1.8432 +    html = cleanerDiv.innerHTML;
  1.8433 +    if (html && (/^<br\/?>$/i).test(html.trim())) {
  1.8434 +      html = false;
  1.8435 +    }
  1.8436 +    f(html);
  1.8437 +    cleanerDiv.parentNode.removeChild(cleanerDiv);
  1.8438 +  }, 0);
  1.8439 +};
  1.8440 +
  1.8441 +/**
  1.8442 + * Get element's style for a specific css property
  1.8443 + *
  1.8444 + * @param {Element} element The element on which to retrieve the style
  1.8445 + * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...)
  1.8446 + *
  1.8447 + * @example
  1.8448 + *    wysihtml.dom.getStyle("display").from(document.body);
  1.8449 + *    // => "block"
  1.8450 + */
  1.8451 +wysihtml.dom.getStyle = (function() {
  1.8452 +  var stylePropertyMapping = {
  1.8453 +        "float": ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat"
  1.8454 +      },
  1.8455 +      REG_EXP_CAMELIZE = /\-[a-z]/g;
  1.8456 +
  1.8457 +  function camelize(str) {
  1.8458 +    return str.replace(REG_EXP_CAMELIZE, function(match) {
  1.8459 +      return match.charAt(1).toUpperCase();
  1.8460 +    });
  1.8461 +  }
  1.8462 +
  1.8463 +  return function(property) {
  1.8464 +    return {
  1.8465 +      from: function(element) {
  1.8466 +        if (element.nodeType !== wysihtml.ELEMENT_NODE) {
  1.8467 +          return;
  1.8468 +        }
  1.8469 +
  1.8470 +        var doc               = element.ownerDocument,
  1.8471 +            camelizedProperty = stylePropertyMapping[property] || camelize(property),
  1.8472 +            style             = element.style,
  1.8473 +            currentStyle      = element.currentStyle,
  1.8474 +            styleValue        = style[camelizedProperty];
  1.8475 +        if (styleValue) {
  1.8476 +          return styleValue;
  1.8477 +        }
  1.8478 +
  1.8479 +        // currentStyle is no standard and only supported by Opera and IE but it has one important advantage over the standard-compliant
  1.8480 +        // window.getComputedStyle, since it returns css property values in their original unit:
  1.8481 +        // If you set an elements width to "50%", window.getComputedStyle will give you it's current width in px while currentStyle
  1.8482 +        // gives you the original "50%".
  1.8483 +        // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio
  1.8484 +        if (currentStyle) {
  1.8485 +          try {
  1.8486 +            return currentStyle[camelizedProperty];
  1.8487 +          } catch(e) {
  1.8488 +            //ie will occasionally fail for unknown reasons. swallowing exception
  1.8489 +          }
  1.8490 +        }
  1.8491 +
  1.8492 +        var win                 = doc.defaultView || doc.parentWindow,
  1.8493 +            needsOverflowReset  = (property === "height" || property === "width") && element.nodeName === "TEXTAREA",
  1.8494 +            originalOverflow,
  1.8495 +            returnValue;
  1.8496 +
  1.8497 +        if (win.getComputedStyle) {
  1.8498 +          // Chrome and Safari both calculate a wrong width and height for textareas when they have scroll bars
  1.8499 +          // therfore we remove and restore the scrollbar and calculate the value in between
  1.8500 +          if (needsOverflowReset) {
  1.8501 +            originalOverflow = style.overflow;
  1.8502 +            style.overflow = "hidden";
  1.8503 +          }
  1.8504 +          returnValue = win.getComputedStyle(element, null).getPropertyValue(property);
  1.8505 +          if (needsOverflowReset) {
  1.8506 +            style.overflow = originalOverflow || "";
  1.8507 +          }
  1.8508 +          return returnValue;
  1.8509 +        }
  1.8510 +      }
  1.8511 +    };
  1.8512 +  };
  1.8513 +})();
  1.8514 +
  1.8515 +wysihtml.dom.getTextNodes = function(node, ingoreEmpty){
  1.8516 +  var all = [];
  1.8517 +  for (node=node.firstChild;node;node=node.nextSibling){
  1.8518 +    if (node.nodeType == 3) {
  1.8519 +      if (!ingoreEmpty || !(/^\s*$/).test(node.innerText || node.textContent)) {
  1.8520 +        all.push(node);
  1.8521 +      }
  1.8522 +    } else {
  1.8523 +      all = all.concat(wysihtml.dom.getTextNodes(node, ingoreEmpty));
  1.8524 +    }
  1.8525 +  }
  1.8526 +  return all;
  1.8527 +};
  1.8528 +
  1.8529 +/**
  1.8530 + * High performant way to check whether an element with a specific class name is in the given document
  1.8531 + * Optimized for being heavily executed
  1.8532 + * Unleashes the power of live node lists
  1.8533 + *
  1.8534 + * @param {Object} doc The document object of the context where to check
  1.8535 + * @param {String} tagName Upper cased tag name
  1.8536 + * @example
  1.8537 + *    wysihtml.dom.hasElementWithClassName(document, "foobar");
  1.8538 + */
  1.8539 +(function(wysihtml) {
  1.8540 +  var LIVE_CACHE          = {},
  1.8541 +      DOCUMENT_IDENTIFIER = 1;
  1.8542 +
  1.8543 +  function _getDocumentIdentifier(doc) {
  1.8544 +    return doc._wysihtml_identifier || (doc._wysihtml_identifier = DOCUMENT_IDENTIFIER++);
  1.8545 +  }
  1.8546 +
  1.8547 +  wysihtml.dom.hasElementWithClassName = function(doc, className) {
  1.8548 +    // getElementsByClassName is not supported by IE<9
  1.8549 +    // but is sometimes mocked via library code (which then doesn't return live node lists)
  1.8550 +    if (!wysihtml.browser.supportsNativeGetElementsByClassName()) {
  1.8551 +      return !!doc.querySelector("." + className);
  1.8552 +    }
  1.8553 +
  1.8554 +    var key         = _getDocumentIdentifier(doc) + ":" + className,
  1.8555 +        cacheEntry  = LIVE_CACHE[key];
  1.8556 +    if (!cacheEntry) {
  1.8557 +      cacheEntry = LIVE_CACHE[key] = doc.getElementsByClassName(className);
  1.8558 +    }
  1.8559 +
  1.8560 +    return cacheEntry.length > 0;
  1.8561 +  };
  1.8562 +})(wysihtml);
  1.8563 +
  1.8564 +/**
  1.8565 + * High performant way to check whether an element with a specific tag name is in the given document
  1.8566 + * Optimized for being heavily executed
  1.8567 + * Unleashes the power of live node lists
  1.8568 + *
  1.8569 + * @param {Object} doc The document object of the context where to check
  1.8570 + * @param {String} tagName Upper cased tag name
  1.8571 + * @example
  1.8572 + *    wysihtml.dom.hasElementWithTagName(document, "IMG");
  1.8573 + */
  1.8574 +wysihtml.dom.hasElementWithTagName = (function() {
  1.8575 +  var LIVE_CACHE          = {},
  1.8576 +      DOCUMENT_IDENTIFIER = 1;
  1.8577 +
  1.8578 +  function _getDocumentIdentifier(doc) {
  1.8579 +    return doc._wysihtml_identifier || (doc._wysihtml_identifier = DOCUMENT_IDENTIFIER++);
  1.8580 +  }
  1.8581 +
  1.8582 +  return function(doc, tagName) {
  1.8583 +    var key         = _getDocumentIdentifier(doc) + ":" + tagName,
  1.8584 +        cacheEntry  = LIVE_CACHE[key];
  1.8585 +    if (!cacheEntry) {
  1.8586 +      cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tagName);
  1.8587 +    }
  1.8588 +
  1.8589 +    return cacheEntry.length > 0;
  1.8590 +  };
  1.8591 +})();
  1.8592 +
  1.8593 +wysihtml.dom.insert = function(elementToInsert) {
  1.8594 +  return {
  1.8595 +    after: function(element) {
  1.8596 +      element.parentNode.insertBefore(elementToInsert, element.nextSibling);
  1.8597 +    },
  1.8598 +
  1.8599 +    before: function(element) {
  1.8600 +      element.parentNode.insertBefore(elementToInsert, element);
  1.8601 +    },
  1.8602 +
  1.8603 +    into: function(element) {
  1.8604 +      element.appendChild(elementToInsert);
  1.8605 +    }
  1.8606 +  };
  1.8607 +};
  1.8608 +
  1.8609 +wysihtml.dom.insertCSS = function(rules) {
  1.8610 +  rules = rules.join("\n");
  1.8611 +
  1.8612 +  return {
  1.8613 +    into: function(doc) {
  1.8614 +      var styleElement = doc.createElement("style");
  1.8615 +      styleElement.type = "text/css";
  1.8616 +
  1.8617 +      if (styleElement.styleSheet) {
  1.8618 +        styleElement.styleSheet.cssText = rules;
  1.8619 +      } else {
  1.8620 +        styleElement.appendChild(doc.createTextNode(rules));
  1.8621 +      }
  1.8622 +
  1.8623 +      var link = doc.querySelector("head link");
  1.8624 +      if (link) {
  1.8625 +        link.parentNode.insertBefore(styleElement, link);
  1.8626 +        return;
  1.8627 +      } else {
  1.8628 +        var head = doc.querySelector("head");
  1.8629 +        if (head) {
  1.8630 +          head.appendChild(styleElement);
  1.8631 +        }
  1.8632 +      }
  1.8633 +    }
  1.8634 +  };
  1.8635 +};
  1.8636 +
  1.8637 +/**
  1.8638 +   * Check whether the given node is a proper loaded image
  1.8639 +   * FIXME: Returns undefined when unknown (Chrome, Safari)
  1.8640 +*/
  1.8641 +
  1.8642 +wysihtml.dom.isLoadedImage = function (node) {
  1.8643 +  try {
  1.8644 +    return node.complete && !node.mozMatchesSelector(":-moz-broken");
  1.8645 +  } catch(e) {
  1.8646 +    if (node.complete && node.readyState === "complete") {
  1.8647 +      return true;
  1.8648 +    }
  1.8649 +  }
  1.8650 +};
  1.8651 +
  1.8652 +// TODO: Refactor dom tree traversing here
  1.8653 +(function(wysihtml) {
  1.8654 +  wysihtml.dom.lineBreaks = function(node) {
  1.8655 +
  1.8656 +    function _isLineBreak(n) {
  1.8657 +      return n.nodeName === "BR";
  1.8658 +    }
  1.8659 +
  1.8660 +    /**
  1.8661 +     * Checks whether the elment causes a visual line break
  1.8662 +     * (<br> or block elements)
  1.8663 +     */
  1.8664 +    function _isLineBreakOrBlockElement(element) {
  1.8665 +      if (_isLineBreak(element)) {
  1.8666 +        return true;
  1.8667 +      }
  1.8668 +
  1.8669 +      if (wysihtml.dom.getStyle("display").from(element) === "block") {
  1.8670 +        return true;
  1.8671 +      }
  1.8672 +
  1.8673 +      return false;
  1.8674 +    }
  1.8675 +
  1.8676 +    return {
  1.8677 +
  1.8678 +      /* wysihtml.dom.lineBreaks(element).add();
  1.8679 +       *
  1.8680 +       * Adds line breaks before and after the given node if the previous and next siblings
  1.8681 +       * aren't already causing a visual line break (block element or <br>)
  1.8682 +       */
  1.8683 +      add: function(options) {
  1.8684 +        var doc             = node.ownerDocument,
  1.8685 +          nextSibling     = wysihtml.dom.domNode(node).next({ignoreBlankTexts: true}),
  1.8686 +          previousSibling = wysihtml.dom.domNode(node).prev({ignoreBlankTexts: true});
  1.8687 +
  1.8688 +        if (nextSibling && !_isLineBreakOrBlockElement(nextSibling)) {
  1.8689 +          wysihtml.dom.insert(doc.createElement("br")).after(node);
  1.8690 +        }
  1.8691 +        if (previousSibling && !_isLineBreakOrBlockElement(previousSibling)) {
  1.8692 +          wysihtml.dom.insert(doc.createElement("br")).before(node);
  1.8693 +        }
  1.8694 +      },
  1.8695 +
  1.8696 +      /* wysihtml.dom.lineBreaks(element).remove();
  1.8697 +       *
  1.8698 +       * Removes line breaks before and after the given node
  1.8699 +       */
  1.8700 +      remove: function(options) {
  1.8701 +        var nextSibling     = wysihtml.dom.domNode(node).next({ignoreBlankTexts: true}),
  1.8702 +            previousSibling = wysihtml.dom.domNode(node).prev({ignoreBlankTexts: true});
  1.8703 +
  1.8704 +        if (nextSibling && _isLineBreak(nextSibling)) {
  1.8705 +          nextSibling.parentNode.removeChild(nextSibling);
  1.8706 +        }
  1.8707 +        if (previousSibling && _isLineBreak(previousSibling)) {
  1.8708 +          previousSibling.parentNode.removeChild(previousSibling);
  1.8709 +        }
  1.8710 +      }
  1.8711 +    };
  1.8712 +  };
  1.8713 +})(wysihtml);
  1.8714 +/**
  1.8715 + * Method to set dom events
  1.8716 + *
  1.8717 + * @example
  1.8718 + *    wysihtml.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... });
  1.8719 + */
  1.8720 +wysihtml.dom.observe = function(element, eventNames, handler) {
  1.8721 +  eventNames = typeof(eventNames) === "string" ? [eventNames] : eventNames;
  1.8722 +
  1.8723 +  var handlerWrapper,
  1.8724 +      eventName,
  1.8725 +      i       = 0,
  1.8726 +      length  = eventNames.length;
  1.8727 +
  1.8728 +  for (; i<length; i++) {
  1.8729 +    eventName = eventNames[i];
  1.8730 +    if (element.addEventListener) {
  1.8731 +      element.addEventListener(eventName, handler, false);
  1.8732 +    } else {
  1.8733 +      handlerWrapper = function(event) {
  1.8734 +        if (!("target" in event)) {
  1.8735 +          event.target = event.srcElement;
  1.8736 +        }
  1.8737 +        event.preventDefault = event.preventDefault || function() {
  1.8738 +          this.returnValue = false;
  1.8739 +        };
  1.8740 +        event.stopPropagation = event.stopPropagation || function() {
  1.8741 +          this.cancelBubble = true;
  1.8742 +        };
  1.8743 +        handler.call(element, event);
  1.8744 +      };
  1.8745 +      element.attachEvent("on" + eventName, handlerWrapper);
  1.8746 +    }
  1.8747 +  }
  1.8748 +
  1.8749 +  return {
  1.8750 +    stop: function() {
  1.8751 +      var eventName,
  1.8752 +          i       = 0,
  1.8753 +          length  = eventNames.length;
  1.8754 +      for (; i<length; i++) {
  1.8755 +        eventName = eventNames[i];
  1.8756 +        if (element.removeEventListener) {
  1.8757 +          element.removeEventListener(eventName, handler, false);
  1.8758 +        } else {
  1.8759 +          element.detachEvent("on" + eventName, handlerWrapper);
  1.8760 +        }
  1.8761 +      }
  1.8762 +    }
  1.8763 +  };
  1.8764 +};
  1.8765 +
  1.8766 +/**
  1.8767 + * HTML Sanitizer
  1.8768 + * Rewrites the HTML based on given rules
  1.8769 + *
  1.8770 + * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized
  1.8771 + * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will
  1.8772 + *    be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the
  1.8773 + *    desired substitution.
  1.8774 + * @param {Object} context Document object in which to parse the html, needed to sandbox the parsing
  1.8775 + *
  1.8776 + * @return {Element|String} Depends on the elementOrHtml parameter. When html then the sanitized html as string elsewise the element.
  1.8777 + *
  1.8778 + * @example
  1.8779 + *    var userHTML = '<div id="foo" onclick="alert(1);"><p><font color="red">foo</font><script>alert(1);</script></p></div>';
  1.8780 + *    wysihtml.dom.parse(userHTML, {
  1.8781 + *      tags {
  1.8782 + *        p:      "div",      // Rename p tags to div tags
  1.8783 + *        font:   "span"      // Rename font tags to span tags
  1.8784 + *        div:    true,       // Keep them, also possible (same result when passing: "div" or true)
  1.8785 + *        script: undefined   // Remove script elements
  1.8786 + *      }
  1.8787 + *    });
  1.8788 + *    // => <div><div><span>foo bar</span></div></div>
  1.8789 + *
  1.8790 + *    var userHTML = '<table><tbody><tr><td>I'm a table!</td></tr></tbody></table>';
  1.8791 + *    wysihtml.dom.parse(userHTML);
  1.8792 + *    // => '<span><span><span><span>I'm a table!</span></span></span></span>'
  1.8793 + *
  1.8794 + *    var userHTML = '<div>foobar<br>foobar</div>';
  1.8795 + *    wysihtml.dom.parse(userHTML, {
  1.8796 + *      tags: {
  1.8797 + *        div: undefined,
  1.8798 + *        br:  true
  1.8799 + *      }
  1.8800 + *    });
  1.8801 + *    // => ''
  1.8802 + *
  1.8803 + *    var userHTML = '<div class="red">foo</div><div class="pink">bar</div>';
  1.8804 + *    wysihtml.dom.parse(userHTML, {
  1.8805 + *      classes: {
  1.8806 + *        red:    1,
  1.8807 + *        green:  1
  1.8808 + *      },
  1.8809 + *      tags: {
  1.8810 + *        div: {
  1.8811 + *          rename_tag:     "p"
  1.8812 + *        }
  1.8813 + *      }
  1.8814 + *    });
  1.8815 + *    // => '<p class="red">foo</p><p>bar</p>'
  1.8816 + */
  1.8817 +
  1.8818 +wysihtml.dom.parse = function(elementOrHtml_current, config_current) {
  1.8819 +  /* TODO: Currently escaped module pattern as otherwise folloowing default swill be shared among multiple editors.
  1.8820 +   * Refactor whole code as this method while workind is kind of awkward too */
  1.8821 +
  1.8822 +  /**
  1.8823 +   * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML
  1.8824 +   * new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the
  1.8825 +   * node isn't closed
  1.8826 +   *
  1.8827 +   * Therefore we've to use the browser's ordinary HTML parser invoked by setting innerHTML.
  1.8828 +   */
  1.8829 +  var NODE_TYPE_MAPPING = {
  1.8830 +        "1": _handleElement,
  1.8831 +        "3": _handleText,
  1.8832 +        "8": _handleComment
  1.8833 +      },
  1.8834 +      // Rename unknown tags to this
  1.8835 +      DEFAULT_NODE_NAME   = "span",
  1.8836 +      WHITE_SPACE_REG_EXP = /\s+/,
  1.8837 +      defaultRules        = { tags: {}, classes: {} },
  1.8838 +      currentRules        = {},
  1.8839 +      blockElements       = ["ADDRESS" ,"BLOCKQUOTE" ,"CENTER" ,"DIR" ,"DIV" ,"DL" ,"FIELDSET" ,
  1.8840 +                             "FORM", "H1" ,"H2" ,"H3" ,"H4" ,"H5" ,"H6" ,"ISINDEX" ,"MENU",
  1.8841 +                             "NOFRAMES", "NOSCRIPT" ,"OL" ,"P" ,"PRE","TABLE", "UL"];
  1.8842 +
  1.8843 +  /**
  1.8844 +   * Iterates over all childs of the element, recreates them, appends them into a document fragment
  1.8845 +   * which later replaces the entire body content
  1.8846 +   */
  1.8847 +   function parse(elementOrHtml, config) {
  1.8848 +    wysihtml.lang.object(currentRules).merge(defaultRules).merge(config.rules).get();
  1.8849 +
  1.8850 +    var context       = config.context || elementOrHtml.ownerDocument || document,
  1.8851 +        fragment      = context.createDocumentFragment(),
  1.8852 +        isString      = typeof(elementOrHtml) === "string",
  1.8853 +        clearInternals = false,
  1.8854 +        element,
  1.8855 +        newNode,
  1.8856 +        firstChild;
  1.8857 +
  1.8858 +    if (config.clearInternals === true) {
  1.8859 +      clearInternals = true;
  1.8860 +    }
  1.8861 +
  1.8862 +    if (isString) {
  1.8863 +      element = wysihtml.dom.getAsDom(elementOrHtml, context);
  1.8864 +    } else {
  1.8865 +      element = elementOrHtml;
  1.8866 +    }
  1.8867 +
  1.8868 +    if (currentRules.selectors) {
  1.8869 +      _applySelectorRules(element, currentRules.selectors);
  1.8870 +    }
  1.8871 +
  1.8872 +    while (element.firstChild) {
  1.8873 +      firstChild = element.firstChild;
  1.8874 +      newNode = _convert(firstChild, config.cleanUp, clearInternals, config.uneditableClass);
  1.8875 +      if (newNode) {
  1.8876 +        fragment.appendChild(newNode);
  1.8877 +      }
  1.8878 +      if (firstChild !== newNode) {
  1.8879 +        element.removeChild(firstChild);
  1.8880 +      }
  1.8881 +    }
  1.8882 +
  1.8883 +    if (config.unjoinNbsps) {
  1.8884 +      // replace joined non-breakable spaces with unjoined
  1.8885 +      var txtnodes = wysihtml.dom.getTextNodes(fragment);
  1.8886 +      for (var n = txtnodes.length; n--;) {
  1.8887 +        txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
  1.8888 +      }
  1.8889 +    }
  1.8890 +
  1.8891 +    // Clear element contents
  1.8892 +    element.innerHTML = "";
  1.8893 +
  1.8894 +    // Insert new DOM tree
  1.8895 +    element.appendChild(fragment);
  1.8896 +
  1.8897 +    return isString ? wysihtml.quirks.getCorrectInnerHTML(element) : element;
  1.8898 +  }
  1.8899 +
  1.8900 +  function _convert(oldNode, cleanUp, clearInternals, uneditableClass) {
  1.8901 +    var oldNodeType     = oldNode.nodeType,
  1.8902 +        oldChilds       = oldNode.childNodes,
  1.8903 +        oldChildsLength = oldChilds.length,
  1.8904 +        method          = NODE_TYPE_MAPPING[oldNodeType],
  1.8905 +        i               = 0,
  1.8906 +        fragment,
  1.8907 +        newNode,
  1.8908 +        newChild,
  1.8909 +        nodeDisplay;
  1.8910 +
  1.8911 +    // Passes directly elemets with uneditable class
  1.8912 +    if (uneditableClass && oldNodeType === 1 && wysihtml.dom.hasClass(oldNode, uneditableClass)) {
  1.8913 +        return oldNode;
  1.8914 +    }
  1.8915 +
  1.8916 +    newNode = method && method(oldNode, clearInternals);
  1.8917 +
  1.8918 +    // Remove or unwrap node in case of return value null or false
  1.8919 +    if (!newNode) {
  1.8920 +        if (newNode === false) {
  1.8921 +            // false defines that tag should be removed but contents should remain (unwrap)
  1.8922 +            fragment = oldNode.ownerDocument.createDocumentFragment();
  1.8923 +
  1.8924 +            for (i = oldChildsLength; i--;) {
  1.8925 +              if (oldChilds[i]) {
  1.8926 +                newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
  1.8927 +                if (newChild) {
  1.8928 +                  if (oldChilds[i] === newChild) {
  1.8929 +                    i--;
  1.8930 +                  }
  1.8931 +                  fragment.insertBefore(newChild, fragment.firstChild);
  1.8932 +                }
  1.8933 +              }
  1.8934 +            }
  1.8935 +
  1.8936 +            nodeDisplay = wysihtml.dom.getStyle("display").from(oldNode);
  1.8937 +
  1.8938 +            if (nodeDisplay === '') {
  1.8939 +              // Handle display style when element not in dom
  1.8940 +              nodeDisplay = wysihtml.lang.array(blockElements).contains(oldNode.tagName) ? "block" : "";
  1.8941 +            }
  1.8942 +            if (wysihtml.lang.array(["block", "flex", "table"]).contains(nodeDisplay)) {
  1.8943 +              fragment.appendChild(oldNode.ownerDocument.createElement("br"));
  1.8944 +            }
  1.8945 +
  1.8946 +            // TODO: try to minimize surplus spaces
  1.8947 +            if (wysihtml.lang.array([
  1.8948 +                "div", "pre", "p",
  1.8949 +                "table", "td", "th",
  1.8950 +                "ul", "ol", "li",
  1.8951 +                "dd", "dl",
  1.8952 +                "footer", "header", "section",
  1.8953 +                "h1", "h2", "h3", "h4", "h5", "h6"
  1.8954 +            ]).contains(oldNode.nodeName.toLowerCase()) && oldNode.parentNode.lastChild !== oldNode) {
  1.8955 +                // add space at first when unwraping non-textflow elements
  1.8956 +                if (!oldNode.nextSibling || oldNode.nextSibling.nodeType !== 3 || !(/^\s/).test(oldNode.nextSibling.nodeValue)) {
  1.8957 +                  fragment.appendChild(oldNode.ownerDocument.createTextNode(" "));
  1.8958 +                }
  1.8959 +            }
  1.8960 +
  1.8961 +            if (fragment.normalize) {
  1.8962 +              fragment.normalize();
  1.8963 +            }
  1.8964 +            return fragment;
  1.8965 +        } else {
  1.8966 +          // Remove
  1.8967 +          return null;
  1.8968 +        }
  1.8969 +    }
  1.8970 +
  1.8971 +    // Converts all childnodes
  1.8972 +    for (i=0; i<oldChildsLength; i++) {
  1.8973 +      if (oldChilds[i]) {
  1.8974 +        newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
  1.8975 +        if (newChild) {
  1.8976 +          if (oldChilds[i] === newChild) {
  1.8977 +            i--;
  1.8978 +          }
  1.8979 +          newNode.appendChild(newChild);
  1.8980 +        }
  1.8981 +      }
  1.8982 +    }
  1.8983 +
  1.8984 +    // Cleanup senseless <span> elements
  1.8985 +    if (cleanUp &&
  1.8986 +        newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME &&
  1.8987 +        (!newNode.childNodes.length ||
  1.8988 +         ((/^\s*$/gi).test(newNode.innerHTML) && (clearInternals || (oldNode.className !== "_wysihtml-temp-placeholder" && oldNode.className !== "rangySelectionBoundary"))) ||
  1.8989 +         !newNode.attributes.length)
  1.8990 +        ) {
  1.8991 +      fragment = newNode.ownerDocument.createDocumentFragment();
  1.8992 +      while (newNode.firstChild) {
  1.8993 +        fragment.appendChild(newNode.firstChild);
  1.8994 +      }
  1.8995 +      if (fragment.normalize) {
  1.8996 +        fragment.normalize();
  1.8997 +      }
  1.8998 +      return fragment;
  1.8999 +    }
  1.9000 +
  1.9001 +    if (newNode.normalize) {
  1.9002 +      newNode.normalize();
  1.9003 +    }
  1.9004 +    return newNode;
  1.9005 +  }
  1.9006 +
  1.9007 +  function _applySelectorRules (element, selectorRules) {
  1.9008 +    var sel, method, els;
  1.9009 +
  1.9010 +    for (sel in selectorRules) {
  1.9011 +      if (selectorRules.hasOwnProperty(sel)) {
  1.9012 +        if (wysihtml.lang.object(selectorRules[sel]).isFunction()) {
  1.9013 +          method = selectorRules[sel];
  1.9014 +        } else if (typeof(selectorRules[sel]) === "string" && elementHandlingMethods[selectorRules[sel]]) {
  1.9015 +          method = elementHandlingMethods[selectorRules[sel]];
  1.9016 +        }
  1.9017 +        els = element.querySelectorAll(sel);
  1.9018 +        for (var i = els.length; i--;) {
  1.9019 +          method(els[i]);
  1.9020 +        }
  1.9021 +      }
  1.9022 +    }
  1.9023 +  }
  1.9024 +
  1.9025 +  function _handleElement(oldNode, clearInternals) {
  1.9026 +    var rule,
  1.9027 +        newNode,
  1.9028 +        tagRules    = currentRules.tags,
  1.9029 +        nodeName    = oldNode.nodeName.toLowerCase(),
  1.9030 +        scopeName   = oldNode.scopeName,
  1.9031 +        renameTag;
  1.9032 +
  1.9033 +    /**
  1.9034 +     * We already parsed that element
  1.9035 +     * ignore it! (yes, this sometimes happens in IE8 when the html is invalid)
  1.9036 +     */
  1.9037 +    if (oldNode._wysihtml) {
  1.9038 +      return null;
  1.9039 +    }
  1.9040 +    oldNode._wysihtml = 1;
  1.9041 +
  1.9042 +    if (oldNode.className === "wysihtml-temp") {
  1.9043 +      return null;
  1.9044 +    }
  1.9045 +
  1.9046 +    /**
  1.9047 +     * IE is the only browser who doesn't include the namespace in the
  1.9048 +     * nodeName, that's why we have to prepend it by ourselves
  1.9049 +     * scopeName is a proprietary IE feature
  1.9050 +     * read more here http://msdn.microsoft.com/en-us/library/ms534388(v=vs.85).aspx
  1.9051 +     */
  1.9052 +    if (scopeName && scopeName != "HTML") {
  1.9053 +      nodeName = scopeName + ":" + nodeName;
  1.9054 +    }
  1.9055 +    /**
  1.9056 +     * Repair node
  1.9057 +     * IE is a bit bitchy when it comes to invalid nested markup which includes unclosed tags
  1.9058 +     * 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
  1.9059 +     */
  1.9060 +    if ("outerHTML" in oldNode) {
  1.9061 +      if (!wysihtml.browser.autoClosesUnclosedTags() &&
  1.9062 +          oldNode.nodeName === "P" &&
  1.9063 +          oldNode.outerHTML.slice(-4).toLowerCase() !== "</p>") {
  1.9064 +        nodeName = "div";
  1.9065 +      }
  1.9066 +    }
  1.9067 +
  1.9068 +    if (nodeName in tagRules) {
  1.9069 +      rule = tagRules[nodeName];
  1.9070 +      if (!rule || rule.remove) {
  1.9071 +        return null;
  1.9072 +      } else if (rule.unwrap) {
  1.9073 +        return false;
  1.9074 +      }
  1.9075 +      rule = typeof(rule) === "string" ? { rename_tag: rule } : rule;
  1.9076 +    } else if (oldNode.firstChild) {
  1.9077 +      rule = { rename_tag: DEFAULT_NODE_NAME };
  1.9078 +    } else {
  1.9079 +      // Remove empty unknown elements
  1.9080 +      return null;
  1.9081 +    }
  1.9082 +
  1.9083 +    // tests if type condition is met or node should be removed/unwrapped/renamed
  1.9084 +    if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type, clearInternals)) {
  1.9085 +      if (rule.remove_action) {
  1.9086 +        if (rule.remove_action === "unwrap") {
  1.9087 +          return false;
  1.9088 +        } else if (rule.remove_action === "rename") {
  1.9089 +          renameTag = rule.remove_action_rename_to || DEFAULT_NODE_NAME;
  1.9090 +        } else {
  1.9091 +          return null;
  1.9092 +        }
  1.9093 +      } else {
  1.9094 +        return null;
  1.9095 +      }
  1.9096 +    }
  1.9097 +
  1.9098 +    newNode = oldNode.ownerDocument.createElement(renameTag || rule.rename_tag || nodeName);
  1.9099 +    _handleAttributes(oldNode, newNode, rule, clearInternals);
  1.9100 +    _handleStyles(oldNode, newNode, rule);
  1.9101 +
  1.9102 +    oldNode = null;
  1.9103 +
  1.9104 +    if (newNode.normalize) { newNode.normalize(); }
  1.9105 +    return newNode;
  1.9106 +  }
  1.9107 +
  1.9108 +  function _testTypes(oldNode, rules, types, clearInternals) {
  1.9109 +    var definition, type;
  1.9110 +
  1.9111 +    // do not interfere with placeholder span or pasting caret position is not maintained
  1.9112 +    if (oldNode.nodeName === "SPAN" && !clearInternals && (oldNode.className === "_wysihtml-temp-placeholder" || oldNode.className === "rangySelectionBoundary")) {
  1.9113 +      return true;
  1.9114 +    }
  1.9115 +
  1.9116 +    for (type in types) {
  1.9117 +      if (types.hasOwnProperty(type) && rules.type_definitions && rules.type_definitions[type]) {
  1.9118 +        definition = rules.type_definitions[type];
  1.9119 +        if (_testType(oldNode, definition)) {
  1.9120 +          return true;
  1.9121 +        }
  1.9122 +      }
  1.9123 +    }
  1.9124 +    return false;
  1.9125 +  }
  1.9126 +
  1.9127 +  function array_contains(a, obj) {
  1.9128 +      var i = a.length;
  1.9129 +      while (i--) {
  1.9130 +         if (a[i] === obj) {
  1.9131 +             return true;
  1.9132 +         }
  1.9133 +      }
  1.9134 +      return false;
  1.9135 +  }
  1.9136 +
  1.9137 +  function _testType(oldNode, definition) {
  1.9138 +
  1.9139 +    var nodeClasses = oldNode.getAttribute("class"),
  1.9140 +        nodeStyles =  oldNode.getAttribute("style"),
  1.9141 +        classesLength, s, s_corrected, a, attr, currentClass, styleProp;
  1.9142 +
  1.9143 +    // test for methods
  1.9144 +    if (definition.methods) {
  1.9145 +      for (var m in definition.methods) {
  1.9146 +        if (definition.methods.hasOwnProperty(m) && typeCeckMethods[m]) {
  1.9147 +
  1.9148 +          if (typeCeckMethods[m](oldNode)) {
  1.9149 +            return true;
  1.9150 +          }
  1.9151 +        }
  1.9152 +      }
  1.9153 +    }
  1.9154 +
  1.9155 +    // test for classes, if one found return true
  1.9156 +    if (nodeClasses && definition.classes) {
  1.9157 +      nodeClasses = nodeClasses.replace(/^\s+/g, '').replace(/\s+$/g, '').split(WHITE_SPACE_REG_EXP);
  1.9158 +      classesLength = nodeClasses.length;
  1.9159 +      for (var i = 0; i < classesLength; i++) {
  1.9160 +        if (definition.classes[nodeClasses[i]]) {
  1.9161 +          return true;
  1.9162 +        }
  1.9163 +      }
  1.9164 +    }
  1.9165 +
  1.9166 +    // test for styles, if one found return true
  1.9167 +    if (nodeStyles && definition.styles) {
  1.9168 +
  1.9169 +      nodeStyles = nodeStyles.split(';');
  1.9170 +      for (s in definition.styles) {
  1.9171 +        if (definition.styles.hasOwnProperty(s)) {
  1.9172 +          for (var sp = nodeStyles.length; sp--;) {
  1.9173 +            styleProp = nodeStyles[sp].split(':');
  1.9174 +
  1.9175 +            if (styleProp[0].replace(/\s/g, '').toLowerCase() === s) {
  1.9176 +              if (definition.styles[s] === true || definition.styles[s] === 1 || wysihtml.lang.array(definition.styles[s]).contains(styleProp[1].replace(/\s/g, '').toLowerCase()) ) {
  1.9177 +                return true;
  1.9178 +              }
  1.9179 +            }
  1.9180 +          }
  1.9181 +        }
  1.9182 +      }
  1.9183 +    }
  1.9184 +
  1.9185 +    // test for attributes in general against regex match
  1.9186 +    if (definition.attrs) {
  1.9187 +        for (a in definition.attrs) {
  1.9188 +            if (definition.attrs.hasOwnProperty(a)) {
  1.9189 +                attr = wysihtml.dom.getAttribute(oldNode, a);
  1.9190 +                if (typeof(attr) === "string") {
  1.9191 +                    if (attr.search(definition.attrs[a]) > -1) {
  1.9192 +                        return true;
  1.9193 +                    }
  1.9194 +                }
  1.9195 +            }
  1.9196 +        }
  1.9197 +    }
  1.9198 +    return false;
  1.9199 +  }
  1.9200 +
  1.9201 +  function _handleStyles(oldNode, newNode, rule) {
  1.9202 +    var s, v;
  1.9203 +    if(rule && rule.keep_styles) {
  1.9204 +      for (s in rule.keep_styles) {
  1.9205 +        if (rule.keep_styles.hasOwnProperty(s)) {
  1.9206 +          v = (s === "float") ? oldNode.style.styleFloat || oldNode.style.cssFloat : oldNode.style[s];
  1.9207 +          // value can be regex and if so should match or style skipped
  1.9208 +          if (rule.keep_styles[s] instanceof RegExp && !(rule.keep_styles[s].test(v))) {
  1.9209 +            continue;
  1.9210 +          }
  1.9211 +          if (s === "float") {
  1.9212 +            // IE compability
  1.9213 +            newNode.style[(oldNode.style.styleFloat) ? 'styleFloat': 'cssFloat'] = v;
  1.9214 +           } else if (oldNode.style[s]) {
  1.9215 +             newNode.style[s] = v;
  1.9216 +           }
  1.9217 +        }
  1.9218 +      }
  1.9219 +    }
  1.9220 +  };
  1.9221 +
  1.9222 +  function _getAttributesBeginningWith(beginning, attributes) {
  1.9223 +    var returnAttributes = [];
  1.9224 +    for (var attr in attributes) {
  1.9225 +      if (attributes.hasOwnProperty(attr) && attr.indexOf(beginning) === 0) {
  1.9226 +        returnAttributes.push(attr);
  1.9227 +      }
  1.9228 +    }
  1.9229 +    return returnAttributes;
  1.9230 +  }
  1.9231 +
  1.9232 +  function _checkAttribute(attributeName, attributeValue, methodName, nodeName) {
  1.9233 +    var method = wysihtml.lang.object(methodName).isFunction() ? methodName : attributeCheckMethods[methodName],
  1.9234 +        newAttributeValue;
  1.9235 +
  1.9236 +    if (method) {
  1.9237 +      newAttributeValue = method(attributeValue, nodeName);
  1.9238 +      if (typeof(newAttributeValue) === "string") {
  1.9239 +        return newAttributeValue;
  1.9240 +      }
  1.9241 +    }
  1.9242 +
  1.9243 +    return false;
  1.9244 +  }
  1.9245 +
  1.9246 +  function _checkAttributes(oldNode, local_attributes) {
  1.9247 +    var globalAttributes  = wysihtml.lang.object(currentRules.attributes || {}).clone(), // global values for check/convert values of attributes
  1.9248 +        checkAttributes   = wysihtml.lang.object(globalAttributes).merge( wysihtml.lang.object(local_attributes || {}).clone()).get(),
  1.9249 +        attributes        = {},
  1.9250 +        oldAttributes     = wysihtml.dom.getAttributes(oldNode),
  1.9251 +        attributeName, newValue, matchingAttributes;
  1.9252 +
  1.9253 +    for (attributeName in checkAttributes) {
  1.9254 +      if ((/\*$/).test(attributeName)) {
  1.9255 +
  1.9256 +        matchingAttributes = _getAttributesBeginningWith(attributeName.slice(0,-1), oldAttributes);
  1.9257 +        for (var i = 0, imax = matchingAttributes.length; i < imax; i++) {
  1.9258 +
  1.9259 +          newValue = _checkAttribute(matchingAttributes[i], oldAttributes[matchingAttributes[i]], checkAttributes[attributeName], oldNode.nodeName);
  1.9260 +          if (newValue !== false) {
  1.9261 +            attributes[matchingAttributes[i]] = newValue;
  1.9262 +          }
  1.9263 +        }
  1.9264 +      } else {
  1.9265 +        newValue = _checkAttribute(attributeName, oldAttributes[attributeName], checkAttributes[attributeName], oldNode.nodeName);
  1.9266 +        if (newValue !== false) {
  1.9267 +          attributes[attributeName] = newValue;
  1.9268 +        }
  1.9269 +      }
  1.9270 +    }
  1.9271 +
  1.9272 +    return attributes;
  1.9273 +  }
  1.9274 +
  1.9275 +  // TODO: refactor. Too long to read
  1.9276 +  function _handleAttributes(oldNode, newNode, rule, clearInternals) {
  1.9277 +    var attributes          = {},                         // fresh new set of attributes to set on newNode
  1.9278 +        setClass            = rule.set_class,             // classes to set
  1.9279 +        addClass            = rule.add_class,             // add classes based on existing attributes
  1.9280 +        addStyle            = rule.add_style,             // add styles based on existing attributes
  1.9281 +        setAttributes       = rule.set_attributes,        // attributes to set on the current node
  1.9282 +        allowedClasses      = currentRules.classes,
  1.9283 +        i                   = 0,
  1.9284 +        classes             = [],
  1.9285 +        styles              = [],
  1.9286 +        newClasses          = [],
  1.9287 +        oldClasses          = [],
  1.9288 +        classesLength,
  1.9289 +        newClassesLength,
  1.9290 +        currentClass,
  1.9291 +        newClass,
  1.9292 +        attributeName,
  1.9293 +        method;
  1.9294 +
  1.9295 +    if (setAttributes) {
  1.9296 +      attributes = wysihtml.lang.object(setAttributes).clone();
  1.9297 +    }
  1.9298 +
  1.9299 +    // check/convert values of attributes
  1.9300 +    attributes = wysihtml.lang.object(attributes).merge(_checkAttributes(oldNode,  rule.check_attributes)).get();
  1.9301 +
  1.9302 +    if (setClass) {
  1.9303 +      classes.push(setClass);
  1.9304 +    }
  1.9305 +
  1.9306 +    if (addClass) {
  1.9307 +      for (attributeName in addClass) {
  1.9308 +        method = addClassMethods[addClass[attributeName]];
  1.9309 +        if (!method) {
  1.9310 +          continue;
  1.9311 +        }
  1.9312 +        newClass = method(wysihtml.dom.getAttribute(oldNode, attributeName));
  1.9313 +        if (typeof(newClass) === "string") {
  1.9314 +          classes.push(newClass);
  1.9315 +        }
  1.9316 +      }
  1.9317 +    }
  1.9318 +
  1.9319 +    if (addStyle) {
  1.9320 +      for (attributeName in addStyle) {
  1.9321 +        method = addStyleMethods[addStyle[attributeName]];
  1.9322 +        if (!method) {
  1.9323 +          continue;
  1.9324 +        }
  1.9325 +
  1.9326 +        newStyle = method(wysihtml.dom.getAttribute(oldNode, attributeName));
  1.9327 +        if (typeof(newStyle) === "string") {
  1.9328 +          styles.push(newStyle);
  1.9329 +        }
  1.9330 +      }
  1.9331 +    }
  1.9332 +
  1.9333 +
  1.9334 +    if (typeof(allowedClasses) === "string" && allowedClasses === "any") {
  1.9335 +      if (oldNode.getAttribute("class")) {
  1.9336 +        if (currentRules.classes_blacklist) {
  1.9337 +          oldClasses = oldNode.getAttribute("class");
  1.9338 +          if (oldClasses) {
  1.9339 +            classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
  1.9340 +          }
  1.9341 +
  1.9342 +          classesLength = classes.length;
  1.9343 +          for (; i<classesLength; i++) {
  1.9344 +            currentClass = classes[i];
  1.9345 +            if (!currentRules.classes_blacklist[currentClass]) {
  1.9346 +              newClasses.push(currentClass);
  1.9347 +            }
  1.9348 +          }
  1.9349 +
  1.9350 +          if (newClasses.length) {
  1.9351 +            attributes["class"] = wysihtml.lang.array(newClasses).unique().join(" ");
  1.9352 +          }
  1.9353 +
  1.9354 +        } else {
  1.9355 +          attributes["class"] = oldNode.getAttribute("class");
  1.9356 +        }
  1.9357 +      } else {
  1.9358 +        if(classes && classes.length > 0) {
  1.9359 +          attributes["class"] = wysihtml.lang.array(classes).unique().join(" ");
  1.9360 +        }
  1.9361 +      }
  1.9362 +    } else {
  1.9363 +      // make sure that wysihtml temp class doesn't get stripped out
  1.9364 +      if (!clearInternals) {
  1.9365 +        allowedClasses["_wysihtml-temp-placeholder"] = 1;
  1.9366 +        allowedClasses["_rangySelectionBoundary"] = 1;
  1.9367 +        allowedClasses["wysiwyg-tmp-selected-cell"] = 1;
  1.9368 +      }
  1.9369 +
  1.9370 +      // add old classes last
  1.9371 +      oldClasses = oldNode.getAttribute("class");
  1.9372 +      if (oldClasses) {
  1.9373 +        classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
  1.9374 +      }
  1.9375 +      classesLength = classes.length;
  1.9376 +      for (; i<classesLength; i++) {
  1.9377 +        currentClass = classes[i];
  1.9378 +        if (allowedClasses[currentClass]) {
  1.9379 +          newClasses.push(currentClass);
  1.9380 +        }
  1.9381 +      }
  1.9382 +
  1.9383 +      if (newClasses.length) {
  1.9384 +        attributes["class"] = wysihtml.lang.array(newClasses).unique().join(" ");
  1.9385 +      }
  1.9386 +    }
  1.9387 +
  1.9388 +    // remove table selection class if present
  1.9389 +    if (attributes["class"] && clearInternals) {
  1.9390 +      attributes["class"] = attributes["class"].replace("wysiwyg-tmp-selected-cell", "");
  1.9391 +      if ((/^\s*$/g).test(attributes["class"])) {
  1.9392 +        delete attributes["class"];
  1.9393 +      }
  1.9394 +    }
  1.9395 +
  1.9396 +    if (styles.length) {
  1.9397 +      attributes["style"] = wysihtml.lang.array(styles).unique().join(" ");
  1.9398 +    }
  1.9399 +
  1.9400 +    // set attributes on newNode
  1.9401 +    for (attributeName in attributes) {
  1.9402 +      // Setting attributes can cause a js error in IE under certain circumstances
  1.9403 +      // eg. on a <img> under https when it's new attribute value is non-https
  1.9404 +      // TODO: Investigate this further and check for smarter handling
  1.9405 +      try {
  1.9406 +        newNode.setAttribute(attributeName, attributes[attributeName]);
  1.9407 +      } catch(e) {}
  1.9408 +    }
  1.9409 +
  1.9410 +    // IE8 sometimes loses the width/height attributes when those are set before the "src"
  1.9411 +    // so we make sure to set them again
  1.9412 +    if (attributes.src) {
  1.9413 +      if (typeof(attributes.width) !== "undefined") {
  1.9414 +        newNode.setAttribute("width", attributes.width);
  1.9415 +      }
  1.9416 +      if (typeof(attributes.height) !== "undefined") {
  1.9417 +        newNode.setAttribute("height", attributes.height);
  1.9418 +      }
  1.9419 +    }
  1.9420 +  }
  1.9421 +
  1.9422 +  function _handleText(oldNode) {
  1.9423 +    var nextSibling = oldNode.nextSibling;
  1.9424 +    if (nextSibling && nextSibling.nodeType === wysihtml.TEXT_NODE) {
  1.9425 +      // Concatenate text nodes
  1.9426 +      nextSibling.data = oldNode.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "") + nextSibling.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "");
  1.9427 +    } else {
  1.9428 +      // \uFEFF = wysihtml.INVISIBLE_SPACE (used as a hack in certain rich text editing situations)
  1.9429 +      var data = oldNode.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "");
  1.9430 +      return oldNode.ownerDocument.createTextNode(data);
  1.9431 +    }
  1.9432 +  }
  1.9433 +
  1.9434 +  function _handleComment(oldNode) {
  1.9435 +    if (currentRules.comments) {
  1.9436 +      return oldNode.ownerDocument.createComment(oldNode.nodeValue);
  1.9437 +    }
  1.9438 +  }
  1.9439 +
  1.9440 +  // ------------ attribute checks ------------ \\
  1.9441 +  var attributeCheckMethods = {
  1.9442 +    url: (function() {
  1.9443 +      var REG_EXP = /^https?:\/\//i;
  1.9444 +      return function(attributeValue) {
  1.9445 +        if (!attributeValue || !attributeValue.match(REG_EXP)) {
  1.9446 +          return null;
  1.9447 +        }
  1.9448 +        return attributeValue.replace(REG_EXP, function(match) {
  1.9449 +          return match.toLowerCase();
  1.9450 +        });
  1.9451 +      };
  1.9452 +    })(),
  1.9453 +
  1.9454 +    src: (function() {
  1.9455 +      var REG_EXP = /^(\/|https?:\/\/)/i;
  1.9456 +      return function(attributeValue) {
  1.9457 +        if (!attributeValue || !attributeValue.match(REG_EXP)) {
  1.9458 +          return null;
  1.9459 +        }
  1.9460 +        return attributeValue.replace(REG_EXP, function(match) {
  1.9461 +          return match.toLowerCase();
  1.9462 +        });
  1.9463 +      };
  1.9464 +    })(),
  1.9465 +
  1.9466 +    href: (function() {
  1.9467 +      var REG_EXP = /^(#|\/|https?:\/\/|mailto:|tel:)/i;
  1.9468 +      return function(attributeValue) {
  1.9469 +        if (!attributeValue || !attributeValue.match(REG_EXP)) {
  1.9470 +          return null;
  1.9471 +        }
  1.9472 +        return attributeValue.replace(REG_EXP, function(match) {
  1.9473 +          return match.toLowerCase();
  1.9474 +        });
  1.9475 +      };
  1.9476 +    })(),
  1.9477 +
  1.9478 +    alt: (function() {
  1.9479 +      var REG_EXP = /[^ a-z0-9_\-]/gi;
  1.9480 +      return function(attributeValue, nodeName) {
  1.9481 +        if (!attributeValue) {
  1.9482 +          if (nodeName === "IMG") {
  1.9483 +            return "";
  1.9484 +          } else {
  1.9485 +            return null;
  1.9486 +          }
  1.9487 +        }
  1.9488 +        return attributeValue.replace(REG_EXP, "");
  1.9489 +      };
  1.9490 +    })(),
  1.9491 +
  1.9492 +    // Integers. Does not work with floating point numbers and units
  1.9493 +    numbers: (function() {
  1.9494 +      var REG_EXP = /\D/g;
  1.9495 +      return function(attributeValue) {
  1.9496 +        attributeValue = (attributeValue || "").replace(REG_EXP, "");
  1.9497 +        return attributeValue || null;
  1.9498 +      };
  1.9499 +    })(),
  1.9500 +
  1.9501 +    // Useful for with/height attributes where floating points and percentages are allowed
  1.9502 +    dimension: (function() {
  1.9503 +      var REG_EXP = /\D*(\d+)(\.\d+)?\s?(%)?\D*/;
  1.9504 +      return function(attributeValue) {
  1.9505 +        attributeValue = (attributeValue || "").replace(REG_EXP, "$1$2$3");
  1.9506 +        return attributeValue || null;
  1.9507 +      };
  1.9508 +    })(),
  1.9509 +
  1.9510 +    any: (function() {
  1.9511 +      return function(attributeValue) {
  1.9512 +        if (!attributeValue) {
  1.9513 +          return null;
  1.9514 +        }
  1.9515 +        return attributeValue;
  1.9516 +      };
  1.9517 +    })()
  1.9518 +  };
  1.9519 +
  1.9520 +  // ------------ style converter (converts an html attribute to a style) ------------ \\
  1.9521 +  var addStyleMethods = {
  1.9522 +    align_text: (function() {
  1.9523 +      var mapping = {
  1.9524 +        left:     "text-align: left;",
  1.9525 +        right:    "text-align: right;",
  1.9526 +        center:   "text-align: center;"
  1.9527 +      };
  1.9528 +      return function(attributeValue) {
  1.9529 +        return mapping[String(attributeValue).toLowerCase()];
  1.9530 +      };
  1.9531 +    })(),
  1.9532 +  };
  1.9533 +
  1.9534 +  // ------------ class converter (converts an html attribute to a class name) ------------ \\
  1.9535 +  var addClassMethods = {
  1.9536 +    align_img: (function() {
  1.9537 +      var mapping = {
  1.9538 +        left:   "wysiwyg-float-left",
  1.9539 +        right:  "wysiwyg-float-right"
  1.9540 +      };
  1.9541 +      return function(attributeValue) {
  1.9542 +        return mapping[String(attributeValue).toLowerCase()];
  1.9543 +      };
  1.9544 +    })(),
  1.9545 +
  1.9546 +    align_text: (function() {
  1.9547 +      var mapping = {
  1.9548 +        left:     "wysiwyg-text-align-left",
  1.9549 +        right:    "wysiwyg-text-align-right",
  1.9550 +        center:   "wysiwyg-text-align-center",
  1.9551 +        justify:  "wysiwyg-text-align-justify"
  1.9552 +      };
  1.9553 +      return function(attributeValue) {
  1.9554 +        return mapping[String(attributeValue).toLowerCase()];
  1.9555 +      };
  1.9556 +    })(),
  1.9557 +
  1.9558 +    clear_br: (function() {
  1.9559 +      var mapping = {
  1.9560 +        left:   "wysiwyg-clear-left",
  1.9561 +        right:  "wysiwyg-clear-right",
  1.9562 +        both:   "wysiwyg-clear-both",
  1.9563 +        all:    "wysiwyg-clear-both"
  1.9564 +      };
  1.9565 +      return function(attributeValue) {
  1.9566 +        return mapping[String(attributeValue).toLowerCase()];
  1.9567 +      };
  1.9568 +    })(),
  1.9569 +
  1.9570 +    size_font: (function() {
  1.9571 +      var mapping = {
  1.9572 +        "1": "wysiwyg-font-size-xx-small",
  1.9573 +        "2": "wysiwyg-font-size-small",
  1.9574 +        "3": "wysiwyg-font-size-medium",
  1.9575 +        "4": "wysiwyg-font-size-large",
  1.9576 +        "5": "wysiwyg-font-size-x-large",
  1.9577 +        "6": "wysiwyg-font-size-xx-large",
  1.9578 +        "7": "wysiwyg-font-size-xx-large",
  1.9579 +        "-": "wysiwyg-font-size-smaller",
  1.9580 +        "+": "wysiwyg-font-size-larger"
  1.9581 +      };
  1.9582 +      return function(attributeValue) {
  1.9583 +        return mapping[String(attributeValue).charAt(0)];
  1.9584 +      };
  1.9585 +    })()
  1.9586 +  };
  1.9587 +
  1.9588 +  // checks if element is possibly visible
  1.9589 +  var typeCeckMethods = {
  1.9590 +    has_visible_contet: (function() {
  1.9591 +      var txt,
  1.9592 +          isVisible = false,
  1.9593 +          visibleElements = ['img', 'video', 'picture', 'br', 'script', 'noscript',
  1.9594 +                             'style', 'table', 'iframe', 'object', 'embed', 'audio',
  1.9595 +                             'svg', 'input', 'button', 'select','textarea', 'canvas'];
  1.9596 +
  1.9597 +      return function(el) {
  1.9598 +
  1.9599 +        // has visible innertext. so is visible
  1.9600 +        txt = (el.innerText || el.textContent).replace(/\s/g, '');
  1.9601 +        if (txt && txt.length > 0) {
  1.9602 +          return true;
  1.9603 +        }
  1.9604 +
  1.9605 +        // matches list of visible dimensioned elements
  1.9606 +        for (var i = visibleElements.length; i--;) {
  1.9607 +          if (el.querySelector(visibleElements[i])) {
  1.9608 +            return true;
  1.9609 +          }
  1.9610 +        }
  1.9611 +
  1.9612 +        // try to measure dimesions in last resort. (can find only of elements in dom)
  1.9613 +        if (el.offsetWidth && el.offsetWidth > 0 && el.offsetHeight && el.offsetHeight > 0) {
  1.9614 +          return true;
  1.9615 +        }
  1.9616 +
  1.9617 +        return false;
  1.9618 +      };
  1.9619 +    })()
  1.9620 +  };
  1.9621 +
  1.9622 +  var elementHandlingMethods = {
  1.9623 +    unwrap: function (element) {
  1.9624 +      wysihtml.dom.unwrap(element);
  1.9625 +    },
  1.9626 +
  1.9627 +    remove: function (element) {
  1.9628 +      element.parentNode.removeChild(element);
  1.9629 +    }
  1.9630 +  };
  1.9631 +
  1.9632 +  return parse(elementOrHtml_current, config_current);
  1.9633 +};
  1.9634 +
  1.9635 +// does a selector query on element or array of elements
  1.9636 +wysihtml.dom.query = function(elements, query) {
  1.9637 +    var ret = [],
  1.9638 +        q;
  1.9639 +
  1.9640 +    if (elements.nodeType) {
  1.9641 +        elements = [elements];
  1.9642 +    }
  1.9643 +
  1.9644 +    for (var e = 0, len = elements.length; e < len; e++) {
  1.9645 +        q = elements[e].querySelectorAll(query);
  1.9646 +        if (q) {
  1.9647 +            for(var i = q.length; i--; ret.unshift(q[i]));
  1.9648 +        }
  1.9649 +    }
  1.9650 +    return ret;
  1.9651 +};
  1.9652 +
  1.9653 +/**
  1.9654 + * Checks for empty text node childs and removes them
  1.9655 + *
  1.9656 + * @param {Element} node The element in which to cleanup
  1.9657 + * @example
  1.9658 + *    wysihtml.dom.removeEmptyTextNodes(element);
  1.9659 + */
  1.9660 +wysihtml.dom.removeEmptyTextNodes = function(node) {
  1.9661 +  var childNode,
  1.9662 +      childNodes        = wysihtml.lang.array(node.childNodes).get(),
  1.9663 +      childNodesLength  = childNodes.length,
  1.9664 +      i                 = 0;
  1.9665 +
  1.9666 +  for (; i<childNodesLength; i++) {
  1.9667 +    childNode = childNodes[i];
  1.9668 +    if (childNode.nodeType === wysihtml.TEXT_NODE && (/^[\n\r]*$/).test(childNode.data)) {
  1.9669 +      childNode.parentNode.removeChild(childNode);
  1.9670 +    }
  1.9671 +  }
  1.9672 +};
  1.9673 +
  1.9674 +wysihtml.dom.removeInvisibleSpaces = function(node) {
  1.9675 +  var textNodes = wysihtml.dom.getTextNodes(node);
  1.9676 +  for (var n = textNodes.length; n--;) {
  1.9677 +    textNodes[n].nodeValue = textNodes[n].nodeValue.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "");
  1.9678 +  }
  1.9679 +};
  1.9680 +
  1.9681 +/**
  1.9682 + * Renames an element (eg. a <div> to a <p>) and keeps its childs
  1.9683 + *
  1.9684 + * @param {Element} element The list element which should be renamed
  1.9685 + * @param {Element} newNodeName The desired tag name
  1.9686 + *
  1.9687 + * @example
  1.9688 + *    <!-- Assume the following dom: -->
  1.9689 + *    <ul id="list">
  1.9690 + *      <li>eminem</li>
  1.9691 + *      <li>dr. dre</li>
  1.9692 + *      <li>50 Cent</li>
  1.9693 + *    </ul>
  1.9694 + *
  1.9695 + *    <script>
  1.9696 + *      wysihtml.dom.renameElement(document.getElementById("list"), "ol");
  1.9697 + *    </script>
  1.9698 + *
  1.9699 + *    <!-- Will result in: -->
  1.9700 + *    <ol>
  1.9701 + *      <li>eminem</li>
  1.9702 + *      <li>dr. dre</li>
  1.9703 + *      <li>50 Cent</li>
  1.9704 + *    </ol>
  1.9705 + */
  1.9706 +wysihtml.dom.renameElement = function(element, newNodeName) {
  1.9707 +  var newElement = element.ownerDocument.createElement(newNodeName),
  1.9708 +      firstChild;
  1.9709 +  while (firstChild = element.firstChild) {
  1.9710 +    newElement.appendChild(firstChild);
  1.9711 +  }
  1.9712 +  wysihtml.dom.copyAttributes(["align", "className"]).from(element).to(newElement);
  1.9713 +  
  1.9714 +  if (element.parentNode) {
  1.9715 +    element.parentNode.replaceChild(newElement, element);
  1.9716 +  }
  1.9717 +
  1.9718 +  return newElement;
  1.9719 +};
  1.9720 +
  1.9721 +/**
  1.9722 + * Takes an element, removes it and replaces it with it's childs
  1.9723 + *
  1.9724 + * @param {Object} node The node which to replace with it's child nodes
  1.9725 + * @example
  1.9726 + *    <div id="foo">
  1.9727 + *      <span>hello</span>
  1.9728 + *    </div>
  1.9729 + *    <script>
  1.9730 + *      // Remove #foo and replace with it's children
  1.9731 + *      wysihtml.dom.replaceWithChildNodes(document.getElementById("foo"));
  1.9732 + *    </script>
  1.9733 + */
  1.9734 +wysihtml.dom.replaceWithChildNodes = function(node) {
  1.9735 +  if (!node.parentNode) {
  1.9736 +    return;
  1.9737 +  }
  1.9738 +
  1.9739 +  while (node.firstChild) {
  1.9740 +    node.parentNode.insertBefore(node.firstChild, node);
  1.9741 +  }
  1.9742 +  node.parentNode.removeChild(node);
  1.9743 +};
  1.9744 +
  1.9745 +/**
  1.9746 + * Unwraps an unordered/ordered list
  1.9747 + *
  1.9748 + * @param {Element} element The list element which should be unwrapped
  1.9749 + *
  1.9750 + * @example
  1.9751 + *    <!-- Assume the following dom: -->
  1.9752 + *    <ul id="list">
  1.9753 + *      <li>eminem</li>
  1.9754 + *      <li>dr. dre</li>
  1.9755 + *      <li>50 Cent</li>
  1.9756 + *    </ul>
  1.9757 + *
  1.9758 + *    <script>
  1.9759 + *      wysihtml.dom.resolveList(document.getElementById("list"));
  1.9760 + *    </script>
  1.9761 + *
  1.9762 + *    <!-- Will result in: -->
  1.9763 + *    eminem<br>
  1.9764 + *    dr. dre<br>
  1.9765 + *    50 Cent<br>
  1.9766 + */
  1.9767 +(function(dom) {
  1.9768 +  function _isBlockElement(node) {
  1.9769 +    return dom.getStyle("display").from(node) === "block";
  1.9770 +  }
  1.9771 +
  1.9772 +  function _isLineBreak(node) {
  1.9773 +    return node.nodeName === "BR";
  1.9774 +  }
  1.9775 +
  1.9776 +  function _appendLineBreak(element) {
  1.9777 +    var lineBreak = element.ownerDocument.createElement("br");
  1.9778 +    element.appendChild(lineBreak);
  1.9779 +  }
  1.9780 +
  1.9781 +  function resolveList(list, useLineBreaks) {
  1.9782 +    if (!list.nodeName.match(/^(MENU|UL|OL)$/)) {
  1.9783 +      return;
  1.9784 +    }
  1.9785 +
  1.9786 +    var doc             = list.ownerDocument,
  1.9787 +        fragment        = doc.createDocumentFragment(),
  1.9788 +        previousSibling = wysihtml.dom.domNode(list).prev({ignoreBlankTexts: true}),
  1.9789 +        nextSibling = wysihtml.dom.domNode(list).next({ignoreBlankTexts: true}),
  1.9790 +        firstChild,
  1.9791 +        lastChild,
  1.9792 +        isLastChild,
  1.9793 +        shouldAppendLineBreak,
  1.9794 +        paragraph,
  1.9795 +        listItem,
  1.9796 +        lastListItem = list.lastElementChild || list.lastChild,
  1.9797 +        isLastItem;
  1.9798 +
  1.9799 +    if (useLineBreaks) {
  1.9800 +      // Insert line break if list is after a non-block element
  1.9801 +      if (previousSibling && !_isBlockElement(previousSibling) && !_isLineBreak(previousSibling)) {
  1.9802 +        _appendLineBreak(fragment);
  1.9803 +      }
  1.9804 +
  1.9805 +      while (listItem = (list.firstElementChild || list.firstChild)) {
  1.9806 +        lastChild = listItem.lastChild;
  1.9807 +        isLastItem = listItem === lastListItem;
  1.9808 +        while (firstChild = listItem.firstChild) {
  1.9809 +          isLastChild           = firstChild === lastChild;
  1.9810 +          // This needs to be done before appending it to the fragment, as it otherwise will lose style information
  1.9811 +          shouldAppendLineBreak = (!isLastItem || (nextSibling && !_isBlockElement(nextSibling))) && isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
  1.9812 +          fragment.appendChild(firstChild);
  1.9813 +          if (shouldAppendLineBreak) {
  1.9814 +            _appendLineBreak(fragment);
  1.9815 +          }
  1.9816 +        }
  1.9817 +
  1.9818 +        listItem.parentNode.removeChild(listItem);
  1.9819 +      }
  1.9820 +    } else {
  1.9821 +      while (listItem = (list.firstElementChild || list.firstChild)) {
  1.9822 +        if (listItem.querySelector && listItem.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6")) {
  1.9823 +          while (firstChild = listItem.firstChild) {
  1.9824 +            fragment.appendChild(firstChild);
  1.9825 +          }
  1.9826 +        } else {
  1.9827 +          paragraph = doc.createElement("p");
  1.9828 +          while (firstChild = listItem.firstChild) {
  1.9829 +            paragraph.appendChild(firstChild);
  1.9830 +          }
  1.9831 +          fragment.appendChild(paragraph);
  1.9832 +        }
  1.9833 +        listItem.parentNode.removeChild(listItem);
  1.9834 +      }
  1.9835 +    }
  1.9836 +
  1.9837 +    list.parentNode.replaceChild(fragment, list);
  1.9838 +  }
  1.9839 +
  1.9840 +  dom.resolveList = resolveList;
  1.9841 +})(wysihtml.dom);
  1.9842 +
  1.9843 +/**
  1.9844 + * Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way
  1.9845 + *
  1.9846 + * Browser Compatibility:
  1.9847 + *  - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted"
  1.9848 + *  - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...)
  1.9849 + *
  1.9850 + * Please note that this class can't benefit from the HTML5 sandbox attribute for the following reasons:
  1.9851 + *    - sandboxing doesn't work correctly with inlined content (src="javascript:'<html>...</html>'")
  1.9852 + *    - sandboxing of physical documents causes that the dom isn't accessible anymore from the outside (iframe.contentWindow, ...)
  1.9853 + *    - setting the "allow-same-origin" flag would fix that, but then still javascript and dom events refuse to fire
  1.9854 + *    - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe
  1.9855 + *      can do anything as if the sandbox attribute wasn't set
  1.9856 + *
  1.9857 + * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready
  1.9858 + * @param {Object} [config] Optional parameters
  1.9859 + *
  1.9860 + * @example
  1.9861 + *    new wysihtml.dom.Sandbox(function(sandbox) {
  1.9862 + *      sandbox.getWindow().document.body.innerHTML = '<img src=foo.gif onerror="alert(document.cookie)">';
  1.9863 + *    });
  1.9864 + */
  1.9865 +(function(wysihtml) {
  1.9866 +  var /**
  1.9867 +       * Default configuration
  1.9868 +       */
  1.9869 +      doc                 = document,
  1.9870 +      /**
  1.9871 +       * Properties to unset/protect on the window object
  1.9872 +       */
  1.9873 +      windowProperties    = [
  1.9874 +        "parent", "top", "opener", "frameElement", "frames",
  1.9875 +        "localStorage", "globalStorage", "sessionStorage", "indexedDB"
  1.9876 +      ],
  1.9877 +      /**
  1.9878 +       * Properties on the window object which are set to an empty function
  1.9879 +       */
  1.9880 +      windowProperties2   = [
  1.9881 +        "open", "close", "openDialog", "showModalDialog",
  1.9882 +        "alert", "confirm", "prompt",
  1.9883 +        "openDatabase", "postMessage",
  1.9884 +        "XMLHttpRequest", "XDomainRequest"
  1.9885 +      ],
  1.9886 +      /**
  1.9887 +       * Properties to unset/protect on the document object
  1.9888 +       */
  1.9889 +      documentProperties  = [
  1.9890 +        "referrer",
  1.9891 +        "write", "open", "close"
  1.9892 +      ];
  1.9893 +
  1.9894 +  wysihtml.dom.Sandbox = Base.extend(
  1.9895 +    /** @scope wysihtml.dom.Sandbox.prototype */ {
  1.9896 +
  1.9897 +    constructor: function(readyCallback, config) {
  1.9898 +      this.callback = readyCallback || wysihtml.EMPTY_FUNCTION;
  1.9899 +      this.config   = wysihtml.lang.object({}).merge(config).get();
  1.9900 +      if (!this.config.className) {
  1.9901 +        this.config.className = "wysihtml-sandbox";
  1.9902 +      }
  1.9903 +      this.editableArea   = this._createIframe();
  1.9904 +    },
  1.9905 +
  1.9906 +    insertInto: function(element) {
  1.9907 +      if (typeof(element) === "string") {
  1.9908 +        element = doc.getElementById(element);
  1.9909 +      }
  1.9910 +
  1.9911 +      element.appendChild(this.editableArea);
  1.9912 +    },
  1.9913 +
  1.9914 +    getIframe: function() {
  1.9915 +      return this.editableArea;
  1.9916 +    },
  1.9917 +
  1.9918 +    getWindow: function() {
  1.9919 +      this._readyError();
  1.9920 +    },
  1.9921 +
  1.9922 +    getDocument: function() {
  1.9923 +      this._readyError();
  1.9924 +    },
  1.9925 +
  1.9926 +    destroy: function() {
  1.9927 +      var iframe = this.getIframe();
  1.9928 +      iframe.parentNode.removeChild(iframe);
  1.9929 +    },
  1.9930 +
  1.9931 +    _readyError: function() {
  1.9932 +      throw new Error("wysihtml.Sandbox: Sandbox iframe isn't loaded yet");
  1.9933 +    },
  1.9934 +
  1.9935 +    /**
  1.9936 +     * Creates the sandbox iframe
  1.9937 +     *
  1.9938 +     * Some important notes:
  1.9939 +     *  - We can't use HTML5 sandbox for now:
  1.9940 +     *    setting it causes that the iframe's dom can't be accessed from the outside
  1.9941 +     *    Therefore we need to set the "allow-same-origin" flag which enables accessing the iframe's dom
  1.9942 +     *    But then there's another problem, DOM events (focus, blur, change, keypress, ...) aren't fired.
  1.9943 +     *    In order to make this happen we need to set the "allow-scripts" flag.
  1.9944 +     *    A combination of allow-scripts and allow-same-origin is almost the same as setting no sandbox attribute at all.
  1.9945 +     *  - Chrome & Safari, doesn't seem to support sandboxing correctly when the iframe's html is inlined (no physical document)
  1.9946 +     *  - IE needs to have the security="restricted" attribute set before the iframe is
  1.9947 +     *    inserted into the dom tree
  1.9948 +     *  - Believe it or not but in IE "security" in document.createElement("iframe") is false, even
  1.9949 +     *    though it supports it
  1.9950 +     *  - When an iframe has security="restricted", in IE eval() & execScript() don't work anymore
  1.9951 +     *  - IE doesn't fire the onload event when the content is inlined in the src attribute, therefore we rely
  1.9952 +     *    on the onreadystatechange event
  1.9953 +     */
  1.9954 +    _createIframe: function() {
  1.9955 +      var that   = this,
  1.9956 +          iframe = doc.createElement("iframe");
  1.9957 +      iframe.className = this.config.className;
  1.9958 +      wysihtml.dom.setAttributes({
  1.9959 +        "security":           "restricted",
  1.9960 +        "allowtransparency":  "true",
  1.9961 +        "frameborder":        0,
  1.9962 +        "width":              0,
  1.9963 +        "height":             0,
  1.9964 +        "marginwidth":        0,
  1.9965 +        "marginheight":       0
  1.9966 +      }).on(iframe);
  1.9967 +
  1.9968 +      // Setting the src like this prevents ssl warnings in IE6
  1.9969 +      if (wysihtml.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()) {
  1.9970 +        iframe.src = "javascript:'<html></html>'";
  1.9971 +      }
  1.9972 +
  1.9973 +      iframe.onload = function() {
  1.9974 +        iframe.onreadystatechange = iframe.onload = null;
  1.9975 +        that._onLoadIframe(iframe);
  1.9976 +      };
  1.9977 +
  1.9978 +      iframe.onreadystatechange = function() {
  1.9979 +        if (/loaded|complete/.test(iframe.readyState)) {
  1.9980 +          iframe.onreadystatechange = iframe.onload = null;
  1.9981 +          that._onLoadIframe(iframe);
  1.9982 +        }
  1.9983 +      };
  1.9984 +
  1.9985 +      return iframe;
  1.9986 +    },
  1.9987 +
  1.9988 +    /**
  1.9989 +     * Callback for when the iframe has finished loading
  1.9990 +     */
  1.9991 +    _onLoadIframe: function(iframe) {
  1.9992 +      // don't resume when the iframe got unloaded (eg. by removing it from the dom)
  1.9993 +      if (!wysihtml.dom.contains(doc.documentElement, iframe)) {
  1.9994 +        return;
  1.9995 +      }
  1.9996 +
  1.9997 +      var that           = this,
  1.9998 +          iframeWindow   = iframe.contentWindow,
  1.9999 +          iframeDocument = iframe.contentWindow.document,
 1.10000 +          charset        = doc.characterSet || doc.charset || "utf-8",
 1.10001 +          sandboxHtml    = this._getHtml({
 1.10002 +            charset:      charset,
 1.10003 +            stylesheets:  this.config.stylesheets
 1.10004 +          });
 1.10005 +
 1.10006 +      // Create the basic dom tree including proper DOCTYPE and charset
 1.10007 +      iframeDocument.open("text/html", "replace");
 1.10008 +      iframeDocument.write(sandboxHtml);
 1.10009 +      iframeDocument.close();
 1.10010 +
 1.10011 +      this.getWindow = function() { return iframe.contentWindow; };
 1.10012 +      this.getDocument = function() { return iframe.contentWindow.document; };
 1.10013 +
 1.10014 +      // Catch js errors and pass them to the parent's onerror event
 1.10015 +      // addEventListener("error") doesn't work properly in some browsers
 1.10016 +      // TODO: apparently this doesn't work in IE9!
 1.10017 +      iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
 1.10018 +        throw new Error("wysihtml.Sandbox: " + errorMessage, fileName, lineNumber);
 1.10019 +      };
 1.10020 +
 1.10021 +      if (!wysihtml.browser.supportsSandboxedIframes()) {
 1.10022 +        // Unset a bunch of sensitive variables
 1.10023 +        // Please note: This isn't hack safe!
 1.10024 +        // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information
 1.10025 +        // IE is secure though, which is the most important thing, since IE is the only browser, who
 1.10026 +        // takes over scripts & styles into contentEditable elements when copied from external websites
 1.10027 +        // or applications (Microsoft Word, ...)
 1.10028 +        var i, length;
 1.10029 +        for (i=0, length=windowProperties.length; i<length; i++) {
 1.10030 +          this._unset(iframeWindow, windowProperties[i]);
 1.10031 +        }
 1.10032 +        for (i=0, length=windowProperties2.length; i<length; i++) {
 1.10033 +          this._unset(iframeWindow, windowProperties2[i], wysihtml.EMPTY_FUNCTION);
 1.10034 +        }
 1.10035 +        for (i=0, length=documentProperties.length; i<length; i++) {
 1.10036 +          this._unset(iframeDocument, documentProperties[i]);
 1.10037 +        }
 1.10038 +        // This doesn't work in Safari 5
 1.10039 +        // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit
 1.10040 +        this._unset(iframeDocument, "cookie", "", true);
 1.10041 +      }
 1.10042 +
 1.10043 +      if (wysihtml.polyfills) {
 1.10044 +        wysihtml.polyfills(iframeWindow, iframeDocument).apply();
 1.10045 +      }
 1.10046 +
 1.10047 +      this.loaded = true;
 1.10048 +
 1.10049 +      // Trigger the callback
 1.10050 +      setTimeout(function() { that.callback(that); }, 0);
 1.10051 +    },
 1.10052 +
 1.10053 +    _getHtml: function(templateVars) {
 1.10054 +      var stylesheets = templateVars.stylesheets,
 1.10055 +          html        = "",
 1.10056 +          i           = 0,
 1.10057 +          length;
 1.10058 +      stylesheets = typeof(stylesheets) === "string" ? [stylesheets] : stylesheets;
 1.10059 +      if (stylesheets) {
 1.10060 +        length = stylesheets.length;
 1.10061 +        for (; i<length; i++) {
 1.10062 +          html += '<link rel="stylesheet" href="' + stylesheets[i] + '">';
 1.10063 +        }
 1.10064 +      }
 1.10065 +      templateVars.stylesheets = html;
 1.10066 +
 1.10067 +      return wysihtml.lang.string(
 1.10068 +        '<!DOCTYPE html><html><head>'
 1.10069 +        + '<meta charset="#{charset}">#{stylesheets}</head>'
 1.10070 +        + '<body></body></html>'
 1.10071 +      ).interpolate(templateVars);
 1.10072 +    },
 1.10073 +
 1.10074 +    /**
 1.10075 +     * Method to unset/override existing variables
 1.10076 +     * @example
 1.10077 +     *    // Make cookie unreadable and unwritable
 1.10078 +     *    this._unset(document, "cookie", "", true);
 1.10079 +     */
 1.10080 +    _unset: function(object, property, value, setter) {
 1.10081 +      try { object[property] = value; } catch(e) {}
 1.10082 +
 1.10083 +      try { object.__defineGetter__(property, function() { return value; }); } catch(e) {}
 1.10084 +      if (setter) {
 1.10085 +        try { object.__defineSetter__(property, function() {}); } catch(e) {}
 1.10086 +      }
 1.10087 +
 1.10088 +      if (!wysihtml.browser.crashesWhenDefineProperty(property)) {
 1.10089 +        try {
 1.10090 +          var config = {
 1.10091 +            get: function() { return value; }
 1.10092 +          };
 1.10093 +          if (setter) {
 1.10094 +            config.set = function() {};
 1.10095 +          }
 1.10096 +          Object.defineProperty(object, property, config);
 1.10097 +        } catch(e) {}
 1.10098 +      }
 1.10099 +    }
 1.10100 +  });
 1.10101 +})(wysihtml);
 1.10102 +
 1.10103 +(function() {
 1.10104 +  var mapping = {
 1.10105 +    "className": "class"
 1.10106 +  };
 1.10107 +  wysihtml.dom.setAttributes = function(attributes) {
 1.10108 +    return {
 1.10109 +      on: function(element) {
 1.10110 +        for (var i in attributes) {
 1.10111 +          element.setAttribute(mapping[i] || i, attributes[i]);
 1.10112 +        }
 1.10113 +      }
 1.10114 +    };
 1.10115 +  };
 1.10116 +})();
 1.10117 +
 1.10118 +wysihtml.dom.setStyles = function(styles) {
 1.10119 +  return {
 1.10120 +    on: function(element) {
 1.10121 +      var style = element.style;
 1.10122 +      if (typeof(styles) === "string") {
 1.10123 +        style.cssText += ";" + styles;
 1.10124 +        return;
 1.10125 +      }
 1.10126 +      for (var i in styles) {
 1.10127 +        if (i === "float") {
 1.10128 +          style.cssFloat = styles[i];
 1.10129 +          style.styleFloat = styles[i];
 1.10130 +        } else {
 1.10131 +          style[i] = styles[i];
 1.10132 +        }
 1.10133 +      }
 1.10134 +    }
 1.10135 +  };
 1.10136 +};
 1.10137 +
 1.10138 +/**
 1.10139 + * Simulate HTML5 placeholder attribute
 1.10140 + *
 1.10141 + * Needed since
 1.10142 + *    - div[contentEditable] elements don't support it
 1.10143 + *    - older browsers (such as IE8 and Firefox 3.6) don't support it at all
 1.10144 + *
 1.10145 + * @param {Object} parent Instance of main wysihtml.Editor class
 1.10146 + * @param {Element} view Instance of wysihtml.views.* class
 1.10147 + * @param {String} placeholderText
 1.10148 + *
 1.10149 + * @example
 1.10150 + *    wysihtml.dom.simulatePlaceholder(this, composer, "Foobar");
 1.10151 + */
 1.10152 +(function(dom) {
 1.10153 +  dom.simulatePlaceholder = function(editor, view, placeholderText, placeholderClassName) {
 1.10154 +    var CLASS_NAME = placeholderClassName || "wysihtml-placeholder",
 1.10155 +        unset = function() {
 1.10156 +          var composerIsVisible   = view.element.offsetWidth > 0 && view.element.offsetHeight > 0;
 1.10157 +          if (view.hasPlaceholderSet()) {
 1.10158 +            view.clear();
 1.10159 +            view.element.focus();
 1.10160 +            if (composerIsVisible ) {
 1.10161 +              setTimeout(function() {
 1.10162 +                var sel = view.selection.getSelection();
 1.10163 +                if (!sel.focusNode || !sel.anchorNode) {
 1.10164 +                  view.selection.selectNode(view.element.firstChild || view.element);
 1.10165 +                }
 1.10166 +              }, 0);
 1.10167 +            }
 1.10168 +          }
 1.10169 +          view.placeholderSet = false;
 1.10170 +          dom.removeClass(view.element, CLASS_NAME);
 1.10171 +        },
 1.10172 +        set = function() {
 1.10173 +          if (view.isEmpty() && !view.placeholderSet) {
 1.10174 +            view.placeholderSet = true;
 1.10175 +            view.setValue(placeholderText, false);
 1.10176 +            dom.addClass(view.element, CLASS_NAME);
 1.10177 +          }
 1.10178 +        };
 1.10179 +
 1.10180 +    editor
 1.10181 +      .on("set_placeholder", set)
 1.10182 +      .on("unset_placeholder", unset)
 1.10183 +      .on("focus:composer", unset)
 1.10184 +      .on("paste:composer", unset)
 1.10185 +      .on("blur:composer", set);
 1.10186 +
 1.10187 +    set();
 1.10188 +  };
 1.10189 +})(wysihtml.dom);
 1.10190 +
 1.10191 +(function(dom) {
 1.10192 +  var documentElement = document.documentElement;
 1.10193 +  if ("textContent" in documentElement) {
 1.10194 +    dom.setTextContent = function(element, text) {
 1.10195 +      element.textContent = text;
 1.10196 +    };
 1.10197 +
 1.10198 +    dom.getTextContent = function(element) {
 1.10199 +      return element.textContent;
 1.10200 +    };
 1.10201 +  } else if ("innerText" in documentElement) {
 1.10202 +    dom.setTextContent = function(element, text) {
 1.10203 +      element.innerText = text;
 1.10204 +    };
 1.10205 +
 1.10206 +    dom.getTextContent = function(element) {
 1.10207 +      return element.innerText;
 1.10208 +    };
 1.10209 +  } else {
 1.10210 +    dom.setTextContent = function(element, text) {
 1.10211 +      element.nodeValue = text;
 1.10212 +    };
 1.10213 +
 1.10214 +    dom.getTextContent = function(element) {
 1.10215 +      return element.nodeValue;
 1.10216 +    };
 1.10217 +  }
 1.10218 +})(wysihtml.dom);
 1.10219 +
 1.10220 +/* Unwraps element and returns list of childNodes that the node contained.
 1.10221 + *
 1.10222 + * Example:
 1.10223 + *    var childnodes = wysihtml.dom.unwrap(document.querySelector('.unwrap-me'));
 1.10224 +*/
 1.10225 +
 1.10226 +wysihtml.dom.unwrap = function(node) {
 1.10227 +  var children = [];
 1.10228 +  if (node.parentNode) {
 1.10229 +    while (node.lastChild) {
 1.10230 +      children.unshift(node.lastChild);
 1.10231 +      wysihtml.dom.insert(node.lastChild).after(node);
 1.10232 +    }
 1.10233 +    node.parentNode.removeChild(node);
 1.10234 +  }
 1.10235 +  return children;
 1.10236 +};
 1.10237 +
 1.10238 +/**
 1.10239 + * Fix most common html formatting misbehaviors of browsers implementation when inserting
 1.10240 + * content via copy & paste contentEditable
 1.10241 + *
 1.10242 + * @author Christopher Blum
 1.10243 + */
 1.10244 +wysihtml.quirks.cleanPastedHTML = (function() {
 1.10245 +
 1.10246 +  var styleToRegex = function (styleStr) {
 1.10247 +    var trimmedStr = wysihtml.lang.string(styleStr).trim(),
 1.10248 +        escapedStr = trimmedStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
 1.10249 +
 1.10250 +    return new RegExp("^((?!^" + escapedStr + "$).)*$", "i");
 1.10251 +  };
 1.10252 +
 1.10253 +  var extendRulesWithStyleExceptions = function (rules, exceptStyles) {
 1.10254 +    var newRules = wysihtml.lang.object(rules).clone(true),
 1.10255 +        tag, style;
 1.10256 +
 1.10257 +    for (tag in newRules.tags) {
 1.10258 +
 1.10259 +      if (newRules.tags.hasOwnProperty(tag)) {
 1.10260 +        if (newRules.tags[tag].keep_styles) {
 1.10261 +          for (style in newRules.tags[tag].keep_styles) {
 1.10262 +            if (newRules.tags[tag].keep_styles.hasOwnProperty(style)) {
 1.10263 +              if (exceptStyles[style]) {
 1.10264 +                newRules.tags[tag].keep_styles[style] = styleToRegex(exceptStyles[style]);
 1.10265 +              }
 1.10266 +            }
 1.10267 +          }
 1.10268 +        }
 1.10269 +      }
 1.10270 +    }
 1.10271 +
 1.10272 +    return newRules;
 1.10273 +  };
 1.10274 +
 1.10275 +  var pickRuleset = function(ruleset, html) {
 1.10276 +    var pickedSet, defaultSet;
 1.10277 +
 1.10278 +    if (!ruleset) {
 1.10279 +      return null;
 1.10280 +    }
 1.10281 +
 1.10282 +    for (var i = 0, max = ruleset.length; i < max; i++) {
 1.10283 +      if (!ruleset[i].condition) {
 1.10284 +        defaultSet = ruleset[i].set;
 1.10285 +      }
 1.10286 +      if (ruleset[i].condition && ruleset[i].condition.test(html)) {
 1.10287 +        return ruleset[i].set;
 1.10288 +      }
 1.10289 +    }
 1.10290 +
 1.10291 +    return defaultSet;
 1.10292 +  };
 1.10293 +
 1.10294 +  return function(html, options) {
 1.10295 +    var exceptStyles = {
 1.10296 +          'color': wysihtml.dom.getStyle("color").from(options.referenceNode),
 1.10297 +          'fontSize': wysihtml.dom.getStyle("font-size").from(options.referenceNode)
 1.10298 +        },
 1.10299 +        rules = extendRulesWithStyleExceptions(pickRuleset(options.rules, html) || {}, exceptStyles),
 1.10300 +        newHtml;
 1.10301 +
 1.10302 +    newHtml = wysihtml.dom.parse(html, {
 1.10303 +      "rules": rules,
 1.10304 +      "cleanUp": true, // <span> elements, empty or without attributes, should be removed/replaced with their content
 1.10305 +      "context": options.referenceNode.ownerDocument,
 1.10306 +      "uneditableClass": options.uneditableClass,
 1.10307 +      "clearInternals" : true, // don't paste temprorary selection and other markings
 1.10308 +      "unjoinNbsps" : true
 1.10309 +    });
 1.10310 +
 1.10311 +    return newHtml;
 1.10312 +  };
 1.10313 +
 1.10314 +})();
 1.10315 +
 1.10316 +/**
 1.10317 + * IE and Opera leave an empty paragraph in the contentEditable element after clearing it
 1.10318 + *
 1.10319 + * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
 1.10320 + * @exaple
 1.10321 + *    wysihtml.quirks.ensureProperClearing(myContentEditableElement);
 1.10322 + */
 1.10323 +wysihtml.quirks.ensureProperClearing = (function() {
 1.10324 +  var clearIfNecessary = function() {
 1.10325 +    var element = this;
 1.10326 +    setTimeout(function() {
 1.10327 +      var innerHTML = element.innerHTML.toLowerCase();
 1.10328 +      if (innerHTML == "<p>&nbsp;</p>" ||
 1.10329 +          innerHTML == "<p>&nbsp;</p><p>&nbsp;</p>") {
 1.10330 +        element.innerHTML = "";
 1.10331 +      }
 1.10332 +    }, 0);
 1.10333 +  };
 1.10334 +
 1.10335 +  return function(composer) {
 1.10336 +    wysihtml.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
 1.10337 +  };
 1.10338 +})();
 1.10339 +
 1.10340 +// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
 1.10341 +//
 1.10342 +// In Firefox this:
 1.10343 +//      var d = document.createElement("div");
 1.10344 +//      d.innerHTML ='<a href="~"></a>';
 1.10345 +//      d.innerHTML;
 1.10346 +// will result in:
 1.10347 +//      <a href="%7E"></a>
 1.10348 +// which is wrong
 1.10349 +(function(wysihtml) {
 1.10350 +  var TILDE_ESCAPED = "%7E";
 1.10351 +  wysihtml.quirks.getCorrectInnerHTML = function(element) {
 1.10352 +    var innerHTML = element.innerHTML;
 1.10353 +    if (innerHTML.indexOf(TILDE_ESCAPED) === -1) {
 1.10354 +      return innerHTML;
 1.10355 +    }
 1.10356 +
 1.10357 +    var elementsWithTilde = element.querySelectorAll("[href*='~'], [src*='~']"),
 1.10358 +        url,
 1.10359 +        urlToSearch,
 1.10360 +        length,
 1.10361 +        i;
 1.10362 +    for (i=0, length=elementsWithTilde.length; i<length; i++) {
 1.10363 +      url         = elementsWithTilde[i].href || elementsWithTilde[i].src;
 1.10364 +      urlToSearch = wysihtml.lang.string(url).replace("~").by(TILDE_ESCAPED);
 1.10365 +      innerHTML   = wysihtml.lang.string(innerHTML).replace(urlToSearch).by(url);
 1.10366 +    }
 1.10367 +    return innerHTML;
 1.10368 +  };
 1.10369 +})(wysihtml);
 1.10370 +
 1.10371 +/**
 1.10372 + * Force rerendering of a given element
 1.10373 + * Needed to fix display misbehaviors of IE
 1.10374 + *
 1.10375 + * @param {Element} element The element object which needs to be rerendered
 1.10376 + * @example
 1.10377 + *    wysihtml.quirks.redraw(document.body);
 1.10378 + */
 1.10379 +(function(wysihtml) {
 1.10380 +  var CLASS_NAME = "wysihtml-quirks-redraw";
 1.10381 +
 1.10382 +  wysihtml.quirks.redraw = function(element) {
 1.10383 +    wysihtml.dom.addClass(element, CLASS_NAME);
 1.10384 +    wysihtml.dom.removeClass(element, CLASS_NAME);
 1.10385 +
 1.10386 +    // Following hack is needed for firefox to make sure that image resize handles are properly removed
 1.10387 +    try {
 1.10388 +      var doc = element.ownerDocument;
 1.10389 +      doc.execCommand("italic", false, null);
 1.10390 +      doc.execCommand("italic", false, null);
 1.10391 +    } catch(e) {}
 1.10392 +  };
 1.10393 +})(wysihtml);
 1.10394 +
 1.10395 +(function(wysihtml) {
 1.10396 +  
 1.10397 +  // List of supported color format parsing methods
 1.10398 +  // If radix is not defined 10 is expected as default
 1.10399 +  var colorParseMethods = {
 1.10400 +        rgba : {
 1.10401 +          regex: /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i,
 1.10402 +          name: "rgba"
 1.10403 +        },
 1.10404 +        rgb : {
 1.10405 +          regex: /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,
 1.10406 +          name: "rgb"
 1.10407 +        },
 1.10408 +        hex6 : {
 1.10409 +          regex: /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i,
 1.10410 +          name: "hex",
 1.10411 +          radix: 16
 1.10412 +        },
 1.10413 +        hex3 : {
 1.10414 +          regex: /^#([0-9a-f])([0-9a-f])([0-9a-f])/i,
 1.10415 +          name: "hex",
 1.10416 +          radix: 16
 1.10417 +        }
 1.10418 +      },
 1.10419 +      // Takes a style key name as an argument and makes a regex that can be used to the match key:value pair from style string
 1.10420 +      makeParamRegExp = function (p) {
 1.10421 +        return new RegExp("(^|\\s|;)" + p + "\\s*:\\s*[^;$]+", "gi");
 1.10422 +      };
 1.10423 +
 1.10424 +  // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns suitable parsing method for it
 1.10425 +  function getColorParseMethod (colorStr) {
 1.10426 +    var prop, colorTypeConf;
 1.10427 +
 1.10428 +    for (prop in colorParseMethods) {
 1.10429 +      if (!colorParseMethods.hasOwnProperty(prop)) { continue; }
 1.10430 +
 1.10431 +      colorTypeConf = colorParseMethods[prop];
 1.10432 +
 1.10433 +      if (colorTypeConf.regex.test(colorStr)) {
 1.10434 +        return colorTypeConf;
 1.10435 +      }
 1.10436 +    }
 1.10437 +  }
 1.10438 +
 1.10439 +  // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns the type of that color format "hex", "rgb", "rgba". 
 1.10440 +  function getColorFormat (colorStr) {
 1.10441 +    var type = getColorParseMethod(colorStr);
 1.10442 +
 1.10443 +    return type ? type.name : undefined;
 1.10444 +  }
 1.10445 +
 1.10446 +  // Public API functions for styleParser
 1.10447 +  wysihtml.quirks.styleParser = {
 1.10448 +
 1.10449 +    // Takes color string value as an argument and returns suitable parsing method for it
 1.10450 +    getColorParseMethod : getColorParseMethod,
 1.10451 +
 1.10452 +    // Takes color string value as an argument and returns the type of that color format "hex", "rgb", "rgba". 
 1.10453 +    getColorFormat : getColorFormat,
 1.10454 +    
 1.10455 +    /* Parses a color string to and array of [red, green, blue, alpha].
 1.10456 +     * paramName: optional argument to parse color value directly from style string parameter
 1.10457 +     *
 1.10458 +     * Examples:
 1.10459 +     *    var colorArray = wysihtml.quirks.styleParser.parseColor("#ABC");            // [170, 187, 204, 1]
 1.10460 +     *    var colorArray = wysihtml.quirks.styleParser.parseColor("#AABBCC");         // [170, 187, 204, 1]
 1.10461 +     *    var colorArray = wysihtml.quirks.styleParser.parseColor("rgb(1,2,3)");      // [1, 2, 3, 1]
 1.10462 +     *    var colorArray = wysihtml.quirks.styleParser.parseColor("rgba(1,2,3,0.5)"); // [1, 2, 3, 0.5]
 1.10463 +     *
 1.10464 +     *    var colorArray = wysihtml.quirks.styleParser.parseColor("background-color: #ABC; color: #000;", "background-color"); // [170, 187, 204, 1]
 1.10465 +     *    var colorArray = wysihtml.quirks.styleParser.parseColor("background-color: #ABC; color: #000;", "color");            // [0, 0, 0, 1]
 1.10466 +     */
 1.10467 +    parseColor : function (stylesStr, paramName) {
 1.10468 +      var paramsRegex, params, colorType, colorMatch, radix,
 1.10469 +          colorStr = stylesStr;
 1.10470 +
 1.10471 +      if (paramName) {
 1.10472 +        paramsRegex = makeParamRegExp(paramName);
 1.10473 +
 1.10474 +        if (!(params = stylesStr.match(paramsRegex))) { return false; }
 1.10475 +
 1.10476 +        params = params.pop().split(":")[1];
 1.10477 +        colorStr = wysihtml.lang.string(params).trim();
 1.10478 +      }
 1.10479 +
 1.10480 +      if (!(colorType = getColorParseMethod(colorStr))) { return false; }
 1.10481 +      if (!(colorMatch = colorStr.match(colorType.regex))) { return false; }
 1.10482 +
 1.10483 +      radix = colorType.radix || 10;
 1.10484 +
 1.10485 +      if (colorType === colorParseMethods.hex3) {
 1.10486 +        colorMatch.shift();
 1.10487 +        colorMatch.push(1);
 1.10488 +        return wysihtml.lang.array(colorMatch).map(function(d, idx) {
 1.10489 +          return (idx < 3) ? (parseInt(d, radix) * radix) + parseInt(d, radix): parseFloat(d);
 1.10490 +        });
 1.10491 +      }
 1.10492 +
 1.10493 +      colorMatch.shift();
 1.10494 +
 1.10495 +      if (!colorMatch[3]) {
 1.10496 +        colorMatch.push(1);
 1.10497 +      }
 1.10498 +
 1.10499 +      return wysihtml.lang.array(colorMatch).map(function(d, idx) {
 1.10500 +        return (idx < 3) ? parseInt(d, radix): parseFloat(d);
 1.10501 +      });
 1.10502 +    },
 1.10503 +
 1.10504 +    /* Takes rgba color array [r,g,b,a] as a value and formats it to color string with given format type
 1.10505 +     * If no format is given, rgba/rgb is returned based on alpha value
 1.10506 +     *
 1.10507 +     * Example:
 1.10508 +     *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "hash");  // "#AABBCC"
 1.10509 +     *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "hex");  // "AABBCC"
 1.10510 +     *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "csv");  // "170, 187, 204, 1"
 1.10511 +     *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "rgba");  // "rgba(170,187,204,1)"
 1.10512 +     *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "rgb");  // "rgb(170,187,204)"
 1.10513 +     *
 1.10514 +     *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 0.5]);  // "rgba(170,187,204,0.5)"
 1.10515 +     *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1]);  // "rgb(170,187,204)"
 1.10516 +     */
 1.10517 +    unparseColor: function(val, colorFormat) {
 1.10518 +      var hexRadix = 16;
 1.10519 +
 1.10520 +      if (colorFormat === "hex") {
 1.10521 +        return (val[0].toString(hexRadix) + val[1].toString(hexRadix) + val[2].toString(hexRadix)).toUpperCase();
 1.10522 +      } else if (colorFormat === "hash") {
 1.10523 +        return "#" + (val[0].toString(hexRadix) + val[1].toString(hexRadix) + val[2].toString(hexRadix)).toUpperCase();
 1.10524 +      } else if (colorFormat === "rgb") {
 1.10525 +        return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
 1.10526 +      } else if (colorFormat === "rgba") {
 1.10527 +        return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
 1.10528 +      } else if (colorFormat === "csv") {
 1.10529 +        return  val[0] + "," + val[1] + "," + val[2] + "," + val[3];
 1.10530 +      }
 1.10531 +
 1.10532 +      if (val[3] && val[3] !== 1) {
 1.10533 +        return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
 1.10534 +      } else {
 1.10535 +        return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
 1.10536 +      }
 1.10537 +    },
 1.10538 +
 1.10539 +    // Parses font size value from style string
 1.10540 +    parseFontSize: function(stylesStr) {
 1.10541 +      var params = stylesStr.match(makeParamRegExp("font-size"));
 1.10542 +      if (params) {
 1.10543 +        return wysihtml.lang.string(params[params.length - 1].split(":")[1]).trim();
 1.10544 +      }
 1.10545 +      return false;
 1.10546 +    }
 1.10547 +  };
 1.10548 +
 1.10549 +})(wysihtml);
 1.10550 +
 1.10551 +/**
 1.10552 + * Selection API
 1.10553 + *
 1.10554 + * @example
 1.10555 + *    var selection = new wysihtml.Selection(editor);
 1.10556 + */
 1.10557 +(function(wysihtml) {
 1.10558 +  var dom = wysihtml.dom;
 1.10559 +
 1.10560 +  function _getCumulativeOffsetTop(element) {
 1.10561 +    var top = 0;
 1.10562 +    if (element.parentNode) {
 1.10563 +      do {
 1.10564 +        top += element.offsetTop || 0;
 1.10565 +        element = element.offsetParent;
 1.10566 +      } while (element);
 1.10567 +    }
 1.10568 +    return top;
 1.10569 +  }
 1.10570 +
 1.10571 +  // Provides the depth of ``descendant`` relative to ``ancestor``
 1.10572 +  function getDepth(ancestor, descendant) {
 1.10573 +      var ret = 0;
 1.10574 +      while (descendant !== ancestor) {
 1.10575 +          ret++;
 1.10576 +          descendant = descendant.parentNode;
 1.10577 +          if (!descendant)
 1.10578 +              throw new Error("not a descendant of ancestor!");
 1.10579 +      }
 1.10580 +      return ret;
 1.10581 +  }
 1.10582 +
 1.10583 +  function getRangeNode(node, offset) {
 1.10584 +    if (node.nodeType === 3) {
 1.10585 +      return node;
 1.10586 +    } else {
 1.10587 +      return node.childNodes[offset] || node;
 1.10588 +    }
 1.10589 +  }
 1.10590 +
 1.10591 +  function getWebkitSelectionFixNode(container) {
 1.10592 +    var blankNode = document.createElement('span');
 1.10593 +
 1.10594 +    var placeholderRemover = function(event) {
 1.10595 +      // Self-destructs the caret and keeps the text inserted into it by user
 1.10596 +      var lastChild;
 1.10597 +
 1.10598 +      container.removeEventListener('mouseup', placeholderRemover);
 1.10599 +      container.removeEventListener('keydown', placeholderRemover);
 1.10600 +      container.removeEventListener('touchstart', placeholderRemover);
 1.10601 +      container.removeEventListener('focus', placeholderRemover);
 1.10602 +      container.removeEventListener('blur', placeholderRemover);
 1.10603 +      container.removeEventListener('paste', delayedPlaceholderRemover);
 1.10604 +      container.removeEventListener('drop', delayedPlaceholderRemover);
 1.10605 +      container.removeEventListener('beforepaste', delayedPlaceholderRemover);
 1.10606 +
 1.10607 +      if (blankNode && blankNode.parentNode) {
 1.10608 +        blankNode.parentNode.removeChild(blankNode);
 1.10609 +      }
 1.10610 +    },
 1.10611 +    delayedPlaceholderRemover = function (event) {
 1.10612 +      if (blankNode && blankNode.parentNode) {
 1.10613 +        setTimeout(placeholderRemover, 0);
 1.10614 +      }
 1.10615 +    };
 1.10616 +
 1.10617 +    blankNode.appendChild(container.ownerDocument.createTextNode(wysihtml.INVISIBLE_SPACE));
 1.10618 +    blankNode.className = '_wysihtml-temp-caret-fix';
 1.10619 +    blankNode.style.display = 'block';
 1.10620 +    blankNode.style.minWidth = '1px';
 1.10621 +    blankNode.style.height = '0px';
 1.10622 +
 1.10623 +    container.addEventListener('mouseup', placeholderRemover);
 1.10624 +    container.addEventListener('keydown', placeholderRemover);
 1.10625 +    container.addEventListener('touchstart', placeholderRemover);
 1.10626 +    container.addEventListener('focus', placeholderRemover);
 1.10627 +    container.addEventListener('blur', placeholderRemover);
 1.10628 +    container.addEventListener('paste', delayedPlaceholderRemover);
 1.10629 +    container.addEventListener('drop', delayedPlaceholderRemover);
 1.10630 +    container.addEventListener('beforepaste', delayedPlaceholderRemover);
 1.10631 +
 1.10632 +    return blankNode;
 1.10633 +  }
 1.10634 +
 1.10635 +  // Should fix the obtained ranges that cannot surrond contents normally to apply changes upon
 1.10636 +  // Being considerate to firefox that sets range start start out of span and end inside on doubleclick initiated selection
 1.10637 +  function expandRangeToSurround(range) {
 1.10638 +      if (range.canSurroundContents()) return;
 1.10639 +
 1.10640 +      var common = range.commonAncestorContainer,
 1.10641 +          start_depth = getDepth(common, range.startContainer),
 1.10642 +          end_depth = getDepth(common, range.endContainer);
 1.10643 +
 1.10644 +      while(!range.canSurroundContents()) {
 1.10645 +        // In the following branches, we cannot just decrement the depth variables because the setStartBefore/setEndAfter may move the start or end of the range more than one level relative to ``common``. So we need to recompute the depth.
 1.10646 +        if (start_depth > end_depth) {
 1.10647 +            range.setStartBefore(range.startContainer);
 1.10648 +            start_depth = getDepth(common, range.startContainer);
 1.10649 +        }
 1.10650 +        else {
 1.10651 +            range.setEndAfter(range.endContainer);
 1.10652 +            end_depth = getDepth(common, range.endContainer);
 1.10653 +        }
 1.10654 +      }
 1.10655 +  }
 1.10656 +
 1.10657 +  wysihtml.Selection = Base.extend(
 1.10658 +    /** @scope wysihtml.Selection.prototype */ {
 1.10659 +    constructor: function(editor, contain, unselectableClass) {
 1.10660 +      // Make sure that our external range library is initialized
 1.10661 +      rangy.init();
 1.10662 +
 1.10663 +      this.editor   = editor;
 1.10664 +      this.composer = editor.composer;
 1.10665 +      this.doc      = this.composer.doc;
 1.10666 +      this.win      = this.composer.win;
 1.10667 +      this.contain = contain;
 1.10668 +      this.unselectableClass = unselectableClass || false;
 1.10669 +    },
 1.10670 +
 1.10671 +    /**
 1.10672 +     * Get the current selection as a bookmark to be able to later restore it
 1.10673 +     *
 1.10674 +     * @return {Object} An object that represents the current selection
 1.10675 +     */
 1.10676 +    getBookmark: function() {
 1.10677 +      var range = this.getRange();
 1.10678 +      return range && range.cloneRange();
 1.10679 +    },
 1.10680 +
 1.10681 +    /**
 1.10682 +     * Restore a selection retrieved via wysihtml.Selection.prototype.getBookmark
 1.10683 +     *
 1.10684 +     * @param {Object} bookmark An object that represents the current selection
 1.10685 +     */
 1.10686 +    setBookmark: function(bookmark) {
 1.10687 +      if (!bookmark) {
 1.10688 +        return;
 1.10689 +      }
 1.10690 +
 1.10691 +      this.setSelection(bookmark);
 1.10692 +    },
 1.10693 +
 1.10694 +    /**
 1.10695 +     * Set the caret in front of the given node
 1.10696 +     *
 1.10697 +     * @param {Object} node The element or text node where to position the caret in front of
 1.10698 +     * @example
 1.10699 +     *    selection.setBefore(myElement);
 1.10700 +     */
 1.10701 +    setBefore: function(node) {
 1.10702 +      var range = rangy.createRange(this.doc);
 1.10703 +      range.setStartBefore(node);
 1.10704 +      range.setEndBefore(node);
 1.10705 +      return this.setSelection(range);
 1.10706 +    },
 1.10707 +
 1.10708 +    // Constructs a self removing whitespace (ain absolute positioned span) for placing selection caret when normal methods fail.
 1.10709 +    // Webkit has an issue with placing caret into places where there are no textnodes near by.
 1.10710 +    createTemporaryCaretSpaceAfter: function (node) {
 1.10711 +      var caretPlaceholder = this.doc.createElement('span'),
 1.10712 +          caretPlaceholderText = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE),
 1.10713 +          placeholderRemover = (function(event) {
 1.10714 +            // Self-destructs the caret and keeps the text inserted into it by user
 1.10715 +            var lastChild;
 1.10716 +
 1.10717 +            this.contain.removeEventListener('mouseup', placeholderRemover);
 1.10718 +            this.contain.removeEventListener('keydown', keyDownHandler);
 1.10719 +            this.contain.removeEventListener('touchstart', placeholderRemover);
 1.10720 +            this.contain.removeEventListener('focus', placeholderRemover);
 1.10721 +            this.contain.removeEventListener('blur', placeholderRemover);
 1.10722 +            this.contain.removeEventListener('paste', delayedPlaceholderRemover);
 1.10723 +            this.contain.removeEventListener('drop', delayedPlaceholderRemover);
 1.10724 +            this.contain.removeEventListener('beforepaste', delayedPlaceholderRemover);
 1.10725 +
 1.10726 +            // If user inserted sth it is in the placeholder and sgould be unwrapped and stripped of invisible whitespace hack
 1.10727 +            // Otherwise the wrapper can just be removed
 1.10728 +            if (caretPlaceholder && caretPlaceholder.parentNode) {
 1.10729 +              caretPlaceholder.innerHTML = caretPlaceholder.innerHTML.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "");
 1.10730 +              if ((/[^\s]+/).test(caretPlaceholder.innerHTML)) {
 1.10731 +                lastChild = caretPlaceholder.lastChild;
 1.10732 +                wysihtml.dom.unwrap(caretPlaceholder);
 1.10733 +                this.setAfter(lastChild);
 1.10734 +              } else {
 1.10735 +                caretPlaceholder.parentNode.removeChild(caretPlaceholder);
 1.10736 +              }
 1.10737 +
 1.10738 +            }
 1.10739 +          }).bind(this),
 1.10740 +          delayedPlaceholderRemover = function (event) {
 1.10741 +            if (caretPlaceholder && caretPlaceholder.parentNode) {
 1.10742 +              setTimeout(placeholderRemover, 0);
 1.10743 +            }
 1.10744 +          },
 1.10745 +          keyDownHandler = function(event) {
 1.10746 +            if (event.which !== 8 && event.which !== 91 && event.which !== 17 && (event.which !== 86 || (!event.ctrlKey && !event.metaKey))) {
 1.10747 +              placeholderRemover();
 1.10748 +            }
 1.10749 +          };
 1.10750 +
 1.10751 +      caretPlaceholder.className = '_wysihtml-temp-caret-fix';
 1.10752 +      caretPlaceholder.style.position = 'absolute';
 1.10753 +      caretPlaceholder.style.display = 'block';
 1.10754 +      caretPlaceholder.style.minWidth = '1px';
 1.10755 +      caretPlaceholder.style.zIndex = '99999';
 1.10756 +      caretPlaceholder.appendChild(caretPlaceholderText);
 1.10757 +
 1.10758 +      node.parentNode.insertBefore(caretPlaceholder, node.nextSibling);
 1.10759 +      this.setBefore(caretPlaceholderText);
 1.10760 +
 1.10761 +      // Remove the caret fix on any of the following events (some are delayed as content change happens after event)
 1.10762 +      this.contain.addEventListener('mouseup', placeholderRemover);
 1.10763 +      this.contain.addEventListener('keydown', keyDownHandler);
 1.10764 +      this.contain.addEventListener('touchstart', placeholderRemover);
 1.10765 +      this.contain.addEventListener('focus', placeholderRemover);
 1.10766 +      this.contain.addEventListener('blur', placeholderRemover);
 1.10767 +      this.contain.addEventListener('paste', delayedPlaceholderRemover);
 1.10768 +      this.contain.addEventListener('drop', delayedPlaceholderRemover);
 1.10769 +      this.contain.addEventListener('beforepaste', delayedPlaceholderRemover);
 1.10770 +
 1.10771 +      return caretPlaceholder;
 1.10772 +    },
 1.10773 +
 1.10774 +    /**
 1.10775 +     * Set the caret after the given node
 1.10776 +     *
 1.10777 +     * @param {Object} node The element or text node where to position the caret in front of
 1.10778 +     * @example
 1.10779 +     *    selection.setBefore(myElement);
 1.10780 +     * callback is an optional parameter accepting a function to execute when selection ahs been set
 1.10781 +     */
 1.10782 +    setAfter: function(node, notVisual, callback) {
 1.10783 +      var win = this.win,
 1.10784 +          range = rangy.createRange(this.doc),
 1.10785 +          fixWebkitSelection = function() {
 1.10786 +            // Webkit fails to add selection if there are no textnodes in that region
 1.10787 +            // (like an uneditable container at the end of content).
 1.10788 +            var parent = node.parentNode,
 1.10789 +                lastSibling = parent ? parent.childNodes[parent.childNodes.length - 1] : null;
 1.10790 +
 1.10791 +            if (!sel || (lastSibling === node && node.nodeType === 1 && win.getComputedStyle(node).display === "block")) {
 1.10792 +              if (notVisual) {
 1.10793 +                // If setAfter is used as internal between actions, self-removing caretPlaceholder has simpler implementation
 1.10794 +                // and remove itself in call stack end instead on user interaction
 1.10795 +                var caretPlaceholder = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE);
 1.10796 +                node.parentNode.insertBefore(caretPlaceholder, node.nextSibling);
 1.10797 +                this.selectNode(caretPlaceholder);
 1.10798 +                setTimeout(function() {
 1.10799 +                  if (caretPlaceholder && caretPlaceholder.parentNode) {
 1.10800 +                    caretPlaceholder.parentNode.removeChild(caretPlaceholder);
 1.10801 +                  }
 1.10802 +                }, 0);
 1.10803 +              } else {
 1.10804 +                this.createTemporaryCaretSpaceAfter(node);
 1.10805 +              }
 1.10806 +            }
 1.10807 +          }.bind(this),
 1.10808 +          sel;
 1.10809 +
 1.10810 +      range.setStartAfter(node);
 1.10811 +      range.setEndAfter(node);
 1.10812 +
 1.10813 +      // In IE contenteditable must be focused before we can set selection
 1.10814 +      // thus setting the focus if activeElement is not this composer
 1.10815 +      if (!document.activeElement || document.activeElement !== this.composer.element) {
 1.10816 +        var scrollPos = this.composer.getScrollPos();
 1.10817 +        this.composer.element.focus();
 1.10818 +        this.composer.setScrollPos(scrollPos);
 1.10819 +        setTimeout(function() {
 1.10820 +          sel = this.setSelection(range);
 1.10821 +          fixWebkitSelection();
 1.10822 +          if (callback) {
 1.10823 +            callback(sel);
 1.10824 +          }
 1.10825 +        }.bind(this), 0);
 1.10826 +      } else {
 1.10827 +        sel = this.setSelection(range);
 1.10828 +        fixWebkitSelection();
 1.10829 +        if (callback) {
 1.10830 +          callback(sel);
 1.10831 +        }
 1.10832 +      }
 1.10833 +    },
 1.10834 +
 1.10835 +    /**
 1.10836 +     * Ability to select/mark nodes
 1.10837 +     *
 1.10838 +     * @param {Element} node The node/element to select
 1.10839 +     * @example
 1.10840 +     *    selection.selectNode(document.getElementById("my-image"));
 1.10841 +     */
 1.10842 +    selectNode: function(node, avoidInvisibleSpace) {
 1.10843 +      var range           = rangy.createRange(this.doc),
 1.10844 +          isElement       = node.nodeType === wysihtml.ELEMENT_NODE,
 1.10845 +          canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"),
 1.10846 +          content         = isElement ? node.innerHTML : node.data,
 1.10847 +          isEmpty         = (content === "" || content === wysihtml.INVISIBLE_SPACE),
 1.10848 +          displayStyle    = dom.getStyle("display").from(node),
 1.10849 +          isBlockElement  = (displayStyle === "block" || displayStyle === "list-item");
 1.10850 +
 1.10851 +      if (isEmpty && isElement && canHaveHTML && !avoidInvisibleSpace) {
 1.10852 +        // Make sure that caret is visible in node by inserting a zero width no breaking space
 1.10853 +        try { node.innerHTML = wysihtml.INVISIBLE_SPACE; } catch(e) {}
 1.10854 +      }
 1.10855 +      if (canHaveHTML) {
 1.10856 +        range.selectNodeContents(node);
 1.10857 +      } else {
 1.10858 +        range.selectNode(node);
 1.10859 +      }
 1.10860 +
 1.10861 +      if (canHaveHTML && isEmpty && isElement) {
 1.10862 +        range.collapse(isBlockElement);
 1.10863 +      } else if (canHaveHTML && isEmpty) {
 1.10864 +        range.setStartAfter(node);
 1.10865 +        range.setEndAfter(node);
 1.10866 +      }
 1.10867 +
 1.10868 +      this.setSelection(range);
 1.10869 +    },
 1.10870 +
 1.10871 +    /**
 1.10872 +     * Get the node which contains the selection
 1.10873 +     *
 1.10874 +     * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange"
 1.10875 +     * @return {Object} The node that contains the caret
 1.10876 +     * @example
 1.10877 +     *    var nodeThatContainsCaret = selection.getSelectedNode();
 1.10878 +     */
 1.10879 +    getSelectedNode: function(controlRange) {
 1.10880 +      var selection,
 1.10881 +          range;
 1.10882 +
 1.10883 +      if (controlRange && this.doc.selection && this.doc.selection.type === "Control") {
 1.10884 +        range = this.doc.selection.createRange();
 1.10885 +        if (range && range.length) {
 1.10886 +          return range.item(0);
 1.10887 +        }
 1.10888 +      }
 1.10889 +
 1.10890 +      selection = this.getSelection(this.doc);
 1.10891 +      if (selection.focusNode === selection.anchorNode) {
 1.10892 +        return selection.focusNode;
 1.10893 +      } else {
 1.10894 +        range = this.getRange(this.doc);
 1.10895 +        return range ? range.commonAncestorContainer : this.doc.body;
 1.10896 +      }
 1.10897 +    },
 1.10898 +
 1.10899 +    fixSelBorders: function() {
 1.10900 +      var range = this.getRange();
 1.10901 +      expandRangeToSurround(range);
 1.10902 +      this.setSelection(range);
 1.10903 +    },
 1.10904 +
 1.10905 +    getSelectedOwnNodes: function(controlRange) {
 1.10906 +      var selection,
 1.10907 +          ranges = this.getOwnRanges(),
 1.10908 +          ownNodes = [];
 1.10909 +
 1.10910 +      for (var i = 0, maxi = ranges.length; i < maxi; i++) {
 1.10911 +          ownNodes.push(ranges[i].commonAncestorContainer || this.doc.body);
 1.10912 +      }
 1.10913 +      return ownNodes;
 1.10914 +    },
 1.10915 +
 1.10916 +    findNodesInSelection: function(nodeTypes) {
 1.10917 +      var ranges = this.getOwnRanges(),
 1.10918 +          nodes = [], curNodes;
 1.10919 +      for (var i = 0, maxi = ranges.length; i < maxi; i++) {
 1.10920 +        curNodes = ranges[i].getNodes([1], function(node) {
 1.10921 +            return wysihtml.lang.array(nodeTypes).contains(node.nodeName);
 1.10922 +        });
 1.10923 +        nodes = nodes.concat(curNodes);
 1.10924 +      }
 1.10925 +      return nodes;
 1.10926 +    },
 1.10927 +
 1.10928 +    filterElements: function(filter) {
 1.10929 +      var ranges = this.getOwnRanges(),
 1.10930 +          nodes = [], curNodes;
 1.10931 +
 1.10932 +      for (var i = 0, maxi = ranges.length; i < maxi; i++) {
 1.10933 +        curNodes = ranges[i].getNodes([1], function(element){
 1.10934 +          return filter(element, ranges[i]);
 1.10935 +        });
 1.10936 +        nodes = nodes.concat(curNodes);
 1.10937 +      }
 1.10938 +      return nodes;
 1.10939 +    },
 1.10940 +
 1.10941 +    containsUneditable: function() {
 1.10942 +      var uneditables = this.getOwnUneditables(),
 1.10943 +          selection = this.getSelection();
 1.10944 +
 1.10945 +      for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
 1.10946 +        if (selection.containsNode(uneditables[i])) {
 1.10947 +          return true;
 1.10948 +        }
 1.10949 +      }
 1.10950 +
 1.10951 +      return false;
 1.10952 +    },
 1.10953 +
 1.10954 +    // Deletes selection contents making sure uneditables/unselectables are not partially deleted
 1.10955 +    // Triggers wysihtml:uneditable:delete custom event on all deleted uneditables if customevents suppoorted
 1.10956 +    deleteContents: function()  {
 1.10957 +      var range = this.getRange();
 1.10958 +      this.deleteRangeContents(range);
 1.10959 +      this.setSelection(range);
 1.10960 +    },
 1.10961 +
 1.10962 +    // Makes sure all uneditable sare notified before deleting contents
 1.10963 +    deleteRangeContents: function (range) {
 1.10964 +      var startParent, endParent, uneditables, ev;
 1.10965 +
 1.10966 +      if (this.unselectableClass) {
 1.10967 +        if ((startParent = wysihtml.dom.getParentElement(range.startContainer, { query: "." + this.unselectableClass }, false, this.contain))) {
 1.10968 +          range.setStartBefore(startParent);
 1.10969 +        }
 1.10970 +        if ((endParent = wysihtml.dom.getParentElement(range.endContainer, { query: "." + this.unselectableClass }, false, this.contain))) {
 1.10971 +          range.setEndAfter(endParent);
 1.10972 +        }
 1.10973 +
 1.10974 +        // If customevents present notify uneditable elements of being deleted
 1.10975 +        uneditables = range.getNodes([1], (function (node) {
 1.10976 +          return wysihtml.dom.hasClass(node, this.unselectableClass);
 1.10977 +        }).bind(this));
 1.10978 +        for (var i = uneditables.length; i--;) {
 1.10979 +          try {
 1.10980 +            ev = new CustomEvent("wysihtml:uneditable:delete");
 1.10981 +            uneditables[i].dispatchEvent(ev);
 1.10982 +          } catch (err) {}
 1.10983 +        }
 1.10984 +      }
 1.10985 +      range.deleteContents();
 1.10986 +    },
 1.10987 +
 1.10988 +    getCaretNode: function () {
 1.10989 +      var selection = this.getSelection();
 1.10990 +      return (selection && selection.anchorNode) ? getRangeNode(selection.anchorNode, selection.anchorOffset) : null;
 1.10991 +    },
 1.10992 +
 1.10993 +    getPreviousNode: function(node, ignoreEmpty) {
 1.10994 +      var displayStyle;
 1.10995 +      if (!node) {
 1.10996 +        var selection = this.getSelection();
 1.10997 +        node = (selection && selection.anchorNode) ? getRangeNode(selection.anchorNode, selection.anchorOffset) : null;
 1.10998 +      }
 1.10999 +
 1.11000 +      if (node === this.contain) {
 1.11001 +          return false;
 1.11002 +      }
 1.11003 +
 1.11004 +      var ret = node.previousSibling,
 1.11005 +          parent;
 1.11006 +
 1.11007 +      if (ret === this.contain) {
 1.11008 +          return false;
 1.11009 +      }
 1.11010 +
 1.11011 +      if (ret && ret.nodeType !== 3 && ret.nodeType !== 1) {
 1.11012 +         // do not count comments and other node types
 1.11013 +         ret = this.getPreviousNode(ret, ignoreEmpty);
 1.11014 +      } else if (ret && ret.nodeType === 3 && (/^\s*$/).test(ret.textContent)) {
 1.11015 +        // do not count empty textnodes as previous nodes
 1.11016 +        ret = this.getPreviousNode(ret, ignoreEmpty);
 1.11017 +      } else if (ignoreEmpty && ret && ret.nodeType === 1) {
 1.11018 +        // Do not count empty nodes if param set.
 1.11019 +        // Contenteditable tends to bypass and delete these silently when deleting with caret when element is inline-like
 1.11020 +        displayStyle = wysihtml.dom.getStyle("display").from(ret);
 1.11021 +        if (
 1.11022 +            !wysihtml.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) &&
 1.11023 +            !wysihtml.lang.array(["block", "inline-block", "flex", "list-item", "table"]).contains(displayStyle) &&
 1.11024 +            (/^[\s]*$/).test(ret.innerHTML)
 1.11025 +          ) {
 1.11026 +            ret = this.getPreviousNode(ret, ignoreEmpty);
 1.11027 +          }
 1.11028 +      } else if (!ret && node !== this.contain) {
 1.11029 +        parent = node.parentNode;
 1.11030 +        if (parent !== this.contain) {
 1.11031 +            ret = this.getPreviousNode(parent, ignoreEmpty);
 1.11032 +        }
 1.11033 +      }
 1.11034 +
 1.11035 +      return (ret !== this.contain) ? ret : false;
 1.11036 +    },
 1.11037 +
 1.11038 +    // Gather info about caret location (caret node, previous and next node)
 1.11039 +    getNodesNearCaret: function() {
 1.11040 +      if (!this.isCollapsed()) {
 1.11041 +        throw "Selection must be caret when using selection.getNodesNearCaret()";
 1.11042 +      }
 1.11043 +
 1.11044 +      var r = this.getOwnRanges(),
 1.11045 +          caretNode, prevNode, nextNode, offset;
 1.11046 +
 1.11047 +      if (r && r.length > 0) {
 1.11048 +        if (r[0].startContainer.nodeType === 1) {
 1.11049 +          caretNode = r[0].startContainer.childNodes[r[0].startOffset - 1];
 1.11050 +          if (!caretNode && r[0].startOffset === 0) {
 1.11051 +            // Is first position before all nodes
 1.11052 +            nextNode = r[0].startContainer.childNodes[0];
 1.11053 +          } else if (caretNode) {
 1.11054 +            prevNode = caretNode.previousSibling;
 1.11055 +            nextNode = caretNode.nextSibling;
 1.11056 +          }
 1.11057 +        } else {
 1.11058 +          if (r[0].startOffset === 0 && r[0].startContainer.previousSibling) {
 1.11059 +            caretNode = r[0].startContainer.previousSibling;
 1.11060 +            if (caretNode.nodeType === 3) {
 1.11061 +              offset = caretNode.data.length;
 1.11062 +            }
 1.11063 +          } else {
 1.11064 +            caretNode = r[0].startContainer;
 1.11065 +            offset = r[0].startOffset;
 1.11066 +          }
 1.11067 +          prevNode = caretNode.previousSibling;
 1.11068 +          nextNode = caretNode.nextSibling;
 1.11069 +        }
 1.11070 +
 1.11071 +        return {
 1.11072 +          "caretNode": caretNode,
 1.11073 +          "prevNode": prevNode,
 1.11074 +          "nextNode": nextNode,
 1.11075 +          "textOffset": offset
 1.11076 +        };
 1.11077 +      }
 1.11078 +
 1.11079 +      return null;
 1.11080 +    },
 1.11081 +
 1.11082 +    getSelectionParentsByTag: function(tagName) {
 1.11083 +      var nodes = this.getSelectedOwnNodes(),
 1.11084 +          curEl, parents = [];
 1.11085 +
 1.11086 +      for (var i = 0, maxi = nodes.length; i < maxi; i++) {
 1.11087 +        curEl = (nodes[i].nodeName &&  nodes[i].nodeName === 'LI') ? nodes[i] : wysihtml.dom.getParentElement(nodes[i], { query: 'li'}, false, this.contain);
 1.11088 +        if (curEl) {
 1.11089 +          parents.push(curEl);
 1.11090 +        }
 1.11091 +      }
 1.11092 +      return (parents.length) ? parents : null;
 1.11093 +    },
 1.11094 +
 1.11095 +    getRangeToNodeEnd: function() {
 1.11096 +      if (this.isCollapsed()) {
 1.11097 +        var range = this.getRange(),
 1.11098 +            sNode, pos, lastR;
 1.11099 +        if (range) {
 1.11100 +          sNode = range.startContainer;
 1.11101 +          pos = range.startOffset;
 1.11102 +          lastR = rangy.createRange(this.doc);
 1.11103 +
 1.11104 +          lastR.selectNodeContents(sNode);
 1.11105 +          lastR.setStart(sNode, pos);
 1.11106 +          return lastR;
 1.11107 +        }
 1.11108 +      }
 1.11109 +    },
 1.11110 +
 1.11111 +    getRangeToNodeBeginning: function() {
 1.11112 +      if (this.isCollapsed()) {
 1.11113 +        var range = this.getRange(),
 1.11114 +            sNode = range.startContainer,
 1.11115 +            pos = range.startOffset,
 1.11116 +            lastR = rangy.createRange(this.doc);
 1.11117 +
 1.11118 +        lastR.selectNodeContents(sNode);
 1.11119 +        lastR.setEnd(sNode, pos);
 1.11120 +        return lastR;
 1.11121 +      }
 1.11122 +    },
 1.11123 +
 1.11124 +    // This function returns if caret is last in a node (no textual visible content follows)
 1.11125 +    caretIsInTheEndOfNode: function(ignoreIfSpaceIsBeforeCaret) {
 1.11126 +      var r = rangy.createRange(this.doc),
 1.11127 +          s = this.getSelection(),
 1.11128 +          rangeToNodeEnd = this.getRangeToNodeEnd(),
 1.11129 +          endc, endtxt, beginc, begintxt;
 1.11130 +
 1.11131 +      if (rangeToNodeEnd) {
 1.11132 +        endc = rangeToNodeEnd.cloneContents();
 1.11133 +        endtxt = endc.textContent;
 1.11134 +
 1.11135 +        if ((/^\s*$/).test(endtxt)) {
 1.11136 +          if (ignoreIfSpaceIsBeforeCaret) {
 1.11137 +            beginc = this.getRangeToNodeBeginning().cloneContents();
 1.11138 +            begintxt = beginc.textContent;
 1.11139 +            return !(/[\u00A0 ][\s\uFEFF]*$/).test(begintxt);
 1.11140 +          } else {
 1.11141 +            return true;
 1.11142 +          }
 1.11143 +        } else {
 1.11144 +          return false;
 1.11145 +        }
 1.11146 +      } else {
 1.11147 +        return false;
 1.11148 +      }
 1.11149 +    },
 1.11150 +
 1.11151 +    caretIsFirstInSelection: function(includeLineBreaks) {
 1.11152 +      var r = rangy.createRange(this.doc),
 1.11153 +          s = this.getSelection(),
 1.11154 +          range = this.getRange(),
 1.11155 +          startNode = getRangeNode(range.startContainer, range.startOffset);
 1.11156 +
 1.11157 +      if (startNode) {
 1.11158 +        if (startNode.nodeType === wysihtml.TEXT_NODE) {
 1.11159 +          if (!startNode.parentNode) {
 1.11160 +            return false;
 1.11161 +          }
 1.11162 +          if (!this.isCollapsed() || (startNode.parentNode.firstChild !== startNode && !wysihtml.dom.domNode(startNode.previousSibling).is.block())) {
 1.11163 +            return false;
 1.11164 +          }
 1.11165 +          var ws = this.win.getComputedStyle(startNode.parentNode).whiteSpace;
 1.11166 +          return (ws === "pre" || ws === "pre-wrap") ? range.startOffset === 0 : (/^\s*$/).test(startNode.data.substr(0,range.startOffset));
 1.11167 +        } else if (includeLineBreaks && wysihtml.dom.domNode(startNode).is.lineBreak()) {
 1.11168 +          return true;
 1.11169 +        } else {
 1.11170 +          r.selectNodeContents(this.getRange().commonAncestorContainer);
 1.11171 +          r.collapse(true);
 1.11172 +          return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
 1.11173 +        }
 1.11174 +      }
 1.11175 +    },
 1.11176 +
 1.11177 +    caretIsInTheBeginnig: function(ofNode) {
 1.11178 +        var selection = this.getSelection(),
 1.11179 +            node = selection.anchorNode,
 1.11180 +            offset = selection.anchorOffset;
 1.11181 +        if (ofNode && node) {
 1.11182 +          return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml.dom.getParentElement(node.parentNode, { query: ofNode }, 1)));
 1.11183 +        } else if (node) {
 1.11184 +          return (offset === 0 && !this.getPreviousNode(node, true));
 1.11185 +        }
 1.11186 +    },
 1.11187 +
 1.11188 +    // Returns object describing node/text before selection
 1.11189 +    // If includePrevLeaves is true returns  also previous last leaf child if selection is in the beginning of current node
 1.11190 +    getBeforeSelection: function(includePrevLeaves) {
 1.11191 +      var sel = this.getSelection(),
 1.11192 +          startNode = (sel.isBackwards()) ? sel.focusNode : sel.anchorNode,
 1.11193 +          startOffset = (sel.isBackwards()) ? sel.focusOffset : sel.anchorOffset,
 1.11194 +          rng = this.createRange(), endNode, inTmpCaret;
 1.11195 +
 1.11196 +      // If start is textnode and all is whitespace before caret. Set start offset to 0
 1.11197 +      if (startNode && startNode.nodeType === 3 && (/^\s*$/).test(startNode.data.slice(0, startOffset))) {
 1.11198 +        startOffset = 0;
 1.11199 +      }
 1.11200 +
 1.11201 +      // Escape temproray helper nodes if selection in them
 1.11202 +      inTmpCaret = wysihtml.dom.getParentElement(startNode, { query: '._wysihtml-temp-caret-fix' }, 1);
 1.11203 +      if (inTmpCaret) {
 1.11204 +        startNode = inTmpCaret.parentNode;
 1.11205 +        startOffset = Array.prototype.indexOf.call(startNode.childNodes, inTmpCaret);
 1.11206 +      }
 1.11207 +
 1.11208 +      if (startNode) {
 1.11209 +        if (startOffset > 0) {
 1.11210 +          if (startNode.nodeType === 3) {
 1.11211 +            rng.setStart(startNode, 0);
 1.11212 +            rng.setEnd(startNode, startOffset);
 1.11213 +            return {
 1.11214 +              type: "text",
 1.11215 +              range: rng,
 1.11216 +              offset : startOffset,
 1.11217 +              node: startNode
 1.11218 +            };
 1.11219 +          } else {
 1.11220 +            rng.setStartBefore(startNode.childNodes[0]);
 1.11221 +            endNode = startNode.childNodes[startOffset - 1];
 1.11222 +            rng.setEndAfter(endNode);
 1.11223 +            return {
 1.11224 +              type: "element",
 1.11225 +              range: rng,
 1.11226 +              offset : startOffset,
 1.11227 +              node: endNode
 1.11228 +            };
 1.11229 +          }
 1.11230 +        } else {
 1.11231 +          rng.setStartAndEnd(startNode, 0);
 1.11232 +
 1.11233 +          if (includePrevLeaves) {
 1.11234 +            var prevNode = this.getPreviousNode(startNode, true),
 1.11235 +                prevLeaf = null;
 1.11236 +
 1.11237 +            if(prevNode) {
 1.11238 +              if (prevNode.nodeType === 1 && wysihtml.dom.hasClass(prevNode, this.unselectableClass)) {
 1.11239 +                prevLeaf = prevNode;
 1.11240 +              } else {
 1.11241 +                prevLeaf = wysihtml.dom.domNode(prevNode).lastLeafNode();
 1.11242 +              }
 1.11243 +            }
 1.11244 +
 1.11245 +            if (prevLeaf) {
 1.11246 +              return {
 1.11247 +                type: "leafnode",
 1.11248 +                range: rng,
 1.11249 +                offset : startOffset,
 1.11250 +                node: prevLeaf
 1.11251 +              };
 1.11252 +            }
 1.11253 +          }
 1.11254 +
 1.11255 +          return {
 1.11256 +            type: "none",
 1.11257 +            range: rng,
 1.11258 +            offset : startOffset,
 1.11259 +            node: startNode
 1.11260 +          };
 1.11261 +        }
 1.11262 +      }
 1.11263 +      return null;
 1.11264 +    },
 1.11265 +
 1.11266 +    // TODO: Figure out a method from following 2 that would work universally
 1.11267 +    executeAndRestoreRangy: function(method, restoreScrollPosition) {
 1.11268 +      var sel = rangy.saveSelection(this.win);
 1.11269 +      if (!sel) {
 1.11270 +        method();
 1.11271 +      } else {
 1.11272 +        try {
 1.11273 +          method();
 1.11274 +        } catch(e) {
 1.11275 +          setTimeout(function() { throw e; }, 0);
 1.11276 +        }
 1.11277 +      }
 1.11278 +      rangy.restoreSelection(sel);
 1.11279 +    },
 1.11280 +
 1.11281 +    // TODO: has problems in chrome 12. investigate block level and uneditable area inbetween
 1.11282 +    executeAndRestore: function(method, restoreScrollPosition) {
 1.11283 +      var body                  = this.doc.body,
 1.11284 +          oldScrollTop          = restoreScrollPosition && body.scrollTop,
 1.11285 +          oldScrollLeft         = restoreScrollPosition && body.scrollLeft,
 1.11286 +          className             = "_wysihtml-temp-placeholder",
 1.11287 +          placeholderHtml       = '<span class="' + className + '">' + wysihtml.INVISIBLE_SPACE + '</span>',
 1.11288 +          range                 = this.getRange(true),
 1.11289 +          caretPlaceholder,
 1.11290 +          newCaretPlaceholder,
 1.11291 +          nextSibling, prevSibling,
 1.11292 +          node, node2, range2,
 1.11293 +          newRange;
 1.11294 +
 1.11295 +      // Nothing selected, execute and say goodbye
 1.11296 +      if (!range) {
 1.11297 +        method(body, body);
 1.11298 +        return;
 1.11299 +      }
 1.11300 +
 1.11301 +      if (!range.collapsed) {
 1.11302 +        range2 = range.cloneRange();
 1.11303 +        node2 = range2.createContextualFragment(placeholderHtml);
 1.11304 +        range2.collapse(false);
 1.11305 +        range2.insertNode(node2);
 1.11306 +        range2.detach();
 1.11307 +      }
 1.11308 +
 1.11309 +      node = range.createContextualFragment(placeholderHtml);
 1.11310 +      range.insertNode(node);
 1.11311 +
 1.11312 +      if (node2) {
 1.11313 +        caretPlaceholder = this.contain.querySelectorAll("." + className);
 1.11314 +        range.setStartBefore(caretPlaceholder[0]);
 1.11315 +        range.setEndAfter(caretPlaceholder[caretPlaceholder.length -1]);
 1.11316 +      }
 1.11317 +      this.setSelection(range);
 1.11318 +
 1.11319 +      // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder
 1.11320 +      try {
 1.11321 +        method(range.startContainer, range.endContainer);
 1.11322 +      } catch(e) {
 1.11323 +        setTimeout(function() { throw e; }, 0);
 1.11324 +      }
 1.11325 +      caretPlaceholder = this.contain.querySelectorAll("." + className);
 1.11326 +      if (caretPlaceholder && caretPlaceholder.length) {
 1.11327 +        newRange = rangy.createRange(this.doc);
 1.11328 +        nextSibling = caretPlaceholder[0].nextSibling;
 1.11329 +        if (caretPlaceholder.length > 1) {
 1.11330 +          prevSibling = caretPlaceholder[caretPlaceholder.length -1].previousSibling;
 1.11331 +        }
 1.11332 +        if (prevSibling && nextSibling) {
 1.11333 +          newRange.setStartBefore(nextSibling);
 1.11334 +          newRange.setEndAfter(prevSibling);
 1.11335 +        } else {
 1.11336 +          newCaretPlaceholder = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE);
 1.11337 +          dom.insert(newCaretPlaceholder).after(caretPlaceholder[0]);
 1.11338 +          newRange.setStartBefore(newCaretPlaceholder);
 1.11339 +          newRange.setEndAfter(newCaretPlaceholder);
 1.11340 +        }
 1.11341 +        this.setSelection(newRange);
 1.11342 +        for (var i = caretPlaceholder.length; i--;) {
 1.11343 +          caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]);
 1.11344 +        }
 1.11345 +
 1.11346 +      } else {
 1.11347 +        // fallback for when all hell breaks loose
 1.11348 +        this.contain.focus();
 1.11349 +      }
 1.11350 +
 1.11351 +      if (restoreScrollPosition) {
 1.11352 +        body.scrollTop  = oldScrollTop;
 1.11353 +        body.scrollLeft = oldScrollLeft;
 1.11354 +      }
 1.11355 +
 1.11356 +      // Remove it again, just to make sure that the placeholder is definitely out of the dom tree
 1.11357 +      try {
 1.11358 +        caretPlaceholder.parentNode.removeChild(caretPlaceholder);
 1.11359 +      } catch(e2) {}
 1.11360 +    },
 1.11361 +
 1.11362 +    set: function(node, offset) {
 1.11363 +      var newRange = rangy.createRange(this.doc);
 1.11364 +      newRange.setStart(node, offset || 0);
 1.11365 +      this.setSelection(newRange);
 1.11366 +    },
 1.11367 +
 1.11368 +    /**
 1.11369 +     * Insert html at the caret or selection position and move the cursor after the inserted html
 1.11370 +     * Replaces selection content if present
 1.11371 +     *
 1.11372 +     * @param {String} html HTML string to insert
 1.11373 +     * @example
 1.11374 +     *    selection.insertHTML("<p>foobar</p>");
 1.11375 +     */
 1.11376 +    insertHTML: function(html) {
 1.11377 +      var range     = this.getRange(),
 1.11378 +          node = this.doc.createElement('DIV'),
 1.11379 +          fragment = this.doc.createDocumentFragment(),
 1.11380 +          lastChild, lastEditorElement;
 1.11381 +
 1.11382 +      if (range) {
 1.11383 +        range.deleteContents();
 1.11384 +        node.innerHTML = html;
 1.11385 +        lastChild = node.lastChild;
 1.11386 +
 1.11387 +        while (node.firstChild) {
 1.11388 +          fragment.appendChild(node.firstChild);
 1.11389 +        }
 1.11390 +        range.insertNode(fragment);
 1.11391 +
 1.11392 +        lastEditorElement = this.contain.lastChild;
 1.11393 +        while (lastEditorElement && lastEditorElement.nodeType === 3 && lastEditorElement.previousSibling && (/^\s*$/).test(lastEditorElement.data)) {
 1.11394 +          lastEditorElement = lastEditorElement.previousSibling;
 1.11395 +        }
 1.11396 +
 1.11397 +        if (lastChild) {
 1.11398 +          // fixes some pad cases mostly on webkit where last nr is needed
 1.11399 +          if (lastEditorElement && lastChild === lastEditorElement && lastChild.nodeType === 1) {
 1.11400 +            this.contain.appendChild(this.doc.createElement('br'));
 1.11401 +          }
 1.11402 +          this.setAfter(lastChild);
 1.11403 +        }
 1.11404 +      }
 1.11405 +    },
 1.11406 +
 1.11407 +    /**
 1.11408 +     * Insert a node at the caret position and move the cursor behind it
 1.11409 +     *
 1.11410 +     * @param {Object} node HTML string to insert
 1.11411 +     * @example
 1.11412 +     *    selection.insertNode(document.createTextNode("foobar"));
 1.11413 +     */
 1.11414 +    insertNode: function(node) {
 1.11415 +      var range = this.getRange();
 1.11416 +      if (range) {
 1.11417 +        range.deleteContents();
 1.11418 +        range.insertNode(node);
 1.11419 +      }
 1.11420 +    },
 1.11421 +
 1.11422 +    canAppendChild: function (node) {
 1.11423 +      var anchorNode, anchorNodeTagNameLower,
 1.11424 +          voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"],
 1.11425 +          range = this.getRange();
 1.11426 +
 1.11427 +      anchorNode = node || range.startContainer;
 1.11428 +
 1.11429 +      if (anchorNode) {
 1.11430 +        anchorNodeTagNameLower = (anchorNode.tagName || anchorNode.nodeName).toLowerCase();
 1.11431 +      }
 1.11432 +
 1.11433 +      return voidElements.indexOf(anchorNodeTagNameLower) === -1;
 1.11434 +    },
 1.11435 +
 1.11436 +    splitElementAtCaret: function (element, insertNode) {
 1.11437 +      var sel = this.getSelection(),
 1.11438 +          range, contentAfterRangeStart,
 1.11439 +          firstChild, lastChild, childNodes;
 1.11440 +
 1.11441 +      if (sel.rangeCount > 0) {
 1.11442 +        range = sel.getRangeAt(0).cloneRange(); // Create a copy of the selection range to work with
 1.11443 +
 1.11444 +        range.setEndAfter(element); // Place the end of the range after the element
 1.11445 +        contentAfterRangeStart = range.extractContents(); // Extract the contents of the element after the caret into a fragment
 1.11446 +
 1.11447 +        childNodes = contentAfterRangeStart.childNodes;
 1.11448 +
 1.11449 +        // Empty elements are cleaned up from extracted content
 1.11450 +        for (var i = childNodes.length; i --;) {
 1.11451 +          if (!wysihtml.dom.domNode(childNodes[i]).is.visible()) {
 1.11452 +            contentAfterRangeStart.removeChild(childNodes[i]);
 1.11453 +          }
 1.11454 +        }
 1.11455 +
 1.11456 +        element.parentNode.insertBefore(contentAfterRangeStart, element.nextSibling);
 1.11457 +
 1.11458 +        if (insertNode) {
 1.11459 +          firstChild = insertNode.firstChild || insertNode;
 1.11460 +          lastChild = insertNode.lastChild || insertNode;
 1.11461 +
 1.11462 +          element.parentNode.insertBefore(insertNode, element.nextSibling);
 1.11463 +
 1.11464 +          // Select inserted node contents
 1.11465 +          if (firstChild && lastChild) {
 1.11466 +             range.setStartBefore(firstChild);
 1.11467 +             range.setEndAfter(lastChild);
 1.11468 +             this.setSelection(range);
 1.11469 +          }
 1.11470 +        } else {
 1.11471 +          range.setStartAfter(element);
 1.11472 +          range.setEndAfter(element);
 1.11473 +        }
 1.11474 +
 1.11475 +        if (!wysihtml.dom.domNode(element).is.visible()) {
 1.11476 +          if (wysihtml.dom.getTextContent(element) === '') {
 1.11477 +            element.parentNode.removeChild(element);
 1.11478 +          } else {
 1.11479 +            element.parentNode.replaceChild(this.doc.createTextNode(" "), element);
 1.11480 +          }
 1.11481 +        }
 1.11482 +
 1.11483 +
 1.11484 +      }
 1.11485 +    },
 1.11486 +
 1.11487 +    /**
 1.11488 +     * Wraps current selection with the given node
 1.11489 +     *
 1.11490 +     * @param {Object} node The node to surround the selected elements with
 1.11491 +     */
 1.11492 +    surround: function(nodeOptions) {
 1.11493 +      var ranges = this.getOwnRanges(),
 1.11494 +          node, nodes = [];
 1.11495 +      if (ranges.length == 0) {
 1.11496 +        return nodes;
 1.11497 +      }
 1.11498 +
 1.11499 +      for (var i = ranges.length; i--;) {
 1.11500 +        node = this.doc.createElement(nodeOptions.nodeName);
 1.11501 +        nodes.push(node);
 1.11502 +        if (nodeOptions.className) {
 1.11503 +          node.className = nodeOptions.className;
 1.11504 +        }
 1.11505 +        if (nodeOptions.cssStyle) {
 1.11506 +          node.setAttribute('style', nodeOptions.cssStyle);
 1.11507 +        }
 1.11508 +        try {
 1.11509 +          // This only works when the range boundaries are not overlapping other elements
 1.11510 +          ranges[i].surroundContents(node);
 1.11511 +          this.selectNode(node);
 1.11512 +        } catch(e) {
 1.11513 +          // fallback
 1.11514 +          node.appendChild(ranges[i].extractContents());
 1.11515 +          ranges[i].insertNode(node);
 1.11516 +        }
 1.11517 +      }
 1.11518 +      return nodes;
 1.11519 +    },
 1.11520 +
 1.11521 +    /**
 1.11522 +     * Scroll the current caret position into the view
 1.11523 +     * FIXME: This is a bit hacky, there might be a smarter way of doing this
 1.11524 +     *
 1.11525 +     * @example
 1.11526 +     *    selection.scrollIntoView();
 1.11527 +     */
 1.11528 +    scrollIntoView: function() {
 1.11529 +      var doc           = this.doc,
 1.11530 +          tolerance     = 5, // px
 1.11531 +          hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight,
 1.11532 +          tempElement   = doc._wysihtmlScrollIntoViewElement = doc._wysihtmlScrollIntoViewElement || (function() {
 1.11533 +            var element = doc.createElement("span");
 1.11534 +            // The element needs content in order to be able to calculate it's position properly
 1.11535 +            element.innerHTML = wysihtml.INVISIBLE_SPACE;
 1.11536 +            return element;
 1.11537 +          })(),
 1.11538 +          offsetTop;
 1.11539 +
 1.11540 +      if (hasScrollBars) {
 1.11541 +        this.insertNode(tempElement);
 1.11542 +        offsetTop = _getCumulativeOffsetTop(tempElement);
 1.11543 +        tempElement.parentNode.removeChild(tempElement);
 1.11544 +        if (offsetTop >= (doc.body.scrollTop + doc.documentElement.offsetHeight - tolerance)) {
 1.11545 +          doc.body.scrollTop = offsetTop;
 1.11546 +        }
 1.11547 +      }
 1.11548 +    },
 1.11549 +
 1.11550 +    /**
 1.11551 +     * Select line where the caret is in
 1.11552 +     */
 1.11553 +    selectLine: function() {
 1.11554 +      var r = rangy.createRange();
 1.11555 +      if (wysihtml.browser.supportsSelectionModify()) {
 1.11556 +        this._selectLine_W3C();
 1.11557 +      } else if (r.nativeRange && r.nativeRange.getBoundingClientRect) {
 1.11558 +        // For IE Edge as it ditched the old api and did not fully implement the new one (as expected)
 1.11559 +        this._selectLineUniversal();
 1.11560 +      }
 1.11561 +    },
 1.11562 +
 1.11563 +    includeRangyRangeHelpers: function() {
 1.11564 +      var s = this.getSelection(),
 1.11565 +          r = s.getRangeAt(0),
 1.11566 +          isHelperNode = function(node) {
 1.11567 +            return (node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary'));
 1.11568 +          },
 1.11569 +          getNodeLength = function (node) {
 1.11570 +            if (node.nodeType === 1) {
 1.11571 +              return node.childNodes && node.childNodes.length || 0;
 1.11572 +            } else {
 1.11573 +              return node.data && node.data.length || 0;
 1.11574 +            }
 1.11575 +          },
 1.11576 +          anode = s.anchorNode.nodeType === 1 ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode,
 1.11577 +          fnode = s.focusNode.nodeType === 1 ? s.focusNode.childNodes[s.focusOffset] : s.focusNode;
 1.11578 +
 1.11579 +      if (fnode && s.focusOffset === getNodeLength(fnode) && fnode.nextSibling && isHelperNode(fnode.nextSibling)) {
 1.11580 +        r.setEndAfter(fnode.nextSibling);
 1.11581 +      }
 1.11582 +      if (anode && s.anchorOffset === 0 && anode.previousSibling && isHelperNode(anode.previousSibling)) {
 1.11583 +        r.setStartBefore(anode.previousSibling);
 1.11584 +      }
 1.11585 +      r.select();
 1.11586 +    },
 1.11587 +
 1.11588 +    /**
 1.11589 +     * See https://developer.mozilla.org/en/DOM/Selection/modify
 1.11590 +     */
 1.11591 +    _selectLine_W3C: function() {
 1.11592 +      var selection = this.win.getSelection(),
 1.11593 +          initialBoundry = [selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset];
 1.11594 +
 1.11595 +      selection.modify("move", "left", "lineboundary");
 1.11596 +      selection.modify("extend", "right", "lineboundary");
 1.11597 +
 1.11598 +      // IF lineboundary extending did not change selection try universal fallback (FF fails sometimes without a reason)
 1.11599 +      if (selection.anchorNode === initialBoundry[0] &&
 1.11600 +          selection.anchorOffset === initialBoundry[1] &&
 1.11601 +          selection.focusNode === initialBoundry[2] &&
 1.11602 +          selection.focusOffset === initialBoundry[3]
 1.11603 +      ) {
 1.11604 +        this._selectLineUniversal();
 1.11605 +      } else {
 1.11606 +        this.includeRangyRangeHelpers();
 1.11607 +      }
 1.11608 +    },
 1.11609 +
 1.11610 +    // collapses selection to current line beginning or end
 1.11611 +    toLineBoundary: function (location, collapse) {
 1.11612 +      collapse = (typeof collapse === 'undefined') ? false : collapse;
 1.11613 +      if (wysihtml.browser.supportsSelectionModify()) {
 1.11614 +        var selection = this.win.getSelection();
 1.11615 +
 1.11616 +        selection.modify("extend", location, "lineboundary");
 1.11617 +        if (collapse) {
 1.11618 +          if (location === "left") {
 1.11619 +            selection.collapseToStart();
 1.11620 +          } else if (location === "right") {
 1.11621 +            selection.collapseToEnd();
 1.11622 +          }
 1.11623 +        }
 1.11624 +      }
 1.11625 +    },
 1.11626 +
 1.11627 +    getRangeRect: function(r) {
 1.11628 +      var textNode = this.doc.createTextNode("i"),
 1.11629 +          testNode = this.doc.createTextNode("i"),
 1.11630 +          rect, cr;
 1.11631 +
 1.11632 +      /*testNode.style.visibility = "hidden";
 1.11633 +      testNode.style.width = "0px";
 1.11634 +      testNode.style.display = "inline-block";
 1.11635 +      testNode.style.overflow = "hidden";
 1.11636 +      testNode.appendChild(textNode);*/
 1.11637 +
 1.11638 +      if (r.collapsed) {
 1.11639 +        r.insertNode(testNode);
 1.11640 +        r.selectNode(testNode);
 1.11641 +        rect = r.nativeRange.getBoundingClientRect();
 1.11642 +        r.deleteContents();
 1.11643 +
 1.11644 +      } else {
 1.11645 +        rect = r.nativeRange.getBoundingClientRect();
 1.11646 +      }
 1.11647 +
 1.11648 +      return rect;
 1.11649 +
 1.11650 +    },
 1.11651 +
 1.11652 +    _selectLineUniversal: function() {
 1.11653 +      var s = this.getSelection(),
 1.11654 +          r = s.getRangeAt(0),
 1.11655 +          rect,
 1.11656 +          startRange, endRange, testRange,
 1.11657 +          count = 0,
 1.11658 +          amount, testRect, found,
 1.11659 +          that = this,
 1.11660 +          isLineBreakingElement = function(el) {
 1.11661 +            return el && el.nodeType === 1 && (that.win.getComputedStyle(el).display === "block" || wysihtml.lang.array(['BR', 'HR']).contains(el.nodeName));
 1.11662 +          },
 1.11663 +          prevNode = function(node) {
 1.11664 +            var pnode = node;
 1.11665 +            if (pnode) {
 1.11666 +              while (pnode && ((pnode.nodeType === 1 && pnode.classList.contains('rangySelectionBoundary')) || (pnode.nodeType === 3 && (/^\s*$/).test(pnode.data)))) {
 1.11667 +                pnode = pnode.previousSibling;
 1.11668 +              }
 1.11669 +            }
 1.11670 +            return pnode;
 1.11671 +          };
 1.11672 +
 1.11673 +      startRange = r.cloneRange();
 1.11674 +      endRange = r.cloneRange();
 1.11675 +
 1.11676 +      if (r.collapsed) {
 1.11677 +        // Collapsed state can not have a bounding rect. Thus need to expand it at least by 1 character first while not crossing line boundary
 1.11678 +        // TODO: figure out a shorter and more readable way
 1.11679 +        if (r.startContainer.nodeType === 3 && r.startOffset < r.startContainer.data.length) {
 1.11680 +          r.moveEnd('character', 1);
 1.11681 +        } else if (r.startContainer.nodeType === 1 && r.startContainer.childNodes[r.startOffset] && r.startContainer.childNodes[r.startOffset].nodeType === 3 && r.startContainer.childNodes[r.startOffset].data.length > 0) {
 1.11682 +          r.moveEnd('character', 1);
 1.11683 +        } else if (
 1.11684 +          r.startOffset > 0 &&
 1.11685 +          (
 1.11686 +            r.startContainer.nodeType === 3 ||
 1.11687 +            (
 1.11688 +              r.startContainer.nodeType === 1 &&
 1.11689 +              !isLineBreakingElement(prevNode(r.startContainer.childNodes[r.startOffset - 1]))
 1.11690 +            )
 1.11691 +          )
 1.11692 +        ) {
 1.11693 +          r.moveStart('character', -1);
 1.11694 +        }
 1.11695 +      }
 1.11696 +      if (!r.collapsed) {
 1.11697 +        r.insertNode(this.doc.createTextNode(wysihtml.INVISIBLE_SPACE));
 1.11698 +      }
 1.11699 +
 1.11700 +      // Is probably just empty line as can not be expanded
 1.11701 +      rect = r.nativeRange.getBoundingClientRect();
 1.11702 +      // If startnode is not line break allready move the start position of range by -1 character until clientRect top changes;
 1.11703 +      do {
 1.11704 +        amount = r.moveStart('character', -1);
 1.11705 +        testRect =  r.nativeRange.getBoundingClientRect();
 1.11706 +
 1.11707 +        if (!testRect || Math.floor(testRect.top) !== Math.floor(rect.top)) {
 1.11708 +          r.moveStart('character', 1);
 1.11709 +          found = true;
 1.11710 +        }
 1.11711 +        count++;
 1.11712 +      } while (amount !== 0 && !found && count < 2000);
 1.11713 +      count = 0;
 1.11714 +      found = false;
 1.11715 +      rect = r.nativeRange.getBoundingClientRect();
 1.11716 +
 1.11717 +      if (r.endContainer !== this.contain || (this.contain.lastChild && this.contain.childNodes[r.endOffset] !== this.contain.lastChild)) {
 1.11718 +        do {
 1.11719 +          amount = r.moveEnd('character', 1);
 1.11720 +          testRect =  r.nativeRange.getBoundingClientRect();
 1.11721 +          if (!testRect || Math.floor(testRect.bottom) !== Math.floor(rect.bottom)) {
 1.11722 +            r.moveEnd('character', -1);
 1.11723 +
 1.11724 +            // Fix a IE line end marked by linebreak element although caret is before it
 1.11725 +            // If causes problems should be changed to be applied only to IE
 1.11726 +            if (r.endContainer && r.endContainer.nodeType === 1 && r.endContainer.childNodes[r.endOffset] && r.endContainer.childNodes[r.endOffset].nodeType === 1 && r.endContainer.childNodes[r.endOffset].nodeName === "BR" && r.endContainer.childNodes[r.endOffset].previousSibling) {
 1.11727 +              if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 1) {
 1.11728 +                r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.childNodes.length);
 1.11729 +              } else if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 3) {
 1.11730 +                r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.data.length);
 1.11731 +              }
 1.11732 +            }
 1.11733 +            found = true;
 1.11734 +          }
 1.11735 +          count++;
 1.11736 +        } while (amount !== 0 && !found && count < 2000);
 1.11737 +      }
 1.11738 +      r.select();
 1.11739 +      this.includeRangyRangeHelpers();
 1.11740 +    },
 1.11741 +
 1.11742 +    getText: function() {
 1.11743 +      var selection = this.getSelection();
 1.11744 +      return selection ? selection.toString() : "";
 1.11745 +    },
 1.11746 +
 1.11747 +    getNodes: function(nodeType, filter) {
 1.11748 +      var range = this.getRange();
 1.11749 +      if (range) {
 1.11750 +        return range.getNodes(Array.isArray(nodeType) ? nodeType : [nodeType], filter);
 1.11751 +      } else {
 1.11752 +        return [];
 1.11753 +      }
 1.11754 +    },
 1.11755 +
 1.11756 +    // Gets all the elements in selection with nodeType
 1.11757 +    // Ignores the elements not belonging to current editable area
 1.11758 +    // If filter is defined nodes must pass the filter function with true to be included in list
 1.11759 +    getOwnNodes: function(nodeType, filter, splitBounds) {
 1.11760 +      var ranges = this.getOwnRanges(),
 1.11761 +          nodes = [];
 1.11762 +      for (var r = 0, rmax = ranges.length; r < rmax; r++) {
 1.11763 +        if (ranges[r]) {
 1.11764 +          if (splitBounds) {
 1.11765 +            ranges[r].splitBoundaries();
 1.11766 +          }
 1.11767 +          nodes = nodes.concat(ranges[r].getNodes(Array.isArray(nodeType) ? nodeType : [nodeType], filter));
 1.11768 +        }
 1.11769 +      }
 1.11770 +
 1.11771 +      return nodes;
 1.11772 +    },
 1.11773 +
 1.11774 +    fixRangeOverflow: function(range) {
 1.11775 +      if (this.contain && this.contain.firstChild && range) {
 1.11776 +        var containment = range.compareNode(this.contain);
 1.11777 +        if (containment !== 2) {
 1.11778 +          if (containment === 1) {
 1.11779 +            range.setStartBefore(this.contain.firstChild);
 1.11780 +          }
 1.11781 +          if (containment === 0) {
 1.11782 +            range.setEndAfter(this.contain.lastChild);
 1.11783 +          }
 1.11784 +          if (containment === 3) {
 1.11785 +            range.setStartBefore(this.contain.firstChild);
 1.11786 +            range.setEndAfter(this.contain.lastChild);
 1.11787 +          }
 1.11788 +        } else if (this._detectInlineRangeProblems(range)) {
 1.11789 +          var previousElementSibling = range.endContainer.previousElementSibling;
 1.11790 +          if (previousElementSibling) {
 1.11791 +            range.setEnd(previousElementSibling, this._endOffsetForNode(previousElementSibling));
 1.11792 +          }
 1.11793 +        }
 1.11794 +      }
 1.11795 +    },
 1.11796 +
 1.11797 +    _endOffsetForNode: function(node) {
 1.11798 +      var range = document.createRange();
 1.11799 +      range.selectNodeContents(node);
 1.11800 +      return range.endOffset;
 1.11801 +    },
 1.11802 +
 1.11803 +    _detectInlineRangeProblems: function(range) {
 1.11804 +      var position = dom.compareDocumentPosition(range.startContainer, range.endContainer);
 1.11805 +      return (
 1.11806 +        range.endOffset == 0 &&
 1.11807 +        position & 4 //Node.DOCUMENT_POSITION_FOLLOWING
 1.11808 +      );
 1.11809 +    },
 1.11810 +
 1.11811 +    getRange: function(dontFix) {
 1.11812 +      var selection = this.getSelection(),
 1.11813 +          range = selection && selection.rangeCount && selection.getRangeAt(0);
 1.11814 +
 1.11815 +      if (dontFix !== true) {
 1.11816 +        this.fixRangeOverflow(range);
 1.11817 +      }
 1.11818 +
 1.11819 +      return range;
 1.11820 +    },
 1.11821 +
 1.11822 +    getOwnUneditables: function() {
 1.11823 +      var allUneditables = dom.query(this.contain, '.' + this.unselectableClass),
 1.11824 +          deepUneditables = dom.query(allUneditables, '.' + this.unselectableClass);
 1.11825 +
 1.11826 +      return wysihtml.lang.array(allUneditables).without(deepUneditables);
 1.11827 +    },
 1.11828 +
 1.11829 +    // Returns an array of ranges that belong only to this editable
 1.11830 +    // Needed as uneditable block in contenteditabel can split range into pieces
 1.11831 +    // If manipulating content reverse loop is usually needed as manipulation can shift subsequent ranges
 1.11832 +    getOwnRanges: function()  {
 1.11833 +      var ranges = [],
 1.11834 +          r = this.getRange(),
 1.11835 +          tmpRanges;
 1.11836 +
 1.11837 +      if (r) { ranges.push(r); }
 1.11838 +
 1.11839 +      if (this.unselectableClass && this.contain && r) {
 1.11840 +        var uneditables = this.getOwnUneditables(),
 1.11841 +            tmpRange;
 1.11842 +        if (uneditables.length > 0) {
 1.11843 +          for (var i = 0, imax = uneditables.length; i < imax; i++) {
 1.11844 +            tmpRanges = [];
 1.11845 +            for (var j = 0, jmax = ranges.length; j < jmax; j++) {
 1.11846 +              if (ranges[j]) {
 1.11847 +                switch (ranges[j].compareNode(uneditables[i])) {
 1.11848 +                  case 2:
 1.11849 +                    // all selection inside uneditable. remove
 1.11850 +                  break;
 1.11851 +                  case 3:
 1.11852 +                    //section begins before and ends after uneditable. spilt
 1.11853 +                    tmpRange = ranges[j].cloneRange();
 1.11854 +                    tmpRange.setEndBefore(uneditables[i]);
 1.11855 +                    tmpRanges.push(tmpRange);
 1.11856 +
 1.11857 +                    tmpRange = ranges[j].cloneRange();
 1.11858 +                    tmpRange.setStartAfter(uneditables[i]);
 1.11859 +                    tmpRanges.push(tmpRange);
 1.11860 +                  break;
 1.11861 +                  default:
 1.11862 +                    // in all other cases uneditable does not touch selection. dont modify
 1.11863 +                    tmpRanges.push(ranges[j]);
 1.11864 +                }
 1.11865 +              }
 1.11866 +              ranges = tmpRanges;
 1.11867 +            }
 1.11868 +          }
 1.11869 +        }
 1.11870 +      }
 1.11871 +      return ranges;
 1.11872 +    },
 1.11873 +
 1.11874 +    getSelection: function() {
 1.11875 +      return rangy.getSelection(this.win);
 1.11876 +    },
 1.11877 +
 1.11878 +    // Sets selection in document to a given range
 1.11879 +    // Set selection method detects if it fails to set any selection in document and returns null on fail
 1.11880 +    // (especially needed in webkit where some ranges just can not create selection for no reason)
 1.11881 +    setSelection: function(range) {
 1.11882 +      var selection = rangy.getSelection(this.win);
 1.11883 +      selection.setSingleRange(range);
 1.11884 +      return (selection && selection.anchorNode && selection.focusNode) ? selection : null;
 1.11885 +    },
 1.11886 +
 1.11887 +
 1.11888 +
 1.11889 +    // Webkit has an ancient error of not selecting all contents when uneditable block element is first or last in editable area
 1.11890 +    selectAll: function() {
 1.11891 +      var range = this.createRange(),
 1.11892 +          composer = this.composer,
 1.11893 +          that = this,
 1.11894 +          blankEndNode = getWebkitSelectionFixNode(this.composer.element),
 1.11895 +          blankStartNode = getWebkitSelectionFixNode(this.composer.element),
 1.11896 +          s;
 1.11897 +
 1.11898 +      var doSelect = function() {
 1.11899 +        range.setStart(composer.element, 0);
 1.11900 +        range.setEnd(composer.element, composer.element.childNodes.length);
 1.11901 +        s = that.setSelection(range);
 1.11902 +      };
 1.11903 +
 1.11904 +      var notSelected = function() {
 1.11905 +        return !s || (s.nativeSelection && s.nativeSelection.type && (s.nativeSelection.type === "Caret" || s.nativeSelection.type === "None"));
 1.11906 +      }
 1.11907 +
 1.11908 +      wysihtml.dom.removeInvisibleSpaces(this.composer.element);
 1.11909 +      doSelect();
 1.11910 +
 1.11911 +      if (this.composer.element.firstChild && notSelected())  {
 1.11912 +        // Try fixing end
 1.11913 +        this.composer.element.appendChild(blankEndNode);
 1.11914 +        doSelect();
 1.11915 +
 1.11916 +        if (notSelected()) {
 1.11917 +          // Remove end fix
 1.11918 +          blankEndNode.parentNode.removeChild(blankEndNode);
 1.11919 +
 1.11920 +          // Try fixing beginning
 1.11921 +          this.composer.element.insertBefore(blankStartNode, this.composer.element.firstChild);
 1.11922 +          doSelect();
 1.11923 +
 1.11924 +          if (notSelected()) {
 1.11925 +            // Try fixing both
 1.11926 +            this.composer.element.appendChild(blankEndNode);
 1.11927 +            doSelect();
 1.11928 +          }
 1.11929 +        }
 1.11930 +      }
 1.11931 +    },
 1.11932 +
 1.11933 +    createRange: function() {
 1.11934 +      return rangy.createRange(this.doc);
 1.11935 +    },
 1.11936 +
 1.11937 +    isCollapsed: function() {
 1.11938 +        return this.getSelection().isCollapsed;
 1.11939 +    },
 1.11940 +
 1.11941 +    getHtml: function() {
 1.11942 +      return this.getSelection().toHtml();
 1.11943 +    },
 1.11944 +
 1.11945 +    getPlainText: function () {
 1.11946 +      return this.getSelection().toString();
 1.11947 +    },
 1.11948 +
 1.11949 +    isEndToEndInNode: function(nodeNames) {
 1.11950 +      var range = this.getRange(),
 1.11951 +          parentElement = range.commonAncestorContainer,
 1.11952 +          startNode = range.startContainer,
 1.11953 +          endNode = range.endContainer;
 1.11954 +
 1.11955 +
 1.11956 +        if (parentElement.nodeType === wysihtml.TEXT_NODE) {
 1.11957 +          parentElement = parentElement.parentNode;
 1.11958 +        }
 1.11959 +
 1.11960 +        if (startNode.nodeType === wysihtml.TEXT_NODE && !(/^\s*$/).test(startNode.data.substr(range.startOffset))) {
 1.11961 +          return false;
 1.11962 +        }
 1.11963 +
 1.11964 +        if (endNode.nodeType === wysihtml.TEXT_NODE && !(/^\s*$/).test(endNode.data.substr(range.endOffset))) {
 1.11965 +          return false;
 1.11966 +        }
 1.11967 +
 1.11968 +        while (startNode && startNode !== parentElement) {
 1.11969 +          if (startNode.nodeType !== wysihtml.TEXT_NODE && !wysihtml.dom.contains(parentElement, startNode)) {
 1.11970 +            return false;
 1.11971 +          }
 1.11972 +          if (wysihtml.dom.domNode(startNode).prev({ignoreBlankTexts: true})) {
 1.11973 +            return false;
 1.11974 +          }
 1.11975 +          startNode = startNode.parentNode;
 1.11976 +        }
 1.11977 +
 1.11978 +        while (endNode && endNode !== parentElement) {
 1.11979 +          if (endNode.nodeType !== wysihtml.TEXT_NODE && !wysihtml.dom.contains(parentElement, endNode)) {
 1.11980 +            return false;
 1.11981 +          }
 1.11982 +          if (wysihtml.dom.domNode(endNode).next({ignoreBlankTexts: true})) {
 1.11983 +            return false;
 1.11984 +          }
 1.11985 +          endNode = endNode.parentNode;
 1.11986 +        }
 1.11987 +
 1.11988 +        return (wysihtml.lang.array(nodeNames).contains(parentElement.nodeName)) ? parentElement : false;
 1.11989 +    },
 1.11990 +
 1.11991 +    isInThisEditable: function() {
 1.11992 +      var sel = this.getSelection(),
 1.11993 +          fnode = sel.focusNode,
 1.11994 +          anode = sel.anchorNode;
 1.11995 +
 1.11996 +      // In IE node contains will not work for textnodes, thus taking parentNode
 1.11997 +      if (fnode && fnode.nodeType !== 1) {
 1.11998 +        fnode = fnode.parentNode;
 1.11999 +      }
 1.12000 +
 1.12001 +      if (anode && anode.nodeType !== 1) {
 1.12002 +        anode = anode.parentNode;
 1.12003 +      }
 1.12004 +
 1.12005 +      return anode && fnode &&
 1.12006 +             (wysihtml.dom.contains(this.composer.element, fnode) || this.composer.element === fnode) &&
 1.12007 +             (wysihtml.dom.contains(this.composer.element, anode) || this.composer.element === anode);
 1.12008 +    },
 1.12009 +
 1.12010 +    deselect: function() {
 1.12011 +      var sel = this.getSelection();
 1.12012 +      sel && sel.removeAllRanges();
 1.12013 +    }
 1.12014 +  });
 1.12015 +
 1.12016 +})(wysihtml);
 1.12017 +
 1.12018 +/**
 1.12019 + * Rich Text Query/Formatting Commands
 1.12020 + *
 1.12021 + * @example
 1.12022 + *    var commands = new wysihtml.Commands(editor);
 1.12023 + */
 1.12024 +wysihtml.Commands = Base.extend(
 1.12025 +  /** @scope wysihtml.Commands.prototype */ {
 1.12026 +  constructor: function(editor) {
 1.12027 +    this.editor   = editor;
 1.12028 +    this.composer = editor.composer;
 1.12029 +    this.doc      = this.composer.doc;
 1.12030 +  },
 1.12031 +
 1.12032 +  /**
 1.12033 +   * Check whether the browser supports the given command
 1.12034 +   *
 1.12035 +   * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
 1.12036 +   * @example
 1.12037 +   *    commands.supports("createLink");
 1.12038 +   */
 1.12039 +  support: function(command) {
 1.12040 +    return wysihtml.browser.supportsCommand(this.doc, command);
 1.12041 +  },
 1.12042 +
 1.12043 +  /**
 1.12044 +   * Check whether the browser supports the given command
 1.12045 +   *
 1.12046 +   * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList")
 1.12047 +   * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...)
 1.12048 +   * @example
 1.12049 +   *    commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg");
 1.12050 +   */
 1.12051 +  exec: function(command, value) {
 1.12052 +    var obj     = wysihtml.commands[command],
 1.12053 +        args    = wysihtml.lang.array(arguments).get(),
 1.12054 +        method  = obj && obj.exec,
 1.12055 +        result  = null;
 1.12056 +
 1.12057 +    // If composer ahs placeholder unset it before command
 1.12058 +    // Do not apply on commands that are behavioral 
 1.12059 +    if (this.composer.hasPlaceholderSet() && !wysihtml.lang.array(['styleWithCSS', 'enableObjectResizing', 'enableInlineTableEditing']).contains(command)) {
 1.12060 +      this.composer.element.innerHTML = "";
 1.12061 +      this.composer.selection.selectNode(this.composer.element);
 1.12062 +    }
 1.12063 +
 1.12064 +    this.editor.fire("beforecommand:composer");
 1.12065 +
 1.12066 +    if (method) {
 1.12067 +      args.unshift(this.composer);
 1.12068 +      result = method.apply(obj, args);
 1.12069 +    } else {
 1.12070 +      try {
 1.12071 +        // try/catch for buggy firefox
 1.12072 +        result = this.doc.execCommand(command, false, value);
 1.12073 +      } catch(e) {}
 1.12074 +    }
 1.12075 +
 1.12076 +    this.editor.fire("aftercommand:composer");
 1.12077 +    return result;
 1.12078 +  },
 1.12079 +
 1.12080 +  remove: function(command, commandValue) {
 1.12081 +    var obj     = wysihtml.commands[command],
 1.12082 +        args    = wysihtml.lang.array(arguments).get(),
 1.12083 +        method  = obj && obj.remove;
 1.12084 +    if (method) {
 1.12085 +      args.unshift(this.composer);
 1.12086 +      return method.apply(obj, args);
 1.12087 +    }
 1.12088 +  },
 1.12089 +
 1.12090 +  /**
 1.12091 +   * Check whether the current command is active
 1.12092 +   * If the caret is within a bold text, then calling this with command "bold" should return true
 1.12093 +   *
 1.12094 +   * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
 1.12095 +   * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src)
 1.12096 +   * @return {Boolean} Whether the command is active
 1.12097 +   * @example
 1.12098 +   *    var isCurrentSelectionBold = commands.state("bold");
 1.12099 +   */
 1.12100 +  state: function(command, commandValue) {
 1.12101 +    var obj     = wysihtml.commands[command],
 1.12102 +        args    = wysihtml.lang.array(arguments).get(),
 1.12103 +        method  = obj && obj.state;
 1.12104 +    if (method) {
 1.12105 +      args.unshift(this.composer);
 1.12106 +      return method.apply(obj, args);
 1.12107 +    } else {
 1.12108 +      try {
 1.12109 +        // try/catch for buggy firefox
 1.12110 +        return this.doc.queryCommandState(command);
 1.12111 +      } catch(e) {
 1.12112 +        return false;
 1.12113 +      }
 1.12114 +    }
 1.12115 +  },
 1.12116 +
 1.12117 +  /* Get command state parsed value if command has stateValue parsing function */
 1.12118 +  stateValue: function(command) {
 1.12119 +    var obj     = wysihtml.commands[command],
 1.12120 +        args    = wysihtml.lang.array(arguments).get(),
 1.12121 +        method  = obj && obj.stateValue;
 1.12122 +    if (method) {
 1.12123 +      args.unshift(this.composer);
 1.12124 +      return method.apply(obj, args);
 1.12125 +    } else {
 1.12126 +      return false;
 1.12127 +    }
 1.12128 +  }
 1.12129 +});
 1.12130 +
 1.12131 +(function(wysihtml) {
 1.12132 +
 1.12133 +  var nodeOptions = {
 1.12134 +    nodeName: "A",
 1.12135 +    toggle: false
 1.12136 +  };
 1.12137 +
 1.12138 +  function getOptions(value) {
 1.12139 +    var options = typeof value === 'object' ? value : {'href': value};
 1.12140 +    return wysihtml.lang.object({}).merge(nodeOptions).merge({'attribute': value}).get();
 1.12141 +  }
 1.12142 +
 1.12143 +  wysihtml.commands.createLink  = {
 1.12144 +    exec: function(composer, command, value) {
 1.12145 +      var opts = getOptions(value);
 1.12146 +
 1.12147 +      if (composer.selection.isCollapsed() && !this.state(composer, command)) {
 1.12148 +        var textNode = composer.doc.createTextNode(opts.attribute.href);
 1.12149 +        composer.selection.insertNode(textNode);
 1.12150 +        composer.selection.selectNode(textNode);
 1.12151 +      }
 1.12152 +      wysihtml.commands.formatInline.exec(composer, command, opts);
 1.12153 +    },
 1.12154 +
 1.12155 +    state: function(composer, command) {
 1.12156 +      return wysihtml.commands.formatInline.state(composer, command, nodeOptions);
 1.12157 +    }
 1.12158 +  };
 1.12159 +
 1.12160 +})(wysihtml);
 1.12161 +
 1.12162 +/* Formatblock
 1.12163 + * Is used to insert block level elements 
 1.12164 + * It tries to solve the case that some block elements should not contain other block level elements (h1-6, p, ...)
 1.12165 + * 
 1.12166 +*/
 1.12167 +(function(wysihtml) {
 1.12168 +
 1.12169 +  var dom = wysihtml.dom,
 1.12170 +      // When the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
 1.12171 +      // instead of creating a H4 within a H1 which would result in semantically invalid html
 1.12172 +      UNNESTABLE_BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre",
 1.12173 +      BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote",
 1.12174 +      INLINE_ELEMENTS = "b, big, i, small, tt, abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var, a, bdo, br, q, span, sub, sup, button, label, textarea, input, select, u";
 1.12175 +
 1.12176 +  function correctOptionsForSimilarityCheck(options) {
 1.12177 +    return {
 1.12178 +      nodeName: options.nodeName || null,
 1.12179 +      className: (!options.classRegExp) ? options.className || null : null,
 1.12180 +      classRegExp: options.classRegExp || null,
 1.12181 +      styleProperty: options.styleProperty || null
 1.12182 +    };
 1.12183 +  }
 1.12184 +
 1.12185 +  function getRangeNode(node, offset) {
 1.12186 +    if (node.nodeType === 3) {
 1.12187 +      return node;
 1.12188 +    } else {
 1.12189 +      return node.childNodes[offset] || node;
 1.12190 +    }
 1.12191 +  }
 1.12192 +
 1.12193 +  // Returns if node is a line break
 1.12194 +  function isBr(n) {
 1.12195 +    return n && n.nodeType === 1 && n.nodeName === "BR";
 1.12196 +  }
 1.12197 +
 1.12198 +  // Is block level element
 1.12199 +  function isBlock(n, composer) {
 1.12200 +    return n && n.nodeType === 1 && composer.win.getComputedStyle(n).display === "block";
 1.12201 +  }
 1.12202 +
 1.12203 +  // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
 1.12204 +  function isBookmark(n) {
 1.12205 +    return n && n.nodeType === 1 && n.classList.contains('rangySelectionBoundary');
 1.12206 +  }
 1.12207 +
 1.12208 +  // Is line breaking node
 1.12209 +  function isLineBreaking(n, composer) {
 1.12210 +    return isBr(n) || isBlock(n, composer);
 1.12211 +  }
 1.12212 +
 1.12213 +  // Removes empty block level elements
 1.12214 +  function cleanup(composer, newBlockElements) {
 1.12215 +    wysihtml.dom.removeInvisibleSpaces(composer.element);
 1.12216 +    var container = composer.element,
 1.12217 +        allElements = container.querySelectorAll(BLOCK_ELEMENTS),
 1.12218 +        noEditQuery = composer.config.classNames.uneditableContainer + ([""]).concat(BLOCK_ELEMENTS.split(',')).join(", " + composer.config.classNames.uneditableContainer + ' '),
 1.12219 +        uneditables = container.querySelectorAll(noEditQuery),
 1.12220 +        elements = wysihtml.lang.array(allElements).without(uneditables), // Lets not touch uneditable elements and their contents
 1.12221 +        nbIdx;
 1.12222 +
 1.12223 +    for (var i = elements.length; i--;) {
 1.12224 +      if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "" && (newBlockElements.length === 0 || elements[i] !== newBlockElements[newBlockElements.length - 1])) {
 1.12225 +        // If cleanup removes some new block elements. remove them from newblocks array too
 1.12226 +        nbIdx = wysihtml.lang.array(newBlockElements).indexOf(elements[i]);
 1.12227 +        if (nbIdx > -1) {
 1.12228 +          newBlockElements.splice(nbIdx, 1);
 1.12229 +        }
 1.12230 +        elements[i].parentNode.removeChild(elements[i]);
 1.12231 +      }
 1.12232 +    }
 1.12233 +    
 1.12234 +    return newBlockElements;
 1.12235 +  }
 1.12236 +
 1.12237 +  function defaultNodeName(composer) {
 1.12238 +    return composer.config.useLineBreaks ? "DIV" : "P";
 1.12239 +  }
 1.12240 +
 1.12241 +  // The outermost un-nestable block element parent of from node
 1.12242 +  function findOuterBlock(node, container, allBlocks) {
 1.12243 +    var n = node,
 1.12244 +        block = null;
 1.12245 +        
 1.12246 +    while (n && container && n !== container) {
 1.12247 +      if (n.nodeType === 1 && n.matches(allBlocks ? BLOCK_ELEMENTS : UNNESTABLE_BLOCK_ELEMENTS)) {
 1.12248 +        block = n;
 1.12249 +      }
 1.12250 +      n = n.parentNode;
 1.12251 +    }
 1.12252 +
 1.12253 +    return block;
 1.12254 +  }
 1.12255 +
 1.12256 +  // Clone for splitting the inner inline element out of its parent inline elements context
 1.12257 +  // For example if selection is in bold and italic, clone the outer nodes and wrap these around content and return
 1.12258 +  function cloneOuterInlines(node, container) {
 1.12259 +    var n = node,
 1.12260 +        innerNode,
 1.12261 +        parentNode,
 1.12262 +        el = null,
 1.12263 +        el2;
 1.12264 +
 1.12265 +    while (n && container && n !== container) {
 1.12266 +      if (n.nodeType === 1 && n.matches(INLINE_ELEMENTS)) {
 1.12267 +        parentNode = n;
 1.12268 +        if (el === null) {
 1.12269 +          el = n.cloneNode(false);
 1.12270 +          innerNode = el;
 1.12271 +        } else {
 1.12272 +          el2 = n.cloneNode(false);
 1.12273 +          el2.appendChild(el);
 1.12274 +          el = el2;
 1.12275 +        }
 1.12276 +      }
 1.12277 +      n = n.parentNode;
 1.12278 +    }
 1.12279 +
 1.12280 +    return {
 1.12281 +      parent: parentNode,
 1.12282 +      outerNode: el,
 1.12283 +      innerNode: innerNode
 1.12284 +    };
 1.12285 +  }
 1.12286 +
 1.12287 +  // Formats an element according to options nodeName, className, styleProperty, styleValue
 1.12288 +  // If element is not defined, creates new element
 1.12289 +  // if opotions is null, remove format instead
 1.12290 +  function applyOptionsToElement(element, options, composer) {
 1.12291 +
 1.12292 +    if (!element) {
 1.12293 +      element = composer.doc.createElement(options.nodeName || defaultNodeName(composer));
 1.12294 +      // Add invisible space as otherwise webkit cannot set selection or range to it correctly
 1.12295 +      element.appendChild(composer.doc.createTextNode(wysihtml.INVISIBLE_SPACE));
 1.12296 +    }
 1.12297 +
 1.12298 +    if (options.nodeName && element.nodeName !== options.nodeName) {
 1.12299 +      element = dom.renameElement(element, options.nodeName);
 1.12300 +    }
 1.12301 +
 1.12302 +    // Remove similar classes before applying className
 1.12303 +    if (options.classRegExp) {
 1.12304 +      element.className = element.className.replace(options.classRegExp, "");
 1.12305 +    }
 1.12306 +    if (options.className) {
 1.12307 +      element.classList.add(options.className);
 1.12308 +    }
 1.12309 +
 1.12310 +    if (options.styleProperty && typeof options.styleValue !== "undefined") {
 1.12311 +      element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue;
 1.12312 +    }
 1.12313 +
 1.12314 +    return element;
 1.12315 +  }
 1.12316 +
 1.12317 +  // Unsets element properties by options
 1.12318 +  // If nodename given and matches current element, element is unwrapped or converted to default node (depending on presence of class and style attributes)
 1.12319 +  function removeOptionsFromElement(element, options, composer) {
 1.12320 +    var style, classes,
 1.12321 +        prevNode = element.previousSibling,
 1.12322 +        nextNode = element.nextSibling,
 1.12323 +        unwrapped = false;
 1.12324 +
 1.12325 +    if (options.styleProperty) {
 1.12326 +      element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = '';
 1.12327 +    }
 1.12328 +    if (options.className) {
 1.12329 +      element.classList.remove(options.className);
 1.12330 +    }
 1.12331 +
 1.12332 +    if (options.classRegExp) {
 1.12333 +      element.className = element.className.replace(options.classRegExp, "");
 1.12334 +    }
 1.12335 +
 1.12336 +    // Clean up blank class attribute
 1.12337 +    if (element.getAttribute('class') !== null && element.getAttribute('class').trim() === "") {
 1.12338 +      element.removeAttribute('class');
 1.12339 +    }
 1.12340 +
 1.12341 +    if (options.nodeName && element.nodeName.toLowerCase() === options.nodeName.toLowerCase()) {
 1.12342 +      style = element.getAttribute('style');
 1.12343 +      if (!style || style.trim() === '') {
 1.12344 +        dom.unwrap(element);
 1.12345 +        unwrapped = true;
 1.12346 +      } else {
 1.12347 +        element = dom.renameElement(element, defaultNodeName(composer));
 1.12348 +      }
 1.12349 +    }
 1.12350 +
 1.12351 +    // Clean up blank style attribute
 1.12352 +    if (element.getAttribute('style') !== null && element.getAttribute('style').trim() === "") {
 1.12353 +      element.removeAttribute('style');
 1.12354 +    }
 1.12355 +
 1.12356 +    if (unwrapped) {
 1.12357 +      applySurroundingLineBreaks(prevNode, nextNode, composer);
 1.12358 +    }
 1.12359 +  }
 1.12360 +
 1.12361 +  // Unwraps block level elements from inside content
 1.12362 +  // Useful as not all block level elements can contain other block-levels
 1.12363 +  function unwrapBlocksFromContent(element) {
 1.12364 +    var blocks = element.querySelectorAll(BLOCK_ELEMENTS) || [], // Find unnestable block elements in extracted contents
 1.12365 +        nextEl, prevEl;
 1.12366 +
 1.12367 +    for (var i = blocks.length; i--;) {
 1.12368 +      nextEl = wysihtml.dom.domNode(blocks[i]).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
 1.12369 +      prevEl = wysihtml.dom.domNode(blocks[i]).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
 1.12370 +      
 1.12371 +      if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') {
 1.12372 +        if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') {
 1.12373 +          blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl);
 1.12374 +        }
 1.12375 +      }
 1.12376 +      if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') {
 1.12377 +        if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') {
 1.12378 +          blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl);
 1.12379 +        }
 1.12380 +      }
 1.12381 +      wysihtml.dom.unwrap(blocks[i]);
 1.12382 +    }
 1.12383 +  }
 1.12384 +
 1.12385 +  // Fix ranges that visually cover whole block element to actually cover the block
 1.12386 +  function fixRangeCoverage(range, composer) {
 1.12387 +    var node,
 1.12388 +        start = range.startContainer,
 1.12389 +        end = range.endContainer;
 1.12390 +
 1.12391 +    // If range has only one childNode and it is end to end the range, extend the range to contain the container element too
 1.12392 +    // This ensures the wrapper node is modified and optios added to it
 1.12393 +    if (start && start.nodeType === 1 && start === end) {
 1.12394 +      if (start.firstChild === start.lastChild && range.endOffset === 1) {
 1.12395 +        if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') {
 1.12396 +          range.setStartBefore(start);
 1.12397 +          range.setEndAfter(end);
 1.12398 +        }
 1.12399 +      }
 1.12400 +      return;
 1.12401 +    }
 1.12402 +
 1.12403 +    // If range starts outside of node and ends inside at textrange and covers the whole node visually, extend end to cover the node end too
 1.12404 +    if (start && start.nodeType === 1 && end.nodeType === 3) {
 1.12405 +      if (start.firstChild === end && range.endOffset === end.data.length) {
 1.12406 +        if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') {
 1.12407 +          range.setEndAfter(start);
 1.12408 +        }
 1.12409 +      }
 1.12410 +      return;
 1.12411 +    }
 1.12412 +    
 1.12413 +    // If range ends outside of node and starts inside at textrange and covers the whole node visually, extend start to cover the node start too
 1.12414 +    if (end && end.nodeType === 1 && start.nodeType === 3) {
 1.12415 +      if (end.firstChild === start && range.startOffset === 0) {
 1.12416 +        if (end !== composer.element && end.nodeName !== 'LI' && end.nodeName !== 'TD') {
 1.12417 +          range.setStartBefore(end);
 1.12418 +        }
 1.12419 +      }
 1.12420 +      return;
 1.12421 +    }
 1.12422 +
 1.12423 +    // If range covers a whole textnode and the textnode is the only child of node, extend range to node 
 1.12424 +    if (start && start.nodeType === 3 && start === end && start.parentNode.childNodes.length === 1) {
 1.12425 +      if (range.endOffset == end.data.length && range.startOffset === 0) {
 1.12426 +        node = start.parentNode;
 1.12427 +        if (node !== composer.element && node.nodeName !== 'LI' && node.nodeName !== 'TD') {
 1.12428 +          range.setStartBefore(node);
 1.12429 +          range.setEndAfter(node);
 1.12430 +        }
 1.12431 +      }
 1.12432 +      return;
 1.12433 +    }
 1.12434 +  }
 1.12435 +  
 1.12436 +  // Scans ranges array for insertion points that are not allowed to insert block tags fixes/splits illegal ranges
 1.12437 +  // Some places do not allow block level elements inbetween (inside ul and outside li)
 1.12438 +  // TODO: might need extending for other nodes besides li (maybe dd,dl,dt)
 1.12439 +  function fixNotPermittedInsertionPoints(ranges) {
 1.12440 +    var newRanges = [],
 1.12441 +        lis, j, maxj, tmpRange, rangePos, closestLI;
 1.12442 +        
 1.12443 +    for (var i = 0, maxi = ranges.length; i < maxi; i++) {
 1.12444 +      
 1.12445 +      // Fixes range start and end positions if inside UL or OL element (outside of LI)
 1.12446 +      if (ranges[i].startContainer.nodeType === 1 && ranges[i].startContainer.matches('ul, ol')) {
 1.12447 +        ranges[i].setStart(ranges[i].startContainer.childNodes[ranges[i].startOffset], 0);
 1.12448 +      }
 1.12449 +      if (ranges[i].endContainer.nodeType === 1 && ranges[i].endContainer.matches('ul, ol')) {
 1.12450 +        closestLI = ranges[i].endContainer.childNodes[Math.max(ranges[i].endOffset - 1, 0)];
 1.12451 +        if (closestLI.childNodes) {
 1.12452 +          ranges[i].setEnd(closestLI, closestLI.childNodes.length);
 1.12453 +        }
 1.12454 +      }
 1.12455 +
 1.12456 +      // Get all LI eleemnts in selection (fully or partially covered)
 1.12457 +      // And make sure ranges are either inside LI or outside UL/OL
 1.12458 +      // Split and add new ranges as needed to cover same range content
 1.12459 +      // TODO: Needs improvement to accept DL, DD, DT
 1.12460 +      lis = ranges[i].getNodes([1], function(node) {
 1.12461 +        return node.nodeName === "LI";
 1.12462 +      });
 1.12463 +      if (lis.length > 0) {
 1.12464 +      
 1.12465 +        for (j = 0, maxj = lis.length; j < maxj; j++) {
 1.12466 +          rangePos = ranges[i].compareNode(lis[j]);
 1.12467 +
 1.12468 +          // Fixes start of range that crosses LI border
 1.12469 +          if (rangePos === ranges[i].NODE_AFTER || rangePos === ranges[i].NODE_INSIDE) {
 1.12470 +            // Range starts before and ends inside the node
 1.12471 +
 1.12472 +            tmpRange = ranges[i].cloneRange();
 1.12473 +            closestLI = wysihtml.dom.domNode(lis[j]).prev({nodeTypes: [1]});
 1.12474 +            
 1.12475 +            if (closestLI) {
 1.12476 +              tmpRange.setEnd(closestLI, closestLI.childNodes.length);
 1.12477 +            } else if (lis[j].closest('ul, ol')) {
 1.12478 +              tmpRange.setEndBefore(lis[j].closest('ul, ol'));
 1.12479 +            } else {
 1.12480 +              tmpRange.setEndBefore(lis[j]);
 1.12481 +            }
 1.12482 +            newRanges.push(tmpRange);
 1.12483 +            ranges[i].setStart(lis[j], 0);
 1.12484 +          }
 1.12485 +          
 1.12486 +          // Fixes end of range that crosses li border
 1.12487 +          if (rangePos === ranges[i].NODE_BEFORE || rangePos === ranges[i].NODE_INSIDE) {
 1.12488 +            // Range starts inside the node and ends after node
 1.12489 +            
 1.12490 +            tmpRange = ranges[i].cloneRange();
 1.12491 +            tmpRange.setEnd(lis[j], lis[j].childNodes.length);
 1.12492 +            newRanges.push(tmpRange);
 1.12493 +            
 1.12494 +            // Find next LI in list and if present set range to it, else 
 1.12495 +            closestLI = wysihtml.dom.domNode(lis[j]).next({nodeTypes: [1]});
 1.12496 +            if (closestLI) {
 1.12497 +              ranges[i].setStart(closestLI, 0);
 1.12498 +            } else if (lis[j].closest('ul, ol')) {
 1.12499 +              ranges[i].setStartAfter(lis[j].closest('ul, ol'));
 1.12500 +            } else {
 1.12501 +              ranges[i].setStartAfter(lis[j]);
 1.12502 +            } 
 1.12503 +          }
 1.12504 +        }
 1.12505 +        newRanges.push(ranges[i]);
 1.12506 +      } else {
 1.12507 +        newRanges.push(ranges[i]);
 1.12508 +      }
 1.12509 +    }
 1.12510 +    return newRanges;
 1.12511 +  }
 1.12512 +  
 1.12513 +  // Return options object with nodeName set if original did not have any
 1.12514 +  // Node name is set to local or global default
 1.12515 +  function getOptionsWithNodename(options, defaultName, composer) {
 1.12516 +    var correctedOptions = (options) ? wysihtml.lang.object(options).clone(true) : null;
 1.12517 +    if (correctedOptions) {  
 1.12518 +      correctedOptions.nodeName = correctedOptions.nodeName || defaultName || defaultNodeName(composer);
 1.12519 +    }
 1.12520 +    return correctedOptions;
 1.12521 +  }
 1.12522 +  
 1.12523 +  // Injects document fragment to range ensuring outer elements are split to a place where block elements are allowed to be inserted
 1.12524 +  // Also wraps empty clones of split parent tags around fragment to keep formatting
 1.12525 +  // If firstOuterBlock is given assume that instead of finding outer (useful for solving cases of some blocks are allowed into others while others are not)
 1.12526 +  function injectFragmentToRange(fragment, range, composer, firstOuterBlock) {
 1.12527 +    var rangeStartContainer = range.startContainer,
 1.12528 +        firstOuterBlock = firstOuterBlock || findOuterBlock(rangeStartContainer, composer.element, true),
 1.12529 +        outerInlines, first, last, prev, next;
 1.12530 +    
 1.12531 +    if (firstOuterBlock) {
 1.12532 +      // If selection starts inside un-nestable block, split-escape the unnestable point and insert node between
 1.12533 +      first = fragment.firstChild;
 1.12534 +      last = fragment.lastChild;
 1.12535 +
 1.12536 +      composer.selection.splitElementAtCaret(firstOuterBlock, fragment);
 1.12537 +
 1.12538 +      next = wysihtml.dom.domNode(last).next({nodeTypes: [1,3], ignoreBlankTexts: true});
 1.12539 +      prev = wysihtml.dom.domNode(first).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
 1.12540 +
 1.12541 +      if (first && !isLineBreaking(first, composer) && prev && !isLineBreaking(prev, composer)) {
 1.12542 +        first.parentNode.insertBefore(composer.doc.createElement('br'), first);
 1.12543 +      }
 1.12544 +
 1.12545 +      if (last && !isLineBreaking(last, composer) && next && !isLineBreaking(next, composer)) {
 1.12546 +        next.parentNode.insertBefore(composer.doc.createElement('br'), next);
 1.12547 +      }
 1.12548 +
 1.12549 +    } else {
 1.12550 +      // Ensure node does not get inserted into an inline where it is not allowed
 1.12551 +      outerInlines = cloneOuterInlines(rangeStartContainer, composer.element);
 1.12552 +      if (outerInlines.outerNode && outerInlines.innerNode && outerInlines.parent) {
 1.12553 +        if (fragment.childNodes.length === 1) {
 1.12554 +          while(fragment.firstChild.firstChild) {
 1.12555 +            outerInlines.innerNode.appendChild(fragment.firstChild.firstChild);
 1.12556 +          }
 1.12557 +          fragment.firstChild.appendChild(outerInlines.outerNode);
 1.12558 +        }
 1.12559 +        composer.selection.splitElementAtCaret(outerInlines.parent, fragment);
 1.12560 +      } else {
 1.12561 +        var fc = fragment.firstChild,
 1.12562 +            lc = fragment.lastChild;
 1.12563 +
 1.12564 +        range.insertNode(fragment);
 1.12565 +        // restore range position as it might get lost in webkit sometimes
 1.12566 +        range.setStartBefore(fc);
 1.12567 +        range.setEndAfter(lc);
 1.12568 +      }
 1.12569 +    }
 1.12570 +  }
 1.12571 +  
 1.12572 +  // Removes all block formatting from range
 1.12573 +  function clearRangeBlockFromating(range, closestBlockName, composer) {
 1.12574 +    var r = range.cloneRange(),
 1.12575 +        prevNode = getRangeNode(r.startContainer, r.startOffset).previousSibling,
 1.12576 +        nextNode = getRangeNode(r.endContainer, r.endOffset).nextSibling,
 1.12577 +        content = r.extractContents(),
 1.12578 +        fragment = composer.doc.createDocumentFragment(),
 1.12579 +        children, blocks,
 1.12580 +        first = true;
 1.12581 +        
 1.12582 +    while(content.firstChild) {
 1.12583 +      // Iterate over all selection content first level childNodes
 1.12584 +      if (content.firstChild.nodeType === 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
 1.12585 +        // If node is a block element
 1.12586 +        // Split block formating and add new block to wrap caret
 1.12587 +        
 1.12588 +        unwrapBlocksFromContent(content.firstChild);
 1.12589 +        children = wysihtml.dom.unwrap(content.firstChild);
 1.12590 +        
 1.12591 +        // Add line break before if needed
 1.12592 +        if (children.length > 0) {
 1.12593 +          if (
 1.12594 +            (fragment.lastChild && (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer))) ||
 1.12595 +            (!fragment.lastChild && prevNode && (prevNode.nodeType !== 1 || isLineBreaking(prevNode, composer)))
 1.12596 +          ){
 1.12597 +            fragment.appendChild(composer.doc.createElement('BR'));
 1.12598 +          }
 1.12599 +        }
 1.12600 +        
 1.12601 +        for (var c = 0, cmax = children.length; c < cmax; c++) {
 1.12602 +          fragment.appendChild(children[c]);
 1.12603 +        }
 1.12604 +        
 1.12605 +        // Add line break after if needed
 1.12606 +        if (children.length > 0) {
 1.12607 +          if (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer)) {
 1.12608 +            if (nextNode || fragment.lastChild !== content.lastChild) {
 1.12609 +              fragment.appendChild(composer.doc.createElement('BR'));
 1.12610 +            }
 1.12611 +          }
 1.12612 +        }
 1.12613 +        
 1.12614 +      } else {
 1.12615 +        fragment.appendChild(content.firstChild);
 1.12616 +      }
 1.12617 +      
 1.12618 +      first = false;
 1.12619 +    }
 1.12620 +    blocks = wysihtml.lang.array(fragment.childNodes).get();
 1.12621 +    injectFragmentToRange(fragment, r, composer);
 1.12622 +    return blocks;
 1.12623 +  }
 1.12624 +  
 1.12625 +  // When block node is inserted, look surrounding nodes and remove surplous linebreak tags (as block format breaks line itself)
 1.12626 +  function removeSurroundingLineBreaks(prevNode, nextNode, composer) {
 1.12627 +    var prevPrev = prevNode && wysihtml.dom.domNode(prevNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
 1.12628 +    if (isBr(nextNode)) {
 1.12629 +      nextNode.parentNode.removeChild(nextNode);
 1.12630 +    }
 1.12631 +    if (isBr(prevNode) && (!prevPrev || prevPrev.nodeType !== 1 || composer.win.getComputedStyle(prevPrev).display !== "block")) {
 1.12632 +      prevNode.parentNode.removeChild(prevNode);
 1.12633 +    }
 1.12634 +  }
 1.12635 +
 1.12636 +  function applySurroundingLineBreaks(prevNode, nextNode, composer) {
 1.12637 +    var prevPrev;
 1.12638 +
 1.12639 +    if (prevNode && isBookmark(prevNode)) {
 1.12640 +      prevNode = prevNode.previousSibling;
 1.12641 +    }
 1.12642 +    if (nextNode && isBookmark(nextNode)) {
 1.12643 +      nextNode = nextNode.nextSibling;
 1.12644 +    }
 1.12645 +
 1.12646 +    prevPrev = prevNode && prevNode.previousSibling;
 1.12647 +
 1.12648 +    if (prevNode && (prevNode.nodeType !== 1 || (composer.win.getComputedStyle(prevNode).display !== "block" && !isBr(prevNode))) && prevNode.parentNode) {
 1.12649 +      prevNode.parentNode.insertBefore(composer.doc.createElement('br'), prevNode.nextSibling);
 1.12650 +    }
 1.12651 +
 1.12652 +    if (nextNode && (nextNode.nodeType !== 1 || composer.win.getComputedStyle(nextNode).display !== "block") && nextNode.parentNode) {
 1.12653 +      nextNode.parentNode.insertBefore(composer.doc.createElement('br'), nextNode);
 1.12654 +    }
 1.12655 +  }
 1.12656 +
 1.12657 +  var isWhitespaceBefore = function (textNode, offset) {
 1.12658 +    var str = textNode.data ? textNode.data.slice(0, offset) : "";
 1.12659 +    return (/^\s*$/).test(str);
 1.12660 +  }
 1.12661 +
 1.12662 +  var isWhitespaceAfter = function (textNode, offset) {
 1.12663 +    var str = textNode.data ? textNode.data.slice(offset) : "";
 1.12664 +    return (/^\s*$/).test(str);
 1.12665 +  }
 1.12666 +
 1.12667 +  var trimBlankTextsAndBreaks = function(fragment) {
 1.12668 +    if (fragment) {
 1.12669 +      while (fragment.firstChild && fragment.firstChild.nodeType === 3 && (/^\s*$/).test(fragment.firstChild.data) && fragment.lastChild !== fragment.firstChild) {
 1.12670 +        fragment.removeChild(fragment.firstChild);
 1.12671 +      }
 1.12672 +
 1.12673 +      while (fragment.lastChild && fragment.lastChild.nodeType === 3 && (/^\s*$/).test(fragment.lastChild.data) && fragment.lastChild !== fragment.firstChild) {
 1.12674 +        fragment.removeChild(fragment.lastChild);
 1.12675 +      }
 1.12676 +
 1.12677 +      if (fragment.firstChild && fragment.firstChild.nodeType === 1 && fragment.firstChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) {
 1.12678 +        fragment.removeChild(fragment.firstChild);
 1.12679 +      }
 1.12680 +
 1.12681 +      if (fragment.lastChild && fragment.lastChild.nodeType === 1 && fragment.lastChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) {
 1.12682 +        fragment.removeChild(fragment.lastChild);
 1.12683 +      }
 1.12684 +    }
 1.12685 +  }
 1.12686 +
 1.12687 +  // Wrap the range with a block level element
 1.12688 +  // If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur
 1.12689 +  function wrapRangeWithElement(range, options, closestBlockName, composer) {
 1.12690 +    var similarOptions = options ? correctOptionsForSimilarityCheck(options) : null,
 1.12691 +        r = range.cloneRange(),
 1.12692 +        rangeStartContainer = r.startContainer,
 1.12693 +        startNode = getRangeNode(r.startContainer, r.startOffset),
 1.12694 +        endNode = getRangeNode(r.endContainer, r.endOffset),
 1.12695 +        prevNode = (r.startContainer === startNode && startNode.nodeType === 3 && !isWhitespaceBefore(startNode, r.startOffset)) ? startNode :  wysihtml.dom.domNode(startNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}),
 1.12696 +        nextNode = (
 1.12697 +          (
 1.12698 +            r.endContainer.nodeType === 1 &&
 1.12699 +            r.endContainer.childNodes[r.endOffset] === endNode &&
 1.12700 +            (
 1.12701 +              endNode.nodeType === 1 ||
 1.12702 +              !isWhitespaceAfter(endNode, r.endOffset) &&
 1.12703 +              !wysihtml.dom.domNode(endNode).is.rangyBookmark()
 1.12704 +            )
 1.12705 +          ) || (
 1.12706 +            r.endContainer === endNode &&
 1.12707 +            endNode.nodeType === 3 &&
 1.12708 +            !isWhitespaceAfter(endNode, r.endOffset)
 1.12709 +          )
 1.12710 +        ) ? endNode : wysihtml.dom.domNode(endNode).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
 1.12711 +        content = r.extractContents(),
 1.12712 +        fragment = composer.doc.createDocumentFragment(),
 1.12713 +        similarOuterBlock = similarOptions ? wysihtml.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null,
 1.12714 +        splitAllBlocks = !closestBlockName || !options || (options.nodeName === "BLOCKQUOTE" && closestBlockName === "BLOCKQUOTE"),
 1.12715 +        firstOuterBlock = similarOuterBlock || findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start
 1.12716 +        wrapper, blocks, children,
 1.12717 +        firstc, lastC;
 1.12718 +
 1.12719 +    if (wysihtml.dom.domNode(nextNode).is.rangyBookmark()) {
 1.12720 +      endNode = nextNode;
 1.12721 +      nextNode = endNode.nextSibling;
 1.12722 +    }
 1.12723 +
 1.12724 +    trimBlankTextsAndBreaks(content);
 1.12725 +
 1.12726 +    if (options && options.nodeName === "BLOCKQUOTE") {
 1.12727 +      
 1.12728 +      // If blockquote is to be inserted no quessing just add it as outermost block on line or selection
 1.12729 +      var tmpEl = applyOptionsToElement(null, options, composer);
 1.12730 +      tmpEl.appendChild(content);
 1.12731 +      fragment.appendChild(tmpEl);
 1.12732 +      blocks = [tmpEl];
 1.12733 +      
 1.12734 +    } else {
 1.12735 +
 1.12736 +      if (!content.firstChild) {
 1.12737 +        // IF selection is caret (can happen if line is empty) add format around tag 
 1.12738 +        fragment.appendChild(applyOptionsToElement(null, options, composer));
 1.12739 +      } else {
 1.12740 +
 1.12741 +        while(content.firstChild) {
 1.12742 +          // Iterate over all selection content first level childNodes
 1.12743 +          
 1.12744 +          if (content.firstChild.nodeType == 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
 1.12745 +            
 1.12746 +            // If node is a block element
 1.12747 +            // Escape(split) block formatting at caret
 1.12748 +            applyOptionsToElement(content.firstChild, options, composer);
 1.12749 +            if (content.firstChild.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
 1.12750 +              unwrapBlocksFromContent(content.firstChild);
 1.12751 +            }
 1.12752 +            fragment.appendChild(content.firstChild);
 1.12753 +            
 1.12754 +          } else {
 1.12755 +            
 1.12756 +            // Wrap subsequent non-block nodes inside new block element
 1.12757 +            wrapper = applyOptionsToElement(null, getOptionsWithNodename(options, closestBlockName, composer), composer);
 1.12758 +            while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) {
 1.12759 +              if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
 1.12760 +                unwrapBlocksFromContent(content.firstChild);
 1.12761 +              }
 1.12762 +              wrapper.appendChild(content.firstChild);
 1.12763 +            }
 1.12764 +            fragment.appendChild(wrapper);
 1.12765 +          }
 1.12766 +        }
 1.12767 +      }
 1.12768 +
 1.12769 +      blocks = wysihtml.lang.array(fragment.childNodes).get();
 1.12770 +    }
 1.12771 +    injectFragmentToRange(fragment, r, composer, firstOuterBlock);
 1.12772 +    removeSurroundingLineBreaks(prevNode, nextNode, composer);
 1.12773 +
 1.12774 +    // Fix webkit madness by inserting linebreak rangy after cursor marker to blank last block
 1.12775 +    // (if it contains rangy bookmark, so selection can be restored later correctly)
 1.12776 +    if (blocks.length > 0 &&
 1.12777 +      (
 1.12778 +        typeof blocks[blocks.length - 1].lastChild === "undefined" || wysihtml.dom.domNode(blocks[blocks.length - 1].lastChild).is.rangyBookmark()
 1.12779 +      )
 1.12780 +    ) {
 1.12781 +      blocks[blocks.length - 1].appendChild(composer.doc.createElement('br'));
 1.12782 +    }
 1.12783 +    return blocks;
 1.12784 +  }
 1.12785 +
 1.12786 +  // Find closest block level element
 1.12787 +  function getParentBlockNodeName(element, composer) {
 1.12788 +    var parentNode = wysihtml.dom.getParentElement(element, {
 1.12789 +          query: BLOCK_ELEMENTS
 1.12790 +        }, null, composer.element);
 1.12791 +
 1.12792 +    return (parentNode) ? parentNode.nodeName : null;
 1.12793 +  }
 1.12794 +  
 1.12795 +  // Expands caret to cover the closest block that:
 1.12796 +  //   * cannot contain other block level elements (h1-6,p, etc)
 1.12797 +  //   * Has the same nodeName that is to be inserted
 1.12798 +  //   * has insertingNodeName
 1.12799 +  //   * is DIV if insertingNodeName is not present
 1.12800 +  //
 1.12801 +  // If nothing found selects the current line
 1.12802 +  function expandCaretToBlock(composer, insertingNodeName) {
 1.12803 +    var parent = wysihtml.dom.getParentElement(composer.selection.getOwnRanges()[0].startContainer, {
 1.12804 +          query: UNNESTABLE_BLOCK_ELEMENTS + ', ' + (insertingNodeName ? insertingNodeName.toLowerCase() : 'div'),
 1.12805 +        }, null, composer.element),
 1.12806 +        range;
 1.12807 +
 1.12808 +    if (parent) {
 1.12809 +      range = composer.selection.createRange();
 1.12810 +      range.selectNode(parent);
 1.12811 +      composer.selection.setSelection(range);
 1.12812 +    } else if (!composer.isEmpty()) {
 1.12813 +      composer.selection.selectLine();
 1.12814 +    }
 1.12815 +  }
 1.12816 +  
 1.12817 +  // Set selection to begin inside first created block element (beginning of it) and end inside (and after content) of last block element
 1.12818 +  // TODO: Checking nodetype might be unnescescary as nodes inserted by formatBlock are nodetype 1 anyway
 1.12819 +  function selectElements(newBlockElements, composer) {
 1.12820 +    var range = composer.selection.createRange(),
 1.12821 +        lastEl = newBlockElements[newBlockElements.length - 1],
 1.12822 +        lastOffset = (lastEl.nodeType === 1 && lastEl.childNodes) ? lastEl.childNodes.length | 0 :  lastEl.length || 0;
 1.12823 +
 1.12824 +    range.setStart(newBlockElements[0], 0);
 1.12825 +    range.setEnd(lastEl, lastOffset);
 1.12826 +    range.select();
 1.12827 +  }
 1.12828 +  
 1.12829 +  // Get all ranges from selection (takes out uneditables and out of editor parts) and apply format to each
 1.12830 +  // Return created/modified block level elements 
 1.12831 +  // Method can be either "apply" or "remove"
 1.12832 +  function formatSelection(method, composer, options) {
 1.12833 +    var ranges = composer.selection.getOwnRanges(),
 1.12834 +        newBlockElements = [],
 1.12835 +        closestBlockName;
 1.12836 +        
 1.12837 +    // Some places do not allow block level elements inbetween (inside ul and outside li, inside table and outside of td/th)
 1.12838 +    ranges = fixNotPermittedInsertionPoints(ranges);
 1.12839 +        
 1.12840 +    for (var i = ranges.length; i--;) {
 1.12841 +      fixRangeCoverage(ranges[i], composer);
 1.12842 +      closestBlockName = getParentBlockNodeName(ranges[i].startContainer, composer);
 1.12843 +      if (method === "remove") {
 1.12844 +        newBlockElements = newBlockElements.concat(clearRangeBlockFromating(ranges[i], closestBlockName, composer));
 1.12845 +      } else {
 1.12846 +        newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, closestBlockName, composer));
 1.12847 +      }
 1.12848 +    }
 1.12849 +    return newBlockElements;
 1.12850 +  }
 1.12851 +  
 1.12852 +  // If properties is passed as a string, look for tag with that tagName/query 
 1.12853 +  function parseOptions(options) {
 1.12854 +    if (typeof options === "string") {
 1.12855 +      options = {
 1.12856 +        nodeName: options.toUpperCase()
 1.12857 +      };
 1.12858 +    }
 1.12859 +    return options;
 1.12860 +  }
 1.12861 +
 1.12862 +  function caretIsOnEmptyLine(composer) {
 1.12863 +    var caretInfo;
 1.12864 +    if (composer.selection.isCollapsed()) {
 1.12865 +      caretInfo = composer.selection.getNodesNearCaret();
 1.12866 +      if (caretInfo && caretInfo.caretNode) {
 1.12867 +        if (
 1.12868 +          // caret is allready breaknode
 1.12869 +          wysihtml.dom.domNode(caretInfo.caretNode).is.lineBreak() ||
 1.12870 +          // caret is textnode
 1.12871 +          (caretInfo.caretNode.nodeType === 3 && caretInfo.textOffset === 0 && (!caretInfo.prevNode || wysihtml.dom.domNode(caretInfo.prevNode).is.lineBreak())) ||
 1.12872 +          // Caret is temprorary rangy selection marker
 1.12873 +          (caretInfo.caretNode.nodeType === 1 && caretInfo.caretNode.classList.contains('rangySelectionBoundary') &&
 1.12874 +            (!caretInfo.prevNode || wysihtml.dom.domNode(caretInfo.prevNode).is.lineBreak() || wysihtml.dom.domNode(caretInfo.prevNode).is.block()) &&
 1.12875 +            (!caretInfo.nextNode || wysihtml.dom.domNode(caretInfo.nextNode).is.lineBreak() || wysihtml.dom.domNode(caretInfo.nextNode).is.block())
 1.12876 +          )
 1.12877 +        ) {
 1.12878 +          return true;
 1.12879 +        }
 1.12880 +      }
 1.12881 +    }
 1.12882 +    return false;
 1.12883 +  }
 1.12884 +
 1.12885 +  wysihtml.commands.formatBlock = {
 1.12886 +    exec: function(composer, command, options) {
 1.12887 +      options = parseOptions(options);
 1.12888 +      var newBlockElements = [],
 1.12889 +          ranges, range, bookmark, state, closestBlockName;
 1.12890 +
 1.12891 +      // Find if current format state is active if options.toggle is set as true
 1.12892 +      // In toggle case active state elemets are formatted instead of working directly on selection
 1.12893 +      if (options && options.toggle) {
 1.12894 +        state = this.state(composer, command, options);
 1.12895 +      }
 1.12896 +      if (state) {
 1.12897 +        // Remove format from state nodes if toggle set and state on and selection is collapsed
 1.12898 +        bookmark = rangy.saveSelection(composer.win);
 1.12899 +        for (var j = 0, jmax = state.length; j < jmax; j++) {
 1.12900 +          removeOptionsFromElement(state[j], options, composer);
 1.12901 +        }
 1.12902 +
 1.12903 +      } else {
 1.12904 +        // If selection is caret expand it to cover nearest suitable block element or row if none found
 1.12905 +        if (composer.selection.isCollapsed()) {
 1.12906 +          bookmark = rangy.saveSelection(composer.win);
 1.12907 +          if (caretIsOnEmptyLine(composer)) {
 1.12908 +            composer.selection.selectLine();
 1.12909 +          } else {
 1.12910 +            expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
 1.12911 +          }
 1.12912 +        }
 1.12913 +        if (options) {
 1.12914 +          newBlockElements = formatSelection("apply", composer, options);
 1.12915 +        } else {
 1.12916 +          // Options == null means block formatting should be removed from selection
 1.12917 +          newBlockElements = formatSelection("remove", composer);
 1.12918 +        }
 1.12919 +        
 1.12920 +      }
 1.12921 +
 1.12922 +      // Remove empty block elements that may be left behind
 1.12923 +      // Also remove them from new blocks list
 1.12924 +      newBlockElements = cleanup(composer, newBlockElements);
 1.12925 +      
 1.12926 +      // Restore selection
 1.12927 +      if (bookmark) {
 1.12928 +        rangy.restoreSelection(bookmark);
 1.12929 +      } else {
 1.12930 +        selectElements(newBlockElements, composer);
 1.12931 +      }
 1.12932 +    },
 1.12933 +    
 1.12934 +    // Removes all block formatting from selection
 1.12935 +    remove: function(composer, command, options) {
 1.12936 +      options = parseOptions(options);
 1.12937 +      var newBlockElements, bookmark;
 1.12938 +      
 1.12939 +      // If selection is caret expand it to cover nearest suitable block element or row if none found
 1.12940 +      if (composer.selection.isCollapsed()) {
 1.12941 +        bookmark = rangy.saveSelection(composer.win);
 1.12942 +        expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
 1.12943 +      }
 1.12944 +      
 1.12945 +      newBlockElements = formatSelection("remove", composer);
 1.12946 +      newBlockElements = cleanup(composer, newBlockElements);
 1.12947 +      
 1.12948 +      // Restore selection
 1.12949 +      if (bookmark) {
 1.12950 +        rangy.restoreSelection(bookmark);
 1.12951 +      } else {
 1.12952 +        selectElements(newBlockElements, composer);
 1.12953 +      }
 1.12954 +    },
 1.12955 +
 1.12956 +    // If options as null is passed returns status describing all block level elements
 1.12957 +    state: function(composer, command, options) {
 1.12958 +      options = parseOptions(options);
 1.12959 +
 1.12960 +      var nodes = composer.selection.filterElements((function (element) { // Finds matching elements inside selection
 1.12961 +            return wysihtml.dom.domNode(element).test(options || { query: BLOCK_ELEMENTS });
 1.12962 +          }).bind(this)),
 1.12963 +          parentNodes = composer.selection.getSelectedOwnNodes(),
 1.12964 +          parent;
 1.12965 +
 1.12966 +      // Finds matching elements that are parents of selection and adds to nodes list
 1.12967 +      for (var i = 0, maxi = parentNodes.length; i < maxi; i++) {
 1.12968 +        parent = dom.getParentElement(parentNodes[i], options || { query: BLOCK_ELEMENTS }, null, composer.element);
 1.12969 +        if (parent && nodes.indexOf(parent) === -1) {
 1.12970 +          nodes.push(parent);
 1.12971 +        }
 1.12972 +      }
 1.12973 +
 1.12974 +      return (nodes.length === 0) ? false : nodes;
 1.12975 +    }
 1.12976 +
 1.12977 +  };
 1.12978 +})(wysihtml);
 1.12979 +
 1.12980 +/**
 1.12981 + * Unifies all inline tags additions and removals
 1.12982 + * See https://github.com/Voog/wysihtml/pull/169 for specification of action
 1.12983 + */
 1.12984 +
 1.12985 +(function(wysihtml) {
 1.12986 +
 1.12987 +  var defaultTag = "SPAN",
 1.12988 +      INLINE_ELEMENTS = "b, big, i, small, tt, abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var, a, bdo, br, q, span, sub, sup, button, label, textarea, input, select, u",
 1.12989 +      queryAliasMap = {
 1.12990 +        "b": "b, strong",
 1.12991 +        "strong": "b, strong",
 1.12992 +        "em": "em, i",
 1.12993 +        "i": "em, i"
 1.12994 +      };
 1.12995 +
 1.12996 +  function hasNoClass(element) {
 1.12997 +    return (/^\s*$/).test(element.className);
 1.12998 +  }
 1.12999 +
 1.13000 +  function hasNoStyle(element) {
 1.13001 +    return !element.getAttribute('style') || (/^\s*$/).test(element.getAttribute('style'));
 1.13002 +  }
 1.13003 +
 1.13004 +  // Associative arrays in javascript are really objects and do not have length defined
 1.13005 +  // Thus have to check emptyness in a different way
 1.13006 +  function hasNoAttributes(element) {
 1.13007 +    var attr = wysihtml.dom.getAttributes(element);
 1.13008 +    return wysihtml.lang.object(attr).isEmpty();
 1.13009 +  }
 1.13010 +
 1.13011 +  // compares two nodes if they are semantically the same
 1.13012 +  // Used in cleanup to find consequent semantically similar elements for merge
 1.13013 +  function isSameNode(element1, element2) {
 1.13014 +    var classes1, classes2,
 1.13015 +        attr1, attr2;
 1.13016 +
 1.13017 +    if (element1.nodeType !== 1 || element2.nodeType !== 1) {
 1.13018 +      return false;
 1.13019 +    }
 1.13020 +
 1.13021 +    if (element1.nodeName !== element2.nodeName) {
 1.13022 +      return false;
 1.13023 +    }
 1.13024 +
 1.13025 +    classes1 = element1.className.trim().replace(/\s+/g, ' ').split(' ');
 1.13026 +    classes2 = element2.className.trim().replace(/\s+/g, ' ').split(' ');
 1.13027 +    if (wysihtml.lang.array(classes1).without(classes2).length > 0) {
 1.13028 +      return false;
 1.13029 +    }
 1.13030 +
 1.13031 +    attr1 = wysihtml.dom.getAttributes(element1);
 1.13032 +    attr2 = wysihtml.dom.getAttributes(element2);
 1.13033 +
 1.13034 +    if (attr1.length !== attr2.length || !wysihtml.lang.object(wysihtml.lang.object(attr1).difference(attr2)).isEmpty()) {
 1.13035 +      return false;
 1.13036 +    }
 1.13037 +
 1.13038 +    return true;
 1.13039 +  }
 1.13040 +
 1.13041 +  function createWrapNode(textNode, options) {
 1.13042 +    var nodeName = options && options.nodeName || defaultTag,
 1.13043 +        element = textNode.ownerDocument.createElement(nodeName);
 1.13044 +
 1.13045 +    // Remove similar classes before applying className
 1.13046 +    if (options.classRegExp) {
 1.13047 +      element.className = element.className.replace(options.classRegExp, "");
 1.13048 +    }
 1.13049 +
 1.13050 +    if (options.className) {
 1.13051 +      element.classList.add(options.className);
 1.13052 +    }
 1.13053 +
 1.13054 +    if (options.styleProperty && typeof options.styleValue !== "undefined") {
 1.13055 +      element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue;
 1.13056 +    }
 1.13057 +
 1.13058 +    if (options.attribute) {
 1.13059 +      if (typeof options.attribute === "object") {
 1.13060 +        for (var a in options.attribute) {
 1.13061 +          if (options.attribute.hasOwnProperty(a)) {
 1.13062 +            element.setAttribute(a, options.attribute[a]);
 1.13063 +          }
 1.13064 +        }
 1.13065 +      } else if (typeof options.attributeValue !== "undefined") {
 1.13066 +        element.setAttribute(options.attribute, options.attributeValue);
 1.13067 +      }
 1.13068 +    }
 1.13069 +
 1.13070 +    return element;
 1.13071 +  }
 1.13072 +
 1.13073 +  // Tests if attr2 list contains all attributes present in attr1
 1.13074 +  // Note: attr 1 can have more attributes than attr2
 1.13075 +  function containsSameAttributes(attr1, attr2) {
 1.13076 +    for (var a in attr1) {
 1.13077 +      if (attr1.hasOwnProperty(a)) {
 1.13078 +        if (typeof attr2[a] === undefined || attr2[a] !== attr1[a]) {
 1.13079 +          return false;
 1.13080 +        }
 1.13081 +      }
 1.13082 +    }
 1.13083 +    return true;
 1.13084 +  }
 1.13085 +
 1.13086 +  // If attrbutes and values are the same > remove
 1.13087 +  // if attributes or values 
 1.13088 +  function updateElementAttributes(element, newAttributes, toggle) {
 1.13089 +    var attr = wysihtml.dom.getAttributes(element),
 1.13090 +        fullContain = containsSameAttributes(newAttributes, attr),
 1.13091 +        attrDifference = wysihtml.lang.object(attr).difference(newAttributes),
 1.13092 +        a, b;
 1.13093 +
 1.13094 +    if (fullContain && toggle !== false) {
 1.13095 +      for (a in newAttributes) {
 1.13096 +        if (newAttributes.hasOwnProperty(a)) {
 1.13097 +          element.removeAttribute(a);
 1.13098 +        }
 1.13099 +      }
 1.13100 +    } else {
 1.13101 +
 1.13102 +      /*if (!wysihtml.lang.object(attrDifference).isEmpty()) {
 1.13103 +        for (b in attrDifference) {
 1.13104 +          if (attrDifference.hasOwnProperty(b)) {
 1.13105 +            element.removeAttribute(b);
 1.13106 +          }
 1.13107 +        }
 1.13108 +      }*/
 1.13109 +
 1.13110 +      for (a in newAttributes) {
 1.13111 +        if (newAttributes.hasOwnProperty(a)) {
 1.13112 +          element.setAttribute(a, newAttributes[a]);
 1.13113 +        }
 1.13114 +      }
 1.13115 +    }
 1.13116 +  }
 1.13117 +
 1.13118 +  function updateFormatOfElement(element, options) {
 1.13119 +    var attr, newNode, a, newAttributes, nodeNameQuery, nodeQueryMatch;
 1.13120 +
 1.13121 +    if (options.className) {
 1.13122 +      if (options.toggle !== false && element.classList.contains(options.className)) {
 1.13123 +        element.classList.remove(options.className);
 1.13124 +      } else {
 1.13125 +        if (options.classRegExp) {
 1.13126 +          element.className = element.className.replace(options.classRegExp, '');
 1.13127 +        }
 1.13128 +        element.classList.add(options.className);
 1.13129 +      }
 1.13130 +      if (hasNoClass(element)) {
 1.13131 +        element.removeAttribute('class');
 1.13132 +      }
 1.13133 +    }
 1.13134 +
 1.13135 +    // change/remove style
 1.13136 +    if (options.styleProperty) {
 1.13137 +      if (options.toggle !== false && element.style[wysihtml.browser.fixStyleKey(options.styleProperty)].trim().replace(/, /g, ",") === options.styleValue) {
 1.13138 +        element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = '';
 1.13139 +      } else {
 1.13140 +        element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue;
 1.13141 +      }
 1.13142 +    }
 1.13143 +    if (hasNoStyle(element)) {
 1.13144 +      element.removeAttribute('style');
 1.13145 +    }
 1.13146 +
 1.13147 +    if (options.attribute) {
 1.13148 +      if (typeof options.attribute === "object") {
 1.13149 +        newAttributes =  options.attribute;
 1.13150 +      } else {
 1.13151 +        newAttributes = {};
 1.13152 +        newAttributes[options.attribute] = options.attributeValue || '';
 1.13153 +      }
 1.13154 +      updateElementAttributes(element, newAttributes, options.toggle);
 1.13155 +    }
 1.13156 +
 1.13157 +
 1.13158 +    // Handle similar semantically same elements (queryAliasMap)
 1.13159 +    nodeNameQuery = options.nodeName ? queryAliasMap[options.nodeName.toLowerCase()] || options.nodeName.toLowerCase() : null;
 1.13160 +    nodeQueryMatch = nodeNameQuery ? wysihtml.dom.domNode(element).test({ query: nodeNameQuery }) : false;
 1.13161 +    
 1.13162 +    // Unwrap element if no attributes present and node name given
 1.13163 +    // or no attributes and if no nodename set but node is the default
 1.13164 +    if (!options.nodeName || options.nodeName === defaultTag || nodeQueryMatch) {
 1.13165 +      if (
 1.13166 +        ((options.toggle !== false && nodeQueryMatch) || (!options.nodeName && element.nodeName === defaultTag)) &&
 1.13167 +        hasNoClass(element) && hasNoStyle(element) && hasNoAttributes(element)
 1.13168 +      ) {
 1.13169 +        wysihtml.dom.unwrap(element);
 1.13170 +      }
 1.13171 +
 1.13172 +    }
 1.13173 +  }
 1.13174 +
 1.13175 +  // Fetch all textnodes in selection
 1.13176 +  // Empty textnodes are ignored except the one containing text caret
 1.13177 +  function getSelectedTextNodes(selection, splitBounds) {
 1.13178 +    var textNodes = [];
 1.13179 +
 1.13180 +    if (!selection.isCollapsed()) {
 1.13181 +      textNodes = textNodes.concat(selection.getOwnNodes([3], function(node) {
 1.13182 +        // Exclude empty nodes except caret node
 1.13183 +        return (!wysihtml.dom.domNode(node).is.emptyTextNode());
 1.13184 +      }, splitBounds));
 1.13185 +    }
 1.13186 +
 1.13187 +    return textNodes;
 1.13188 +  }
 1.13189 +
 1.13190 +  function findSimilarTextNodeWrapper(textNode, options, container, exact) {
 1.13191 +    var node = textNode,
 1.13192 +        similarOptions = exact ? options : correctOptionsForSimilarityCheck(options);
 1.13193 +
 1.13194 +    do {
 1.13195 +      if (node.nodeType === 1 && isSimilarNode(node, similarOptions)) {
 1.13196 +        return node;
 1.13197 +      }
 1.13198 +      node = node.parentNode;
 1.13199 +    } while (node && node !== container);
 1.13200 +
 1.13201 +    return null;
 1.13202 +  }
 1.13203 +
 1.13204 +  function correctOptionsForSimilarityCheck(options) {
 1.13205 +    return {
 1.13206 +      nodeName: options.nodeName || null,
 1.13207 +      className: (!options.classRegExp) ? options.className || null : null,
 1.13208 +      classRegExp: options.classRegExp || null,
 1.13209 +      styleProperty: options.styleProperty || null
 1.13210 +    };
 1.13211 +  }
 1.13212 +
 1.13213 +  // Finds inline node with similar nodeName/style/className
 1.13214 +  // If nodeName is specified inline node with the same (or alias) nodeName is expected to prove similar regardless of attributes
 1.13215 +  function isSimilarNode(node, options) {
 1.13216 +    var o;
 1.13217 +    if (options.nodeName) {
 1.13218 +      var query = queryAliasMap[options.nodeName.toLowerCase()] || options.nodeName.toLowerCase();
 1.13219 +      return wysihtml.dom.domNode(node).test({ query: query });
 1.13220 +    } else {
 1.13221 +      o = wysihtml.lang.object(options).clone();
 1.13222 +      o.query = INLINE_ELEMENTS; // make sure only inline elements with styles and classes are counted
 1.13223 +      return wysihtml.dom.domNode(node).test(o);
 1.13224 +    }
 1.13225 +  }
 1.13226 +
 1.13227 +  function selectRange(composer, range) {
 1.13228 +    var d = document.documentElement || document.body,
 1.13229 +        oldScrollTop  = d.scrollTop,
 1.13230 +        oldScrollLeft = d.scrollLeft,
 1.13231 +        selection = rangy.getSelection(composer.win);
 1.13232 +
 1.13233 +    rangy.getSelection(composer.win).removeAllRanges();
 1.13234 +    
 1.13235 +    // IE looses focus of contenteditable on removeallranges and can not set new selection unless contenteditable is focused again
 1.13236 +    try {
 1.13237 +      rangy.getSelection(composer.win).addRange(range);
 1.13238 +    } catch (e) {}
 1.13239 +    if (!composer.doc.activeElement || !wysihtml.dom.contains(composer.element, composer.doc.activeElement)) {
 1.13240 +      composer.element.focus();
 1.13241 +      d.scrollTop  = oldScrollTop;
 1.13242 +      d.scrollLeft = oldScrollLeft;
 1.13243 +      rangy.getSelection(composer.win).addRange(range);
 1.13244 +    }
 1.13245 +  }
 1.13246 +
 1.13247 +  function selectTextNodes(textNodes, composer) {
 1.13248 +    var range = rangy.createRange(composer.doc),
 1.13249 +        lastText = textNodes[textNodes.length - 1];
 1.13250 +
 1.13251 +    if (textNodes[0] && lastText) {
 1.13252 +      range.setStart(textNodes[0], 0);
 1.13253 +      range.setEnd(lastText, lastText.length);
 1.13254 +      selectRange(composer, range);
 1.13255 +    }
 1.13256 +    
 1.13257 +  }
 1.13258 +
 1.13259 +  function selectTextNode(composer, node, start, end) {
 1.13260 +    var range = rangy.createRange(composer.doc);
 1.13261 +    if (node) {
 1.13262 +      range.setStart(node, start);
 1.13263 +      range.setEnd(node, typeof end !== 'undefined' ? end : start);
 1.13264 +      selectRange(composer, range);
 1.13265 +    }
 1.13266 +  }
 1.13267 +
 1.13268 +  function getState(composer, options, exact) {
 1.13269 +    var searchNodes = getSelectedTextNodes(composer.selection),
 1.13270 +        nodes = [],
 1.13271 +        partial = false,
 1.13272 +        node, range, caretNode;
 1.13273 +
 1.13274 +    if (composer.selection.isInThisEditable()) {
 1.13275 +
 1.13276 +      if (searchNodes.length === 0 && composer.selection.isCollapsed()) {
 1.13277 +        caretNode = composer.selection.getSelection().anchorNode;
 1.13278 +        if (!caretNode) {
 1.13279 +          // selection not in editor
 1.13280 +          return {
 1.13281 +              nodes: [],
 1.13282 +              partial: false
 1.13283 +          };
 1.13284 +        }
 1.13285 +        if (caretNode.nodeType === 3) {
 1.13286 +          searchNodes = [caretNode];
 1.13287 +        }
 1.13288 +      }
 1.13289 +
 1.13290 +      // Handle collapsed selection caret
 1.13291 +      if (!searchNodes.length) {
 1.13292 +        range = composer.selection.getOwnRanges()[0];
 1.13293 +        if (range) {
 1.13294 +          searchNodes = [range.endContainer];
 1.13295 +        }
 1.13296 +      }
 1.13297 +
 1.13298 +      for (var i = 0, maxi = searchNodes.length; i < maxi; i++) {
 1.13299 +        node = findSimilarTextNodeWrapper(searchNodes[i], options, composer.element, exact);
 1.13300 +        if (node) {
 1.13301 +          nodes.push(node);
 1.13302 +        } else {
 1.13303 +          partial = true;
 1.13304 +        }
 1.13305 +      }
 1.13306 +
 1.13307 +    }
 1.13308 +    
 1.13309 +    return {
 1.13310 +      nodes: nodes,
 1.13311 +      partial: partial
 1.13312 +    };
 1.13313 +  }
 1.13314 +
 1.13315 +  // Returns if caret is inside a word in textnode (not on boundary)
 1.13316 +  // If selection anchornode is not text node, returns false
 1.13317 +  function caretIsInsideWord(selection) {
 1.13318 +    var anchor, offset, beforeChar, afterChar;
 1.13319 +    if (selection) {
 1.13320 +      anchor = selection.anchorNode;
 1.13321 +      offset = selection.anchorOffset;
 1.13322 +      if (anchor && anchor.nodeType === 3 && offset > 0 && offset < anchor.data.length) {
 1.13323 +        beforeChar = anchor.data[offset - 1];
 1.13324 +        afterChar = anchor.data[offset];
 1.13325 +        return (/\w/).test(beforeChar) && (/\w/).test(afterChar);
 1.13326 +      }
 1.13327 +    }
 1.13328 +    return false;
 1.13329 +  }
 1.13330 +
 1.13331 +  // Returns a range and textnode containing object from caret position covering a whole word
 1.13332 +  // wordOffsety describes the original position of caret in the new textNode 
 1.13333 +  // Caret has to be inside a textNode.
 1.13334 +  function getRangeForWord(selection) {
 1.13335 +    var anchor, offset, doc, range, offsetStart, offsetEnd, beforeChar, afterChar,
 1.13336 +        txtNodes = [];
 1.13337 +    if (selection) {
 1.13338 +      anchor = selection.anchorNode;
 1.13339 +      offset = offsetStart = offsetEnd = selection.anchorOffset;
 1.13340 +      doc = anchor.ownerDocument;
 1.13341 +      range = rangy.createRange(doc);
 1.13342 +
 1.13343 +      if (anchor && anchor.nodeType === 3) {
 1.13344 +
 1.13345 +        while (offsetStart > 0 && (/\w/).test(anchor.data[offsetStart - 1])) {
 1.13346 +          offsetStart--;
 1.13347 +        }
 1.13348 +
 1.13349 +        while (offsetEnd < anchor.data.length && (/\w/).test(anchor.data[offsetEnd])) {
 1.13350 +          offsetEnd++;
 1.13351 +        }
 1.13352 +
 1.13353 +        range.setStartAndEnd(anchor, offsetStart, offsetEnd);
 1.13354 +        range.splitBoundaries();
 1.13355 +        txtNodes = range.getNodes([3], function(node) {
 1.13356 +          return (!wysihtml.dom.domNode(node).is.emptyTextNode());
 1.13357 +        });
 1.13358 +
 1.13359 +        return {
 1.13360 +          wordOffset: offset - offsetStart,
 1.13361 +          range: range,
 1.13362 +          textNode: txtNodes[0]
 1.13363 +        };
 1.13364 +
 1.13365 +      }
 1.13366 +    }
 1.13367 +    return false;
 1.13368 +  }
 1.13369 +
 1.13370 +  // Contents of 2 elements are merged to fitst element. second element is removed as consequence
 1.13371 +  function mergeContents(element1, element2) {
 1.13372 +    while (element2.firstChild) {
 1.13373 +      element1.appendChild(element2.firstChild);
 1.13374 +    }
 1.13375 +    element2.parentNode.removeChild(element2);
 1.13376 +  }
 1.13377 +
 1.13378 +  function mergeConsequentSimilarElements(elements) {
 1.13379 +    for (var i = elements.length; i--;) {
 1.13380 +      
 1.13381 +      if (elements[i] && elements[i].parentNode) { // Test if node is not allready removed in cleanup
 1.13382 +
 1.13383 +        if (elements[i].nextSibling && isSameNode(elements[i], elements[i].nextSibling)) {
 1.13384 +          mergeContents(elements[i], elements[i].nextSibling);
 1.13385 +        }
 1.13386 +
 1.13387 +        if (elements[i].previousSibling && isSameNode(elements[i]  , elements[i].previousSibling)) {
 1.13388 +          mergeContents(elements[i].previousSibling, elements[i]);
 1.13389 +        }
 1.13390 +
 1.13391 +      }
 1.13392 +    }
 1.13393 +  }
 1.13394 +
 1.13395 +  function cleanupAndSetSelection(composer, textNodes, options) {
 1.13396 +    if (textNodes.length > 0) {
 1.13397 +      selectTextNodes(textNodes, composer);
 1.13398 +    }
 1.13399 +    mergeConsequentSimilarElements(getState(composer, options).nodes);
 1.13400 +    if (textNodes.length > 0) {
 1.13401 +      selectTextNodes(textNodes, composer);
 1.13402 +    }
 1.13403 +  }
 1.13404 +
 1.13405 +  function cleanupAndSetCaret(composer, textNode, offset, options) {
 1.13406 +    selectTextNode(composer, textNode, offset);
 1.13407 +    mergeConsequentSimilarElements(getState(composer, options).nodes);
 1.13408 +    selectTextNode(composer, textNode, offset);
 1.13409 +  }
 1.13410 +
 1.13411 +  // Formats a textnode with given options
 1.13412 +  function formatTextNode(textNode, options) {
 1.13413 +    var wrapNode = createWrapNode(textNode, options);
 1.13414 +
 1.13415 +    textNode.parentNode.insertBefore(wrapNode, textNode);
 1.13416 +    wrapNode.appendChild(textNode);
 1.13417 +  }
 1.13418 +
 1.13419 +  // Changes/toggles format of a textnode
 1.13420 +  function unformatTextNode(textNode, composer, options) {
 1.13421 +    var container = composer.element,
 1.13422 +        wrapNode = findSimilarTextNodeWrapper(textNode, options, container),
 1.13423 +        newWrapNode;
 1.13424 +
 1.13425 +    if (wrapNode) {
 1.13426 +      newWrapNode = wrapNode.cloneNode(false);
 1.13427 +
 1.13428 +      wysihtml.dom.domNode(textNode).escapeParent(wrapNode, newWrapNode);
 1.13429 +      updateFormatOfElement(newWrapNode, options);
 1.13430 +    }
 1.13431 +  }
 1.13432 +
 1.13433 +  // Removes the format around textnode
 1.13434 +  function removeFormatFromTextNode(textNode, composer, options) {
 1.13435 +    var container = composer.element,
 1.13436 +        wrapNode = findSimilarTextNodeWrapper(textNode, options, container);
 1.13437 +
 1.13438 +    if (wrapNode) {
 1.13439 +      wysihtml.dom.domNode(textNode).escapeParent(wrapNode);
 1.13440 +    }
 1.13441 +  }
 1.13442 +
 1.13443 +  // Creates node around caret formated with options
 1.13444 +  function formatTextRange(range, composer, options) {
 1.13445 +    var wrapNode = createWrapNode(range.endContainer, options);
 1.13446 +
 1.13447 +    range.surroundContents(wrapNode);
 1.13448 +    composer.selection.selectNode(wrapNode);
 1.13449 +  }
 1.13450 +
 1.13451 +  // Changes/toggles format of whole selection
 1.13452 +  function updateFormat(composer, textNodes, state, options) {
 1.13453 +    var exactState = getState(composer, options, true),
 1.13454 +        selection = composer.selection.getSelection(),
 1.13455 +        wordObj, textNode, newNode, i;
 1.13456 +
 1.13457 +    if (!textNodes.length) {
 1.13458 +      // Selection is caret
 1.13459 +
 1.13460 +
 1.13461 +      if (options.toggle !== false) {
 1.13462 +        if (caretIsInsideWord(selection)) {
 1.13463 +
 1.13464 +          // Unformat whole word 
 1.13465 +          wordObj = getRangeForWord(selection);
 1.13466 +          textNode = wordObj.textNode;
 1.13467 +          unformatTextNode(wordObj.textNode, composer, options);
 1.13468 +          cleanupAndSetCaret(composer, wordObj.textNode, wordObj.wordOffset, options);
 1.13469 +
 1.13470 +        } else {
 1.13471 +
 1.13472 +          // Escape caret out of format
 1.13473 +          textNode = composer.doc.createTextNode(wysihtml.INVISIBLE_SPACE);
 1.13474 +          newNode = state.nodes[0].cloneNode(false);
 1.13475 +          newNode.appendChild(textNode);
 1.13476 +          composer.selection.splitElementAtCaret(state.nodes[0], newNode);
 1.13477 +          updateFormatOfElement(newNode, options);
 1.13478 +          cleanupAndSetSelection(composer, [textNode], options);
 1.13479 +          var s = composer.selection.getSelection();
 1.13480 +          if (s.anchorNode && s.focusNode) {
 1.13481 +            // Has an error in IE when collapsing selection. probably from rangy
 1.13482 +            try {
 1.13483 +              s.collapseToEnd();
 1.13484 +            } catch (e) {}
 1.13485 +          }
 1.13486 +        }
 1.13487 +      } else {
 1.13488 +        // In non-toggle mode the closest state element has to be found and the state updated differently
 1.13489 +        for (i = state.nodes.length; i--;) {
 1.13490 +          updateFormatOfElement(state.nodes[i], options);
 1.13491 +        }
 1.13492 +      }
 1.13493 +
 1.13494 +    } else {
 1.13495 +
 1.13496 +      if (!exactState.partial && options.toggle !== false) {
 1.13497 +
 1.13498 +        // If whole selection (all textnodes) are in the applied format
 1.13499 +        // remove the format from selection
 1.13500 +        // Non-toggle mode never removes. Remove has to be called explicitly
 1.13501 +        for (i = textNodes.length; i--;) {
 1.13502 +          unformatTextNode(textNodes[i], composer, options);
 1.13503 +        }
 1.13504 +
 1.13505 +      } else {
 1.13506 +        
 1.13507 +        // Selection is partially in format
 1.13508 +        // change it to new if format if textnode allreafy in similar state
 1.13509 +        // else just apply
 1.13510 +        
 1.13511 +        for (i = textNodes.length; i--;) {
 1.13512 +          
 1.13513 +          if (findSimilarTextNodeWrapper(textNodes[i], options, composer.element)) {
 1.13514 +            unformatTextNode(textNodes[i], composer, options);
 1.13515 +          }
 1.13516 +
 1.13517 +          if (!findSimilarTextNodeWrapper(textNodes[i], options, composer.element)) {
 1.13518 +            formatTextNode(textNodes[i], options);
 1.13519 +          }
 1.13520 +        }
 1.13521 +
 1.13522 +      }
 1.13523 +
 1.13524 +      cleanupAndSetSelection(composer, textNodes, options);
 1.13525 +    }
 1.13526 +  }
 1.13527 +
 1.13528 +  // Removes format from selection
 1.13529 +  function removeFormat(composer, textNodes, state, options) {
 1.13530 +    var textNode, textOffset, newNode, i,
 1.13531 +        selection = composer.selection.getSelection();
 1.13532 +
 1.13533 +    if (!textNodes.length) {    
 1.13534 +      textNode = selection.anchorNode;
 1.13535 +      textOffset = selection.anchorOffset;
 1.13536 +
 1.13537 +      for (i = state.nodes.length; i--;) {
 1.13538 +        wysihtml.dom.unwrap(state.nodes[i]);
 1.13539 +      }
 1.13540 +
 1.13541 +      cleanupAndSetCaret(composer, textNode, textOffset, options);
 1.13542 +    } else {
 1.13543 +      for (i = textNodes.length; i--;) {
 1.13544 +        removeFormatFromTextNode(textNodes[i], composer, options);
 1.13545 +      }
 1.13546 +      cleanupAndSetSelection(composer, textNodes, options);
 1.13547 +    }
 1.13548 +  }
 1.13549 +
 1.13550 +  // Adds format to selection
 1.13551 +  function applyFormat(composer, textNodes, options) {
 1.13552 +    var wordObj, i,
 1.13553 +        selection = composer.selection.getSelection();
 1.13554 + 
 1.13555 +    if (!textNodes.length) {
 1.13556 +      // Handle collapsed selection caret and return
 1.13557 +      if (caretIsInsideWord(selection)) {
 1.13558 +
 1.13559 +        wordObj = getRangeForWord(selection);
 1.13560 +        formatTextNode(wordObj.textNode, options);
 1.13561 +        cleanupAndSetCaret(composer, wordObj.textNode, wordObj.wordOffset, options);
 1.13562 +
 1.13563 +      } else {
 1.13564 +        var r = composer.selection.getOwnRanges()[0];
 1.13565 +        if (r) {
 1.13566 +          formatTextRange(r, composer, options);
 1.13567 +        }
 1.13568 +      }
 1.13569 +      
 1.13570 +    } else {
 1.13571 +      // Handle textnodes in selection and apply format
 1.13572 +      for (i = textNodes.length; i--;) {
 1.13573 +        formatTextNode(textNodes[i], options);
 1.13574 +      }
 1.13575 +      cleanupAndSetSelection(composer, textNodes, options);
 1.13576 +    }
 1.13577 +  }
 1.13578 +  
 1.13579 +  // If properties is passed as a string, correct options with that nodeName
 1.13580 +  function fixOptions(options) {
 1.13581 +    options = (typeof options === "string") ? { nodeName: options } : options;
 1.13582 +    if (options.nodeName) { options.nodeName = options.nodeName.toUpperCase(); }
 1.13583 +    return options;
 1.13584 +  }
 1.13585 +
 1.13586 +  wysihtml.commands.formatInline = {
 1.13587 +
 1.13588 +    // Basics:
 1.13589 +    // In case of plain text or inline state not set wrap all non-empty textnodes with
 1.13590 +    // In case a similar inline wrapper node is detected on one of textnodes, the wrapper node is changed (if fully contained) or split and changed (partially contained)
 1.13591 +    //    In case of changing mode every textnode is addressed separatly
 1.13592 +    exec: function(composer, command, options) {
 1.13593 +      options = fixOptions(options);
 1.13594 +
 1.13595 +      // Join adjactent textnodes first
 1.13596 +      composer.element.normalize();
 1.13597 +
 1.13598 +      var textNodes = getSelectedTextNodes(composer.selection, true),
 1.13599 +          state = getState(composer, options);
 1.13600 +      if (state.nodes.length > 0) {
 1.13601 +        // Text allready has the format applied
 1.13602 +        updateFormat(composer, textNodes, state, options);
 1.13603 +      } else {
 1.13604 +        // Selection is not in the applied format
 1.13605 +        applyFormat(composer, textNodes, options);
 1.13606 +      }
 1.13607 +      composer.element.normalize();
 1.13608 +    },
 1.13609 +
 1.13610 +    remove: function(composer, command, options) {
 1.13611 +      options = fixOptions(options);
 1.13612 +      composer.element.normalize();
 1.13613 +
 1.13614 +      var textNodes = getSelectedTextNodes(composer.selection, true),
 1.13615 +          state = getState(composer, options);
 1.13616 +
 1.13617 +      if (state.nodes.length > 0) {
 1.13618 +        // Text allready has the format applied
 1.13619 +        removeFormat(composer, textNodes, state, options);
 1.13620 +      }
 1.13621 +      
 1.13622 +      composer.element.normalize();
 1.13623 +    },
 1.13624 +
 1.13625 +    state: function(composer, command, options) {
 1.13626 +      options = fixOptions(options);
 1.13627 +      var nodes = getState(composer, options, true).nodes;
 1.13628 +      return (nodes.length === 0) ? false : nodes;
 1.13629 +    }
 1.13630 +  };
 1.13631 +
 1.13632 +})(wysihtml);
 1.13633 +
 1.13634 +(function(wysihtml){
 1.13635 +  wysihtml.commands.indentList = {
 1.13636 +    exec: function(composer, command, value) {
 1.13637 +      var listEls = composer.selection.getSelectionParentsByTag('LI');
 1.13638 +      if (listEls) {
 1.13639 +        return this.tryToPushLiLevel(listEls, composer.selection);
 1.13640 +      }
 1.13641 +      return false;
 1.13642 +    },
 1.13643 +
 1.13644 +    state: function(composer, command) {
 1.13645 +        return false;
 1.13646 +    },
 1.13647 +
 1.13648 +    tryToPushLiLevel: function(liNodes, selection) {
 1.13649 +      var listTag, list, prevLi, liNode, prevLiList,
 1.13650 +          found = false;
 1.13651 +
 1.13652 +      selection.executeAndRestoreRangy(function() {
 1.13653 +
 1.13654 +        for (var i = liNodes.length; i--;) {
 1.13655 +          liNode = liNodes[i];
 1.13656 +          listTag = (liNode.parentNode.nodeName === 'OL') ? 'OL' : 'UL';
 1.13657 +          list = liNode.ownerDocument.createElement(listTag);
 1.13658 +          prevLi = wysihtml.dom.domNode(liNode).prev({nodeTypes: [wysihtml.ELEMENT_NODE]});
 1.13659 +          prevLiList = (prevLi) ? prevLi.querySelector('ul, ol') : null;
 1.13660 +
 1.13661 +          if (prevLi) {
 1.13662 +            if (prevLiList) {
 1.13663 +              prevLiList.appendChild(liNode);
 1.13664 +            } else {
 1.13665 +              list.appendChild(liNode);
 1.13666 +              prevLi.appendChild(list);
 1.13667 +            }
 1.13668 +            found = true;
 1.13669 +          }
 1.13670 +        }
 1.13671 +
 1.13672 +      });
 1.13673 +      return found;
 1.13674 +    }
 1.13675 +  };
 1.13676 +}(wysihtml));
 1.13677 +
 1.13678 +(function(wysihtml){
 1.13679 +  wysihtml.commands.insertHTML = {
 1.13680 +    exec: function(composer, command, html) {
 1.13681 +        composer.selection.insertHTML(html);
 1.13682 +    },
 1.13683 +
 1.13684 +    state: function() {
 1.13685 +      return false;
 1.13686 +    }
 1.13687 +  };
 1.13688 +}(wysihtml));
 1.13689 +
 1.13690 +(function(wysihtml) {
 1.13691 +  var LINE_BREAK = "<br>" + (wysihtml.browser.needsSpaceAfterLineBreak() ? " " : "");
 1.13692 +
 1.13693 +  wysihtml.commands.insertLineBreak = {
 1.13694 +    exec: function(composer, command) {
 1.13695 +      composer.selection.insertHTML(LINE_BREAK);
 1.13696 +    },
 1.13697 +
 1.13698 +    state: function() {
 1.13699 +      return false;
 1.13700 +    }
 1.13701 +  };
 1.13702 +})(wysihtml);
 1.13703 +
 1.13704 +wysihtml.commands.insertList = (function(wysihtml) {
 1.13705 +
 1.13706 +  var isNode = function(node, name) {
 1.13707 +    if (node && node.nodeName) {
 1.13708 +      if (typeof name === 'string') {
 1.13709 +        name = [name];
 1.13710 +      }
 1.13711 +      for (var n = name.length; n--;) {
 1.13712 +        if (node.nodeName === name[n]) {
 1.13713 +          return true;
 1.13714 +        }
 1.13715 +      }
 1.13716 +    }
 1.13717 +    return false;
 1.13718 +  };
 1.13719 +
 1.13720 +  var findListEl = function(node, nodeName, composer) {
 1.13721 +    var ret = {
 1.13722 +          el: null,
 1.13723 +          other: false
 1.13724 +        };
 1.13725 +
 1.13726 +    if (node) {
 1.13727 +      var parentLi = wysihtml.dom.getParentElement(node, { query: "li" }, false, composer.element),
 1.13728 +          otherNodeName = (nodeName === "UL") ? "OL" : "UL";
 1.13729 +
 1.13730 +      if (isNode(node, nodeName)) {
 1.13731 +        ret.el = node;
 1.13732 +      } else if (isNode(node, otherNodeName)) {
 1.13733 +        ret = {
 1.13734 +          el: node,
 1.13735 +          other: true
 1.13736 +        };
 1.13737 +      } else if (parentLi) {
 1.13738 +        if (isNode(parentLi.parentNode, nodeName)) {
 1.13739 +          ret.el = parentLi.parentNode;
 1.13740 +        } else if (isNode(parentLi.parentNode, otherNodeName)) {
 1.13741 +          ret = {
 1.13742 +            el : parentLi.parentNode,
 1.13743 +            other: true
 1.13744 +          };
 1.13745 +        }
 1.13746 +      }
 1.13747 +    }
 1.13748 +
 1.13749 +    // do not count list elements outside of composer
 1.13750 +    if (ret.el && !composer.element.contains(ret.el)) {
 1.13751 +      ret.el = null;
 1.13752 +    }
 1.13753 +
 1.13754 +    return ret;
 1.13755 +  };
 1.13756 +
 1.13757 +  var handleSameTypeList = function(el, nodeName, composer) {
 1.13758 +    var otherNodeName = (nodeName === "UL") ? "OL" : "UL",
 1.13759 +        otherLists, innerLists;
 1.13760 +    // Unwrap list
 1.13761 +    // <ul><li>foo</li><li>bar</li></ul>
 1.13762 +    // becomes:
 1.13763 +    // foo<br>bar<br>
 1.13764 +
 1.13765 +    composer.selection.executeAndRestoreRangy(function() {
 1.13766 +      otherLists = getListsInSelection(otherNodeName, composer);
 1.13767 +      if (otherLists.length) {
 1.13768 +        for (var l = otherLists.length; l--;) {
 1.13769 +          wysihtml.dom.renameElement(otherLists[l], nodeName.toLowerCase());
 1.13770 +        }
 1.13771 +      } else {
 1.13772 +        innerLists = getListsInSelection(['OL', 'UL'], composer);
 1.13773 +        for (var i = innerLists.length; i--;) {
 1.13774 +          wysihtml.dom.resolveList(innerLists[i], composer.config.useLineBreaks);
 1.13775 +        }
 1.13776 +        if (innerLists.length === 0) {
 1.13777 +          wysihtml.dom.resolveList(el, composer.config.useLineBreaks);
 1.13778 +        }
 1.13779 +      }
 1.13780 +    });
 1.13781 +  };
 1.13782 +
 1.13783 +  var handleOtherTypeList =  function(el, nodeName, composer) {
 1.13784 +    var otherNodeName = (nodeName === "UL") ? "OL" : "UL";
 1.13785 +    // Turn an ordered list into an unordered list
 1.13786 +    // <ol><li>foo</li><li>bar</li></ol>
 1.13787 +    // becomes:
 1.13788 +    // <ul><li>foo</li><li>bar</li></ul>
 1.13789 +    // Also rename other lists in selection
 1.13790 +    composer.selection.executeAndRestoreRangy(function() {
 1.13791 +      var renameLists = [el].concat(getListsInSelection(otherNodeName, composer));
 1.13792 +
 1.13793 +      // All selection inner lists get renamed too
 1.13794 +      for (var l = renameLists.length; l--;) {
 1.13795 +        wysihtml.dom.renameElement(renameLists[l], nodeName.toLowerCase());
 1.13796 +      }
 1.13797 +    });
 1.13798 +  };
 1.13799 +
 1.13800 +  var getListsInSelection = function(nodeName, composer) {
 1.13801 +      var ranges = composer.selection.getOwnRanges(),
 1.13802 +          renameLists = [];
 1.13803 +
 1.13804 +      for (var r = ranges.length; r--;) {
 1.13805 +        renameLists = renameLists.concat(ranges[r].getNodes([1], function(node) {
 1.13806 +          return isNode(node, nodeName);
 1.13807 +        }));
 1.13808 +      }
 1.13809 +
 1.13810 +      return renameLists;
 1.13811 +  };
 1.13812 +
 1.13813 +  var createListFallback = function(nodeName, composer) {
 1.13814 +    var sel = rangy.saveSelection(composer.win);
 1.13815 +
 1.13816 +    // Fallback for Create list
 1.13817 +    var tempClassName =  "_wysihtml-temp-" + new Date().getTime(),
 1.13818 +        isEmpty, list;
 1.13819 +
 1.13820 +    composer.commands.exec("formatBlock", {
 1.13821 +      "nodeName": "div",
 1.13822 +      "className": tempClassName
 1.13823 +    });
 1.13824 +
 1.13825 +    var tempElement = composer.element.querySelector("." + tempClassName);
 1.13826 +
 1.13827 +    // This space causes new lists to never break on enter
 1.13828 +    var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
 1.13829 +    tempElement.innerHTML = tempElement.innerHTML.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "");
 1.13830 +    if (tempElement) {
 1.13831 +      isEmpty = (/^(\s|(<br>))+$/i).test(tempElement.innerHTML);
 1.13832 +      list = wysihtml.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.classNames.uneditableContainer);
 1.13833 +      if (sel) {
 1.13834 +        rangy.restoreSelection(sel);
 1.13835 +      }
 1.13836 +      if (isEmpty) {
 1.13837 +        composer.selection.selectNode(list.querySelector("li"), true);
 1.13838 +      }
 1.13839 +    }
 1.13840 +  };
 1.13841 +
 1.13842 +  return {
 1.13843 +    exec: function(composer, command, nodeName) {
 1.13844 +      var doc           = composer.doc,
 1.13845 +          cmd           = (nodeName === "OL") ? "insertOrderedList" : "insertUnorderedList",
 1.13846 +          s = composer.selection.getSelection(),
 1.13847 +          anode = s.anchorNode.nodeType === 1 && s.anchorNode.firstChild ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode,
 1.13848 +          fnode = s.focusNode.nodeType === 1 && s.focusNode.firstChild ? s.focusNode.childNodes[s.focusOffset] || s.focusNode.lastChild : s.focusNode,
 1.13849 +          selectedNode, list;
 1.13850 +
 1.13851 +      if (s.isBackwards()) {
 1.13852 +        // swap variables
 1.13853 +        anode = [fnode, fnode = anode][0];
 1.13854 +      }
 1.13855 +
 1.13856 +      if (wysihtml.dom.domNode(fnode).is.emptyTextNode(true) && fnode) {
 1.13857 +        fnode = wysihtml.dom.domNode(fnode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
 1.13858 +      }
 1.13859 +      if (wysihtml.dom.domNode(anode).is.emptyTextNode(true) && anode) {
 1.13860 +        anode = wysihtml.dom.domNode(anode).next({nodeTypes: [1,3], ignoreBlankTexts: true});
 1.13861 +      }
 1.13862 +
 1.13863 +      if (anode && fnode) {
 1.13864 +        if (anode === fnode) {
 1.13865 +          selectedNode = anode;
 1.13866 +        } else {
 1.13867 +          selectedNode = wysihtml.dom.domNode(anode).commonAncestor(fnode, composer.element);
 1.13868 +        }
 1.13869 +      } else {
 1.13870 +        selectedNode  = composer.selection.getSelectedNode();
 1.13871 +      }
 1.13872 +
 1.13873 +      list = findListEl(selectedNode, nodeName, composer);
 1.13874 +
 1.13875 +      if (!list.el) {
 1.13876 +        if (composer.commands.support(cmd)) {
 1.13877 +          doc.execCommand(cmd, false, null);
 1.13878 +        } else {
 1.13879 +          createListFallback(nodeName, composer);
 1.13880 +        }
 1.13881 +      } else if (list.other) {
 1.13882 +        handleOtherTypeList(list.el, nodeName, composer);
 1.13883 +      } else {
 1.13884 +        handleSameTypeList(list.el, nodeName, composer);
 1.13885 +      }
 1.13886 +    },
 1.13887 +
 1.13888 +    state: function(composer, command, nodeName) {
 1.13889 +      var selectedNode = composer.selection.getSelectedNode(),
 1.13890 +          list         = findListEl(selectedNode, nodeName, composer);
 1.13891 +
 1.13892 +      return (list.el && !list.other) ? list.el : false;
 1.13893 +    }
 1.13894 +  };
 1.13895 +
 1.13896 +})(wysihtml);
 1.13897 +
 1.13898 +(function(wysihtml){
 1.13899 +
 1.13900 +  wysihtml.commands.outdentList = {
 1.13901 +    exec: function(composer, command, value) {
 1.13902 +      var listEls = composer.selection.getSelectionParentsByTag('LI');
 1.13903 +      if (listEls) {
 1.13904 +        return this.tryToPullLiLevel(listEls, composer);
 1.13905 +      }
 1.13906 +      return false;
 1.13907 +    },
 1.13908 +
 1.13909 +    state: function(composer, command) {
 1.13910 +        return false;
 1.13911 +    },
 1.13912 +
 1.13913 +    tryToPullLiLevel: function(liNodes, composer) {
 1.13914 +      var listNode, outerListNode, outerLiNode, list, prevLi, liNode, afterList,
 1.13915 +          found = false,
 1.13916 +          that = this;
 1.13917 +
 1.13918 +      composer.selection.executeAndRestoreRangy(function() {
 1.13919 +
 1.13920 +        for (var i = liNodes.length; i--;) {
 1.13921 +          liNode = liNodes[i];
 1.13922 +          if (liNode.parentNode) {
 1.13923 +            listNode = liNode.parentNode;
 1.13924 +
 1.13925 +            if (listNode.tagName === 'OL' || listNode.tagName === 'UL') {
 1.13926 +              found = true;
 1.13927 +
 1.13928 +              outerListNode = wysihtml.dom.getParentElement(listNode.parentNode, { query: 'ol, ul' }, false, composer.element);
 1.13929 +              outerLiNode = wysihtml.dom.getParentElement(listNode.parentNode, { query: 'li' }, false, composer.element);
 1.13930 +
 1.13931 +              if (outerListNode && outerLiNode) {
 1.13932 +
 1.13933 +                if (liNode.nextSibling) {
 1.13934 +                  afterList = that.getAfterList(listNode, liNode);
 1.13935 +                  liNode.appendChild(afterList);
 1.13936 +                }
 1.13937 +                outerListNode.insertBefore(liNode, outerLiNode.nextSibling);
 1.13938 +
 1.13939 +              } else {
 1.13940 +
 1.13941 +                if (liNode.nextSibling) {
 1.13942 +                  afterList = that.getAfterList(listNode, liNode);
 1.13943 +                  liNode.appendChild(afterList);
 1.13944 +                }
 1.13945 +
 1.13946 +                for (var j = liNode.childNodes.length; j--;) {
 1.13947 +                  listNode.parentNode.insertBefore(liNode.childNodes[j], listNode.nextSibling);
 1.13948 +                }
 1.13949 +
 1.13950 +                listNode.parentNode.insertBefore(document.createElement('br'), listNode.nextSibling);
 1.13951 +                liNode.parentNode.removeChild(liNode);
 1.13952 +
 1.13953 +              }
 1.13954 +
 1.13955 +              // cleanup
 1.13956 +              if (listNode.childNodes.length === 0) {
 1.13957 +                  listNode.parentNode.removeChild(listNode);
 1.13958 +              }
 1.13959 +            }
 1.13960 +          }
 1.13961 +        }
 1.13962 +
 1.13963 +      });
 1.13964 +      return found;
 1.13965 +    },
 1.13966 +
 1.13967 +    getAfterList: function(listNode, liNode) {
 1.13968 +      var nodeName = listNode.nodeName,
 1.13969 +          newList = document.createElement(nodeName);
 1.13970 +
 1.13971 +      while (liNode.nextSibling) {
 1.13972 +        newList.appendChild(liNode.nextSibling);
 1.13973 +      }
 1.13974 +      return newList;
 1.13975 +    }
 1.13976 +
 1.13977 +  };
 1.13978 +}(wysihtml));
 1.13979 +
 1.13980 +(function(wysihtml){
 1.13981 +  wysihtml.commands.redo = {
 1.13982 +    exec: function(composer) {
 1.13983 +      return composer.undoManager.redo();
 1.13984 +    },
 1.13985 +
 1.13986 +    state: function(composer) {
 1.13987 +      return false;
 1.13988 +    }
 1.13989 +  };
 1.13990 +}(wysihtml));
 1.13991 +
 1.13992 +(function(wysihtml) {
 1.13993 +
 1.13994 +  var nodeOptions = {
 1.13995 +    nodeName: "A"
 1.13996 +  };
 1.13997 +
 1.13998 +  wysihtml.commands.removeLink = {
 1.13999 +    exec: function(composer, command) {
 1.14000 +      wysihtml.commands.formatInline.remove(composer, command, nodeOptions);
 1.14001 +    },
 1.14002 +
 1.14003 +    state: function(composer, command) {
 1.14004 +      return wysihtml.commands.formatInline.state(composer, command, nodeOptions);
 1.14005 +    }
 1.14006 +  };
 1.14007 +
 1.14008 +})(wysihtml);
 1.14009 +
 1.14010 +(function(wysihtml){
 1.14011 +  wysihtml.commands.undo = {
 1.14012 +    exec: function(composer) {
 1.14013 +      return composer.undoManager.undo();
 1.14014 +    },
 1.14015 +
 1.14016 +    state: function(composer) {
 1.14017 +      return false;
 1.14018 +    }
 1.14019 +  };
 1.14020 +}(wysihtml));
 1.14021 +
 1.14022 +/**
 1.14023 + * Undo Manager for wysihtml
 1.14024 + * slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface
 1.14025 + */
 1.14026 +(function(wysihtml) {
 1.14027 +  var Z_KEY               = 90,
 1.14028 +      Y_KEY               = 89,
 1.14029 +      BACKSPACE_KEY       = 8,
 1.14030 +      DELETE_KEY          = 46,
 1.14031 +      MAX_HISTORY_ENTRIES = 25,
 1.14032 +      DATA_ATTR_NODE      = "data-wysihtml-selection-node",
 1.14033 +      DATA_ATTR_OFFSET    = "data-wysihtml-selection-offset",
 1.14034 +      UNDO_HTML           = '<span id="_wysihtml-undo" class="_wysihtml-temp">' + wysihtml.INVISIBLE_SPACE + '</span>',
 1.14035 +      REDO_HTML           = '<span id="_wysihtml-redo" class="_wysihtml-temp">' + wysihtml.INVISIBLE_SPACE + '</span>',
 1.14036 +      dom                 = wysihtml.dom;
 1.14037 +
 1.14038 +  function cleanTempElements(doc) {
 1.14039 +    var tempElement;
 1.14040 +    while (tempElement = doc.querySelector("._wysihtml-temp")) {
 1.14041 +      tempElement.parentNode.removeChild(tempElement);
 1.14042 +    }
 1.14043 +  }
 1.14044 +
 1.14045 +  wysihtml.UndoManager = wysihtml.lang.Dispatcher.extend(
 1.14046 +    /** @scope wysihtml.UndoManager.prototype */ {
 1.14047 +    constructor: function(editor) {
 1.14048 +      this.editor = editor;
 1.14049 +      this.composer = editor.composer;
 1.14050 +      this.element = this.composer.element;
 1.14051 +
 1.14052 +      this.position = 0;
 1.14053 +      this.historyStr = [];
 1.14054 +      this.historyDom = [];
 1.14055 +
 1.14056 +      this.transact();
 1.14057 +
 1.14058 +      this._observe();
 1.14059 +    },
 1.14060 +
 1.14061 +    _observe: function() {
 1.14062 +      var that      = this,
 1.14063 +          doc       = this.composer.sandbox.getDocument(),
 1.14064 +          lastKey;
 1.14065 +
 1.14066 +      // Catch CTRL+Z and CTRL+Y
 1.14067 +      dom.observe(this.element, "keydown", function(event) {
 1.14068 +        if (event.altKey || (!event.ctrlKey && !event.metaKey)) {
 1.14069 +          return;
 1.14070 +        }
 1.14071 +
 1.14072 +        var keyCode = event.keyCode,
 1.14073 +            isUndo = keyCode === Z_KEY && !event.shiftKey,
 1.14074 +            isRedo = (keyCode === Z_KEY && event.shiftKey) || (keyCode === Y_KEY);
 1.14075 +
 1.14076 +        if (isUndo) {
 1.14077 +          that.undo();
 1.14078 +          event.preventDefault();
 1.14079 +        } else if (isRedo) {
 1.14080 +          that.redo();
 1.14081 +          event.preventDefault();
 1.14082 +        }
 1.14083 +      });
 1.14084 +
 1.14085 +      // Catch delete and backspace
 1.14086 +      dom.observe(this.element, "keydown", function(event) {
 1.14087 +        var keyCode = event.keyCode;
 1.14088 +        if (keyCode === lastKey) {
 1.14089 +          return;
 1.14090 +        }
 1.14091 +
 1.14092 +        lastKey = keyCode;
 1.14093 +
 1.14094 +        if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) {
 1.14095 +          that.transact();
 1.14096 +        }
 1.14097 +      });
 1.14098 +
 1.14099 +      this.editor
 1.14100 +        .on("newword:composer", function() {
 1.14101 +          that.transact();
 1.14102 +        })
 1.14103 +
 1.14104 +        .on("beforecommand:composer", function() {
 1.14105 +          that.transact();
 1.14106 +        });
 1.14107 +    },
 1.14108 +
 1.14109 +    transact: function() {
 1.14110 +      var previousHtml      = this.historyStr[this.position - 1],
 1.14111 +          currentHtml       = this.composer.getValue(false, false),
 1.14112 +          composerIsVisible   = this.element.offsetWidth > 0 && this.element.offsetHeight > 0,
 1.14113 +          range, node, offset, element, position;
 1.14114 +
 1.14115 +      if (currentHtml === previousHtml) {
 1.14116 +        return;
 1.14117 +      }
 1.14118 +
 1.14119 +      var length = this.historyStr.length = this.historyDom.length = this.position;
 1.14120 +      if (length > MAX_HISTORY_ENTRIES) {
 1.14121 +        this.historyStr.shift();
 1.14122 +        this.historyDom.shift();
 1.14123 +        this.position--;
 1.14124 +      }
 1.14125 +
 1.14126 +      this.position++;
 1.14127 +
 1.14128 +      if (composerIsVisible) {
 1.14129 +        // Do not start saving selection if composer is not visible
 1.14130 +        range   = this.composer.selection.getRange();
 1.14131 +        node    = (range && range.startContainer) ? range.startContainer : this.element;
 1.14132 +        offset  = (range && range.startOffset) ? range.startOffset : 0;
 1.14133 +
 1.14134 +        if (node.nodeType === wysihtml.ELEMENT_NODE) {
 1.14135 +          element = node;
 1.14136 +        } else {
 1.14137 +          element  = node.parentNode;
 1.14138 +          position = this.getChildNodeIndex(element, node);
 1.14139 +        }
 1.14140 +
 1.14141 +        element.setAttribute(DATA_ATTR_OFFSET, offset);
 1.14142 +        if (typeof(position) !== "undefined") {
 1.14143 +          element.setAttribute(DATA_ATTR_NODE, position);
 1.14144 +        }
 1.14145 +      }
 1.14146 +
 1.14147 +      var clone = this.element.cloneNode(!!currentHtml);
 1.14148 +      this.historyDom.push(clone);
 1.14149 +      this.historyStr.push(currentHtml);
 1.14150 +
 1.14151 +      if (element) {
 1.14152 +        element.removeAttribute(DATA_ATTR_OFFSET);
 1.14153 +        element.removeAttribute(DATA_ATTR_NODE);
 1.14154 +      }
 1.14155 +
 1.14156 +    },
 1.14157 +
 1.14158 +    undo: function() {
 1.14159 +      this.transact();
 1.14160 +
 1.14161 +      if (!this.undoPossible()) {
 1.14162 +        return;
 1.14163 +      }
 1.14164 +
 1.14165 +      this.set(this.historyDom[--this.position - 1]);
 1.14166 +      this.editor.fire("undo:composer");
 1.14167 +    },
 1.14168 +
 1.14169 +    redo: function() {
 1.14170 +      if (!this.redoPossible()) {
 1.14171 +        return;
 1.14172 +      }
 1.14173 +
 1.14174 +      this.set(this.historyDom[++this.position - 1]);
 1.14175 +      this.editor.fire("redo:composer");
 1.14176 +    },
 1.14177 +
 1.14178 +    undoPossible: function() {
 1.14179 +      return this.position > 1;
 1.14180 +    },
 1.14181 +
 1.14182 +    redoPossible: function() {
 1.14183 +      return this.position < this.historyStr.length;
 1.14184 +    },
 1.14185 +
 1.14186 +    set: function(historyEntry) {
 1.14187 +      this.element.innerHTML = "";
 1.14188 +
 1.14189 +      var i = 0,
 1.14190 +          childNodes = historyEntry.childNodes,
 1.14191 +          length = historyEntry.childNodes.length;
 1.14192 +
 1.14193 +      for (; i<length; i++) {
 1.14194 +        this.element.appendChild(childNodes[i].cloneNode(true));
 1.14195 +      }
 1.14196 +
 1.14197 +      // Restore selection
 1.14198 +      var offset,
 1.14199 +          node,
 1.14200 +          position;
 1.14201 +
 1.14202 +      if (historyEntry.hasAttribute(DATA_ATTR_OFFSET)) {
 1.14203 +        offset    = historyEntry.getAttribute(DATA_ATTR_OFFSET);
 1.14204 +        position  = historyEntry.getAttribute(DATA_ATTR_NODE);
 1.14205 +        node      = this.element;
 1.14206 +      } else {
 1.14207 +        node      = this.element.querySelector("[" + DATA_ATTR_OFFSET + "]") || this.element;
 1.14208 +        offset    = node.getAttribute(DATA_ATTR_OFFSET);
 1.14209 +        position  = node.getAttribute(DATA_ATTR_NODE);
 1.14210 +        node.removeAttribute(DATA_ATTR_OFFSET);
 1.14211 +        node.removeAttribute(DATA_ATTR_NODE);
 1.14212 +      }
 1.14213 +
 1.14214 +      if (position !== null) {
 1.14215 +        node = this.getChildNodeByIndex(node, +position);
 1.14216 +      }
 1.14217 +
 1.14218 +      this.composer.selection.set(node, offset);
 1.14219 +    },
 1.14220 +
 1.14221 +    getChildNodeIndex: function(parent, child) {
 1.14222 +      var i           = 0,
 1.14223 +          childNodes  = parent.childNodes,
 1.14224 +          length      = childNodes.length;
 1.14225 +      for (; i<length; i++) {
 1.14226 +        if (childNodes[i] === child) {
 1.14227 +          return i;
 1.14228 +        }
 1.14229 +      }
 1.14230 +    },
 1.14231 +
 1.14232 +    getChildNodeByIndex: function(parent, index) {
 1.14233 +      return parent.childNodes[index];
 1.14234 +    }
 1.14235 +  });
 1.14236 +})(wysihtml);
 1.14237 +
 1.14238 +/**
 1.14239 + * TODO: the following methods still need unit test coverage
 1.14240 + */
 1.14241 +wysihtml.views.View = Base.extend(
 1.14242 +  /** @scope wysihtml.views.View.prototype */ {
 1.14243 +  constructor: function(parent, textareaElement, config) {
 1.14244 +    this.parent   = parent;
 1.14245 +    this.element  = textareaElement;
 1.14246 +    this.config   = config;
 1.14247 +    if (!this.config.noTextarea) {
 1.14248 +        this._observeViewChange();
 1.14249 +    }
 1.14250 +  },
 1.14251 +
 1.14252 +  _observeViewChange: function() {
 1.14253 +    var that = this;
 1.14254 +    this.parent.on("beforeload", function() {
 1.14255 +      that.parent.on("change_view", function(view) {
 1.14256 +        if (view === that.name) {
 1.14257 +          that.parent.currentView = that;
 1.14258 +          that.show();
 1.14259 +          // Using tiny delay here to make sure that the placeholder is set before focusing
 1.14260 +          setTimeout(function() { that.focus(); }, 0);
 1.14261 +        } else {
 1.14262 +          that.hide();
 1.14263 +        }
 1.14264 +      });
 1.14265 +    });
 1.14266 +  },
 1.14267 +
 1.14268 +  focus: function() {
 1.14269 +    if (this.element && this.element.ownerDocument && this.element.ownerDocument.querySelector(":focus") === this.element) {
 1.14270 +      return;
 1.14271 +    }
 1.14272 +
 1.14273 +    try { if(this.element) { this.element.focus(); } } catch(e) {}
 1.14274 +  },
 1.14275 +
 1.14276 +  hide: function() {
 1.14277 +    this.element.style.display = "none";
 1.14278 +  },
 1.14279 +
 1.14280 +  show: function() {
 1.14281 +    this.element.style.display = "";
 1.14282 +  },
 1.14283 +
 1.14284 +  disable: function() {
 1.14285 +    this.element.setAttribute("disabled", "disabled");
 1.14286 +  },
 1.14287 +
 1.14288 +  enable: function() {
 1.14289 +    this.element.removeAttribute("disabled");
 1.14290 +  }
 1.14291 +});
 1.14292 +
 1.14293 +(function(wysihtml) {
 1.14294 +  var dom       = wysihtml.dom,
 1.14295 +      browser   = wysihtml.browser;
 1.14296 +
 1.14297 +  wysihtml.views.Composer = wysihtml.views.View.extend(
 1.14298 +    /** @scope wysihtml.views.Composer.prototype */ {
 1.14299 +    name: "composer",
 1.14300 +
 1.14301 +    constructor: function(parent, editableElement, config) {
 1.14302 +      this.base(parent, editableElement, config);
 1.14303 +      if (!this.config.noTextarea) {
 1.14304 +          this.textarea = this.parent.textarea;
 1.14305 +      } else {
 1.14306 +          this.editableArea = editableElement;
 1.14307 +      }
 1.14308 +      if (this.config.contentEditableMode) {
 1.14309 +          this._initContentEditableArea();
 1.14310 +      } else {
 1.14311 +          this._initSandbox();
 1.14312 +      }
 1.14313 +    },
 1.14314 +
 1.14315 +    clear: function() {
 1.14316 +      this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : "<br>";
 1.14317 +    },
 1.14318 +
 1.14319 +    getValue: function(parse, clearInternals) {
 1.14320 +      var value = this.isEmpty() ? "" : wysihtml.quirks.getCorrectInnerHTML(this.element);
 1.14321 +      if (parse !== false) {
 1.14322 +        value = this.parent.parse(value, (clearInternals === false) ? false : true);
 1.14323 +      }
 1.14324 +      return value;
 1.14325 +    },
 1.14326 +
 1.14327 +    setValue: function(html, parse) {
 1.14328 +      if (parse !== false) {
 1.14329 +        html = this.parent.parse(html);
 1.14330 +      }
 1.14331 +
 1.14332 +      try {
 1.14333 +        this.element.innerHTML = html;
 1.14334 +      } catch (e) {
 1.14335 +        this.element.innerText = html;
 1.14336 +      }
 1.14337 +    },
 1.14338 +
 1.14339 +    cleanUp: function(rules) {
 1.14340 +      var bookmark;
 1.14341 +      if (this.selection && this.selection.isInThisEditable()) {
 1.14342 +        bookmark = rangy.saveSelection(this.win);
 1.14343 +      }
 1.14344 +      this.parent.parse(this.element, undefined, rules);
 1.14345 +      if (bookmark) {
 1.14346 +        rangy.restoreSelection(bookmark);
 1.14347 +      }
 1.14348 +    },
 1.14349 +
 1.14350 +    show: function() {
 1.14351 +      this.editableArea.style.display = this._displayStyle || "";
 1.14352 +
 1.14353 +      if (!this.config.noTextarea && !this.textarea.element.disabled) {
 1.14354 +        // Firefox needs this, otherwise contentEditable becomes uneditable
 1.14355 +        this.disable();
 1.14356 +        this.enable();
 1.14357 +      }
 1.14358 +    },
 1.14359 +
 1.14360 +    hide: function() {
 1.14361 +      this._displayStyle = dom.getStyle("display").from(this.editableArea);
 1.14362 +      if (this._displayStyle === "none") {
 1.14363 +        this._displayStyle = null;
 1.14364 +      }
 1.14365 +      this.editableArea.style.display = "none";
 1.14366 +    },
 1.14367 +
 1.14368 +    disable: function() {
 1.14369 +      this.parent.fire("disable:composer");
 1.14370 +      this.element.removeAttribute("contentEditable");
 1.14371 +    },
 1.14372 +
 1.14373 +    enable: function() {
 1.14374 +      this.parent.fire("enable:composer");
 1.14375 +      this.element.setAttribute("contentEditable", "true");
 1.14376 +    },
 1.14377 +
 1.14378 +    focus: function(setToEnd) {
 1.14379 +      // IE 8 fires the focus event after .focus()
 1.14380 +      // This is needed by our simulate_placeholder.js to work
 1.14381 +      // therefore we clear it ourselves this time
 1.14382 +      if (wysihtml.browser.doesAsyncFocus() && this.hasPlaceholderSet()) {
 1.14383 +        this.clear();
 1.14384 +      }
 1.14385 +
 1.14386 +      this.base();
 1.14387 +
 1.14388 +      var lastChild = this.element.lastChild;
 1.14389 +      if (setToEnd && lastChild && this.selection) {
 1.14390 +        if (lastChild.nodeName === "BR") {
 1.14391 +          this.selection.setBefore(this.element.lastChild);
 1.14392 +        } else {
 1.14393 +          this.selection.setAfter(this.element.lastChild);
 1.14394 +        }
 1.14395 +      }
 1.14396 +    },
 1.14397 +
 1.14398 +    getScrollPos: function() {
 1.14399 +      if (this.doc && this.win) {
 1.14400 +        var pos = {};
 1.14401 +
 1.14402 +        if (typeof this.win.pageYOffset !== "undefined") {
 1.14403 +          pos.y = this.win.pageYOffset;
 1.14404 +        } else {
 1.14405 +          pos.y = (this.doc.documentElement || this.doc.body.parentNode || this.doc.body).scrollTop;
 1.14406 +        }
 1.14407 +
 1.14408 +        if (typeof this.win.pageXOffset !== "undefined") {
 1.14409 +          pos.x = this.win.pageXOffset;
 1.14410 +        } else {
 1.14411 +          pos.x = (this.doc.documentElement || this.doc.body.parentNode || this.doc.body).scrollLeft;
 1.14412 +        }
 1.14413 +
 1.14414 +        return pos;
 1.14415 +      }
 1.14416 +    },
 1.14417 +
 1.14418 +    setScrollPos: function(pos) {
 1.14419 +      if (pos && typeof pos.x !== "undefined" && typeof pos.y !== "undefined") {
 1.14420 +        this.win.scrollTo(pos.x, pos.y);
 1.14421 +      }
 1.14422 +    },
 1.14423 +
 1.14424 +    getTextContent: function() {
 1.14425 +      return dom.getTextContent(this.element);
 1.14426 +    },
 1.14427 +
 1.14428 +    hasPlaceholderSet: function() {
 1.14429 +      return this.getTextContent() == ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")) && this.placeholderSet;
 1.14430 +    },
 1.14431 +
 1.14432 +    isEmpty: function() {
 1.14433 +      var innerHTML = this.element.innerHTML.toLowerCase();
 1.14434 +      return (/^(\s|<br>|<\/br>|<p>|<\/p>)*$/i).test(innerHTML)  ||
 1.14435 +             innerHTML === ""            ||
 1.14436 +             innerHTML === "<br>"        ||
 1.14437 +             innerHTML === "<p></p>"     ||
 1.14438 +             innerHTML === "<p><br></p>" ||
 1.14439 +             this.hasPlaceholderSet();
 1.14440 +    },
 1.14441 +
 1.14442 +    _initContentEditableArea: function() {
 1.14443 +        var that = this;
 1.14444 +        if (this.config.noTextarea) {
 1.14445 +            this.sandbox = new dom.ContentEditableArea(function() {
 1.14446 +                that._create();
 1.14447 +            }, {
 1.14448 +              className: this.config.classNames.sandbox
 1.14449 +            }, this.editableArea);
 1.14450 +        } else {
 1.14451 +            this.sandbox = new dom.ContentEditableArea(function() {
 1.14452 +                that._create();
 1.14453 +            }, {
 1.14454 +              className: this.config.classNames.sandbox
 1.14455 +            });
 1.14456 +            this.editableArea = this.sandbox.getContentEditable();
 1.14457 +            dom.insert(this.editableArea).after(this.textarea.element);
 1.14458 +            this._createWysiwygFormField();
 1.14459 +        }
 1.14460 +    },
 1.14461 +
 1.14462 +    _initSandbox: function() {
 1.14463 +      var that = this;
 1.14464 +      this.sandbox = new dom.Sandbox(function() {
 1.14465 +        that._create();
 1.14466 +      }, {
 1.14467 +        stylesheets:  this.config.stylesheets,
 1.14468 +        className: this.config.classNames.sandbox
 1.14469 +      });
 1.14470 +      this.editableArea  = this.sandbox.getIframe();
 1.14471 +
 1.14472 +      var textareaElement = this.textarea.element;
 1.14473 +      dom.insert(this.editableArea).after(textareaElement);
 1.14474 +
 1.14475 +      this._createWysiwygFormField();
 1.14476 +    },
 1.14477 +
 1.14478 +    // Creates hidden field which tells the server after submit, that the user used an wysiwyg editor
 1.14479 +    _createWysiwygFormField: function() {
 1.14480 +        if (this.textarea.element.form) {
 1.14481 +          var hiddenField = document.createElement("input");
 1.14482 +          hiddenField.type   = "hidden";
 1.14483 +          hiddenField.name   = "_wysihtml_mode";
 1.14484 +          hiddenField.value  = 1;
 1.14485 +          dom.insert(hiddenField).after(this.textarea.element);
 1.14486 +        }
 1.14487 +    },
 1.14488 +
 1.14489 +    _create: function() {
 1.14490 +      var that = this;
 1.14491 +      this.doc                = this.sandbox.getDocument();
 1.14492 +      this.win                = this.sandbox.getWindow();
 1.14493 +      this.element            = (this.config.contentEditableMode) ? this.sandbox.getContentEditable() : this.doc.body;
 1.14494 +      if (!this.config.noTextarea) {
 1.14495 +          this.textarea           = this.parent.textarea;
 1.14496 +          this.element.innerHTML  = this.textarea.getValue(true, false);
 1.14497 +      } else {
 1.14498 +          this.cleanUp(); // cleans contenteditable on initiation as it may contain html
 1.14499 +      }
 1.14500 +
 1.14501 +      // Make sure our selection handler is ready
 1.14502 +      this.selection = new wysihtml.Selection(this.parent, this.element, this.config.classNames.uneditableContainer);
 1.14503 +
 1.14504 +      // Make sure commands dispatcher is ready
 1.14505 +      this.commands  = new wysihtml.Commands(this.parent);
 1.14506 +
 1.14507 +      if (!this.config.noTextarea) {
 1.14508 +          dom.copyAttributes([
 1.14509 +              "className", "spellcheck", "title", "lang", "dir", "accessKey"
 1.14510 +          ]).from(this.textarea.element).to(this.element);
 1.14511 +      }
 1.14512 +
 1.14513 +      this._initAutoLinking();
 1.14514 +
 1.14515 +      dom.addClass(this.element, this.config.classNames.composer);
 1.14516 +      //
 1.14517 +      // Make the editor look like the original textarea, by syncing styles
 1.14518 +      if (this.config.style && !this.config.contentEditableMode) {
 1.14519 +        this.style();
 1.14520 +      }
 1.14521 +
 1.14522 +      this.observe();
 1.14523 +
 1.14524 +      var name = this.config.name;
 1.14525 +      if (name) {
 1.14526 +        dom.addClass(this.element, name);
 1.14527 +        if (!this.config.contentEditableMode) { dom.addClass(this.editableArea, name); }
 1.14528 +      }
 1.14529 +
 1.14530 +      this.enable();
 1.14531 +
 1.14532 +      if (!this.config.noTextarea && this.textarea.element.disabled) {
 1.14533 +        this.disable();
 1.14534 +      }
 1.14535 +
 1.14536 +      // Simulate html5 placeholder attribute on contentEditable element
 1.14537 +      var placeholderText = typeof(this.config.placeholder) === "string"
 1.14538 +        ? this.config.placeholder
 1.14539 +        : ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder"));
 1.14540 +      if (placeholderText) {
 1.14541 +        dom.simulatePlaceholder(this.parent, this, placeholderText, this.config.classNames.placeholder);
 1.14542 +      }
 1.14543 +
 1.14544 +      // Make sure that the browser avoids using inline styles whenever possible
 1.14545 +      this.commands.exec("styleWithCSS", false);
 1.14546 +
 1.14547 +      this._initObjectResizing();
 1.14548 +      this._initUndoManager();
 1.14549 +      this._initLineBreaking();
 1.14550 +
 1.14551 +      // Simulate html5 autofocus on contentEditable element
 1.14552 +      // This doesn't work on IOS (5.1.1)
 1.14553 +      if (!this.config.noTextarea && (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) && !browser.isIos()) {
 1.14554 +        setTimeout(function() { that.focus(true); }, 100);
 1.14555 +      }
 1.14556 +
 1.14557 +      // IE sometimes leaves a single paragraph, which can't be removed by the user
 1.14558 +      if (!browser.clearsContentEditableCorrectly()) {
 1.14559 +        wysihtml.quirks.ensureProperClearing(this);
 1.14560 +      }
 1.14561 +
 1.14562 +      // Set up a sync that makes sure that textarea and editor have the same content
 1.14563 +      if (this.initSync && this.config.sync) {
 1.14564 +        this.initSync();
 1.14565 +      }
 1.14566 +
 1.14567 +      // Okay hide the textarea, we are ready to go
 1.14568 +      if (!this.config.noTextarea) { this.textarea.hide(); }
 1.14569 +
 1.14570 +      // Fire global (before-)load event
 1.14571 +      this.parent.fire("beforeload").fire("load");
 1.14572 +    },
 1.14573 +
 1.14574 +    _initAutoLinking: function() {
 1.14575 +      var that                           = this,
 1.14576 +          supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(),
 1.14577 +          supportsAutoLinking            = browser.doesAutoLinkingInContentEditable();
 1.14578 +
 1.14579 +      if (supportsDisablingOfAutoLinking) {
 1.14580 +        this.commands.exec("AutoUrlDetect", false, false);
 1.14581 +      }
 1.14582 +
 1.14583 +      if (!this.config.autoLink) {
 1.14584 +        return;
 1.14585 +      }
 1.14586 +
 1.14587 +      // Only do the auto linking by ourselves when the browser doesn't support auto linking
 1.14588 +      // OR when he supports auto linking but we were able to turn it off (IE9+)
 1.14589 +      if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
 1.14590 +        this.parent.on("newword:composer", function() {
 1.14591 +          if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
 1.14592 +            var nodeWithSelection = that.selection.getSelectedNode(),
 1.14593 +                uneditables = that.element.querySelectorAll("." + that.config.classNames.uneditableContainer),
 1.14594 +                isInUneditable = false;
 1.14595 +
 1.14596 +            for (var i = uneditables.length; i--;) {
 1.14597 +              if (wysihtml.dom.contains(uneditables[i], nodeWithSelection)) {
 1.14598 +                isInUneditable = true;
 1.14599 +              }
 1.14600 +            }
 1.14601 +
 1.14602 +            if (!isInUneditable) dom.autoLink(nodeWithSelection, [that.config.classNames.uneditableContainer]);
 1.14603 +          }
 1.14604 +        });
 1.14605 +
 1.14606 +        dom.observe(this.element, "blur", function() {
 1.14607 +          dom.autoLink(that.element, [that.config.classNames.uneditableContainer]);
 1.14608 +        });
 1.14609 +      }
 1.14610 +
 1.14611 +      // Assuming we have the following:
 1.14612 +      //  <a href="http://www.google.de">http://www.google.de</a>
 1.14613 +      // If a user now changes the url in the innerHTML we want to make sure that
 1.14614 +      // it's synchronized with the href attribute (as long as the innerHTML is still a url)
 1.14615 +      var // Use a live NodeList to check whether there are any links in the document
 1.14616 +          links           = this.sandbox.getDocument().getElementsByTagName("a"),
 1.14617 +          // The autoLink helper method reveals a reg exp to detect correct urls
 1.14618 +          urlRegExp       = dom.autoLink.URL_REG_EXP,
 1.14619 +          getTextContent  = function(element) {
 1.14620 +            var textContent = wysihtml.lang.string(dom.getTextContent(element)).trim();
 1.14621 +            if (textContent.substr(0, 4) === "www.") {
 1.14622 +              textContent = "http://" + textContent;
 1.14623 +            }
 1.14624 +            return textContent;
 1.14625 +          };
 1.14626 +
 1.14627 +      dom.observe(this.element, "keydown", function(event) {
 1.14628 +        if (!links.length) {
 1.14629 +          return;
 1.14630 +        }
 1.14631 +
 1.14632 +        var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument),
 1.14633 +            link         = dom.getParentElement(selectedNode, { query: "a" }, 4),
 1.14634 +            textContent;
 1.14635 +
 1.14636 +        if (!link) {
 1.14637 +          return;
 1.14638 +        }
 1.14639 +
 1.14640 +        textContent = getTextContent(link);
 1.14641 +        // keydown is fired before the actual content is changed
 1.14642 +        // therefore we set a timeout to change the href
 1.14643 +        setTimeout(function() {
 1.14644 +          var newTextContent = getTextContent(link);
 1.14645 +          if (newTextContent === textContent) {
 1.14646 +            return;
 1.14647 +          }
 1.14648 +
 1.14649 +          // Only set href when new href looks like a valid url
 1.14650 +          if (newTextContent.match(urlRegExp)) {
 1.14651 +            link.setAttribute("href", newTextContent);
 1.14652 +          }
 1.14653 +        }, 0);
 1.14654 +      });
 1.14655 +    },
 1.14656 +
 1.14657 +    _initObjectResizing: function() {
 1.14658 +      this.commands.exec("enableObjectResizing", true);
 1.14659 +
 1.14660 +      // IE sets inline styles after resizing objects
 1.14661 +      // The following lines make sure that the width/height css properties
 1.14662 +      // are copied over to the width/height attributes
 1.14663 +      if (browser.supportsEvent("resizeend")) {
 1.14664 +        var properties        = ["width", "height"],
 1.14665 +            propertiesLength  = properties.length,
 1.14666 +            element           = this.element;
 1.14667 +
 1.14668 +        dom.observe(element, "resizeend", function(event) {
 1.14669 +          var target = event.target || event.srcElement,
 1.14670 +              style  = target.style,
 1.14671 +              i      = 0,
 1.14672 +              property;
 1.14673 +
 1.14674 +          if (target.nodeName !== "IMG") {
 1.14675 +            return;
 1.14676 +          }
 1.14677 +
 1.14678 +          for (; i<propertiesLength; i++) {
 1.14679 +            property = properties[i];
 1.14680 +            if (style[property]) {
 1.14681 +              target.setAttribute(property, parseInt(style[property], 10));
 1.14682 +              style[property] = "";
 1.14683 +            }
 1.14684 +          }
 1.14685 +
 1.14686 +          // After resizing IE sometimes forgets to remove the old resize handles
 1.14687 +          wysihtml.quirks.redraw(element);
 1.14688 +        });
 1.14689 +      }
 1.14690 +    },
 1.14691 +
 1.14692 +    _initUndoManager: function() {
 1.14693 +      this.undoManager = new wysihtml.UndoManager(this.parent);
 1.14694 +    },
 1.14695 +
 1.14696 +    _initLineBreaking: function() {
 1.14697 +      var that                              = this,
 1.14698 +          USE_NATIVE_LINE_BREAK_INSIDE_TAGS = "li, p, h1, h2, h3, h4, h5, h6",
 1.14699 +          LIST_TAGS                         = "ul, ol, menu";
 1.14700 +
 1.14701 +      function adjust(selectedNode) {
 1.14702 +        var parentElement = dom.getParentElement(selectedNode, { query: "p, div" }, 2);
 1.14703 +        if (parentElement && dom.contains(that.element, parentElement)) {
 1.14704 +          that.selection.executeAndRestoreRangy(function() {
 1.14705 +            if (that.config.useLineBreaks) {
 1.14706 +              if (!parentElement.firstChild || (parentElement.firstChild === parentElement.lastChild && parentElement.firstChild.nodeType === 1 && parentElement.firstChild.classList.contains('rangySelectionBoundary'))) {
 1.14707 +                parentElement.appendChild(that.doc.createElement('br'));
 1.14708 +              }
 1.14709 +              dom.replaceWithChildNodes(parentElement);
 1.14710 +            } else if (parentElement.nodeName !== "P") {
 1.14711 +              dom.renameElement(parentElement, "p");
 1.14712 +            }
 1.14713 +          });
 1.14714 +        }
 1.14715 +      }
 1.14716 +
 1.14717 +      // Ensures when editor is empty and not line breaks mode, the inital state has a paragraph in it on focus with caret inside paragraph
 1.14718 +      if (!this.config.useLineBreaks) {
 1.14719 +        dom.observe(this.element, ["focus"], function() {
 1.14720 +          if (that.isEmpty()) {
 1.14721 +            setTimeout(function() {
 1.14722 +              var paragraph = that.doc.createElement("P");
 1.14723 +              that.element.innerHTML = "";
 1.14724 +              that.element.appendChild(paragraph);
 1.14725 +              if (!browser.displaysCaretInEmptyContentEditableCorrectly()) {
 1.14726 +                paragraph.innerHTML = "<br>";
 1.14727 +                that.selection.setBefore(paragraph.firstChild);
 1.14728 +              } else {
 1.14729 +                that.selection.selectNode(paragraph, true);
 1.14730 +              }
 1.14731 +            }, 0);
 1.14732 +          }
 1.14733 +        });
 1.14734 +      }
 1.14735 +
 1.14736 +      dom.observe(this.element, "keydown", function(event) {
 1.14737 +        var keyCode = event.keyCode;
 1.14738 +
 1.14739 +        if (event.shiftKey || event.ctrlKey || event.defaultPrevented) {
 1.14740 +          return;
 1.14741 +        }
 1.14742 +
 1.14743 +        if (keyCode !== wysihtml.ENTER_KEY && keyCode !== wysihtml.BACKSPACE_KEY) {
 1.14744 +          return;
 1.14745 +        }
 1.14746 +        var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { query: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4);
 1.14747 +        if (blockElement) {
 1.14748 +          setTimeout(function() {
 1.14749 +            // Unwrap paragraph after leaving a list or a H1-6
 1.14750 +            var selectedNode = that.selection.getSelectedNode(),
 1.14751 +                list;
 1.14752 +
 1.14753 +            if (blockElement.nodeName === "LI") {
 1.14754 +              if (!selectedNode) {
 1.14755 +                return;
 1.14756 +              }
 1.14757 +
 1.14758 +              list = dom.getParentElement(selectedNode, { query: LIST_TAGS }, 2);
 1.14759 +
 1.14760 +              if (!list) {
 1.14761 +                adjust(selectedNode);
 1.14762 +              }
 1.14763 +            }
 1.14764 +
 1.14765 +            if (keyCode === wysihtml.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) {
 1.14766 +              adjust(selectedNode);
 1.14767 +            }
 1.14768 +          }, 0);
 1.14769 +          return;
 1.14770 +        }
 1.14771 +        if (that.config.useLineBreaks && keyCode === wysihtml.ENTER_KEY && !wysihtml.browser.insertsLineBreaksOnReturn()) {
 1.14772 +          event.preventDefault();
 1.14773 +          that.commands.exec("insertLineBreak");
 1.14774 +        }
 1.14775 +      });
 1.14776 +    }
 1.14777 +  });
 1.14778 +})(wysihtml);
 1.14779 +
 1.14780 +(function(wysihtml) {
 1.14781 +  var dom             = wysihtml.dom,
 1.14782 +      doc             = document,
 1.14783 +      win             = window,
 1.14784 +      HOST_TEMPLATE   = doc.createElement("div"),
 1.14785 +      /**
 1.14786 +       * Styles to copy from textarea to the composer element
 1.14787 +       */
 1.14788 +      TEXT_FORMATTING = [
 1.14789 +        "background-color",
 1.14790 +        "color", "cursor",
 1.14791 +        "font-family", "font-size", "font-style", "font-variant", "font-weight",
 1.14792 +        "line-height", "letter-spacing",
 1.14793 +        "text-align", "text-decoration", "text-indent", "text-rendering",
 1.14794 +        "word-break", "word-wrap", "word-spacing"
 1.14795 +      ],
 1.14796 +      /**
 1.14797 +       * Styles to copy from textarea to the iframe
 1.14798 +       */
 1.14799 +      BOX_FORMATTING = [
 1.14800 +        "background-color",
 1.14801 +        "border-collapse",
 1.14802 +        "border-bottom-color", "border-bottom-style", "border-bottom-width",
 1.14803 +        "border-left-color", "border-left-style", "border-left-width",
 1.14804 +        "border-right-color", "border-right-style", "border-right-width",
 1.14805 +        "border-top-color", "border-top-style", "border-top-width",
 1.14806 +        "clear", "display", "float",
 1.14807 +        "margin-bottom", "margin-left", "margin-right", "margin-top",
 1.14808 +        "outline-color", "outline-offset", "outline-width", "outline-style",
 1.14809 +        "padding-left", "padding-right", "padding-top", "padding-bottom",
 1.14810 +        "position", "top", "left", "right", "bottom", "z-index",
 1.14811 +        "vertical-align", "text-align",
 1.14812 +        "-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing",
 1.14813 +        "-webkit-box-shadow", "-moz-box-shadow", "-ms-box-shadow","box-shadow",
 1.14814 +        "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius",
 1.14815 +        "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius",
 1.14816 +        "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius",
 1.14817 +        "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius",
 1.14818 +        "width", "height"
 1.14819 +      ],
 1.14820 +      ADDITIONAL_CSS_RULES = [
 1.14821 +        "html                 { height: 100%; }",
 1.14822 +        "body                 { height: 100%; padding: 1px 0 0 0; margin: -1px 0 0 0; }",
 1.14823 +        "body > p:first-child { margin-top: 0; }",
 1.14824 +        "._wysihtml-temp     { display: none; }",
 1.14825 +        wysihtml.browser.isGecko ?
 1.14826 +          "body.placeholder { color: graytext !important; }" :
 1.14827 +          "body.placeholder { color: #a9a9a9 !important; }",
 1.14828 +        // Ensure that user see's broken images and can delete them
 1.14829 +        "img:-moz-broken      { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"
 1.14830 +      ];
 1.14831 +
 1.14832 +  /**
 1.14833 +   * With "setActive" IE offers a smart way of focusing elements without scrolling them into view:
 1.14834 +   * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx
 1.14835 +   *
 1.14836 +   * Other browsers need a more hacky way: (pssst don't tell my mama)
 1.14837 +   * In order to prevent the element being scrolled into view when focusing it, we simply
 1.14838 +   * move it out of the scrollable area, focus it, and reset it's position
 1.14839 +   */
 1.14840 +  var focusWithoutScrolling = function(element) {
 1.14841 +    if (element.setActive) {
 1.14842 +      // Following line could cause a js error when the textarea is invisible
 1.14843 +      // See https://github.com/xing/wysihtml5/issues/9
 1.14844 +      try { element.setActive(); } catch(e) {}
 1.14845 +    } else {
 1.14846 +      var elementStyle = element.style,
 1.14847 +          originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop,
 1.14848 +          originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft,
 1.14849 +          originalStyles = {
 1.14850 +            position:         elementStyle.position,
 1.14851 +            top:              elementStyle.top,
 1.14852 +            left:             elementStyle.left,
 1.14853 +            WebkitUserSelect: elementStyle.WebkitUserSelect
 1.14854 +          };
 1.14855 +
 1.14856 +      dom.setStyles({
 1.14857 +        position:         "absolute",
 1.14858 +        top:              "-99999px",
 1.14859 +        left:             "-99999px",
 1.14860 +        // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother
 1.14861 +        WebkitUserSelect: "none"
 1.14862 +      }).on(element);
 1.14863 +
 1.14864 +      element.focus();
 1.14865 +
 1.14866 +      dom.setStyles(originalStyles).on(element);
 1.14867 +
 1.14868 +      if (win.scrollTo) {
 1.14869 +        // Some browser extensions unset this method to prevent annoyances
 1.14870 +        // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100
 1.14871 +        // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1
 1.14872 +        win.scrollTo(originalScrollLeft, originalScrollTop);
 1.14873 +      }
 1.14874 +    }
 1.14875 +  };
 1.14876 +
 1.14877 +
 1.14878 +  wysihtml.views.Composer.prototype.style = function() {
 1.14879 +    var that                  = this,
 1.14880 +        originalActiveElement = doc.querySelector(":focus"),
 1.14881 +        textareaElement       = this.textarea.element,
 1.14882 +        hasPlaceholder        = textareaElement.hasAttribute("placeholder"),
 1.14883 +        originalPlaceholder   = hasPlaceholder && textareaElement.getAttribute("placeholder"),
 1.14884 +        originalDisplayValue  = textareaElement.style.display,
 1.14885 +        originalDisabled      = textareaElement.disabled,
 1.14886 +        displayValueForCopying;
 1.14887 +
 1.14888 +    this.focusStylesHost      = HOST_TEMPLATE.cloneNode(false);
 1.14889 +    this.blurStylesHost       = HOST_TEMPLATE.cloneNode(false);
 1.14890 +    this.disabledStylesHost   = HOST_TEMPLATE.cloneNode(false);
 1.14891 +
 1.14892 +    // Remove placeholder before copying (as the placeholder has an affect on the computed style)
 1.14893 +    if (hasPlaceholder) {
 1.14894 +      textareaElement.removeAttribute("placeholder");
 1.14895 +    }
 1.14896 +
 1.14897 +    if (textareaElement === originalActiveElement) {
 1.14898 +      textareaElement.blur();
 1.14899 +    }
 1.14900 +
 1.14901 +    // enable for copying styles
 1.14902 +    textareaElement.disabled = false;
 1.14903 +
 1.14904 +    // set textarea to display="none" to get cascaded styles via getComputedStyle
 1.14905 +    textareaElement.style.display = displayValueForCopying = "none";
 1.14906 +
 1.14907 +    if ((textareaElement.getAttribute("rows") && dom.getStyle("height").from(textareaElement) === "auto") ||
 1.14908 +        (textareaElement.getAttribute("cols") && dom.getStyle("width").from(textareaElement) === "auto")) {
 1.14909 +      textareaElement.style.display = displayValueForCopying = originalDisplayValue;
 1.14910 +    }
 1.14911 +
 1.14912 +    // --------- iframe styles (has to be set before editor styles, otherwise IE9 sets wrong fontFamily on blurStylesHost) ---------
 1.14913 +    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.editableArea).andTo(this.blurStylesHost);
 1.14914 +
 1.14915 +    // --------- editor styles ---------
 1.14916 +    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.element).andTo(this.blurStylesHost);
 1.14917 +
 1.14918 +    // --------- apply standard rules ---------
 1.14919 +    dom.insertCSS(ADDITIONAL_CSS_RULES).into(this.element.ownerDocument);
 1.14920 +
 1.14921 +    // --------- :disabled styles ---------
 1.14922 +    textareaElement.disabled = true;
 1.14923 +    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
 1.14924 +    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
 1.14925 +    textareaElement.disabled = originalDisabled;
 1.14926 +
 1.14927 +    // --------- :focus styles ---------
 1.14928 +    textareaElement.style.display = originalDisplayValue;
 1.14929 +    focusWithoutScrolling(textareaElement);
 1.14930 +    textareaElement.style.display = displayValueForCopying;
 1.14931 +
 1.14932 +    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.focusStylesHost);
 1.14933 +    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.focusStylesHost);
 1.14934 +
 1.14935 +    // reset textarea
 1.14936 +    textareaElement.style.display = originalDisplayValue;
 1.14937 +
 1.14938 +    dom.copyStyles(["display"]).from(textareaElement).to(this.editableArea);
 1.14939 +
 1.14940 +    // Make sure that we don't change the display style of the iframe when copying styles oblur/onfocus
 1.14941 +    // this is needed for when the change_view event is fired where the iframe is hidden and then
 1.14942 +    // the blur event fires and re-displays it
 1.14943 +    var boxFormattingStyles = wysihtml.lang.array(BOX_FORMATTING).without(["display"]);
 1.14944 +
 1.14945 +    // --------- restore focus ---------
 1.14946 +    if (originalActiveElement) {
 1.14947 +      focusWithoutScrolling(originalActiveElement);
 1.14948 +    } else {
 1.14949 +      textareaElement.blur();
 1.14950 +    }
 1.14951 +
 1.14952 +    // --------- restore placeholder ---------
 1.14953 +    if (hasPlaceholder) {
 1.14954 +      textareaElement.setAttribute("placeholder", originalPlaceholder);
 1.14955 +    }
 1.14956 +
 1.14957 +    // --------- Sync focus/blur styles ---------
 1.14958 +    this.parent.on("focus:composer", function() {
 1.14959 +      dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.editableArea);
 1.14960 +      dom.copyStyles(TEXT_FORMATTING)     .from(that.focusStylesHost).to(that.element);
 1.14961 +    });
 1.14962 +
 1.14963 +    this.parent.on("blur:composer", function() {
 1.14964 +      dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
 1.14965 +      dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element);
 1.14966 +    });
 1.14967 +
 1.14968 +    this.parent.observe("disable:composer", function() {
 1.14969 +      dom.copyStyles(boxFormattingStyles) .from(that.disabledStylesHost).to(that.editableArea);
 1.14970 +      dom.copyStyles(TEXT_FORMATTING)     .from(that.disabledStylesHost).to(that.element);
 1.14971 +    });
 1.14972 +
 1.14973 +    this.parent.observe("enable:composer", function() {
 1.14974 +      dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
 1.14975 +      dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element);
 1.14976 +    });
 1.14977 +
 1.14978 +    return this;
 1.14979 +  };
 1.14980 +})(wysihtml);
 1.14981 +
 1.14982 +/**
 1.14983 + * Taking care of events
 1.14984 + *  - Simulating 'change' event on contentEditable element
 1.14985 + *  - Handling drag & drop logic
 1.14986 + *  - Catch paste events
 1.14987 + *  - Dispatch proprietary newword:composer event
 1.14988 + *  - Keyboard shortcuts
 1.14989 + */
 1.14990 +(function(wysihtml) {
 1.14991 +  var dom       = wysihtml.dom,
 1.14992 +      domNode = dom.domNode,
 1.14993 +      browser   = wysihtml.browser,
 1.14994 +      /**
 1.14995 +       * Map keyCodes to query commands
 1.14996 +       */
 1.14997 +      shortcuts = {
 1.14998 +        "66": "bold",     // B
 1.14999 +        "73": "italic",   // I
 1.15000 +        "85": "underline" // U
 1.15001 +      };
 1.15002 +
 1.15003 +  var actions = {
 1.15004 +
 1.15005 +    // Adds multiple eventlisteners to target, bound to one callback
 1.15006 +    // TODO: If needed elsewhere make it part of wysihtml.dom or sth
 1.15007 +    addListeners: function (target, events, callback) {
 1.15008 +      for(var i = 0, max = events.length; i < max; i++) {
 1.15009 +        target.addEventListener(events[i], callback, false);
 1.15010 +      }
 1.15011 +    },
 1.15012 +
 1.15013 +    // Removes multiple eventlisteners from target, bound to one callback
 1.15014 +    // TODO: If needed elsewhere make it part of wysihtml.dom or sth
 1.15015 +    removeListeners: function (target, events, callback) {
 1.15016 +      for(var i = 0, max = events.length; i < max; i++) {
 1.15017 +        target.removeEventListener(events[i], callback, false);
 1.15018 +      }
 1.15019 +    },
 1.15020 +
 1.15021 +    // Override for giving user ability to delete last line break in table cell
 1.15022 +    fixLastBrDeletionInTable: function(composer, force) {
 1.15023 +      if (composer.selection.caretIsInTheEndOfNode()) {
 1.15024 +        var sel = composer.selection.getSelection(),
 1.15025 +            aNode = sel.anchorNode;
 1.15026 +        if (aNode && aNode.nodeType === 1 && (wysihtml.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) {
 1.15027 +          var nextNode = aNode.childNodes[sel.anchorOffset];
 1.15028 +          if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") {
 1.15029 +            nextNode.parentNode.removeChild(nextNode);
 1.15030 +            return true;
 1.15031 +          }
 1.15032 +        }
 1.15033 +      }
 1.15034 +      return false;
 1.15035 +    },
 1.15036 +
 1.15037 +    // If found an uneditable before caret then notify it before deletion
 1.15038 +    handleUneditableDeletion: function(composer) {
 1.15039 +      var before = composer.selection.getBeforeSelection(true);
 1.15040 +      if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) {
 1.15041 +        if (actions.fixLastBrDeletionInTable(composer, true)) {
 1.15042 +          return true;
 1.15043 +        }
 1.15044 +        try {
 1.15045 +          var ev = new CustomEvent("wysihtml:uneditable:delete", {bubbles: true, cancelable: false});
 1.15046 +          before.node.dispatchEvent(ev);
 1.15047 +        } catch (err) {}
 1.15048 +        before.node.parentNode.removeChild(before.node);
 1.15049 +        return true;
 1.15050 +      }
 1.15051 +      return false;
 1.15052 +    },
 1.15053 +
 1.15054 +    // Deletion with caret in the beginning of headings and other block elvel elements needs special attention
 1.15055 +    // Not allways does it concate text to previous block node correctly (browsers do unexpected miracles here especially webkit)
 1.15056 +    fixDeleteInTheBeginningOfBlock: function(composer) {
 1.15057 +      var selection = composer.selection,
 1.15058 +          prevNode = selection.getPreviousNode();
 1.15059 +
 1.15060 +      if (selection.caretIsFirstInSelection(wysihtml.browser.usesControlRanges()) && prevNode) {
 1.15061 +        if (prevNode.nodeType === 1 &&
 1.15062 +            wysihtml.dom.domNode(prevNode).is.block() &&
 1.15063 +            !domNode(prevNode).test({
 1.15064 +              query: "ol, ul, table, tr, dl"
 1.15065 +            })
 1.15066 +        ) {
 1.15067 +          if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
 1.15068 +            // If heading is empty remove the heading node
 1.15069 +            prevNode.parentNode.removeChild(prevNode);
 1.15070 +            return true;
 1.15071 +          } else {
 1.15072 +            if (prevNode.lastChild) {
 1.15073 +              var selNode = prevNode.lastChild,
 1.15074 +                  selectedNode = selection.getSelectedNode(),
 1.15075 +                  commonAncestorNode = domNode(prevNode).commonAncestor(selectedNode, composer.element),
 1.15076 +                  curNode = wysihtml.dom.getParentElement(selectedNode, {
 1.15077 +                    query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote"
 1.15078 +                  }, false, commonAncestorNode || composer.element);
 1.15079 +
 1.15080 +              if (curNode) {
 1.15081 +                domNode(curNode).transferContentTo(prevNode, true);
 1.15082 +                selection.setAfter(selNode);
 1.15083 +                return true;
 1.15084 +              } else if (wysihtml.browser.usesControlRanges()) {
 1.15085 +                selectedNode = selection.getCaretNode();
 1.15086 +                domNode(selectedNode).transferContentTo(prevNode, true);
 1.15087 +                selection.setAfter(selNode);
 1.15088 +                return true;
 1.15089 +              }
 1.15090 +            }
 1.15091 +          }
 1.15092 +        }
 1.15093 +      }
 1.15094 +      return false;
 1.15095 +    },
 1.15096 +
 1.15097 +    /* In IE when deleting with caret at the begining of LI, list gets broken into half instead of merging the LI with previous */
 1.15098 +    /* This does not match other browsers an is less intuitive from UI standpoint, thus has to be fixed */
 1.15099 +    fixDeleteInTheBeginningOfLi: function(composer) {
 1.15100 +      if (wysihtml.browser.hasLiDeletingProblem()) {
 1.15101 +        var selection = composer.selection.getSelection(),
 1.15102 +            aNode = selection.anchorNode,
 1.15103 +            listNode, prevNode, firstNode,
 1.15104 +            isInBeginnig = composer.selection.caretIsFirstInSelection(),
 1.15105 +            prevNode,
 1.15106 +            intermediaryNode;
 1.15107 +
 1.15108 +        // Fix caret at the beginnig of first textNode in LI
 1.15109 +        if (aNode.nodeType === 3 && selection.anchorOffset === 0 && aNode === aNode.parentNode.firstChild) {
 1.15110 +          aNode = aNode.parentNode;
 1.15111 +          isInBeginnig = true;
 1.15112 +        }
 1.15113 +
 1.15114 +        if (isInBeginnig && aNode && aNode.nodeType === 1 && aNode.nodeName === "LI") {
 1.15115 +          prevNode = domNode(aNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
 1.15116 +          if (!prevNode && aNode.parentNode && (aNode.parentNode.nodeName === "UL" || aNode.parentNode.nodeName === "OL")) {
 1.15117 +            prevNode = domNode(aNode.parentNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
 1.15118 +            intermediaryNode = aNode.parentNode;
 1.15119 +          }
 1.15120 +          if (prevNode) {
 1.15121 +            firstNode = aNode.firstChild;
 1.15122 +            domNode(aNode).transferContentTo(prevNode, true);
 1.15123 +
 1.15124 +            if (intermediaryNode && intermediaryNode.children.length === 0){
 1.15125 +              intermediaryNode.remove();
 1.15126 +            }
 1.15127 +
 1.15128 +            if (firstNode) {
 1.15129 +              composer.selection.setBefore(firstNode);
 1.15130 +            } else if (prevNode) {
 1.15131 +              if (prevNode.nodeType === 1) {
 1.15132 +                if (prevNode.lastChild) {
 1.15133 +                  composer.selection.setAfter(prevNode.lastChild);
 1.15134 +                } else {
 1.15135 +                  composer.selection.selectNode(prevNode);
 1.15136 +                }
 1.15137 +              } else {
 1.15138 +                composer.selection.setAfter(prevNode);
 1.15139 +              }
 1.15140 +            }
 1.15141 +            return true;
 1.15142 +          }
 1.15143 +        }
 1.15144 +      }
 1.15145 +      return false;
 1.15146 +    },
 1.15147 +
 1.15148 +    fixDeleteInTheBeginningOfControlSelection: function(composer) {
 1.15149 +      var selection = composer.selection,
 1.15150 +          prevNode = selection.getPreviousNode(),
 1.15151 +          selectedNode = selection.getSelectedNode(),
 1.15152 +          afterCaretNode;
 1.15153 +
 1.15154 +      if (selection.caretIsFirstInSelection()) {
 1.15155 +        if (selectedNode.nodeType === 3) {
 1.15156 +          selectedNode = selectedNode.parentNode;
 1.15157 +        }
 1.15158 +        afterCaretNode = selectedNode.firstChild;
 1.15159 +        domNode(selectedNode).transferContentTo(prevNode, true);
 1.15160 +        if (afterCaretNode) {
 1.15161 +          composer.selection.setBefore(afterCaretNode);
 1.15162 +        }
 1.15163 +        return true;
 1.15164 +      }
 1.15165 +      return false;
 1.15166 +    },
 1.15167 +
 1.15168 +    // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature)
 1.15169 +    // Returns true if some corrections is applied so events know when to prevent default
 1.15170 +    doLineBreaksModeEnterWithCaret: function(composer) {
 1.15171 +      var breakNodes = "p, pre, div, blockquote",
 1.15172 +          caretInfo, parent, txtNode,
 1.15173 +          ret = false;
 1.15174 +
 1.15175 +      caretInfo = composer.selection.getNodesNearCaret();
 1.15176 +      if (caretInfo) {
 1.15177 +
 1.15178 +        if (caretInfo.caretNode || caretInfo.nextNode) {
 1.15179 +          parent = dom.getParentElement(caretInfo.caretNode || caretInfo.nextNode, { query: breakNodes }, 2);
 1.15180 +          if (parent === composer.element) {
 1.15181 +            parent = undefined;
 1.15182 +          }
 1.15183 +        }
 1.15184 +
 1.15185 +        if (parent && caretInfo.caretNode) {
 1.15186 +          if (domNode(caretInfo.caretNode).is.lineBreak()) {
 1.15187 +
 1.15188 +            if (composer.config.doubleLineBreakEscapesBlock) {
 1.15189 +              // Double enter (enter on blank line) exits block element in useLineBreaks mode.
 1.15190 +              ret = true;
 1.15191 +              caretInfo.caretNode.parentNode.removeChild(caretInfo.caretNode);
 1.15192 +
 1.15193 +              // Ensure surplous line breaks are not added to preceding element
 1.15194 +              if (domNode(caretInfo.nextNode).is.lineBreak()) {
 1.15195 +                caretInfo.nextNode.parentNode.removeChild(caretInfo.nextNode);
 1.15196 +              }
 1.15197 +
 1.15198 +              var brNode = composer.doc.createElement('br');
 1.15199 +              if (domNode(caretInfo.nextNode).is.lineBreak() && caretInfo.nextNode === parent.lastChild) {
 1.15200 +                parent.parentNode.insertBefore(brNode, parent.nextSibling);
 1.15201 +              } else {
 1.15202 +                composer.selection.splitElementAtCaret(parent, brNode);
 1.15203 +              }
 1.15204 +
 1.15205 +              // Ensure surplous blank lines are not added to preceding element
 1.15206 +              if (caretInfo.nextNode && caretInfo.nextNode.nodeType === 3) {
 1.15207 +                // Replaces blank lines at the beginning of textnode
 1.15208 +                caretInfo.nextNode.data = caretInfo.nextNode.data.replace(/^ *[\r\n]+/, '');
 1.15209 +              }
 1.15210 +              composer.selection.setBefore(brNode);
 1.15211 +            }
 1.15212 +
 1.15213 +          } else if (caretInfo.caretNode.nodeType === 3 && wysihtml.browser.hasCaretBlockElementIssue() && caretInfo.textOffset === caretInfo.caretNode.data.length && !caretInfo.nextNode) {
 1.15214 +
 1.15215 +            // This fixes annoying webkit issue when you press enter at the end of a block then seemingly nothing happens.
 1.15216 +            // in reality one line break is generated and cursor is reported after it, but when entering something cursor jumps before the br
 1.15217 +            ret = true;
 1.15218 +            var br1 = composer.doc.createElement('br'),
 1.15219 +                br2 = composer.doc.createElement('br'),
 1.15220 +                f = composer.doc.createDocumentFragment();
 1.15221 +            f.appendChild(br1);
 1.15222 +            f.appendChild(br2);
 1.15223 +            composer.selection.insertNode(f);
 1.15224 +            composer.selection.setBefore(br2);
 1.15225 +
 1.15226 +          }
 1.15227 +        }
 1.15228 +      }
 1.15229 +      return ret;
 1.15230 +    }
 1.15231 +  };
 1.15232 +
 1.15233 +  var handleDeleteKeyPress = function(event, composer) {
 1.15234 +    var selection = composer.selection,
 1.15235 +        element = composer.element;
 1.15236 +
 1.15237 +    if (selection.isCollapsed()) {
 1.15238 +      /**
 1.15239 +       * when the editor is empty in useLineBreaks = false mode, preserve
 1.15240 +       * the default value in it which is <p><br></p>
 1.15241 +       */
 1.15242 +      if (composer.isEmpty() && !composer.config.useLineBreaks) {
 1.15243 +        event.preventDefault();
 1.15244 +        return;
 1.15245 +      }
 1.15246 +      if (actions.handleUneditableDeletion(composer)) {
 1.15247 +        event.preventDefault();
 1.15248 +        return;
 1.15249 +      }
 1.15250 +      if (actions.fixDeleteInTheBeginningOfLi(composer)) {
 1.15251 +        event.preventDefault();
 1.15252 +        return;
 1.15253 +      }
 1.15254 +      if (actions.fixDeleteInTheBeginningOfBlock(composer)) {
 1.15255 +        event.preventDefault();
 1.15256 +        return;
 1.15257 +      }
 1.15258 +      if (actions.fixLastBrDeletionInTable(composer)) {
 1.15259 +        event.preventDefault();
 1.15260 +        return;
 1.15261 +      }
 1.15262 +      if (wysihtml.browser.usesControlRanges()) {
 1.15263 +        if (actions.fixDeleteInTheBeginningOfControlSelection(composer)) {
 1.15264 +          event.preventDefault();
 1.15265 +          return;
 1.15266 +        }
 1.15267 +      }
 1.15268 +    } else {
 1.15269 +      if (selection.containsUneditable()) {
 1.15270 +        event.preventDefault();
 1.15271 +        selection.deleteContents();
 1.15272 +      }
 1.15273 +    }
 1.15274 +  };
 1.15275 +
 1.15276 +  var handleEnterKeyPress = function(event, composer) {
 1.15277 +    if (composer.config.useLineBreaks && !event.shiftKey && !event.ctrlKey) {
 1.15278 +      // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature)
 1.15279 +
 1.15280 +      var breakNodes = "p, pre, div, blockquote",
 1.15281 +          caretInfo, parent, txtNode;
 1.15282 +
 1.15283 +      if (composer.selection.isCollapsed()) {
 1.15284 +        if (actions.doLineBreaksModeEnterWithCaret(composer)) {
 1.15285 +          event.preventDefault();
 1.15286 +        }
 1.15287 +      }
 1.15288 +    }
 1.15289 +
 1.15290 +    if (browser.hasCaretAtLinkEndInsertionProblems() && composer.selection.caretIsInTheEndOfNode()) {
 1.15291 +      var target = composer.selection.getSelectedNode(true),
 1.15292 +          targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element
 1.15293 +          invisibleSpace, space;
 1.15294 +
 1.15295 +      if (targetEl && targetEl.closest('a') && target.nodeType === 3 && target === targetEl.lastChild) {
 1.15296 +        // Seems like enter was pressed and caret was at the end of link node
 1.15297 +        // This means user wants to escape the link now (caret is last in link node too).
 1.15298 +        composer.selection.setAfter(targetEl);
 1.15299 +      }
 1.15300 +    }
 1.15301 +  };
 1.15302 +
 1.15303 +  var handleTabKeyDown = function(composer, element, shiftKey) {
 1.15304 +    if (!composer.selection.isCollapsed()) {
 1.15305 +      composer.selection.deleteContents();
 1.15306 +    } else if (composer.selection.caretIsInTheBeginnig('li')) {
 1.15307 +      if (shiftKey) {
 1.15308 +        if (composer.commands.exec('outdentList')) return;
 1.15309 +      } else {
 1.15310 +        if (composer.commands.exec('indentList')) return;
 1.15311 +      }
 1.15312 +    }
 1.15313 +
 1.15314 +    // Is &emsp; close enough to tab. Could not find enough counter arguments for now.
 1.15315 +    composer.commands.exec("insertHTML", "&emsp;");
 1.15316 +  };
 1.15317 +
 1.15318 +  var handleDomNodeRemoved = function(event) {
 1.15319 +      if (this.domNodeRemovedInterval) {
 1.15320 +        clearInterval(domNodeRemovedInterval);
 1.15321 +      }
 1.15322 +      this.parent.fire("destroy:composer");
 1.15323 +  };
 1.15324 +
 1.15325 +  // Listens to "drop", "paste", "mouseup", "focus", "keyup" events and fires
 1.15326 +  var handleUserInteraction = function (event) {
 1.15327 +    this.parent.fire("beforeinteraction", event).fire("beforeinteraction:composer", event);
 1.15328 +    setTimeout((function() {
 1.15329 +      this.parent.fire("interaction", event).fire("interaction:composer", event);
 1.15330 +    }).bind(this), 0);
 1.15331 +  };
 1.15332 +
 1.15333 +  var handleFocus = function(event) {
 1.15334 +    this.parent.fire("focus", event).fire("focus:composer", event);
 1.15335 +
 1.15336 +    // Delay storing of state until all focus handler are fired
 1.15337 +    // especially the one which resets the placeholder
 1.15338 +    setTimeout((function() {
 1.15339 +      this.focusState = this.getValue(false, false);
 1.15340 +    }).bind(this), 0);
 1.15341 +  };
 1.15342 +
 1.15343 +  var handleBlur = function(event) {
 1.15344 +    if (this.focusState !== this.getValue(false, false)) {
 1.15345 +      //create change event if supported (all except IE8)
 1.15346 +      var changeevent = event;
 1.15347 +      if(typeof Object.create == 'function') {
 1.15348 +        changeevent = Object.create(event, { type: { value: 'change' } });
 1.15349 +      }
 1.15350 +      this.parent.fire("change", changeevent).fire("change:composer", changeevent);
 1.15351 +    }
 1.15352 +    this.parent.fire("blur", event).fire("blur:composer", event);
 1.15353 +  };
 1.15354 +
 1.15355 +  var handlePaste = function(event) {
 1.15356 +    this.parent.fire(event.type, event).fire(event.type + ":composer", event);
 1.15357 +    if (event.type === "paste") {
 1.15358 +      setTimeout((function() {
 1.15359 +        this.parent.fire("newword:composer");
 1.15360 +      }).bind(this), 0);
 1.15361 +    }
 1.15362 +  };
 1.15363 +
 1.15364 +  var handleCopy = function(event) {
 1.15365 +    if (this.config.copyedFromMarking) {
 1.15366 +      // If supported the copied source can be based directly on selection
 1.15367 +      // Very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection.
 1.15368 +      if (wysihtml.browser.supportsModernPaste()) {
 1.15369 +        event.clipboardData.setData("text/html", this.config.copyedFromMarking + this.selection.getHtml());
 1.15370 +        event.clipboardData.setData("text/plain", this.selection.getPlainText());
 1.15371 +        event.preventDefault();
 1.15372 +      }
 1.15373 +      this.parent.fire(event.type, event).fire(event.type + ":composer", event);
 1.15374 +    }
 1.15375 +  };
 1.15376 +
 1.15377 +  var handleKeyUp = function(event) {
 1.15378 +    var keyCode = event.keyCode;
 1.15379 +    if (keyCode === wysihtml.SPACE_KEY || keyCode === wysihtml.ENTER_KEY) {
 1.15380 +      this.parent.fire("newword:composer");
 1.15381 +    }
 1.15382 +  };
 1.15383 +
 1.15384 +  var handleMouseDown = function(event) {
 1.15385 +    if (!browser.canSelectImagesInContentEditable()) {
 1.15386 +      // Make sure that images are selected when clicking on them
 1.15387 +      var target = event.target,
 1.15388 +          allImages = this.element.querySelectorAll('img'),
 1.15389 +          notMyImages = this.element.querySelectorAll('.' + this.config.classNames.uneditableContainer + ' img'),
 1.15390 +          myImages = wysihtml.lang.array(allImages).without(notMyImages);
 1.15391 +
 1.15392 +      if (target.nodeName === "IMG" && wysihtml.lang.array(myImages).contains(target)) {
 1.15393 +        this.selection.selectNode(target);
 1.15394 +      }
 1.15395 +    }
 1.15396 +
 1.15397 +    // Saves mousedown position for IE controlSelect fix
 1.15398 +    if (wysihtml.browser.usesControlRanges()) {
 1.15399 +      this.selection.lastMouseDownPos = {x: event.clientX, y: event.clientY};
 1.15400 +      setTimeout(function() {
 1.15401 +        delete this.selection.lastMouseDownPos;
 1.15402 +      }.bind(this), 0);
 1.15403 +    }
 1.15404 +  };
 1.15405 +
 1.15406 +  // IE has this madness of control selects of overflowed and some other elements (weird box around element on selection and second click selects text)
 1.15407 +  // This fix handles the second click problem by adding cursor to the right position under cursor inside when controlSelection is made
 1.15408 +  var handleIEControlSelect = function(event) {
 1.15409 +    var target = event.target,
 1.15410 +        pos = this.selection.lastMouseDownPos;
 1.15411 +    if (pos) {
 1.15412 +      var caretPosition = document.body.createTextRange();
 1.15413 +        setTimeout(function() {
 1.15414 +          try {
 1.15415 +            caretPosition.moveToPoint(pos.x, pos.y);
 1.15416 +            caretPosition.select();
 1.15417 +          } catch (e) {}
 1.15418 +        }.bind(this), 0);
 1.15419 +    }
 1.15420 +  };
 1.15421 +
 1.15422 +  var handleClick = function(event) {
 1.15423 +    if (this.config.classNames.uneditableContainer) {
 1.15424 +      // If uneditables is configured, makes clicking on uneditable move caret after clicked element (so it can be deleted like text)
 1.15425 +      // If uneditable needs text selection itself event.stopPropagation can be used to prevent this behaviour
 1.15426 +      var uneditable = wysihtml.dom.getParentElement(event.target, { query: "." + this.config.classNames.uneditableContainer }, false, this.element);
 1.15427 +      if (uneditable) {
 1.15428 +        this.selection.setAfter(uneditable);
 1.15429 +      }
 1.15430 +    }
 1.15431 +  };
 1.15432 +
 1.15433 +  var handleDrop = function(event) {
 1.15434 +    if (!browser.canSelectImagesInContentEditable()) {
 1.15435 +      // TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case
 1.15436 +      setTimeout((function() {
 1.15437 +        this.selection.getSelection().removeAllRanges();
 1.15438 +      }).bind(this), 0);
 1.15439 +    }
 1.15440 +  };
 1.15441 +
 1.15442 +  var handleKeyDown = function(event) {
 1.15443 +    var keyCode = event.keyCode,
 1.15444 +        command = shortcuts[keyCode],
 1.15445 +        target = this.selection.getSelectedNode(true),
 1.15446 +        targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element
 1.15447 +        parent;
 1.15448 +
 1.15449 +    // Select all (meta/ctrl + a)
 1.15450 +    if ((event.ctrlKey || event.metaKey) && !event.altKey && keyCode === 65) {
 1.15451 +      this.selection.selectAll();
 1.15452 +      event.preventDefault();
 1.15453 +      return;
 1.15454 +    }
 1.15455 +
 1.15456 +    // Shortcut logic
 1.15457 +    if ((event.ctrlKey || event.metaKey) && !event.altKey && command) {
 1.15458 +      this.commands.exec(command);
 1.15459 +      event.preventDefault();
 1.15460 +    }
 1.15461 +
 1.15462 +    if (keyCode === wysihtml.BACKSPACE_KEY) {
 1.15463 +      // Delete key override for special cases
 1.15464 +      handleDeleteKeyPress(event, this);
 1.15465 +    }
 1.15466 +
 1.15467 +    // Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor
 1.15468 +    if (keyCode === wysihtml.BACKSPACE_KEY || keyCode === wysihtml.DELETE_KEY) {
 1.15469 +      if (target && target.nodeName === "IMG") {
 1.15470 +        event.preventDefault();
 1.15471 +        parent = target.parentNode;
 1.15472 +        parent.removeChild(target);// delete the <img>
 1.15473 +        // And it's parent <a> too if it hasn't got any other child nodes
 1.15474 +        if (parent.nodeName === "A" && !parent.firstChild) {
 1.15475 +          parent.parentNode.removeChild(parent);
 1.15476 +        }
 1.15477 +        setTimeout((function() {
 1.15478 +          wysihtml.quirks.redraw(this.element);
 1.15479 +        }).bind(this), 0);
 1.15480 +      }
 1.15481 +    }
 1.15482 +
 1.15483 +    if (this.config.handleTabKey && keyCode === wysihtml.TAB_KEY) {
 1.15484 +      // TAB key handling
 1.15485 +      event.preventDefault();
 1.15486 +      handleTabKeyDown(this, this.element, event.shiftKey);
 1.15487 +    }
 1.15488 +
 1.15489 +    if (keyCode === wysihtml.ENTER_KEY) {
 1.15490 +      handleEnterKeyPress(event, this);
 1.15491 +    }
 1.15492 +
 1.15493 +  };
 1.15494 +
 1.15495 +  var handleKeyPress = function(event) {
 1.15496 +
 1.15497 +    // This block should run only if some character is inserted (nor command keys like delete, backspace, enter, etc.)
 1.15498 +    if (event.which !== 0) {
 1.15499 +
 1.15500 +      // Test if caret is last in a link in webkit and try to fix webkit problem,
 1.15501 +      // that all inserted content is added outside of link.
 1.15502 +      // This issue was added as a not thought through fix for getting caret after link in contenteditable if it is last in editable area.
 1.15503 +      // Allthough it fixes this minor case it actually introduces a cascade of problems when editing links.
 1.15504 +      // The standard approachi in other wysiwygs seems as a step backwards - introducing a separate modal for managing links content text.
 1.15505 +      // I find it to be too big of a tradeoff in terms of expected simple UI flow, thus trying to fight against it.
 1.15506 +      // Also adds link escaping by double space with caret at the end of link for all browsers
 1.15507 +
 1.15508 +      if (this.selection.caretIsInTheEndOfNode()) {
 1.15509 +        var target = this.selection.getSelectedNode(true),
 1.15510 +            targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element
 1.15511 +            invisibleSpace, space;
 1.15512 +
 1.15513 +        if (targetEl && targetEl.closest('a') && target === targetEl.lastChild) {
 1.15514 +
 1.15515 +          if (event.which !== 32 || this.selection.caretIsInTheEndOfNode(true) && browser.hasCaretAtLinkEndInsertionProblems()) {
 1.15516 +            // Executed if there is no whitespace before caret in textnode in case of pressing space.
 1.15517 +            // Whitespace before marks that user wants to escape the node by pressing double space.
 1.15518 +            // Otherwise insert the character in the link not out as it would like to go natively
 1.15519 +
 1.15520 +            invisibleSpace = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE);
 1.15521 +            this.selection.insertNode(invisibleSpace);
 1.15522 +            this.selection.setBefore(invisibleSpace);
 1.15523 +            setTimeout(function() {
 1.15524 +
 1.15525 +              if (invisibleSpace.textContent.length > 1) {
 1.15526 +                invisibleSpace.textContent = invisibleSpace.textContent.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, '');
 1.15527 +                this.selection.setAfter(invisibleSpace);
 1.15528 +              } else {
 1.15529 +                invisibleSpace.remove();
 1.15530 +              }
 1.15531 +
 1.15532 +            }.bind(this), 0);
 1.15533 +          } else if (event.which === 32) {
 1.15534 +            // Seems like space was pressed and there was a space before the caret allready
 1.15535 +            // This means user wants to escape the link now (caret is last in link node too) so we let the native browser do it-s job and escape.
 1.15536 +            // But lets move the trailing space too out of link if present
 1.15537 +
 1.15538 +            if (target.nodeType === 3 && (/[\u00A0 ]$/).test(target.textContent)) {
 1.15539 +
 1.15540 +              target.textContent = target.textContent.replace(/[\u00A0 ]$/, '');
 1.15541 +              space = this.doc.createTextNode(' ');
 1.15542 +              targetEl.parentNode.insertBefore(space, targetEl.nextSibling);
 1.15543 +              this.selection.setAfter(space, false);
 1.15544 +              event.preventDefault();
 1.15545 +
 1.15546 +            }
 1.15547 +          }
 1.15548 +        }
 1.15549 +      }
 1.15550 +    }
 1.15551 +  }
 1.15552 +
 1.15553 +  var handleIframeFocus = function(event) {
 1.15554 +    setTimeout((function() {
 1.15555 +      if (this.doc.querySelector(":focus") !== this.element) {
 1.15556 +        this.focus();
 1.15557 +      }
 1.15558 +    }).bind(this), 0);
 1.15559 +  };
 1.15560 +
 1.15561 +  var handleIframeBlur = function(event) {
 1.15562 +    setTimeout((function() {
 1.15563 +      this.selection.getSelection().removeAllRanges();
 1.15564 +    }).bind(this), 0);
 1.15565 +  };
 1.15566 +
 1.15567 +  // Testing requires actions to be accessible from out of scope
 1.15568 +  wysihtml.views.Composer.prototype.observeActions = actions;
 1.15569 +
 1.15570 +  wysihtml.views.Composer.prototype.observe = function() {
 1.15571 +    var that                = this,
 1.15572 +        container           = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
 1.15573 +        element             = this.element,
 1.15574 +        focusBlurElement    = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? this.element : this.sandbox.getWindow();
 1.15575 +
 1.15576 +    this.focusState = this.getValue(false, false);
 1.15577 +    this.actions = actions;
 1.15578 +
 1.15579 +    // --------- destroy:composer event ---------
 1.15580 +    container.addEventListener(["DOMNodeRemoved"], handleDomNodeRemoved.bind(this), false);
 1.15581 +
 1.15582 +    // DOMNodeRemoved event is not supported in IE 8
 1.15583 +    // TODO: try to figure out a polyfill style fix, so it could be transferred to polyfills and removed if ie8 is not needed
 1.15584 +    if (!browser.supportsMutationEvents()) {
 1.15585 +      this.domNodeRemovedInterval = setInterval(function() {
 1.15586 +        if (!dom.contains(document.documentElement, container)) {
 1.15587 +          handleDomNodeRemoved.call(this);
 1.15588 +        }
 1.15589 +      }, 250);
 1.15590 +    }
 1.15591 +
 1.15592 +    actions.addListeners(focusBlurElement, ['drop', 'paste', 'mouseup', 'focus', 'keyup'], handleUserInteraction.bind(this));
 1.15593 +    focusBlurElement.addEventListener('focus', handleFocus.bind(this), false);
 1.15594 +    focusBlurElement.addEventListener('blur',  handleBlur.bind(this), false);
 1.15595 +
 1.15596 +    actions.addListeners(this.element, ['drop', 'paste', 'beforepaste'], handlePaste.bind(this), false);
 1.15597 +    this.element.addEventListener('copy',       handleCopy.bind(this), false);
 1.15598 +    this.element.addEventListener('mousedown',  handleMouseDown.bind(this), false);
 1.15599 +    this.element.addEventListener('click',      handleClick.bind(this), false);
 1.15600 +    this.element.addEventListener('drop',       handleDrop.bind(this), false);
 1.15601 +    this.element.addEventListener('keyup',      handleKeyUp.bind(this), false);
 1.15602 +    this.element.addEventListener('keydown',    handleKeyDown.bind(this), false);
 1.15603 +    this.element.addEventListener('keypress',   handleKeyPress.bind(this), false);
 1.15604 +
 1.15605 +    // IE controlselect madness fix
 1.15606 +    if (wysihtml.browser.usesControlRanges()) {
 1.15607 +      this.element.addEventListener('mscontrolselect', handleIEControlSelect.bind(this), false);
 1.15608 +    }
 1.15609 +
 1.15610 +    this.element.addEventListener("dragenter", (function() {
 1.15611 +      this.parent.fire("unset_placeholder");
 1.15612 +    }).bind(this), false);
 1.15613 +
 1.15614 +  };
 1.15615 +})(wysihtml);
 1.15616 +
 1.15617 +/**
 1.15618 + * Class that takes care that the value of the composer and the textarea is always in sync
 1.15619 + */
 1.15620 +(function(wysihtml) {
 1.15621 +  var INTERVAL = 400;
 1.15622 +
 1.15623 +  wysihtml.views.Synchronizer = Base.extend(
 1.15624 +    /** @scope wysihtml.views.Synchronizer.prototype */ {
 1.15625 +
 1.15626 +    constructor: function(editor, textarea, composer) {
 1.15627 +      this.editor   = editor;
 1.15628 +      this.textarea = textarea;
 1.15629 +      this.composer = composer;
 1.15630 +
 1.15631 +      this._observe();
 1.15632 +    },
 1.15633 +
 1.15634 +    /**
 1.15635 +     * Sync html from composer to textarea
 1.15636 +     * Takes care of placeholders
 1.15637 +     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the textarea
 1.15638 +     */
 1.15639 +    fromComposerToTextarea: function(shouldParseHtml) {
 1.15640 +      this.textarea.setValue(wysihtml.lang.string(this.composer.getValue(false, false)).trim(), shouldParseHtml);
 1.15641 +    },
 1.15642 +
 1.15643 +    /**
 1.15644 +     * Sync value of textarea to composer
 1.15645 +     * Takes care of placeholders
 1.15646 +     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer
 1.15647 +     */
 1.15648 +    fromTextareaToComposer: function(shouldParseHtml) {
 1.15649 +      var textareaValue = this.textarea.getValue(false, false);
 1.15650 +      if (textareaValue) {
 1.15651 +        this.composer.setValue(textareaValue, shouldParseHtml);
 1.15652 +      } else {
 1.15653 +        this.composer.clear();
 1.15654 +        this.editor.fire("set_placeholder");
 1.15655 +      }
 1.15656 +    },
 1.15657 +
 1.15658 +    /**
 1.15659 +     * Invoke syncing based on view state
 1.15660 +     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer/textarea
 1.15661 +     */
 1.15662 +    sync: function(shouldParseHtml) {
 1.15663 +      if (this.editor.currentView.name === "textarea") {
 1.15664 +        this.fromTextareaToComposer(shouldParseHtml);
 1.15665 +      } else {
 1.15666 +        this.fromComposerToTextarea(shouldParseHtml);
 1.15667 +      }
 1.15668 +    },
 1.15669 +
 1.15670 +    /**
 1.15671 +     * Initializes interval-based syncing
 1.15672 +     * also makes sure that on-submit the composer's content is synced with the textarea
 1.15673 +     * immediately when the form gets submitted
 1.15674 +     */
 1.15675 +    _observe: function() {
 1.15676 +      var interval,
 1.15677 +          that          = this,
 1.15678 +          form          = this.textarea.element.form,
 1.15679 +          startInterval = function() {
 1.15680 +            interval = setInterval(function() { that.fromComposerToTextarea(); }, INTERVAL);
 1.15681 +          },
 1.15682 +          stopInterval  = function() {
 1.15683 +            clearInterval(interval);
 1.15684 +            interval = null;
 1.15685 +          };
 1.15686 +
 1.15687 +      startInterval();
 1.15688 +
 1.15689 +      if (form) {
 1.15690 +        // If the textarea is in a form make sure that after onreset and onsubmit the composer
 1.15691 +        // has the correct state
 1.15692 +        wysihtml.dom.observe(form, "submit", function() {
 1.15693 +          that.sync(true);
 1.15694 +        });
 1.15695 +        wysihtml.dom.observe(form, "reset", function() {
 1.15696 +          setTimeout(function() { that.fromTextareaToComposer(); }, 0);
 1.15697 +        });
 1.15698 +      }
 1.15699 +
 1.15700 +      this.editor.on("change_view", function(view) {
 1.15701 +        if (view === "composer" && !interval) {
 1.15702 +          that.fromTextareaToComposer(true);
 1.15703 +          startInterval();
 1.15704 +        } else if (view === "textarea") {
 1.15705 +          that.fromComposerToTextarea(true);
 1.15706 +          stopInterval();
 1.15707 +        }
 1.15708 +      });
 1.15709 +
 1.15710 +      this.editor.on("destroy:composer", stopInterval);
 1.15711 +    }
 1.15712 +  });
 1.15713 +})(wysihtml);
 1.15714 +
 1.15715 +(function(wysihtml) {
 1.15716 +
 1.15717 +  wysihtml.views.SourceView = Base.extend(
 1.15718 +    /** @scope wysihtml.views.SourceView.prototype */ {
 1.15719 +
 1.15720 +    constructor: function(editor, composer) {
 1.15721 +      this.editor   = editor;
 1.15722 +      this.composer = composer;
 1.15723 +
 1.15724 +      this._observe();
 1.15725 +    },
 1.15726 +
 1.15727 +    switchToTextarea: function(shouldParseHtml) {
 1.15728 +      var composerStyles = this.composer.win.getComputedStyle(this.composer.element),
 1.15729 +          width = parseFloat(composerStyles.width),
 1.15730 +          height = Math.max(parseFloat(composerStyles.height), 100);
 1.15731 +
 1.15732 +      if (!this.textarea) {
 1.15733 +        this.textarea = this.composer.doc.createElement('textarea');
 1.15734 +        this.textarea.className = "wysihtml-source-view";
 1.15735 +      }
 1.15736 +      this.textarea.style.width = width + 'px';
 1.15737 +      this.textarea.style.height = height + 'px';
 1.15738 +      this.textarea.value = this.editor.getValue(shouldParseHtml, true);
 1.15739 +      this.composer.element.parentNode.insertBefore(this.textarea, this.composer.element);
 1.15740 +      this.editor.currentView = "source";
 1.15741 +      this.composer.element.style.display = 'none';
 1.15742 +    },
 1.15743 +
 1.15744 +    switchToComposer: function(shouldParseHtml) {
 1.15745 +      var textareaValue = this.textarea.value;
 1.15746 +      if (textareaValue) {
 1.15747 +        this.composer.setValue(textareaValue, shouldParseHtml);
 1.15748 +      } else {
 1.15749 +        this.composer.clear();
 1.15750 +        this.editor.fire("set_placeholder");
 1.15751 +      }
 1.15752 +      this.textarea.parentNode.removeChild(this.textarea);
 1.15753 +      this.editor.currentView = this.composer;
 1.15754 +      this.composer.element.style.display = '';
 1.15755 +    },
 1.15756 +
 1.15757 +    _observe: function() {
 1.15758 +      this.editor.on("change_view", function(view) {
 1.15759 +        if (view === "composer") {
 1.15760 +          this.switchToComposer(true);
 1.15761 +        } else if (view === "textarea") {
 1.15762 +          this.switchToTextarea(true);
 1.15763 +        }
 1.15764 +      }.bind(this));
 1.15765 +    }
 1.15766 +
 1.15767 +  });
 1.15768 +
 1.15769 +})(wysihtml);
 1.15770 +
 1.15771 +wysihtml.views.Textarea = wysihtml.views.View.extend(
 1.15772 +  /** @scope wysihtml.views.Textarea.prototype */ {
 1.15773 +  name: "textarea",
 1.15774 +
 1.15775 +  constructor: function(parent, textareaElement, config) {
 1.15776 +    this.base(parent, textareaElement, config);
 1.15777 +
 1.15778 +    this._observe();
 1.15779 +  },
 1.15780 +
 1.15781 +  clear: function() {
 1.15782 +    this.element.value = "";
 1.15783 +  },
 1.15784 +
 1.15785 +  getValue: function(parse) {
 1.15786 +    var value = this.isEmpty() ? "" : this.element.value;
 1.15787 +    if (parse !== false) {
 1.15788 +      value = this.parent.parse(value);
 1.15789 +    }
 1.15790 +    return value;
 1.15791 +  },
 1.15792 +
 1.15793 +  setValue: function(html, parse) {
 1.15794 +    if (parse !== false) {
 1.15795 +      html = this.parent.parse(html);
 1.15796 +    }
 1.15797 +    this.element.value = html;
 1.15798 +  },
 1.15799 +
 1.15800 +  cleanUp: function(rules) {
 1.15801 +      var html = this.parent.parse(this.element.value, undefined, rules);
 1.15802 +      this.element.value = html;
 1.15803 +  },
 1.15804 +
 1.15805 +  hasPlaceholderSet: function() {
 1.15806 +    var supportsPlaceholder = wysihtml.browser.supportsPlaceholderAttributeOn(this.element),
 1.15807 +        placeholderText     = this.element.getAttribute("placeholder") || null,
 1.15808 +        value               = this.element.value,
 1.15809 +        isEmpty             = !value;
 1.15810 +    return (supportsPlaceholder && isEmpty) || (value === placeholderText);
 1.15811 +  },
 1.15812 +
 1.15813 +  isEmpty: function() {
 1.15814 +    return !wysihtml.lang.string(this.element.value).trim() || this.hasPlaceholderSet();
 1.15815 +  },
 1.15816 +
 1.15817 +  _observe: function() {
 1.15818 +    var element = this.element,
 1.15819 +        parent  = this.parent,
 1.15820 +        eventMapping = {
 1.15821 +          focusin:  "focus",
 1.15822 +          focusout: "blur"
 1.15823 +        },
 1.15824 +        /**
 1.15825 +         * Calling focus() or blur() on an element doesn't synchronously trigger the attached focus/blur events
 1.15826 +         * This is the case for focusin and focusout, so let's use them whenever possible, kkthxbai
 1.15827 +         */
 1.15828 +        events = wysihtml.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"];
 1.15829 +
 1.15830 +    parent.on("beforeload", function() {
 1.15831 +      wysihtml.dom.observe(element, events, function(event) {
 1.15832 +        var eventName = eventMapping[event.type] || event.type;
 1.15833 +        parent.fire(eventName).fire(eventName + ":textarea");
 1.15834 +      });
 1.15835 +
 1.15836 +      wysihtml.dom.observe(element, ["paste", "drop"], function() {
 1.15837 +        setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0);
 1.15838 +      });
 1.15839 +    });
 1.15840 +  }
 1.15841 +});
 1.15842 +
 1.15843 +/**
 1.15844 + * WYSIHTML Editor
 1.15845 + *
 1.15846 + * @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface
 1.15847 + * @param {Object} [config] See defaults object below for explanation of each individual config option
 1.15848 + *
 1.15849 + * @events
 1.15850 + *    load
 1.15851 + *    beforeload (for internal use only)
 1.15852 + *    focus
 1.15853 + *    focus:composer
 1.15854 + *    focus:textarea
 1.15855 + *    blur
 1.15856 + *    blur:composer
 1.15857 + *    blur:textarea
 1.15858 + *    change
 1.15859 + *    change:composer
 1.15860 + *    change:textarea
 1.15861 + *    paste
 1.15862 + *    paste:composer
 1.15863 + *    paste:textarea
 1.15864 + *    newword:composer
 1.15865 + *    destroy:composer
 1.15866 + *    undo:composer
 1.15867 + *    redo:composer
 1.15868 + *    beforecommand:composer
 1.15869 + *    aftercommand:composer
 1.15870 + *    enable:composer
 1.15871 + *    disable:composer
 1.15872 + *    change_view
 1.15873 + */
 1.15874 +(function(wysihtml) {
 1.15875 +  var undef;
 1.15876 +
 1.15877 +  wysihtml.Editor = wysihtml.lang.Dispatcher.extend({
 1.15878 +    /** @scope wysihtml.Editor.prototype */
 1.15879 +    defaults: {
 1.15880 +      // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body
 1.15881 +      name:                 undef,
 1.15882 +      // Whether the editor should look like the textarea (by adopting styles)
 1.15883 +      style:                true,
 1.15884 +      // Whether urls, entered by the user should automatically become clickable-links
 1.15885 +      autoLink:             true,
 1.15886 +      // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation
 1.15887 +      handleTabKey:         true,
 1.15888 +      // Object which includes parser rules to apply when html gets cleaned
 1.15889 +      // See parser_rules/*.js for examples
 1.15890 +      parserRules:          { tags: { br: {}, span: {}, div: {}, p: {}, b: {}, i: {}, u: {} }, classes: {} },
 1.15891 +      // Object which includes parser when the user inserts content via copy & paste. If null parserRules will be used instead
 1.15892 +      pasteParserRulesets: null,
 1.15893 +      // Parser method to use when the user inserts content
 1.15894 +      parser:               wysihtml.dom.parse,
 1.15895 +      // By default wysihtml will insert a <br> for line breaks, set this to false to use <p>
 1.15896 +      useLineBreaks:        true,
 1.15897 +      // Double enter (enter on blank line) exits block element in useLineBreaks mode.
 1.15898 +      // It enables a way of escaping out of block elements and splitting block elements
 1.15899 +      doubleLineBreakEscapesBlock: true,
 1.15900 +      // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
 1.15901 +      stylesheets:          [],
 1.15902 +      // Placeholder text to use, defaults to the placeholder attribute on the textarea element
 1.15903 +      placeholderText:      undef,
 1.15904 +      // Whether the rich text editor should be rendered on touch devices (wysihtml >= 0.3.0 comes with basic support for iOS 5)
 1.15905 +      supportTouchDevices:  true,
 1.15906 +      // Whether senseless <span> elements (empty or without attributes) should be removed/replaced with their content
 1.15907 +      cleanUp:              true,
 1.15908 +      // Whether to use div instead of secure iframe
 1.15909 +      contentEditableMode: false,
 1.15910 +      classNames: {
 1.15911 +        // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
 1.15912 +        composer: "wysihtml-editor",
 1.15913 +        // Class name to add to the body when the wysihtml editor is supported
 1.15914 +        body: "wysihtml-supported",
 1.15915 +        // classname added to editable area element (iframe/div) on creation
 1.15916 +        sandbox: "wysihtml-sandbox",
 1.15917 +        // class on editable area with placeholder
 1.15918 +        placeholder: "wysihtml-placeholder",
 1.15919 +        // Classname of container that editor should not touch and pass through
 1.15920 +        uneditableContainer: "wysihtml-uneditable-container"
 1.15921 +      },
 1.15922 +      // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste)
 1.15923 +      // Also copied source is based directly on selection - 
 1.15924 +      // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection).
 1.15925 +      // If falsy value is passed source override is also disabled
 1.15926 +      copyedFromMarking: '<meta name="copied-from" content="wysihtml">'
 1.15927 +    },
 1.15928 +    
 1.15929 +    constructor: function(editableElement, config) {
 1.15930 +      this.editableElement  = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement;
 1.15931 +      this.config           = wysihtml.lang.object({}).merge(this.defaults).merge(config).get();
 1.15932 +      this._isCompatible    = wysihtml.browser.supported();
 1.15933 +
 1.15934 +      // merge classNames
 1.15935 +      if (config && config.classNames) {
 1.15936 +        wysihtml.lang.object(this.config.classNames).merge(config.classNames);
 1.15937 +      }
 1.15938 +
 1.15939 +      if (this.editableElement.nodeName.toLowerCase() != "textarea") {
 1.15940 +          this.config.contentEditableMode = true;
 1.15941 +          this.config.noTextarea = true;
 1.15942 +      }
 1.15943 +      if (!this.config.noTextarea) {
 1.15944 +          this.textarea         = new wysihtml.views.Textarea(this, this.editableElement, this.config);
 1.15945 +          this.currentView      = this.textarea;
 1.15946 +      }
 1.15947 +
 1.15948 +      // Sort out unsupported/unwanted browsers here
 1.15949 +      if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml.browser.isTouchDevice())) {
 1.15950 +        var that = this;
 1.15951 +        setTimeout(function() { that.fire("beforeload").fire("load"); }, 0);
 1.15952 +        return;
 1.15953 +      }
 1.15954 +
 1.15955 +      // Add class name to body, to indicate that the editor is supported
 1.15956 +      wysihtml.dom.addClass(document.body, this.config.classNames.body);
 1.15957 +
 1.15958 +      this.composer = new wysihtml.views.Composer(this, this.editableElement, this.config);
 1.15959 +      this.currentView = this.composer;
 1.15960 +
 1.15961 +      if (typeof(this.config.parser) === "function") {
 1.15962 +        this._initParser();
 1.15963 +      }
 1.15964 +
 1.15965 +      this.on("beforeload", this.handleBeforeLoad);
 1.15966 +    },
 1.15967 +
 1.15968 +    handleBeforeLoad: function() {
 1.15969 +        if (!this.config.noTextarea) {
 1.15970 +          this.synchronizer = new wysihtml.views.Synchronizer(this, this.textarea, this.composer);
 1.15971 +        } else {
 1.15972 +          this.sourceView = new wysihtml.views.SourceView(this, this.composer);
 1.15973 +        }
 1.15974 +        this.runEditorExtenders();
 1.15975 +    },
 1.15976 +    
 1.15977 +    runEditorExtenders: function() {
 1.15978 +      wysihtml.editorExtenders.forEach(function(extender) {
 1.15979 +        extender(this);
 1.15980 +      }.bind(this));
 1.15981 +    },
 1.15982 +
 1.15983 +    isCompatible: function() {
 1.15984 +      return this._isCompatible;
 1.15985 +    },
 1.15986 +
 1.15987 +    clear: function() {
 1.15988 +      this.currentView.clear();
 1.15989 +      return this;
 1.15990 +    },
 1.15991 +
 1.15992 +    getValue: function(parse, clearInternals) {
 1.15993 +      return this.currentView.getValue(parse, clearInternals);
 1.15994 +    },
 1.15995 +
 1.15996 +    setValue: function(html, parse) {
 1.15997 +      this.fire("unset_placeholder");
 1.15998 +
 1.15999 +      if (!html) {
 1.16000 +        return this.clear();
 1.16001 +      }
 1.16002 +
 1.16003 +      this.currentView.setValue(html, parse);
 1.16004 +      return this;
 1.16005 +    },
 1.16006 +
 1.16007 +    cleanUp: function(rules) {
 1.16008 +        this.currentView.cleanUp(rules);
 1.16009 +    },
 1.16010 +
 1.16011 +    focus: function(setToEnd) {
 1.16012 +      this.currentView.focus(setToEnd);
 1.16013 +      return this;
 1.16014 +    },
 1.16015 +
 1.16016 +    /**
 1.16017 +     * Deactivate editor (make it readonly)
 1.16018 +     */
 1.16019 +    disable: function() {
 1.16020 +      this.currentView.disable();
 1.16021 +      return this;
 1.16022 +    },
 1.16023 +
 1.16024 +    /**
 1.16025 +     * Activate editor
 1.16026 +     */
 1.16027 +    enable: function() {
 1.16028 +      this.currentView.enable();
 1.16029 +      return this;
 1.16030 +    },
 1.16031 +
 1.16032 +    isEmpty: function() {
 1.16033 +      return this.currentView.isEmpty();
 1.16034 +    },
 1.16035 +
 1.16036 +    hasPlaceholderSet: function() {
 1.16037 +      return this.currentView.hasPlaceholderSet();
 1.16038 +    },
 1.16039 +
 1.16040 +    destroy: function() {
 1.16041 +      if (this.composer && this.composer.sandbox) {
 1.16042 +        this.composer.sandbox.destroy();
 1.16043 +      }
 1.16044 +      this.fire("destroy:composer");
 1.16045 +      this.off();
 1.16046 +    },
 1.16047 +
 1.16048 +    parse: function(htmlOrElement, clearInternals, customRules) {
 1.16049 +      var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null);
 1.16050 +      var returnValue = this.config.parser(htmlOrElement, {
 1.16051 +        "rules": customRules || this.config.parserRules,
 1.16052 +        "cleanUp": this.config.cleanUp,
 1.16053 +        "context": parseContext,
 1.16054 +        "uneditableClass": this.config.classNames.uneditableContainer,
 1.16055 +        "clearInternals" : clearInternals
 1.16056 +      });
 1.16057 +      if (typeof(htmlOrElement) === "object") {
 1.16058 +        wysihtml.quirks.redraw(htmlOrElement);
 1.16059 +      }
 1.16060 +      return returnValue;
 1.16061 +    },
 1.16062 +
 1.16063 +    /**
 1.16064 +     * Prepare html parser logic
 1.16065 +     *  - Observes for paste and drop
 1.16066 +     */
 1.16067 +    _initParser: function() {
 1.16068 +      var oldHtml;
 1.16069 +
 1.16070 +      if (wysihtml.browser.supportsModernPaste()) {
 1.16071 +        this.on("paste:composer", function(event) {
 1.16072 +          event.preventDefault();
 1.16073 +          oldHtml = wysihtml.dom.getPastedHtml(event);
 1.16074 +          if (oldHtml) {
 1.16075 +            this._cleanAndPaste(oldHtml);
 1.16076 +          }
 1.16077 +        }.bind(this));
 1.16078 +
 1.16079 +      } else {
 1.16080 +        this.on("beforepaste:composer", function(event) {
 1.16081 +          event.preventDefault();
 1.16082 +          var scrollPos = this.composer.getScrollPos();
 1.16083 +
 1.16084 +          wysihtml.dom.getPastedHtmlWithDiv(this.composer, function(pastedHTML) {
 1.16085 +            if (pastedHTML) {
 1.16086 +              this._cleanAndPaste(pastedHTML);
 1.16087 +            }
 1.16088 +            this.composer.setScrollPos(scrollPos);
 1.16089 +          }.bind(this));
 1.16090 +
 1.16091 +        }.bind(this));
 1.16092 +      }
 1.16093 +    },
 1.16094 +
 1.16095 +    _cleanAndPaste: function (oldHtml) {
 1.16096 +      var cleanHtml = wysihtml.quirks.cleanPastedHTML(oldHtml, {
 1.16097 +        "referenceNode": this.composer.element,
 1.16098 +        "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}],
 1.16099 +        "uneditableClass": this.config.classNames.uneditableContainer
 1.16100 +      });
 1.16101 +      this.composer.selection.deleteContents();
 1.16102 +      this.composer.selection.insertHTML(cleanHtml);
 1.16103 +    }
 1.16104 +  });
 1.16105 +})(wysihtml);

Impressum / About Us