diff --git a/Gruntfile.js b/Gruntfile.js
index 582d122..9d6624d 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -195,7 +195,10 @@ module.exports = function(grunt) {
src: ['src/generated/commands.js'],
dest: 'dist/amd/commands.js',
options: {
- wrapper: ['define("bootstrap.wysihtml5.commands", ["wysihtml5"], function(wysihtml5) {\n', '\n});']
+ wrapper: [
+ '',
+ ''
+ ]
}
}
},
diff --git a/bower.json b/bower.json
index e58f48a..b3936a1 100644
--- a/bower.json
+++ b/bower.json
@@ -6,10 +6,10 @@
"dist/bootstrap3-wysihtml5.min.js"
],
"dependencies": {
- "wysihtml5x": "0.4.15",
+ "wysihtml5x": "0.5.2",
"bootstrap": ">=3",
"components-font-awesome": ">=4",
- "handlebars": "~1.3.0",
+ "handlebars": "~4.0.5",
"jquery": ">=2.1"
},
"homepage": "https://github.com/Waxolunist/bootstrap3-wysihtml5-bower",
diff --git a/dist/amd/bootstrap3-wysihtml5.all.js b/dist/amd/bootstrap3-wysihtml5.all.js
index 22a6e21..64789df 100644
--- a/dist/amd/bootstrap3-wysihtml5.all.js
+++ b/dist/amd/bootstrap3-wysihtml5.all.js
@@ -1,15090 +1,1307 @@
-define('wysihtml5', ['rangy', 'rangy-selectionsaverestore'], function (rangy) {
- window.rangy = rangy;
-
-// 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);
- }
- }
- );
- })();
-}
-
-// isArray polyfill for ie8
-if(!Array.isArray) {
- Array.isArray = function(arg) {
- return Object.prototype.toString.call(arg) === '[object Array]';
- };
-};/**
- * @license wysihtml5x v0.4.15
- * 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.15",
-
- // namespaces
- commands: {},
- dom: {},
- quirks: {},
- toolbar: {},
- lang: {},
- selection: {},
- views: {},
-
- INVISIBLE_SPACE: "\uFEFF",
-
- EMPTY_FUNCTION: function() {},
-
- ELEMENT_NODE: 1,
- TEXT_NODE: 3,
-
- BACKSPACE_KEY: 8,
- ENTER_KEY: 13,
- ESCAPE_KEY: 27,
- SPACE_KEY: 32,
- DELETE_KEY: 46
-};
-;/**
- * Rangy, a cross-browser JavaScript range and selection library
- * http://code.google.com/p/rangy/
- *
- * Copyright 2014, Tim Down
- * Licensed under the MIT license.
- * Version: 1.3alpha.20140804
- * Build date: 4 August 2014
- */
-
-(function(factory, global) {
- if (typeof define == "function" && define.amd) {
- // AMD. Register as an anonymous module.
- define(factory);
-/*
- TODO: look into this properly.
-
- } else if (typeof exports == "object") {
- // Node/CommonJS style for Browserify
- module.exports = factory;
-*/
- } else {
- // No AMD or CommonJS support so we place Rangy in a global variable
- global.rangy = factory();
- }
-})(function() {
-
- 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.20140804",
- 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,
- autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize
- }
- };
-
- 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 && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
- api.util.extend(o, p, true);
- }
- obj[i] = p;
- }
- }
- // Special case for toString, which does not show up in for...in loops in IE <= 8
- if (props.hasOwnProperty("toString")) {
- obj.toString = props.toString;
- }
- 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;
- }
- }
-
- 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 shimListeners = [];
-
- api.addShimListener = function(listener) {
- shimListeners.push(listener);
- };
-
- function shim(win) {
- win = win || window;
- init();
-
- // Notify listeners
- for (var i = 0, len = shimListeners.length; i < len; ++i) {
- shimListeners[i](win);
- }
- }
-
- api.shim = api.createMissingNativeApi = shim;
-
- function Module(name, dependencies, initializer) {
- this.name = name;
- this.dependencies = dependencies;
- this.initialized = false;
- this.supported = false;
- this.initializer = initializer;
- }
-
- Module.prototype = {
- init: function() {
- 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;
- }
-
- 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];
- }
-
- var module = createModule(false, name, dependencies, initFunc);
-
- // Initialize the module immediately if the core is already initialized
- if (api.initialized) {
- module.init();
- }
- };
-
- 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 && api.config.autoInitialize) {
- 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);
-
- /*----------------------------------------------------------------------------------------------------------------*/
-
- // DOM utility methods used by Rangy
- api.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) {
- var n;
- try {
- n = node.parentNode;
- return false;
- } catch (e) {
- return true;
- }
- }
-
- (function() {
- var el = document.createElement("b");
- el.innerHTML = "1";
- var textNode = el.firstChild;
- el.innerHTML = "
";
- 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 + ">[index:" + getNodeIndex(node) + ",length:" + 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,
- INVALID_NODE_TYPE_ERR: 24
- };
-
- 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;
- });
-
- /*----------------------------------------------------------------------------------------------------------------*/
-
- // Pure JavaScript implementation of DOM Range
- api.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();
- }
-
- 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();
- 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();
- } 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();
- } 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() {
- this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
- }
- };
-
- /*----------------------------------------------------------------------------------------------------------------*/
-
- var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
- var rootContainerNodeTypes = [2, 9, 11];
- var readonlyNodeTypes = [5, 6, 10, 12];
- var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
- var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
-
- function createAncestorFinder(nodeTypes) {
- return function(node, selfIsAncestor) {
- var t, n = selfIsAncestor ? node : node.parentNode;
- while (n) {
- t = n.nodeType;
- if (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 DOMException("INVALID_NODE_TYPE_ERR");
- }
- }
-
- function assertValidNodeType(node, invalidTypes) {
- if (!arrayContains(invalidTypes, node.nodeType)) {
- throw new DOMException("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) {
- 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 = "x";
- 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) {
- 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);
- }
-
- function rangeToHtml(range) {
- assertRangeValid(range);
- var container = range.commonAncestorContainer.parentNode.cloneNode(false);
- container.appendChild( range.cloneContents() );
- return container.innerHTML;
- }
-
- /*----------------------------------------------------------------------------------------------------------------*/
-
- 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 DOMException("INVALID_STATE_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() {
- return rangeToHtml(this);
- },
-
- // 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 DOMException("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);
- return this.containsRange(nodeRange);
- } else {
- return this.containsNodeContents(node);
- }
- },
-
- getNodes: function(nodeTypes, filter) {
- assertRangeValid(this);
- return getNodesInRange(this, nodeTypes, filter);
- },
-
- getDocument: function() {
- return getRangeDocument(this);
- },
-
- collapseBefore: function(node) {
- this.setEndBefore(node);
- this.collapse(false);
- },
-
- collapseAfter: function(node) {
- 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;
- }
-
- 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);
- },
-
- detach: function() {
- // In DOM4, detach() is now a no-op.
- }
- });
-
- 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) {
- function createBeforeAfterNodeSetter(isBefore, isStart) {
- return function(node) {
- 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) {
- assertNoDocTypeNotationEntityAncestor(node, true);
- assertValidOffset(node, offset);
-
- setRangeStart(this, node, offset);
- },
-
- setEnd: function(node, offset) {
- 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() {
- 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) {
- assertNoDocTypeNotationEntityAncestor(node, true);
-
- boundaryUpdater(this, node, 0, node, getNodeLength(node));
- },
-
- selectNode: function(node) {
- 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;
- },
-
- 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) {
- 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 Range(doc) {
- this.startContainer = doc;
- this.startOffset = 0;
- this.endContainer = doc;
- this.endOffset = 0;
- this.document = doc;
- updateCollapsedAndCommonAncestor(this);
- }
-
- createPrototypeRange(Range, updateBoundaries);
-
- util.extend(Range, {
- rangeProperties: rangeProperties,
- RangeIterator: RangeIterator,
- copyComparisonConstants: copyComparisonConstants,
- createPrototypeRange: createPrototypeRange,
- inspect: inspect,
- toHtml: rangeToHtml,
- 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;
- });
-
- /*----------------------------------------------------------------------------------------------------------------*/
-
- // Wrappers for the browser's native DOM Range and/or TextRange implementation
- api.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);
- }
- }
-
- var createBeforeAfterNodeSetter;
-
- WrappedRange = function(range) {
- if (!range) {
- throw module.createError("WrappedRange: Range must be specified");
- }
- this.nativeRange = range;
- updateRangeProperties(this);
- };
-
- DomRange.createPrototypeRange(WrappedRange, updateNativeRange);
-
- 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);
-
- 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):
-
-
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 approach used 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); - - var 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; - } - }; - - WrappedTextRange.rangeToTextRange = rangeToTextRange; - - WrappedTextRange.prototype.toTextRange = function() { - return rangeToTextRange(this); - }; - - 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.addShimListener(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) - api.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) { - // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a - // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's - // nothing we can do about this while retaining the feature test so we have to resort to a browser - // sniff. I'm not happy about it. See - // https://code.google.com/p/chromium/issues/detail?id=399791 - var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /); - if (chromeMatch && parseInt(chromeMatch[1]) >= 36) { - selectionSupportsMultipleRanges = false; - } else { - 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); - } - } - - // Clean up - body.removeChild(testEl); - sel.removeAllRanges(); - - 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 the 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 && implementsDocSelection && 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 && implementsDocSelection) { - 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 ) || false; - }; - - 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() { - var rangeHtmls = []; - this.eachRange(function(range) { - rangeHtmls.push( DomRange.toHtml(range) ); - }); - return rangeHtmls.join(""); - }; - - if (features.implementsTextRange) { - selProto.getNativeTextRange = function() { - var sel, textRange; - if ( (sel = this.docSelection) ) { - var range = sel.createRange(); - if (isTextRange(range)) { - return range; - } else { - throw module.createError("getNativeTextRange: selection is a control selection"); - } - } else if (this.rangeCount > 0) { - return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) ); - } else { - throw module.createError("getNativeTextRange: selection contains no range"); - } - }; - } - - 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.addShimListener(function(win) { - if (typeof win.getSelection == "undefined") { - win.getSelection = function() { - return getSelection(win); - }; - } - win = null; - }); - }); - - - /*----------------------------------------------------------------------------------------------------------------*/ - - return api; -}, this);;/** - * 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 2014, Tim Down - * Licensed under the MIT license. - * Version: 1.3alpha.20140804 - * Build date: 4 August 2014 - */ -(function(factory, global) { - if (typeof define == "function" && define.amd) { - // AMD. Register as an anonymous module with a dependency on Rangy. - define(["rangy"], factory); - /* - } else if (typeof exports == "object") { - // Node/CommonJS style for Browserify - module.exports = factory; - */ - } else { - // No AMD or CommonJS support so we use the rangy global variable - factory(global.rangy); - } -})(function(rangy) { - 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); - 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 - }); - }); - -}, this);;/* - Base.js, version 1.1a - Copyright 2006-2010, Dean Edwards - License: http://www.opensource.org/licenses/mit-license.php -*/ - -var Base = function() { - // dummy -}; - -Base.extend = function(_instance, _static) { // subclass - var extend = Base.prototype.extend; - - // build the prototype - Base._prototyping = true; - var proto = new this; - extend.call(proto, _instance); - proto.base = function() { - // call this method from any other method to invoke that method's ancestor - }; - delete Base._prototyping; - - // create the wrapper for the constructor function - //var constructor = proto.constructor.valueOf(); //-dean - var constructor = proto.constructor; - var klass = proto.constructor = function() { - if (!Base._prototyping) { - if (this._constructing || this.constructor == klass) { // instantiation - this._constructing = true; - constructor.apply(this, arguments); - delete this._constructing; - } else if (arguments[0] != null) { // casting - return (arguments[0].extend || extend).call(arguments[0], proto); - } - } - }; - - // build the class interface - klass.ancestor = this; - klass.extend = this.extend; - klass.forEach = this.forEach; - klass.implement = this.implement; - klass.prototype = proto; - klass.toString = this.toString; - klass.valueOf = function(type) { - //return (type == "object") ? klass : constructor; //-dean - return (type == "object") ? klass : constructor.valueOf(); - }; - extend.call(klass, _static); - // class initialisation - if (typeof klass.init == "function") klass.init(); - return klass; -}; - -Base.prototype = { - extend: function(source, value) { - if (arguments.length > 1) { // extending with a name/value pair - var ancestor = this[source]; - if (ancestor && (typeof value == "function") && // overriding a method? - // the valueOf() comparison is to avoid circular references - (!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) && - /\bbase\b/.test(value)) { - // get the underlying method - var method = value.valueOf(); - // override - value = function() { - var previous = this.base || Base.prototype.base; - this.base = ancestor; - var returnValue = method.apply(this, arguments); - this.base = previous; - return returnValue; - }; - // point to the underlying method - value.valueOf = function(type) { - return (type == "object") ? value : method; - }; - value.toString = Base.toString; - } - this[source] = value; - } else if (source) { // extending with an object literal - var extend = Base.prototype.extend; - // if this object has a customised extend method then use it - if (!Base._prototyping && typeof this != "function") { - extend = this.extend || extend; - } - var proto = {toSource: null}; - // do the "toString" and other methods manually - var hidden = ["constructor", "toString", "valueOf"]; - // if we are prototyping then include the constructor - var i = Base._prototyping ? 0 : 1; - while (key = hidden[i++]) { - if (source[key] != proto[key]) { - extend.call(this, key, source[key]); - - } - } - // copy each of the source object's properties to this object - for (var key in source) { - if (!proto[key]) extend.call(this, key, source[key]); - } - } - return this; - } -}; - -// initialise -Base = Base.extend({ - constructor: function() { - this.extend(arguments[0]); - } -}, { - ancestor: Object, - version: "1.1", - - forEach: function(object, block, context) { - for (var key in object) { - if (this.prototype[key] === undefined) { - block.call(context, object[key], key, object); - } - } - }, - - implement: function() { - for (var i = 0; i < arguments.length; i++) { - if (typeof arguments[i] == "function") { - // if it's a function, call it - arguments[i](this.prototype); - } else { - // add the interface using the extend method - this.prototype.extend(arguments[i]); - } - } - return this; - }, - - toString: function() { - return String(this.valueOf()); - } -});;/** - * Detect browser support for specific features - */ -wysihtml5.browser = (function() { - var userAgent = navigator.userAgent, - testElement = document.createElement("div"), - // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect - isGecko = userAgent.indexOf("Gecko") !== -1 && userAgent.indexOf("KHTML") === -1, - isWebKit = userAgent.indexOf("AppleWebKit/") !== -1, - isChrome = userAgent.indexOf("Chrome/") !== -1, - isOpera = userAgent.indexOf("Opera/") !== -1; - - function iosVersion(userAgent) { - return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1]; - } - - function androidVersion(userAgent) { - return +(userAgent.match(/android (\d+)/) || [undefined, 0])[1]; - } - - function isIE(version, equation) { - var rv = -1, - re; - - if (navigator.appName == 'Microsoft Internet Explorer') { - re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); - } else if (navigator.appName == 'Netscape') { - re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})"); - } - - if (re && re.exec(navigator.userAgent) != null) { - rv = parseFloat(RegExp.$1); - } - - if (rv === -1) { return false; } - if (!version) { return true; } - if (!equation) { return version === rv; } - if (equation === "<") { return version < rv; } - if (equation === ">") { return version > rv; } - if (equation === "<=") { return version <= rv; } - if (equation === ">=") { return version >= rv; } - } - - return { - // Static variable needed, publicly accessible, to be able override it in unit tests - USER_AGENT: userAgent, - - /** - * Exclude browsers that are not capable of displaying and handling - * contentEditable as desired: - * - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable - * - IE < 8 create invalid markup and crash randomly from time to time - * - * @return {Boolean} - */ - supported: function() { - var userAgent = this.USER_AGENT.toLowerCase(), - // Essential for making html elements editable - hasContentEditableSupport = "contentEditable" in testElement, - // Following methods are needed in order to interact with the contentEditable area - hasEditingApiSupport = document.execCommand && document.queryCommandSupported && document.queryCommandState, - // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+ - hasQuerySelectorSupport = document.querySelector && document.querySelectorAll, - // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05) - isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1; - return hasContentEditableSupport - && hasEditingApiSupport - && hasQuerySelectorSupport - && !isIncompatibleMobileBrowser; - }, - - isTouchDevice: function() { - return this.supportsEvent("touchmove"); - }, - - isIos: function() { - return (/ipad|iphone|ipod/i).test(this.USER_AGENT); - }, - - isAndroid: function() { - return this.USER_AGENT.indexOf("Android") !== -1; - }, - - /** - * Whether the browser supports sandboxed iframes - * Currently only IE 6+ offers such feature