vendor/assets/javascripts/wysihtml5x.js in wysihtml5x-rails-0.4.1 vs vendor/assets/javascripts/wysihtml5x.js in wysihtml5x-rails-0.4.4
- old
+ new
@@ -1,18 +1,37 @@
-/**
- * @license wysihtml5x v0.4.1
+// TODO: in future try to replace most inline compability checks with polyfills for code readability
+
+// element.textContent polyfill.
+// Unsupporting browsers: IE8
+
+if (Object.defineProperty && Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(Element.prototype, "textContent") && !Object.getOwnPropertyDescriptor(Element.prototype, "textContent").get) {
+ (function() {
+ var innerText = Object.getOwnPropertyDescriptor(Element.prototype, "innerText");
+ Object.defineProperty(Element.prototype, "textContent",
+ {
+ get: function() {
+ return innerText.get.call(this);
+ },
+ set: function(s) {
+ return innerText.set.call(this, s);
+ }
+ }
+ );
+ })();
+};/**
+ * @license wysihtml5x v0.4.4
* https://github.com/Edicy/wysihtml5
*
* Author: Christopher Blum (https://github.com/tiff)
* Secondary author of extended features: Oliver Pulges (https://github.com/pulges)
*
* Copyright (C) 2012 XING AG
* Licensed under the MIT license (MIT)
*
*/
var wysihtml5 = {
- version: "0.4.1",
+ version: "0.4.4",
// namespaces
commands: {},
dom: {},
quirks: {},
@@ -32,242 +51,4122 @@
ENTER_KEY: 13,
ESCAPE_KEY: 27,
SPACE_KEY: 32,
DELETE_KEY: 46
};
+;/**
+ * Rangy, a cross-browser JavaScript range and selection library
+ * http://code.google.com/p/rangy/
+ *
+ * Copyright 2013, Tim Down
+ * Licensed under the MIT license.
+ * Version: 1.3alpha.804
+ * Build date: 8 December 2013
+ */
+
+(function(global) {
+ var amdSupported = (typeof global.define == "function" && global.define.amd);
+
+ var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";
+
+ // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START
+ // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.
+ var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
+ "commonAncestorContainer"];
+
+ // Minimal set of methods required for DOM Level 2 Range compliance
+ var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
+ "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
+ "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];
+
+ var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];
+
+ // Subset of TextRange's full set of methods that we're interested in
+ var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select",
+ "setEndPoint", "getBoundingClientRect"];
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Trio of functions taken from Peter Michaux's article:
+ // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
+ function isHostMethod(o, p) {
+ var t = typeof o[p];
+ return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
+ }
+
+ function isHostObject(o, p) {
+ return !!(typeof o[p] == OBJECT && o[p]);
+ }
+
+ function isHostProperty(o, p) {
+ return typeof o[p] != UNDEFINED;
+ }
+
+ // Creates a convenience function to save verbose repeated calls to tests functions
+ function createMultiplePropertyTest(testFunc) {
+ return function(o, props) {
+ var i = props.length;
+ while (i--) {
+ if (!testFunc(o, props[i])) {
+ return false;
+ }
+ }
+ return true;
+ };
+ }
+
+ // Next trio of functions are a convenience to save verbose repeated calls to previous two functions
+ var areHostMethods = createMultiplePropertyTest(isHostMethod);
+ var areHostObjects = createMultiplePropertyTest(isHostObject);
+ var areHostProperties = createMultiplePropertyTest(isHostProperty);
+
+ function isTextRange(range) {
+ return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
+ }
+
+ function getBody(doc) {
+ return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
+ }
+
+ var modules = {};
+
+ var api = {
+ version: "1.3alpha.804",
+ initialized: false,
+ supported: true,
+
+ util: {
+ isHostMethod: isHostMethod,
+ isHostObject: isHostObject,
+ isHostProperty: isHostProperty,
+ areHostMethods: areHostMethods,
+ areHostObjects: areHostObjects,
+ areHostProperties: areHostProperties,
+ isTextRange: isTextRange,
+ getBody: getBody
+ },
+
+ features: {},
+
+ modules: modules,
+ config: {
+ alertOnFail: true,
+ alertOnWarn: false,
+ preferTextRange: false
+ }
+ };
+
+ function consoleLog(msg) {
+ if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
+ window.console.log(msg);
+ }
+ }
+
+ function alertOrLog(msg, shouldAlert) {
+ if (shouldAlert) {
+ window.alert(msg);
+ } else {
+ consoleLog(msg);
+ }
+ }
+
+ function fail(reason) {
+ api.initialized = true;
+ api.supported = false;
+ alertOrLog("Rangy is not supported on this page in your browser. Reason: " + reason, api.config.alertOnFail);
+ }
+
+ api.fail = fail;
+
+ function warn(msg) {
+ alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn);
+ }
+
+ api.warn = warn;
+
+ // Add utility extend() method
+ if ({}.hasOwnProperty) {
+ api.util.extend = function(obj, props, deep) {
+ var o, p;
+ for (var i in props) {
+ if (props.hasOwnProperty(i)) {
+ o = obj[i];
+ p = props[i];
+ //if (deep) alert([o !== null, typeof o == "object", p !== null, typeof p == "object"])
+ if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
+ api.util.extend(o, p, true);
+ }
+ obj[i] = p;
+ }
+ }
+ return obj;
+ };
+ } else {
+ fail("hasOwnProperty not supported");
+ }
+
+ // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
+ (function() {
+ var el = document.createElement("div");
+ el.appendChild(document.createElement("span"));
+ var slice = [].slice;
+ var toArray;
+ try {
+ if (slice.call(el.childNodes, 0)[0].nodeType == 1) {
+ toArray = function(arrayLike) {
+ return slice.call(arrayLike, 0);
+ };
+ }
+ } catch (e) {}
+
+ if (!toArray) {
+ toArray = function(arrayLike) {
+ var arr = [];
+ for (var i = 0, len = arrayLike.length; i < len; ++i) {
+ arr[i] = arrayLike[i];
+ }
+ return arr;
+ };
+ }
+
+ api.util.toArray = toArray;
+ })();
+
+
+ // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or
+ // normalization of event properties
+ var addListener;
+ if (isHostMethod(document, "addEventListener")) {
+ addListener = function(obj, eventType, listener) {
+ obj.addEventListener(eventType, listener, false);
+ };
+ } else if (isHostMethod(document, "attachEvent")) {
+ addListener = function(obj, eventType, listener) {
+ obj.attachEvent("on" + eventType, listener);
+ };
+ } else {
+ fail("Document does not have required addEventListener or attachEvent method");
+ }
+
+ api.util.addListener = addListener;
+
+ var initListeners = [];
+
+ function getErrorDesc(ex) {
+ return ex.message || ex.description || String(ex);
+ }
+
+ // Initialization
+ function init() {
+ if (api.initialized) {
+ return;
+ }
+ var testRange;
+ var implementsDomRange = false, implementsTextRange = false;
+
+ // First, perform basic feature tests
+
+ if (isHostMethod(document, "createRange")) {
+ testRange = document.createRange();
+ if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
+ implementsDomRange = true;
+ }
+ testRange.detach();
+ }
+
+ var body = getBody(document);
+ if (!body || body.nodeName.toLowerCase() != "body") {
+ fail("No body element found");
+ return;
+ }
+
+ if (body && isHostMethod(body, "createTextRange")) {
+ testRange = body.createTextRange();
+ if (isTextRange(testRange)) {
+ implementsTextRange = true;
+ }
+ }
+
+ if (!implementsDomRange && !implementsTextRange) {
+ fail("Neither Range nor TextRange are available");
+ return;
+ }
+
+ api.initialized = true;
+ api.features = {
+ implementsDomRange: implementsDomRange,
+ implementsTextRange: implementsTextRange
+ };
+
+ // Initialize modules
+ var module, errorMessage;
+ for (var moduleName in modules) {
+ if ( (module = modules[moduleName]) instanceof Module ) {
+ module.init(module, api);
+ }
+ }
+
+ // Call init listeners
+ for (var i = 0, len = initListeners.length; i < len; ++i) {
+ try {
+ initListeners[i](api);
+ } catch (ex) {
+ errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex);
+ consoleLog(errorMessage);
+ }
+ }
+ }
+
+ // Allow external scripts to initialize this library in case it's loaded after the document has loaded
+ api.init = init;
+
+ // Execute listener immediately if already initialized
+ api.addInitListener = function(listener) {
+ if (api.initialized) {
+ listener(api);
+ } else {
+ initListeners.push(listener);
+ }
+ };
+
+ var createMissingNativeApiListeners = [];
+
+ api.addCreateMissingNativeApiListener = function(listener) {
+ createMissingNativeApiListeners.push(listener);
+ };
+
+ function createMissingNativeApi(win) {
+ win = win || window;
+ init();
+
+ // Notify listeners
+ for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) {
+ createMissingNativeApiListeners[i](win);
+ }
+ }
+
+ api.createMissingNativeApi = createMissingNativeApi;
+
+ function Module(name, dependencies, initializer) {
+ this.name = name;
+ this.dependencies = dependencies;
+ this.initialized = false;
+ this.supported = false;
+ this.initializer = initializer;
+ }
+
+ Module.prototype = {
+ init: function(api) {
+ var requiredModuleNames = this.dependencies || [];
+ for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) {
+ moduleName = requiredModuleNames[i];
+
+ requiredModule = modules[moduleName];
+ if (!requiredModule || !(requiredModule instanceof Module)) {
+ throw new Error("required module '" + moduleName + "' not found");
+ }
+
+ requiredModule.init();
+
+ if (!requiredModule.supported) {
+ throw new Error("required module '" + moduleName + "' not supported");
+ }
+ }
+
+ // Now run initializer
+ this.initializer(this)
+ },
+
+ fail: function(reason) {
+ this.initialized = true;
+ this.supported = false;
+ throw new Error("Module '" + this.name + "' failed to load: " + reason);
+ },
+
+ warn: function(msg) {
+ api.warn("Module " + this.name + ": " + msg);
+ },
+
+ deprecationNotice: function(deprecated, replacement) {
+ api.warn("DEPRECATED: " + deprecated + " in module " + this.name + "is deprecated. Please use "
+ + replacement + " instead");
+ },
+
+ createError: function(msg) {
+ return new Error("Error in Rangy " + this.name + " module: " + msg);
+ }
+ };
+
+ function createModule(isCore, name, dependencies, initFunc) {
+ var newModule = new Module(name, dependencies, function(module) {
+ if (!module.initialized) {
+ module.initialized = true;
+ try {
+ initFunc(api, module);
+ module.supported = true;
+ } catch (ex) {
+ var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);
+ consoleLog(errorMessage);
+ }
+ }
+ });
+ modules[name] = newModule;
+
/*
- Rangy, a cross-browser JavaScript range and selection library
- http://code.google.com/p/rangy/
+ // Add module AMD support
+ if (!isCore && amdSupported) {
+ global.define(["rangy-core"], function(rangy) {
+
+ });
+ }
+*/
+ }
- Copyright 2012, Tim Down
- Licensed under the MIT license.
- Version: 1.2.3
- Build date: 26 February 2012
+ api.createModule = function(name) {
+ // Allow 2 or 3 arguments (second argument is an optional array of dependencies)
+ var initFunc, dependencies;
+ if (arguments.length == 2) {
+ initFunc = arguments[1];
+ dependencies = [];
+ } else {
+ initFunc = arguments[2];
+ dependencies = arguments[1];
+ }
+ createModule(false, name, dependencies, initFunc);
+ };
+
+ api.createCoreModule = function(name, dependencies, initFunc) {
+ createModule(true, name, dependencies, initFunc);
+ };
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately
+
+ function RangePrototype() {}
+ api.RangePrototype = RangePrototype;
+ api.rangePrototype = new RangePrototype();
+
+ function SelectionPrototype() {}
+ api.selectionPrototype = new SelectionPrototype();
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Wait for document to load before running tests
+
+ var docReady = false;
+
+ var loadHandler = function(e) {
+ if (!docReady) {
+ docReady = true;
+ if (!api.initialized) {
+ init();
+ }
+ }
+ };
+
+ // Test whether we have window and document objects that we will need
+ if (typeof window == UNDEFINED) {
+ fail("No window found");
+ return;
+ }
+ if (typeof document == UNDEFINED) {
+ fail("No document found");
+ return;
+ }
+
+ if (isHostMethod(document, "addEventListener")) {
+ document.addEventListener("DOMContentLoaded", loadHandler, false);
+ }
+
+ // Add a fallback in case the DOMContentLoaded event isn't supported
+ addListener(window, "load", loadHandler);
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // AMD, for those who like this kind of thing
+
+ if (amdSupported) {
+ // AMD. Register as an anonymous module.
+ global.define(function() {
+ api.amd = true;
+ return api;
+ });
+ }
+
+ // Create a "rangy" property of the global object in any case. Other Rangy modules (which use Rangy's own simple
+ // module system) rely on the existence of this global property
+ global.rangy = api;
+})(this);
+
+rangy.createCoreModule("DomUtil", [], function(api, module) {
+ var UNDEF = "undefined";
+ var util = api.util;
+
+ // Perform feature tests
+ if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
+ module.fail("document missing a Node creation method");
+ }
+
+ if (!util.isHostMethod(document, "getElementsByTagName")) {
+ module.fail("document missing getElementsByTagName method");
+ }
+
+ var el = document.createElement("div");
+ if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
+ !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
+ module.fail("Incomplete Element implementation");
+ }
+
+ // innerHTML is required for Range's createContextualFragment method
+ if (!util.isHostProperty(el, "innerHTML")) {
+ module.fail("Element is missing innerHTML property");
+ }
+
+ var textNode = document.createTextNode("test");
+ if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
+ !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
+ !util.areHostProperties(textNode, ["data"]))) {
+ module.fail("Incomplete Text Node implementation");
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
+ // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
+ // contains just the document as a single element and the value searched for is the document.
+ var arrayContains = /*Array.prototype.indexOf ?
+ function(arr, val) {
+ return arr.indexOf(val) > -1;
+ }:*/
+
+ function(arr, val) {
+ var i = arr.length;
+ while (i--) {
+ if (arr[i] === val) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
+ function isHtmlNamespace(node) {
+ var ns;
+ return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
+ }
+
+ function parentElement(node) {
+ var parent = node.parentNode;
+ return (parent.nodeType == 1) ? parent : null;
+ }
+
+ function getNodeIndex(node) {
+ var i = 0;
+ while( (node = node.previousSibling) ) {
+ ++i;
+ }
+ return i;
+ }
+
+ function getNodeLength(node) {
+ switch (node.nodeType) {
+ case 7:
+ case 10:
+ return 0;
+ case 3:
+ case 8:
+ return node.length;
+ default:
+ return node.childNodes.length;
+ }
+ }
+
+ function getCommonAncestor(node1, node2) {
+ var ancestors = [], n;
+ for (n = node1; n; n = n.parentNode) {
+ ancestors.push(n);
+ }
+
+ for (n = node2; n; n = n.parentNode) {
+ if (arrayContains(ancestors, n)) {
+ return n;
+ }
+ }
+
+ return null;
+ }
+
+ function isAncestorOf(ancestor, descendant, selfIsAncestor) {
+ var n = selfIsAncestor ? descendant : descendant.parentNode;
+ while (n) {
+ if (n === ancestor) {
+ return true;
+ } else {
+ n = n.parentNode;
+ }
+ }
+ return false;
+ }
+
+ function isOrIsAncestorOf(ancestor, descendant) {
+ return isAncestorOf(ancestor, descendant, true);
+ }
+
+ function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
+ var p, n = selfIsAncestor ? node : node.parentNode;
+ while (n) {
+ p = n.parentNode;
+ if (p === ancestor) {
+ return n;
+ }
+ n = p;
+ }
+ return null;
+ }
+
+ function isCharacterDataNode(node) {
+ var t = node.nodeType;
+ return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
+ }
+
+ function isTextOrCommentNode(node) {
+ if (!node) {
+ return false;
+ }
+ var t = node.nodeType;
+ return t == 3 || t == 8 ; // Text or Comment
+ }
+
+ function insertAfter(node, precedingNode) {
+ var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
+ if (nextNode) {
+ parent.insertBefore(node, nextNode);
+ } else {
+ parent.appendChild(node);
+ }
+ return node;
+ }
+
+ // Note that we cannot use splitText() because it is bugridden in IE 9.
+ function splitDataNode(node, index, positionsToPreserve) {
+ var newNode = node.cloneNode(false);
+ newNode.deleteData(0, index);
+ node.deleteData(index, node.length - index);
+ insertAfter(newNode, node);
+
+ // Preserve positions
+ if (positionsToPreserve) {
+ for (var i = 0, position; position = positionsToPreserve[i++]; ) {
+ // Handle case where position was inside the portion of node after the split point
+ if (position.node == node && position.offset > index) {
+ position.node = newNode;
+ position.offset -= index;
+ }
+ // Handle the case where the position is a node offset within node's parent
+ else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {
+ ++position.offset;
+ }
+ }
+ }
+ return newNode;
+ }
+
+ function getDocument(node) {
+ if (node.nodeType == 9) {
+ return node;
+ } else if (typeof node.ownerDocument != UNDEF) {
+ return node.ownerDocument;
+ } else if (typeof node.document != UNDEF) {
+ return node.document;
+ } else if (node.parentNode) {
+ return getDocument(node.parentNode);
+ } else {
+ throw module.createError("getDocument: no document found for node");
+ }
+ }
+
+ function getWindow(node) {
+ var doc = getDocument(node);
+ if (typeof doc.defaultView != UNDEF) {
+ return doc.defaultView;
+ } else if (typeof doc.parentWindow != UNDEF) {
+ return doc.parentWindow;
+ } else {
+ throw module.createError("Cannot get a window object for node");
+ }
+ }
+
+ function getIframeDocument(iframeEl) {
+ if (typeof iframeEl.contentDocument != UNDEF) {
+ return iframeEl.contentDocument;
+ } else if (typeof iframeEl.contentWindow != UNDEF) {
+ return iframeEl.contentWindow.document;
+ } else {
+ throw module.createError("getIframeDocument: No Document object found for iframe element");
+ }
+ }
+
+ function getIframeWindow(iframeEl) {
+ if (typeof iframeEl.contentWindow != UNDEF) {
+ return iframeEl.contentWindow;
+ } else if (typeof iframeEl.contentDocument != UNDEF) {
+ return iframeEl.contentDocument.defaultView;
+ } else {
+ throw module.createError("getIframeWindow: No Window object found for iframe element");
+ }
+ }
+
+ // This looks bad. Is it worth it?
+ function isWindow(obj) {
+ return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document");
+ }
+
+ function getContentDocument(obj, module, methodName) {
+ var doc;
+
+ if (!obj) {
+ doc = document;
+ }
+
+ // Test if a DOM node has been passed and obtain a document object for it if so
+ else if (util.isHostProperty(obj, "nodeType")) {
+ doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe")
+ ? getIframeDocument(obj) : getDocument(obj);
+ }
+
+ // Test if the doc parameter appears to be a Window object
+ else if (isWindow(obj)) {
+ doc = obj.document;
+ }
+
+ if (!doc) {
+ throw module.createError(methodName + "(): Parameter must be a Window object or DOM node");
+ }
+
+ return doc;
+ }
+
+ function getRootContainer(node) {
+ var parent;
+ while ( (parent = node.parentNode) ) {
+ node = parent;
+ }
+ return node;
+ }
+
+ function comparePoints(nodeA, offsetA, nodeB, offsetB) {
+ // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
+ var nodeC, root, childA, childB, n;
+ if (nodeA == nodeB) {
+ // Case 1: nodes are the same
+ return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
+ } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
+ // Case 2: node C (container B or an ancestor) is a child node of A
+ return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
+ } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
+ // Case 3: node C (container A or an ancestor) is a child node of B
+ return getNodeIndex(nodeC) < offsetB ? -1 : 1;
+ } else {
+ root = getCommonAncestor(nodeA, nodeB);
+ if (!root) {
+ throw new Error("comparePoints error: nodes have no common ancestor");
+ }
+
+ // Case 4: containers are siblings or descendants of siblings
+ childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
+ childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
+
+ if (childA === childB) {
+ // This shouldn't be possible
+ throw module.createError("comparePoints got to case 4 and childA and childB are the same!");
+ } else {
+ n = root.firstChild;
+ while (n) {
+ if (n === childA) {
+ return -1;
+ } else if (n === childB) {
+ return 1;
+ }
+ n = n.nextSibling;
+ }
+ }
+ }
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
+ var crashyTextNodes = false;
+
+ function isBrokenNode(node) {
+ try {
+ node.parentNode;
+ return false;
+ } catch (e) {
+ return true;
+ }
+ }
+
+ (function() {
+ var el = document.createElement("b");
+ el.innerHTML = "1";
+ var textNode = el.firstChild;
+ el.innerHTML = "<br>";
+ crashyTextNodes = isBrokenNode(textNode);
+
+ api.features.crashyTextNodes = crashyTextNodes;
+ })();
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ function inspectNode(node) {
+ if (!node) {
+ return "[No node]";
+ }
+ if (crashyTextNodes && isBrokenNode(node)) {
+ return "[Broken node]";
+ }
+ if (isCharacterDataNode(node)) {
+ return '"' + node.data + '"';
+ }
+ if (node.nodeType == 1) {
+ var idAttr = node.id ? ' id="' + node.id + '"' : "";
+ return "<" + node.nodeName + idAttr + ">[" + getNodeIndex(node) + "][" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]";
+ }
+ return node.nodeName;
+ }
+
+ function fragmentFromNodeChildren(node) {
+ var fragment = getDocument(node).createDocumentFragment(), child;
+ while ( (child = node.firstChild) ) {
+ fragment.appendChild(child);
+ }
+ return fragment;
+ }
+
+ var getComputedStyleProperty;
+ if (typeof window.getComputedStyle != UNDEF) {
+ getComputedStyleProperty = function(el, propName) {
+ return getWindow(el).getComputedStyle(el, null)[propName];
+ };
+ } else if (typeof document.documentElement.currentStyle != UNDEF) {
+ getComputedStyleProperty = function(el, propName) {
+ return el.currentStyle[propName];
+ };
+ } else {
+ module.fail("No means of obtaining computed style properties found");
+ }
+
+ function NodeIterator(root) {
+ this.root = root;
+ this._next = root;
+ }
+
+ NodeIterator.prototype = {
+ _current: null,
+
+ hasNext: function() {
+ return !!this._next;
+ },
+
+ next: function() {
+ var n = this._current = this._next;
+ var child, next;
+ if (this._current) {
+ child = n.firstChild;
+ if (child) {
+ this._next = child;
+ } else {
+ next = null;
+ while ((n !== this.root) && !(next = n.nextSibling)) {
+ n = n.parentNode;
+ }
+ this._next = next;
+ }
+ }
+ return this._current;
+ },
+
+ detach: function() {
+ this._current = this._next = this.root = null;
+ }
+ };
+
+ function createIterator(root) {
+ return new NodeIterator(root);
+ }
+
+ function DomPosition(node, offset) {
+ this.node = node;
+ this.offset = offset;
+ }
+
+ DomPosition.prototype = {
+ equals: function(pos) {
+ return !!pos && this.node === pos.node && this.offset == pos.offset;
+ },
+
+ inspect: function() {
+ return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
+ },
+
+ toString: function() {
+ return this.inspect();
+ }
+ };
+
+ function DOMException(codeName) {
+ this.code = this[codeName];
+ this.codeName = codeName;
+ this.message = "DOMException: " + this.codeName;
+ }
+
+ DOMException.prototype = {
+ INDEX_SIZE_ERR: 1,
+ HIERARCHY_REQUEST_ERR: 3,
+ WRONG_DOCUMENT_ERR: 4,
+ NO_MODIFICATION_ALLOWED_ERR: 7,
+ NOT_FOUND_ERR: 8,
+ NOT_SUPPORTED_ERR: 9,
+ INVALID_STATE_ERR: 11
+ };
+
+ DOMException.prototype.toString = function() {
+ return this.message;
+ };
+
+ api.dom = {
+ arrayContains: arrayContains,
+ isHtmlNamespace: isHtmlNamespace,
+ parentElement: parentElement,
+ getNodeIndex: getNodeIndex,
+ getNodeLength: getNodeLength,
+ getCommonAncestor: getCommonAncestor,
+ isAncestorOf: isAncestorOf,
+ isOrIsAncestorOf: isOrIsAncestorOf,
+ getClosestAncestorIn: getClosestAncestorIn,
+ isCharacterDataNode: isCharacterDataNode,
+ isTextOrCommentNode: isTextOrCommentNode,
+ insertAfter: insertAfter,
+ splitDataNode: splitDataNode,
+ getDocument: getDocument,
+ getWindow: getWindow,
+ getIframeWindow: getIframeWindow,
+ getIframeDocument: getIframeDocument,
+ getBody: util.getBody,
+ isWindow: isWindow,
+ getContentDocument: getContentDocument,
+ getRootContainer: getRootContainer,
+ comparePoints: comparePoints,
+ isBrokenNode: isBrokenNode,
+ inspectNode: inspectNode,
+ getComputedStyleProperty: getComputedStyleProperty,
+ fragmentFromNodeChildren: fragmentFromNodeChildren,
+ createIterator: createIterator,
+ DomPosition: DomPosition
+ };
+
+ api.DOMException = DOMException;
+});
+rangy.createCoreModule("DomRange", ["DomUtil"], function(api, module) {
+ var dom = api.dom;
+ var util = api.util;
+ var DomPosition = dom.DomPosition;
+ var DOMException = api.DOMException;
+
+ var isCharacterDataNode = dom.isCharacterDataNode;
+ var getNodeIndex = dom.getNodeIndex;
+ var isOrIsAncestorOf = dom.isOrIsAncestorOf;
+ var getDocument = dom.getDocument;
+ var comparePoints = dom.comparePoints;
+ var splitDataNode = dom.splitDataNode;
+ var getClosestAncestorIn = dom.getClosestAncestorIn;
+ var getNodeLength = dom.getNodeLength;
+ var arrayContains = dom.arrayContains;
+ var getRootContainer = dom.getRootContainer;
+ var crashyTextNodes = api.features.crashyTextNodes;
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Utility functions
+
+ function isNonTextPartiallySelected(node, range) {
+ return (node.nodeType != 3) &&
+ (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));
+ }
+
+ function getRangeDocument(range) {
+ return range.document || getDocument(range.startContainer);
+ }
+
+ function getBoundaryBeforeNode(node) {
+ return new DomPosition(node.parentNode, getNodeIndex(node));
+ }
+
+ function getBoundaryAfterNode(node) {
+ return new DomPosition(node.parentNode, getNodeIndex(node) + 1);
+ }
+
+ function insertNodeAtPosition(node, n, o) {
+ var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
+ if (isCharacterDataNode(n)) {
+ if (o == n.length) {
+ dom.insertAfter(node, n);
+ } else {
+ n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o));
+ }
+ } else if (o >= n.childNodes.length) {
+ n.appendChild(node);
+ } else {
+ n.insertBefore(node, n.childNodes[o]);
+ }
+ return firstNodeInserted;
+ }
+
+ function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {
+ assertRangeValid(rangeA);
+ assertRangeValid(rangeB);
+
+ if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {
+ throw new DOMException("WRONG_DOCUMENT_ERR");
+ }
+
+ var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),
+ endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);
+
+ return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
+ }
+
+ function cloneSubtree(iterator) {
+ var partiallySelected;
+ for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
+ partiallySelected = iterator.isPartiallySelectedSubtree();
+ node = node.cloneNode(!partiallySelected);
+ if (partiallySelected) {
+ subIterator = iterator.getSubtreeIterator();
+ node.appendChild(cloneSubtree(subIterator));
+ subIterator.detach(true);
+ }
+
+ if (node.nodeType == 10) { // DocumentType
+ throw new DOMException("HIERARCHY_REQUEST_ERR");
+ }
+ frag.appendChild(node);
+ }
+ return frag;
+ }
+
+ function iterateSubtree(rangeIterator, func, iteratorState) {
+ var it, n;
+ iteratorState = iteratorState || { stop: false };
+ for (var node, subRangeIterator; node = rangeIterator.next(); ) {
+ if (rangeIterator.isPartiallySelectedSubtree()) {
+ if (func(node) === false) {
+ iteratorState.stop = true;
+ return;
+ } else {
+ // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of
+ // the node selected by the Range.
+ subRangeIterator = rangeIterator.getSubtreeIterator();
+ iterateSubtree(subRangeIterator, func, iteratorState);
+ subRangeIterator.detach(true);
+ if (iteratorState.stop) {
+ return;
+ }
+ }
+ } else {
+ // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
+ // descendants
+ it = dom.createIterator(node);
+ while ( (n = it.next()) ) {
+ if (func(n) === false) {
+ iteratorState.stop = true;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ function deleteSubtree(iterator) {
+ var subIterator;
+ while (iterator.next()) {
+ if (iterator.isPartiallySelectedSubtree()) {
+ subIterator = iterator.getSubtreeIterator();
+ deleteSubtree(subIterator);
+ subIterator.detach(true);
+ } else {
+ iterator.remove();
+ }
+ }
+ }
+
+ function extractSubtree(iterator) {
+ for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
+
+ if (iterator.isPartiallySelectedSubtree()) {
+ node = node.cloneNode(false);
+ subIterator = iterator.getSubtreeIterator();
+ node.appendChild(extractSubtree(subIterator));
+ subIterator.detach(true);
+ } else {
+ iterator.remove();
+ }
+ if (node.nodeType == 10) { // DocumentType
+ throw new DOMException("HIERARCHY_REQUEST_ERR");
+ }
+ frag.appendChild(node);
+ }
+ return frag;
+ }
+
+ function getNodesInRange(range, nodeTypes, filter) {
+ var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
+ var filterExists = !!filter;
+ if (filterNodeTypes) {
+ regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
+ }
+
+ var nodes = [];
+ iterateSubtree(new RangeIterator(range, false), function(node) {
+ if (filterNodeTypes && !regex.test(node.nodeType)) {
+ return;
+ }
+ if (filterExists && !filter(node)) {
+ return;
+ }
+ // Don't include a boundary container if it is a character data node and the range does not contain any
+ // of its character data. See issue 190.
+ var sc = range.startContainer;
+ if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) {
+ return;
+ }
+
+ var ec = range.endContainer;
+ if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) {
+ return;
+ }
+
+ nodes.push(node);
+ });
+ return nodes;
+ }
+
+ function inspect(range) {
+ var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
+ return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
+ dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
+
+ function RangeIterator(range, clonePartiallySelectedTextNodes) {
+ this.range = range;
+ this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
+
+
+ if (!range.collapsed) {
+ this.sc = range.startContainer;
+ this.so = range.startOffset;
+ this.ec = range.endContainer;
+ this.eo = range.endOffset;
+ var root = range.commonAncestorContainer;
+
+ if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
+ this.isSingleCharacterDataNode = true;
+ this._first = this._last = this._next = this.sc;
+ } else {
+ this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?
+ this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
+ this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?
+ this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
+ }
+ }
+ }
+
+ RangeIterator.prototype = {
+ _current: null,
+ _next: null,
+ _first: null,
+ _last: null,
+ isSingleCharacterDataNode: false,
+
+ reset: function() {
+ this._current = null;
+ this._next = this._first;
+ },
+
+ hasNext: function() {
+ return !!this._next;
+ },
+
+ next: function() {
+ // Move to next node
+ var current = this._current = this._next;
+ if (current) {
+ this._next = (current !== this._last) ? current.nextSibling : null;
+
+ // Check for partially selected text nodes
+ if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
+ if (current === this.ec) {
+ (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
+ }
+ if (this._current === this.sc) {
+ (current = current.cloneNode(true)).deleteData(0, this.so);
+ }
+ }
+ }
+
+ return current;
+ },
+
+ remove: function() {
+ var current = this._current, start, end;
+
+ if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
+ start = (current === this.sc) ? this.so : 0;
+ end = (current === this.ec) ? this.eo : current.length;
+ if (start != end) {
+ current.deleteData(start, end - start);
+ }
+ } else {
+ if (current.parentNode) {
+ current.parentNode.removeChild(current);
+ } else {
+ }
+ }
+ },
+
+ // Checks if the current node is partially selected
+ isPartiallySelectedSubtree: function() {
+ var current = this._current;
+ return isNonTextPartiallySelected(current, this.range);
+ },
+
+ getSubtreeIterator: function() {
+ var subRange;
+ if (this.isSingleCharacterDataNode) {
+ subRange = this.range.cloneRange();
+ subRange.collapse(false);
+ } else {
+ subRange = new Range(getRangeDocument(this.range));
+ var current = this._current;
+ var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current);
+
+ if (isOrIsAncestorOf(current, this.sc)) {
+ startContainer = this.sc;
+ startOffset = this.so;
+ }
+ if (isOrIsAncestorOf(current, this.ec)) {
+ endContainer = this.ec;
+ endOffset = this.eo;
+ }
+
+ updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
+ }
+ return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
+ },
+
+ detach: function(detachRange) {
+ if (detachRange) {
+ this.range.detach();
+ }
+ this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
+ }
+ };
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Exceptions
+
+ function RangeException(codeName) {
+ this.code = this[codeName];
+ this.codeName = codeName;
+ this.message = "RangeException: " + this.codeName;
+ }
+
+ RangeException.prototype = {
+ BAD_BOUNDARYPOINTS_ERR: 1,
+ INVALID_NODE_TYPE_ERR: 2
+ };
+
+ RangeException.prototype.toString = function() {
+ return this.message;
+ };
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
+ var rootContainerNodeTypes = [2, 9, 11];
+ var readonlyNodeTypes = [5, 6, 10, 12];
+ var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
+ var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
+
+ function createAncestorFinder(nodeTypes) {
+ return function(node, selfIsAncestor) {
+ var t, n = selfIsAncestor ? node : node.parentNode;
+ while (n) {
+ t = n.nodeType;
+ if (arrayContains(nodeTypes, t)) {
+ return n;
+ }
+ n = n.parentNode;
+ }
+ return null;
+ };
+ }
+
+ var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
+ var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
+ var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
+
+ function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
+ if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
+ throw new RangeException("INVALID_NODE_TYPE_ERR");
+ }
+ }
+
+ function assertNotDetached(range) {
+ if (!range.startContainer) {
+ throw new DOMException("INVALID_STATE_ERR");
+ }
+ }
+
+ function assertValidNodeType(node, invalidTypes) {
+ if (!arrayContains(invalidTypes, node.nodeType)) {
+ throw new RangeException("INVALID_NODE_TYPE_ERR");
+ }
+ }
+
+ function assertValidOffset(node, offset) {
+ if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
+ throw new DOMException("INDEX_SIZE_ERR");
+ }
+ }
+
+ function assertSameDocumentOrFragment(node1, node2) {
+ if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
+ throw new DOMException("WRONG_DOCUMENT_ERR");
+ }
+ }
+
+ function assertNodeNotReadOnly(node) {
+ if (getReadonlyAncestor(node, true)) {
+ throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
+ }
+ }
+
+ function assertNode(node, codeName) {
+ if (!node) {
+ throw new DOMException(codeName);
+ }
+ }
+
+ function isOrphan(node) {
+ return (crashyTextNodes && dom.isBrokenNode(node)) ||
+ !arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
+ }
+
+ function isValidOffset(node, offset) {
+ return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);
+ }
+
+ function isRangeValid(range) {
+ return (!!range.startContainer && !!range.endContainer
+ && !isOrphan(range.startContainer)
+ && !isOrphan(range.endContainer)
+ && isValidOffset(range.startContainer, range.startOffset)
+ && isValidOffset(range.endContainer, range.endOffset));
+ }
+
+ function assertRangeValid(range) {
+ assertNotDetached(range);
+ if (!isRangeValid(range)) {
+ throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
+ }
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Test the browser's innerHTML support to decide how to implement createContextualFragment
+ var styleEl = document.createElement("style");
+ var htmlParsingConforms = false;
+ try {
+ styleEl.innerHTML = "<b>x</b>";
+ htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
+ } catch (e) {
+ // IE 6 and 7 throw
+ }
+
+ api.features.htmlParsingConforms = htmlParsingConforms;
+
+ var createContextualFragment = htmlParsingConforms ?
+
+ // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
+ // discussion and base code for this implementation at issue 67.
+ // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
+ // Thanks to Aleks Williams.
+ function(fragmentStr) {
+ // "Let node the context object's start's node."
+ var node = this.startContainer;
+ var doc = getDocument(node);
+
+ // "If the context object's start's node is null, raise an INVALID_STATE_ERR
+ // exception and abort these steps."
+ if (!node) {
+ throw new DOMException("INVALID_STATE_ERR");
+ }
+
+ // "Let element be as follows, depending on node's interface:"
+ // Document, Document Fragment: null
+ var el = null;
+
+ // "Element: node"
+ if (node.nodeType == 1) {
+ el = node;
+
+ // "Text, Comment: node's parentElement"
+ } else if (isCharacterDataNode(node)) {
+ el = dom.parentElement(node);
+ }
+
+ // "If either element is null or element's ownerDocument is an HTML document
+ // and element's local name is "html" and element's namespace is the HTML
+ // namespace"
+ if (el === null || (
+ el.nodeName == "HTML"
+ && dom.isHtmlNamespace(getDocument(el).documentElement)
+ && dom.isHtmlNamespace(el)
+ )) {
+
+ // "let element be a new Element with "body" as its local name and the HTML
+ // namespace as its namespace.""
+ el = doc.createElement("body");
+ } else {
+ el = el.cloneNode(false);
+ }
+
+ // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
+ // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
+ // "In either case, the algorithm must be invoked with fragment as the input
+ // and element as the context element."
+ el.innerHTML = fragmentStr;
+
+ // "If this raises an exception, then abort these steps. Otherwise, let new
+ // children be the nodes returned."
+
+ // "Let fragment be a new DocumentFragment."
+ // "Append all new children to fragment."
+ // "Return fragment."
+ return dom.fragmentFromNodeChildren(el);
+ } :
+
+ // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
+ // previous versions of Rangy used (with the exception of using a body element rather than a div)
+ function(fragmentStr) {
+ assertNotDetached(this);
+ var doc = getRangeDocument(this);
+ var el = doc.createElement("body");
+ el.innerHTML = fragmentStr;
+
+ return dom.fragmentFromNodeChildren(el);
+ };
+
+ function splitRangeBoundaries(range, positionsToPreserve) {
+ assertRangeValid(range);
+
+ var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
+ var startEndSame = (sc === ec);
+
+ if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
+ splitDataNode(ec, eo, positionsToPreserve);
+ }
+
+ if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
+ sc = splitDataNode(sc, so, positionsToPreserve);
+ if (startEndSame) {
+ eo -= so;
+ ec = sc;
+ } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
+ eo++;
+ }
+ so = 0;
+ }
+ range.setStartAndEnd(sc, so, ec, eo);
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
+ "commonAncestorContainer"];
+
+ var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
+ var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
+
+ util.extend(api.rangePrototype, {
+ compareBoundaryPoints: function(how, range) {
+ assertRangeValid(this);
+ assertSameDocumentOrFragment(this.startContainer, range.startContainer);
+
+ var nodeA, offsetA, nodeB, offsetB;
+ var prefixA = (how == e2s || how == s2s) ? "start" : "end";
+ var prefixB = (how == s2e || how == s2s) ? "start" : "end";
+ nodeA = this[prefixA + "Container"];
+ offsetA = this[prefixA + "Offset"];
+ nodeB = range[prefixB + "Container"];
+ offsetB = range[prefixB + "Offset"];
+ return comparePoints(nodeA, offsetA, nodeB, offsetB);
+ },
+
+ insertNode: function(node) {
+ assertRangeValid(this);
+ assertValidNodeType(node, insertableNodeTypes);
+ assertNodeNotReadOnly(this.startContainer);
+
+ if (isOrIsAncestorOf(node, this.startContainer)) {
+ throw new DOMException("HIERARCHY_REQUEST_ERR");
+ }
+
+ // No check for whether the container of the start of the Range is of a type that does not allow
+ // children of the type of node: the browser's DOM implementation should do this for us when we attempt
+ // to add the node
+
+ var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
+ this.setStartBefore(firstNodeInserted);
+ },
+
+ cloneContents: function() {
+ assertRangeValid(this);
+
+ var clone, frag;
+ if (this.collapsed) {
+ return getRangeDocument(this).createDocumentFragment();
+ } else {
+ if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {
+ clone = this.startContainer.cloneNode(true);
+ clone.data = clone.data.slice(this.startOffset, this.endOffset);
+ frag = getRangeDocument(this).createDocumentFragment();
+ frag.appendChild(clone);
+ return frag;
+ } else {
+ var iterator = new RangeIterator(this, true);
+ clone = cloneSubtree(iterator);
+ iterator.detach();
+ }
+ return clone;
+ }
+ },
+
+ canSurroundContents: function() {
+ assertRangeValid(this);
+ assertNodeNotReadOnly(this.startContainer);
+ assertNodeNotReadOnly(this.endContainer);
+
+ // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
+ // no non-text nodes.
+ var iterator = new RangeIterator(this, true);
+ var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
+ (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
+ iterator.detach();
+ return !boundariesInvalid;
+ },
+
+ surroundContents: function(node) {
+ assertValidNodeType(node, surroundNodeTypes);
+
+ if (!this.canSurroundContents()) {
+ throw new RangeException("BAD_BOUNDARYPOINTS_ERR");
+ }
+
+ // Extract the contents
+ var content = this.extractContents();
+
+ // Clear the children of the node
+ if (node.hasChildNodes()) {
+ while (node.lastChild) {
+ node.removeChild(node.lastChild);
+ }
+ }
+
+ // Insert the new node and add the extracted contents
+ insertNodeAtPosition(node, this.startContainer, this.startOffset);
+ node.appendChild(content);
+
+ this.selectNode(node);
+ },
+
+ cloneRange: function() {
+ assertRangeValid(this);
+ var range = new Range(getRangeDocument(this));
+ var i = rangeProperties.length, prop;
+ while (i--) {
+ prop = rangeProperties[i];
+ range[prop] = this[prop];
+ }
+ return range;
+ },
+
+ toString: function() {
+ assertRangeValid(this);
+ var sc = this.startContainer;
+ if (sc === this.endContainer && isCharacterDataNode(sc)) {
+ return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
+ } else {
+ var textParts = [], iterator = new RangeIterator(this, true);
+ iterateSubtree(iterator, function(node) {
+ // Accept only text or CDATA nodes, not comments
+ if (node.nodeType == 3 || node.nodeType == 4) {
+ textParts.push(node.data);
+ }
+ });
+ iterator.detach();
+ return textParts.join("");
+ }
+ },
+
+ // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
+ // been removed from Mozilla.
+
+ compareNode: function(node) {
+ assertRangeValid(this);
+
+ var parent = node.parentNode;
+ var nodeIndex = getNodeIndex(node);
+
+ if (!parent) {
+ throw new DOMException("NOT_FOUND_ERR");
+ }
+
+ var startComparison = this.comparePoint(parent, nodeIndex),
+ endComparison = this.comparePoint(parent, nodeIndex + 1);
+
+ if (startComparison < 0) { // Node starts before
+ return (endComparison > 0) ? n_b_a : n_b;
+ } else {
+ return (endComparison > 0) ? n_a : n_i;
+ }
+ },
+
+ comparePoint: function(node, offset) {
+ assertRangeValid(this);
+ assertNode(node, "HIERARCHY_REQUEST_ERR");
+ assertSameDocumentOrFragment(node, this.startContainer);
+
+ if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
+ return -1;
+ } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
+ return 1;
+ }
+ return 0;
+ },
+
+ createContextualFragment: createContextualFragment,
+
+ toHtml: function() {
+ assertRangeValid(this);
+ var container = this.commonAncestorContainer.parentNode.cloneNode(false);
+ container.appendChild(this.cloneContents());
+ return container.innerHTML;
+ },
+
+ // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
+ // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
+ intersectsNode: function(node, touchingIsIntersecting) {
+ assertRangeValid(this);
+ assertNode(node, "NOT_FOUND_ERR");
+ if (getDocument(node) !== getRangeDocument(this)) {
+ return false;
+ }
+
+ var parent = node.parentNode, offset = getNodeIndex(node);
+ assertNode(parent, "NOT_FOUND_ERR");
+
+ var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),
+ endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
+
+ return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
+ },
+
+ isPointInRange: function(node, offset) {
+ assertRangeValid(this);
+ assertNode(node, "HIERARCHY_REQUEST_ERR");
+ assertSameDocumentOrFragment(node, this.startContainer);
+
+ return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
+ (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
+ },
+
+ // The methods below are non-standard and invented by me.
+
+ // Sharing a boundary start-to-end or end-to-start does not count as intersection.
+ intersectsRange: function(range) {
+ return rangesIntersect(this, range, false);
+ },
+
+ // Sharing a boundary start-to-end or end-to-start does count as intersection.
+ intersectsOrTouchesRange: function(range) {
+ return rangesIntersect(this, range, true);
+ },
+
+ intersection: function(range) {
+ if (this.intersectsRange(range)) {
+ var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
+ endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
+
+ var intersectionRange = this.cloneRange();
+ if (startComparison == -1) {
+ intersectionRange.setStart(range.startContainer, range.startOffset);
+ }
+ if (endComparison == 1) {
+ intersectionRange.setEnd(range.endContainer, range.endOffset);
+ }
+ return intersectionRange;
+ }
+ return null;
+ },
+
+ union: function(range) {
+ if (this.intersectsOrTouchesRange(range)) {
+ var unionRange = this.cloneRange();
+ if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
+ unionRange.setStart(range.startContainer, range.startOffset);
+ }
+ if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
+ unionRange.setEnd(range.endContainer, range.endOffset);
+ }
+ return unionRange;
+ } else {
+ throw new RangeException("Ranges do not intersect");
+ }
+ },
+
+ containsNode: function(node, allowPartial) {
+ if (allowPartial) {
+ return this.intersectsNode(node, false);
+ } else {
+ return this.compareNode(node) == n_i;
+ }
+ },
+
+ containsNodeContents: function(node) {
+ return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;
+ },
+
+ containsRange: function(range) {
+ var intersection = this.intersection(range);
+ return intersection !== null && range.equals(intersection);
+ },
+
+ containsNodeText: function(node) {
+ var nodeRange = this.cloneRange();
+ nodeRange.selectNode(node);
+ var textNodes = nodeRange.getNodes([3]);
+ if (textNodes.length > 0) {
+ nodeRange.setStart(textNodes[0], 0);
+ var lastTextNode = textNodes.pop();
+ nodeRange.setEnd(lastTextNode, lastTextNode.length);
+ var contains = this.containsRange(nodeRange);
+ nodeRange.detach();
+ return contains;
+ } else {
+ return this.containsNodeContents(node);
+ }
+ },
+
+ getNodes: function(nodeTypes, filter) {
+ assertRangeValid(this);
+ return getNodesInRange(this, nodeTypes, filter);
+ },
+
+ getDocument: function() {
+ return getRangeDocument(this);
+ },
+
+ collapseBefore: function(node) {
+ assertNotDetached(this);
+
+ this.setEndBefore(node);
+ this.collapse(false);
+ },
+
+ collapseAfter: function(node) {
+ assertNotDetached(this);
+
+ this.setStartAfter(node);
+ this.collapse(true);
+ },
+
+ getBookmark: function(containerNode) {
+ var doc = getRangeDocument(this);
+ var preSelectionRange = api.createRange(doc);
+ containerNode = containerNode || dom.getBody(doc);
+ preSelectionRange.selectNodeContents(containerNode);
+ var range = this.intersection(preSelectionRange);
+ var start = 0, end = 0;
+ if (range) {
+ preSelectionRange.setEnd(range.startContainer, range.startOffset);
+ start = preSelectionRange.toString().length;
+ end = start + range.toString().length;
+ preSelectionRange.detach();
+ }
+
+ return {
+ start: start,
+ end: end,
+ containerNode: containerNode
+ };
+ },
+
+ moveToBookmark: function(bookmark) {
+ var containerNode = bookmark.containerNode;
+ var charIndex = 0;
+ this.setStart(containerNode, 0);
+ this.collapse(true);
+ var nodeStack = [containerNode], node, foundStart = false, stop = false;
+ var nextCharIndex, i, childNodes;
+
+ while (!stop && (node = nodeStack.pop())) {
+ if (node.nodeType == 3) {
+ nextCharIndex = charIndex + node.length;
+ if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {
+ this.setStart(node, bookmark.start - charIndex);
+ foundStart = true;
+ }
+ if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {
+ this.setEnd(node, bookmark.end - charIndex);
+ stop = true;
+ }
+ charIndex = nextCharIndex;
+ } else {
+ childNodes = node.childNodes;
+ i = childNodes.length;
+ while (i--) {
+ nodeStack.push(childNodes[i]);
+ }
+ }
+ }
+ },
+
+ getName: function() {
+ return "DomRange";
+ },
+
+ equals: function(range) {
+ return Range.rangesEqual(this, range);
+ },
+
+ isValid: function() {
+ return isRangeValid(this);
+ },
+
+ inspect: function() {
+ return inspect(this);
+ }
+ });
+
+ function copyComparisonConstantsToObject(obj) {
+ obj.START_TO_START = s2s;
+ obj.START_TO_END = s2e;
+ obj.END_TO_END = e2e;
+ obj.END_TO_START = e2s;
+
+ obj.NODE_BEFORE = n_b;
+ obj.NODE_AFTER = n_a;
+ obj.NODE_BEFORE_AND_AFTER = n_b_a;
+ obj.NODE_INSIDE = n_i;
+ }
+
+ function copyComparisonConstants(constructor) {
+ copyComparisonConstantsToObject(constructor);
+ copyComparisonConstantsToObject(constructor.prototype);
+ }
+
+ function createRangeContentRemover(remover, boundaryUpdater) {
+ return function() {
+ assertRangeValid(this);
+
+ var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
+
+ var iterator = new RangeIterator(this, true);
+
+ // Work out where to position the range after content removal
+ var node, boundary;
+ if (sc !== root) {
+ node = getClosestAncestorIn(sc, root, true);
+ boundary = getBoundaryAfterNode(node);
+ sc = boundary.node;
+ so = boundary.offset;
+ }
+
+ // Check none of the range is read-only
+ iterateSubtree(iterator, assertNodeNotReadOnly);
+
+ iterator.reset();
+
+ // Remove the content
+ var returnValue = remover(iterator);
+ iterator.detach();
+
+ // Move to the new position
+ boundaryUpdater(this, sc, so, sc, so);
+
+ return returnValue;
+ };
+ }
+
+ function createPrototypeRange(constructor, boundaryUpdater, detacher) {
+ function createBeforeAfterNodeSetter(isBefore, isStart) {
+ return function(node) {
+ assertNotDetached(this);
+ assertValidNodeType(node, beforeAfterNodeTypes);
+ assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
+
+ var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
+ (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
+ };
+ }
+
+ function setRangeStart(range, node, offset) {
+ var ec = range.endContainer, eo = range.endOffset;
+ if (node !== range.startContainer || offset !== range.startOffset) {
+ // Check the root containers of the range and the new boundary, and also check whether the new boundary
+ // is after the current end. In either case, collapse the range to the new position
+ if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) {
+ ec = node;
+ eo = offset;
+ }
+ boundaryUpdater(range, node, offset, ec, eo);
+ }
+ }
+
+ function setRangeEnd(range, node, offset) {
+ var sc = range.startContainer, so = range.startOffset;
+ if (node !== range.endContainer || offset !== range.endOffset) {
+ // Check the root containers of the range and the new boundary, and also check whether the new boundary
+ // is after the current end. In either case, collapse the range to the new position
+ if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {
+ sc = node;
+ so = offset;
+ }
+ boundaryUpdater(range, sc, so, node, offset);
+ }
+ }
+
+ // Set up inheritance
+ var F = function() {};
+ F.prototype = api.rangePrototype;
+ constructor.prototype = new F();
+
+ util.extend(constructor.prototype, {
+ setStart: function(node, offset) {
+ assertNotDetached(this);
+ assertNoDocTypeNotationEntityAncestor(node, true);
+ assertValidOffset(node, offset);
+
+ setRangeStart(this, node, offset);
+ },
+
+ setEnd: function(node, offset) {
+ assertNotDetached(this);
+ assertNoDocTypeNotationEntityAncestor(node, true);
+ assertValidOffset(node, offset);
+
+ setRangeEnd(this, node, offset);
+ },
+
+ /**
+ * Convenience method to set a range's start and end boundaries. Overloaded as follows:
+ * - Two parameters (node, offset) creates a collapsed range at that position
+ * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
+ * startOffset and ending at endOffset
+ * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
+ * startNode and ending at endOffset in endNode
+ */
+ setStartAndEnd: function() {
+ assertNotDetached(this);
+
+ var args = arguments;
+ var sc = args[0], so = args[1], ec = sc, eo = so;
+
+ switch (args.length) {
+ case 3:
+ eo = args[2];
+ break;
+ case 4:
+ ec = args[2];
+ eo = args[3];
+ break;
+ }
+
+ boundaryUpdater(this, sc, so, ec, eo);
+ },
+
+ setBoundary: function(node, offset, isStart) {
+ this["set" + (isStart ? "Start" : "End")](node, offset);
+ },
+
+ setStartBefore: createBeforeAfterNodeSetter(true, true),
+ setStartAfter: createBeforeAfterNodeSetter(false, true),
+ setEndBefore: createBeforeAfterNodeSetter(true, false),
+ setEndAfter: createBeforeAfterNodeSetter(false, false),
+
+ collapse: function(isStart) {
+ assertRangeValid(this);
+ if (isStart) {
+ boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
+ } else {
+ boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
+ }
+ },
+
+ selectNodeContents: function(node) {
+ assertNotDetached(this);
+ assertNoDocTypeNotationEntityAncestor(node, true);
+
+ boundaryUpdater(this, node, 0, node, getNodeLength(node));
+ },
+
+ selectNode: function(node) {
+ assertNotDetached(this);
+ assertNoDocTypeNotationEntityAncestor(node, false);
+ assertValidNodeType(node, beforeAfterNodeTypes);
+
+ var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
+ boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
+ },
+
+ extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
+
+ deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
+
+ canSurroundContents: function() {
+ assertRangeValid(this);
+ assertNodeNotReadOnly(this.startContainer);
+ assertNodeNotReadOnly(this.endContainer);
+
+ // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
+ // no non-text nodes.
+ var iterator = new RangeIterator(this, true);
+ var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
+ (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
+ iterator.detach();
+ return !boundariesInvalid;
+ },
+
+ detach: function() {
+ detacher(this);
+ },
+
+ splitBoundaries: function() {
+ splitRangeBoundaries(this);
+ },
+
+ splitBoundariesPreservingPositions: function(positionsToPreserve) {
+ splitRangeBoundaries(this, positionsToPreserve);
+ },
+
+ normalizeBoundaries: function() {
+ assertRangeValid(this);
+
+ var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
+
+ var mergeForward = function(node) {
+ var sibling = node.nextSibling;
+ if (sibling && sibling.nodeType == node.nodeType) {
+ ec = node;
+ eo = node.length;
+ node.appendData(sibling.data);
+ sibling.parentNode.removeChild(sibling);
+ }
+ };
+
+ var mergeBackward = function(node) {
+ var sibling = node.previousSibling;
+ if (sibling && sibling.nodeType == node.nodeType) {
+ sc = node;
+ var nodeLength = node.length;
+ so = sibling.length;
+ node.insertData(0, sibling.data);
+ sibling.parentNode.removeChild(sibling);
+ if (sc == ec) {
+ eo += so;
+ ec = sc;
+ } else if (ec == node.parentNode) {
+ var nodeIndex = getNodeIndex(node);
+ if (eo == nodeIndex) {
+ ec = node;
+ eo = nodeLength;
+ } else if (eo > nodeIndex) {
+ eo--;
+ }
+ }
+ }
+ };
+
+ var normalizeStart = true;
+
+ if (isCharacterDataNode(ec)) {
+ if (ec.length == eo) {
+ mergeForward(ec);
+ }
+ } else {
+ if (eo > 0) {
+ var endNode = ec.childNodes[eo - 1];
+ if (endNode && isCharacterDataNode(endNode)) {
+ mergeForward(endNode);
+ }
+ }
+ normalizeStart = !this.collapsed;
+ }
+
+ if (normalizeStart) {
+ if (isCharacterDataNode(sc)) {
+ if (so == 0) {
+ mergeBackward(sc);
+ }
+ } else {
+ if (so < sc.childNodes.length) {
+ var startNode = sc.childNodes[so];
+ if (startNode && isCharacterDataNode(startNode)) {
+ mergeBackward(startNode);
+ }
+ }
+ }
+ } else {
+ sc = ec;
+ so = eo;
+ }
+
+ boundaryUpdater(this, sc, so, ec, eo);
+ },
+
+ collapseToPoint: function(node, offset) {
+ assertNotDetached(this);
+ assertNoDocTypeNotationEntityAncestor(node, true);
+ assertValidOffset(node, offset);
+ this.setStartAndEnd(node, offset);
+ }
+ });
+
+ copyComparisonConstants(constructor);
+ }
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ // Updates commonAncestorContainer and collapsed after boundary change
+ function updateCollapsedAndCommonAncestor(range) {
+ range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
+ range.commonAncestorContainer = range.collapsed ?
+ range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
+ }
+
+ function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
+ range.startContainer = startContainer;
+ range.startOffset = startOffset;
+ range.endContainer = endContainer;
+ range.endOffset = endOffset;
+ range.document = dom.getDocument(startContainer);
+
+ updateCollapsedAndCommonAncestor(range);
+ }
+
+ function detach(range) {
+ assertNotDetached(range);
+ range.startContainer = range.startOffset = range.endContainer = range.endOffset = range.document = null;
+ range.collapsed = range.commonAncestorContainer = null;
+ }
+
+ function Range(doc) {
+ this.startContainer = doc;
+ this.startOffset = 0;
+ this.endContainer = doc;
+ this.endOffset = 0;
+ this.document = doc;
+ updateCollapsedAndCommonAncestor(this);
+ }
+
+ createPrototypeRange(Range, updateBoundaries, detach);
+
+ util.extend(Range, {
+ rangeProperties: rangeProperties,
+ RangeIterator: RangeIterator,
+ copyComparisonConstants: copyComparisonConstants,
+ createPrototypeRange: createPrototypeRange,
+ inspect: inspect,
+ getRangeDocument: getRangeDocument,
+ rangesEqual: function(r1, r2) {
+ return r1.startContainer === r2.startContainer &&
+ r1.startOffset === r2.startOffset &&
+ r1.endContainer === r2.endContainer &&
+ r1.endOffset === r2.endOffset;
+ }
+ });
+
+ api.DomRange = Range;
+ api.RangeException = RangeException;
+});
+rangy.createCoreModule("WrappedRange", ["DomRange"], function(api, module) {
+ var WrappedRange, WrappedTextRange;
+ var dom = api.dom;
+ var util = api.util;
+ var DomPosition = dom.DomPosition;
+ var DomRange = api.DomRange;
+ var getBody = dom.getBody;
+ var getContentDocument = dom.getContentDocument;
+ var isCharacterDataNode = dom.isCharacterDataNode;
+
+
+ /*----------------------------------------------------------------------------------------------------------------*/
+
+ if (api.features.implementsDomRange) {
+ // This is a wrapper around the browser's native DOM Range. It has two aims:
+ // - Provide workarounds for specific browser bugs
+ // - provide convenient extensions, which are inherited from Rangy's DomRange
+
+ (function() {
+ var rangeProto;
+ var rangeProperties = DomRange.rangeProperties;
+
+ function updateRangeProperties(range) {
+ var i = rangeProperties.length, prop;
+ while (i--) {
+ prop = rangeProperties[i];
+ range[prop] = range.nativeRange[prop];
+ }
+ // Fix for broken collapsed property in IE 9.
+ range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
+ }
+
+ function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
+ var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
+ var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
+ var nativeRangeDifferent = !range.equals(range.nativeRange);
+
+ // Always set both boundaries for the benefit of IE9 (see issue 35)
+ if (startMoved || endMoved || nativeRangeDifferent) {
+ range.setEnd(endContainer, endOffset);
+ range.setStart(startContainer, startOffset);
+ }
+ }
+
+ function detach(range) {
+ range.nativeRange.detach();
+ range.detached = true;
+ var i = rangeProperties.length;
+ while (i--) {
+ range[ rangeProperties[i] ] = null;
+ }
+ }
+
+ var createBeforeAfterNodeSetter;
+
+ WrappedRange = function(range) {
+ if (!range) {
+ throw module.createError("WrappedRange: Range must be specified");
+ }
+ this.nativeRange = range;
+ updateRangeProperties(this);
+ };
+
+ DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);
+
+ rangeProto = WrappedRange.prototype;
+
+ rangeProto.selectNode = function(node) {
+ this.nativeRange.selectNode(node);
+ updateRangeProperties(this);
+ };
+
+ rangeProto.cloneContents = function() {
+ return this.nativeRange.cloneContents();
+ };
+
+ // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
+ // insertNode() is never delegated to the native range.
+
+ rangeProto.surroundContents = function(node) {
+ this.nativeRange.surroundContents(node);
+ updateRangeProperties(this);
+ };
+
+ rangeProto.collapse = function(isStart) {
+ this.nativeRange.collapse(isStart);
+ updateRangeProperties(this);
+ };
+
+ rangeProto.cloneRange = function() {
+ return new WrappedRange(this.nativeRange.cloneRange());
+ };
+
+ rangeProto.refresh = function() {
+ updateRangeProperties(this);
+ };
+
+ rangeProto.toString = function() {
+ return this.nativeRange.toString();
+ };
+
+ // Create test range and node for feature detection
+
+ var testTextNode = document.createTextNode("test");
+ getBody(document).appendChild(testTextNode);
+ var range = document.createRange();
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
+ // correct for it
+
+ range.setStart(testTextNode, 0);
+ range.setEnd(testTextNode, 0);
+
+ try {
+ range.setStart(testTextNode, 1);
+
+ rangeProto.setStart = function(node, offset) {
+ this.nativeRange.setStart(node, offset);
+ updateRangeProperties(this);
+ };
+
+ rangeProto.setEnd = function(node, offset) {
+ this.nativeRange.setEnd(node, offset);
+ updateRangeProperties(this);
+ };
+
+ createBeforeAfterNodeSetter = function(name) {
+ return function(node) {
+ this.nativeRange[name](node);
+ updateRangeProperties(this);
+ };
+ };
+
+ } catch(ex) {
+
+ rangeProto.setStart = function(node, offset) {
+ try {
+ this.nativeRange.setStart(node, offset);
+ } catch (ex) {
+ this.nativeRange.setEnd(node, offset);
+ this.nativeRange.setStart(node, offset);
+ }
+ updateRangeProperties(this);
+ };
+
+ rangeProto.setEnd = function(node, offset) {
+ try {
+ this.nativeRange.setEnd(node, offset);
+ } catch (ex) {
+ this.nativeRange.setStart(node, offset);
+ this.nativeRange.setEnd(node, offset);
+ }
+ updateRangeProperties(this);
+ };
+
+ createBeforeAfterNodeSetter = function(name, oppositeName) {
+ return function(node) {
+ try {
+ this.nativeRange[name](node);
+ } catch (ex) {
+ this.nativeRange[oppositeName](node);
+ this.nativeRange[name](node);
+ }
+ updateRangeProperties(this);
+ };
+ };
+ }
+
+ rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
+ rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
+ rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
+ rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing
+ // whether the native implementation can be trusted
+ rangeProto.selectNodeContents = function(node) {
+ this.setStartAndEnd(node, 0, dom.getNodeLength(node));
+ };
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for
+ // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
+
+ range.selectNodeContents(testTextNode);
+ range.setEnd(testTextNode, 3);
+
+ var range2 = document.createRange();
+ range2.selectNodeContents(testTextNode);
+ range2.setEnd(testTextNode, 4);
+ range2.setStart(testTextNode, 2);
+
+ if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&
+ range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
+ // This is the wrong way round, so correct for it
+
+ rangeProto.compareBoundaryPoints = function(type, range) {
+ range = range.nativeRange || range;
+ if (type == range.START_TO_END) {
+ type = range.END_TO_START;
+ } else if (type == range.END_TO_START) {
+ type = range.START_TO_END;
+ }
+ return this.nativeRange.compareBoundaryPoints(type, range);
+ };
+ } else {
+ rangeProto.compareBoundaryPoints = function(type, range) {
+ return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
+ };
+ }
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Test for IE 9 deleteContents() and extractContents() bug and correct it. See issue 107.
+
+ var el = document.createElement("div");
+ el.innerHTML = "123";
+ var textNode = el.firstChild;
+ var body = getBody(document);
+ body.appendChild(el);
+
+ range.setStart(textNode, 1);
+ range.setEnd(textNode, 2);
+ range.deleteContents();
+
+ if (textNode.data == "13") {
+ // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
+ // extractContents()
+ rangeProto.deleteContents = function() {
+ this.nativeRange.deleteContents();
+ updateRangeProperties(this);
+ };
+
+ rangeProto.extractContents = function() {
+ var frag = this.nativeRange.extractContents();
+ updateRangeProperties(this);
+ return frag;
+ };
+ } else {
+ }
+
+ body.removeChild(el);
+ body = null;
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Test for existence of createContextualFragment and delegate to it if it exists
+ if (util.isHostMethod(range, "createContextualFragment")) {
+ rangeProto.createContextualFragment = function(fragmentStr) {
+ return this.nativeRange.createContextualFragment(fragmentStr);
+ };
+ }
+
+ /*--------------------------------------------------------------------------------------------------------*/
+
+ // Clean up
+ getBody(document).removeChild(testTextNode);
+ range.detach();
+ range2.detach();
+
+ rangeProto.getName = function() {
+ return "WrappedRange";
+ };
+
+ api.WrappedRange = WrappedRange;
+
+ api.createNativeRange = function(doc) {
+ doc = getContentDocument(doc, module, "createNativeRange");
+ return doc.createRange();
+ };
+ })();
+ }
+
+ if (api.features.implementsTextRange) {
+ /*
+ This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
+ method. For example, in the following (where pipes denote the selection boundaries):
+
+ <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
+
+ var range = document.selection.createRange();
+ alert(range.parentElement().id); // Should alert "ul" but alerts "b"
+
+ This method returns the common ancestor node of the following:
+ - the parentElement() of the textRange
+ - the parentElement() of the textRange after calling collapse(true)
+ - the parentElement() of the textRange after calling collapse(false)
+ */
+ var getTextRangeContainerElement = function(textRange) {
+ var parentEl = textRange.parentElement();
+ var range = textRange.duplicate();
+ range.collapse(true);
+ var startEl = range.parentElement();
+ range = textRange.duplicate();
+ range.collapse(false);
+ var endEl = range.parentElement();
+ var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
+
+ return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
+ };
+
+ var textRangeIsCollapsed = function(textRange) {
+ return textRange.compareEndPoints("StartToEnd", textRange) == 0;
+ };
+
+ // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as
+ // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has
+ // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling
+ // for inputs and images, plus optimizations.
+ var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
+ var workingRange = textRange.duplicate();
+ workingRange.collapse(isStart);
+ var containerElement = workingRange.parentElement();
+
+ // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
+ // check for that
+ if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
+ containerElement = wholeRangeContainerElement;
+ }
+
+
+ // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
+ // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
+ if (!containerElement.canHaveHTML) {
+ var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
+ return {
+ boundaryPosition: pos,
+ nodeInfo: {
+ nodeIndex: pos.offset,
+ containerElement: pos.node
+ }
+ };
+ }
+
+ var workingNode = dom.getDocument(containerElement).createElement("span");
+
+ // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
+ // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
+ if (workingNode.parentNode) {
+ workingNode.parentNode.removeChild(workingNode);
+ }
+
+ var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
+ var previousNode, nextNode, boundaryPosition, boundaryNode;
+ var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;
+ var childNodeCount = containerElement.childNodes.length;
+ var end = childNodeCount;
+
+ // Check end first. Code within the loop assumes that the endth child node of the container is definitely
+ // after the range boundary.
+ var nodeIndex = end;
+
+ while (true) {
+ if (nodeIndex == childNodeCount) {
+ containerElement.appendChild(workingNode);
+ } else {
+ containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
+ }
+ workingRange.moveToElementText(workingNode);
+ comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
+ if (comparison == 0 || start == end) {
+ break;
+ } else if (comparison == -1) {
+ if (end == start + 1) {
+ // We know the endth child node is after the range boundary, so we must be done.
+ break;
+ } else {
+ start = nodeIndex;
+ }
+ } else {
+ end = (end == start + 1) ? start : nodeIndex;
+ }
+ nodeIndex = Math.floor((start + end) / 2);
+ containerElement.removeChild(workingNode);
+ }
+
+
+ // We've now reached or gone past the boundary of the text range we're interested in
+ // so have identified the node we want
+ boundaryNode = workingNode.nextSibling;
+
+ if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) {
+ // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the
+ // node containing the text range's boundary, so we move the end of the working range to the boundary point
+ // and measure the length of its text to get the boundary's offset within the node.
+ workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
+
+ var offset;
+
+ if (/[\r\n]/.test(boundaryNode.data)) {
+ /*
+ For the particular case of a boundary within a text node containing rendered line breaks (within a <pre>
+ element, for example), we need a slightly complicated approach to get the boundary's offset in IE. The
+ facts:
+
+ - Each line break is represented as \r in the text node's data/nodeValue properties
+ - Each line break is represented as \r\n in the TextRange's 'text' property
+ - The 'text' property of the TextRange does not contain trailing line breaks
+
+ To get round the problem presented by the final fact above, we can use the fact that TextRange's
+ moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
+ the same as the number of characters it was instructed to move. The simplest approach is to use this to
+ store the characters moved when moving both the start and end of the range to the start of the document
+ body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
+ However, this is extremely slow when the document is large and the range is near the end of it. Clearly
+ doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
+ problem.
+
+ Another approach that works is to use moveStart() to move the start boundary of the range up to the end
+ boundary one character at a time and incrementing a counter with the value returned by the moveStart()
+ call. However, the check for whether the start boundary has reached the end boundary is expensive, so
+ this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
+ the range within the document).
+
+ The method below is a hybrid of the two methods above. It uses the fact that a string containing the
+ TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
+ text of the TextRange, so the start of the range is moved that length initially and then a character at
+ a time to make up for any trailing line breaks not contained in the 'text' property. This has good
+ performance in most situations compared to the previous two methods.
+ */
+ var tempRange = workingRange.duplicate();
+ var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
+
+ offset = tempRange.moveStart("character", rangeLength);
+ while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
+ offset++;
+ tempRange.moveStart("character", 1);
+ }
+ } else {
+ offset = workingRange.text.length;
+ }
+ boundaryPosition = new DomPosition(boundaryNode, offset);
+ } else {
+
+ // If the boundary immediately follows a character data node and this is the end boundary, we should favour
+ // a position within that, and likewise for a start boundary preceding a character data node
+ previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
+ nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
+ if (nextNode && isCharacterDataNode(nextNode)) {
+ boundaryPosition = new DomPosition(nextNode, 0);
+ } else if (previousNode && isCharacterDataNode(previousNode)) {
+ boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
+ } else {
+ boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
+ }
+ }
+
+ // Clean up
+ workingNode.parentNode.removeChild(workingNode);
+
+ return {
+ boundaryPosition: boundaryPosition,
+ nodeInfo: {
+ nodeIndex: nodeIndex,
+ containerElement: containerElement
+ }
+ };
+ };
+
+ // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
+ // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
+ // (http://code.google.com/p/ierange/)
+ var createBoundaryTextRange = function(boundaryPosition, isStart) {
+ var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
+ var doc = dom.getDocument(boundaryPosition.node);
+ var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
+ var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
+
+ if (nodeIsDataNode) {
+ boundaryNode = boundaryPosition.node;
+ boundaryParent = boundaryNode.parentNode;
+ } else {
+ childNodes = boundaryPosition.node.childNodes;
+ boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
+ boundaryParent = boundaryPosition.node;
+ }
+
+ // Position the range immediately before the node containing the boundary
+ workingNode = doc.createElement("span");
+
+ // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
+ // element rather than immediately before or after it
+ workingNode.innerHTML = "&#feff;";
+
+ // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
+ // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
+ if (boundaryNode) {
+ boundaryParent.insertBefore(workingNode, boundaryNode);
+ } else {
+ boundaryParent.appendChild(workingNode);
+ }
+
+ workingRange.moveToElementText(workingNode);
+ workingRange.collapse(!isStart);
+
+ // Clean up
+ boundaryParent.removeChild(workingNode);
+
+ // Move the working range to the text offset, if required
+ if (nodeIsDataNode) {
+ workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
+ }
+
+ return workingRange;
+ };
+
+ /*------------------------------------------------------------------------------------------------------------*/
+
+ // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
+ // prototype
+
+ WrappedTextRange = function(textRange) {
+ this.textRange = textRange;
+ this.refresh();
+ };
+
+ WrappedTextRange.prototype = new DomRange(document);
+
+ WrappedTextRange.prototype.refresh = function() {
+ var start, end, startBoundary;
+
+ // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
+ var rangeContainerElement = getTextRangeContainerElement(this.textRange);
+
+ if (textRangeIsCollapsed(this.textRange)) {
+ end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
+ true).boundaryPosition;
+ } else {
+ startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
+ start = startBoundary.boundaryPosition;
+
+ // An optimization used here is that if the start and end boundaries have the same parent element, the
+ // search scope for the end boundary can be limited to exclude the portion of the element that precedes
+ // the start boundary
+ end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
+ startBoundary.nodeInfo).boundaryPosition;
+ }
+
+ this.setStart(start.node, start.offset);
+ this.setEnd(end.node, end.offset);
+ };
+
+ WrappedTextRange.prototype.getName = function() {
+ return "WrappedTextRange";
+ };
+
+ DomRange.copyComparisonConstants(WrappedTextRange);
+
+ WrappedTextRange.rangeToTextRange = function(range) {
+ if (range.collapsed) {
+ return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
+ } else {
+ var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
+ var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
+ var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
+ textRange.setEndPoint("StartToStart", startRange);
+ textRange.setEndPoint("EndToEnd", endRange);
+ return textRange;
+ }
+ };
+
+ api.WrappedTextRange = WrappedTextRange;
+
+ // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
+ // implementation to use by default.
+ if (!api.features.implementsDomRange || api.config.preferTextRange) {
+ // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
+ var globalObj = (function() { return this; })();
+ if (typeof globalObj.Range == "undefined") {
+ globalObj.Range = WrappedTextRange;
+ }
+
+ api.createNativeRange = function(doc) {
+ doc = getContentDocument(doc, module, "createNativeRange");
+ return getBody(doc).createTextRange();
+ };
+
+ api.WrappedRange = WrappedTextRange;
+ }
+ }
+
+ api.createRange = function(doc) {
+ doc = getContentDocument(doc, module, "createRange");
+ return new api.WrappedRange(api.createNativeRange(doc));
+ };
+
+ api.createRangyRange = function(doc) {
+ doc = getContentDocument(doc, module, "createRangyRange");
+ return new DomRange(doc);
+ };
+
+ api.createIframeRange = function(iframeEl) {
+ module.deprecationNotice("createIframeRange()", "createRange(iframeEl)");
+ return api.createRange(iframeEl);
+ };
+
+ api.createIframeRangyRange = function(iframeEl) {
+ module.deprecationNotice("createIframeRangyRange()", "createRangyRange(iframeEl)");
+ return api.createRangyRange(iframeEl);
+ };
+
+ api.addCreateMissingNativeApiListener(function(win) {
+ var doc = win.document;
+ if (typeof doc.createRange == "undefined") {
+ doc.createRange = function() {
+ return api.createRange(doc);
+ };
+ }
+ doc = win = null;
+ });
+});
+// This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
+// in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
+rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
+ api.config.checkSelectionRanges = true;
+
+ var BOOLEAN = "boolean";
+ var NUMBER = "number";
+ var dom = api.dom;
+ var util = api.util;
+ var isHostMethod = util.isHostMethod;
+ var DomRange = api.DomRange;
+ var WrappedRange = api.WrappedRange;
+ var DOMException = api.DOMException;
+ var DomPosition = dom.DomPosition;
+ var getNativeSelection;
+ var selectionIsCollapsed;
+ var features = api.features;
+ var CONTROL = "Control";
+ var getDocument = dom.getDocument;
+ var getBody = dom.getBody;
+ var rangesEqual = DomRange.rangesEqual;
+
+
+ // Utility function to support direction parameters in the API that may be a string ("backward" or "forward") or a
+ // Boolean (true for backwards).
+ function isDirectionBackward(dir) {
+ return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
+ }
+
+ function getWindow(win, methodName) {
+ if (!win) {
+ return window;
+ } else if (dom.isWindow(win)) {
+ return win;
+ } else if (win instanceof WrappedSelection) {
+ return win.win;
+ } else {
+ var doc = dom.getContentDocument(win, module, methodName);
+ return dom.getWindow(doc);
+ }
+ }
+
+ function getWinSelection(winParam) {
+ return getWindow(winParam, "getWinSelection").getSelection();
+ }
+
+ function getDocSelection(winParam) {
+ return getWindow(winParam, "getDocSelection").document.selection;
+ }
+
+ function winSelectionIsBackward(sel) {
+ var backward = false;
+ if (sel.anchorNode) {
+ backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
+ }
+ return backward;
+ }
+
+ // Test for the Range/TextRange and Selection features required
+ // Test for ability to retrieve selection
+ var implementsWinGetSelection = isHostMethod(window, "getSelection"),
+ implementsDocSelection = util.isHostObject(document, "selection");
+
+ features.implementsWinGetSelection = implementsWinGetSelection;
+ features.implementsDocSelection = implementsDocSelection;
+
+ var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
+
+ if (useDocumentSelection) {
+ getNativeSelection = getDocSelection;
+ api.isSelectionValid = function(winParam) {
+ var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
+
+ // Check whether the selection TextRange is actually contained within the correct document
+ return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
+ };
+ } else if (implementsWinGetSelection) {
+ getNativeSelection = getWinSelection;
+ api.isSelectionValid = function() {
+ return true;
+ };
+ } else {
+ module.fail("Neither document.selection or window.getSelection() detected.");
+ }
+
+ api.getNativeSelection = getNativeSelection;
+
+ var testSelection = getNativeSelection();
+ var testRange = api.createNativeRange(document);
+ var body = getBody(document);
+
+ // Obtaining a range from a selection
+ var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
+ ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
+
+ features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
+
+ // Test for existence of native selection extend() method
+ var selectionHasExtend = isHostMethod(testSelection, "extend");
+ features.selectionHasExtend = selectionHasExtend;
+
+ // Test if rangeCount exists
+ var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);
+ features.selectionHasRangeCount = selectionHasRangeCount;
+
+ var selectionSupportsMultipleRanges = false;
+ var collapsedNonEditableSelectionsSupported = true;
+
+ var addRangeBackwardToNative = selectionHasExtend ?
+ function(nativeSelection, range) {
+ var doc = DomRange.getRangeDocument(range);
+ var endRange = api.createRange(doc);
+ endRange.collapseToPoint(range.endContainer, range.endOffset);
+ nativeSelection.addRange(getNativeRange(endRange));
+ nativeSelection.extend(range.startContainer, range.startOffset);
+ } : null;
+
+ if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
+ typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
+
+ (function() {
+ // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
+ // performed on the current document's selection. See issue 109.
+
+ // Note also that if a selection previously existed, it is wiped by these tests. This should usually be fine
+ // because initialization usually happens when the document loads, but could be a problem for a script that
+ // loads and initializes Rangy later. If anyone complains, code could be added to save and restore the
+ // selection.
+ var sel = window.getSelection();
+ if (sel) {
+ // Store the current selection
+ var originalSelectionRangeCount = sel.rangeCount;
+ var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);
+ var originalSelectionRanges = [];
+ var originalSelectionBackward = winSelectionIsBackward(sel);
+ for (var i = 0; i < originalSelectionRangeCount; ++i) {
+ originalSelectionRanges[i] = sel.getRangeAt(i);
+ }
+
+ // Create some test elements
+ var body = getBody(document);
+ var testEl = body.appendChild( document.createElement("div") );
+ testEl.contentEditable = "false";
+ var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
+
+ // Test whether the native selection will allow a collapsed selection within a non-editable element
+ var r1 = document.createRange();
+
+ r1.setStart(textNode, 1);
+ r1.collapse(true);
+ sel.addRange(r1);
+ collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
+ sel.removeAllRanges();
+
+ // Test whether the native selection is capable of supporting multiple ranges
+ if (!selectionHasMultipleRanges) {
+ var r2 = r1.cloneRange();
+ r1.setStart(textNode, 0);
+ r2.setEnd(textNode, 3);
+ r2.setStart(textNode, 2);
+ sel.addRange(r1);
+ sel.addRange(r2);
+
+ selectionSupportsMultipleRanges = (sel.rangeCount == 2);
+ r2.detach();
+ }
+
+ // Clean up
+ body.removeChild(testEl);
+ sel.removeAllRanges();
+ r1.detach();
+
+ for (i = 0; i < originalSelectionRangeCount; ++i) {
+ if (i == 0 && originalSelectionBackward) {
+ if (addRangeBackwardToNative) {
+ addRangeBackwardToNative(sel, originalSelectionRanges[i]);
+ } else {
+ api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because browser does not support Selection.extend");
+ sel.addRange(originalSelectionRanges[i])
+ }
+ } else {
+ sel.addRange(originalSelectionRanges[i])
+ }
+ }
+ }
+ })();
+ }
+
+ features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
+ features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
+
+ // ControlRanges
+ var implementsControlRange = false, testControlRange;
+
+ if (body && isHostMethod(body, "createControlRange")) {
+ testControlRange = body.createControlRange();
+ if (util.areHostProperties(testControlRange, ["item", "add"])) {
+ implementsControlRange = true;
+ }
+ }
+ features.implementsControlRange = implementsControlRange;
+
+ // Selection collapsedness
+ if (selectionHasAnchorAndFocus) {
+ selectionIsCollapsed = function(sel) {
+ return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
+ };
+ } else {
+ selectionIsCollapsed = function(sel) {
+ return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
+ };
+ }
+
+ function updateAnchorAndFocusFromRange(sel, range, backward) {
+ var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
+ sel.anchorNode = range[anchorPrefix + "Container"];
+ sel.anchorOffset = range[anchorPrefix + "Offset"];
+ sel.focusNode = range[focusPrefix + "Container"];
+ sel.focusOffset = range[focusPrefix + "Offset"];
+ }
+
+ function updateAnchorAndFocusFromNativeSelection(sel) {
+ var nativeSel = sel.nativeSelection;
+ sel.anchorNode = nativeSel.anchorNode;
+ sel.anchorOffset = nativeSel.anchorOffset;
+ sel.focusNode = nativeSel.focusNode;
+ sel.focusOffset = nativeSel.focusOffset;
+ }
+
+ function updateEmptySelection(sel) {
+ sel.anchorNode = sel.focusNode = null;
+ sel.anchorOffset = sel.focusOffset = 0;
+ sel.rangeCount = 0;
+ sel.isCollapsed = true;
+ sel._ranges.length = 0;
+ }
+
+ function getNativeRange(range) {
+ var nativeRange;
+ if (range instanceof DomRange) {
+ nativeRange = api.createNativeRange(range.getDocument());
+ nativeRange.setEnd(range.endContainer, range.endOffset);
+ nativeRange.setStart(range.startContainer, range.startOffset);
+ } else if (range instanceof WrappedRange) {
+ nativeRange = range.nativeRange;
+ } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
+ nativeRange = range;
+ }
+ return nativeRange;
+ }
+
+ function rangeContainsSingleElement(rangeNodes) {
+ if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
+ return false;
+ }
+ for (var i = 1, len = rangeNodes.length; i < len; ++i) {
+ if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function getSingleElementFromRange(range) {
+ var nodes = range.getNodes();
+ if (!rangeContainsSingleElement(nodes)) {
+ throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
+ }
+ return nodes[0];
+ }
+
+ // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
+ function isTextRange(range) {
+ return !!range && typeof range.text != "undefined";
+ }
+
+ function updateFromTextRange(sel, range) {
+ // Create a Range from the selected TextRange
+ var wrappedRange = new WrappedRange(range);
+ sel._ranges = [wrappedRange];
+
+ updateAnchorAndFocusFromRange(sel, wrappedRange, false);
+ sel.rangeCount = 1;
+ sel.isCollapsed = wrappedRange.collapsed;
+ }
+
+ function updateControlSelection(sel) {
+ // Update the wrapped selection based on what's now in the native selection
+ sel._ranges.length = 0;
+ if (sel.docSelection.type == "None") {
+ updateEmptySelection(sel);
+ } else {
+ var controlRange = sel.docSelection.createRange();
+ if (isTextRange(controlRange)) {
+ // This case (where the selection type is "Control" and calling createRange() on the selection returns
+ // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
+ // ControlRange have been removed from the ControlRange and removed from the document.
+ updateFromTextRange(sel, controlRange);
+ } else {
+ sel.rangeCount = controlRange.length;
+ var range, doc = getDocument(controlRange.item(0));
+ for (var i = 0; i < sel.rangeCount; ++i) {
+ range = api.createRange(doc);
+ range.selectNode(controlRange.item(i));
+ sel._ranges.push(range);
+ }
+ sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
+ updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
+ }
+ }
+ }
+
+ function addRangeToControlSelection(sel, range) {
+ var controlRange = sel.docSelection.createRange();
+ var rangeElement = getSingleElementFromRange(range);
+
+ // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
+ // contained by the supplied range
+ var doc = getDocument(controlRange.item(0));
+ var newControlRange = getBody(doc).createControlRange();
+ for (var i = 0, len = controlRange.length; i < len; ++i) {
+ newControlRange.add(controlRange.item(i));
+ }
+ try {
+ newControlRange.add(rangeElement);
+ } catch (ex) {
+ throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
+ }
+ newControlRange.select();
+
+ // Update the wrapped selection based on what's now in the native selection
+ updateControlSelection(sel);
+ }
+
+ var getSelectionRangeAt;
+
+ if (isHostMethod(testSelection, "getRangeAt")) {
+ // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
+ // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
+ // lesson to us all, especially me.
+ getSelectionRangeAt = function(sel, index) {
+ try {
+ return sel.getRangeAt(index);
+ } catch (ex) {
+ return null;
+ }
+ };
+ } else if (selectionHasAnchorAndFocus) {
+ getSelectionRangeAt = function(sel) {
+ var doc = getDocument(sel.anchorNode);
+ var range = api.createRange(doc);
+ range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
+
+ // Handle the case when the selection was selected backwards (from the end to the start in the
+ // document)
+ if (range.collapsed !== this.isCollapsed) {
+ range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
+ }
+
+ return range;
+ };
+ }
+
+ function WrappedSelection(selection, docSelection, win) {
+ this.nativeSelection = selection;
+ this.docSelection = docSelection;
+ this._ranges = [];
+ this.win = win;
+ this.refresh();
+ }
+
+ WrappedSelection.prototype = api.selectionPrototype;
+
+ function deleteProperties(sel) {
+ sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
+ sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
+ sel.detached = true;
+ }
+
+ var cachedRangySelections = [];
+
+ function actOnCachedSelection(win, action) {
+ var i = cachedRangySelections.length, cached, sel;
+ while (i--) {
+ cached = cachedRangySelections[i];
+ sel = cached.selection;
+ if (action == "deleteAll") {
+ deleteProperties(sel);
+ } else if (cached.win == win) {
+ if (action == "delete") {
+ cachedRangySelections.splice(i, 1);
+ return true;
+ } else {
+ return sel;
+ }
+ }
+ }
+ if (action == "deleteAll") {
+ cachedRangySelections.length = 0;
+ }
+ return null;
+ }
+
+ var getSelection = function(win) {
+ // Check if the parameter is a Rangy Selection object
+ if (win && win instanceof WrappedSelection) {
+ win.refresh();
+ return win;
+ }
+
+ win = getWindow(win, "getNativeSelection");
+
+ var sel = actOnCachedSelection(win);
+ var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
+ if (sel) {
+ sel.nativeSelection = nativeSel;
+ sel.docSelection = docSel;
+ sel.refresh();
+ } else {
+ sel = new WrappedSelection(nativeSel, docSel, win);
+ cachedRangySelections.push( { win: win, selection: sel } );
+ }
+ return sel;
+ };
+
+ api.getSelection = getSelection;
+
+ api.getIframeSelection = function(iframeEl) {
+ module.deprecationNotice("getIframeSelection()", "getSelection(iframeEl)");
+ return api.getSelection(dom.getIframeWindow(iframeEl));
+ };
+
+ var selProto = WrappedSelection.prototype;
+
+ function createControlSelection(sel, ranges) {
+ // Ensure that the selection becomes of type "Control"
+ var doc = getDocument(ranges[0].startContainer);
+ var controlRange = getBody(doc).createControlRange();
+ for (var i = 0, el, len = ranges.length; i < len; ++i) {
+ el = getSingleElementFromRange(ranges[i]);
+ try {
+ controlRange.add(el);
+ } catch (ex) {
+ throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)");
+ }
+ }
+ controlRange.select();
+
+ // Update the wrapped selection based on what's now in the native selection
+ updateControlSelection(sel);
+ }
+
+ // Selecting a range
+ if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
+ selProto.removeAllRanges = function() {
+ this.nativeSelection.removeAllRanges();
+ updateEmptySelection(this);
+ };
+
+ var addRangeBackward = function(sel, range) {
+ addRangeBackwardToNative(sel.nativeSelection, range);
+ sel.refresh();
+ };
+
+ if (selectionHasRangeCount) {
+ selProto.addRange = function(range, direction) {
+ if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
+ addRangeToControlSelection(this, range);
+ } else {
+ if (isDirectionBackward(direction) && selectionHasExtend) {
+ addRangeBackward(this, range);
+ } else {
+ var previousRangeCount;
+ if (selectionSupportsMultipleRanges) {
+ previousRangeCount = this.rangeCount;
+ } else {
+ this.removeAllRanges();
+ previousRangeCount = 0;
+ }
+ // Clone the native range so that changing the selected range does not affect the selection.
+ // This is contrary to the spec but is the only way to achieve consistency between browsers. See
+ // issue 80.
+ this.nativeSelection.addRange(getNativeRange(range).cloneRange());
+
+ // Check whether adding the range was successful
+ this.rangeCount = this.nativeSelection.rangeCount;
+
+ if (this.rangeCount == previousRangeCount + 1) {
+ // The range was added successfully
+
+ // Check whether the range that we added to the selection is reflected in the last range extracted from
+ // the selection
+ if (api.config.checkSelectionRanges) {
+ var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
+ if (nativeRange && !rangesEqual(nativeRange, range)) {
+ // Happens in WebKit with, for example, a selection placed at the start of a text node
+ range = new WrappedRange(nativeRange);
+ }
+ }
+ this._ranges[this.rangeCount - 1] = range;
+ updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
+ this.isCollapsed = selectionIsCollapsed(this);
+ } else {
+ // The range was not added successfully. The simplest thing is to refresh
+ this.refresh();
+ }
+ }
+ }
+ };
+ } else {
+ selProto.addRange = function(range, direction) {
+ if (isDirectionBackward(direction) && selectionHasExtend) {
+ addRangeBackward(this, range);
+ } else {
+ this.nativeSelection.addRange(getNativeRange(range));
+ this.refresh();
+ }
+ };
+ }
+
+ selProto.setRanges = function(ranges) {
+ if (implementsControlRange && ranges.length > 1) {
+ createControlSelection(this, ranges);
+ } else {
+ this.removeAllRanges();
+ for (var i = 0, len = ranges.length; i < len; ++i) {
+ this.addRange(ranges[i]);
+ }
+ }
+ };
+ } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
+ implementsControlRange && useDocumentSelection) {
+
+ selProto.removeAllRanges = function() {
+ // Added try/catch as fix for issue #21
+ try {
+ this.docSelection.empty();
+
+ // Check for empty() not working (issue #24)
+ if (this.docSelection.type != "None") {
+ // Work around failure to empty a control selection by instead selecting a TextRange and then
+ // calling empty()
+ var doc;
+ if (this.anchorNode) {
+ doc = getDocument(this.anchorNode);
+ } else if (this.docSelection.type == CONTROL) {
+ var controlRange = this.docSelection.createRange();
+ if (controlRange.length) {
+ doc = getDocument( controlRange.item(0) );
+ }
+ }
+ if (doc) {
+ var textRange = getBody(doc).createTextRange();
+ textRange.select();
+ this.docSelection.empty();
+ }
+ }
+ } catch(ex) {}
+ updateEmptySelection(this);
+ };
+
+ selProto.addRange = function(range) {
+ if (this.docSelection.type == CONTROL) {
+ addRangeToControlSelection(this, range);
+ } else {
+ api.WrappedTextRange.rangeToTextRange(range).select();
+ this._ranges[0] = range;
+ this.rangeCount = 1;
+ this.isCollapsed = this._ranges[0].collapsed;
+ updateAnchorAndFocusFromRange(this, range, false);
+ }
+ };
+
+ selProto.setRanges = function(ranges) {
+ this.removeAllRanges();
+ var rangeCount = ranges.length;
+ if (rangeCount > 1) {
+ createControlSelection(this, ranges);
+ } else if (rangeCount) {
+ this.addRange(ranges[0]);
+ }
+ };
+ } else {
+ module.fail("No means of selecting a Range or TextRange was found");
+ return false;
+ }
+
+ selProto.getRangeAt = function(index) {
+ if (index < 0 || index >= this.rangeCount) {
+ throw new DOMException("INDEX_SIZE_ERR");
+ } else {
+ // Clone the range to preserve selection-range independence. See issue 80.
+ return this._ranges[index].cloneRange();
+ }
+ };
+
+ var refreshSelection;
+
+ if (useDocumentSelection) {
+ refreshSelection = function(sel) {
+ var range;
+ if (api.isSelectionValid(sel.win)) {
+ range = sel.docSelection.createRange();
+ } else {
+ range = getBody(sel.win.document).createTextRange();
+ range.collapse(true);
+ }
+
+ if (sel.docSelection.type == CONTROL) {
+ updateControlSelection(sel);
+ } else if (isTextRange(range)) {
+ updateFromTextRange(sel, range);
+ } else {
+ updateEmptySelection(sel);
+ }
+ };
+ } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) {
+ refreshSelection = function(sel) {
+ if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
+ updateControlSelection(sel);
+ } else {
+ sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
+ if (sel.rangeCount) {
+ for (var i = 0, len = sel.rangeCount; i < len; ++i) {
+ sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
+ }
+ updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
+ sel.isCollapsed = selectionIsCollapsed(sel);
+ } else {
+ updateEmptySelection(sel);
+ }
+ }
+ };
+ } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
+ refreshSelection = function(sel) {
+ var range, nativeSel = sel.nativeSelection;
+ if (nativeSel.anchorNode) {
+ range = getSelectionRangeAt(nativeSel, 0);
+ sel._ranges = [range];
+ sel.rangeCount = 1;
+ updateAnchorAndFocusFromNativeSelection(sel);
+ sel.isCollapsed = selectionIsCollapsed(sel);
+ } else {
+ updateEmptySelection(sel);
+ }
+ };
+ } else {
+ module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
+ return false;
+ }
+
+ selProto.refresh = function(checkForChanges) {
+ var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
+ var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
+
+ refreshSelection(this);
+ if (checkForChanges) {
+ // Check the range count first
+ var i = oldRanges.length;
+ if (i != this._ranges.length) {
+ return true;
+ }
+
+ // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
+ // ranges after this
+ if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
+ return true;
+ }
+
+ // Finally, compare each range in turn
+ while (i--) {
+ if (!rangesEqual(oldRanges[i], this._ranges[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ // Removal of a single range
+ var removeRangeManually = function(sel, range) {
+ var ranges = sel.getAllRanges();
+ sel.removeAllRanges();
+ for (var i = 0, len = ranges.length; i < len; ++i) {
+ if (!rangesEqual(range, ranges[i])) {
+ sel.addRange(ranges[i]);
+ }
+ }
+ if (!sel.rangeCount) {
+ updateEmptySelection(sel);
+ }
+ };
+
+ if (implementsControlRange) {
+ selProto.removeRange = function(range) {
+ if (this.docSelection.type == CONTROL) {
+ var controlRange = this.docSelection.createRange();
+ var rangeElement = getSingleElementFromRange(range);
+
+ // Create a new ControlRange containing all the elements in the selected ControlRange minus the
+ // element contained by the supplied range
+ var doc = getDocument(controlRange.item(0));
+ var newControlRange = getBody(doc).createControlRange();
+ var el, removed = false;
+ for (var i = 0, len = controlRange.length; i < len; ++i) {
+ el = controlRange.item(i);
+ if (el !== rangeElement || removed) {
+ newControlRange.add(controlRange.item(i));
+ } else {
+ removed = true;
+ }
+ }
+ newControlRange.select();
+
+ // Update the wrapped selection based on what's now in the native selection
+ updateControlSelection(this);
+ } else {
+ removeRangeManually(this, range);
+ }
+ };
+ } else {
+ selProto.removeRange = function(range) {
+ removeRangeManually(this, range);
+ };
+ }
+
+ // Detecting if a selection is backward
+ var selectionIsBackward;
+ if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
+ selectionIsBackward = winSelectionIsBackward;
+
+ selProto.isBackward = function() {
+ return selectionIsBackward(this);
+ };
+ } else {
+ selectionIsBackward = selProto.isBackward = function() {
+ return false;
+ };
+ }
+
+ // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
+ selProto.isBackwards = selProto.isBackward;
+
+ // Selection stringifier
+ // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
+ // The current spec does not yet define this method.
+ selProto.toString = function() {
+ var rangeTexts = [];
+ for (var i = 0, len = this.rangeCount; i < len; ++i) {
+ rangeTexts[i] = "" + this._ranges[i];
+ }
+ return rangeTexts.join("");
+ };
+
+ function assertNodeInSameDocument(sel, node) {
+ if (sel.win.document != getDocument(node)) {
+ throw new DOMException("WRONG_DOCUMENT_ERR");
+ }
+ }
+
+ // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
+ selProto.collapse = function(node, offset) {
+ assertNodeInSameDocument(this, node);
+ var range = api.createRange(node);
+ range.collapseToPoint(node, offset);
+ this.setSingleRange(range);
+ this.isCollapsed = true;
+ };
+
+ selProto.collapseToStart = function() {
+ if (this.rangeCount) {
+ var range = this._ranges[0];
+ this.collapse(range.startContainer, range.startOffset);
+ } else {
+ throw new DOMException("INVALID_STATE_ERR");
+ }
+ };
+
+ selProto.collapseToEnd = function() {
+ if (this.rangeCount) {
+ var range = this._ranges[this.rangeCount - 1];
+ this.collapse(range.endContainer, range.endOffset);
+ } else {
+ throw new DOMException("INVALID_STATE_ERR");
+ }
+ };
+
+ // The spec is very specific on how selectAllChildren should be implemented so the native implementation is
+ // never used by Rangy.
+ selProto.selectAllChildren = function(node) {
+ assertNodeInSameDocument(this, node);
+ var range = api.createRange(node);
+ range.selectNodeContents(node);
+ this.setSingleRange(range);
+ };
+
+ selProto.deleteFromDocument = function() {
+ // Sepcial behaviour required for IE's control selections
+ if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
+ var controlRange = this.docSelection.createRange();
+ var element;
+ while (controlRange.length) {
+ element = controlRange.item(0);
+ controlRange.remove(element);
+ element.parentNode.removeChild(element);
+ }
+ this.refresh();
+ } else if (this.rangeCount) {
+ var ranges = this.getAllRanges();
+ if (ranges.length) {
+ this.removeAllRanges();
+ for (var i = 0, len = ranges.length; i < len; ++i) {
+ ranges[i].deleteContents();
+ }
+ // The spec says nothing about what the selection should contain after calling deleteContents on each
+ // range. Firefox moves the selection to where the final selected range was, so we emulate that
+ this.addRange(ranges[len - 1]);
+ }
+ }
+ };
+
+ // The following are non-standard extensions
+ selProto.eachRange = function(func, returnValue) {
+ for (var i = 0, len = this._ranges.length; i < len; ++i) {
+ if ( func( this.getRangeAt(i) ) ) {
+ return returnValue;
+ }
+ }
+ };
+
+ selProto.getAllRanges = function() {
+ var ranges = [];
+ this.eachRange(function(range) {
+ ranges.push(range);
+ });
+ return ranges;
+ };
+
+ selProto.setSingleRange = function(range, direction) {
+ this.removeAllRanges();
+ this.addRange(range, direction);
+ };
+
+ selProto.callMethodOnEachRange = function(methodName, params) {
+ var results = [];
+ this.eachRange( function(range) {
+ results.push( range[methodName].apply(range, params) );
+ } );
+ return results;
+ };
+
+ function createStartOrEndSetter(isStart) {
+ return function(node, offset) {
+ var range;
+ if (this.rangeCount) {
+ range = this.getRangeAt(0);
+ range["set" + (isStart ? "Start" : "End")](node, offset);
+ } else {
+ range = api.createRange(this.win.document);
+ range.setStartAndEnd(node, offset);
+ }
+ this.setSingleRange(range, this.isBackward());
+ };
+ }
+
+ selProto.setStart = createStartOrEndSetter(true);
+ selProto.setEnd = createStartOrEndSetter(false);
+
+ // Add select() method to Range prototype. Any existing selection will be removed.
+ api.rangePrototype.select = function(direction) {
+ getSelection( this.getDocument() ).setSingleRange(this, direction);
+ };
+
+ selProto.changeEachRange = function(func) {
+ var ranges = [];
+ var backward = this.isBackward();
+
+ this.eachRange(function(range) {
+ func(range);
+ ranges.push(range);
+ });
+
+ this.removeAllRanges();
+ if (backward && ranges.length == 1) {
+ this.addRange(ranges[0], "backward");
+ } else {
+ this.setRanges(ranges);
+ }
+ };
+
+ selProto.containsNode = function(node, allowPartial) {
+ return this.eachRange( function(range) {
+ return range.containsNode(node, allowPartial);
+ }, true );
+ };
+
+ selProto.getBookmark = function(containerNode) {
+ return {
+ backward: this.isBackward(),
+ rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
+ };
+ };
+
+ selProto.moveToBookmark = function(bookmark) {
+ var selRanges = [];
+ for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
+ range = api.createRange(this.win);
+ range.moveToBookmark(rangeBookmark);
+ selRanges.push(range);
+ }
+ if (bookmark.backward) {
+ this.setSingleRange(selRanges[0], "backward");
+ } else {
+ this.setRanges(selRanges);
+ }
+ };
+
+ selProto.toHtml = function() {
+ return this.callMethodOnEachRange("toHtml").join("");
+ };
+
+ function inspect(sel) {
+ var rangeInspects = [];
+ var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
+ var focus = new DomPosition(sel.focusNode, sel.focusOffset);
+ var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
+
+ if (typeof sel.rangeCount != "undefined") {
+ for (var i = 0, len = sel.rangeCount; i < len; ++i) {
+ rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
+ }
+ }
+ return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
+ ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
+ }
+
+ selProto.getName = function() {
+ return "WrappedSelection";
+ };
+
+ selProto.inspect = function() {
+ return inspect(this);
+ };
+
+ selProto.detach = function() {
+ actOnCachedSelection(this.win, "delete");
+ deleteProperties(this);
+ };
+
+ WrappedSelection.detachAll = function() {
+ actOnCachedSelection(null, "deleteAll");
+ };
+
+ WrappedSelection.inspect = inspect;
+ WrappedSelection.isDirectionBackward = isDirectionBackward;
+
+ api.Selection = WrappedSelection;
+
+ api.selectionPrototype = selProto;
+
+ api.addCreateMissingNativeApiListener(function(win) {
+ if (typeof win.getSelection == "undefined") {
+ win.getSelection = function() {
+ return getSelection(win);
+ };
+ }
+ win = null;
+ });
+});
+;/**
+ * Selection save and restore module for Rangy.
+ * Saves and restores user selections using marker invisible elements in the DOM.
+ *
+ * Part of Rangy, a cross-browser JavaScript range and selection library
+ * http://code.google.com/p/rangy/
+ *
+ * Depends on Rangy core.
+ *
+ * Copyright 2013, Tim Down
+ * Licensed under the MIT license.
+ * Version: 1.3alpha.804
+ * Build date: 8 December 2013
+ */
+rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
+ var dom = api.dom;
+
+ var markerTextChar = "\ufeff";
+
+ function gEBI(id, doc) {
+ return (doc || document).getElementById(id);
+ }
+
+ function insertRangeBoundaryMarker(range, atStart) {
+ var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
+ var markerEl;
+ var doc = dom.getDocument(range.startContainer);
+
+ // Clone the Range and collapse to the appropriate boundary point
+ var boundaryRange = range.cloneRange();
+ boundaryRange.collapse(atStart);
+
+ // Create the marker element containing a single invisible character using DOM methods and insert it
+ markerEl = doc.createElement("span");
+ markerEl.id = markerId;
+ markerEl.style.lineHeight = "0";
+ markerEl.style.display = "none";
+ markerEl.className = "rangySelectionBoundary";
+ markerEl.appendChild(doc.createTextNode(markerTextChar));
+
+ boundaryRange.insertNode(markerEl);
+ boundaryRange.detach();
+ return markerEl;
+ }
+
+ function setRangeBoundary(doc, range, markerId, atStart) {
+ var markerEl = gEBI(markerId, doc);
+ if (markerEl) {
+ range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
+ markerEl.parentNode.removeChild(markerEl);
+ } else {
+ module.warn("Marker element has been removed. Cannot restore selection.");
+ }
+ }
+
+ function compareRanges(r1, r2) {
+ return r2.compareBoundaryPoints(r1.START_TO_START, r1);
+ }
+
+ function saveRange(range, backward) {
+ var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
+
+ if (range.collapsed) {
+ endEl = insertRangeBoundaryMarker(range, false);
+ return {
+ document: doc,
+ markerId: endEl.id,
+ collapsed: true
+ };
+ } else {
+ endEl = insertRangeBoundaryMarker(range, false);
+ startEl = insertRangeBoundaryMarker(range, true);
+
+ return {
+ document: doc,
+ startMarkerId: startEl.id,
+ endMarkerId: endEl.id,
+ collapsed: false,
+ backward: backward,
+ toString: function() {
+ return "original text: '" + text + "', new text: '" + range.toString() + "'";
+ }
+ };
+ }
+ }
+
+ function restoreRange(rangeInfo, normalize) {
+ var doc = rangeInfo.document;
+ if (typeof normalize == "undefined") {
+ normalize = true;
+ }
+ var range = api.createRange(doc);
+ if (rangeInfo.collapsed) {
+ var markerEl = gEBI(rangeInfo.markerId, doc);
+ if (markerEl) {
+ markerEl.style.display = "inline";
+ var previousNode = markerEl.previousSibling;
+
+ // Workaround for issue 17
+ if (previousNode && previousNode.nodeType == 3) {
+ markerEl.parentNode.removeChild(markerEl);
+ range.collapseToPoint(previousNode, previousNode.length);
+ } else {
+ range.collapseBefore(markerEl);
+ markerEl.parentNode.removeChild(markerEl);
+ }
+ } else {
+ module.warn("Marker element has been removed. Cannot restore selection.");
+ }
+ } else {
+ setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
+ setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
+ }
+
+ if (normalize) {
+ range.normalizeBoundaries();
+ }
+
+ return range;
+ }
+
+ function saveRanges(ranges, backward) {
+ var rangeInfos = [], range, doc;
+
+ // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
+ ranges = ranges.slice(0);
+ ranges.sort(compareRanges);
+
+ for (var i = 0, len = ranges.length; i < len; ++i) {
+ rangeInfos[i] = saveRange(ranges[i], backward);
+ }
+
+ // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
+ // between its markers
+ for (i = len - 1; i >= 0; --i) {
+ range = ranges[i];
+ doc = api.DomRange.getRangeDocument(range);
+ if (range.collapsed) {
+ range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
+ } else {
+ range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
+ range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
+ }
+ }
+
+ return rangeInfos;
+ }
+
+ function saveSelection(win) {
+ if (!api.isSelectionValid(win)) {
+ module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
+ return null;
+ }
+ var sel = api.getSelection(win);
+ var ranges = sel.getAllRanges();
+ var backward = (ranges.length == 1 && sel.isBackward());
+
+ var rangeInfos = saveRanges(ranges, backward);
+
+ // Ensure current selection is unaffected
+ if (backward) {
+ sel.setSingleRange(ranges[0], "backward");
+ } else {
+ sel.setRanges(ranges);
+ }
+
+ return {
+ win: win,
+ rangeInfos: rangeInfos,
+ restored: false
+ };
+ }
+
+ function restoreRanges(rangeInfos) {
+ var ranges = [];
+
+ // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
+ // normalization affecting previously restored ranges.
+ var rangeCount = rangeInfos.length;
+
+ for (var i = rangeCount - 1; i >= 0; i--) {
+ ranges[i] = restoreRange(rangeInfos[i], true);
+ }
+
+ return ranges;
+ }
+
+ function restoreSelection(savedSelection, preserveDirection) {
+ if (!savedSelection.restored) {
+ var rangeInfos = savedSelection.rangeInfos;
+ var sel = api.getSelection(savedSelection.win);
+ var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
+
+ if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
+ sel.removeAllRanges();
+ sel.addRange(ranges[0], true);
+ } else {
+ sel.setRanges(ranges);
+ }
+
+ savedSelection.restored = true;
+ }
+ }
+
+ function removeMarkerElement(doc, markerId) {
+ var markerEl = gEBI(markerId, doc);
+ if (markerEl) {
+ markerEl.parentNode.removeChild(markerEl);
+ }
+ }
+
+ function removeMarkers(savedSelection) {
+ var rangeInfos = savedSelection.rangeInfos;
+ for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
+ rangeInfo = rangeInfos[i];
+ if (rangeInfo.collapsed) {
+ removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
+ } else {
+ removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
+ removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
+ }
+ }
+ }
+
+ api.util.extend(api, {
+ saveRange: saveRange,
+ restoreRange: restoreRange,
+ saveRanges: saveRanges,
+ restoreRanges: restoreRanges,
+ saveSelection: saveSelection,
+ restoreSelection: restoreSelection,
+ removeMarkerElement: removeMarkerElement,
+ removeMarkers: removeMarkers
+ });
+});
+;/*
+ Base.js, version 1.1a
+ Copyright 2006-2010, Dean Edwards
+ License: http://www.opensource.org/licenses/mit-license.php
*/
-window.rangy=function(){function l(p,u){var w=typeof p[u];return w=="function"||!!(w=="object"&&p[u])||w=="unknown"}function K(p,u){return!!(typeof p[u]=="object"&&p[u])}function H(p,u){return typeof p[u]!="undefined"}function I(p){return function(u,w){for(var B=w.length;B--;)if(!p(u,w[B]))return false;return true}}function z(p){return p&&A(p,x)&&v(p,t)}function C(p){window.alert("Rangy not supported in your browser. Reason: "+p);c.initialized=true;c.supported=false}function N(){if(!c.initialized){var p,
-u=false,w=false;if(l(document,"createRange")){p=document.createRange();if(A(p,n)&&v(p,i))u=true;p.detach()}if((p=K(document,"body")?document.body:document.getElementsByTagName("body")[0])&&l(p,"createTextRange")){p=p.createTextRange();if(z(p))w=true}!u&&!w&&C("Neither Range nor TextRange are implemented");c.initialized=true;c.features={implementsDomRange:u,implementsTextRange:w};u=k.concat(f);w=0;for(p=u.length;w<p;++w)try{u[w](c)}catch(B){K(window,"console")&&l(window.console,"log")&&window.console.log("Init listener threw an exception. Continuing.",
-B)}}}function O(p){this.name=p;this.supported=this.initialized=false}var i=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer","START_TO_START","START_TO_END","END_TO_START","END_TO_END"],n=["setStart","setStartBefore","setStartAfter","setEnd","setEndBefore","setEndAfter","collapse","selectNode","selectNodeContents","compareBoundaryPoints","deleteContents","extractContents","cloneContents","insertNode","surroundContents","cloneRange","toString","detach"],
-t=["boundingHeight","boundingLeft","boundingTop","boundingWidth","htmlText","text"],x=["collapse","compareEndPoints","duplicate","getBookmark","moveToBookmark","moveToElementText","parentElement","pasteHTML","select","setEndPoint","getBoundingClientRect"],A=I(l),q=I(K),v=I(H),c={version:"1.2.3",initialized:false,supported:true,util:{isHostMethod:l,isHostObject:K,isHostProperty:H,areHostMethods:A,areHostObjects:q,areHostProperties:v,isTextRange:z},features:{},modules:{},config:{alertOnWarn:false,preferTextRange:false}};
-c.fail=C;c.warn=function(p){p="Rangy warning: "+p;if(c.config.alertOnWarn)window.alert(p);else typeof window.console!="undefined"&&typeof window.console.log!="undefined"&&window.console.log(p)};if({}.hasOwnProperty)c.util.extend=function(p,u){for(var w in u)if(u.hasOwnProperty(w))p[w]=u[w]};else C("hasOwnProperty not supported");var f=[],k=[];c.init=N;c.addInitListener=function(p){c.initialized?p(c):f.push(p)};var r=[];c.addCreateMissingNativeApiListener=function(p){r.push(p)};c.createMissingNativeApi=
-function(p){p=p||window;N();for(var u=0,w=r.length;u<w;++u)r[u](p)};O.prototype.fail=function(p){this.initialized=true;this.supported=false;throw Error("Module '"+this.name+"' failed to load: "+p);};O.prototype.warn=function(p){c.warn("Module "+this.name+": "+p)};O.prototype.createError=function(p){return Error("Error in Rangy "+this.name+" module: "+p)};c.createModule=function(p,u){var w=new O(p);c.modules[p]=w;k.push(function(B){u(B,w);w.initialized=true;w.supported=true})};c.requireModules=function(p){for(var u=
-0,w=p.length,B,V;u<w;++u){V=p[u];B=c.modules[V];if(!B||!(B instanceof O))throw Error("Module '"+V+"' not found");if(!B.supported)throw Error("Module '"+V+"' not supported");}};var L=false;q=function(){if(!L){L=true;c.initialized||N()}};if(typeof window=="undefined")C("No window found");else if(typeof document=="undefined")C("No document found");else{l(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",q,false);if(l(window,"addEventListener"))window.addEventListener("load",
-q,false);else l(window,"attachEvent")?window.attachEvent("onload",q):C("Window does not have required addEventListener or attachEvent method");return c}}();
-rangy.createModule("DomUtil",function(l,K){function H(c){for(var f=0;c=c.previousSibling;)f++;return f}function I(c,f){var k=[],r;for(r=c;r;r=r.parentNode)k.push(r);for(r=f;r;r=r.parentNode)if(v(k,r))return r;return null}function z(c,f,k){for(k=k?c:c.parentNode;k;){c=k.parentNode;if(c===f)return k;k=c}return null}function C(c){c=c.nodeType;return c==3||c==4||c==8}function N(c,f){var k=f.nextSibling,r=f.parentNode;k?r.insertBefore(c,k):r.appendChild(c);return c}function O(c){if(c.nodeType==9)return c;
-else if(typeof c.ownerDocument!="undefined")return c.ownerDocument;else if(typeof c.document!="undefined")return c.document;else if(c.parentNode)return O(c.parentNode);else throw Error("getDocument: no document found for node");}function i(c){if(!c)return"[No node]";return C(c)?'"'+c.data+'"':c.nodeType==1?"<"+c.nodeName+(c.id?' id="'+c.id+'"':"")+">["+c.childNodes.length+"]":c.nodeName}function n(c){this._next=this.root=c}function t(c,f){this.node=c;this.offset=f}function x(c){this.code=this[c];
-this.codeName=c;this.message="DOMException: "+this.codeName}var A=l.util;A.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||K.fail("document missing a Node creation method");A.isHostMethod(document,"getElementsByTagName")||K.fail("document missing getElementsByTagName method");var q=document.createElement("div");A.areHostMethods(q,["insertBefore","appendChild","cloneNode"])||K.fail("Incomplete Element implementation");A.isHostProperty(q,"innerHTML")||K.fail("Element is missing innerHTML property");
-q=document.createTextNode("test");A.areHostMethods(q,["splitText","deleteData","insertData","appendData","cloneNode"])||K.fail("Incomplete Text Node implementation");var v=function(c,f){for(var k=c.length;k--;)if(c[k]===f)return true;return false};n.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var c=this._current=this._next,f;if(this._current)if(f=c.firstChild)this._next=f;else{for(f=null;c!==this.root&&!(f=c.nextSibling);)c=c.parentNode;this._next=f}return this._current},
-detach:function(){this._current=this._next=this.root=null}};t.prototype={equals:function(c){return this.node===c.node&this.offset==c.offset},inspect:function(){return"[DomPosition("+i(this.node)+":"+this.offset+")]"}};x.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11};x.prototype.toString=function(){return this.message};l.dom={arrayContains:v,isHtmlNamespace:function(c){var f;return typeof c.namespaceURI==
-"undefined"||(f=c.namespaceURI)===null||f=="http://www.w3.org/1999/xhtml"},parentElement:function(c){c=c.parentNode;return c.nodeType==1?c:null},getNodeIndex:H,getNodeLength:function(c){var f;return C(c)?c.length:(f=c.childNodes)?f.length:0},getCommonAncestor:I,isAncestorOf:function(c,f,k){for(f=k?f:f.parentNode;f;)if(f===c)return true;else f=f.parentNode;return false},getClosestAncestorIn:z,isCharacterDataNode:C,insertAfter:N,splitDataNode:function(c,f){var k=c.cloneNode(false);k.deleteData(0,f);
-c.deleteData(f,c.length-f);N(k,c);return k},getDocument:O,getWindow:function(c){c=O(c);if(typeof c.defaultView!="undefined")return c.defaultView;else if(typeof c.parentWindow!="undefined")return c.parentWindow;else throw Error("Cannot get a window object for node");},getIframeWindow:function(c){if(typeof c.contentWindow!="undefined")return c.contentWindow;else if(typeof c.contentDocument!="undefined")return c.contentDocument.defaultView;else throw Error("getIframeWindow: No Window object found for iframe element");
-},getIframeDocument:function(c){if(typeof c.contentDocument!="undefined")return c.contentDocument;else if(typeof c.contentWindow!="undefined")return c.contentWindow.document;else throw Error("getIframeWindow: No Document object found for iframe element");},getBody:function(c){return A.isHostObject(c,"body")?c.body:c.getElementsByTagName("body")[0]},getRootContainer:function(c){for(var f;f=c.parentNode;)c=f;return c},comparePoints:function(c,f,k,r){var L;if(c==k)return f===r?0:f<r?-1:1;else if(L=z(k,
-c,true))return f<=H(L)?-1:1;else if(L=z(c,k,true))return H(L)<r?-1:1;else{f=I(c,k);c=c===f?f:z(c,f,true);k=k===f?f:z(k,f,true);if(c===k)throw Error("comparePoints got to case 4 and childA and childB are the same!");else{for(f=f.firstChild;f;){if(f===c)return-1;else if(f===k)return 1;f=f.nextSibling}throw Error("Should not be here!");}}},inspectNode:i,fragmentFromNodeChildren:function(c){for(var f=O(c).createDocumentFragment(),k;k=c.firstChild;)f.appendChild(k);return f},createIterator:function(c){return new n(c)},
-DomPosition:t};l.DOMException=x});
-rangy.createModule("DomRange",function(l){function K(a,e){return a.nodeType!=3&&(g.isAncestorOf(a,e.startContainer,true)||g.isAncestorOf(a,e.endContainer,true))}function H(a){return g.getDocument(a.startContainer)}function I(a,e,j){if(e=a._listeners[e])for(var o=0,E=e.length;o<E;++o)e[o].call(a,{target:a,args:j})}function z(a){return new Z(a.parentNode,g.getNodeIndex(a))}function C(a){return new Z(a.parentNode,g.getNodeIndex(a)+1)}function N(a,e,j){var o=a.nodeType==11?a.firstChild:a;if(g.isCharacterDataNode(e))j==
-e.length?g.insertAfter(a,e):e.parentNode.insertBefore(a,j==0?e:g.splitDataNode(e,j));else j>=e.childNodes.length?e.appendChild(a):e.insertBefore(a,e.childNodes[j]);return o}function O(a){for(var e,j,o=H(a.range).createDocumentFragment();j=a.next();){e=a.isPartiallySelectedSubtree();j=j.cloneNode(!e);if(e){e=a.getSubtreeIterator();j.appendChild(O(e));e.detach(true)}if(j.nodeType==10)throw new S("HIERARCHY_REQUEST_ERR");o.appendChild(j)}return o}function i(a,e,j){var o,E;for(j=j||{stop:false};o=a.next();)if(a.isPartiallySelectedSubtree())if(e(o)===
-false){j.stop=true;return}else{o=a.getSubtreeIterator();i(o,e,j);o.detach(true);if(j.stop)return}else for(o=g.createIterator(o);E=o.next();)if(e(E)===false){j.stop=true;return}}function n(a){for(var e;a.next();)if(a.isPartiallySelectedSubtree()){e=a.getSubtreeIterator();n(e);e.detach(true)}else a.remove()}function t(a){for(var e,j=H(a.range).createDocumentFragment(),o;e=a.next();){if(a.isPartiallySelectedSubtree()){e=e.cloneNode(false);o=a.getSubtreeIterator();e.appendChild(t(o));o.detach(true)}else a.remove();
-if(e.nodeType==10)throw new S("HIERARCHY_REQUEST_ERR");j.appendChild(e)}return j}function x(a,e,j){var o=!!(e&&e.length),E,T=!!j;if(o)E=RegExp("^("+e.join("|")+")$");var m=[];i(new q(a,false),function(s){if((!o||E.test(s.nodeType))&&(!T||j(s)))m.push(s)});return m}function A(a){return"["+(typeof a.getName=="undefined"?"Range":a.getName())+"("+g.inspectNode(a.startContainer)+":"+a.startOffset+", "+g.inspectNode(a.endContainer)+":"+a.endOffset+")]"}function q(a,e){this.range=a;this.clonePartiallySelectedTextNodes=
-e;if(!a.collapsed){this.sc=a.startContainer;this.so=a.startOffset;this.ec=a.endContainer;this.eo=a.endOffset;var j=a.commonAncestorContainer;if(this.sc===this.ec&&g.isCharacterDataNode(this.sc)){this.isSingleCharacterDataNode=true;this._first=this._last=this._next=this.sc}else{this._first=this._next=this.sc===j&&!g.isCharacterDataNode(this.sc)?this.sc.childNodes[this.so]:g.getClosestAncestorIn(this.sc,j,true);this._last=this.ec===j&&!g.isCharacterDataNode(this.ec)?this.ec.childNodes[this.eo-1]:g.getClosestAncestorIn(this.ec,
-j,true)}}}function v(a){this.code=this[a];this.codeName=a;this.message="RangeException: "+this.codeName}function c(a,e,j){this.nodes=x(a,e,j);this._next=this.nodes[0];this._position=0}function f(a){return function(e,j){for(var o,E=j?e:e.parentNode;E;){o=E.nodeType;if(g.arrayContains(a,o))return E;E=E.parentNode}return null}}function k(a,e){if(G(a,e))throw new v("INVALID_NODE_TYPE_ERR");}function r(a){if(!a.startContainer)throw new S("INVALID_STATE_ERR");}function L(a,e){if(!g.arrayContains(e,a.nodeType))throw new v("INVALID_NODE_TYPE_ERR");
-}function p(a,e){if(e<0||e>(g.isCharacterDataNode(a)?a.length:a.childNodes.length))throw new S("INDEX_SIZE_ERR");}function u(a,e){if(h(a,true)!==h(e,true))throw new S("WRONG_DOCUMENT_ERR");}function w(a){if(D(a,true))throw new S("NO_MODIFICATION_ALLOWED_ERR");}function B(a,e){if(!a)throw new S(e);}function V(a){return!!a.startContainer&&!!a.endContainer&&!(!g.arrayContains(ba,a.startContainer.nodeType)&&!h(a.startContainer,true))&&!(!g.arrayContains(ba,a.endContainer.nodeType)&&!h(a.endContainer,
-true))&&a.startOffset<=(g.isCharacterDataNode(a.startContainer)?a.startContainer.length:a.startContainer.childNodes.length)&&a.endOffset<=(g.isCharacterDataNode(a.endContainer)?a.endContainer.length:a.endContainer.childNodes.length)}function J(a){r(a);if(!V(a))throw Error("Range error: Range is no longer valid after DOM mutation ("+a.inspect()+")");}function ca(){}function Y(a){a.START_TO_START=ia;a.START_TO_END=la;a.END_TO_END=ra;a.END_TO_START=ma;a.NODE_BEFORE=na;a.NODE_AFTER=oa;a.NODE_BEFORE_AND_AFTER=
-pa;a.NODE_INSIDE=ja}function W(a){Y(a);Y(a.prototype)}function da(a,e){return function(){J(this);var j=this.startContainer,o=this.startOffset,E=this.commonAncestorContainer,T=new q(this,true);if(j!==E){j=g.getClosestAncestorIn(j,E,true);o=C(j);j=o.node;o=o.offset}i(T,w);T.reset();E=a(T);T.detach();e(this,j,o,j,o);return E}}function fa(a,e,j){function o(m,s){return function(y){r(this);L(y,$);L(d(y),ba);y=(m?z:C)(y);(s?E:T)(this,y.node,y.offset)}}function E(m,s,y){var F=m.endContainer,Q=m.endOffset;
-if(s!==m.startContainer||y!==m.startOffset){if(d(s)!=d(F)||g.comparePoints(s,y,F,Q)==1){F=s;Q=y}e(m,s,y,F,Q)}}function T(m,s,y){var F=m.startContainer,Q=m.startOffset;if(s!==m.endContainer||y!==m.endOffset){if(d(s)!=d(F)||g.comparePoints(s,y,F,Q)==-1){F=s;Q=y}e(m,F,Q,s,y)}}a.prototype=new ca;l.util.extend(a.prototype,{setStart:function(m,s){r(this);k(m,true);p(m,s);E(this,m,s)},setEnd:function(m,s){r(this);k(m,true);p(m,s);T(this,m,s)},setStartBefore:o(true,true),setStartAfter:o(false,true),setEndBefore:o(true,
-false),setEndAfter:o(false,false),collapse:function(m){J(this);m?e(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):e(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(m){r(this);k(m,true);e(this,m,0,m,g.getNodeLength(m))},selectNode:function(m){r(this);k(m,false);L(m,$);var s=z(m);m=C(m);e(this,s.node,s.offset,m.node,m.offset)},extractContents:da(t,e),deleteContents:da(n,e),canSurroundContents:function(){J(this);w(this.startContainer);
-w(this.endContainer);var m=new q(this,true),s=m._first&&K(m._first,this)||m._last&&K(m._last,this);m.detach();return!s},detach:function(){j(this)},splitBoundaries:function(){J(this);var m=this.startContainer,s=this.startOffset,y=this.endContainer,F=this.endOffset,Q=m===y;g.isCharacterDataNode(y)&&F>0&&F<y.length&&g.splitDataNode(y,F);if(g.isCharacterDataNode(m)&&s>0&&s<m.length){m=g.splitDataNode(m,s);if(Q){F-=s;y=m}else y==m.parentNode&&F>=g.getNodeIndex(m)&&F++;s=0}e(this,m,s,y,F)},normalizeBoundaries:function(){J(this);
-var m=this.startContainer,s=this.startOffset,y=this.endContainer,F=this.endOffset,Q=function(U){var R=U.nextSibling;if(R&&R.nodeType==U.nodeType){y=U;F=U.length;U.appendData(R.data);R.parentNode.removeChild(R)}},qa=function(U){var R=U.previousSibling;if(R&&R.nodeType==U.nodeType){m=U;var sa=U.length;s=R.length;U.insertData(0,R.data);R.parentNode.removeChild(R);if(m==y){F+=s;y=m}else if(y==U.parentNode){R=g.getNodeIndex(U);if(F==R){y=U;F=sa}else F>R&&F--}}},ga=true;if(g.isCharacterDataNode(y))y.length==
-F&&Q(y);else{if(F>0)(ga=y.childNodes[F-1])&&g.isCharacterDataNode(ga)&&Q(ga);ga=!this.collapsed}if(ga)if(g.isCharacterDataNode(m))s==0&&qa(m);else{if(s<m.childNodes.length)(Q=m.childNodes[s])&&g.isCharacterDataNode(Q)&&qa(Q)}else{m=y;s=F}e(this,m,s,y,F)},collapseToPoint:function(m,s){r(this);k(m,true);p(m,s);if(m!==this.startContainer||s!==this.startOffset||m!==this.endContainer||s!==this.endOffset)e(this,m,s,m,s)}});W(a)}function ea(a){a.collapsed=a.startContainer===a.endContainer&&a.startOffset===
-a.endOffset;a.commonAncestorContainer=a.collapsed?a.startContainer:g.getCommonAncestor(a.startContainer,a.endContainer)}function ha(a,e,j,o,E){var T=a.startContainer!==e||a.startOffset!==j,m=a.endContainer!==o||a.endOffset!==E;a.startContainer=e;a.startOffset=j;a.endContainer=o;a.endOffset=E;ea(a);I(a,"boundarychange",{startMoved:T,endMoved:m})}function M(a){this.startContainer=a;this.startOffset=0;this.endContainer=a;this.endOffset=0;this._listeners={boundarychange:[],detach:[]};ea(this)}l.requireModules(["DomUtil"]);
-var g=l.dom,Z=g.DomPosition,S=l.DOMException;q.prototype={_current:null,_next:null,_first:null,_last:null,isSingleCharacterDataNode:false,reset:function(){this._current=null;this._next=this._first},hasNext:function(){return!!this._next},next:function(){var a=this._current=this._next;if(a){this._next=a!==this._last?a.nextSibling:null;if(g.isCharacterDataNode(a)&&this.clonePartiallySelectedTextNodes){if(a===this.ec)(a=a.cloneNode(true)).deleteData(this.eo,a.length-this.eo);if(this._current===this.sc)(a=
-a.cloneNode(true)).deleteData(0,this.so)}}return a},remove:function(){var a=this._current,e,j;if(g.isCharacterDataNode(a)&&(a===this.sc||a===this.ec)){e=a===this.sc?this.so:0;j=a===this.ec?this.eo:a.length;e!=j&&a.deleteData(e,j-e)}else a.parentNode&&a.parentNode.removeChild(a)},isPartiallySelectedSubtree:function(){return K(this._current,this.range)},getSubtreeIterator:function(){var a;if(this.isSingleCharacterDataNode){a=this.range.cloneRange();a.collapse()}else{a=new M(H(this.range));var e=this._current,
-j=e,o=0,E=e,T=g.getNodeLength(e);if(g.isAncestorOf(e,this.sc,true)){j=this.sc;o=this.so}if(g.isAncestorOf(e,this.ec,true)){E=this.ec;T=this.eo}ha(a,j,o,E,T)}return new q(a,this.clonePartiallySelectedTextNodes)},detach:function(a){a&&this.range.detach();this.range=this._current=this._next=this._first=this._last=this.sc=this.so=this.ec=this.eo=null}};v.prototype={BAD_BOUNDARYPOINTS_ERR:1,INVALID_NODE_TYPE_ERR:2};v.prototype.toString=function(){return this.message};c.prototype={_current:null,hasNext:function(){return!!this._next},
-next:function(){this._current=this._next;this._next=this.nodes[++this._position];return this._current},detach:function(){this._current=this._next=this.nodes=null}};var $=[1,3,4,5,7,8,10],ba=[2,9,11],aa=[1,3,4,5,7,8,10,11],b=[1,3,4,5,7,8],d=g.getRootContainer,h=f([9,11]),D=f([5,6,10,12]),G=f([6,10,12]),P=document.createElement("style"),X=false;try{P.innerHTML="<b>x</b>";X=P.firstChild.nodeType==3}catch(ta){}l.features.htmlParsingConforms=X;var ka=["startContainer","startOffset","endContainer","endOffset",
-"collapsed","commonAncestorContainer"],ia=0,la=1,ra=2,ma=3,na=0,oa=1,pa=2,ja=3;ca.prototype={attachListener:function(a,e){this._listeners[a].push(e)},compareBoundaryPoints:function(a,e){J(this);u(this.startContainer,e.startContainer);var j=a==ma||a==ia?"start":"end",o=a==la||a==ia?"start":"end";return g.comparePoints(this[j+"Container"],this[j+"Offset"],e[o+"Container"],e[o+"Offset"])},insertNode:function(a){J(this);L(a,aa);w(this.startContainer);if(g.isAncestorOf(a,this.startContainer,true))throw new S("HIERARCHY_REQUEST_ERR");
-this.setStartBefore(N(a,this.startContainer,this.startOffset))},cloneContents:function(){J(this);var a,e;if(this.collapsed)return H(this).createDocumentFragment();else{if(this.startContainer===this.endContainer&&g.isCharacterDataNode(this.startContainer)){a=this.startContainer.cloneNode(true);a.data=a.data.slice(this.startOffset,this.endOffset);e=H(this).createDocumentFragment();e.appendChild(a);return e}else{e=new q(this,true);a=O(e);e.detach()}return a}},canSurroundContents:function(){J(this);w(this.startContainer);
-w(this.endContainer);var a=new q(this,true),e=a._first&&K(a._first,this)||a._last&&K(a._last,this);a.detach();return!e},surroundContents:function(a){L(a,b);if(!this.canSurroundContents())throw new v("BAD_BOUNDARYPOINTS_ERR");var e=this.extractContents();if(a.hasChildNodes())for(;a.lastChild;)a.removeChild(a.lastChild);N(a,this.startContainer,this.startOffset);a.appendChild(e);this.selectNode(a)},cloneRange:function(){J(this);for(var a=new M(H(this)),e=ka.length,j;e--;){j=ka[e];a[j]=this[j]}return a},
-toString:function(){J(this);var a=this.startContainer;if(a===this.endContainer&&g.isCharacterDataNode(a))return a.nodeType==3||a.nodeType==4?a.data.slice(this.startOffset,this.endOffset):"";else{var e=[];a=new q(this,true);i(a,function(j){if(j.nodeType==3||j.nodeType==4)e.push(j.data)});a.detach();return e.join("")}},compareNode:function(a){J(this);var e=a.parentNode,j=g.getNodeIndex(a);if(!e)throw new S("NOT_FOUND_ERR");a=this.comparePoint(e,j);e=this.comparePoint(e,j+1);return a<0?e>0?pa:na:e>0?
-oa:ja},comparePoint:function(a,e){J(this);B(a,"HIERARCHY_REQUEST_ERR");u(a,this.startContainer);if(g.comparePoints(a,e,this.startContainer,this.startOffset)<0)return-1;else if(g.comparePoints(a,e,this.endContainer,this.endOffset)>0)return 1;return 0},createContextualFragment:X?function(a){var e=this.startContainer,j=g.getDocument(e);if(!e)throw new S("INVALID_STATE_ERR");var o=null;if(e.nodeType==1)o=e;else if(g.isCharacterDataNode(e))o=g.parentElement(e);o=o===null||o.nodeName=="HTML"&&g.isHtmlNamespace(g.getDocument(o).documentElement)&&
-g.isHtmlNamespace(o)?j.createElement("body"):o.cloneNode(false);o.innerHTML=a;return g.fragmentFromNodeChildren(o)}:function(a){r(this);var e=H(this).createElement("body");e.innerHTML=a;return g.fragmentFromNodeChildren(e)},toHtml:function(){J(this);var a=H(this).createElement("div");a.appendChild(this.cloneContents());return a.innerHTML},intersectsNode:function(a,e){J(this);B(a,"NOT_FOUND_ERR");if(g.getDocument(a)!==H(this))return false;var j=a.parentNode,o=g.getNodeIndex(a);B(j,"NOT_FOUND_ERR");
-var E=g.comparePoints(j,o,this.endContainer,this.endOffset);j=g.comparePoints(j,o+1,this.startContainer,this.startOffset);return e?E<=0&&j>=0:E<0&&j>0},isPointInRange:function(a,e){J(this);B(a,"HIERARCHY_REQUEST_ERR");u(a,this.startContainer);return g.comparePoints(a,e,this.startContainer,this.startOffset)>=0&&g.comparePoints(a,e,this.endContainer,this.endOffset)<=0},intersectsRange:function(a,e){J(this);if(H(a)!=H(this))throw new S("WRONG_DOCUMENT_ERR");var j=g.comparePoints(this.startContainer,
-this.startOffset,a.endContainer,a.endOffset),o=g.comparePoints(this.endContainer,this.endOffset,a.startContainer,a.startOffset);return e?j<=0&&o>=0:j<0&&o>0},intersection:function(a){if(this.intersectsRange(a)){var e=g.comparePoints(this.startContainer,this.startOffset,a.startContainer,a.startOffset),j=g.comparePoints(this.endContainer,this.endOffset,a.endContainer,a.endOffset),o=this.cloneRange();e==-1&&o.setStart(a.startContainer,a.startOffset);j==1&&o.setEnd(a.endContainer,a.endOffset);return o}return null},
-union:function(a){if(this.intersectsRange(a,true)){var e=this.cloneRange();g.comparePoints(a.startContainer,a.startOffset,this.startContainer,this.startOffset)==-1&&e.setStart(a.startContainer,a.startOffset);g.comparePoints(a.endContainer,a.endOffset,this.endContainer,this.endOffset)==1&&e.setEnd(a.endContainer,a.endOffset);return e}else throw new v("Ranges do not intersect");},containsNode:function(a,e){return e?this.intersectsNode(a,false):this.compareNode(a)==ja},containsNodeContents:function(a){return this.comparePoint(a,
-0)>=0&&this.comparePoint(a,g.getNodeLength(a))<=0},containsRange:function(a){return this.intersection(a).equals(a)},containsNodeText:function(a){var e=this.cloneRange();e.selectNode(a);var j=e.getNodes([3]);if(j.length>0){e.setStart(j[0],0);a=j.pop();e.setEnd(a,a.length);a=this.containsRange(e);e.detach();return a}else return this.containsNodeContents(a)},createNodeIterator:function(a,e){J(this);return new c(this,a,e)},getNodes:function(a,e){J(this);return x(this,a,e)},getDocument:function(){return H(this)},
-collapseBefore:function(a){r(this);this.setEndBefore(a);this.collapse(false)},collapseAfter:function(a){r(this);this.setStartAfter(a);this.collapse(true)},getName:function(){return"DomRange"},equals:function(a){return M.rangesEqual(this,a)},isValid:function(){return V(this)},inspect:function(){return A(this)}};fa(M,ha,function(a){r(a);a.startContainer=a.startOffset=a.endContainer=a.endOffset=null;a.collapsed=a.commonAncestorContainer=null;I(a,"detach",null);a._listeners=null});l.rangePrototype=ca.prototype;
-M.rangeProperties=ka;M.RangeIterator=q;M.copyComparisonConstants=W;M.createPrototypeRange=fa;M.inspect=A;M.getRangeDocument=H;M.rangesEqual=function(a,e){return a.startContainer===e.startContainer&&a.startOffset===e.startOffset&&a.endContainer===e.endContainer&&a.endOffset===e.endOffset};l.DomRange=M;l.RangeException=v});
-rangy.createModule("WrappedRange",function(l){function K(i,n,t,x){var A=i.duplicate();A.collapse(t);var q=A.parentElement();z.isAncestorOf(n,q,true)||(q=n);if(!q.canHaveHTML)return new C(q.parentNode,z.getNodeIndex(q));n=z.getDocument(q).createElement("span");var v,c=t?"StartToStart":"StartToEnd";do{q.insertBefore(n,n.previousSibling);A.moveToElementText(n)}while((v=A.compareEndPoints(c,i))>0&&n.previousSibling);c=n.nextSibling;if(v==-1&&c&&z.isCharacterDataNode(c)){A.setEndPoint(t?"EndToStart":"EndToEnd",
-i);if(/[\r\n]/.test(c.data)){q=A.duplicate();t=q.text.replace(/\r\n/g,"\r").length;for(t=q.moveStart("character",t);q.compareEndPoints("StartToEnd",q)==-1;){t++;q.moveStart("character",1)}}else t=A.text.length;q=new C(c,t)}else{c=(x||!t)&&n.previousSibling;q=(t=(x||t)&&n.nextSibling)&&z.isCharacterDataNode(t)?new C(t,0):c&&z.isCharacterDataNode(c)?new C(c,c.length):new C(q,z.getNodeIndex(n))}n.parentNode.removeChild(n);return q}function H(i,n){var t,x,A=i.offset,q=z.getDocument(i.node),v=q.body.createTextRange(),
-c=z.isCharacterDataNode(i.node);if(c){t=i.node;x=t.parentNode}else{t=i.node.childNodes;t=A<t.length?t[A]:null;x=i.node}q=q.createElement("span");q.innerHTML="&#feff;";t?x.insertBefore(q,t):x.appendChild(q);v.moveToElementText(q);v.collapse(!n);x.removeChild(q);if(c)v[n?"moveStart":"moveEnd"]("character",A);return v}l.requireModules(["DomUtil","DomRange"]);var I,z=l.dom,C=z.DomPosition,N=l.DomRange;if(l.features.implementsDomRange&&(!l.features.implementsTextRange||!l.config.preferTextRange)){(function(){function i(f){for(var k=
-t.length,r;k--;){r=t[k];f[r]=f.nativeRange[r]}}var n,t=N.rangeProperties,x,A;I=function(f){if(!f)throw Error("Range must be specified");this.nativeRange=f;i(this)};N.createPrototypeRange(I,function(f,k,r,L,p){var u=f.endContainer!==L||f.endOffset!=p;if(f.startContainer!==k||f.startOffset!=r||u){f.setEnd(L,p);f.setStart(k,r)}},function(f){f.nativeRange.detach();f.detached=true;for(var k=t.length,r;k--;){r=t[k];f[r]=null}});n=I.prototype;n.selectNode=function(f){this.nativeRange.selectNode(f);i(this)};
-n.deleteContents=function(){this.nativeRange.deleteContents();i(this)};n.extractContents=function(){var f=this.nativeRange.extractContents();i(this);return f};n.cloneContents=function(){return this.nativeRange.cloneContents()};n.surroundContents=function(f){this.nativeRange.surroundContents(f);i(this)};n.collapse=function(f){this.nativeRange.collapse(f);i(this)};n.cloneRange=function(){return new I(this.nativeRange.cloneRange())};n.refresh=function(){i(this)};n.toString=function(){return this.nativeRange.toString()};
-var q=document.createTextNode("test");z.getBody(document).appendChild(q);var v=document.createRange();v.setStart(q,0);v.setEnd(q,0);try{v.setStart(q,1);x=true;n.setStart=function(f,k){this.nativeRange.setStart(f,k);i(this)};n.setEnd=function(f,k){this.nativeRange.setEnd(f,k);i(this)};A=function(f){return function(k){this.nativeRange[f](k);i(this)}}}catch(c){x=false;n.setStart=function(f,k){try{this.nativeRange.setStart(f,k)}catch(r){this.nativeRange.setEnd(f,k);this.nativeRange.setStart(f,k)}i(this)};
-n.setEnd=function(f,k){try{this.nativeRange.setEnd(f,k)}catch(r){this.nativeRange.setStart(f,k);this.nativeRange.setEnd(f,k)}i(this)};A=function(f,k){return function(r){try{this.nativeRange[f](r)}catch(L){this.nativeRange[k](r);this.nativeRange[f](r)}i(this)}}}n.setStartBefore=A("setStartBefore","setEndBefore");n.setStartAfter=A("setStartAfter","setEndAfter");n.setEndBefore=A("setEndBefore","setStartBefore");n.setEndAfter=A("setEndAfter","setStartAfter");v.selectNodeContents(q);n.selectNodeContents=
-v.startContainer==q&&v.endContainer==q&&v.startOffset==0&&v.endOffset==q.length?function(f){this.nativeRange.selectNodeContents(f);i(this)}:function(f){this.setStart(f,0);this.setEnd(f,N.getEndOffset(f))};v.selectNodeContents(q);v.setEnd(q,3);x=document.createRange();x.selectNodeContents(q);x.setEnd(q,4);x.setStart(q,2);n.compareBoundaryPoints=v.compareBoundaryPoints(v.START_TO_END,x)==-1&v.compareBoundaryPoints(v.END_TO_START,x)==1?function(f,k){k=k.nativeRange||k;if(f==k.START_TO_END)f=k.END_TO_START;
-else if(f==k.END_TO_START)f=k.START_TO_END;return this.nativeRange.compareBoundaryPoints(f,k)}:function(f,k){return this.nativeRange.compareBoundaryPoints(f,k.nativeRange||k)};if(l.util.isHostMethod(v,"createContextualFragment"))n.createContextualFragment=function(f){return this.nativeRange.createContextualFragment(f)};z.getBody(document).removeChild(q);v.detach();x.detach()})();l.createNativeRange=function(i){i=i||document;return i.createRange()}}else if(l.features.implementsTextRange){I=function(i){this.textRange=
-i;this.refresh()};I.prototype=new N(document);I.prototype.refresh=function(){var i,n,t=this.textRange;i=t.parentElement();var x=t.duplicate();x.collapse(true);n=x.parentElement();x=t.duplicate();x.collapse(false);t=x.parentElement();n=n==t?n:z.getCommonAncestor(n,t);n=n==i?n:z.getCommonAncestor(i,n);if(this.textRange.compareEndPoints("StartToEnd",this.textRange)==0)n=i=K(this.textRange,n,true,true);else{i=K(this.textRange,n,true,false);n=K(this.textRange,n,false,false)}this.setStart(i.node,i.offset);
-this.setEnd(n.node,n.offset)};N.copyComparisonConstants(I);var O=function(){return this}();if(typeof O.Range=="undefined")O.Range=I;l.createNativeRange=function(i){i=i||document;return i.body.createTextRange()}}if(l.features.implementsTextRange)I.rangeToTextRange=function(i){if(i.collapsed)return H(new C(i.startContainer,i.startOffset),true);else{var n=H(new C(i.startContainer,i.startOffset),true),t=H(new C(i.endContainer,i.endOffset),false);i=z.getDocument(i.startContainer).body.createTextRange();
-i.setEndPoint("StartToStart",n);i.setEndPoint("EndToEnd",t);return i}};I.prototype.getName=function(){return"WrappedRange"};l.WrappedRange=I;l.createRange=function(i){i=i||document;return new I(l.createNativeRange(i))};l.createRangyRange=function(i){i=i||document;return new N(i)};l.createIframeRange=function(i){return l.createRange(z.getIframeDocument(i))};l.createIframeRangyRange=function(i){return l.createRangyRange(z.getIframeDocument(i))};l.addCreateMissingNativeApiListener(function(i){i=i.document;
-if(typeof i.createRange=="undefined")i.createRange=function(){return l.createRange(this)};i=i=null})});
-rangy.createModule("WrappedSelection",function(l,K){function H(b){return(b||window).getSelection()}function I(b){return(b||window).document.selection}function z(b,d,h){var D=h?"end":"start";h=h?"start":"end";b.anchorNode=d[D+"Container"];b.anchorOffset=d[D+"Offset"];b.focusNode=d[h+"Container"];b.focusOffset=d[h+"Offset"]}function C(b){b.anchorNode=b.focusNode=null;b.anchorOffset=b.focusOffset=0;b.rangeCount=0;b.isCollapsed=true;b._ranges.length=0}function N(b){var d;if(b instanceof k){d=b._selectionNativeRange;
-if(!d){d=l.createNativeRange(c.getDocument(b.startContainer));d.setEnd(b.endContainer,b.endOffset);d.setStart(b.startContainer,b.startOffset);b._selectionNativeRange=d;b.attachListener("detach",function(){this._selectionNativeRange=null})}}else if(b instanceof r)d=b.nativeRange;else if(l.features.implementsDomRange&&b instanceof c.getWindow(b.startContainer).Range)d=b;return d}function O(b){var d=b.getNodes(),h;a:if(!d.length||d[0].nodeType!=1)h=false;else{h=1;for(var D=d.length;h<D;++h)if(!c.isAncestorOf(d[0],
-d[h])){h=false;break a}h=true}if(!h)throw Error("getSingleElementFromRange: range "+b.inspect()+" did not consist of a single element");return d[0]}function i(b,d){var h=new r(d);b._ranges=[h];z(b,h,false);b.rangeCount=1;b.isCollapsed=h.collapsed}function n(b){b._ranges.length=0;if(b.docSelection.type=="None")C(b);else{var d=b.docSelection.createRange();if(d&&typeof d.text!="undefined")i(b,d);else{b.rangeCount=d.length;for(var h,D=c.getDocument(d.item(0)),G=0;G<b.rangeCount;++G){h=l.createRange(D);
-h.selectNode(d.item(G));b._ranges.push(h)}b.isCollapsed=b.rangeCount==1&&b._ranges[0].collapsed;z(b,b._ranges[b.rangeCount-1],false)}}}function t(b,d){var h=b.docSelection.createRange(),D=O(d),G=c.getDocument(h.item(0));G=c.getBody(G).createControlRange();for(var P=0,X=h.length;P<X;++P)G.add(h.item(P));try{G.add(D)}catch(ta){throw Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");}G.select();n(b)}function x(b,d,h){this.nativeSelection=
-b;this.docSelection=d;this._ranges=[];this.win=h;this.refresh()}function A(b,d){var h=c.getDocument(d[0].startContainer);h=c.getBody(h).createControlRange();for(var D=0,G;D<rangeCount;++D){G=O(d[D]);try{h.add(G)}catch(P){throw Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");}}h.select();n(b)}function q(b,d){if(b.anchorNode&&c.getDocument(b.anchorNode)!==c.getDocument(d))throw new L("WRONG_DOCUMENT_ERR");}function v(b){var d=
-[],h=new p(b.anchorNode,b.anchorOffset),D=new p(b.focusNode,b.focusOffset),G=typeof b.getName=="function"?b.getName():"Selection";if(typeof b.rangeCount!="undefined")for(var P=0,X=b.rangeCount;P<X;++P)d[P]=k.inspect(b.getRangeAt(P));return"["+G+"(Ranges: "+d.join(", ")+")(anchor: "+h.inspect()+", focus: "+D.inspect()+"]"}l.requireModules(["DomUtil","DomRange","WrappedRange"]);l.config.checkSelectionRanges=true;var c=l.dom,f=l.util,k=l.DomRange,r=l.WrappedRange,L=l.DOMException,p=c.DomPosition,u,w,
-B=l.util.isHostMethod(window,"getSelection"),V=l.util.isHostObject(document,"selection"),J=V&&(!B||l.config.preferTextRange);if(J){u=I;l.isSelectionValid=function(b){b=(b||window).document;var d=b.selection;return d.type!="None"||c.getDocument(d.createRange().parentElement())==b}}else if(B){u=H;l.isSelectionValid=function(){return true}}else K.fail("Neither document.selection or window.getSelection() detected.");l.getNativeSelection=u;B=u();var ca=l.createNativeRange(document),Y=c.getBody(document),
-W=f.areHostObjects(B,f.areHostProperties(B,["anchorOffset","focusOffset"]));l.features.selectionHasAnchorAndFocus=W;var da=f.isHostMethod(B,"extend");l.features.selectionHasExtend=da;var fa=typeof B.rangeCount=="number";l.features.selectionHasRangeCount=fa;var ea=false,ha=true;f.areHostMethods(B,["addRange","getRangeAt","removeAllRanges"])&&typeof B.rangeCount=="number"&&l.features.implementsDomRange&&function(){var b=document.createElement("iframe");b.frameBorder=0;b.style.position="absolute";b.style.left=
-"-10000px";Y.appendChild(b);var d=c.getIframeDocument(b);d.open();d.write("<html><head></head><body>12</body></html>");d.close();var h=c.getIframeWindow(b).getSelection(),D=d.documentElement.lastChild.firstChild;d=d.createRange();d.setStart(D,1);d.collapse(true);h.addRange(d);ha=h.rangeCount==1;h.removeAllRanges();var G=d.cloneRange();d.setStart(D,0);G.setEnd(D,2);h.addRange(d);h.addRange(G);ea=h.rangeCount==2;d.detach();G.detach();Y.removeChild(b)}();l.features.selectionSupportsMultipleRanges=ea;
-l.features.collapsedNonEditableSelectionsSupported=ha;var M=false,g;if(Y&&f.isHostMethod(Y,"createControlRange")){g=Y.createControlRange();if(f.areHostProperties(g,["item","add"]))M=true}l.features.implementsControlRange=M;w=W?function(b){return b.anchorNode===b.focusNode&&b.anchorOffset===b.focusOffset}:function(b){return b.rangeCount?b.getRangeAt(b.rangeCount-1).collapsed:false};var Z;if(f.isHostMethod(B,"getRangeAt"))Z=function(b,d){try{return b.getRangeAt(d)}catch(h){return null}};else if(W)Z=
-function(b){var d=c.getDocument(b.anchorNode);d=l.createRange(d);d.setStart(b.anchorNode,b.anchorOffset);d.setEnd(b.focusNode,b.focusOffset);if(d.collapsed!==this.isCollapsed){d.setStart(b.focusNode,b.focusOffset);d.setEnd(b.anchorNode,b.anchorOffset)}return d};l.getSelection=function(b){b=b||window;var d=b._rangySelection,h=u(b),D=V?I(b):null;if(d){d.nativeSelection=h;d.docSelection=D;d.refresh(b)}else{d=new x(h,D,b);b._rangySelection=d}return d};l.getIframeSelection=function(b){return l.getSelection(c.getIframeWindow(b))};
-g=x.prototype;if(!J&&W&&f.areHostMethods(B,["removeAllRanges","addRange"])){g.removeAllRanges=function(){this.nativeSelection.removeAllRanges();C(this)};var S=function(b,d){var h=k.getRangeDocument(d);h=l.createRange(h);h.collapseToPoint(d.endContainer,d.endOffset);b.nativeSelection.addRange(N(h));b.nativeSelection.extend(d.startContainer,d.startOffset);b.refresh()};g.addRange=fa?function(b,d){if(M&&V&&this.docSelection.type=="Control")t(this,b);else if(d&&da)S(this,b);else{var h;if(ea)h=this.rangeCount;
-else{this.removeAllRanges();h=0}this.nativeSelection.addRange(N(b));this.rangeCount=this.nativeSelection.rangeCount;if(this.rangeCount==h+1){if(l.config.checkSelectionRanges)if((h=Z(this.nativeSelection,this.rangeCount-1))&&!k.rangesEqual(h,b))b=new r(h);this._ranges[this.rangeCount-1]=b;z(this,b,aa(this.nativeSelection));this.isCollapsed=w(this)}else this.refresh()}}:function(b,d){if(d&&da)S(this,b);else{this.nativeSelection.addRange(N(b));this.refresh()}};g.setRanges=function(b){if(M&&b.length>
-1)A(this,b);else{this.removeAllRanges();for(var d=0,h=b.length;d<h;++d)this.addRange(b[d])}}}else if(f.isHostMethod(B,"empty")&&f.isHostMethod(ca,"select")&&M&&J){g.removeAllRanges=function(){try{this.docSelection.empty();if(this.docSelection.type!="None"){var b;if(this.anchorNode)b=c.getDocument(this.anchorNode);else if(this.docSelection.type=="Control"){var d=this.docSelection.createRange();if(d.length)b=c.getDocument(d.item(0)).body.createTextRange()}if(b){b.body.createTextRange().select();this.docSelection.empty()}}}catch(h){}C(this)};
-g.addRange=function(b){if(this.docSelection.type=="Control")t(this,b);else{r.rangeToTextRange(b).select();this._ranges[0]=b;this.rangeCount=1;this.isCollapsed=this._ranges[0].collapsed;z(this,b,false)}};g.setRanges=function(b){this.removeAllRanges();var d=b.length;if(d>1)A(this,b);else d&&this.addRange(b[0])}}else{K.fail("No means of selecting a Range or TextRange was found");return false}g.getRangeAt=function(b){if(b<0||b>=this.rangeCount)throw new L("INDEX_SIZE_ERR");else return this._ranges[b]};
-var $;if(J)$=function(b){var d;if(l.isSelectionValid(b.win))d=b.docSelection.createRange();else{d=c.getBody(b.win.document).createTextRange();d.collapse(true)}if(b.docSelection.type=="Control")n(b);else d&&typeof d.text!="undefined"?i(b,d):C(b)};else if(f.isHostMethod(B,"getRangeAt")&&typeof B.rangeCount=="number")$=function(b){if(M&&V&&b.docSelection.type=="Control")n(b);else{b._ranges.length=b.rangeCount=b.nativeSelection.rangeCount;if(b.rangeCount){for(var d=0,h=b.rangeCount;d<h;++d)b._ranges[d]=
-new l.WrappedRange(b.nativeSelection.getRangeAt(d));z(b,b._ranges[b.rangeCount-1],aa(b.nativeSelection));b.isCollapsed=w(b)}else C(b)}};else if(W&&typeof B.isCollapsed=="boolean"&&typeof ca.collapsed=="boolean"&&l.features.implementsDomRange)$=function(b){var d;d=b.nativeSelection;if(d.anchorNode){d=Z(d,0);b._ranges=[d];b.rangeCount=1;d=b.nativeSelection;b.anchorNode=d.anchorNode;b.anchorOffset=d.anchorOffset;b.focusNode=d.focusNode;b.focusOffset=d.focusOffset;b.isCollapsed=w(b)}else C(b)};else{K.fail("No means of obtaining a Range or TextRange from the user's selection was found");
-return false}g.refresh=function(b){var d=b?this._ranges.slice(0):null;$(this);if(b){b=d.length;if(b!=this._ranges.length)return false;for(;b--;)if(!k.rangesEqual(d[b],this._ranges[b]))return false;return true}};var ba=function(b,d){var h=b.getAllRanges(),D=false;b.removeAllRanges();for(var G=0,P=h.length;G<P;++G)if(D||d!==h[G])b.addRange(h[G]);else D=true;b.rangeCount||C(b)};g.removeRange=M?function(b){if(this.docSelection.type=="Control"){var d=this.docSelection.createRange();b=O(b);var h=c.getDocument(d.item(0));
-h=c.getBody(h).createControlRange();for(var D,G=false,P=0,X=d.length;P<X;++P){D=d.item(P);if(D!==b||G)h.add(d.item(P));else G=true}h.select();n(this)}else ba(this,b)}:function(b){ba(this,b)};var aa;if(!J&&W&&l.features.implementsDomRange){aa=function(b){var d=false;if(b.anchorNode)d=c.comparePoints(b.anchorNode,b.anchorOffset,b.focusNode,b.focusOffset)==1;return d};g.isBackwards=function(){return aa(this)}}else aa=g.isBackwards=function(){return false};g.toString=function(){for(var b=[],d=0,h=this.rangeCount;d<
-h;++d)b[d]=""+this._ranges[d];return b.join("")};g.collapse=function(b,d){q(this,b);var h=l.createRange(c.getDocument(b));h.collapseToPoint(b,d);this.removeAllRanges();this.addRange(h);this.isCollapsed=true};g.collapseToStart=function(){if(this.rangeCount){var b=this._ranges[0];this.collapse(b.startContainer,b.startOffset)}else throw new L("INVALID_STATE_ERR");};g.collapseToEnd=function(){if(this.rangeCount){var b=this._ranges[this.rangeCount-1];this.collapse(b.endContainer,b.endOffset)}else throw new L("INVALID_STATE_ERR");
-};g.selectAllChildren=function(b){q(this,b);var d=l.createRange(c.getDocument(b));d.selectNodeContents(b);this.removeAllRanges();this.addRange(d)};g.deleteFromDocument=function(){if(M&&V&&this.docSelection.type=="Control"){for(var b=this.docSelection.createRange(),d;b.length;){d=b.item(0);b.remove(d);d.parentNode.removeChild(d)}this.refresh()}else if(this.rangeCount){b=this.getAllRanges();this.removeAllRanges();d=0;for(var h=b.length;d<h;++d)b[d].deleteContents();this.addRange(b[h-1])}};g.getAllRanges=
-function(){return this._ranges.slice(0)};g.setSingleRange=function(b){this.setRanges([b])};g.containsNode=function(b,d){for(var h=0,D=this._ranges.length;h<D;++h)if(this._ranges[h].containsNode(b,d))return true;return false};g.toHtml=function(){var b="";if(this.rangeCount){b=k.getRangeDocument(this._ranges[0]).createElement("div");for(var d=0,h=this._ranges.length;d<h;++d)b.appendChild(this._ranges[d].cloneContents());b=b.innerHTML}return b};g.getName=function(){return"WrappedSelection"};g.inspect=
-function(){return v(this)};g.detach=function(){this.win=this.anchorNode=this.focusNode=this.win._rangySelection=null};x.inspect=v;l.Selection=x;l.selectionPrototype=g;l.addCreateMissingNativeApiListener(function(b){if(typeof b.getSelection=="undefined")b.getSelection=function(){return l.getSelection(this)};b=null})});/*
- Base.js, version 1.1a
- Copyright 2006-2010, Dean Edwards
- License: http://www.opensource.org/licenses/mit-license.php
-*/
var Base = function() {
- // dummy
+ // dummy
};
Base.extend = function(_instance, _static) { // subclass
- var extend = Base.prototype.extend;
-
- // build the prototype
- Base._prototyping = true;
- var proto = new this;
- extend.call(proto, _instance);
+ var extend = Base.prototype.extend;
+
+ // build the prototype
+ Base._prototyping = true;
+ var proto = new this;
+ extend.call(proto, _instance);
proto.base = function() {
// call this method from any other method to invoke that method's ancestor
};
- delete Base._prototyping;
-
- // create the wrapper for the constructor function
- //var constructor = proto.constructor.valueOf(); //-dean
- var constructor = proto.constructor;
- var klass = proto.constructor = function() {
- if (!Base._prototyping) {
- if (this._constructing || this.constructor == klass) { // instantiation
- this._constructing = true;
- constructor.apply(this, arguments);
- delete this._constructing;
- } else if (arguments[0] != null) { // casting
- return (arguments[0].extend || extend).call(arguments[0], proto);
- }
- }
- };
-
- // build the class interface
- klass.ancestor = this;
- klass.extend = this.extend;
- klass.forEach = this.forEach;
- klass.implement = this.implement;
- klass.prototype = proto;
- klass.toString = this.toString;
- klass.valueOf = function(type) {
- //return (type == "object") ? klass : constructor; //-dean
- return (type == "object") ? klass : constructor.valueOf();
- };
- extend.call(klass, _static);
- // class initialisation
- if (typeof klass.init == "function") klass.init();
- return klass;
+ delete Base._prototyping;
+
+ // create the wrapper for the constructor function
+ //var constructor = proto.constructor.valueOf(); //-dean
+ var constructor = proto.constructor;
+ var klass = proto.constructor = function() {
+ if (!Base._prototyping) {
+ if (this._constructing || this.constructor == klass) { // instantiation
+ this._constructing = true;
+ constructor.apply(this, arguments);
+ delete this._constructing;
+ } else if (arguments[0] != null) { // casting
+ return (arguments[0].extend || extend).call(arguments[0], proto);
+ }
+ }
+ };
+
+ // build the class interface
+ klass.ancestor = this;
+ klass.extend = this.extend;
+ klass.forEach = this.forEach;
+ klass.implement = this.implement;
+ klass.prototype = proto;
+ klass.toString = this.toString;
+ klass.valueOf = function(type) {
+ //return (type == "object") ? klass : constructor; //-dean
+ return (type == "object") ? klass : constructor.valueOf();
+ };
+ extend.call(klass, _static);
+ // class initialisation
+ if (typeof klass.init == "function") klass.init();
+ return klass;
};
-Base.prototype = {
- extend: function(source, value) {
- if (arguments.length > 1) { // extending with a name/value pair
- var ancestor = this[source];
- if (ancestor && (typeof value == "function") && // overriding a method?
- // the valueOf() comparison is to avoid circular references
- (!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
- /\bbase\b/.test(value)) {
- // get the underlying method
- var method = value.valueOf();
- // override
- value = function() {
- var previous = this.base || Base.prototype.base;
- this.base = ancestor;
- var returnValue = method.apply(this, arguments);
- this.base = previous;
- return returnValue;
- };
- // point to the underlying method
- value.valueOf = function(type) {
- return (type == "object") ? value : method;
- };
- value.toString = Base.toString;
- }
- this[source] = value;
- } else if (source) { // extending with an object literal
- var extend = Base.prototype.extend;
- // if this object has a customised extend method then use it
- if (!Base._prototyping && typeof this != "function") {
- extend = this.extend || extend;
- }
- var proto = {toSource: null};
- // do the "toString" and other methods manually
- var hidden = ["constructor", "toString", "valueOf"];
- // if we are prototyping then include the constructor
- var i = Base._prototyping ? 0 : 1;
- while (key = hidden[i++]) {
- if (source[key] != proto[key]) {
- extend.call(this, key, source[key]);
+Base.prototype = {
+ extend: function(source, value) {
+ if (arguments.length > 1) { // extending with a name/value pair
+ var ancestor = this[source];
+ if (ancestor && (typeof value == "function") && // overriding a method?
+ // the valueOf() comparison is to avoid circular references
+ (!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
+ /\bbase\b/.test(value)) {
+ // get the underlying method
+ var method = value.valueOf();
+ // override
+ value = function() {
+ var previous = this.base || Base.prototype.base;
+ this.base = ancestor;
+ var returnValue = method.apply(this, arguments);
+ this.base = previous;
+ return returnValue;
+ };
+ // point to the underlying method
+ value.valueOf = function(type) {
+ return (type == "object") ? value : method;
+ };
+ value.toString = Base.toString;
+ }
+ this[source] = value;
+ } else if (source) { // extending with an object literal
+ var extend = Base.prototype.extend;
+ // if this object has a customised extend method then use it
+ if (!Base._prototyping && typeof this != "function") {
+ extend = this.extend || extend;
+ }
+ var proto = {toSource: null};
+ // do the "toString" and other methods manually
+ var hidden = ["constructor", "toString", "valueOf"];
+ // if we are prototyping then include the constructor
+ var i = Base._prototyping ? 0 : 1;
+ while (key = hidden[i++]) {
+ if (source[key] != proto[key]) {
+ extend.call(this, key, source[key]);
- }
- }
- // copy each of the source object's properties to this object
- for (var key in source) {
- if (!proto[key]) extend.call(this, key, source[key]);
- }
- }
- return this;
- }
+ }
+ }
+ // copy each of the source object's properties to this object
+ for (var key in source) {
+ if (!proto[key]) extend.call(this, key, source[key]);
+ }
+ }
+ return this;
+ }
};
// initialise
Base = Base.extend({
- constructor: function() {
- this.extend(arguments[0]);
- }
+ constructor: function() {
+ this.extend(arguments[0]);
+ }
}, {
- ancestor: Object,
- version: "1.1",
-
- forEach: function(object, block, context) {
- for (var key in object) {
- if (this.prototype[key] === undefined) {
- block.call(context, object[key], key, object);
- }
- }
- },
-
- implement: function() {
- for (var i = 0; i < arguments.length; i++) {
- if (typeof arguments[i] == "function") {
- // if it's a function, call it
- arguments[i](this.prototype);
- } else {
- // add the interface using the extend method
- this.prototype.extend(arguments[i]);
- }
- }
- return this;
- },
-
- toString: function() {
- return String(this.valueOf());
- }
-});/**
+ ancestor: Object,
+ version: "1.1",
+
+ forEach: function(object, block, context) {
+ for (var key in object) {
+ if (this.prototype[key] === undefined) {
+ block.call(context, object[key], key, object);
+ }
+ }
+ },
+
+ implement: function() {
+ for (var i = 0; i < arguments.length; i++) {
+ if (typeof arguments[i] == "function") {
+ // if it's a function, call it
+ arguments[i](this.prototype);
+ } else {
+ // add the interface using the extend method
+ this.prototype.extend(arguments[i]);
+ }
+ }
+ return this;
+ },
+
+ toString: function() {
+ return String(this.valueOf());
+ }
+});;/**
* Detect browser support for specific features
*/
wysihtml5.browser = (function() {
var userAgent = navigator.userAgent,
testElement = document.createElement("div"),
@@ -543,15 +4442,10 @@
*/
supportsSelectionModify: function() {
return "getSelection" in window && "modify" in window.getSelection();
},
- // Returns if there is a way for setting selection to expand a line
- supportsSelectLine: function () {
- return (this.supportsSelectionModify() || document.selection) ? true : false;
- },
-
/**
* Opera needs a white space after a <br> in order to position the caret correctly
*/
needsSpaceAfterLineBreak: function() {
return isOpera;
@@ -631,11 +4525,11 @@
supportsMutationEvents: function() {
return ("MutationEvent" in window);
}
};
})();
-wysihtml5.lang.array = function(arr) {
+;wysihtml5.lang.array = function(arr) {
return {
/**
* Check whether a given object exists in an array
*
* @example
@@ -725,11 +4619,11 @@
return A;
}
}
};
};
-wysihtml5.lang.Dispatcher = Base.extend(
+;wysihtml5.lang.Dispatcher = Base.extend(
/** @scope wysihtml5.lang.Dialog.prototype */ {
on: function(eventName, handler) {
this.events = this.events || {};
this.events[eventName] = this.events[eventName] || [];
this.events[eventName].push(handler);
@@ -775,11 +4669,11 @@
// deprecated, use .off()
stopObserving: function() {
return this.off.apply(this, arguments);
}
});
-wysihtml5.lang.object = function(obj) {
+;wysihtml5.lang.object = function(obj) {
return {
/**
* @example
* wysihtml5.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get();
* // => { foo: 1, bar: 2, baz: 3 }
@@ -817,11 +4711,11 @@
isArray: function() {
return Object.prototype.toString.call(obj) === "[object Array]";
}
};
};
-(function() {
+;(function() {
var WHITE_SPACE_START = /^\s+/,
WHITE_SPACE_END = /\s+$/,
ENTITY_REG_EXP = /[&<>"]/g,
ENTITY_MAP = {
'&': '&',
@@ -875,11 +4769,11 @@
return str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
}
};
};
})();
-/**
+;/**
* Find urls in descendant text nodes of an element and auto-links them
* Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
*
* @param {Element} element Container element in which to search for urls
*
@@ -1017,11 +4911,11 @@
wysihtml5.dom.autoLink = autoLink;
// Reveal url reg exp to the outside
wysihtml5.dom.autoLink.URL_REG_EXP = URL_REG_EXP;
})(wysihtml5);
-(function(wysihtml5) {
+;(function(wysihtml5) {
var api = wysihtml5.dom;
api.addClass = function(element, className) {
var classList = element.classList;
if (classList) {
@@ -1050,11 +4944,11 @@
var elementClassName = element.className;
return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
};
})(wysihtml5);
-wysihtml5.dom.contains = (function() {
+;wysihtml5.dom.contains = (function() {
var documentElement = document.documentElement;
if (documentElement.contains) {
return function(container, element) {
if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
element = element.parentNode;
@@ -1066,11 +4960,11 @@
// https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition
return !!(container.compareDocumentPosition(element) & 16);
};
}
})();
-/**
+;/**
* Converts an HTML fragment/element into a unordered/ordered list
*
* @param {Element} element The element which should be turned into a list
* @param {String} listType The list type in which to convert the tree (either "ul" or "ol")
* @return {Element} The created list
@@ -1172,11 +5066,11 @@
return list;
}
return convertToList;
})();
-/**
+;/**
* Copy a set of attributes from one element to another
*
* @param {Array} attributesToCopy List of attributes which should be copied
* @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
* copy the attributes from., this again returns an object which provides a method named "to" which can be invoked
@@ -1207,11 +5101,11 @@
}
};
}
};
};
-/**
+;/**
* Copy a set of styles from one element to another
* Please note that this only works properly across browsers when the element from which to copy the styles
* is in the dom
*
* Interesting article on how to copy styles
@@ -1280,11 +5174,11 @@
};
}
};
};
})(wysihtml5.dom);
-/**
+;/**
* Event Delegation
*
* @example
* wysihtml5.dom.delegate(document.body, "a", "click", function() {
* // foo
@@ -1306,11 +5200,11 @@
}
});
};
})(wysihtml5);
-/**
+;/**
* Returns the given html wrapped in a div element
*
* Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly
* when inserted via innerHTML
*
@@ -1370,11 +5264,11 @@
tempElement = _innerHTMLShiv(html, context);
}
return tempElement;
};
})();
-/**
+;/**
* Walks the dom tree from the given node up until it finds a match
* Designed for optimal performance.
*
* @param {Element} node The from which to check the parent nodes
* @param {Object} matchingSet Object to match against (possible properties: nodeName, className, classRegExp)
@@ -1411,10 +5305,18 @@
return !!classNames.length;
}
return classNames[classNames.length - 1] === className;
}
+ function _hasStyle(element, cssStyle, styleRegExp) {
+ var styles = (element.getAttribute('style') || "").match(styleRegExp) || [];
+ if (!cssStyle) {
+ return !!styles.length;
+ }
+ return styles[styles.length - 1] === cssStyle;
+ }
+
function _getParentElementWithNodeName(node, nodeName, levels) {
while (levels-- && node && node.nodeName !== "BODY") {
if (_isSameNodeName(node.nodeName, nodeName)) {
return node;
}
@@ -1433,24 +5335,71 @@
node = node.parentNode;
}
return null;
}
+ function _getParentElementWithNodeNameAndStyle(node, nodeName, cssStyle, styleRegExp, levels) {
+ while (levels-- && node && node.nodeName !== "BODY") {
+ if (_isElement(node) &&
+ _isSameNodeName(node.nodeName, nodeName) &&
+ _hasStyle(node, cssStyle, styleRegExp)
+ ) {
+ return node;
+ }
+ node = node.parentNode;
+ }
+ return null;
+ }
+
+ function _getParentElementWithNodeNameAndClassNameAndStyle(node, nodeName, className, classRegExp, cssStyle, styleRegExp, levels) {
+ while (levels-- && node && node.nodeName !== "BODY") {
+ if (_isElement(node) &&
+ _isSameNodeName(node.nodeName, nodeName) &&
+ _hasStyle(node, cssStyle, styleRegExp) &&
+ _hasClassName(node, className, classRegExp)
+ ) {
+ return node;
+ }
+ node = node.parentNode;
+ }
+ return null;
+ }
+
return function(node, matchingSet, levels) {
levels = levels || 50; // Go max 50 nodes upwards from current node
- if (matchingSet.className || matchingSet.classRegExp) {
+ if ((matchingSet.className || matchingSet.classRegExp) && (matchingSet.cssStyle || matchingSet.styleRegExp)) {
+ return _getParentElementWithNodeNameAndClassNameAndStyle(
+ node, matchingSet.nodeName, matchingSet.className, matchingSet.classRegExp, matchingSet.cssStyle, matchingSet.styleRegExp, levels
+ );
+ } else if (matchingSet.className || matchingSet.classRegExp) {
return _getParentElementWithNodeNameAndClassName(
node, matchingSet.nodeName, matchingSet.className, matchingSet.classRegExp, levels
);
+ } else if (matchingSet.cssStyle || matchingSet.styleRegExp) {
+ return _getParentElementWithNodeNameAndStyle(
+ node, matchingSet.nodeName, matchingSet.cssStyle, matchingSet.styleRegExp, levels
+ );
} else {
return _getParentElementWithNodeName(
node, matchingSet.nodeName, levels
);
}
};
})();
-/**
+;wysihtml5.dom.getNextElement = function(node){
+ var nextSibling = node.nextSibling;
+ while(nextSibling && nextSibling.nodeType != 1) {
+ nextSibling = nextSibling.nextSibling;
+ }
+ return nextSibling;
+};;wysihtml5.dom.getPreviousElement = function(node){
+ var nextSibling = node.previousSibling;
+ while(nextSibling && nextSibling.nodeType != 1) {
+ nextSibling = nextSibling.previousSibling;
+ }
+ return nextSibling;
+};;/**
* Get element's style for a specific css property
*
* @param {Element} element The element on which to retrieve the style
* @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...)
*
@@ -1519,11 +5468,21 @@
}
}
};
};
})();
-/**
+;wysihtml5.dom.getTextNodes = function(node){
+ var all = [];
+ for (node=node.firstChild;node;node=node.nextSibling){
+ if (node.nodeType==3) {
+ all.push(node);
+ } else {
+ all = all.concat(wysihtml5.dom.getTextNodes(node));
+ }
+ }
+ return all;
+};;/**
* High performant way to check whether an element with a specific tag name is in the given document
* Optimized for being heavily executed
* Unleashes the power of live node lists
*
* @param {Object} doc The document object of the context where to check
@@ -1547,11 +5506,11 @@
}
return cacheEntry.length > 0;
};
})();
-/**
+;/**
* High performant way to check whether an element with a specific class name is in the given document
* Optimized for being heavily executed
* Unleashes the power of live node lists
*
* @param {Object} doc The document object of the context where to check
@@ -1581,11 +5540,11 @@
}
return cacheEntry.length > 0;
};
})(wysihtml5);
-wysihtml5.dom.insert = function(elementToInsert) {
+;wysihtml5.dom.insert = function(elementToInsert) {
return {
after: function(element) {
element.parentNode.insertBefore(elementToInsert, element.nextSibling);
},
@@ -1596,11 +5555,11 @@
into: function(element) {
element.appendChild(elementToInsert);
}
};
};
-wysihtml5.dom.insertCSS = function(rules) {
+;wysihtml5.dom.insertCSS = function(rules) {
rules = rules.join("\n");
return {
into: function(doc) {
var styleElement = doc.createElement("style");
@@ -1623,11 +5582,11 @@
}
}
}
};
};
-/**
+;/**
* Method to set dom events
*
* @example
* wysihtml5.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... });
*/
@@ -1674,11 +5633,11 @@
}
}
}
};
};
-/**
+;/**
* HTML Sanitizer
* Rewrites the HTML based on given rules
*
* @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized
* @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will
@@ -2201,11 +6160,11 @@
var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
function _handleText(oldNode) {
var nextSibling = oldNode.nextSibling;
if (nextSibling && nextSibling.nodeType === wysihtml5.TEXT_NODE) {
// Concatenate text nodes
- nextSibling.data = oldNode.data + nextSibling.data;
+ nextSibling.data = oldNode.data.replace(INVISIBLE_SPACE_REG_EXP, "") + nextSibling.data.replace(INVISIBLE_SPACE_REG_EXP, "");
} else {
// \uFEFF = wysihtml5.INVISIBLE_SPACE (used as a hack in certain rich text editing situations)
var data = oldNode.data.replace(INVISIBLE_SPACE_REG_EXP, "");
return oldNode.ownerDocument.createTextNode(data);
}
@@ -2237,11 +6196,11 @@
});
};
})(),
href: (function() {
- var REG_EXP = /^(\/|https?:\/\/|mailto:)/i;
+ var REG_EXP = /^(#|\/|https?:\/\/|mailto:)/i;
return function(attributeValue) {
if (!attributeValue || !attributeValue.match(REG_EXP)) {
return null;
}
return attributeValue.replace(REG_EXP, function(match) {
@@ -2329,11 +6288,11 @@
})()
};
return parse;
})();
-/**
+;/**
* Checks for empty text node childs and removes them
*
* @param {Element} node The element in which to cleanup
* @example
* wysihtml5.dom.removeEmptyTextNodes(element);
@@ -2348,11 +6307,11 @@
if (childNode.nodeType === wysihtml5.TEXT_NODE && childNode.data === "") {
childNode.parentNode.removeChild(childNode);
}
}
};
-/**
+;/**
* Renames an element (eg. a <div> to a <p>) and keeps its childs
*
* @param {Element} element The list element which should be renamed
* @param {Element} newNodeName The desired tag name
*
@@ -2383,11 +6342,11 @@
}
wysihtml5.dom.copyAttributes(["align", "className"]).from(element).to(newElement);
element.parentNode.replaceChild(newElement, element);
return newElement;
};
-/**
+;/**
* Takes an element, removes it and replaces it with it's childs
*
* @param {Object} node The node which to replace with it's child nodes
* @example
* <div id="foo">
@@ -2413,11 +6372,11 @@
fragment.appendChild(node.firstChild);
}
node.parentNode.replaceChild(fragment, node);
node = fragment = null;
};
-/**
+;/**
* Unwraps an unordered/ordered list
*
* @param {Element} element The list element which should be unwrapped
*
* @example
@@ -2506,11 +6465,11 @@
list.parentNode.replaceChild(fragment, list);
}
dom.resolveList = resolveList;
})(wysihtml5.dom);
-/**
+;/**
* Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way
*
* Browser Compatibility:
* - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted"
* - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...)
@@ -2758,11 +6717,11 @@
} catch(e) {}
}
}
});
})(wysihtml5);
-(function(wysihtml5) {
+;(function(wysihtml5) {
var doc = document;
wysihtml5.dom.ContentEditableArea = Base.extend({
getContentEditable: function() {
return this.element;
},
@@ -2827,11 +6786,11 @@
return '';
}
});
})(wysihtml5);
-(function() {
+;(function() {
var mapping = {
"className": "class"
};
wysihtml5.dom.setAttributes = function(attributes) {
return {
@@ -2841,11 +6800,11 @@
}
}
};
};
})();
-wysihtml5.dom.setStyles = function(styles) {
+;wysihtml5.dom.setStyles = function(styles) {
return {
on: function(element) {
var style = element.style;
if (typeof(styles) === "string") {
style.cssText += ";" + styles;
@@ -2860,11 +6819,11 @@
}
}
}
};
};
-/**
+;/**
* Simulate HTML5 placeholder attribute
*
* Needed since
* - div[contentEditable] elements don't support it
* - older browsers (such as IE8 and Firefox 3.6) don't support it at all
@@ -2902,11 +6861,11 @@
.on("blur:composer", set);
set();
};
})(wysihtml5.dom);
-(function(dom) {
+;(function(dom) {
var documentElement = document.documentElement;
if ("textContent" in documentElement) {
dom.setTextContent = function(element, text) {
element.textContent = text;
};
@@ -2931,11 +6890,11 @@
return element.nodeValue;
};
}
})(wysihtml5.dom);
-/**
+;/**
* Get a set of attribute from one element
*
* IE gives wrong results for hasAttribute/getAttribute, for example:
* var td = document.createElement("td");
* td.getAttribute("rowspan"); // => "1" in IE
@@ -2962,11 +6921,11 @@
return hasAttribute ? node.getAttribute(attributeName) : null;
} else{
return node.getAttribute(attributeName);
}
};
-(function(wysihtml5) {
+;(function(wysihtml5) {
var api = wysihtml5.dom;
var MapCell = function(cell) {
this.el = cell;
@@ -3840,11 +7799,11 @@
};
})(wysihtml5);
-// does a selector query on element or array of elements
+;// does a selector query on element or array of elements
wysihtml5.dom.query = function(elements, query) {
var ret = [],
q;
@@ -3858,11 +7817,75 @@
for(var i = q.length; i--; ret.unshift(q[i]));
}
}
return ret;
};
-/**
+;wysihtml5.dom.compareDocumentPosition = (function() {
+ var documentElement = document.documentElement;
+ if (documentElement.compareDocumentPosition) {
+ return function(container, element) {
+ return container.compareDocumentPosition(element);
+ };
+ } else {
+ return function( container, element ) {
+ // implementation borrowed from https://github.com/tmpvar/jsdom/blob/681a8524b663281a0f58348c6129c8c184efc62c/lib/jsdom/level3/core.js // MIT license
+ var thisOwner, otherOwner;
+
+ if( container.nodeType === 9) // Node.DOCUMENT_NODE
+ thisOwner = container;
+ else
+ thisOwner = container.ownerDocument;
+
+ if( element.nodeType === 9) // Node.DOCUMENT_NODE
+ otherOwner = element;
+ else
+ otherOwner = element.ownerDocument;
+
+ if( container === element ) return 0;
+ if( container === element.ownerDocument ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
+ if( container.ownerDocument === element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
+ if( thisOwner !== otherOwner ) return 1; // Node.DOCUMENT_POSITION_DISCONNECTED;
+
+ // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child.
+ if( container.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && container.childNodes && wysihtml5.lang.array(container.childNodes).indexOf( element ) !== -1)
+ return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
+
+ if( element.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && element.childNodes && wysihtml5.lang.array(element.childNodes).indexOf( container ) !== -1)
+ return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
+
+ var point = container;
+ var parents = [ ];
+ var previous = null;
+ while( point ) {
+ if( point == element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
+ parents.push( point );
+ point = point.parentNode;
+ }
+ point = element;
+ previous = null;
+ while( point ) {
+ if( point == container ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
+ var location_index = wysihtml5.lang.array(parents).indexOf( point );
+ if( location_index !== -1) {
+ var smallest_common_ancestor = parents[ location_index ];
+ var this_index = wysihtml5.lang.array(smallest_common_ancestor.childNodes).indexOf( parents[location_index - 1]);//smallest_common_ancestor.childNodes.toArray().indexOf( parents[location_index - 1] );
+ var other_index = wysihtml5.lang.array(smallest_common_ancestor.childNodes).indexOf( previous ); //smallest_common_ancestor.childNodes.toArray().indexOf( previous );
+ if( this_index > other_index ) {
+ return 2; //Node.DOCUMENT_POSITION_PRECEDING;
+ }
+ else {
+ return 4; //Node.DOCUMENT_POSITION_FOLLOWING;
+ }
+ }
+ previous = point;
+ point = point.parentNode;
+ }
+ return 1; //Node.DOCUMENT_POSITION_DISCONNECTED;
+ };
+ }
+})();
+;/**
* Fix most common html formatting misbehaviors of browsers implementation when inserting
* content via copy & paste contentEditable
*
* @author Christopher Blum
*/
@@ -3881,11 +7904,11 @@
isString = typeof(elementOrHtml) === "string",
method,
matches,
matchesLength,
i,
- j = 0;
+ j = 0, n;
if (isString) {
element = wysihtml5.dom.getAsDom(elementOrHtml, context);
} else {
element = elementOrHtml;
}
@@ -3897,18 +7920,24 @@
for (; j<matchesLength; j++) {
method(matches[j]);
}
}
+ // replace joined non-breakable spaces with unjoined
+ var txtnodes = wysihtml5.dom.getTextNodes(element);
+ for (n = txtnodes.length; n--;) {
+ txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
+ }
+
matches = elementOrHtml = rules = null;
return isString ? element.innerHTML : element;
}
return cleanPastedHTML;
})();
-/**
+;/**
* IE and Opera leave an empty paragraph in the contentEditable element after clearing it
*
* @param {Object} contentEditableElement The contentEditable element to observe for clearing events
* @exaple
* wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
@@ -3927,11 +7956,11 @@
return function(composer) {
wysihtml5.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
};
})();
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
+;// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
//
// In Firefox this:
// var d = document.createElement("div");
// d.innerHTML ='<a href="~"></a>';
// d.innerHTML;
@@ -3957,11 +7986,11 @@
innerHTML = wysihtml5.lang.string(innerHTML).replace(urlToSearch).by(url);
}
return innerHTML;
};
})(wysihtml5);
-/**
+;/**
* Force rerendering of a given element
* Needed to fix display misbehaviors of IE
*
* @param {Element} element The element object which needs to be rerendered
* @example
@@ -3980,11 +8009,11 @@
doc.execCommand("italic", false, null);
doc.execCommand("italic", false, null);
} catch(e) {}
};
})(wysihtml5);
-wysihtml5.quirks.tableCellsSelection = function(editable, editor) {
+;wysihtml5.quirks.tableCellsSelection = function(editable, editor) {
var dom = wysihtml5.dom,
select = {
table: null,
start: null,
@@ -4017,10 +8046,11 @@
if (select.table) {
removeCellSelections();
dom.addClass(target, selection_class);
moveHandler = dom.observe(editable, "mousemove", handleMouseMove);
upHandler = dom.observe(editable, "mouseup", handleMouseUp);
+ editor.fire("tableselectstart").fire("tableselectstart:composer");
}
}
// remove all selection classes
function removeCellSelections () {
@@ -4040,22 +8070,27 @@
}
}
function handleMouseMove (event) {
var curTable = null,
- cell = dom.getParentElement(event.target, { nodeName: ["TD","TH"] });
+ cell = dom.getParentElement(event.target, { nodeName: ["TD","TH"] }),
+ oldEnd;
if (cell && select.table && select.start) {
curTable = dom.getParentElement(cell, { nodeName: ["TABLE"] });
if (curTable && curTable === select.table) {
removeCellSelections();
+ oldEnd = select.end;
select.end = cell;
select.cells = dom.table.getCellsBetween(select.start, cell);
if (select.cells.length > 1) {
editor.composer.selection.deselect();
}
addSelections(select.cells);
+ if (select.end !== oldEnd) {
+ editor.fire("tableselectchange").fire("tableselectchange:composer");
+ }
}
}
}
function handleMouseUp (event) {
@@ -4091,11 +8126,11 @@
}
return init();
};
-(function(wysihtml5) {
+;(function(wysihtml5) {
var RGBA_REGEX = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i,
RGB_REGEX = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,
HEX6_REGEX = /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i,
HEX3_REGEX = /^#([0-9a-f])([0-9a-f])([0-9a-f])/i;
@@ -4176,11 +8211,11 @@
return false;
}
};
})(wysihtml5);
-/**
+;/**
* Selection API
*
* @example
* var selection = new wysihtml5.Selection(editor);
*/
@@ -4407,18 +8442,51 @@
}
return (ret !== this.contain) ? ret : false;
},
+ getRangeToNodeEnd: function() {
+ if (this.isCollapsed()) {
+ var range = this.getRange(),
+ sNode = range.startContainer,
+ pos = range.startOffset,
+ lastR = rangy.createRange(this.doc);
+ lastR.selectNodeContents(sNode);
+ lastR.setStart(sNode, pos);
+ return lastR;
+ }
+ },
- caretIsInTheBeginnig: function() {
+ caretIsLastInSelection: function() {
+ var r = rangy.createRange(this.doc),
+ s = this.getSelection(),
+ endc = this.getRangeToNodeEnd().cloneContents(),
+ endtxt = endc.textContent;
+
+ return (/^\s*$/).test(endtxt);
+ },
+
+ caretIsFirstInSelection: function() {
+ var r = rangy.createRange(this.doc),
+ s = this.getSelection();
+
+ r.selectNodeContents(this.getRange().commonAncestorContainer);
+ r.collapse(true);
+
+ return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
+ },
+
+ caretIsInTheBeginnig: function(ofNode) {
var selection = this.getSelection(),
node = selection.anchorNode,
offset = selection.anchorOffset;
-
- return (offset === 0 && !this.getPreviousNode(node, true));
+ if (ofNode) {
+ return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml5.dom.getParentElement(node.parentNode, ofNode, 1)));
+ } else {
+ return (offset === 0 && !this.getPreviousNode(node, true));
+ }
},
caretIsBeforeUneditable: function() {
var selection = this.getSelection(),
node = selection.anchorNode,
@@ -4436,10 +8504,27 @@
}
}
return false;
},
+ // TODO: Figure out a method from following 3 that would work universally
+ executeAndRestoreRangy: function(method, restoreScrollPosition) {
+ var win = this.doc.defaultView || this.doc.parentWindow,
+ sel = rangy.saveSelection(win);
+
+ if (!sel) {
+ method();
+ } else {
+ try {
+ method();
+ } catch(e) {
+ setTimeout(function() { throw e; }, 0);
+ }
+ }
+ rangy.restoreSelection(sel);
+ },
+
// TODO: has problems in chrome 12. investigate block level and uneditable area inbetween
executeAndRestore: function(method, restoreScrollPosition) {
var body = this.doc.body,
oldScrollTop = restoreScrollPosition && body.scrollTop,
oldScrollLeft = restoreScrollPosition && body.scrollLeft,
@@ -4492,12 +8577,12 @@
if (prevSibling && nextSibling) {
newRange.setStartBefore(nextSibling);
newRange.setEndAfter(prevSibling);
} else {
newCaretPlaceholder = this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
- dom.insert(newCaretPlaceholder).before(caretPlaceholder[0]);
- newRange.setStartAfter(newCaretPlaceholder);
+ dom.insert(newCaretPlaceholder).after(caretPlaceholder[0]);
+ newRange.setStartBefore(newCaretPlaceholder);
newRange.setEndAfter(newCaretPlaceholder);
}
this.setSelection(newRange);
for (var i = caretPlaceholder.length; i--;) {
caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]);
@@ -4517,53 +8602,10 @@
try {
caretPlaceholder.parentNode.removeChild(caretPlaceholder);
} catch(e2) {}
},
- /**
- * Different approach of preserving the selection (doesn't modify the dom)
- * Takes all text nodes in the selection and saves the selection position in the first and last one
- */
- executeAndRestoreSimple: function(method) {
- var range = this.getRange(),
- body = this.doc.body,
- newRange,
- firstNode,
- lastNode,
- textNodes,
- rangeBackup;
-
- // Nothing selected, execute and say goodbye
- if (!range) {
- method(body, body);
- return;
- }
-
- textNodes = range.getNodes([3]);
- firstNode = textNodes[0] || range.startContainer;
- lastNode = textNodes[textNodes.length - 1] || range.endContainer;
-
- rangeBackup = {
- collapsed: range.collapsed,
- startContainer: firstNode,
- startOffset: firstNode === range.startContainer ? range.startOffset : 0,
- endContainer: lastNode,
- endOffset: lastNode === range.endContainer ? range.endOffset : lastNode.length
- };
-
- try {
- method(range.startContainer, range.endContainer);
- } catch(e) {
- setTimeout(function() { throw e; }, 0);
- }
-
- newRange = rangy.createRange(this.doc);
- try { newRange.setStart(rangeBackup.startContainer, rangeBackup.startOffset); } catch(e1) {}
- try { newRange.setEnd(rangeBackup.endContainer, rangeBackup.endOffset); } catch(e2) {}
- try { this.setSelection(newRange); } catch(e3) {}
- },
-
set: function(node, offset) {
var newRange = rangy.createRange(this.doc);
newRange.setStart(node, offset || 0);
this.setSelection(newRange);
},
@@ -4616,10 +8658,13 @@
node = this.doc.createElement(nodeOptions.nodeName);
nodes.push(node);
if (nodeOptions.className) {
node.className = nodeOptions.className;
}
+ if (nodeOptions.cssStyle) {
+ node.setAttribute('style', nodeOptions.cssStyle);
+ }
try {
// This only works when the range boundaries are not overlapping other elements
ranges[i].surroundContents(node);
this.selectNode(node);
} catch(e) {
@@ -4807,14 +8852,14 @@
range.selectNodeContents(node)
return range.endOffset;
},
_detectInlineRangeProblems: function(range) {
- position = range.startContainer.compareDocumentPosition(range.endContainer);
+ position = dom.compareDocumentPosition(range.startContainer, range.endContainer);
return (
range.endOffset == 0 &&
- position & Node.DOCUMENT_POSITION_FOLLOWING
+ position & 4 //Node.DOCUMENT_POSITION_FOLLOWING
);
},
getRange: function(dontFix) {
var selection = this.getSelection(),
@@ -4902,11 +8947,11 @@
sel && sel.removeAllRanges();
}
});
})(wysihtml5);
-/**
+;/**
* Inspired by the rangy CSS Applier module written by Tim Down and licensed under the MIT license.
* http://code.google.com/p/rangy/
*
* changed in order to be able ...
* - to use custom tags
@@ -5474,11 +9519,11 @@
};
wysihtml5.selection.HTMLApplier = HTMLApplier;
})(wysihtml5, rangy);
-/**
+;/**
* Rich Text Query/Formatting Commands
*
* @example
* var commands = new wysihtml5.Commands(editor);
*/
@@ -5569,11 +9614,11 @@
} else {
return false;
}
}
});
-wysihtml5.commands.bold = {
+;wysihtml5.commands.bold = {
exec: function(composer, command) {
wysihtml5.commands.formatInline.execWithToggle(composer, command, "b");
},
state: function(composer, command) {
@@ -5584,11 +9629,11 @@
// opera: <b>, <strong>
return wysihtml5.commands.formatInline.state(composer, command, "b");
}
};
-(function(wysihtml5) {
+;(function(wysihtml5) {
var undef,
NODE_NAME = "A",
dom = wysihtml5.dom;
function _format(composer, attributes) {
@@ -5603,11 +9648,11 @@
isEmpty,
elementToSetCaretAfter,
textContent,
whiteSpace,
j;
- wysihtml5.commands.formatInline.exec(composer, undef, NODE_NAME, tempClass, tempClassRegExp, undef, undef, undef, true);
+ wysihtml5.commands.formatInline.exec(composer, undef, NODE_NAME, tempClass, tempClassRegExp, undef, undef, true, true);
anchors = doc.querySelectorAll(NODE_NAME + "." + tempClass);
length = anchors.length;
for (; i<length; i++) {
anchor = anchors[i];
anchor.removeAttribute("class");
@@ -5686,11 +9731,11 @@
state: function(composer, command) {
return wysihtml5.commands.formatInline.state(composer, command, "A");
}
};
})(wysihtml5);
-(function(wysihtml5) {
+;(function(wysihtml5) {
var dom = wysihtml5.dom;
function _removeFormat(composer, anchors) {
var length = anchors.length,
i = 0,
@@ -5734,11 +9779,11 @@
state: function(composer, command) {
return wysihtml5.commands.formatInline.state(composer, command, "A");
}
};
})(wysihtml5);
-/**
+;/**
* document.execCommand("fontSize") will create either inline styles (firefox, chrome) or use font tags
* which we don't want
* Instead we set a css class
*/
(function(wysihtml5) {
@@ -5752,11 +9797,11 @@
state: function(composer, command, size) {
return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
}
};
})(wysihtml5);
-/* In case font size adjustment to any number defined by user is preferred, we cannot use classes and must use inline styles. */
+;/* In case font size adjustment to any number defined by user is preferred, we cannot use classes and must use inline styles. */
(function(wysihtml5) {
var REG_EXP = /(\s|^)font-size\s*:\s*[^;\s]+;?/gi;
wysihtml5.commands.fontSizeStyle = {
exec: function(composer, command, size) {
@@ -5786,11 +9831,11 @@
}
return false;
}
};
})(wysihtml5);
-/**
+;/**
* document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
* which we don't want
* Instead we set a css class
*/
(function(wysihtml5) {
@@ -5804,11 +9849,11 @@
state: function(composer, command, color) {
return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
}
};
})(wysihtml5);
-/**
+;/**
* document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
* which we don't want
* Instead we set a css class
*/
(function(wysihtml5) {
@@ -5852,11 +9897,11 @@
return false;
}
};
})(wysihtml5);
-/* In case background adjustment to any color defined by user is preferred, we cannot use classes and must use inline styles. */
+;/* In case background adjustment to any color defined by user is preferred, we cannot use classes and must use inline styles. */
(function(wysihtml5) {
var REG_EXP = /(\s|^)background-color\s*:\s*[^;\s]+;?/gi;
wysihtml5.commands.bgColorStyle = {
exec: function(composer, command, color) {
@@ -5895,11 +9940,11 @@
return false;
}
};
})(wysihtml5);
-(function(wysihtml5) {
+;(function(wysihtml5) {
var dom = wysihtml5.dom,
// Following elements are grouped
// when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
// instead of creating a H4 within a H1 which would result in semantically invalid html
BLOCK_ELEMENTS_GROUP = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "PRE", "BLOCKQUOTE", "DIV"];
@@ -5915,19 +9960,37 @@
} else {
element.className = className;
}
}
+ function _addStyle(element, cssStyle, styleRegExp) {
+ _removeStyle(element, styleRegExp);
+ if (element.getAttribute('style')) {
+ element.setAttribute('style', wysihtml5.lang.string(element.getAttribute('style') + " " + cssStyle).trim());
+ } else {
+ element.setAttribute('style', cssStyle);
+ }
+ }
+
function _removeClass(element, classRegExp) {
var ret = classRegExp.test(element.className);
element.className = element.className.replace(classRegExp, "");
if (wysihtml5.lang.string(element.className).trim() == '') {
element.removeAttribute('class');
}
return ret;
}
+ function _removeStyle(element, styleRegExp) {
+ var ret = styleRegExp.test(element.getAttribute('style'));
+ element.setAttribute('style', (element.getAttribute('style') || "").replace(styleRegExp, ""));
+ if (wysihtml5.lang.string(element.getAttribute('style') || "").trim() == '') {
+ element.removeAttribute('style');
+ }
+ return ret;
+ }
+
/**
* Check whether given node is a text node and whether it's empty
*/
function _isBlankTextNode(node) {
return node.nodeType === wysihtml5.TEXT_NODE && !wysihtml5.lang.string(node.data).trim();
@@ -6062,35 +10125,42 @@
function _hasClasses(element) {
return !!wysihtml5.lang.string(element.className).trim();
}
+ function _hasStyles(element) {
+ return !!wysihtml5.lang.string(element.getAttribute('style') || '').trim();
+ }
+
wysihtml5.commands.formatBlock = {
- exec: function(composer, command, nodeName, className, classRegExp) {
+ exec: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
var doc = composer.doc,
- blockElements = this.state(composer, command, nodeName, className, classRegExp),
+ blockElements = this.state(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp),
useLineBreaks = composer.config.useLineBreaks,
defaultNodeName = useLineBreaks ? "DIV" : "P",
- selectedNodes, classRemoveAction, blockRenameFound;
-
+ selectedNodes, classRemoveAction, blockRenameFound, styleRemoveAction;
nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
if (blockElements.length) {
- composer.selection.executeAndRestoreSimple(function() {
+ composer.selection.executeAndRestoreRangy(function() {
for (var b = blockElements.length; b--;) {
if (classRegExp) {
classRemoveAction = _removeClass(blockElements[b], classRegExp);
}
+ if (styleRegExp) {
+ styleRemoveAction = _removeStyle(blockElements[b], styleRegExp);
+ }
- if (classRemoveAction && nodeName === null && blockElements[b].nodeName != defaultNodeName) {
- // dont rename or remove element when just setting block formating class
+ if ((styleRemoveAction || classRemoveAction) && nodeName === null && blockElements[b].nodeName != defaultNodeName) {
+ // dont rename or remove element when just setting block formating class or style
return;
}
- var hasClasses = _hasClasses(blockElements[b]);
+ var hasClasses = _hasClasses(blockElements[b]),
+ hasStyles = _hasStyles(blockElements[b]);
- if (!hasClasses && (useLineBreaks || nodeName === "P")) {
+ if (!hasClasses && !hasStyles && (useLineBreaks || nodeName === "P")) {
// Insert a line break afterwards and beforewards when there are siblings
// that are not of type line break or block element
_addLineBreakBeforeAndAfter(blockElements[b]);
dom.replaceWithChildNodes(blockElements[b]);
} else {
@@ -6104,27 +10174,29 @@
}
// Find similiar block element and rename it (<h2 class="foo"></h2> => <h1 class="foo"></h1>)
if (nodeName === null || wysihtml5.lang.array(BLOCK_ELEMENTS_GROUP).contains(nodeName)) {
selectedNodes = composer.selection.findNodesInSelection(BLOCK_ELEMENTS_GROUP).concat(composer.selection.getSelectedOwnNodes());
- composer.selection.executeAndRestoreSimple(function() {
+ composer.selection.executeAndRestoreRangy(function() {
for (var n = selectedNodes.length; n--;) {
blockElement = dom.getParentElement(selectedNodes[n], {
nodeName: BLOCK_ELEMENTS_GROUP
});
if (blockElement == composer.element) {
- blockElement = null;
+ blockElement = null;
}
if (blockElement) {
// Rename current block element to new block element and add class
if (nodeName) {
blockElement = dom.renameElement(blockElement, nodeName);
}
if (className) {
_addClass(blockElement, className, classRegExp);
}
-
+ if (cssStyle) {
+ _addStyle(blockElement, cssStyle, styleRegExp);
+ }
blockRenameFound = true;
}
}
});
@@ -6132,29 +10204,18 @@
if (blockRenameFound) {
return;
}
}
- if (wysihtml5.browser.supportsSelectLine()) {
- _selectionWrap(composer, {
- "nodeName": (nodeName || defaultNodeName),
- "className": className || null
- });
- } else {
- // Falling back to native command for Opera up to 12 mostly
- // Native command does not create elements from selecton boundaries.
- // Not quite user expected behaviour
- if (composer.commands.support(command)) {
- _execCommand(doc, composer, command, nodeName || defaultNodeName, className);
- return;
- }
- }
-
-
+ _selectionWrap(composer, {
+ "nodeName": (nodeName || defaultNodeName),
+ "className": className || null,
+ "cssStyle": cssStyle || null
+ });
},
- state: function(composer, command, nodeName, className, classRegExp) {
+ state: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
var nodes = composer.selection.getSelectedOwnNodes(),
parents = [],
parent;
nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
@@ -6162,11 +10223,13 @@
//var selectedNode = composer.selection.getSelectedNode();
for (var i = 0, maxi = nodes.length; i < maxi; i++) {
parent = dom.getParentElement(nodes[i], {
nodeName: nodeName,
className: className,
- classRegExp: classRegExp
+ classRegExp: classRegExp,
+ cssStyle: cssStyle,
+ styleRegExp: styleRegExp
});
if (parent && wysihtml5.lang.array(parents).indexOf(parent) == -1) {
parents.push(parent);
}
}
@@ -6177,11 +10240,60 @@
}
};
})(wysihtml5);
-/**
+;/* Formats block for as a <pre><code class="classname"></code></pre> block
+ * Useful in conjuction for sytax highlight utility: highlight.js
+ *
+ * Usage:
+ *
+ * editorInstance.composer.commands.exec("formatCode", "language-html");
+*/
+
+wysihtml5.commands.formatCode = {
+
+ exec: function(composer, command, classname) {
+ var pre = this.state(composer),
+ code, range, selectedNodes;
+ if (pre) {
+ // caret is already within a <pre><code>...</code></pre>
+ composer.selection.executeAndRestore(function() {
+ code = pre.querySelector("code");
+ wysihtml5.dom.replaceWithChildNodes(pre);
+ if (code) {
+ wysihtml5.dom.replaceWithChildNodes(code);
+ }
+ });
+ } else {
+ // Wrap in <pre><code>...</code></pre>
+ range = composer.selection.getRange();
+ selectedNodes = range.extractContents();
+ pre = composer.doc.createElement("pre");
+ code = composer.doc.createElement("code");
+
+ if (classname) {
+ code.className = classname;
+ }
+
+ pre.appendChild(code);
+ code.appendChild(selectedNodes);
+ range.insertNode(pre);
+ composer.selection.selectNode(pre);
+ }
+ },
+
+ state: function(composer) {
+ var selectedNode = composer.selection.getSelectedNode();
+ if (selectedNode && selectedNode.nodeName && selectedNode.nodeName == "PRE"&&
+ selectedNode.firstChild && selectedNode.firstChild.nodeName && selectedNode.firstChild.nodeName == "CODE") {
+ return selectedNode;
+ } else {
+ return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "CODE" }) && wysihtml5.dom.getParentElement(selectedNode, { nodeName: "PRE" });
+ }
+ }
+};;/**
* formatInline scenarios for tag "B" (| = caret, |foo| = selected text)
*
* #1 caret in unformatted text:
* abcdefg|
* output:
@@ -6246,41 +10358,53 @@
if (!ownRanges || ownRanges.length == 0) {
return false;
}
composer.selection.getSelection().removeAllRanges();
_getApplier(tagName, className, classRegExp, cssStyle, styleRegExp).toggleRange(ownRanges);
- range.setStart(ownRanges[0].startContainer, ownRanges[0].startOffset);
- range.setEnd(
- ownRanges[ownRanges.length - 1].endContainer,
- ownRanges[ownRanges.length - 1].endOffset
- );
- if (!noCleanup) {
- if (!dontRestoreSelect) {
- composer.selection.setSelection(range);
- composer.selection.executeAndRestore(function() {
+
+ if (!dontRestoreSelect) {
+ range.setStart(ownRanges[0].startContainer, ownRanges[0].startOffset);
+ range.setEnd(
+ ownRanges[ownRanges.length - 1].endContainer,
+ ownRanges[ownRanges.length - 1].endOffset
+ );
+ composer.selection.setSelection(range);
+ composer.selection.executeAndRestore(function() {
+ if (!noCleanup) {
composer.cleanUp();
- }, true, true);
- } else {
- composer.cleanUp();
- }
+ }
+ }, true, true);
+ } else if (!noCleanup) {
+ composer.cleanUp();
}
},
// Executes so that if collapsed caret is in a state and executing that state it should unformat that state
// It is achieved by selecting the entire state element before executing.
// This works on built in contenteditable inline format commands
execWithToggle: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
var that = this;
- if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) && composer.selection.isCollapsed()) {
+
+ if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) &&
+ composer.selection.isCollapsed() &&
+ !composer.selection.caretIsLastInSelection() &&
+ !composer.selection.caretIsFirstInSelection()
+ ) {
var state_element = that.state(composer, command, tagName, className, classRegExp)[0];
- composer.selection.executeAndRestoreSimple(function() {
+ composer.selection.executeAndRestoreRangy(function() {
var parent = state_element.parentNode;
composer.selection.selectNode(state_element, true);
wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
});
} else {
- wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp);
+ if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) && !composer.selection.isCollapsed()) {
+ composer.selection.executeAndRestoreRangy(function() {
+ wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
+ });
+ } else {
+ wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp);
+ }
}
},
state: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
var doc = composer.doc,
@@ -6306,11 +10430,11 @@
return _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp).isAppliedToRange(ownRanges);
}
};
})(wysihtml5);
-wysihtml5.commands.insertHTML = {
+;wysihtml5.commands.insertHTML = {
exec: function(composer, command, html) {
if (composer.commands.support(command)) {
composer.doc.execCommand(command, false, html);
} else {
composer.selection.insertHTML(html);
@@ -6319,11 +10443,11 @@
state: function() {
return false;
}
};
-(function(wysihtml5) {
+;(function(wysihtml5) {
var NODE_NAME = "IMG";
wysihtml5.commands.insertImage = {
/**
* Inserts an <img>
@@ -6417,11 +10541,11 @@
return imagesInSelection[0];
}
};
})(wysihtml5);
-(function(wysihtml5) {
+;(function(wysihtml5) {
var LINE_BREAK = "<br>" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
wysihtml5.commands.insertLineBreak = {
exec: function(composer, command) {
if (composer.commands.support(command)) {
@@ -6437,11 +10561,11 @@
state: function() {
return false;
}
};
})(wysihtml5);
-wysihtml5.commands.insertOrderedList = {
+;wysihtml5.commands.insertOrderedList = {
exec: function(composer, command) {
var doc = composer.doc,
selectedNode = composer.selection.getSelectedNode(),
list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" }),
otherList = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" }),
@@ -6449,14 +10573,14 @@
isEmpty,
tempElement;
// do not count list elements outside of composer
if (list && !composer.element.contains(list)) {
- list = null
+ list = null;
}
if (otherList && !composer.element.contains(otherList)) {
- otherList = null
+ otherList = null;
}
if (!list && !otherList && composer.commands.support(command)) {
doc.execCommand(command, false, null);
return;
@@ -6478,34 +10602,39 @@
composer.selection.executeAndRestore(function() {
wysihtml5.dom.renameElement(otherList, "ol");
});
} else {
// Create list
- tempElement = composer.selection.deblockAndSurround({
- "nodeName": "div",
- "className": tempClassName
- });
- if (tempElement) {
- isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE || tempElement.innerHTML === "<br>";
- composer.selection.executeAndRestore(function() {
- list = wysihtml5.dom.convertToList(tempElement, "ol", composer.parent.config.uneditableContainerClassname);
+ composer.selection.executeAndRestoreRangy(function() {
+ tempElement = composer.selection.deblockAndSurround({
+ "nodeName": "div",
+ "className": tempClassName
});
- if (isEmpty) {
- composer.selection.selectNode(list.querySelector("li"), true);
+
+ // This space causes new lists to never break on enter
+ var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
+ tempElement.innerHTML = tempElement.innerHTML.replace(INVISIBLE_SPACE_REG_EXP, "");
+
+ if (tempElement) {
+ isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE || tempElement.innerHTML === "<br>";
+ list = wysihtml5.dom.convertToList(tempElement, "ol", composer.parent.config.uneditableContainerClassname);
+ if (isEmpty) {
+ composer.selection.selectNode(list.querySelector("li"), true);
+ }
}
- }
+ });
}
},
state: function(composer) {
var selectedNode = composer.selection.getSelectedNode(),
node = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" });
return (composer.element.contains(node) ? node : false);
}
};
-wysihtml5.commands.insertUnorderedList = {
+;wysihtml5.commands.insertUnorderedList = {
exec: function(composer, command) {
var doc = composer.doc,
selectedNode = composer.selection.getSelectedNode(),
list = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" }),
otherList = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "OL" }),
@@ -6513,14 +10642,14 @@
isEmpty,
tempElement;
// do not count list elements outside of composer
if (list && !composer.element.contains(list)) {
- list = null
+ list = null;
}
if (otherList && !composer.element.contains(otherList)) {
- otherList = null
+ otherList = null;
}
if (!list && !otherList && composer.commands.support(command)) {
doc.execCommand(command, false, null);
return;
@@ -6542,34 +10671,39 @@
composer.selection.executeAndRestore(function() {
wysihtml5.dom.renameElement(otherList, "ul");
});
} else {
// Create list
- tempElement = composer.selection.deblockAndSurround({
- "nodeName": "div",
- "className": tempClassName
- });
- if (tempElement) {
- isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE || tempElement.innerHTML === "<br>";
- composer.selection.executeAndRestore(function() {
- list = wysihtml5.dom.convertToList(tempElement, "ul", composer.parent.config.uneditableContainerClassname);
+ composer.selection.executeAndRestoreRangy(function() {
+ tempElement = composer.selection.deblockAndSurround({
+ "nodeName": "div",
+ "className": tempClassName
});
- if (isEmpty) {
- composer.selection.selectNode(list.querySelector("li"), true);
+
+ // This space causes new lists to never break on enter
+ var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
+ tempElement.innerHTML = tempElement.innerHTML.replace(INVISIBLE_SPACE_REG_EXP, "");
+
+ if (tempElement) {
+ isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE || tempElement.innerHTML === "<br>";
+ list = wysihtml5.dom.convertToList(tempElement, "ul", composer.parent.config.uneditableContainerClassname);
+ if (isEmpty) {
+ composer.selection.selectNode(list.querySelector("li"), true);
+ }
}
- }
+ });
}
},
state: function(composer) {
var selectedNode = composer.selection.getSelectedNode(),
node = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "UL" });
return (composer.element.contains(node) ? node : false);
}
};
-wysihtml5.commands.italic = {
+;wysihtml5.commands.italic = {
exec: function(composer, command) {
wysihtml5.commands.formatInline.execWithToggle(composer, command, "i");
},
state: function(composer, command) {
@@ -6579,11 +10713,11 @@
// ie: <i>, <em>
// opera: only <i>
return wysihtml5.commands.formatInline.state(composer, command, "i");
}
};
-(function(wysihtml5) {
+;(function(wysihtml5) {
var CLASS_NAME = "wysiwyg-text-align-center",
REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
wysihtml5.commands.justifyCenter = {
exec: function(composer, command) {
@@ -6593,11 +10727,11 @@
state: function(composer, command) {
return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
}
};
})(wysihtml5);
-(function(wysihtml5) {
+;(function(wysihtml5) {
var CLASS_NAME = "wysiwyg-text-align-left",
REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
wysihtml5.commands.justifyLeft = {
exec: function(composer, command) {
@@ -6607,11 +10741,11 @@
state: function(composer, command) {
return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
}
};
})(wysihtml5);
-(function(wysihtml5) {
+;(function(wysihtml5) {
var CLASS_NAME = "wysiwyg-text-align-right",
REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
wysihtml5.commands.justifyRight = {
exec: function(composer, command) {
@@ -6621,11 +10755,11 @@
state: function(composer, command) {
return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
}
};
})(wysihtml5);
-(function(wysihtml5) {
+;(function(wysihtml5) {
var CLASS_NAME = "wysiwyg-text-align-justify",
REG_EXP = /wysiwyg-text-align-[0-9a-z]+/g;
wysihtml5.commands.justifyFull = {
exec: function(composer, command) {
@@ -6635,38 +10769,80 @@
state: function(composer, command) {
return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
}
};
})(wysihtml5);
-wysihtml5.commands.redo = {
+;(function(wysihtml5) {
+ var STYLE_STR = "text-align: right;",
+ REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
+
+ wysihtml5.commands.alignRightStyle = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ var STYLE_STR = "text-align: left;",
+ REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
+
+ wysihtml5.commands.alignLeftStyle = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;(function(wysihtml5) {
+ var STYLE_STR = "text-align: center;",
+ REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
+
+ wysihtml5.commands.alignCenterStyle = {
+ exec: function(composer, command) {
+ return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ },
+
+ state: function(composer, command) {
+ return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
+ }
+ };
+})(wysihtml5);
+;wysihtml5.commands.redo = {
exec: function(composer) {
return composer.undoManager.redo();
},
state: function(composer) {
return false;
}
};
-wysihtml5.commands.underline = {
+;wysihtml5.commands.underline = {
exec: function(composer, command) {
wysihtml5.commands.formatInline.execWithToggle(composer, command, "u");
},
state: function(composer, command) {
return wysihtml5.commands.formatInline.state(composer, command, "u");
}
};
-wysihtml5.commands.undo = {
+;wysihtml5.commands.undo = {
exec: function(composer) {
return composer.undoManager.undo();
},
state: function(composer) {
return false;
}
};
-wysihtml5.commands.createTable = {
+;wysihtml5.commands.createTable = {
exec: function(composer, command, value) {
var col, row, html;
if (value && value.cols && value.rows && parseInt(value.cols, 10) > 0 && parseInt(value.rows, 10) > 0) {
if (value.tableStyle) {
html = "<table style=\"" + value.tableStyle + "\">";
@@ -6691,11 +10867,11 @@
state: function(composer, command) {
return false;
}
};
-wysihtml5.commands.mergeTableCells = {
+;wysihtml5.commands.mergeTableCells = {
exec: function(composer, command) {
if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
if (this.state(composer, command)) {
wysihtml5.dom.table.unmergeCell(composer.tableSelection.start);
} else {
@@ -6721,11 +10897,11 @@
}
}
return false;
}
};
-wysihtml5.commands.addTableCells = {
+;wysihtml5.commands.addTableCells = {
exec: function(composer, command, value) {
if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
// switches start and end if start is bigger than end (reverse selection)
var tableSelect = wysihtml5.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end);
@@ -6742,11 +10918,11 @@
state: function(composer, command) {
return false;
}
};
-wysihtml5.commands.deleteTableCells = {
+;wysihtml5.commands.deleteTableCells = {
exec: function(composer, command, value) {
if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
var tableSelect = wysihtml5.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end),
idx = wysihtml5.dom.table.indexOf(tableSelect.start),
selCell,
@@ -6782,11 +10958,11 @@
state: function(composer, command) {
return false;
}
};
-/**
+;/**
* Undo Manager for wysihtml5
* slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface
*/
(function(wysihtml5) {
var Z_KEY = 90,
@@ -6859,57 +11035,10 @@
if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) {
that.transact();
}
});
- // Now this is very hacky:
- // These days browsers don't offer a undo/redo event which we could hook into
- // to be notified when the user hits undo/redo in the contextmenu.
- // Therefore we simply insert two elements as soon as the contextmenu gets opened.
- // The last element being inserted will be immediately be removed again by a exexCommand("undo")
- // => When the second element appears in the dom tree then we know the user clicked "redo" in the context menu
- // => When the first element disappears from the dom tree then we know the user clicked "undo" in the context menu
-
- // TODO: unexpected behaviour. Tends to undo on contextmenu showing in chrome on newly inserted blocks
- /*if (wysihtml5.browser.hasUndoInContextMenu()) {
- var interval, observed, cleanUp = function() {
- cleanTempElements(doc);
- clearInterval(interval);
- };
-
- dom.observe(this.element, "contextmenu", function() {
- cleanUp();
- that.composer.selection.executeAndRestoreSimple(function() {
- if (that.element.lastChild) {
- that.composer.selection.setAfter(that.element.lastChild);
- }
-
- // enable undo button in context menu
- doc.execCommand("insertHTML", false, UNDO_HTML);
- // enable redo button in context menu
- doc.execCommand("insertHTML", false, REDO_HTML);
- doc.execCommand("undo", false, null);
- });
-
- interval = setInterval(function() {
- if (doc.getElementById("_wysihtml5-redo")) {
- cleanUp();
- that.redo();
- } else if (!doc.getElementById("_wysihtml5-undo")) {
- cleanUp();
- that.undo();
- }
- }, 400);
-
- if (!observed) {
- observed = true;
- dom.observe(document, "mousedown", cleanUp);
- dom.observe(doc, ["mousedown", "paste", "cut", "copy"], cleanUp);
- }
- });
- }*/
-
this.editor
.on("newword:composer", function() {
that.transact();
})
@@ -7038,11 +11167,11 @@
getChildNodeByIndex: function(parent, index) {
return parent.childNodes[index];
}
});
})(wysihtml5);
-/**
+;/**
* TODO: the following methods still need unit test coverage
*/
wysihtml5.views.View = Base.extend(
/** @scope wysihtml5.views.View.prototype */ {
constructor: function(parent, textareaElement, config) {
@@ -7092,11 +11221,11 @@
enable: function() {
this.element.removeAttribute("disabled");
}
});
-(function(wysihtml5) {
+;(function(wysihtml5) {
var dom = wysihtml5.dom,
browser = wysihtml5.browser;
wysihtml5.views.Composer = wysihtml5.views.View.extend(
/** @scope wysihtml5.views.Composer.prototype */ {
@@ -7542,11 +11671,11 @@
}
});
}
});
})(wysihtml5);
-(function(wysihtml5) {
+;(function(wysihtml5) {
var dom = wysihtml5.dom,
doc = document,
win = window,
HOST_TEMPLATE = doc.createElement("div"),
/**
@@ -7743,11 +11872,11 @@
});
return this;
};
})(wysihtml5);
-/**
+;/**
* Taking care of events
* - Simulating 'change' event on contentEditable element
* - Handling drag & drop logic
* - Catch paste events
* - Dispatch proprietary newword:composer event
@@ -7763,10 +11892,101 @@
"66": "bold", // B
"73": "italic", // I
"85": "underline" // U
};
+ var deleteAroundEditable = function(selection, uneditable, element) {
+ // merge node with previous node from uneditable
+ var prevNode = selection.getPreviousNode(uneditable, true),
+ curNode = selection.getSelectedNode();
+
+ if (curNode.nodeType !== 1 && curNode.parentNode !== element) { curNode = curNode.parentNode; }
+ if (prevNode) {
+ if (curNode.nodeType == 1) {
+ var first = curNode.firstChild;
+
+ if (prevNode.nodeType == 1) {
+ while (curNode.firstChild) {
+ prevNode.appendChild(curNode.firstChild);
+ }
+ } else {
+ while (curNode.firstChild) {
+ uneditable.parentNode.insertBefore(curNode.firstChild, uneditable);
+ }
+ }
+ if (curNode.parentNode) {
+ curNode.parentNode.removeChild(curNode);
+ }
+ selection.setBefore(first);
+ } else {
+ if (prevNode.nodeType == 1) {
+ prevNode.appendChild(curNode);
+ } else {
+ uneditable.parentNode.insertBefore(curNode, uneditable);
+ }
+ selection.setBefore(curNode);
+ }
+ }
+ };
+
+ var handleDeleteKeyPress = function(selection, element) {
+ if (selection.isCollapsed()) {
+ if (selection.caretIsInTheBeginnig()) {
+ event.preventDefault();
+ } else {
+ var beforeUneditable = selection.caretIsBeforeUneditable();
+
+ // Do a special delete if caret would delete uneditable
+ if (beforeUneditable) {
+ event.preventDefault();
+ deleteAroundEditable(selection, beforeUneditable, element);
+ }
+ }
+ } else if (selection.containsUneditable()) {
+ event.preventDefault();
+ selection.deleteContents();
+ }
+ };
+
+ var tryToPushLiLevel = function(selection) {
+ var prevLi;
+ selection.executeAndRestoreRangy(function() {
+ var selNode = selection.getSelectedNode(),
+ liNode = (selNode.nodeName && selNode.nodeName === 'LI') ? selNode : selNode.parentNode,
+ listTag, list;
+
+ if (liNode.getAttribute('class') === "rangySelectionBoundary") {
+ liNode = liNode.parentNode;
+ }
+
+ if (liNode.nodeName === 'LI') {
+ listTag = (liNode.parentNode.nodeName === 'OL') ? 'OL' : 'UL';
+ list = selNode.ownerDocument.createElement(listTag);
+ prevLi = wysihtml5.dom.getPreviousElement(liNode);
+
+ if (prevLi) {
+ list.appendChild(liNode);
+ prevLi.appendChild(list);
+ }
+ }
+
+ });
+ return (prevLi) ? true : false;
+ };
+
+
+ var handleTabKeyDown = function(composer, element) {
+ if (!composer.selection.isCollapsed()) {
+ composer.selection.deleteContents();
+ } else if (composer.selection.caretIsInTheBeginnig('LI')) {
+ if (tryToPushLiLevel(composer.selection)) return;
+ }
+
+ // Is   close enough to tab. Could not find enough counter arguments for now.
+ composer.commands.exec("insertHTML", " ");
+ };
+
wysihtml5.views.Composer.prototype.observe = function() {
var that = this,
state = this.getValue(),
container = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
element = this.element,
@@ -7798,11 +12018,17 @@
}, 0);
});
if (this.config.handleTables) {
- this.tableSelection = wysihtml5.quirks.tableCellsSelection(element, that.parent);
+ if(this.doc.execCommand && wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") && wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing")) {
+ setTimeout(function() {
+ that.doc.execCommand("enableObjectResizing", false, "false");
+ that.doc.execCommand("enableInlineTableEditing", false, "false");
+ }, 0);
+ }
+ this.tableSelection = wysihtml5.quirks.tableCellsSelection(element, that.parent);
}
// --------- Focus & blur logic ---------
dom.observe(focusBlurElement, "focus", function() {
that.parent.fire("focus").fire("focus:composer");
@@ -7899,60 +12125,16 @@
command = shortcuts[keyCode];
if ((event.ctrlKey || event.metaKey) && !event.altKey && command) {
that.commands.exec(command);
event.preventDefault();
}
- if (keyCode == 8) {
-
- if (that.selection.isCollapsed()) {
- if (that.selection.caretIsInTheBeginnig()) {
- event.preventDefault();
- } else {
- var beforeUneditable = that.selection.caretIsBeforeUneditable();
- if (beforeUneditable) {
- event.preventDefault();
-
- // TODO: take the how to delete around uneditable out of here
- // merge node with previous node from uneditable
- var prevNode = that.selection.getPreviousNode(beforeUneditable, true),
- curNode = that.selection.getSelectedNode();
-
- if (curNode.nodeType !== 1 && curNode.parentNode !== element) { curNode = curNode.parentNode; }
- if (prevNode) {
- if (curNode.nodeType == 1) {
- var first = curNode.firstChild;
-
- if (prevNode.nodeType == 1) {
- while (curNode.firstChild) {
- prevNode.appendChild(curNode.firstChild);
- }
- } else {
- while (curNode.firstChild) {
- beforeUneditable.parentNode.insertBefore(curNode.firstChild, beforeUneditable);
- }
- }
- if (curNode.parentNode) {
- curNode.parentNode.removeChild(curNode);
- }
- that.selection.setBefore(first);
- } else {
- if (prevNode.nodeType == 1) {
- prevNode.appendChild(curNode);
- } else {
- beforeUneditable.parentNode.insertBefore(curNode, beforeUneditable);
- }
- that.selection.setBefore(curNode);
- }
- }
- }
-
- }
- } else if (that.selection.containsUneditable()) {
- event.preventDefault();
- that.selection.deleteContents();
- }
-
+ if (keyCode === 8) {
+ // delete key
+ handleDeleteKeyPress(that.selection, element);
+ } else if (keyCode === 9) {
+ event.preventDefault();
+ handleTabKeyDown(that, element);
}
});
// --------- Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor ---------
dom.observe(element, "keydown", function(event) {
@@ -7973,11 +12155,11 @@
}
});
// --------- IE 8+9 focus the editor when the iframe is clicked (without actually firing the 'focus' event on the <body>) ---------
if (!this.config.contentEditableMode && browser.hasIframeFocusIssue()) {
- dom.observe(this.iframe, "focus", function() {
+ dom.observe(container, "focus", function() {
setTimeout(function() {
if (that.doc.querySelector(":focus") !== that.element) {
that.focus();
}
}, 0);
@@ -8009,11 +12191,11 @@
target.setAttribute("title", title);
}
});
};
})(wysihtml5);
-/**
+;/**
* Class that takes care that the value of the composer and the textarea is always in sync
*/
(function(wysihtml5) {
var INTERVAL = 400;
@@ -8106,11 +12288,11 @@
this.editor.on("destroy:composer", stopInterval);
}
});
})(wysihtml5);
-wysihtml5.views.Textarea = wysihtml5.views.View.extend(
+;wysihtml5.views.Textarea = wysihtml5.views.View.extend(
/** @scope wysihtml5.views.Textarea.prototype */ {
name: "textarea",
constructor: function(parent, textareaElement, config) {
this.base(parent, textareaElement, config);
@@ -8177,10 +12359,10 @@
setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0);
});
});
}
});
-/**
+;/**
* WYSIHTML5 Editor
*
* @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface
* @param {Object} [config] See defaultConfig object below for explanation of each individual config option
*