From 769ab5c4ada1e535d42cc1768abdd79e05198126 Mon Sep 17 00:00:00 2001 From: timdown Date: Mon, 18 Mar 2013 23:36:59 +0000 Subject: [PATCH 01/23] Fix for missing method from issue 157. git-svn-id: http://rangy.googlecode.com/svn/trunk@776 88e730d5-b869-a254-3594-6732aba23865 --- src/js/core/wrappedselection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/core/wrappedselection.js b/src/js/core/wrappedselection.js index fae57e7..d78f632 100644 --- a/src/js/core/wrappedselection.js +++ b/src/js/core/wrappedselection.js @@ -650,7 +650,7 @@ rangy.createModule("WrappedSelection", function(api, module) { var ranges = sel.getAllRanges(); sel.removeAllRanges(); for (var i = 0, len = ranges.length; i < len; ++i) { - if (!api.rangesEqual(range, ranges[i])) { + if (!rangesEqual(range, ranges[i])) { sel.addRange(ranges[i]); } } From bcd63d88b936637155d1dd3be2d1e77bfd601d6f Mon Sep 17 00:00:00 2001 From: timdown Date: Sun, 14 Apr 2013 23:49:46 +0000 Subject: [PATCH 02/23] Changed so that Rangy's own DOM4-compliant implementation is always used for selectNodeContents() (issue 162) git-svn-id: http://rangy.googlecode.com/svn/trunk@777 88e730d5-b869-a254-3594-6732aba23865 --- src/js/core/wrappedrange.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/js/core/wrappedrange.js b/src/js/core/wrappedrange.js index 857499d..2569a43 100644 --- a/src/js/core/wrappedrange.js +++ b/src/js/core/wrappedrange.js @@ -179,20 +179,11 @@ rangy.createModule("WrappedRange", function(api, module) { /*--------------------------------------------------------------------------------------------------------*/ - // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to - // the 0th character of the text node - range.selectNodeContents(testTextNode); - if (range.startContainer == testTextNode && range.endContainer == testTextNode && - range.startOffset == 0 && range.endOffset == testTextNode.length) { - rangeProto.selectNodeContents = function(node) { - this.nativeRange.selectNodeContents(node); - updateRangeProperties(this); - }; - } else { - rangeProto.selectNodeContents = function(node) { - this.setStartAndEnd(node, 0, dom.getNodeLength(node)); - }; - } + // 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)); + }; /*--------------------------------------------------------------------------------------------------------*/ From eb0dad3b243f6c28986b96185b44b1c431164241 Mon Sep 17 00:00:00 2001 From: timdown Date: Thu, 25 Apr 2013 23:23:00 +0000 Subject: [PATCH 03/23] Added unfinished support for AMD (issue 154) git-svn-id: http://rangy.googlecode.com/svn/trunk@778 88e730d5-b869-a254-3594-6732aba23865 --- src/js/core/core.js | 109 +++++++++++++------ src/js/core/dom.js | 2 +- src/js/core/domrange.js | 4 +- src/js/core/wrappedrange.js | 4 +- src/js/core/wrappedselection.js | 9 +- src/js/modules/rangy-commands.js | 4 +- src/js/modules/rangy-commands_new.js | 4 +- src/js/modules/rangy-cssclassapplier.js | 4 +- src/js/modules/rangy-events.js | 4 +- src/js/modules/rangy-highlighter-old.js | 4 +- src/js/modules/rangy-highlighter.js | 4 +- src/js/modules/rangy-position.js | 4 +- src/js/modules/rangy-selectionsaverestore.js | 4 +- src/js/modules/rangy-serializer.js | 3 +- src/js/modules/rangy-textcommands.js | 4 +- src/js/modules/rangy-textrange-old.js | 4 +- src/js/modules/rangy-textrange.js | 4 +- src/js/modules/rangy-util.js | 4 +- 18 files changed, 97 insertions(+), 82 deletions(-) diff --git a/src/js/core/core.js b/src/js/core/core.js index cce7e71..ae085ce 100644 --- a/src/js/core/core.js +++ b/src/js/core/core.js @@ -8,9 +8,9 @@ * Build date: %%build:date%% */ -var rangy; -rangy = rangy || (function() { +(function(global) { var log = log4javascript.getLogger("rangy.core"); + var amdSupported = (typeof global.define == "function" && global.define.amd); var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; @@ -68,13 +68,13 @@ rangy = rangy || (function() { 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: "%%build:version%%", initialized: false, @@ -178,7 +178,7 @@ rangy = rangy || (function() { })(); - // Very simple event handler wrapper function that doesn't attempt to solve issue such as "this" handling or + // 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")) { @@ -192,11 +192,11 @@ rangy = rangy || (function() { } 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); } @@ -247,10 +247,10 @@ rangy = rangy || (function() { var module, errorMessage; for (var moduleName in modules) { if ( (module = modules[moduleName]) instanceof Module ) { - module.init(); + module.init(module, api); } } - + // Call init listeners for (var i = 0, len = initListeners.length; i < len; ++i) { try { @@ -293,14 +293,36 @@ rangy = rangy || (function() { api.createMissingNativeApi = createMissingNativeApi; - function Module(name, initializer) { + function Module(name, dependencies, initializer) { this.name = name; + this.dependencies = dependencies; this.initialized = false; this.supported = false; - this.init = initializer; + this.initializer = initializer; } Module.prototype = { + init: function(api) { + var requiredModuleNames = this.dependencies || []; + for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) { + moduleName = requiredModuleNames[i]; + + requiredModule = modules[moduleName]; + if (!requiredModule || !(requiredModule instanceof Module)) { + throw new Error("required module '" + moduleName + "' not found"); + } + + requiredModule.init(); + + if (!requiredModule.supported) { + throw new Error("required module '" + moduleName + "' not supported"); + } + } + + // Now run initializer + this.initializer(this) + }, + fail: function(reason) { this.initialized = true; this.supported = false; @@ -321,9 +343,9 @@ rangy = rangy || (function() { return new Error("Error in Rangy " + this.name + " module: " + msg); } }; - - api.createModule = function(name, initFunc) { - var module = new Module(name, function() { + + function createModule(isCore, name, dependencies, initFunc) { + var newModule = new Module(name, dependencies, function(module) { if (!module.initialized) { module.initialized = true; try { @@ -336,24 +358,33 @@ rangy = rangy || (function() { } } }); - modules[name] = module; - }; - - api.requireModules = function(moduleNames) { - for (var i = 0, len = moduleNames.length, module, moduleName; i < len; ++i) { - moduleName = moduleNames[i]; - - module = modules[moduleName]; - if (!module || !(module instanceof Module)) { - throw new Error("required module '" + moduleName + "' not found"); - } + modules[name] = newModule; + +/* + // Add module AMD support + if (!isCore && amdSupported) { + global.define(["rangy-core"], function(rangy) { + + }); + } +*/ + } - module.init(); - - if (!module.supported) { - throw new Error("required module '" + moduleName + "' not supported"); - } + api.createModule = function(name) { + // Allow 2 or 3 arguments (second argument is an optional array of dependencies) + var initFunc, dependencies; + if (arguments.length == 2) { + initFunc = arguments[1]; + dependencies = []; + } else { + initFunc = arguments[2]; + dependencies = arguments[1]; } + createModule(false, name, dependencies, initFunc); + }; + + api.createCoreModule = function(name, dependencies, initFunc) { + createModule(true, name, dependencies, initFunc); }; /*----------------------------------------------------------------------------------------------------------------*/ @@ -400,5 +431,19 @@ rangy = rangy || (function() { // Add a fallback in case the DOMContentLoaded event isn't supported addListener(window, "load", loadHandler); - return api; -})(); + /*----------------------------------------------------------------------------------------------------------------*/ + + // AMD, for those who like this kind of thing + + if (amdSupported) { + // AMD. Register as an anonymous module. + global.define(function() { + api.amd = true; + return api; + }); + } + + // Create a "rangy" property of the global object in any case. Other Rangy modules (which use Rangy's own simple + // module system) rely on the existence of this global property + global.rangy = api; +})(this); diff --git a/src/js/core/dom.js b/src/js/core/dom.js index a55b62b..4775291 100644 --- a/src/js/core/dom.js +++ b/src/js/core/dom.js @@ -1,4 +1,4 @@ -rangy.createModule("DomUtil", function(api, module) { +rangy.createCoreModule("DomUtil", [], function(api, module) { var log = log4javascript.getLogger("rangy.dom"); var UNDEF = "undefined"; var util = api.util; diff --git a/src/js/core/domrange.js b/src/js/core/domrange.js index 3253746..3dfe822 100644 --- a/src/js/core/domrange.js +++ b/src/js/core/domrange.js @@ -1,6 +1,4 @@ -rangy.createModule("DomRange", function(api, module) { - api.requireModules( ["DomUtil"] ); - +rangy.createCoreModule("DomRange", ["DomUtil"], function(api, module) { var log = log4javascript.getLogger("rangy.DomRange"); var dom = api.dom; var util = api.util; diff --git a/src/js/core/wrappedrange.js b/src/js/core/wrappedrange.js index 2569a43..83a3c5b 100644 --- a/src/js/core/wrappedrange.js +++ b/src/js/core/wrappedrange.js @@ -1,6 +1,4 @@ -rangy.createModule("WrappedRange", function(api, module) { - api.requireModules( ["DomUtil", "DomRange"] ); - +rangy.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { var WrappedRange, WrappedTextRange; var dom = api.dom; var util = api.util; diff --git a/src/js/core/wrappedselection.js b/src/js/core/wrappedselection.js index d78f632..01b4ea4 100644 --- a/src/js/core/wrappedselection.js +++ b/src/js/core/wrappedselection.js @@ -1,8 +1,6 @@ // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections) -rangy.createModule("WrappedSelection", function(api, module) { - api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] ); - +rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) { api.config.checkSelectionRanges = true; var BOOLEAN = "boolean"; @@ -766,8 +764,9 @@ rangy.createModule("WrappedSelection", function(api, module) { assertNodeInSameDocument(this, node); var range = api.createRange(node); range.selectNodeContents(node); - this.removeAllRanges(); - this.addRange(range); + console.log("before", range.inspect()); + this.setSingleRange(range); + console.log("after", this._ranges[0].inspect()); }; selProto.deleteFromDocument = function() { diff --git a/src/js/modules/rangy-commands.js b/src/js/modules/rangy-commands.js index c022e11..9f452a5 100644 --- a/src/js/modules/rangy-commands.js +++ b/src/js/modules/rangy-commands.js @@ -18,15 +18,13 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("Commands", function(api, module) { +rangy.createModule("Commands", ["WrappedSelection"], function(api, module) { /* http://aryeh.name/spec/editcommands/autoimplementation.html https://bitbucket.org/ms2ger/dom-range/src/tip/test/ http://aryeh.name/gitweb.cgi?p=editcommands;a=blob_plain;f=editcommands.html;hb=HEAD */ - api.requireModules( ["WrappedSelection", "WrappedRange"] ); - var dom = api.dom; var log = log4javascript.getLogger("rangy.commands"); var BOOLEAN = "boolean", UNDEF = "undefined"; diff --git a/src/js/modules/rangy-commands_new.js b/src/js/modules/rangy-commands_new.js index a10f487..3e9bc1d 100644 --- a/src/js/modules/rangy-commands_new.js +++ b/src/js/modules/rangy-commands_new.js @@ -18,15 +18,13 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("Commands", function(api, module) { +rangy.createModule("Commands", ["WrappedSelection"], function(api, module) { /* http://aryeh.name/spec/editcommands/autoimplementation.html https://bitbucket.org/ms2ger/dom-range/src/tip/test/ http://aryeh.name/gitweb.cgi?p=editcommands;a=blob_plain;f=editcommands.html;hb=HEAD */ - api.requireModules( ["WrappedSelection", "WrappedRange"] ); - var dom = api.dom; var log = log4javascript.getLogger("rangy.commands"); var BOOLEAN = "boolean", UNDEF = "undefined"; diff --git a/src/js/modules/rangy-cssclassapplier.js b/src/js/modules/rangy-cssclassapplier.js index 91c1805..06d0baf 100644 --- a/src/js/modules/rangy-cssclassapplier.js +++ b/src/js/modules/rangy-cssclassapplier.js @@ -12,9 +12,7 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("CssClassApplier", function(api, module) { - api.requireModules( ["WrappedSelection", "WrappedRange"] ); - +rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { var dom = api.dom; var DomPosition = dom.DomPosition; var contains = dom.arrayContains; diff --git a/src/js/modules/rangy-events.js b/src/js/modules/rangy-events.js index 6db9b2a..24e0302 100644 --- a/src/js/modules/rangy-events.js +++ b/src/js/modules/rangy-events.js @@ -12,9 +12,7 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("Events", function(api, module) { - api.requireModules( ["Position"] ); - +rangy.createModule("Events", ["Position"], function(api, module) { var log = log4javascript.getLogger("rangy.events"); var DomPosition = api.dom.DomPosition; diff --git a/src/js/modules/rangy-highlighter-old.js b/src/js/modules/rangy-highlighter-old.js index 923de7e..179d0b8 100644 --- a/src/js/modules/rangy-highlighter-old.js +++ b/src/js/modules/rangy-highlighter-old.js @@ -9,9 +9,7 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("Highlighter", function(api, module) { - api.requireModules( ["SaveRestore", "Serializer", "CssClassApplier"] ); - +rangy.createModule("Highlighter", ["SaveRestore", "Serializer", "ClassApplier"], function(api, module) { var log = log4javascript.getLogger("rangy.Highlighter"); var dom = api.dom; var contains = dom.arrayContains; diff --git a/src/js/modules/rangy-highlighter.js b/src/js/modules/rangy-highlighter.js index 0c113c7..0976952 100644 --- a/src/js/modules/rangy-highlighter.js +++ b/src/js/modules/rangy-highlighter.js @@ -9,9 +9,7 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("Highlighter", function(api, module) { - api.requireModules( ["CssClassApplier"] ); - +rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) { var log = log4javascript.getLogger("rangy.Highlighter"); var dom = api.dom; var contains = dom.arrayContains; diff --git a/src/js/modules/rangy-position.js b/src/js/modules/rangy-position.js index e742081..3e69241 100644 --- a/src/js/modules/rangy-position.js +++ b/src/js/modules/rangy-position.js @@ -12,9 +12,7 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("Position", function(api, module) { - api.requireModules( ["WrappedSelection", "WrappedRange"] ); - +rangy.createModule("Position", ["WrappedSelection"], function(api, module) { //var log = log4javascript.getLogger("rangy.position"); var NUMBER = "number", UNDEF = "undefined"; diff --git a/src/js/modules/rangy-selectionsaverestore.js b/src/js/modules/rangy-selectionsaverestore.js index 0c68264..1a6d955 100644 --- a/src/js/modules/rangy-selectionsaverestore.js +++ b/src/js/modules/rangy-selectionsaverestore.js @@ -12,9 +12,7 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("SaveRestore", function(api, module) { - api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] ); - +rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) { var dom = api.dom; var markerTextChar = "\ufeff"; diff --git a/src/js/modules/rangy-serializer.js b/src/js/modules/rangy-serializer.js index f09134a..a8429fb 100644 --- a/src/js/modules/rangy-serializer.js +++ b/src/js/modules/rangy-serializer.js @@ -13,8 +13,7 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("Serializer", function(api, module) { - api.requireModules( ["WrappedSelection", "WrappedRange"] ); +rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) { var UNDEF = "undefined"; // encodeURIComponent and decodeURIComponent are required for cookie handling diff --git a/src/js/modules/rangy-textcommands.js b/src/js/modules/rangy-textcommands.js index d20e425..90e9582 100644 --- a/src/js/modules/rangy-textcommands.js +++ b/src/js/modules/rangy-textcommands.js @@ -12,9 +12,7 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("TextCommands", function(api, module) { - api.requireModules( ["WrappedSelection", "WrappedRange"] ); - +rangy.createModule("TextCommands", ["WrappedSelection"], function(api, module) { var dom = api.dom; var log = log4javascript.getLogger("rangy.textcommands"); diff --git a/src/js/modules/rangy-textrange-old.js b/src/js/modules/rangy-textrange-old.js index 748ddfc..d6d9cd8 100644 --- a/src/js/modules/rangy-textrange-old.js +++ b/src/js/modules/rangy-textrange-old.js @@ -63,9 +63,7 @@ * Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be * feature-tested */ -rangy.createModule("TextRange", function(api, module) { - api.requireModules( ["WrappedSelection"] ); - +rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { var UNDEF = "undefined"; var CHARACTER = "character", WORD = "word"; var dom = api.dom, util = api.util, DomPosition = dom.DomPosition; diff --git a/src/js/modules/rangy-textrange.js b/src/js/modules/rangy-textrange.js index fa0e0fc..c336d47 100644 --- a/src/js/modules/rangy-textrange.js +++ b/src/js/modules/rangy-textrange.js @@ -63,9 +63,7 @@ * Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be * feature-tested */ -rangy.createModule("TextRange", function(api, module) { - api.requireModules( ["WrappedSelection"] ); - +rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { var UNDEF = "undefined"; var CHARACTER = "character", WORD = "word"; var dom = api.dom, util = api.util; diff --git a/src/js/modules/rangy-util.js b/src/js/modules/rangy-util.js index c92289a..d583919 100644 --- a/src/js/modules/rangy-util.js +++ b/src/js/modules/rangy-util.js @@ -12,9 +12,7 @@ * Version: %%build:version%% * Build date: %%build:date%% */ -rangy.createModule("Util", function(api, module) { - api.requireModules( ["WrappedSelection", "WrappedRange"] ); - +rangy.createModule("Util", ["WrappedSelection"], function(api, module) { var rangeProto = api.rangePrototype; var selProto = api.selectionPrototype; From 908c71e02464737ace4131900c49a28f9f2ed99a Mon Sep 17 00:00:00 2001 From: timdown Date: Sat, 11 May 2013 14:56:09 +0000 Subject: [PATCH 04/23] Fix for issue 160 git-svn-id: http://rangy.googlecode.com/svn/trunk@779 88e730d5-b869-a254-3594-6732aba23865 --- src/js/modules/rangy-cssclassapplier.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/modules/rangy-cssclassapplier.js b/src/js/modules/rangy-cssclassapplier.js index 06d0baf..c94e8ff 100644 --- a/src/js/modules/rangy-cssclassapplier.js +++ b/src/js/modules/rangy-cssclassapplier.js @@ -869,6 +869,9 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { isAppliedToRanges: function(ranges) { var i = ranges.length; + if (i == 0) { + return false; + } while (i--) { if (!this.isAppliedToRange(ranges[i])) { return false; From 9b5f1d8e37880b88f43fb7b683c9c791693d3762 Mon Sep 17 00:00:00 2001 From: timdown Date: Sat, 11 May 2013 23:29:30 +0000 Subject: [PATCH 05/23] Fix for issue 165: calling rangeToTextRange on WrappedTextRange rather than relying on WrappedRange git-svn-id: http://rangy.googlecode.com/svn/trunk@780 88e730d5-b869-a254-3594-6732aba23865 --- src/js/core/wrappedrange.js | 78 ++++++++++++++++---------------- src/js/core/wrappedselection.js | 2 +- src/js/modules/rangy-position.js | 7 +-- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/js/core/wrappedrange.js b/src/js/core/wrappedrange.js index 83a3c5b..25d995b 100644 --- a/src/js/core/wrappedrange.js +++ b/src/js/core/wrappedrange.js @@ -281,19 +281,19 @@ rangy.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { 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): + 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): -
  • | a
  • b |
+
  • | a
  • b |
- var range = document.selection.createRange(); - alert(range.parentElement().id); // Should alert "ul" but alerts "b" + var range = document.selection.createRange(); + alert(range.parentElement().id); // Should alert "ul" but alerts "b" - This method returns the common ancestor node of the following: - - the parentElement() of the textRange - - the parentElement() of the textRange after calling collapse(true) - - the parentElement() of the textRange after calling collapse(false) - */ + This method returns the common ancestor node of the following: + - the parentElement() of the textRange + - the parentElement() of the textRange after calling collapse(true) + - the parentElement() of the textRange after calling collapse(false) + */ var getTextRangeContainerElement = function(textRange) { var parentEl = textRange.parentElement(); log.info("getTextRangeContainerElement parentEl is " + dom.inspectNode(parentEl)); @@ -403,35 +403,35 @@ rangy.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { if (/[\r\n]/.test(boundaryNode.data)) { /* - For the particular case of a boundary within a text node containing rendered line breaks (within a
-                     element, for example), we need a slightly complicated approach to get the boundary's offset in IE. The
-                     facts:
-
-                     - Each line break is represented as \r in the text node's data/nodeValue properties
-                     - Each line break is represented as \r\n in the TextRange's 'text' property
-                     - The 'text' property of the TextRange does not contain trailing line breaks
-
-                     To get round the problem presented by the final fact above, we can use the fact that TextRange's
-                     moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
-                     the same as the number of characters it was instructed to move. The simplest approach is to use this to
-                     store the characters moved when moving both the start and end of the range to the start of the document
-                     body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
-                     However, this is extremely slow when the document is large and the range is near the end of it. Clearly
-                     doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
-                     problem.
-
-                     Another approach that works is to use moveStart() to move the start boundary of the range up to the end
-                     boundary one character at a time and incrementing a counter with the value returned by the moveStart()
-                     call. However, the check for whether the start boundary has reached the end boundary is expensive, so
-                     this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
-                     the range within the document).
-
-                     The method below is a hybrid of the two methods above. It uses the fact that a string containing the
-                     TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
-                     text of the TextRange, so the start of the range is moved that length initially and then a character at
-                     a time to make up for any trailing line breaks not contained in the 'text' property. This has good
-                     performance in most situations compared to the previous two methods.
-                     */
+                    For the particular case of a boundary within a text node containing rendered line breaks (within a 
+                    element, for example), we need a slightly complicated approach to get the boundary's offset in IE. The
+                    facts:
+                    
+                    - Each line break is represented as \r in the text node's data/nodeValue properties
+                    - Each line break is represented as \r\n in the TextRange's 'text' property
+                    - The 'text' property of the TextRange does not contain trailing line breaks
+                    
+                    To get round the problem presented by the final fact above, we can use the fact that TextRange's
+                    moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
+                    the same as the number of characters it was instructed to move. The simplest approach is to use this to
+                    store the characters moved when moving both the start and end of the range to the start of the document
+                    body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
+                    However, this is extremely slow when the document is large and the range is near the end of it. Clearly
+                    doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
+                    problem.
+                    
+                    Another approach that works is to use moveStart() to move the start boundary of the range up to the end
+                    boundary one character at a time and incrementing a counter with the value returned by the moveStart()
+                    call. However, the check for whether the start boundary has reached the end boundary is expensive, so
+                    this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
+                    the range within the document).
+                    
+                    The method below is a hybrid of the two methods above. It uses the fact that a string containing the
+                    TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
+                    text of the TextRange, so the start of the range is moved that length initially and then a character at
+                    a time to make up for any trailing line breaks not contained in the 'text' property. This has good
+                    performance in most situations compared to the previous two methods.
+                    */
                     var tempRange = workingRange.duplicate();
                     var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
 
diff --git a/src/js/core/wrappedselection.js b/src/js/core/wrappedselection.js
index 01b4ea4..bf36dfb 100644
--- a/src/js/core/wrappedselection.js
+++ b/src/js/core/wrappedselection.js
@@ -525,7 +525,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio
             if (this.docSelection.type == CONTROL) {
                 addRangeToControlSelection(this, range);
             } else {
-                WrappedRange.rangeToTextRange(range).select();
+                api.WrappedTextRange.rangeToTextRange(range).select();
                 this._ranges[0] = range;
                 this.rangeCount = 1;
                 this.isCollapsed = this._ranges[0].collapsed;
diff --git a/src/js/modules/rangy-position.js b/src/js/modules/rangy-position.js
index 3e69241..3f76e9c 100644
--- a/src/js/modules/rangy-position.js
+++ b/src/js/modules/rangy-position.js
@@ -17,6 +17,7 @@ rangy.createModule("Position", ["WrappedSelection"], function(api, module) {
 
     var NUMBER = "number", UNDEF = "undefined";
     var WrappedRange = api.WrappedRange;
+    var WrappedTextRange = api.WrappedTextRange;
     var dom = api.dom, util = api.util, DomPosition = dom.DomPosition;
     
     // Feature detection
@@ -278,7 +279,7 @@ rangy.createModule("Position", ["WrappedSelection"], function(api, module) {
     if (api.features.implementsTextRange && elementSupportsGetBoundingClientRect) {
         rangeProto.getBoundingClientRect = function() {
             // We need a TextRange
-            var textRange = WrappedRange.rangeToTextRange(this);
+            var textRange = WrappedTextRange.rangeToTextRange(this);
 
             // Work around table problems (table cell bounding rects seem not to count if TextRange spans cells)
             var cells = this.getNodes([1], function(el) {
@@ -301,7 +302,7 @@ rangy.createModule("Position", ["WrappedSelection"], function(api, module) {
                             subRange.setStartAfter(lastTable);
                         }
                         subRange.setEndBefore(table);
-                        rects.push(WrappedRange.rangeToTextRange(subRange).getBoundingClientRect());
+                        rects.push(WrappedTextRange.rangeToTextRange(subRange).getBoundingClientRect());
                     }
 
                     if (this.containsNode(cell)) {
@@ -324,7 +325,7 @@ rangy.createModule("Position", ["WrappedSelection"], function(api, module) {
                 if (!endTable && lastTable) {
                     subRange = this.cloneRange();
                     subRange.setStartAfter(lastTable);
-                    rects.push(WrappedRange.rangeToTextRange(subRange).getBoundingClientRect());
+                    rects.push(WrappedTextRange.rangeToTextRange(subRange).getBoundingClientRect());
                 }
                 rect = mergeRects(rects);
             } else {

From fcae2f05807aa4be6b99cdf375b586d8fa9c2352 Mon Sep 17 00:00:00 2001
From: timdown 
Date: Mon, 3 Jun 2013 22:40:09 +0000
Subject: [PATCH 06/23] Added highlighter conveniences:
 getHighlightsInSelection() method and unhighlightSelection now returns the
 highlights that have been removed

git-svn-id: http://rangy.googlecode.com/svn/trunk@782 88e730d5-b869-a254-3594-6732aba23865
---
 src/js/modules/rangy-highlighter.js | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/js/modules/rangy-highlighter.js b/src/js/modules/rangy-highlighter.js
index 0976952..44ef989 100644
--- a/src/js/modules/rangy-highlighter.js
+++ b/src/js/modules/rangy-highlighter.js
@@ -384,7 +384,6 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) {
             var converter = this.converter;
             selection = selection || api.getSelection();
             var classApplier = this.classAppliers[className];
-            var highlights = this.highlights;
             var doc = selection.win.document;
             var containerElement = containerElementId ? doc.getElementById(containerElementId) : getBody(doc);
 
@@ -414,11 +413,16 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) {
             var intersectingHighlights = this.getIntersectingHighlights( selection.getAllRanges() );
             this.removeHighlights(intersectingHighlights);
             selection.removeAllRanges();
+            return intersectingHighlights;
         },
 
-        selectionOverlapsHighlight: function(selection) {
+        getHighlightsInSelection: function(selection) {
             selection = selection || api.getSelection();
-            return this.getIntersectingHighlights(selection.getAllRanges()).length > 0;
+            return this.getIntersectingHighlights(selection.getAllRanges());
+        },
+
+        selectionOverlapsHighlight: function(selection) {
+            return this.getHighlightsInSelection(selection).length > 0;
         },
 
         serialize: function(options) {

From df4d22fe187835ca10ebdf90c03bb6f760d2fb07 Mon Sep 17 00:00:00 2001
From: timdown 
Date: Fri, 7 Jun 2013 23:19:12 +0000
Subject: [PATCH 07/23] Fix for issue 169 (error handling in comparePoints)

git-svn-id: http://rangy.googlecode.com/svn/trunk@783 88e730d5-b869-a254-3594-6732aba23865
---
 src/js/core/dom.js | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/js/core/dom.js b/src/js/core/dom.js
index 4775291..ee70e8c 100644
--- a/src/js/core/dom.js
+++ b/src/js/core/dom.js
@@ -272,9 +272,13 @@ rangy.createCoreModule("DomUtil", [], function(api, module) {
             // Case 3: node C (container A or an ancestor) is a child node of B
             return getNodeIndex(nodeC) < offsetB  ? -1 : 1;
         } else {
-            log.debug("case 4");
-            // Case 4: containers are siblings or descendants of siblings
             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
+            log.debug("case 4");
             childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
             childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
 

From 9090009049c59b1fc530f7f017a9d1b88a8ea1ee Mon Sep 17 00:00:00 2001
From: timdown 
Date: Thu, 18 Jul 2013 22:46:33 +0000
Subject: [PATCH 08/23] Fix for issue 174 (canDeserializeRange bug) plus
 started on highlighter tests

git-svn-id: http://rangy.googlecode.com/svn/trunk@784 88e730d5-b869-a254-3594-6732aba23865
---
 src/js/modules/rangy-serializer.js            |  6 ++-
 ...cssclassapplier.html => classapplier.html} |  0
 ...pliertests.html => classappliertests.html} | 24 ++++++------
 ...ssappliertests.js => classappliertests.js} |  2 +-
 test/highlightertests.html                    | 38 +++++++++++++++++++
 test/highlightertests.js                      | 25 ++++++++++++
 test/index.html                               | 19 +++++-----
 test/serializertests.html                     | 37 ++++++++++++++++++
 test/serializertests.js                       | 10 +++++
 9 files changed, 137 insertions(+), 24 deletions(-)
 rename test/{cssclassapplier.html => classapplier.html} (100%)
 rename test/{cssclassappliertests.html => classappliertests.html} (74%)
 rename test/{cssclassappliertests.js => classappliertests.js} (97%)
 create mode 100644 test/highlightertests.html
 create mode 100644 test/highlightertests.js
 create mode 100644 test/serializertests.html
 create mode 100644 test/serializertests.js

diff --git a/src/js/modules/rangy-serializer.js b/src/js/modules/rangy-serializer.js
index a8429fb..9a45efc 100644
--- a/src/js/modules/rangy-serializer.js
+++ b/src/js/modules/rangy-serializer.js
@@ -164,6 +164,8 @@ rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) {
         return serialized;
     }
 
+    var deserializeRegex = /^([^,]+),([^,\{]+)(\{([^}]+)\})?$/;
+    
     function deserializeRange(serialized, rootNode, doc) {
         if (rootNode) {
             doc = doc || dom.getDocument(rootNode);
@@ -171,7 +173,7 @@ rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) {
             doc = doc || document;
             rootNode = doc.documentElement;
         }
-        var result = /^([^,]+),([^,\{]+)(\{([^}]+)\})?$/.exec(serialized);
+        var result = deserializeRegex.exec(serialized);
         var checksum = result[4], rootNodeChecksum = getElementChecksum(rootNode);
         if (checksum && checksum !== getElementChecksum(rootNode)) {
             throw module.createError("deserializeRange(): checksums of serialized range root node (" + checksum +
@@ -187,7 +189,7 @@ rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) {
         if (!rootNode) {
             rootNode = (doc || document).documentElement;
         }
-        var result = /^([^,]+),([^,]+)(\{([^}]+)\})?$/.exec(serialized);
+        var result = deserializeRegex.exec(serialized);
         var checksum = result[3];
         return !checksum || checksum === getElementChecksum(rootNode);
     }
diff --git a/test/cssclassapplier.html b/test/classapplier.html
similarity index 100%
rename from test/cssclassapplier.html
rename to test/classapplier.html
diff --git a/test/cssclassappliertests.html b/test/classappliertests.html
similarity index 74%
rename from test/cssclassappliertests.html
rename to test/classappliertests.html
index fb25306..50981b4 100644
--- a/test/cssclassappliertests.html
+++ b/test/classappliertests.html
@@ -1,8 +1,8 @@
 
 
 
-	
-		Rangy - CSS Class Applier Tests
+    
+        Rangy - Class Applier Tests
 
         
         
@@ -14,11 +14,11 @@
             log4javascript.setShowStackTraces(true);
             //log4javascript.setEnabled(false);
         
-		
+        
         
-		
+        
 
         
         
@@ -26,12 +26,12 @@
         
         
         
-        
-		
-	
-	
+        
+        
+    
+    
         
-
- +
+ diff --git a/test/cssclassappliertests.js b/test/classappliertests.js similarity index 97% rename from test/cssclassappliertests.js rename to test/classappliertests.js index ae7c949..101c671 100644 --- a/test/cssclassappliertests.js +++ b/test/classappliertests.js @@ -1,4 +1,4 @@ -xn.test.suite("CSS Class Applier module tests", function(s) { +xn.test.suite("Class Applier module tests", function(s) { s.tearDown = function() { document.getElementById("test").innerHTML = ""; }; diff --git a/test/highlightertests.html b/test/highlightertests.html new file mode 100644 index 0000000..bf051a0 --- /dev/null +++ b/test/highlightertests.html @@ -0,0 +1,38 @@ + + + + + Rangy - Highlighter Tests + + + + + + + + + + + + + + + + + + + +
+
+ + + diff --git a/test/highlightertests.js b/test/highlightertests.js new file mode 100644 index 0000000..bd32513 --- /dev/null +++ b/test/highlightertests.js @@ -0,0 +1,25 @@ +xn.test.suite("Highlighter module tests", function(s) { + s.tearDown = function() { + document.getElementById("test").innerHTML = ""; + }; + + s.test("highlightSelection test", function(t) { + var applier = rangy.createClassApplier("c1"); + var highlighter = rangy.createHighlighter(); + highlighter.addClassApplier(applier); + + var testEl = document.getElementById("test"); + var range = rangyTestUtils.createRangeInHtml(testEl, 'one [two] three four'); + range.select(); + + var highlights = highlighter.highlightSelection("c1"); + + t.assertEquals(highlights.length, 1); + + + //t.assertEquals(highlights.length, 1); + + + }); + +}, false); diff --git a/test/index.html b/test/index.html index d06176b..fac54da 100644 --- a/test/index.html +++ b/test/index.html @@ -1,20 +1,21 @@ - +
    -
  • range
  • -
  • save/restore
  • -
  • range tests
  • -
  • selection tests
  • -
  • CSS class applier tests
  • +
  • Range
  • +
  • Save/restore
  • +
  • Range tests
  • +
  • Selection tests
  • +
  • Class applier tests
  • selection save/restore tests
  • +
  • Serializer tests
  • +
  • Highlighter tests
  • Command tests
  • -
  • Text range tests
  • -
  • Text range 2 tests
  • +
  • Old text range tests
  • +
  • Text range tests
  • Build tests
  • TextRange-to-Range performance tests
  • Feature tests
  • diff --git a/test/serializertests.html b/test/serializertests.html new file mode 100644 index 0000000..d2a5f2b --- /dev/null +++ b/test/serializertests.html @@ -0,0 +1,37 @@ + + + + + Rangy - Serializer Tests + + + + + + + + + + + + + + + + + + +
    +
    + + + diff --git a/test/serializertests.js b/test/serializertests.js new file mode 100644 index 0000000..59773ea --- /dev/null +++ b/test/serializertests.js @@ -0,0 +1,10 @@ +xn.test.suite("Class Applier module tests", function(s) { + s.tearDown = function() { + document.getElementById("test").innerHTML = ""; + }; + + s.test("canDeserializeRange test", function(t) { + t.assertFalse(rangy.canDeserializeRange("0/9999:1,0/9999:20{a1b2c3d4}")) + }); + +}, false); \ No newline at end of file From ed00d1be8db81d24268c098ee69e280b2fbdd0a7 Mon Sep 17 00:00:00 2001 From: timdown Date: Thu, 18 Jul 2013 23:40:37 +0000 Subject: [PATCH 09/23] Implemented elementAttributes option in ClassApplier git-svn-id: http://rangy.googlecode.com/svn/trunk@785 88e730d5-b869-a254-3594-6732aba23865 --- src/js/modules/rangy-cssclassapplier.js | 90 +++++++++++++++---------- test/classappliertests.js | 42 +++++++++++- 2 files changed, 94 insertions(+), 38 deletions(-) diff --git a/src/js/modules/rangy-cssclassapplier.js b/src/js/modules/rangy-cssclassapplier.js index c94e8ff..98e039e 100644 --- a/src/js/modules/rangy-cssclassapplier.js +++ b/src/js/modules/rangy-cssclassapplier.js @@ -21,6 +21,17 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { var defaultTagName = "span"; + function each(obj, func) { + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + if (func(i, obj[i]) === false) { + return false; + } + } + } + return true; + } + function trim(str) { return str.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); } @@ -63,10 +74,6 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { return getSortedClassName(el1) == getSortedClassName(el2); } - function compareRanges(r1, r2) { - return r1.compareBoundaryPoints(r2.START_TO_START, r2); - } - function movePosition(position, oldParent, oldIndex, newParent, newIndex) { var node = position.node, offset = position.offset; @@ -182,20 +189,16 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { return false; } - function elementHasProps(el, props) { - var propValue; - for (var p in props) { - if (props.hasOwnProperty(p)) { - propValue = props[p]; - if (typeof propValue == "object") { - if (!elementHasProps(el[p], propValue)) { - return false; - } - } else if (el[p] !== propValue) { + function elementHasProperties(el, props) { + each(props, function(p, propValue) { + if (typeof propValue == "object") { + if (!elementHasProperties(el[p], propValue)) { return false; } + } else if (el[p] !== propValue) { + return false; } - } + }); return true; } @@ -444,23 +447,24 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly", "useExistingElements", "removeEmptyElements"]; - // TODO: Populate this with every attribute name that corresponds to a property with a different name + // TODO: Populate this with every attribute name that corresponds to a property with a different name. Really?? var attrNamesForProperties = {}; function ClassApplier(cssClass, options, tagNames) { - this.cssClass = cssClass; - var normalize, i, len, propName; + var normalize, i, len, propName, applier = this; + applier.cssClass = cssClass; - var elementPropertiesFromOptions = null; + var elementPropertiesFromOptions = null, elementAttributes = {}; // Initialize from options object if (typeof options == "object" && options !== null) { tagNames = options.tagNames; elementPropertiesFromOptions = options.elementProperties; + elementAttributes = options.elementAttributes; for (i = 0; propName = optionProperties[i++]; ) { if (options.hasOwnProperty(propName)) { - this[propName] = options[propName]; + applier[propName] = options[propName]; } } normalize = options.normalize; @@ -469,42 +473,47 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { } // Backward compatibility: the second parameter can also be a Boolean indicating to normalize after unapplying - this.normalize = (typeof normalize == "undefined") ? true : normalize; + applier.normalize = (typeof normalize == "undefined") ? true : normalize; // Initialize element properties and attribute exceptions - this.attrExceptions = []; - var el = document.createElement(this.elementTagName); - this.elementProperties = this.copyPropertiesToElement(elementPropertiesFromOptions, el, true); + applier.attrExceptions = []; + var el = document.createElement(applier.elementTagName); + applier.elementProperties = applier.copyPropertiesToElement(elementPropertiesFromOptions, el, true); + each(elementAttributes, function(attrName) { + applier.attrExceptions.push(attrName); + }); + applier.elementAttributes = elementAttributes; - this.elementSortedClassName = this.elementProperties.hasOwnProperty("className") ? - this.elementProperties.className : cssClass; + applier.elementSortedClassName = applier.elementProperties.hasOwnProperty("className") ? + applier.elementProperties.className : cssClass; // Initialize tag names - this.applyToAnyTagName = false; + applier.applyToAnyTagName = false; var type = typeof tagNames; if (type == "string") { if (tagNames == "*") { - this.applyToAnyTagName = true; + applier.applyToAnyTagName = true; } else { - this.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/); + applier.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/); } } else if (type == "object" && typeof tagNames.length == "number") { - this.tagNames = []; + applier.tagNames = []; for (i = 0, len = tagNames.length; i < len; ++i) { if (tagNames[i] == "*") { - this.applyToAnyTagName = true; + applier.applyToAnyTagName = true; } else { - this.tagNames.push(tagNames[i].toLowerCase()); + applier.tagNames.push(tagNames[i].toLowerCase()); } } } else { - this.tagNames = [this.elementTagName]; + applier.tagNames = [applier.elementTagName]; } } ClassApplier.prototype = { elementTagName: defaultTagName, elementProperties: {}, + elementAttributes: {}, ignoreWhiteSpace: true, applyToEditableOnly: false, useExistingElements: true, @@ -551,7 +560,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { if (createCopy) { elProps[p] = el[p]; - // Not all properties map to identically named attributes + // Not all properties map to identically-named attributes attrName = attrNamesForProperties.hasOwnProperty(p) ? attrNamesForProperties[p] : p; this.attrExceptions.push(attrName); } @@ -561,6 +570,14 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { return createCopy ? elProps : ""; }, + + copyAttributesToElement: function(attrs, el) { + for (var attrName in attrs) { + if (attrs.hasOwnProperty(attrName)) { + el.setAttribute(attrName, attrs[attrName]); + } + } + }, hasClass: function(node) { return node.nodeType == 1 && @@ -652,6 +669,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { createContainer: function(doc) { var el = doc.createElement(this.elementTagName); this.copyPropertiesToElement(this.elementProperties, el, false); + this.copyAttributesToElement(this.elementAttributes, el); addClass(el, this.cssClass); return el; }, @@ -663,7 +681,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { if (parent.childNodes.length == 1 && this.useExistingElements && contains(this.tagNames, parent.tagName.toLowerCase()) && - elementHasProps(parent, this.elementProperties)) { + elementHasProperties(parent, this.elementProperties)) { addClass(parent, this.cssClass); } else { @@ -677,7 +695,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { isRemovable: function(el) { return el.tagName.toLowerCase() == this.elementTagName && getSortedClassName(el) == this.elementSortedClassName - && elementHasProps(el, this.elementProperties) + && elementHasProperties(el, this.elementProperties) && !elementHasNonClassAttributes(el, this.attrExceptions) && this.isModifiable(el); }, diff --git a/test/classappliertests.js b/test/classappliertests.js index 101c671..f64b73b 100644 --- a/test/classappliertests.js +++ b/test/classappliertests.js @@ -269,7 +269,7 @@ xn.test.suite("Class Applier module tests", function(s) { } function getHtml(node, includeSelf) { - var html = ""; + var html = "", i, len, attr, children; if (node.nodeType == 1) { if (includeSelf) { html = "<" + node.tagName.toLowerCase(); @@ -282,10 +282,16 @@ xn.test.suite("Class Applier module tests", function(s) { if (node.href) { html += ' href="' + node.href + '"'; } + for ( i = 0, len = node.attributes.length; i < len; ++i) { + attr = node.attributes[i]; + if (!/^(id|href|class|style)$/.test(attr.name)) { + html += ' ' + attr.name + '="' + node.getAttribute(attr.name) + '"'; + } + } html += ">"; } - for (var i = 0, children = node.childNodes, len = children.length; i <= len; ++i) { + for (i = 0, children = node.childNodes, len = children.length; i <= len; ++i) { if (isElementRangeBoundary(node, i, range, true)) { html += "|"; } @@ -633,6 +639,38 @@ xn.test.suite("Class Applier module tests", function(s) { t.assert(applier.isAppliedToRange(range)); }); + s.test("Apply elementAttributes", function(t) { + var applier = rangy.createCssClassApplier("test", { + elementAttributes: { + "data-test": "foo" + } + }); + var testEl = document.getElementById("test"); + var range = createRangeInHtml(testEl, '
    1[2]3
    '); + applier.applyToRange(range); + t.assertEquals('
    1[2]3
    ', htmlAndRangeToString(testEl, range)); + }); + + s.test("Unapply simple", function(t) { + var applier = rangy.createCssClassApplier("test"); + var testEl = document.getElementById("test"); + var range = createRangeInHtml(testEl, '
    1[2]3
    '); + applier.undoToRange(range); + t.assertEquals('
    1[2]3
    ', htmlAndRangeToString(testEl, range)); + }); + + s.test("Unapply elementAttributes", function(t) { + var applier = rangy.createCssClassApplier("test", { + elementAttributes: { + "data-test": "foo" + } + }); + var testEl = document.getElementById("test"); + var range = createRangeInHtml(testEl, '
    1[2]3
    '); + applier.undoToRange(range); + t.assertEquals('
    1[2]3
    ', htmlAndRangeToString(testEl, range)); + }); + if (rangy.features.selectionSupportsMultipleRanges) { s.test("Undo to multiple ranges", function(t) { var testEl = document.getElementById("test"); From 8b95930f757c269306d2fbf653c477832ae3d82d Mon Sep 17 00:00:00 2001 From: timdown Date: Tue, 23 Jul 2013 22:43:19 +0000 Subject: [PATCH 10/23] Fix for issue 176 (class applier merge exception) git-svn-id: http://rangy.googlecode.com/svn/trunk@786 88e730d5-b869-a254-3594-6732aba23865 --- src/js/modules/rangy-cssclassapplier.js | 16 ++++++++++------ test/classappliertests.js | 25 ++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/js/modules/rangy-cssclassapplier.js b/src/js/modules/rangy-cssclassapplier.js index 98e039e..709098b 100644 --- a/src/js/modules/rangy-cssclassapplier.js +++ b/src/js/modules/rangy-cssclassapplier.js @@ -172,6 +172,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { name = attr1.name; if (name != "class") { attr2 = el2.attributes.getNamedItem(name); + if ( (attr1 === null) != (attr2 === null) ) return false; if (attr1.specified != attr2.specified) return false; if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false; } @@ -360,11 +361,11 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { } function createAdjacentMergeableTextNodeGetter(forward) { - var propName = forward ? "nextSibling" : "previousSibling"; + var siblingPropName = forward ? "nextSibling" : "previousSibling"; return function(textNode, checkParentElement) { var el = textNode.parentNode; - var adjacentNode = textNode[propName]; + var adjacentNode = textNode[siblingPropName]; if (adjacentNode) { // Can merge if the node's previous/next sibling is a text node if (adjacentNode && adjacentNode.nodeType == 3) { @@ -372,10 +373,13 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { } } else if (checkParentElement) { // Compare text node parent element with its sibling - adjacentNode = el[propName]; + adjacentNode = el[siblingPropName]; log.info("adjacentNode: " + adjacentNode); - if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)/* && adjacentNode.hasChildNodes()*/) { - return adjacentNode[forward ? "firstChild" : "lastChild"]; + if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) { + var adjacentNodeChild = adjacentNode[forward ? "firstChild" : "lastChild"]; + if (adjacentNodeChild && adjacentNodeChild.nodeType == 3) { + return adjacentNodeChild; + } } } return null; @@ -620,7 +624,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { for (var i = 0, len = textNodes.length; i < len; ++i) { textNode = textNodes[i]; precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo); - log.debug("Checking for merge. text node: " + textNode.data + ", parent: " + dom.inspectNode(textNode.parentNode) + ", preceding: " + (precedingTextNode ? precedingTextNode.data : null)); + log.debug("Checking for merge. text node: " + textNode.data + ", parent: " + dom.inspectNode(textNode.parentNode) + ", preceding: " + dom.inspectNode(precedingTextNode)); if (precedingTextNode) { if (!currentMerge) { currentMerge = new Merge(precedingTextNode); diff --git a/test/classappliertests.js b/test/classappliertests.js index f64b73b..c4da697 100644 --- a/test/classappliertests.js +++ b/test/classappliertests.js @@ -284,7 +284,8 @@ xn.test.suite("Class Applier module tests", function(s) { } for ( i = 0, len = node.attributes.length; i < len; ++i) { attr = node.attributes[i]; - if (!/^(id|href|class|style)$/.test(attr.name)) { + if (!attr) { alert(i) } + if (attr.specified && !/^(id|href|class|style)$/.test(attr.name)) { html += ' ' + attr.name + '="' + node.getAttribute(attr.name) + '"'; } } @@ -671,6 +672,28 @@ xn.test.suite("Class Applier module tests", function(s) { t.assertEquals('
    1[2]3
    ', htmlAndRangeToString(testEl, range)); }); + s.test("Merge error (issue 176)", function(t) { + var applier = rangy.createCssClassApplier("one"); + var testEl = document.getElementById("test"); + testEl.innerHTML = 'ab'; + var range = rangy.createRange(); + range.selectNode(testEl); + applier.applyToRange(range); + //t.assertEquals('[ab]', htmlAndRangeToString(testEl, range)); + }); + + s.test("Apply with className element property (issue 177)", function(t) { + var applier = rangy.createCssClassApplier("test", { + elementProperties: { + "className": "foo" + } + }); + var testEl = document.getElementById("test"); + var range = createRangeInHtml(testEl, '[1]'); + applier.applyToRange(range); + t.assertEquals('[1]', htmlAndRangeToString(testEl, range)); + }); + if (rangy.features.selectionSupportsMultipleRanges) { s.test("Undo to multiple ranges", function(t) { var testEl = document.getElementById("test"); From dfb0b910c50f2b82ee338311a7803c6ec3770c3e Mon Sep 17 00:00:00 2001 From: timdown Date: Thu, 25 Jul 2013 22:20:53 +0000 Subject: [PATCH 11/23] Changed MIME type for CSS file git-svn-id: http://rangy.googlecode.com/svn/trunk@787 88e730d5-b869-a254-3594-6732aba23865 From 1af306b2fb3cc97417591f4fe1ea82fc71a8dadf Mon Sep 17 00:00:00 2001 From: timdown Date: Thu, 25 Jul 2013 23:07:46 +0000 Subject: [PATCH 12/23] Added onElementCreate option to class applier git-svn-id: http://rangy.googlecode.com/svn/trunk@788 88e730d5-b869-a254-3594-6732aba23865 --- src/js/modules/rangy-cssclassapplier.js | 8 +++++--- test/classappliertests.js | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/js/modules/rangy-cssclassapplier.js b/src/js/modules/rangy-cssclassapplier.js index 709098b..c6a26db 100644 --- a/src/js/modules/rangy-cssclassapplier.js +++ b/src/js/modules/rangy-cssclassapplier.js @@ -333,10 +333,8 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { while ( (child = descendantNode.childNodes[descendantOffset]) ) { movePreservingPositions(child, newNode, newChildIndex++, positionsToPreserve); - //newNode.appendChild(child); } movePreservingPositions(newNode, parentNode, dom.getNodeIndex(descendantNode) + 1, positionsToPreserve); - //dom.insertAfter(newNode, descendantNode); return (descendantNode == node) ? newNode : splitNodeAt(node, parentNode, dom.getNodeIndex(newNode), positionsToPreserve); } else if (node != descendantNode) { newNode = descendantNode.parentNode; @@ -449,7 +447,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { }; var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly", "useExistingElements", - "removeEmptyElements"]; + "removeEmptyElements", "onElementCreate"]; // TODO: Populate this with every attribute name that corresponds to a property with a different name. Really?? var attrNamesForProperties = {}; @@ -522,6 +520,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { applyToEditableOnly: false, useExistingElements: true, removeEmptyElements: true, + onElementCreate: null, copyPropertiesToElement: function(props, el, createCopy) { var s, elStyle, elProps = {}, elPropsStyle, propValue, elPropValue, attrName; @@ -675,6 +674,9 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { this.copyPropertiesToElement(this.elementProperties, el, false); this.copyAttributesToElement(this.elementAttributes, el); addClass(el, this.cssClass); + if (this.onElementCreate) { + this.onElementCreate(el, this); + } return el; }, diff --git a/test/classappliertests.js b/test/classappliertests.js index c4da697..c7e385b 100644 --- a/test/classappliertests.js +++ b/test/classappliertests.js @@ -694,6 +694,25 @@ xn.test.suite("Class Applier module tests", function(s) { t.assertEquals('[1]', htmlAndRangeToString(testEl, range)); }); + s.test("onElementCreate test", function(t) { + var elementDataTest; + + var applier = rangy.createCssClassApplier("test", { + elementAttributes: { + "data-test": "foo" + }, + onElementCreate: function(el) { + elementDataTest = el.getAttribute("data-test"); + } + }); + + var testEl = document.getElementById("test"); + var range = createRangeInHtml(testEl, '[1]'); + applier.applyToRange(range); + + t.assertEquals(elementDataTest, "foo"); + }); + if (rangy.features.selectionSupportsMultipleRanges) { s.test("Undo to multiple ranges", function(t) { var testEl = document.getElementById("test"); From ab2b912a4424ee0c70a9ac0eb56a52c4f77911ac Mon Sep 17 00:00:00 2001 From: timdown Date: Fri, 26 Jul 2013 23:02:25 +0000 Subject: [PATCH 13/23] Fixed highlighter demo (issue 172) git-svn-id: http://rangy.googlecode.com/svn/trunk@789 88e730d5-b869-a254-3594-6732aba23865 --- demos/highlighter.html | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/demos/highlighter.html b/demos/highlighter.html index 13e3fb3..2d6bee9 100644 --- a/demos/highlighter.html +++ b/demos/highlighter.html @@ -106,8 +106,8 @@

    Highlighter


    - - + +

    Preserving highlights between page requests

    @@ -140,14 +140,14 @@

    Rangy Highlighter Module Demo

    match has 90 minutes of play, with a break of 15 minutes in the middle. The break in the middle is called half-time.

    -

    Competitions (this section is editable)

    -

    +

    Competitions

    +

    There are many competitions for football, for both football clubs and countries. Football clubs usually play other teams in their own country, with a few exceptions. Cardiff City F.C. from Wales for example, play in the English leagues and in the English FA Cup.

    -

    Who plays football (this section is editable and in pre-formatted text)

    -
    +        

    Who plays football (this section is in pre-formatted text)

    +
     Football is the world's most popular sport. It is played in more
     countries than any other game. In fact, FIFA (the Federation
     Internationale de Football Association) has more members than the
    @@ -157,9 +157,6 @@ 

    Who plays football (this section is editable and in pr

    -

    From aff4ef781e038741bc89369b59762590b89886bf Mon Sep 17 00:00:00 2001 From: timdown Date: Sat, 27 Jul 2013 23:42:26 +0000 Subject: [PATCH 14/23] Tweaks git-svn-id: http://rangy.googlecode.com/svn/trunk@790 88e730d5-b869-a254-3594-6732aba23865 --- src/js/core/wrappedselection.js | 2 +- src/js/modules/rangy-highlighter.js | 2 +- src/js/modules/rangy-util.js | 8 -------- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/js/core/wrappedselection.js b/src/js/core/wrappedselection.js index bf36dfb..38e9c95 100644 --- a/src/js/core/wrappedselection.js +++ b/src/js/core/wrappedselection.js @@ -841,7 +841,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio selProto.setStart = createStartOrEndSetter(true); selProto.setEnd = createStartOrEndSetter(false); - // Add cheeky select() method to Range prototype + // Add select() method to Range prototype. Any existing selection will be removed. api.rangePrototype.select = function(direction) { getSelection( this.getDocument() ).setSingleRange(this, direction); }; diff --git a/src/js/modules/rangy-highlighter.js b/src/js/modules/rangy-highlighter.js index 44ef989..bad9783 100644 --- a/src/js/modules/rangy-highlighter.js +++ b/src/js/modules/rangy-highlighter.js @@ -315,7 +315,7 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) { } } - var charRange, highlightCharRange, highlightRange, merged; + var charRange, highlightCharRange, merged; for (i = 0, len = charRanges.length; i < len; ++i) { charRange = charRanges[i]; merged = false; diff --git a/src/js/modules/rangy-util.js b/src/js/modules/rangy-util.js index d583919..0004749 100644 --- a/src/js/modules/rangy-util.js +++ b/src/js/modules/rangy-util.js @@ -70,14 +70,6 @@ rangy.createModule("Util", ["WrappedSelection"], function(api, module) { api.getSelection().selectNodeContents(node); }; - - /** - * Convenience method to select a range. Any existing selection will be removed. - */ - rangeProto.select = function(direction) { - api.getSelection( this.getDocument() ).setSingleRange(this, direction); - }; - rangeProto.selectSelectedTextElements = (function() { function isInlineElement(node) { return node.nodeType == 1 && api.dom.getComputedStyleProperty(node, "display") == "inline"; From 6a26afe4a0c142cd9eb140642d9fefa5fc9142ef Mon Sep 17 00:00:00 2001 From: timdown Date: Mon, 5 Aug 2013 23:23:51 +0000 Subject: [PATCH 15/23] Fix for issue 171. git-svn-id: http://rangy.googlecode.com/svn/trunk@791 88e730d5-b869-a254-3594-6732aba23865 --- demos/textrange.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/demos/textrange.html b/demos/textrange.html index 1adebde..2a15d7a 100644 --- a/demos/textrange.html +++ b/demos/textrange.html @@ -140,7 +140,9 @@ function initSnapToWords2() { gEBI("demo2").onmouseup = function() { rangy.getSelection().expand("word", { - includeTrailingSpace: true + wordOptions: { + includeTrailingSpace: true + } }); }; } @@ -148,7 +150,9 @@ function initSnapToWords3() { gEBI("demo3").onmouseup = function() { rangy.getSelection().expand("word", { - wordRegex: /[a-z0-9]+(['\-][a-z0-9]+)*/gi + wordOptions: { + wordRegex: /[a-z0-9]+(['\-][a-z0-9]+)*/gi + } }); }; } From 00feae36301c40c9266a2b4c1ac856596127629a Mon Sep 17 00:00:00 2001 From: timdown Date: Thu, 21 Nov 2013 17:02:00 +0000 Subject: [PATCH 16/23] Improvements to text range line break handling. Tidy of class applier. git-svn-id: http://rangy.googlecode.com/svn/trunk@796 88e730d5-b869-a254-3594-6732aba23865 --- paste.html | 71 ++++++++++++++++++++ src/js/core/dom.js | 2 +- src/js/modules/rangy-cssclassapplier.js | 20 +++--- src/js/modules/rangy-serializer.js | 12 ++-- src/js/modules/rangy-textrange.js | 87 ++++++++++++++++--------- test/textrangetests.js | 71 ++++++++++++++------ test/xntest.js | 58 +++++++++++++++-- 7 files changed, 249 insertions(+), 72 deletions(-) create mode 100644 paste.html diff --git a/paste.html b/paste.html new file mode 100644 index 0000000..f311801 --- /dev/null +++ b/paste.html @@ -0,0 +1,71 @@ + + + + + + + + Selected pasted content +
    + + +

    + Here is some nice text +
    + + + \ No newline at end of file diff --git a/src/js/core/dom.js b/src/js/core/dom.js index ee70e8c..35cc7fe 100644 --- a/src/js/core/dom.js +++ b/src/js/core/dom.js @@ -338,7 +338,7 @@ rangy.createCoreModule("DomUtil", [], function(api, module) { } if (node.nodeType == 1) { var idAttr = node.id ? ' id="' + node.id + '"' : ""; - return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "][" + node.innerHTML.slice(0, 20) + "]"; + return "<" + node.nodeName + idAttr + ">[" + getNodeIndex(node) + "][" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]"; } return node.nodeName; } diff --git a/src/js/modules/rangy-cssclassapplier.js b/src/js/modules/rangy-cssclassapplier.js index c6a26db..d19c9e4 100644 --- a/src/js/modules/rangy-cssclassapplier.js +++ b/src/js/modules/rangy-cssclassapplier.js @@ -204,22 +204,18 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { } var getComputedStyleProperty = dom.getComputedStyleProperty; - var isEditableElement; - - (function() { + var isEditableElement = (function() { var testEl = document.createElement("div"); - if (typeof testEl.isContentEditable == "boolean") { - isEditableElement = function(node) { + return typeof testEl.isContentEditable == "boolean" ? + function (node) { return node && node.nodeType == 1 && node.isContentEditable; - }; - } else { - isEditableElement = function(node) { + } : + function (node) { if (!node || node.nodeType != 1 || node.contentEditable == "false") { return false; } return node.contentEditable == "true" || isEditableElement(node.parentNode); }; - } })(); function isEditingHost(node) { @@ -864,6 +860,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { sel.setRanges(ranges); }, +/* getTextSelectedByRange: function(textNode, range) { var textRange = range.cloneRange(); textRange.selectNodeContents(textNode); @@ -874,6 +871,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { return text; }, +*/ isAppliedToRange: function(range) { if (range.collapsed || range.toString() == "") { @@ -917,6 +915,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { } }, +/* toggleRanges: function(ranges) { if (this.isAppliedToRanges(ranges)) { this.undoToRanges(ranges); @@ -924,6 +923,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { this.applyToRanges(ranges); } }, +*/ toggleSelection: function(win) { if (this.isAppliedToSelection(win)) { @@ -945,6 +945,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { return elements; }, +/* getElementsWithClassIntersectingSelection: function(win) { var sel = api.getSelection(win); var elements = []; @@ -959,6 +960,7 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { }); return elements; }, +*/ detach: function() {} }; diff --git a/src/js/modules/rangy-serializer.js b/src/js/modules/rangy-serializer.js index 9a45efc..891c04f 100644 --- a/src/js/modules/rangy-serializer.js +++ b/src/js/modules/rangy-serializer.js @@ -120,22 +120,22 @@ rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) { } function serializePosition(node, offset, rootNode) { - var pathBits = [], n = node; + var pathParts = [], n = node; rootNode = rootNode || dom.getDocument(node).documentElement; while (n && n != rootNode) { - pathBits.push(dom.getNodeIndex(n, true)); + pathParts.push(dom.getNodeIndex(n, true)); n = n.parentNode; } - return pathBits.join("/") + ":" + offset; + return pathParts.join("/") + ":" + offset; } function deserializePosition(serialized, rootNode, doc) { if (!rootNode) { rootNode = (doc || document).documentElement; } - var bits = serialized.split(":"); + var parts = serialized.split(":"); var node = rootNode; - var nodeIndices = bits[0] ? bits[0].split("/") : [], i = nodeIndices.length, nodeIndex; + var nodeIndices = parts[0] ? parts[0].split("/") : [], i = nodeIndices.length, nodeIndex; while (i--) { nodeIndex = parseInt(nodeIndices[i], 10); @@ -147,7 +147,7 @@ rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) { } } - return new dom.DomPosition(node, parseInt(bits[1], 10)); + return new dom.DomPosition(node, parseInt(parts[1], 10)); } function serializeRange(range, omitChecksum, rootNode) { diff --git a/src/js/modules/rangy-textrange.js b/src/js/modules/rangy-textrange.js index c336d47..db4608d 100644 --- a/src/js/modules/rangy-textrange.js +++ b/src/js/modules/rangy-textrange.js @@ -86,6 +86,7 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { // but not other browsers). Also test whether trailing spaces before
    elements are collapsed. var trailingSpaceInBlockCollapses = false; var trailingSpaceBeforeBrCollapses = false; + var trailingSpaceBeforeBlockCollapses = false; var trailingSpaceBeforeLineBreakInPreLineCollapses = true; (function() { @@ -105,8 +106,13 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { sel.collapse(el, 2); sel.setStart(el.firstChild, 0); trailingSpaceBeforeBrCollapses = ("" + sel).length == 1; - body.removeChild(el); + el.innerHTML = "1

    1

    "; + sel.collapse(el, 2); + sel.setStart(el.firstChild, 0); + trailingSpaceBeforeBlockCollapses = ("" + sel).length == 1; + + body.removeChild(el); sel.removeAllRanges(); })(); @@ -163,15 +169,17 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { var defaultCharacterOptions = { includeBlockContentTrailingSpace: true, includeSpaceBeforeBr: true, + includeSpaceBeforeBlock: true, includePreLineTrailingSpace: true }; var defaultCaretCharacterOptions = { includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses, includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses, + includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses, includePreLineTrailingSpace: true }; - + var defaultWordOptions = { "en": { wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi, @@ -336,26 +344,6 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { return getAncestors(node).concat([node]); } - // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI - function isHtmlNode(node) { - var ns; - return typeof (ns = node.namespaceURI) == UNDEF || (ns === null || ns == "http://www.w3.org/1999/xhtml"); - } - - function isHtmlElement(node, tagNames) { - if (!node || node.nodeType != 1 || !isHtmlNode(node)) { - return false; - } - switch (typeof tagNames) { - case "string": - return node.tagName.toLowerCase() == tagNames.toLowerCase(); - case "object": - return new RegExp("^(" + tagNames.join("|S") + ")$", "i").test(node.tagName); - default: - return true; - } - } - function nextNodeDescendants(node) { while (node && !node.nextSibling) { node = node.parentNode; @@ -389,8 +377,6 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { return null; } - - // Adpated from Aryeh's code. // "A whitespace node is either a Text node whose data is the empty string; or // a Text node whose data consists only of one or more tabs (0x0009), line @@ -530,10 +516,11 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { NON_SPACE = "NON_SPACE", UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE", COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE", + TRAILING_SPACE_BEFORE_BLOCK = "TRAILING_SPACE_BEFORE_BLOCK", TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK", TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR", - PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK"; - + PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK", + TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR"; extend(nodeProto, { isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"), @@ -621,6 +608,17 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { return false; }, "node"), + + isRenderedBlock: createCachingGetter("isRenderedBlock", function(el) { + // Ensure that a block element containing a
    is considered to have inner text + var brs = el.getElementsByTagName("br"); + for (var i = 0, len = brs.length; i < len; ++i) { + if (!isCollapsedNode(brs[i])) { + return true; + } + } + return this.hasInnerText(); + }, "node"), getTrailingSpace: createCachingGetter("trailingSpace", function(el) { if (el.tagName.toLowerCase() == "br") { @@ -645,7 +643,7 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { case "table-cell": return "\t"; default: - return this.hasInnerText(true) ? "\n" : ""; + return this.isRenderedBlock(true) ? "\n" : ""; } } return ""; @@ -662,7 +660,7 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { case "table-cell": break; default: - return this.hasInnerText(false) ? "\n" : ""; + return this.isRenderedBlock(false) ? "\n" : ""; } return ""; }, "node") @@ -827,7 +825,7 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { // Check if this position's character is invariant (i.e. not dependent on character options) and return it // if so if (this.isCharInvariant) { - log.debug("Character is invariant. Returning'" + this.character + "'"); + log.debug("Character is invariant. Returning '" + this.character + "'"); log.groupEnd(); return this.character; } @@ -880,10 +878,16 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { this.type = TRAILING_SPACE_BEFORE_BR; } else if (nextPos.isTrailingSpace && nextPos.character == "\n") { this.type = TRAILING_SPACE_IN_BLOCK; + } else if (nextPos.isLeadingSpace && nextPos.character == "\n") { + this.type = TRAILING_SPACE_BEFORE_BLOCK; } + + log.debug("nextPos.isLeadingSpace: " + nextPos.isLeadingSpace + ", this type: " + this.type); if (nextPos.character === "\n") { if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) { log.debug("Character is a space which is followed by a br. Policy from options is to collapse."); + } else if (this.type == TRAILING_SPACE_BEFORE_BLOCK && !characterOptions.includeSpaceBeforeBlock) { + log.debug("Character is a space which is followed by a block. Policy from options is to collapse."); } else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) { log.debug("Character is a space which is the final character in a block. Policy from options is to collapse."); } else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) { @@ -893,7 +897,23 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { if (this.isTrailingSpace) { log.debug("Trailing line break preceding another trailing line break is excluded."); } else if (this.isBr) { - log.debug("Br preceding a trailing line break is excluded."); + log.debug("Trailing line break (type " + nextPos.type + ", characterType " + nextPos.characterType + ") following a br is excluded but br may be included."); + nextPos.type = TRAILING_LINE_BREAK_AFTER_BR; + + if (getPreviousPos() && previousPos.isLeadingSpace && previousPos.character == "\n") { + log.debug("Trailing space following a br following a leading line break is excluded."); + nextPos.character = ""; + } else { + log.debug("Trailing space following a br not preceded by a leading line break is included."); + //character = "\n"; + //nextPos + //log.debug("Br preceding a trailing line break is excluded."); + /* + nextPos.character = ""; + log.debug("Br preceding a trailing line break included but excludes trailing line break."); + character = "\n"; + */ + } } } else { log.debug("Collapsible line break followed by a non-trailing line break is being included."); @@ -1376,7 +1396,7 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { case CHARACTER: charIterator = createCharacterIterator(pos, backward, null, characterOptions); while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) { - log.info("*** movePositionBy GOT CHAR " + newPos.character + "[" + newPos.character.charCodeAt(0) + "]"); + log.info("*** movePositionBy GOT CHAR " + currentPos.character + "[" + currentPos.character.charCodeAt(0) + "] at position " + currentPos.inspect()); ++unitsMoved; newPos = currentPos; log.debug("unitsMoved: " + unitsMoved + ", absCount: " + absCount) @@ -1503,6 +1523,8 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { chars.push(pos); text += currentChar; } + + //console.log("text " + text) if (isRegex) { result = searchTerm.exec(text); @@ -1523,6 +1545,7 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length); break; } + console.log(text.replace(/\s/g, function(m) { return "[" + m.charCodeAt(0) + "]"}), matchStartIndex) } // Check whether regex match extends to the end of the range @@ -1896,6 +1919,8 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { characterRange = rangeInfo.characterRange; range = api.createRange(containerNode); range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions); + console.log("New selected range: " + range.inspect()) + log.info("New selected range: " + range.inspect()); this.addRange(range, rangeInfo.backward); } } diff --git a/test/textrangetests.js b/test/textrangetests.js index 5ccc724..0c37f7c 100644 --- a/test/textrangetests.js +++ b/test/textrangetests.js @@ -448,25 +448,58 @@ xn.test.suite("Text Range module tests", function(s) { t.assertEquals(range.startOffset, 0); }); -/* - s.test("selection move() on block inside block (issue 114)", function(t) { - t.el.innerHTML = '
    1
    2
    '; - var firstTextNode = t.el.firstChild.firstChild; - var range = rangy.createRange(); - range.collapseToPoint(firstTextNode, 1); - var selRange = range.cloneRange(); - - var sel = rangy.getSelection(); - sel.addRange(range); - sel.move("character", 1); - selRange.setEnd(sel.focusNode, sel.focusOffset); - t.assertEquals(selRange.text(), "\n"); - - sel.move("character", 1); - selRange.setEnd(sel.focusNode, sel.focusOffset); - t.assertEquals(selRange.text(), "\n2"); - }); -*/ + s.test("range move() on br inside block", function(t) { + t.el.innerHTML = '
    x

    '; + var firstTextNode = t.el.firstChild.firstChild; + var range = rangy.createRange(); + range.collapseToPoint(firstTextNode, 0); + range.move("character", 2); + + t.assertEquals(range.startContainer, t.el); + t.assertEquals(range.startOffset, 1); + }); + + s.test("range move() on br inside block 2", function(t) { + t.el.innerHTML = '
    x

    y
    '; + var firstTextNode = t.el.firstChild.firstChild; + var range = rangy.createRange(); + range.collapseToPoint(firstTextNode, 0); + range.move("character", 2); + + t.assertEquals(range.startContainer, t.el.firstChild.lastChild); + t.assertEquals(range.startOffset, 0); + }); + + s.test("innerText on br inside block 1", function(t) { + t.el.innerHTML = '

    '; + t.assertEquals(rangy.innerText(t.el).replace(/\n/g, "\\n"), "\\n"); + }); + + s.test("innerText on br inside block 2", function(t) { + t.el.innerHTML = '
    x

    '; + t.assertEquals(rangy.innerText(t.el).replace(/\n/g, "\\n"), "x\\n"); + }); + + s.test("innerText on br inside block 3", function(t) { + t.el.innerHTML = '
    x

    y
    z'; + t.assertEquals(rangy.innerText(t.el).replace(/\n/g, "\\n"), "x\\ny\\nz"); + }); + + s.test("innerText on br inside block 4", function(t) { + t.el.innerHTML = '
    x

    y
    z'; + t.assertEquals(rangy.innerText(t.el).replace(/\n/g, "\\n"), "x\\ny\\nz"); + }); + + s.test("innerText on br inside block 5", function(t) { + t.el.innerHTML = 'x


    '; + t.assertEquals(rangy.innerText(t.el).replace(/\n/g, "\\n"), "x\\n\\n"); + }); + + s.test("innerText on br inside block 6", function(t) { + t.el.innerHTML = '

    '; + t.assertEquals(rangy.innerText(t.el).replace(/\n/g, "\\n"), "\\n"); + }); + s.test("selectCharacters on text node", function(t) { t.el.innerHTML = 'One Two'; diff --git a/test/xntest.js b/test/xntest.js index ef0651e..35b0080 100644 --- a/test/xntest.js +++ b/test/xntest.js @@ -44,7 +44,7 @@ if (!Function.prototype.apply) { /* -------------------------------------------------------------------------- */ -var xn = new Object(); +var xn = {}; (function() { // Utility functions @@ -222,6 +222,9 @@ var xn = new Object(); var startTime; var loaded = false; var anyFailed = false; + var runningSingleTest = false; + var singleTestId = null; + var singleTest = null; var log4javascriptEnabled = false; @@ -235,6 +238,13 @@ var xn = new Object(); var init = function() { if (initialized) { return true; } + + var query = window.location.search, matches; + + if ( query && (matches = query.match(/^\?test=([\d]+)/)) ) { + runningSingleTest = true; + singleTestId = parseInt(matches[1]); + } container = document.createElement("div"); @@ -333,11 +343,18 @@ var xn = new Object(); this.log("adding a test named " + name); var t = new Test(name, callback, this, setUp, tearDown); this.tests.push(t); + + console.log(t.index, singleTestId) + if (runningSingleTest && t.index == singleTestId) { + singleTest = t; + } + return t; }; Suite.prototype.build = function() { // Build the elements used by the suite var suite = this; + this.testFailed = false; this.container = document.createElement("div"); this.container.className = "xn_test_suite_container"; @@ -376,16 +393,26 @@ var xn = new Object(); // invoke callback to build the tests this.callback.apply(this, [this]); + if (runningSingleTest) { + if (singleTest.suite == this) { + this.tests = [singleTest]; + } else { + container.removeChild(this.container); + } + } + this.headingTextNode.data = this.name + " [" + this.tests.length + "]" }; Suite.prototype.run = function() { - this.log("running suite '%s'", this.name); - this.startTime = new Date(); + if (!runningSingleTest || singleTest.suite == this) { + this.log("running suite '%s'", this.name); + this.startTime = new Date(); - // now run the first test - this._currentIndex = 0; - this.runNextTest(); + // now run the first test + this._currentIndex = 0; + this.runNextTest(); + } }; Suite.prototype.updateProgressBar = function() { @@ -474,6 +501,9 @@ var xn = new Object(); /** * Create a new test */ + + var testIndex = 0; + var Test = function(name, callback, suite, setUp, tearDown) { this.name = name; this.callback = callback; @@ -485,6 +515,20 @@ var xn = new Object(); this.assertCount = 0; this.logMessages = []; this.logExpanded = false; + this.index = ++testIndex; + }; + + Test.prototype.createSingleTestButton = function() { + var test = this; + var frag = document.createDocumentFragment(); + frag.appendChild( document.createTextNode(" (") ); + var singleTestButton = frag.appendChild( document.createElement("a") ); + singleTestButton.innerHTML = "This test only"; + var l = window.location; + var currentUrl = l.search ? l.href.replace(l.search, "") : l.href; + singleTestButton.href = currentUrl + "?test=" + test.index; + frag.appendChild( document.createTextNode(")") ); + return frag; }; /** @@ -496,6 +540,7 @@ var xn = new Object(); var text = this.name + " passed in " + timeTaken + "ms"; this.reportHeading.appendChild(document.createTextNode(text)); + this.reportHeading.appendChild(this.createSingleTestButton()); this.reportHeading.className = "success"; var dd = document.createElement("dd"); @@ -517,6 +562,7 @@ var xn = new Object(); this.reportHeading.className = "failure"; var text = document.createTextNode(this.name + " failed in " + timeTaken + "ms"); this.reportHeading.appendChild(text); + this.reportHeading.appendChild(this.createSingleTestButton()); var dd = document.createElement("dd"); dd.appendChild(document.createTextNode(msg)); From 876e29de0ea15845573809f37b7ac8dc678ff21f Mon Sep 17 00:00:00 2001 From: timdown Date: Thu, 21 Nov 2013 18:53:11 +0000 Subject: [PATCH 17/23] Fix for issue 190: a boundary container text node is included in getNodes() only if the range contains at least one character from the node git-svn-id: http://rangy.googlecode.com/svn/trunk@797 88e730d5-b869-a254-3594-6732aba23865 --- src/js/core/domrange.js | 20 +++++++++++++++++-- test/rangetests.js | 43 ++++++++++++++++++++++++++++++++++++++++- test/xntest.js | 1 - 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/js/core/domrange.js b/src/js/core/domrange.js index 3dfe822..28815c3 100644 --- a/src/js/core/domrange.js +++ b/src/js/core/domrange.js @@ -164,9 +164,25 @@ rangy.createCoreModule("DomRange", ["DomUtil"], function(api, module) { var nodes = []; iterateSubtree(new RangeIterator(range, false), function(node) { - if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) { - nodes.push(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; } diff --git a/test/rangetests.js b/test/rangetests.js index 21a6b7e..a05a9cf 100644 --- a/test/rangetests.js +++ b/test/rangetests.js @@ -29,7 +29,6 @@ function testExceptionCode(t, func, code) { function testRangeCreator(docs, docName, rangeCreator, rangeCreatorName) { xn.test.suite(rangeCreatorName + " in " + docName + " document", function(s) { var doc; - var DomRange = rangy.DomRange; var DOMException = rangy.DOMException; var RangeException = rangy.RangeException; var testRange = rangeCreator(document); @@ -645,6 +644,48 @@ function testRangeCreator(docs, docName, rangeCreator, rangeCreatorName) { t.assertEquals(b, range.endContainer); t.assertEquals(3, range.endOffset); }); + + function concatRangeTextNodes(range) { + var text = ""; + var nodes = range.getNodes([3]); + for (var i = 0, len = nodes.length; i < len; ++i) { + text += nodes[i].nodeValue; + } + return text; + } + + s.test("Concatenate getNodes([3]) after splitBoundaries same as toString - simple", function(t) { + var range = rangeCreator(doc); + range.setStartAndEnd(t.nodes.plainText, 1, t.nodes.boldText, 3); + t.assertEquals(concatRangeTextNodes(range), "plainbold"); + range.splitBoundaries(); + t.assertEquals(concatRangeTextNodes(range), "lainbol"); + t.assertEquals(range.toString(), "lainbol"); + }); + + s.test("Concatenate getNodes([3]) after splitBoundaries same as toString - end at position 0 in text node (issue 190)", function(t) { + var range = rangeCreator(doc); + range.setStartAndEnd(t.nodes.plainText, 1, t.nodes.boldText, 0); + range.splitBoundaries(); + t.assertEquals(concatRangeTextNodes(range), "lain"); + t.assertEquals(range.toString(), "lain"); + }); + + s.test("Concatenate getNodes([3]) after splitBoundaries same as toString - start position at end of text node (issue 190)", function(t) { + var range = rangeCreator(doc); + range.setStartAndEnd(t.nodes.plainText, 5, t.nodes.boldText, 3); + range.splitBoundaries(); + t.assertEquals(concatRangeTextNodes(range), "bol"); + t.assertEquals(range.toString(), "bol"); + }); + + s.test("Concatenate getNodes([3]) after splitBoundaries same as toString - start position at end of text node and end at position 0 in text node (issue 190)", function(t) { + var range = rangeCreator(doc); + range.setStartAndEnd(t.nodes.plainText, 5, t.nodes.boldText, 0); + range.splitBoundaries(); + t.assertEquals(concatRangeTextNodes(range), ""); + t.assertEquals(range.toString(), ""); + }); } if (testRange.normalizeBoundaries) { diff --git a/test/xntest.js b/test/xntest.js index 35b0080..813bacc 100644 --- a/test/xntest.js +++ b/test/xntest.js @@ -344,7 +344,6 @@ var xn = {}; var t = new Test(name, callback, this, setUp, tearDown); this.tests.push(t); - console.log(t.index, singleTestId) if (runningSingleTest && t.index == singleTestId) { singleTest = t; } From 9a15638ec1690c28eced94a3a144d1e824d946b1 Mon Sep 17 00:00:00 2001 From: timdown Date: Fri, 22 Nov 2013 00:33:23 +0000 Subject: [PATCH 18/23] Fix for issue 189. Original selection is now restored after Rangy initialization. git-svn-id: http://rangy.googlecode.com/svn/trunk@798 88e730d5-b869-a254-3594-6732aba23865 --- src/js/core/domrange.js | 6 +- src/js/core/wrappedselection.js | 95 +++++++++++++++++-------- src/js/modules/rangy-cssclassapplier.js | 6 +- src/js/modules/rangy-textcommands.js | 12 ++-- 4 files changed, 76 insertions(+), 43 deletions(-) diff --git a/src/js/core/domrange.js b/src/js/core/domrange.js index 28815c3..eef5b0a 100644 --- a/src/js/core/domrange.js +++ b/src/js/core/domrange.js @@ -659,16 +659,16 @@ rangy.createCoreModule("DomRange", ["DomUtil"], function(api, module) { if (sc === this.endContainer && isCharacterDataNode(sc)) { return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; } else { - var textBits = [], iterator = new RangeIterator(this, true); + var textParts = [], iterator = new RangeIterator(this, true); log.info("toString iterator: " + dom.inspectNode(iterator._first) + ", " + dom.inspectNode(iterator._last)); iterateSubtree(iterator, function(node) { // Accept only text or CDATA nodes, not comments if (node.nodeType == 3 || node.nodeType == 4) { - textBits.push(node.data); + textParts.push(node.data); } }); iterator.detach(); - return textBits.join(""); + return textParts.join(""); } }, diff --git a/src/js/core/wrappedselection.js b/src/js/core/wrappedselection.js index 38e9c95..998d60e 100644 --- a/src/js/core/wrappedselection.js +++ b/src/js/core/wrappedselection.js @@ -4,6 +4,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio api.config.checkSelectionRanges = true; var BOOLEAN = "boolean"; + var NUMBER = "number"; var dom = api.dom; var util = api.util; var isHostMethod = util.isHostMethod; @@ -24,7 +25,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio // 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") ? (dir == "backward") : !!dir; + return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir; } function getWindow(win, methodName) { @@ -47,6 +48,14 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio 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 @@ -90,16 +99,25 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio // 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"); + 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) { + typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) { (function() { // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are @@ -111,6 +129,16 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio // 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"; @@ -126,20 +154,35 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio sel.removeAllRanges(); // Test whether the native selection is capable of supporting multiple ranges - 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); + if (!selectionHasMultipleRanges) { + var r2 = r1.cloneRange(); + r1.setStart(textNode, 0); + r2.setEnd(textNode, 3); + r2.setStart(textNode, 2); + sel.addRange(r1); + sel.addRange(r2); + + selectionSupportsMultipleRanges = (sel.rangeCount == 2); + r2.detach(); + } // Clean up body.removeChild(testEl); sel.removeAllRanges(); r1.detach(); - r2.detach(); + + for (i = 0; i < originalSelectionRangeCount; ++i) { + if (i == 0 && originalSelectionBackward) { + if (addRangeBackwardToNative) { + addRangeBackwardToNative(sel, originalSelectionRanges[i]); + } else { + api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because browser does not support Selection.extend"); + sel.addRange(originalSelectionRanges[i]) + } + } else { + sel.addRange(originalSelectionRanges[i]) + } + } } })(); } @@ -337,7 +380,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio var cachedRangySelections = []; - function findCachedSelection(win, action) { + function actOnCachedSelection(win, action) { var i = cachedRangySelections.length, cached, sel; while (i--) { cached = cachedRangySelections[i]; @@ -368,7 +411,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio win = getWindow(win, "getNativeSelection"); - var sel = findCachedSelection(win); + var sel = actOnCachedSelection(win); var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null; if (sel) { sel.nativeSelection = nativeSel; @@ -416,11 +459,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio }; var addRangeBackward = function(sel, range) { - var doc = DomRange.getRangeDocument(range); - var endRange = api.createRange(doc); - endRange.collapseToPoint(range.endContainer, range.endOffset); - sel.nativeSelection.addRange(getNativeRange(endRange)); - sel.nativeSelection.extend(range.startContainer, range.startOffset); + addRangeBackwardToNative(sel.nativeSelection, range); sel.refresh(); }; @@ -577,7 +616,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio updateEmptySelection(sel); } }; - } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") { + } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) { refreshSelection = function(sel) { if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) { updateControlSelection(sel); @@ -693,13 +732,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio // Detecting if a selection is backward var selectionIsBackward; if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) { - selectionIsBackward = function(sel) { - var backward = false; - if (sel.anchorNode) { - backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1); - } - return backward; - }; + selectionIsBackward = winSelectionIsBackward; selProto.isBackward = function() { return selectionIsBackward(this); @@ -770,7 +803,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio }; selProto.deleteFromDocument = function() { - // Sepcial behaviour required for Control selections + // Sepcial behaviour required for IE's control selections if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) { var controlRange = this.docSelection.createRange(); var element; @@ -918,12 +951,12 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio }; selProto.detach = function() { - findCachedSelection(this.win, "delete"); + actOnCachedSelection(this.win, "delete"); deleteProperties(this); }; WrappedSelection.detachAll = function() { - findCachedSelection(null, "deleteAll"); + actOnCachedSelection(null, "deleteAll"); }; WrappedSelection.inspect = inspect; diff --git a/src/js/modules/rangy-cssclassapplier.js b/src/js/modules/rangy-cssclassapplier.js index d19c9e4..cac434b 100644 --- a/src/js/modules/rangy-cssclassapplier.js +++ b/src/js/modules/rangy-cssclassapplier.js @@ -434,11 +434,11 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { }, toString: function() { - var textBits = []; + var textParts = []; for (var i = 0, len = this.textNodes.length; i < len; ++i) { - textBits[i] = "'" + this.textNodes[i].data + "'"; + textParts[i] = "'" + this.textNodes[i].data + "'"; } - return "[Merge(" + textBits.join(",") + ")]"; + return "[Merge(" + textParts.join(",") + ")]"; } }; diff --git a/src/js/modules/rangy-textcommands.js b/src/js/modules/rangy-textcommands.js index 90e9582..e0f0fde 100644 --- a/src/js/modules/rangy-textcommands.js +++ b/src/js/modules/rangy-textcommands.js @@ -184,11 +184,11 @@ rangy.createModule("TextCommands", ["WrappedSelection"], function(api, module) { Merge.prototype = { doMerge: function() { - var textBits = [], textNode, parent, text; + var textParts = [], textNode, parent, text; for (var i = 0, len = this.textNodes.length; i < len; ++i) { textNode = this.textNodes[i]; parent = textNode.parentNode; - textBits[i] = textNode.data; + textParts[i] = textNode.data; if (i) { parent.removeChild(textNode); if (!parent.hasChildNodes()) { @@ -196,7 +196,7 @@ rangy.createModule("TextCommands", ["WrappedSelection"], function(api, module) { } } } - this.firstTextNode.data = text = textBits.join(""); + this.firstTextNode.data = text = textParts.join(""); return text; }, @@ -209,11 +209,11 @@ rangy.createModule("TextCommands", ["WrappedSelection"], function(api, module) { }, toString: function() { - var textBits = []; + var textParts = []; for (var i = 0, len = this.textNodes.length; i < len; ++i) { - textBits[i] = "'" + this.textNodes[i].data + "'"; + textParts[i] = "'" + this.textNodes[i].data + "'"; } - return "[Merge(" + textBits.join(",") + ")]"; + return "[Merge(" + textParts.join(",") + ")]"; } }; From eb2a44f70c2457b0822d91ffa32efea7fe9f1e6d Mon Sep 17 00:00:00 2001 From: timdown Date: Sat, 23 Nov 2013 00:48:13 +0000 Subject: [PATCH 19/23] Fix for issue 188 (preserving range when removing empty containers in class applier) git-svn-id: http://rangy.googlecode.com/svn/trunk@799 88e730d5-b869-a254-3594-6732aba23865 --- src/js/modules/rangy-cssclassapplier.js | 33 ++++++++++++++++++++++--- test/classappliertests.js | 24 ++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/js/modules/rangy-cssclassapplier.js b/src/js/modules/rangy-cssclassapplier.js index cac434b..bc0ea29 100644 --- a/src/js/modules/rangy-cssclassapplier.js +++ b/src/js/modules/rangy-cssclassapplier.js @@ -76,11 +76,10 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { function movePosition(position, oldParent, oldIndex, newParent, newIndex) { var node = position.node, offset = position.offset; - var newNode = node, newOffset = offset; if (node == newParent && offset > newIndex) { - newOffset++; + ++newOffset; } if (node == oldParent && (offset == oldIndex || offset == oldIndex + 1)) { @@ -89,12 +88,19 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { } if (node == oldParent && offset > oldIndex + 1) { - newOffset--; + --newOffset; } position.node = newNode; position.offset = newOffset; } + + function movePositionWhenRemovingNode(position, parentNode, index) { + log.debug("movePositionWhenRemovingNode " + position, position.node == parentNode, position.offset, index) + if (position.node == parentNode && position.offset > index) { + --position.offset; + } + } function movePreservingPositions(node, newParent, newIndex, positionsToPreserve) { log.debug("movePreservingPositions " + dom.inspectNode(node) + " to index " + newIndex + " in " + dom.inspectNode(newParent), positionsToPreserve); @@ -117,6 +123,19 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { newParent.insertBefore(node, newParent.childNodes[newIndex]); } } + + function removePreservingPositions(node, positionsToPreserve) { + log.debug("removePreservingPositions " + dom.inspectNode(node), positionsToPreserve); + + var oldParent = node.parentNode; + var oldIndex = dom.getNodeIndex(node); + + for (var i = 0, position; position = positionsToPreserve[i++]; ) { + movePositionWhenRemovingNode(position, oldParent, oldIndex); + } + + node.parentNode.removeChild(node); + } function moveChildrenPreservingPositions(node, newParent, newIndex, removeNode, positionsToPreserve) { var child, children = []; @@ -715,10 +734,16 @@ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) { return applier.isEmptyContainer(el); }); + var rangesToPreserve = [range] + var positionsToPreserve = getRangeBoundaries(rangesToPreserve); + for (var i = 0, node; node = nodesToRemove[i++]; ) { log.debug("Removing empty container " + dom.inspectNode(node)); - node.parentNode.removeChild(node); + removePreservingPositions(node, positionsToPreserve); } + + // Update the range from the preserved boundary positions + updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve); }, undoToTextNode: function(textNode, range, ancestorWithClass, positionsToPreserve) { diff --git a/test/classappliertests.js b/test/classappliertests.js index c7e385b..1248176 100644 --- a/test/classappliertests.js +++ b/test/classappliertests.js @@ -713,6 +713,30 @@ xn.test.suite("Class Applier module tests", function(s) { t.assertEquals(elementDataTest, "foo"); }); + s.test("removeEmptyContainers error (issue 188)", function(t) { + var applier = rangy.createCssClassApplier("test"); + var testEl = document.getElementById("test"); + testEl.innerHTML = ''; + var range = rangy.createRange(); + range.selectNodeContents(testEl); + applier.applyToRange(range); + }); + + s.test("removeEmptyContainers error undoToRange (issue 188)", function(t) { + var applier = rangy.createCssClassApplier("test"); + var testEl = document.getElementById("test"); + testEl.innerHTML = '123'; + var range = rangy.createRange(); + range.setStartAndEnd(testEl, 1, 4); + applier.undoToRange(range); + t.assertEquals(testEl.innerHTML, "123"); + t.assertEquals(testEl.childNodes.length, 1); + t.assertEquals(range.startContainer, testEl.firstChild); + t.assertEquals(range.startOffset, 1); + t.assertEquals(range.endContainer, testEl.firstChild); + t.assertEquals(range.endOffset, 2); + }); + if (rangy.features.selectionSupportsMultipleRanges) { s.test("Undo to multiple ranges", function(t) { var testEl = document.getElementById("test"); From 2be294b7e655185978f0794acfb4ac69dbd28aef Mon Sep 17 00:00:00 2001 From: timdown Date: Fri, 29 Nov 2013 00:26:23 +0000 Subject: [PATCH 20/23] Updated dev files from latest release git-svn-id: http://rangy.googlecode.com/svn/trunk@800 88e730d5-b869-a254-3594-6732aba23865 --- dev/rangy-core.js | 6 +- dev/rangy-cssclassapplier.js | 6 +- dev/rangy-highlighter.js | 6 +- dev/rangy-selectionsaverestore.js | 6 +- dev/rangy-serializer.js | 6 +- dev/rangy-textrange.js | 6 +- dev/uncompressed/rangy-core.js | 396 +++++++++++------- dev/uncompressed/rangy-cssclassapplier.js | 180 +++++--- dev/uncompressed/rangy-highlighter.js | 20 +- .../rangy-selectionsaverestore.js | 8 +- dev/uncompressed/rangy-serializer.js | 25 +- dev/uncompressed/rangy-textrange.js | 82 ++-- 12 files changed, 455 insertions(+), 292 deletions(-) diff --git a/dev/rangy-core.js b/dev/rangy-core.js index 81b4b37..67fef3f 100644 --- a/dev/rangy-core.js +++ b/dev/rangy-core.js @@ -4,7 +4,7 @@ * * Copyright 2013, Tim Down * Licensed under the MIT license. - * Version: 1.3alpha.772 - * Build date: 26 February 2013 + * Version: 1.3alpha.799 + * Build date: 27 November 2013 */ -var rangy;rangy=rangy||function(){function h(c,d){var e=typeof c[d];return e==b||e==a&&!!c[d]||e=="unknown"}function i(b,c){return typeof b[c]==a&&!!b[c]}function j(a,b){return typeof a[b]!=c}function k(a){return function(b,c){var d=c.length;while(d--)if(!a(b,c[d]))return!1;return!0}}function o(a){return a&&l(a,g)&&n(a,f)}function p(a){return i(a,"body")?a.body:a.getElementsByTagName("body")[0]}function s(a){i(window,"console")&&h(window.console,"log")&&window.console.log(a)}function t(a,b){b?window.alert(a):s(a)}function u(a){r.initialized=!0,r.supported=!1,t("Rangy is not supported on this page in your browser. Reason: "+a,r.config.alertOnFail)}function v(a){t("Rangy warning: "+a,r.config.alertOnWarn)}function y(a){return a.message||a.description||String(a)}function z(){if(r.initialized)return;var a,b=!1,c=!1;h(document,"createRange")&&(a=document.createRange(),l(a,e)&&n(a,d)&&(b=!0),a.detach());var f=p(document);if(!f||f.nodeName.toLowerCase()!="body"){u("No body element found");return}f&&h(f,"createTextRange")&&(a=f.createTextRange(),o(a)&&(c=!0));if(!b&&!c){u("Neither Range nor TextRange are available");return}r.initialized=!0,r.features={implementsDomRange:b,implementsTextRange:c};var g,i;for(var j in q)(g=q[j])instanceof C&&g.init();for(var k=0,m=x.length;kb?(f.node=d,f.offset-=b):f.node==a.parentNode&&f.offset>j(a)&&++f.offset;return d}function t(a){if(a.nodeType==9)return a;if(typeof a.ownerDocument!=c)return a.ownerDocument;if(typeof a.document!=c)return a.document;if(a.parentNode)return t(a.parentNode);throw b.createError("getDocument: no document found for node")}function u(a){var d=t(a);if(typeof d.defaultView!=c)return d.defaultView;if(typeof d.parentWindow!=c)return d.parentWindow;throw b.createError("Cannot get a window object for node")}function v(a){if(typeof a.contentDocument!=c)return a.contentDocument;if(typeof a.contentWindow!=c)return a.contentWindow.document;throw b.createError("getIframeDocument: No Document object found for iframe element")}function w(a){if(typeof a.contentWindow!=c)return a.contentWindow;if(typeof a.contentDocument!=c)return a.contentDocument.defaultView;throw b.createError("getIframeWindow: No Window object found for iframe element")}function x(a){return a&&d.isHostMethod(a,"setTimeout")&&d.isHostObject(a,"document")}function y(a,b,c){var e;a?d.isHostProperty(a,"nodeType")?e=a.nodeType==1&&a.tagName.toLowerCase()=="iframe"?v(a):t(a):x(a)&&(e=a.document):e=document;if(!e)throw b.createError(c+"(): Parameter must be a Window object or DOM node");return e}function z(a){var b;while(b=a.parentNode)a=b;return a}function A(a,c,d,e){var f,g,h,i,k;if(a==d)return c===e?0:c["+a.childNodes.length+"]["+a.innerHTML.slice(0,20)+"]"}return a.nodeName}function E(a){var b=t(a).createDocumentFragment(),c;while(c=a.firstChild)b.appendChild(c);return b}function G(a){this.root=a,this._next=a}function H(a){return new G(a)}function I(a,b){this.node=a,this.offset=b}function J(a){this.code=this[a],this.codeName=a,this.message="DOMException: "+this.codeName}var c="undefined",d=a.util;d.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||b.fail("document missing a Node creation method"),d.isHostMethod(document,"getElementsByTagName")||b.fail("document missing getElementsByTagName method");var e=document.createElement("div");d.areHostMethods(e,["insertBefore","appendChild","cloneNode"]||!d.areHostObjects(e,["previousSibling","nextSibling","childNodes","parentNode"]))||b.fail("Incomplete Element implementation"),d.isHostProperty(e,"innerHTML")||b.fail("Element is missing innerHTML property");var f=document.createTextNode("test");d.areHostMethods(f,["splitText","deleteData","insertData","appendData","cloneNode"]||!d.areHostObjects(e,["previousSibling","nextSibling","childNodes","parentNode"])||!d.areHostProperties(f,["data"]))||b.fail("Incomplete Text Node implementation");var g=function(a,b){var c=a.length;while(c--)if(a[c]===b)return!0;return!1},B=!1;(function(){var b=document.createElement("b");b.innerHTML="1";var c=b.firstChild;b.innerHTML="
    ",B=C(c),a.features.crashyTextNodes=B})();var F;typeof window.getComputedStyle!=c?F=function(a,b){return u(a).getComputedStyle(a,null)[b]}:typeof document.documentElement.currentStyle!=c?F=function(a,b){return a.currentStyle[b]}:b.fail("No means of obtaining computed style properties found"),G.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var a=this._current=this._next,b,c;if(this._current){b=a.firstChild;if(b)this._next=b;else{c=null;while(a!==this.root&&!(c=a.nextSibling))a=a.parentNode;this._next=c}}return this._current},detach:function(){this._current=this._next=this.root=null}},I.prototype={equals:function(a){return!!a&&this.node===a.node&&this.offset==a.offset},inspect:function(){return"[DomPosition("+D(this.node)+":"+this.offset+")]"},toString:function(){return this.inspect()}},J.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},J.prototype.toString=function(){return this.message},a.dom={arrayContains:g,isHtmlNamespace:h,parentElement:i,getNodeIndex:j,getNodeLength:k,getCommonAncestor:l,isAncestorOf:m,isOrIsAncestorOf:n,getClosestAncestorIn:o,isCharacterDataNode:p,isTextOrCommentNode:q,insertAfter:r,splitDataNode:s,getDocument:t,getWindow:u,getIframeWindow:w,getIframeDocument:v,getBody:d.getBody,isWindow:x,getContentDocument:y,getRootContainer:z,comparePoints:A,isBrokenNode:C,inspectNode:D,getComputedStyleProperty:F,fragmentFromNodeChildren:E,createIterator:H,DomPosition:I},a.DOMException=J}),rangy.createModule("DomRange",function(a,b){function r(a,b){return a.nodeType!=3&&(i(a,b.startContainer)||i(a,b.endContainer))}function s(a){return a.document||j(a.startContainer)}function t(a){return new e(a.parentNode,h(a))}function u(a){return new e(a.parentNode,h(a)+1)}function v(a,b,d){var e=a.nodeType==11?a.firstChild:a;return g(b)?d==b.length?c.insertAfter(a,b):b.parentNode.insertBefore(a,d==0?b:l(b,d)):d>=b.childNodes.length?b.appendChild(a):b.insertBefore(a,b.childNodes[d]),e}function w(a,b,c){Y(a),Y(b);if(s(b)!=s(a))throw new f("WRONG_DOCUMENT_ERR");var d=k(a.startContainer,a.startOffset,b.endContainer,b.endOffset),e=k(a.endContainer,a.endOffset,b.startContainer,b.startOffset);return c?d<=0&&e>=0:d<0&&e>0}function x(a){var b;for(var c,d=s(a.range).createDocumentFragment(),e;c=a.next();){b=a.isPartiallySelectedSubtree(),c=c.cloneNode(!b),b&&(e=a.getSubtreeIterator(),c.appendChild(x(e)),e.detach(!0));if(c.nodeType==10)throw new f("HIERARCHY_REQUEST_ERR");d.appendChild(c)}return d}function y(a,b,d){var e,f;d=d||{stop:!1};for(var g,h;g=a.next();)if(a.isPartiallySelectedSubtree()){if(b(g)===!1){d.stop=!0;return}h=a.getSubtreeIterator(),y(h,b,d),h.detach(!0);if(d.stop)return}else{e=c.createIterator(g);while(f=e.next())if(b(f)===!1){d.stop=!0;return}}}function z(a){var b;while(a.next())a.isPartiallySelectedSubtree()?(b=a.getSubtreeIterator(),z(b),b.detach(!0)):a.remove()}function A(a){for(var b,c=s(a.range).createDocumentFragment(),d;b=a.next();){a.isPartiallySelectedSubtree()?(b=b.cloneNode(!1),d=a.getSubtreeIterator(),b.appendChild(A(d)),d.detach(!0)):a.remove();if(b.nodeType==10)throw new f("HIERARCHY_REQUEST_ERR");c.appendChild(b)}return c}function B(a,b,c){var d=!!b&&!!b.length,e,f=!!c;d&&(e=new RegExp("^("+b.join("|")+")$"));var g=[];return y(new D(a,!1),function(a){(!d||e.test(a.nodeType))&&(!f||c(a))&&g.push(a)}),g}function C(a){var b=typeof a.getName=="undefined"?"Range":a.getName();return"["+b+"("+c.inspectNode(a.startContainer)+":"+a.startOffset+", "+c.inspectNode(a.endContainer)+":"+a.endOffset+")]"}function D(a,b){this.range=a,this.clonePartiallySelectedTextNodes=b;if(!a.collapsed){this.sc=a.startContainer,this.so=a.startOffset,this.ec=a.endContainer,this.eo=a.endOffset;var c=a.commonAncestorContainer;this.sc===this.ec&&g(this.sc)?(this.isSingleCharacterDataNode=!0,this._first=this._last=this._next=this.sc):(this._first=this._next=this.sc===c&&!g(this.sc)?this.sc.childNodes[this.so]:m(this.sc,c,!0),this._last=this.ec===c&&!g(this.ec)?this.ec.childNodes[this.eo-1]:m(this.ec,c,!0))}}function E(a){this.code=this[a],this.codeName=a,this.message="RangeException: "+this.codeName}function K(a){return function(b,c){var d,e=c?b:b.parentNode;while(e){d=e.nodeType;if(o(a,d))return e;e=e.parentNode}return null}}function O(a,b){if(N(a,b))throw new E("INVALID_NODE_TYPE_ERR")}function P(a){if(!a.startContainer)throw new f("INVALID_STATE_ERR")}function Q(a,b){if(!o(b,a.nodeType))throw new E("INVALID_NODE_TYPE_ERR")}function R(a,b){if(b<0||b>(g(a)?a.length:a.childNodes.length))throw new f("INDEX_SIZE_ERR")}function S(a,b){if(L(a,!0)!==L(b,!0))throw new f("WRONG_DOCUMENT_ERR")}function T(a){if(M(a,!0))throw new f("NO_MODIFICATION_ALLOWED_ERR")}function U(a,b){if(!a)throw new f(b)}function V(a){return q&&c.isBrokenNode(a)||!o(G,a.nodeType)&&!L(a,!0)}function W(a,b){return b<=(g(a)?a.length:a.childNodes.length)}function X(a){return!!a.startContainer&&!!a.endContainer&&!V(a.startContainer)&&!V(a.endContainer)&&W(a.startContainer,a.startOffset)&&W(a.endContainer,a.endOffset)}function Y(a){P(a);if(!X(a))throw new Error("Range error: Range is no longer valid after DOM mutation ("+a.inspect()+")")}function bb(a,b){Y(a);var c=a.startContainer,d=a.startOffset,e=a.endContainer,f=a.endOffset,i=c===e;g(e)&&f>0&&f0&&d=h(c)&&f++,d=0),a.setStartAndEnd(c,d,e,f)}function lb(){}function mb(a){a.START_TO_START=db,a.START_TO_END=eb,a.END_TO_END=fb,a.END_TO_START=gb,a.NODE_BEFORE=hb,a.NODE_AFTER=ib,a.NODE_BEFORE_AND_AFTER=jb,a.NODE_INSIDE=kb}function nb(a){mb(a),mb(a.prototype)}function ob(a,b){return function(){Y(this);var c=this.startContainer,d=this.startOffset,e=this.commonAncestorContainer,f=new D(this,!0),g,h;c!==e&&(g=m(c,e,!0),h=u(g),c=h.node,d=h.offset),y(f,T),f.reset();var i=a(f);return f.detach(),b(this,c,d,c,d),i}}function pb(a,b,c){function e(a,b){return function(c){P(this),Q(c,F),Q(p(c),G);var d=(a?t:u)(c);(b?f:i)(this,d.node,d.offset)}}function f(a,c,d){var e=a.endContainer,f=a.endOffset;if(c!==a.startContainer||d!==a.startOffset){if(p(c)!=p(e)||k(c,d,e,f)==1)e=c,f=d;b(a,c,d,e,f)}}function i(a,c,d){var e=a.startContainer,f=a.startOffset;if(c!==a.endContainer||d!==a.endOffset){if(p(c)!=p(e)||k(c,d,e,f)==-1)e=c,f=d;b(a,e,f,c,d)}}a.prototype=new lb,d.extend(a.prototype,{setStart:function(a,b){P(this),O(a,!0),R(a,b),f(this,a,b)},setEnd:function(a,b){P(this),O(a,!0),R(a,b),i(this,a,b)},setStartAndEnd:function(){P(this);var a=arguments,c=a[0],d=a[1],e=c,f=d;switch(a.length){case 3:f=a[2];break;case 4:e=a[2],f=a[3]}b(this,c,d,e,f)},setBoundary:function(a,b,c){this["set"+(c?"Start":"End")](a,b)},setStartBefore:e(!0,!0),setStartAfter:e(!1,!0),setEndBefore:e(!0,!1),setEndAfter:e(!1,!1),collapse:function(a){Y(this),a?b(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):b(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(a){P(this),O(a,!0),b(this,a,0,a,n(a))},selectNode:function(a){P(this),O(a,!1),Q(a,F);var c=t(a),d=u(a);b(this,c.node,c.offset,d.node,d.offset)},extractContents:ob(A,b),deleteContents:ob(z,b),canSurroundContents:function(){Y(this),T(this.startContainer),T(this.endContainer);var a=new D(this,!0),b=a._first&&r(a._first,this)||a._last&&r(a._last,this);return a.detach(),!b},detach:function(){c(this)},splitBoundaries:function(){bb(this)},splitBoundariesPreservingPositions:function(a){bb(this,a)},normalizeBoundaries:function(){Y(this);var a=this.startContainer,c=this.startOffset,d=this.endContainer,e=this.endOffset,f=function(a){var b=a.nextSibling;b&&b.nodeType==a.nodeType&&(d=a,e=a.length,a.appendData(b.data),b.parentNode.removeChild(b))},i=function(b){var f=b.previousSibling;if(f&&f.nodeType==b.nodeType){a=b;var g=b.length;c=f.length,b.insertData(0,f.data),f.parentNode.removeChild(f);if(a==d)e+=c,d=a;else if(d==b.parentNode){var i=h(b);e==i?(d=b,e=g):e>i&&e--}}},j=!0;if(g(d))d.length==e&&f(d);else{if(e>0){var k=d.childNodes[e-1];k&&g(k)&&f(k)}j=!this.collapsed}if(j){if(g(a))c==0&&i(a);else if(cx",$=Z.firstChild.nodeType==3}catch(_){}a.features.htmlParsingConforms=$;var ab=$?function(a){var b=this.startContainer,d=j(b);if(!b)throw new f("INVALID_STATE_ERR");var e=null;return b.nodeType==1?e=b:g(b)&&(e=c.parentElement(b)),e===null||e.nodeName=="HTML"&&c.isHtmlNamespace(j(e).documentElement)&&c.isHtmlNamespace(e)?e=d.createElement("body"):e=e.cloneNode(!1),e.innerHTML=a,c.fragmentFromNodeChildren(e)}:function(a){P(this);var b=s(this),d=b.createElement("body");return d.innerHTML=a,c.fragmentFromNodeChildren(d)},cb=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],db=0,eb=1,fb=2,gb=3,hb=0,ib=1,jb=2,kb=3;lb.prototype={compareBoundaryPoints:function(a,b){Y(this),S(this.startContainer,b.startContainer);var c,d,e,f,g=a==gb||a==db?"start":"end",h=a==eb||a==db?"start":"end";return c=this[g+"Container"],d=this[g+"Offset"],e=b[h+"Container"],f=b[h+"Offset"],k(c,d,e,f)},insertNode:function(a){Y(this),Q(a,I),T(this.startContainer);if(i(a,this.startContainer))throw new f("HIERARCHY_REQUEST_ERR");var b=v(a,this.startContainer,this.startOffset);this.setStartBefore(b)},cloneContents:function(){Y(this);var a,b;if(this.collapsed)return s(this).createDocumentFragment();if(this.startContainer===this.endContainer&&g(this.startContainer))return a=this.startContainer.cloneNode(!0),a.data=a.data.slice(this.startOffset,this.endOffset),b=s(this).createDocumentFragment(),b.appendChild(a),b;var c=new D(this,!0);return a=x(c),c.detach(),a},canSurroundContents:function(){Y(this),T(this.startContainer),T(this.endContainer);var a=new D(this,!0),b=a._first&&r(a._first,this)||a._last&&r(a._last,this);return a.detach(),!b},surroundContents:function(a){Q(a,J);if(!this.canSurroundContents())throw new E("BAD_BOUNDARYPOINTS_ERR");var b=this.extractContents();if(a.hasChildNodes())while(a.lastChild)a.removeChild(a.lastChild);v(a,this.startContainer,this.startOffset),a.appendChild(b),this.selectNode(a)},cloneRange:function(){Y(this);var a=new tb(s(this)),b=cb.length,c;while(b--)c=cb[b],a[c]=this[c];return a},toString:function(){Y(this);var a=this.startContainer;if(a===this.endContainer&&g(a))return a.nodeType==3||a.nodeType==4?a.data.slice(this.startOffset,this.endOffset):"";var b=[],c=new D(this,!0);return y(c,function(a){(a.nodeType==3||a.nodeType==4)&&b.push(a.data)}),c.detach(),b.join("")},compareNode:function(a){Y(this);var b=a.parentNode,c=h(a);if(!b)throw new f("NOT_FOUND_ERR");var d=this.comparePoint(b,c),e=this.comparePoint(b,c+1);return d<0?e>0?jb:hb:e>0?ib:kb},comparePoint:function(a,b){return Y(this),U(a,"HIERARCHY_REQUEST_ERR"),S(a,this.startContainer),k(a,b,this.startContainer,this.startOffset)<0?-1:k(a,b,this.endContainer,this.endOffset)>0?1:0},createContextualFragment:ab,toHtml:function(){Y(this);var a=this.commonAncestorContainer.parentNode.cloneNode(!1);return a.appendChild(this.cloneContents()),a.innerHTML},intersectsNode:function(a,b){Y(this),U(a,"NOT_FOUND_ERR");if(j(a)!==s(this))return!1;var c=a.parentNode,d=h(a);U(c,"NOT_FOUND_ERR");var e=k(c,d,this.endContainer,this.endOffset),f=k(c,d+1,this.startContainer,this.startOffset);return b?e<=0&&f>=0:e<0&&f>0},isPointInRange:function(a,b){return Y(this),U(a,"HIERARCHY_REQUEST_ERR"),S(a,this.startContainer),k(a,b,this.startContainer,this.startOffset)>=0&&k(a,b,this.endContainer,this.endOffset)<=0},intersectsRange:function(a){return w(this,a,!1)},intersectsOrTouchesRange:function(a){return w(this,a,!0)},intersection:function(a){if(this.intersectsRange(a)){var b=k(this.startContainer,this.startOffset,a.startContainer,a.startOffset),c=k(this.endContainer,this.endOffset,a.endContainer,a.endOffset),d=this.cloneRange();return b==-1&&d.setStart(a.startContainer,a.startOffset),c==1&&d.setEnd(a.endContainer,a.endOffset),d}return null},union:function(a){if(this.intersectsOrTouchesRange(a)){var b=this.cloneRange();return k(a.startContainer,a.startOffset,this.startContainer,this.startOffset)==-1&&b.setStart(a.startContainer,a.startOffset),k(a.endContainer,a.endOffset,this.endContainer,this.endOffset)==1&&b.setEnd(a.endContainer,a.endOffset),b}throw new E("Ranges do not intersect")},containsNode:function(a,b){return b?this.intersectsNode(a,!1):this.compareNode(a)==kb},containsNodeContents:function(a){return this.comparePoint(a,0)>=0&&this.comparePoint(a,n(a))<=0},containsRange:function(a){var b=this.intersection(a);return b!==null&&a.equals(b)},containsNodeText:function(a){var b=this.cloneRange();b.selectNode(a);var c=b.getNodes([3]);if(c.length>0){b.setStart(c[0],0);var d=c.pop();b.setEnd(d,d.length);var e=this.containsRange(b);return b.detach(),e}return this.containsNodeContents(a)},getNodes:function(a,b){return Y(this),B(this,a,b)},getDocument:function(){return s(this)},collapseBefore:function(a){P(this),this.setEndBefore(a),this.collapse(!1)},collapseAfter:function(a){P(this),this.setStartAfter(a),this.collapse(!0)},getBookmark:function(b){var d=s(this),e=a.createRange(d);b=b||c.getBody(d),e.selectNodeContents(b);var f=this.intersection(e),g=0,h=0;return f&&(e.setEnd(f.startContainer,f.startOffset),g=e.toString().length,h=g+f.toString().length,e.detach()),{start:g,end:h,containerNode:b}},moveToBookmark:function(a){var b=a.containerNode,c=0;this.setStart(b,0),this.collapse(!0);var d=[b],e,f=!1,g=!1,h,i,j;while(!g&&(e=d.pop()))if(e.nodeType==3)h=c+e.length,!f&&a.start>=c&&a.start<=h&&(this.setStart(e,a.start-c),f=!0),f&&a.end>=c&&a.end<=h&&(this.setEnd(e,a.end-c),g=!0),c=h;else{j=e.childNodes,i=j.length;while(i--)d.push(j[i])}},getName:function(){return"DomRange"},equals:function(a){return tb.rangesEqual(this,a)},isValid:function(){return X(this)},inspect:function(){return C(this)}},pb(tb,rb,sb),a.rangePrototype=lb.prototype,d.extend(tb,{rangeProperties:cb,RangeIterator:D,copyComparisonConstants:nb,createPrototypeRange:pb,inspect:C,getRangeDocument:s,rangesEqual:function(a,b){return a.startContainer===b.startContainer&&a.startOffset===b.startOffset&&a.endContainer===b.endContainer&&a.endOffset===b.endOffset}}),a.DomRange=tb,a.RangeException=E}),rangy.createModule("WrappedRange",function(a,b){a.requireModules(["DomUtil","DomRange"]);var c,d,e=a.dom,f=a.util,g=e.DomPosition,h=a.DomRange,i=e.getBody,j=e.getContentDocument,k=e.isCharacterDataNode;a.features.implementsDomRange&&function(){function k(a){var b=g.length,c;while(b--)c=g[b],a[c]=a.nativeRange[c];a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset}function l(a,b,c,d,e){var f=a.startContainer!==b||a.startOffset!=c,g=a.endContainer!==d||a.endOffset!=e,h=!a.equals(a.nativeRange);if(f||g||h)a.setEnd(d,e),a.setStart(b,c)}function m(a){a.nativeRange.detach(),a.detached=!0;var b=g.length;while(b--)a[g[b]]=null}var d,g=h.rangeProperties,n;c=function(a){if(!a)throw b.createError("WrappedRange: Range must be specified");this.nativeRange=a,k(this)},h.createPrototypeRange(c,l,m),d=c.prototype,d.selectNode=function(a){this.nativeRange.selectNode(a),k(this)},d.cloneContents=function(){return this.nativeRange.cloneContents()},d.surroundContents=function(a){this.nativeRange.surroundContents(a),k(this)},d.collapse=function(a){this.nativeRange.collapse(a),k(this)},d.cloneRange=function(){return new c(this.nativeRange.cloneRange())},d.refresh=function(){k(this)},d.toString=function(){return this.nativeRange.toString()};var o=document.createTextNode("test");i(document).appendChild(o);var p=document.createRange();p.setStart(o,0),p.setEnd(o,0);try{p.setStart(o,1),d.setStart=function(a,b){this.nativeRange.setStart(a,b),k(this)},d.setEnd=function(a,b){this.nativeRange.setEnd(a,b),k(this)},n=function(a){return function(b){this.nativeRange[a](b),k(this)}}}catch(q){d.setStart=function(a,b){try{this.nativeRange.setStart(a,b)}catch(c){this.nativeRange.setEnd(a,b),this.nativeRange.setStart(a,b)}k(this)},d.setEnd=function(a,b){try{this.nativeRange.setEnd(a,b)}catch(c){this.nativeRange.setStart(a,b),this.nativeRange.setEnd(a,b)}k(this)},n=function(a,b){return function(c){try{this.nativeRange[a](c)}catch(d){this.nativeRange[b](c),this.nativeRange[a](c)}k(this)}}}d.setStartBefore=n("setStartBefore","setEndBefore"),d.setStartAfter=n("setStartAfter","setEndAfter"),d.setEndBefore=n("setEndBefore","setStartBefore"),d.setEndAfter=n("setEndAfter","setStartAfter"),p.selectNodeContents(o),p.startContainer==o&&p.endContainer==o&&p.startOffset==0&&p.endOffset==o.length?d.selectNodeContents=function(a){this.nativeRange.selectNodeContents(a),k(this)}:d.selectNodeContents=function(a){this.setStartAndEnd(a,0,e.getNodeLength(a))},p.selectNodeContents(o),p.setEnd(o,3);var r=document.createRange();r.selectNodeContents(o),r.setEnd(o,4),r.setStart(o,2),p.compareBoundaryPoints(p.START_TO_END,r)==-1&&p.compareBoundaryPoints(p.END_TO_START,r)==1?d.compareBoundaryPoints=function(a,b){return b=b.nativeRange||b,a==b.START_TO_END?a=b.END_TO_START:a==b.END_TO_START&&(a=b.START_TO_END),this.nativeRange.compareBoundaryPoints(a,b)}:d.compareBoundaryPoints=function(a,b){return this.nativeRange.compareBoundaryPoints(a,b.nativeRange||b)};var s=document.createElement("div");s.innerHTML="123";var t=s.firstChild,u=i(document);u.appendChild(s),p.setStart(t,1),p.setEnd(t,2),p.deleteContents(),t.data=="13"&&(d.deleteContents=function(){this.nativeRange.deleteContents(),k(this)},d.extractContents=function(){var a=this.nativeRange.extractContents();return k(this),a}),u.removeChild(s),u=null,f.isHostMethod(p,"createContextualFragment")&&(d.createContextualFragment=function(a){return this.nativeRange.createContextualFragment(a)}),i(document).removeChild(o),p.detach(),r.detach(),d.getName=function(){return"WrappedRange"},a.WrappedRange=c,a.createNativeRange=function(a){return a=j(a,b,"createNativeRange"),a.createRange()}}();if(a.features.implementsTextRange){var l=function(a){var b=a.parentElement(),c=a.duplicate();c.collapse(!0);var d=c.parentElement();c=a.duplicate(),c.collapse(!1);var f=c.parentElement(),g=d==f?d:e.getCommonAncestor(d,f);return g==b?g:e.getCommonAncestor(b,g)},m=function(a){return a.compareEndPoints("StartToEnd",a)==0},n=function(a,b,c,d,f){var h=a.duplicate();h.collapse(c);var i=h.parentElement();e.isOrIsAncestorOf(b,i)||(i=b);if(!i.canHaveHTML){var j=new g(i.parentNode,e.getNodeIndex(i));return{boundaryPosition:j,nodeInfo:{nodeIndex:j.offset,containerElement:j.node}}}var l=e.getDocument(i).createElement("span");l.parentNode&&l.parentNode.removeChild(l);var m,n=c?"StartToStart":"StartToEnd",o,p,q,r,s=f&&f.containerElement==i?f.nodeIndex:0,t=i.childNodes.length,u=t,v=u;for(;;){v==t?i.appendChild(l):i.insertBefore(l,i.childNodes[v]),h.moveToElementText(l),m=h.compareEndPoints(n,a);if(m==0||s==u)break;if(m==-1){if(u==s+1)break;s=v}else u=u==s+1?s:v;v=Math.floor((s+u)/2),i.removeChild(l)}r=l.nextSibling;if(m==-1&&r&&k(r)){h.setEndPoint(c?"EndToStart":"EndToEnd",a);var w;if(/[\r\n]/.test(r.data)){var x=h.duplicate(),y=x.text.replace(/\r\n/g,"\r").length;w=x.moveStart("character",y);while((m=x.compareEndPoints("StartToEnd",x))==-1)w++,x.moveStart("character",1)}else w=h.text.length;q=new g(r,w)}else o=(d||!c)&&l.previousSibling,p=(d||c)&&l.nextSibling,p&&k(p)?q=new g(p,0):o&&k(o)?q=new g(o,o.data.length):q=new g(i,e.getNodeIndex(l));return l.parentNode.removeChild(l),{boundaryPosition:q,nodeInfo:{nodeIndex:v,containerElement:i}}},o=function(a,b){var c,d,f=a.offset,g=e.getDocument(a.node),h,j,l=i(g).createTextRange(),m=k(a.node);return m?(c=a.node,d=c.parentNode):(j=a.node.childNodes,c=f1)Z(this,a);else{this.removeAllRanges();for(var b=0,c=a.length;b1?Z(this,a):b&&this.addRange(a[0])}}Y.getRangeAt=function(a){if(a<0||a>=this.rangeCount)throw new i("INDEX_SIZE_ERR");return this._ranges[a].cloneRange()};var _;if(x)_=function(b){var c;a.isSelectionValid(b.win)?c=b.docSelection.createRange():(c=p(b.win.document).createTextRange(),c.collapse(!0)),b.docSelection.type==n?Q(b):O(c)?P(b,c):K(b)};else if(f(y,"getRangeAt")&&typeof y.rangeCount=="number")_=function(b){if(G&&w&&b.docSelection.type==n)Q(b);else{b._ranges.length=b.rangeCount=b.nativeSelection.rangeCount;if(b.rangeCount){for(var c=0,d=b.rangeCount;cb?(f.node=d,f.offset-=b):f.node==a.parentNode&&f.offset>j(a)&&++f.offset;return d}function t(a){if(a.nodeType==9)return a;if(typeof a.ownerDocument!=c)return a.ownerDocument;if(typeof a.document!=c)return a.document;if(a.parentNode)return t(a.parentNode);throw b.createError("getDocument: no document found for node")}function u(a){var d=t(a);if(typeof d.defaultView!=c)return d.defaultView;if(typeof d.parentWindow!=c)return d.parentWindow;throw b.createError("Cannot get a window object for node")}function v(a){if(typeof a.contentDocument!=c)return a.contentDocument;if(typeof a.contentWindow!=c)return a.contentWindow.document;throw b.createError("getIframeDocument: No Document object found for iframe element")}function w(a){if(typeof a.contentWindow!=c)return a.contentWindow;if(typeof a.contentDocument!=c)return a.contentDocument.defaultView;throw b.createError("getIframeWindow: No Window object found for iframe element")}function x(a){return a&&d.isHostMethod(a,"setTimeout")&&d.isHostObject(a,"document")}function y(a,b,c){var e;a?d.isHostProperty(a,"nodeType")?e=a.nodeType==1&&a.tagName.toLowerCase()=="iframe"?v(a):t(a):x(a)&&(e=a.document):e=document;if(!e)throw b.createError(c+"(): Parameter must be a Window object or DOM node");return e}function z(a){var b;while(b=a.parentNode)a=b;return a}function A(a,c,d,e){var f,g,h,i,k;if(a==d)return c===e?0:c["+j(a)+"]["+a.childNodes.length+"]["+(a.innerHTML||"[innerHTML not supported]").slice(0,25)+"]"}return a.nodeName}function E(a){var b=t(a).createDocumentFragment(),c;while(c=a.firstChild)b.appendChild(c);return b}function G(a){this.root=a,this._next=a}function H(a){return new G(a)}function I(a,b){this.node=a,this.offset=b}function J(a){this.code=this[a],this.codeName=a,this.message="DOMException: "+this.codeName}var c="undefined",d=a.util;d.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||b.fail("document missing a Node creation method"),d.isHostMethod(document,"getElementsByTagName")||b.fail("document missing getElementsByTagName method");var e=document.createElement("div");d.areHostMethods(e,["insertBefore","appendChild","cloneNode"]||!d.areHostObjects(e,["previousSibling","nextSibling","childNodes","parentNode"]))||b.fail("Incomplete Element implementation"),d.isHostProperty(e,"innerHTML")||b.fail("Element is missing innerHTML property");var f=document.createTextNode("test");d.areHostMethods(f,["splitText","deleteData","insertData","appendData","cloneNode"]||!d.areHostObjects(e,["previousSibling","nextSibling","childNodes","parentNode"])||!d.areHostProperties(f,["data"]))||b.fail("Incomplete Text Node implementation");var g=function(a,b){var c=a.length;while(c--)if(a[c]===b)return!0;return!1},B=!1;(function(){var b=document.createElement("b");b.innerHTML="1";var c=b.firstChild;b.innerHTML="
    ",B=C(c),a.features.crashyTextNodes=B})();var F;typeof window.getComputedStyle!=c?F=function(a,b){return u(a).getComputedStyle(a,null)[b]}:typeof document.documentElement.currentStyle!=c?F=function(a,b){return a.currentStyle[b]}:b.fail("No means of obtaining computed style properties found"),G.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var a=this._current=this._next,b,c;if(this._current){b=a.firstChild;if(b)this._next=b;else{c=null;while(a!==this.root&&!(c=a.nextSibling))a=a.parentNode;this._next=c}}return this._current},detach:function(){this._current=this._next=this.root=null}},I.prototype={equals:function(a){return!!a&&this.node===a.node&&this.offset==a.offset},inspect:function(){return"[DomPosition("+D(this.node)+":"+this.offset+")]"},toString:function(){return this.inspect()}},J.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},J.prototype.toString=function(){return this.message},a.dom={arrayContains:g,isHtmlNamespace:h,parentElement:i,getNodeIndex:j,getNodeLength:k,getCommonAncestor:l,isAncestorOf:m,isOrIsAncestorOf:n,getClosestAncestorIn:o,isCharacterDataNode:p,isTextOrCommentNode:q,insertAfter:r,splitDataNode:s,getDocument:t,getWindow:u,getIframeWindow:w,getIframeDocument:v,getBody:d.getBody,isWindow:x,getContentDocument:y,getRootContainer:z,comparePoints:A,isBrokenNode:C,inspectNode:D,getComputedStyleProperty:F,fragmentFromNodeChildren:E,createIterator:H,DomPosition:I},a.DOMException=J}),rangy.createCoreModule("DomRange",["DomUtil"],function(a,b){function r(a,b){return a.nodeType!=3&&(i(a,b.startContainer)||i(a,b.endContainer))}function s(a){return a.document||j(a.startContainer)}function t(a){return new e(a.parentNode,h(a))}function u(a){return new e(a.parentNode,h(a)+1)}function v(a,b,d){var e=a.nodeType==11?a.firstChild:a;return g(b)?d==b.length?c.insertAfter(a,b):b.parentNode.insertBefore(a,d==0?b:l(b,d)):d>=b.childNodes.length?b.appendChild(a):b.insertBefore(a,b.childNodes[d]),e}function w(a,b,c){Y(a),Y(b);if(s(b)!=s(a))throw new f("WRONG_DOCUMENT_ERR");var d=k(a.startContainer,a.startOffset,b.endContainer,b.endOffset),e=k(a.endContainer,a.endOffset,b.startContainer,b.startOffset);return c?d<=0&&e>=0:d<0&&e>0}function x(a){var b;for(var c,d=s(a.range).createDocumentFragment(),e;c=a.next();){b=a.isPartiallySelectedSubtree(),c=c.cloneNode(!b),b&&(e=a.getSubtreeIterator(),c.appendChild(x(e)),e.detach(!0));if(c.nodeType==10)throw new f("HIERARCHY_REQUEST_ERR");d.appendChild(c)}return d}function y(a,b,d){var e,f;d=d||{stop:!1};for(var g,h;g=a.next();)if(a.isPartiallySelectedSubtree()){if(b(g)===!1){d.stop=!0;return}h=a.getSubtreeIterator(),y(h,b,d),h.detach(!0);if(d.stop)return}else{e=c.createIterator(g);while(f=e.next())if(b(f)===!1){d.stop=!0;return}}}function z(a){var b;while(a.next())a.isPartiallySelectedSubtree()?(b=a.getSubtreeIterator(),z(b),b.detach(!0)):a.remove()}function A(a){for(var b,c=s(a.range).createDocumentFragment(),d;b=a.next();){a.isPartiallySelectedSubtree()?(b=b.cloneNode(!1),d=a.getSubtreeIterator(),b.appendChild(A(d)),d.detach(!0)):a.remove();if(b.nodeType==10)throw new f("HIERARCHY_REQUEST_ERR");c.appendChild(b)}return c}function B(a,b,c){var d=!!b&&!!b.length,e,f=!!c;d&&(e=new RegExp("^("+b.join("|")+")$"));var h=[];return y(new D(a,!1),function(b){if(d&&!e.test(b.nodeType))return;if(f&&!c(b))return;var i=a.startContainer;if(b==i&&g(i)&&a.startOffset==i.length)return;var j=a.endContainer;if(b==j&&g(j)&&a.endOffset==0)return;h.push(b)}),h}function C(a){var b=typeof a.getName=="undefined"?"Range":a.getName();return"["+b+"("+c.inspectNode(a.startContainer)+":"+a.startOffset+", "+c.inspectNode(a.endContainer)+":"+a.endOffset+")]"}function D(a,b){this.range=a,this.clonePartiallySelectedTextNodes=b;if(!a.collapsed){this.sc=a.startContainer,this.so=a.startOffset,this.ec=a.endContainer,this.eo=a.endOffset;var c=a.commonAncestorContainer;this.sc===this.ec&&g(this.sc)?(this.isSingleCharacterDataNode=!0,this._first=this._last=this._next=this.sc):(this._first=this._next=this.sc===c&&!g(this.sc)?this.sc.childNodes[this.so]:m(this.sc,c,!0),this._last=this.ec===c&&!g(this.ec)?this.ec.childNodes[this.eo-1]:m(this.ec,c,!0))}}function E(a){this.code=this[a],this.codeName=a,this.message="RangeException: "+this.codeName}function K(a){return function(b,c){var d,e=c?b:b.parentNode;while(e){d=e.nodeType;if(o(a,d))return e;e=e.parentNode}return null}}function O(a,b){if(N(a,b))throw new E("INVALID_NODE_TYPE_ERR")}function P(a){if(!a.startContainer)throw new f("INVALID_STATE_ERR")}function Q(a,b){if(!o(b,a.nodeType))throw new E("INVALID_NODE_TYPE_ERR")}function R(a,b){if(b<0||b>(g(a)?a.length:a.childNodes.length))throw new f("INDEX_SIZE_ERR")}function S(a,b){if(L(a,!0)!==L(b,!0))throw new f("WRONG_DOCUMENT_ERR")}function T(a){if(M(a,!0))throw new f("NO_MODIFICATION_ALLOWED_ERR")}function U(a,b){if(!a)throw new f(b)}function V(a){return q&&c.isBrokenNode(a)||!o(G,a.nodeType)&&!L(a,!0)}function W(a,b){return b<=(g(a)?a.length:a.childNodes.length)}function X(a){return!!a.startContainer&&!!a.endContainer&&!V(a.startContainer)&&!V(a.endContainer)&&W(a.startContainer,a.startOffset)&&W(a.endContainer,a.endOffset)}function Y(a){P(a);if(!X(a))throw new Error("Range error: Range is no longer valid after DOM mutation ("+a.inspect()+")")}function bb(a,b){Y(a);var c=a.startContainer,d=a.startOffset,e=a.endContainer,f=a.endOffset,i=c===e;g(e)&&f>0&&f0&&d=h(c)&&f++,d=0),a.setStartAndEnd(c,d,e,f)}function lb(a){a.START_TO_START=db,a.START_TO_END=eb,a.END_TO_END=fb,a.END_TO_START=gb,a.NODE_BEFORE=hb,a.NODE_AFTER=ib,a.NODE_BEFORE_AND_AFTER=jb,a.NODE_INSIDE=kb}function mb(a){lb(a),lb(a.prototype)}function nb(a,b){return function(){Y(this);var c=this.startContainer,d=this.startOffset,e=this.commonAncestorContainer,f=new D(this,!0),g,h;c!==e&&(g=m(c,e,!0),h=u(g),c=h.node,d=h.offset),y(f,T),f.reset();var i=a(f);return f.detach(),b(this,c,d,c,d),i}}function ob(b,c,e){function f(a,b){return function(c){P(this),Q(c,F),Q(p(c),G);var d=(a?t:u)(c);(b?i:j)(this,d.node,d.offset)}}function i(a,b,d){var e=a.endContainer,f=a.endOffset;if(b!==a.startContainer||d!==a.startOffset){if(p(b)!=p(e)||k(b,d,e,f)==1)e=b,f=d;c(a,b,d,e,f)}}function j(a,b,d){var e=a.startContainer,f=a.startOffset;if(b!==a.endContainer||d!==a.endOffset){if(p(b)!=p(e)||k(b,d,e,f)==-1)e=b,f=d;c(a,e,f,b,d)}}var l=function(){};l.prototype=a.rangePrototype,b.prototype=new l,d.extend(b.prototype,{setStart:function(a,b){P(this),O(a,!0),R(a,b),i(this,a,b)},setEnd:function(a,b){P(this),O(a,!0),R(a,b),j(this,a,b)},setStartAndEnd:function(){P(this);var a=arguments,b=a[0],d=a[1],e=b,f=d;switch(a.length){case 3:f=a[2];break;case 4:e=a[2],f=a[3]}c(this,b,d,e,f)},setBoundary:function(a,b,c){this["set"+(c?"Start":"End")](a,b)},setStartBefore:f(!0,!0),setStartAfter:f(!1,!0),setEndBefore:f(!0,!1),setEndAfter:f(!1,!1),collapse:function(a){Y(this),a?c(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):c(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(a){P(this),O(a,!0),c(this,a,0,a,n(a))},selectNode:function(a){P(this),O(a,!1),Q(a,F);var b=t(a),d=u(a);c(this,b.node,b.offset,d.node,d.offset)},extractContents:nb(A,c),deleteContents:nb(z,c),canSurroundContents:function(){Y(this),T(this.startContainer),T(this.endContainer);var a=new D(this,!0),b=a._first&&r(a._first,this)||a._last&&r(a._last,this);return a.detach(),!b},detach:function(){e(this)},splitBoundaries:function(){bb(this)},splitBoundariesPreservingPositions:function(a){bb(this,a)},normalizeBoundaries:function(){Y(this);var a=this.startContainer,b=this.startOffset,d=this.endContainer,e=this.endOffset,f=function(a){var b=a.nextSibling;b&&b.nodeType==a.nodeType&&(d=a,e=a.length,a.appendData(b.data),b.parentNode.removeChild(b))},i=function(c){var f=c.previousSibling;if(f&&f.nodeType==c.nodeType){a=c;var g=c.length;b=f.length,c.insertData(0,f.data),f.parentNode.removeChild(f);if(a==d)e+=b,d=a;else if(d==c.parentNode){var i=h(c);e==i?(d=c,e=g):e>i&&e--}}},j=!0;if(g(d))d.length==e&&f(d);else{if(e>0){var k=d.childNodes[e-1];k&&g(k)&&f(k)}j=!this.collapsed}if(j){if(g(a))b==0&&i(a);else if(bx",$=Z.firstChild.nodeType==3}catch(_){}a.features.htmlParsingConforms=$;var ab=$?function(a){var b=this.startContainer,d=j(b);if(!b)throw new f("INVALID_STATE_ERR");var e=null;return b.nodeType==1?e=b:g(b)&&(e=c.parentElement(b)),e===null||e.nodeName=="HTML"&&c.isHtmlNamespace(j(e).documentElement)&&c.isHtmlNamespace(e)?e=d.createElement("body"):e=e.cloneNode(!1),e.innerHTML=a,c.fragmentFromNodeChildren(e)}:function(a){P(this);var b=s(this),d=b.createElement("body");return d.innerHTML=a,c.fragmentFromNodeChildren(d)},cb=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],db=0,eb=1,fb=2,gb=3,hb=0,ib=1,jb=2,kb=3;d.extend(a.rangePrototype,{compareBoundaryPoints:function(a,b){Y(this),S(this.startContainer,b.startContainer);var c,d,e,f,g=a==gb||a==db?"start":"end",h=a==eb||a==db?"start":"end";return c=this[g+"Container"],d=this[g+"Offset"],e=b[h+"Container"],f=b[h+"Offset"],k(c,d,e,f)},insertNode:function(a){Y(this),Q(a,I),T(this.startContainer);if(i(a,this.startContainer))throw new f("HIERARCHY_REQUEST_ERR");var b=v(a,this.startContainer,this.startOffset);this.setStartBefore(b)},cloneContents:function(){Y(this);var a,b;if(this.collapsed)return s(this).createDocumentFragment();if(this.startContainer===this.endContainer&&g(this.startContainer))return a=this.startContainer.cloneNode(!0),a.data=a.data.slice(this.startOffset,this.endOffset),b=s(this).createDocumentFragment(),b.appendChild(a),b;var c=new D(this,!0);return a=x(c),c.detach(),a},canSurroundContents:function(){Y(this),T(this.startContainer),T(this.endContainer);var a=new D(this,!0),b=a._first&&r(a._first,this)||a._last&&r(a._last,this);return a.detach(),!b},surroundContents:function(a){Q(a,J);if(!this.canSurroundContents())throw new E("BAD_BOUNDARYPOINTS_ERR");var b=this.extractContents();if(a.hasChildNodes())while(a.lastChild)a.removeChild(a.lastChild);v(a,this.startContainer,this.startOffset),a.appendChild(b),this.selectNode(a)},cloneRange:function(){Y(this);var a=new sb(s(this)),b=cb.length,c;while(b--)c=cb[b],a[c]=this[c];return a},toString:function(){Y(this);var a=this.startContainer;if(a===this.endContainer&&g(a))return a.nodeType==3||a.nodeType==4?a.data.slice(this.startOffset,this.endOffset):"";var b=[],c=new D(this,!0);return y(c,function(a){(a.nodeType==3||a.nodeType==4)&&b.push(a.data)}),c.detach(),b.join("")},compareNode:function(a){Y(this);var b=a.parentNode,c=h(a);if(!b)throw new f("NOT_FOUND_ERR");var d=this.comparePoint(b,c),e=this.comparePoint(b,c+1);return d<0?e>0?jb:hb:e>0?ib:kb},comparePoint:function(a,b){return Y(this),U(a,"HIERARCHY_REQUEST_ERR"),S(a,this.startContainer),k(a,b,this.startContainer,this.startOffset)<0?-1:k(a,b,this.endContainer,this.endOffset)>0?1:0},createContextualFragment:ab,toHtml:function(){Y(this);var a=this.commonAncestorContainer.parentNode.cloneNode(!1);return a.appendChild(this.cloneContents()),a.innerHTML},intersectsNode:function(a,b){Y(this),U(a,"NOT_FOUND_ERR");if(j(a)!==s(this))return!1;var c=a.parentNode,d=h(a);U(c,"NOT_FOUND_ERR");var e=k(c,d,this.endContainer,this.endOffset),f=k(c,d+1,this.startContainer,this.startOffset);return b?e<=0&&f>=0:e<0&&f>0},isPointInRange:function(a,b){return Y(this),U(a,"HIERARCHY_REQUEST_ERR"),S(a,this.startContainer),k(a,b,this.startContainer,this.startOffset)>=0&&k(a,b,this.endContainer,this.endOffset)<=0},intersectsRange:function(a){return w(this,a,!1)},intersectsOrTouchesRange:function(a){return w(this,a,!0)},intersection:function(a){if(this.intersectsRange(a)){var b=k(this.startContainer,this.startOffset,a.startContainer,a.startOffset),c=k(this.endContainer,this.endOffset,a.endContainer,a.endOffset),d=this.cloneRange();return b==-1&&d.setStart(a.startContainer,a.startOffset),c==1&&d.setEnd(a.endContainer,a.endOffset),d}return null},union:function(a){if(this.intersectsOrTouchesRange(a)){var b=this.cloneRange();return k(a.startContainer,a.startOffset,this.startContainer,this.startOffset)==-1&&b.setStart(a.startContainer,a.startOffset),k(a.endContainer,a.endOffset,this.endContainer,this.endOffset)==1&&b.setEnd(a.endContainer,a.endOffset),b}throw new E("Ranges do not intersect")},containsNode:function(a,b){return b?this.intersectsNode(a,!1):this.compareNode(a)==kb},containsNodeContents:function(a){return this.comparePoint(a,0)>=0&&this.comparePoint(a,n(a))<=0},containsRange:function(a){var b=this.intersection(a);return b!==null&&a.equals(b)},containsNodeText:function(a){var b=this.cloneRange();b.selectNode(a);var c=b.getNodes([3]);if(c.length>0){b.setStart(c[0],0);var d=c.pop();b.setEnd(d,d.length);var e=this.containsRange(b);return b.detach(),e}return this.containsNodeContents(a)},getNodes:function(a,b){return Y(this),B(this,a,b)},getDocument:function(){return s(this)},collapseBefore:function(a){P(this),this.setEndBefore(a),this.collapse(!1)},collapseAfter:function(a){P(this),this.setStartAfter(a),this.collapse(!0)},getBookmark:function(b){var d=s(this),e=a.createRange(d);b=b||c.getBody(d),e.selectNodeContents(b);var f=this.intersection(e),g=0,h=0;return f&&(e.setEnd(f.startContainer,f.startOffset),g=e.toString().length,h=g+f.toString().length,e.detach()),{start:g,end:h,containerNode:b}},moveToBookmark:function(a){var b=a.containerNode,c=0;this.setStart(b,0),this.collapse(!0);var d=[b],e,f=!1,g=!1,h,i,j;while(!g&&(e=d.pop()))if(e.nodeType==3)h=c+e.length,!f&&a.start>=c&&a.start<=h&&(this.setStart(e,a.start-c),f=!0),f&&a.end>=c&&a.end<=h&&(this.setEnd(e,a.end-c),g=!0),c=h;else{j=e.childNodes,i=j.length;while(i--)d.push(j[i])}},getName:function(){return"DomRange"},equals:function(a){return sb.rangesEqual(this,a)},isValid:function(){return X(this)},inspect:function(){return C(this)}}),ob(sb,qb,rb),d.extend(sb,{rangeProperties:cb,RangeIterator:D,copyComparisonConstants:mb,createPrototypeRange:ob,inspect:C,getRangeDocument:s,rangesEqual:function(a,b){return a.startContainer===b.startContainer&&a.startOffset===b.startOffset&&a.endContainer===b.endContainer&&a.endOffset===b.endOffset}}),a.DomRange=sb,a.RangeException=E}),rangy.createCoreModule("WrappedRange",["DomRange"],function(a,b){var c,d,e=a.dom,f=a.util,g=e.DomPosition,h=a.DomRange,i=e.getBody,j=e.getContentDocument,k=e.isCharacterDataNode;a.features.implementsDomRange&&function(){function k(a){var b=g.length,c;while(b--)c=g[b],a[c]=a.nativeRange[c];a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset}function l(a,b,c,d,e){var f=a.startContainer!==b||a.startOffset!=c,g=a.endContainer!==d||a.endOffset!=e,h=!a.equals(a.nativeRange);if(f||g||h)a.setEnd(d,e),a.setStart(b,c)}function m(a){a.nativeRange.detach(),a.detached=!0;var b=g.length;while(b--)a[g[b]]=null}var d,g=h.rangeProperties,n;c=function(a){if(!a)throw b.createError("WrappedRange: Range must be specified");this.nativeRange=a,k(this)},h.createPrototypeRange(c,l,m),d=c.prototype,d.selectNode=function(a){this.nativeRange.selectNode(a),k(this)},d.cloneContents=function(){return this.nativeRange.cloneContents()},d.surroundContents=function(a){this.nativeRange.surroundContents(a),k(this)},d.collapse=function(a){this.nativeRange.collapse(a),k(this)},d.cloneRange=function(){return new c(this.nativeRange.cloneRange())},d.refresh=function(){k(this)},d.toString=function(){return this.nativeRange.toString()};var o=document.createTextNode("test");i(document).appendChild(o);var p=document.createRange();p.setStart(o,0),p.setEnd(o,0);try{p.setStart(o,1),d.setStart=function(a,b){this.nativeRange.setStart(a,b),k(this)},d.setEnd=function(a,b){this.nativeRange.setEnd(a,b),k(this)},n=function(a){return function(b){this.nativeRange[a](b),k(this)}}}catch(q){d.setStart=function(a,b){try{this.nativeRange.setStart(a,b)}catch(c){this.nativeRange.setEnd(a,b),this.nativeRange.setStart(a,b)}k(this)},d.setEnd=function(a,b){try{this.nativeRange.setEnd(a,b)}catch(c){this.nativeRange.setStart(a,b),this.nativeRange.setEnd(a,b)}k(this)},n=function(a,b){return function(c){try{this.nativeRange[a](c)}catch(d){this.nativeRange[b](c),this.nativeRange[a](c)}k(this)}}}d.setStartBefore=n("setStartBefore","setEndBefore"),d.setStartAfter=n("setStartAfter","setEndAfter"),d.setEndBefore=n("setEndBefore","setStartBefore"),d.setEndAfter=n("setEndAfter","setStartAfter"),d.selectNodeContents=function(a){this.setStartAndEnd(a,0,e.getNodeLength(a))},p.selectNodeContents(o),p.setEnd(o,3);var r=document.createRange();r.selectNodeContents(o),r.setEnd(o,4),r.setStart(o,2),p.compareBoundaryPoints(p.START_TO_END,r)==-1&&p.compareBoundaryPoints(p.END_TO_START,r)==1?d.compareBoundaryPoints=function(a,b){return b=b.nativeRange||b,a==b.START_TO_END?a=b.END_TO_START:a==b.END_TO_START&&(a=b.START_TO_END),this.nativeRange.compareBoundaryPoints(a,b)}:d.compareBoundaryPoints=function(a,b){return this.nativeRange.compareBoundaryPoints(a,b.nativeRange||b)};var s=document.createElement("div");s.innerHTML="123";var t=s.firstChild,u=i(document);u.appendChild(s),p.setStart(t,1),p.setEnd(t,2),p.deleteContents(),t.data=="13"&&(d.deleteContents=function(){this.nativeRange.deleteContents(),k(this)},d.extractContents=function(){var a=this.nativeRange.extractContents();return k(this),a}),u.removeChild(s),u=null,f.isHostMethod(p,"createContextualFragment")&&(d.createContextualFragment=function(a){return this.nativeRange.createContextualFragment(a)}),i(document).removeChild(o),p.detach(),r.detach(),d.getName=function(){return"WrappedRange"},a.WrappedRange=c,a.createNativeRange=function(a){return a=j(a,b,"createNativeRange"),a.createRange()}}();if(a.features.implementsTextRange){var l=function(a){var b=a.parentElement(),c=a.duplicate();c.collapse(!0);var d=c.parentElement();c=a.duplicate(),c.collapse(!1);var f=c.parentElement(),g=d==f?d:e.getCommonAncestor(d,f);return g==b?g:e.getCommonAncestor(b,g)},m=function(a){return a.compareEndPoints("StartToEnd",a)==0},n=function(a,b,c,d,f){var h=a.duplicate();h.collapse(c);var i=h.parentElement();e.isOrIsAncestorOf(b,i)||(i=b);if(!i.canHaveHTML){var j=new g(i.parentNode,e.getNodeIndex(i));return{boundaryPosition:j,nodeInfo:{nodeIndex:j.offset,containerElement:j.node}}}var l=e.getDocument(i).createElement("span");l.parentNode&&l.parentNode.removeChild(l);var m,n=c?"StartToStart":"StartToEnd",o,p,q,r,s=f&&f.containerElement==i?f.nodeIndex:0,t=i.childNodes.length,u=t,v=u;for(;;){v==t?i.appendChild(l):i.insertBefore(l,i.childNodes[v]),h.moveToElementText(l),m=h.compareEndPoints(n,a);if(m==0||s==u)break;if(m==-1){if(u==s+1)break;s=v}else u=u==s+1?s:v;v=Math.floor((s+u)/2),i.removeChild(l)}r=l.nextSibling;if(m==-1&&r&&k(r)){h.setEndPoint(c?"EndToStart":"EndToEnd",a);var w;if(/[\r\n]/.test(r.data)){var x=h.duplicate(),y=x.text.replace(/\r\n/g,"\r").length;w=x.moveStart("character",y);while((m=x.compareEndPoints("StartToEnd",x))==-1)w++,x.moveStart("character",1)}else w=h.text.length;q=new g(r,w)}else o=(d||!c)&&l.previousSibling,p=(d||c)&&l.nextSibling,p&&k(p)?q=new g(p,0):o&&k(o)?q=new g(o,o.data.length):q=new g(i,e.getNodeIndex(l));return l.parentNode.removeChild(l),{boundaryPosition:q,nodeInfo:{nodeIndex:v,containerElement:i}}},o=function(a,b){var c,d,f=a.offset,g=e.getDocument(a.node),h,j,l=i(g).createTextRange(),m=k(a.node);return m?(c=a.node,d=c.parentNode):(j=a.node.childNodes,c=f1,e=[],f=w(b);for(var g=0;g1)ab(this,a);else{this.removeAllRanges();for(var b=0,c=a.length;b1?ab(this,a):b&&this.addRange(a[0])}}_.getRangeAt=function(a){if(a<0||a>=this.rangeCount)throw new j("INDEX_SIZE_ERR");return this._ranges[a].cloneRange()};var cb;if(z)cb=function(b){var c;a.isSelectionValid(b.win)?c=b.docSelection.createRange():(c=q(b.win.document).createTextRange(),c.collapse(!0)),b.docSelection.type==o?T(b):R(c)?S(b,c):N(b)};else if(g(A,"getRangeAt")&&typeof A.rangeCount==d)cb=function(b){if(J&&y&&b.docSelection.type==o)T(b);else{b._ranges.length=b.rangeCount=b.nativeSelection.rangeCount;if(b.rangeCount){for(var c=0,d=b.rangeCount;ce&&i++,f==b&&(g==c||g==c+1)&&(h=d,i+=e-c),f==b&&g>c+1&&i--,a.node=h,a.offset=i}function p(a,b,d,e){d==-1&&(d=b.childNodes.length);var f=a.parentNode,g=c.getNodeIndex(a);for(var h=0,i;i=e[h++];)o(i,f,g,b,d);b.childNodes.length==d?b.appendChild(a):b.insertBefore(a,b.childNodes[d])}function q(a,b,c,d,e){var f,g=[];while(f=a.firstChild)p(f,b,c++,e),g.push(f);return d&&a.parentNode.removeChild(a),g}function r(a,b){return q(a,a.parentNode,c.getNodeIndex(a),!0,b)}function s(a,b){var c=a.cloneRange();c.selectNodeContents(b);var d=c.intersection(a),e=d?d.toString():"";return c.detach(),e!=""}function t(a){var b=a.getNodes([3]),c=0,d;while((d=b[c])&&!s(a,d))++c;var e=b.length-1;while((d=b[e])&&!s(a,d))--e;return b.slice(c,e+1)}function u(a,b){if(a.attributes.length!=b.attributes.length)return!1;for(var c=0,d=a.attributes.length,e,f,g;c0&&b1){var d=[],e=0,f,g;for(var h=0,i=b.length,j,k;h0){g.removeChild(f),g.hasChildNodes()||g.parentNode.removeChild(g);if(a)for(j=0;k=a[j++];)k.node==f&&(k.node=c,k.offset+=e)}d[h]=f.data,e+=f.data.length}c.data=d.join("")}return c.data},getLength:function(){var a=this.textNodes.length,b=0;while(a--)b+=this.textNodes[a].length;return b},toString:function(){var a=[];for(var b=0,c=this.textNodes.length;be&&++i,f==b&&(g==c||g==c+1)&&(h=d,i+=e-c),f==b&&g>c+1&&--i,a.node=h,a.offset=i}function p(a,b,c){a.node==b&&a.offset>c&&--a.offset}function q(a,b,d,e){d==-1&&(d=b.childNodes.length);var f=a.parentNode,g=c.getNodeIndex(a);for(var h=0,i;i=e[h++];)o(i,f,g,b,d);b.childNodes.length==d?b.appendChild(a):b.insertBefore(a,b.childNodes[d])}function r(a,b){var d=a.parentNode,e=c.getNodeIndex(a);for(var f=0,g;g=b[f++];)p(g,d,e);a.parentNode.removeChild(a)}function s(a,b,c,d,e){var f,g=[];while(f=a.firstChild)q(f,b,c++,e),g.push(f);return d&&a.parentNode.removeChild(a),g}function t(a,b){return s(a,a.parentNode,c.getNodeIndex(a),!0,b)}function u(a,b){var c=a.cloneRange();c.selectNodeContents(b);var d=c.intersection(a),e=d?d.toString():"";return c.detach(),e!=""}function v(a){var b=a.getNodes([3]),c=0,d;while((d=b[c])&&!u(a,d))++c;var e=b.length-1;while((d=b[e])&&!u(a,d))--e;return b.slice(c,e+1)}function w(a,b){if(a.attributes.length!=b.attributes.length)return!1;for(var c=0,d=a.attributes.length,e,f,g;c0&&b1){var d=[],e=0,f,g;for(var h=0,i=b.length,j,k;h0){g.removeChild(f),g.hasChildNodes()||g.parentNode.removeChild(g);if(a)for(j=0;k=a[j++];)k.node==f&&(k.node=c,k.offset+=e)}d[h]=f.data,e+=f.data.length}c.data=d.join("")}return c.data},getLength:function(){var a=this.textNodes.length,b=0;while(a--)b+=this.textNodes[a].length;return b},toString:function(){var a=[];for(var b=0,c=this.textNodes.length;ba.start},union:function(a){return new m(Math.min(this.start,a.start),Math.max(this.end,a.end))},intersection:function(a){return new m(Math.max(this.start,a.start),Math.min(this.end,a.end))},toString:function(){return"[CharacterRange("+this.start+", "+this.end+")]"}},m.fromCharacterRange=function(a){return new m(a.start,a.end)};var n={rangeToCharacterRange:function(a,b){var c=a.getBookmark(b);return new m(c.start,c.end)},characterRangeToRange:function(b,c,d){var e=a.createRange(b);return e.moveToBookmark({start:c.start,end:c.end,containerNode:d}),e},serializeSelection:function(a,b){var c=a.getAllRanges(),d=c.length,e=[],f=d==1&&a.isBackward();for(var g=0,h=c.length;g0},serialize:function(a){var b=this.highlights;b.sort(f);var c=["type:"+this.converter.type];return g(b,function(b){var d=b.characterRange,e=[d.start,d.end,b.id,b.classApplier.cssClass,b.containerElementId];a&&a.serializeHighlightText&&e.push(b.getText()),c.push(e.join("$"))}),c.join("|")},deserialize:function(a){var b=a.split("|"),c=[],d=b[0],f,g,h,i=!1;if(!d||!(f=/^type:(\w+)$/.exec(d)))throw new Error("Serialized highlights are invalid.");g=f[1],g!=this.converter.type&&(h=l(g),i=!0),b.shift();var j,k,n,p,q;for(var r=b.length,s;r-->0;)s=b[r].split("$"),n=new m(+s[0],+s[1]),p=s[4]||null,q=p?this.doc.getElementById(p):e(this.doc),i&&(n=this.converter.rangeToCharacterRange(h.characterRangeToRange(this.doc,n,q),q)),j=this.classAppliers[s[3]],k=new o(this.doc,n,j,this.converter,parseInt(s[2]),p),k.apply(),c.push(k);this.highlights=c}},a.Highlighter=p,a.createHighlighter=function(a,b){return new p(a,b)}}) \ No newline at end of file +rangy.createModule("Highlighter",["ClassApplier"],function(a,b){function f(a,b){return a.characterRange.start-b.characterRange.start}function j(a,b){this.type=a,this.converterCreator=b}function k(a,b){i[a]=new j(a,b)}function l(a){var b=i[a];if(b instanceof j)return b.create();throw new Error("Highlighter type '"+a+"' is not valid")}function m(a,b){this.start=a,this.end=b}function o(a,b,c,d,e,f){e?(this.id=e,h=Math.max(h,e+1)):this.id=h++,this.characterRange=b,this.doc=a,this.classApplier=c,this.converter=d,this.containerElementId=f||null,this.applied=!1}function p(a,b){b=b||"textContent",this.doc=a||document,this.classAppliers={},this.highlights=[],this.converter=l(b)}var c=a.dom,d=c.arrayContains,e=c.getBody,g=[].forEach?function(a,b){a.forEach(b)}:function(a,b){for(var c=0,d=a.length;ca.start},union:function(a){return new m(Math.min(this.start,a.start),Math.max(this.end,a.end))},intersection:function(a){return new m(Math.max(this.start,a.start),Math.min(this.end,a.end))},toString:function(){return"[CharacterRange("+this.start+", "+this.end+")]"}},m.fromCharacterRange=function(a){return new m(a.start,a.end)};var n={rangeToCharacterRange:function(a,b){var c=a.getBookmark(b);return new m(c.start,c.end)},characterRangeToRange:function(b,c,d){var e=a.createRange(b);return e.moveToBookmark({start:c.start,end:c.end,containerNode:d}),e},serializeSelection:function(a,b){var c=a.getAllRanges(),d=c.length,e=[],f=d==1&&a.isBackward();for(var g=0,h=c.length;g0},serialize:function(a){var b=this.highlights;b.sort(f);var c=["type:"+this.converter.type];return g(b,function(b){var d=b.characterRange,e=[d.start,d.end,b.id,b.classApplier.cssClass,b.containerElementId];a&&a.serializeHighlightText&&e.push(b.getText()),c.push(e.join("$"))}),c.join("|")},deserialize:function(a){var b=a.split("|"),c=[],d=b[0],f,g,h,i=!1;if(!d||!(f=/^type:(\w+)$/.exec(d)))throw new Error("Serialized highlights are invalid.");g=f[1],g!=this.converter.type&&(h=l(g),i=!0),b.shift();var j,k,n,p,q;for(var r=b.length,s;r-->0;)s=b[r].split("$"),n=new m(+s[0],+s[1]),p=s[4]||null,q=p?this.doc.getElementById(p):e(this.doc),i&&(n=this.converter.rangeToCharacterRange(h.characterRangeToRange(this.doc,n,q),q)),j=this.classAppliers[s[3]],k=new o(this.doc,n,j,this.converter,parseInt(s[2]),p),k.apply(),c.push(k);this.highlights=c}},a.Highlighter=p,a.createHighlighter=function(a,b){return new p(a,b)}}) \ No newline at end of file diff --git a/dev/rangy-selectionsaverestore.js b/dev/rangy-selectionsaverestore.js index 096d2b1..475c519 100644 --- a/dev/rangy-selectionsaverestore.js +++ b/dev/rangy-selectionsaverestore.js @@ -9,7 +9,7 @@ * * Copyright 2013, Tim Down * Licensed under the MIT license. - * Version: 1.3alpha.772 - * Build date: 26 February 2013 + * Version: 1.3alpha.799 + * Build date: 27 November 2013 */ -rangy.createModule("SaveRestore",function(a,b){function e(a,b){return(b||document).getElementById(a)}function f(a,b){var e="selectionBoundary_"+ +(new Date)+"_"+(""+Math.random()).slice(2),f,g=c.getDocument(a.startContainer),h=a.cloneRange();return h.collapse(b),f=g.createElement("span"),f.id=e,f.style.lineHeight="0",f.style.display="none",f.className="rangySelectionBoundary",f.appendChild(g.createTextNode(d)),h.insertNode(f),h.detach(),f}function g(a,c,d,f){var g=e(d,a);g?(c[f?"setStartBefore":"setEndBefore"](g),g.parentNode.removeChild(g)):b.warn("Marker element has been removed. Cannot restore selection.")}function h(a,b){return b.compareBoundaryPoints(a.START_TO_START,a)}function i(b,c){var d,e,g=a.DomRange.getRangeDocument(b),h=b.toString();return b.collapsed?(e=f(b,!1),{document:g,markerId:e.id,collapsed:!0}):(e=f(b,!1),d=f(b,!0),{document:g,startMarkerId:d.id,endMarkerId:e.id,collapsed:!1,backward:c,toString:function(){return"original text: '"+h+"', new text: '"+b.toString()+"'"}})}function j(c,d){var f=c.document;typeof d=="undefined"&&(d=!0);var h=a.createRange(f);if(c.collapsed){var i=e(c.markerId,f);if(i){i.style.display="inline";var j=i.previousSibling;j&&j.nodeType==3?(i.parentNode.removeChild(i),h.collapseToPoint(j,j.length)):(h.collapseBefore(i),i.parentNode.removeChild(i))}else b.warn("Marker element has been removed. Cannot restore selection.")}else g(f,h,c.startMarkerId,!0),g(f,h,c.endMarkerId,!1);return d&&h.normalizeBoundaries(),h}function k(b,c){var d=[],f,g;b=b.slice(0),b.sort(h);for(var j=0,k=b.length;j=0;--j)f=b[j],g=a.DomRange.getRangeDocument(f),f.collapsed?f.collapseAfter(e(d[j].markerId,g)):(f.setEndBefore(e(d[j].endMarkerId,g)),f.setStartAfter(e(d[j].startMarkerId,g)));return d}function l(c){if(!a.isSelectionValid(c))return b.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."),null;var d=a.getSelection(c),e=d.getAllRanges(),f=e.length==1&&d.isBackward(),g=k(e,f);return f?d.setSingleRange(e[0],"backward"):d.setRanges(e),{win:c,rangeInfos:g,restored:!1}}function m(a){var b=[],c=a.length;for(var d=c-1;d>=0;d--)b[d]=j(a[d],!0);return b}function n(b,c){if(!b.restored){var d=b.rangeInfos,e=a.getSelection(b.win),f=m(d),g=d.length;g==1&&c&&a.features.selectionHasExtend&&d[0].backward?(e.removeAllRanges(),e.addRange(f[0],!0)):e.setRanges(f),b.restored=!0}}function o(a,b){var c=e(b,a);c&&c.parentNode.removeChild(c)}function p(a){var b=a.rangeInfos;for(var c=0,d=b.length,e;c=0;--j)f=b[j],g=a.DomRange.getRangeDocument(f),f.collapsed?f.collapseAfter(e(d[j].markerId,g)):(f.setEndBefore(e(d[j].endMarkerId,g)),f.setStartAfter(e(d[j].startMarkerId,g)));return d}function l(c){if(!a.isSelectionValid(c))return b.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."),null;var d=a.getSelection(c),e=d.getAllRanges(),f=e.length==1&&d.isBackward(),g=k(e,f);return f?d.setSingleRange(e[0],"backward"):d.setRanges(e),{win:c,rangeInfos:g,restored:!1}}function m(a){var b=[],c=a.length;for(var d=c-1;d>=0;d--)b[d]=j(a[d],!0);return b}function n(b,c){if(!b.restored){var d=b.rangeInfos,e=a.getSelection(b.win),f=m(d),g=d.length;g==1&&c&&a.features.selectionHasExtend&&d[0].backward?(e.removeAllRanges(),e.addRange(f[0],!0)):e.setRanges(f),b.restored=!0}}function o(a,b){var c=e(b,a);c&&c.parentNode.removeChild(c)}function p(a){var b=a.rangeInfos;for(var c=0,d=b.length,e;c/g,">")}function g(a,b){b=b||[];var c=a.nodeType,d=a.childNodes,e=d.length,h=[c,a.nodeName,e].join(":"),i="",j="";switch(c){case 3:i=f(a.nodeValue);break;case 8:i="";break;default:i="<"+h+">",j=""}i&&b.push(i);for(var k=0;k>6|192,e&63|128):b.push(e>>12|224,e>>6&63|128,e&63|128);return b}function c(){var a=[];for(var b=0,c,d;b<256;++b){d=b,c=8;while(c--)(d&1)==1?d=d>>>1^3988292384:d>>>=1;a[b]=d>>>0}return a}function d(){return b||(b=c()),b}var b=null;return function(b){var c=a(b),e=-1,f=d();for(var g=0,h=c.length,i;g>>8^f[i];return(e^-1)>>>0}}(),e=a.dom,q="rangySerializedSelection";a.serializePosition=i,a.deserializePosition=j,a.serializeRange=k,a.deserializeRange=l,a.canDeserializeRange=m,a.serializeSelection=n,a.deserializeSelection=o,a.canDeserializeSelection=p,a.restoreSelectionFromCookie=s,a.saveSelectionCookie=t,a.getElementChecksum=h,a.nodeToInfoString=g}) \ No newline at end of file +rangy.createModule("Serializer",["WrappedSelection"],function(a,b){function f(a){return a.replace(//g,">")}function g(a,b){b=b||[];var c=a.nodeType,d=a.childNodes,e=d.length,h=[c,a.nodeName,e].join(":"),i="",j="";switch(c){case 3:i=f(a.nodeValue);break;case 8:i="";break;default:i="<"+h+">",j=""}i&&b.push(i);for(var k=0;k>6|192,e&63|128):b.push(e>>12|224,e>>6&63|128,e&63|128);return b}function c(){var a=[];for(var b=0,c,d;b<256;++b){d=b,c=8;while(c--)(d&1)==1?d=d>>>1^3988292384:d>>>=1;a[b]=d>>>0}return a}function d(){return b||(b=c()),b}var b=null;return function(b){var c=a(b),e=-1,f=d();for(var g=0,h=c.length,i;g>>8^f[i];return(e^-1)>>>0}}(),e=a.dom,l=/^([^,]+),([^,\{]+)(\{([^}]+)\})?$/,r="rangySerializedSelection";a.serializePosition=i,a.deserializePosition=j,a.serializeRange=k,a.deserializeRange=m,a.canDeserializeRange=n,a.serializeSelection=o,a.deserializeSelection=p,a.canDeserializeSelection=q,a.restoreSelectionFromCookie=t,a.saveSelectionCookie=u,a.getElementChecksum=h,a.nodeToInfoString=g}) \ No newline at end of file diff --git a/dev/rangy-textrange.js b/dev/rangy-textrange.js index f7be4c6..4c3fd0f 100644 --- a/dev/rangy-textrange.js +++ b/dev/rangy-textrange.js @@ -26,7 +26,7 @@ * * Copyright 2013, Tim Down * Licensed under the MIT license. - * Version: 1.3alpha.772 - * Build date: 26 February 2013 + * Version: 1.3alpha.799 + * Build date: 27 November 2013 */ -rangy.createModule("TextRange",function(a,b){function t(a,b){function f(b,c,d){var f=a.slice(b,c),g={isWord:d,chars:f,toString:function(){return f.join("")}};for(var h=0,i=f.length;hg&&f(g,h,!1);if(b.includeTrailingSpace)while(m.test(a[i]))++i;f(h,i,!0),g=i}return g0)k=f(a.chars.concat(b),c);return k.shift()},previousStartToken:function(){var a,b;while(m.length==1&&!(a=m[0]).isWord&&(b=g(!1)).length>0)m=f(b.reverse().concat(a.chars),c);return m.pop()},dispose:function(){d.dispose(),e.dispose(),k=m=null}}}function vb(a,b,c,f,g){var h=0,i,j=a,k,l,m=Math.abs(c),n;if(c!==0){var o=c<0;switch(b){case d:k=sb(a,o,null,f);while((i=k.next())&&h0){q=r(l,m);break}}else o=!0}else if((l=h.indexOf(b))!=-1){q=r(l,l+b.length);break}}return o&&(q=r(l,m)),g.dispose(),q}function Ab(a){return function(){var b=!!nb,c=qb(),d=[c].concat(g.toArray(arguments)),e=a.apply(this,d);return b||rb(),e}}function Bb(a,b){return Ab(function(c,e,f,g){typeof f=="undefined"&&(f=e,e=d),g=x(g,C);var h=z(g.characterOptions),i=y(g.wordOptions),j=a;b&&(j=f>=0,this.collapse(!j));var k=vb(c.getRangeBoundaryPosition(this,j),e,f,h,i),l=k.position;return this[j?"setStart":"setEnd"](l.node,l.offset),k.unitsMoved})}function Cb(a){return Ab(function(b,c){c=z(c);var d,e=wb(b,this,c,!a),f=0;while((d=e.next())&&l.test(d.character))++f;e.dispose();var g=f>0;return g&&this[a?"moveStart":"moveEnd"]("character",a?f:-f,{characterOptions:c}),g})}function Db(a){return Ab(function(b,c){var d=!1;return this.changeEachRange(function(b){d=b[a](c)||d}),d})}a.requireModules(["WrappedSelection"]);var c="undefined",d="character",e="word",f=a.dom,g=a.util,h=g.extend,i=f.getBody,j=/^[ \t\f\r\n]+$/,k=/^[ \t\f\r]+$/,l=/^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/,m=/^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/,n=/^[\n-\r\u0085\u2028\u2029]$/,o="en",p=a.Selection.isDirectionBackward,q=!1,r=!1,s=!0;(function(){var b=document.createElement("div");b.contentEditable="true",b.innerHTML="

    1

    ";var c=i(document),d=b.firstChild,e=a.getSelection();c.appendChild(b),e.collapse(d.lastChild,2),e.setStart(d.firstChild,0),q=(""+e).length==1,b.innerHTML="1
    ",e.collapse(b,2),e.setStart(b.firstChild,0),r=(""+e).length==1,c.removeChild(b),e.removeAllRanges()})();var u={includeBlockContentTrailingSpace:!0,includeSpaceBeforeBr:!0,includePreLineTrailingSpace:!0},v={includeBlockContentTrailingSpace:!s,includeSpaceBeforeBr:!r,includePreLineTrailingSpace:!0},w={en:{wordRegex:/[a-z0-9]+('[a-z0-9]+)*/gi,includeTrailingSpace:!1,tokenizer:t}},B={caseSensitive:!1,withinRange:null,wholeWordsOnly:!1,wrap:!1,direction:"forward",wordOptions:null,characterOptions:null},C={wordOptions:null,characterOptions:null},D={wordOptions:null,characterOptions:null,trim:!1,trimStart:!0,trimEnd:!0},E={wordOptions:null,characterOptions:null,direction:"forward"},F=f.getComputedStyleProperty,G;(function(){var a=document.createElement("table"),b=i(document);b.appendChild(a),G=F(a,"display")=="block",b.removeChild(a)})(),a.features.tableCssDisplayBlock=G;var H={table:"table",caption:"table-caption",colgroup:"table-column-group",col:"table-column",thead:"table-header-group",tbody:"table-row-group",tfoot:"table-footer-group",tr:"table-row",td:"table-cell",th:"table-cell"};Z.prototype={get:function(a){return this.store.hasOwnProperty(a)?this.store[a]:null},set:function(a,b){return this.store[a]=b}};var $=0,_=0;a.report=function(){console.log("Cached: "+$+", uncached: "+_)};var cb={getPosition:function(a){var b=this.positions;return b.get(a)||b.set(a,new kb(this,a))},toString:function(){return"[NodeWrapper("+f.inspectNode(this.node)+")]"}};bb.prototype=cb;var db="EMPTY",eb="NON_SPACE",fb="UNCOLLAPSIBLE_SPACE",gb="COLLAPSIBLE_SPACE",hb="TRAILING_SPACE_IN_BLOCK",ib="TRAILING_SPACE_BEFORE_BR",jb="PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK";h(cb,{isCharacterDataNode:ab("isCharacterDataNode",f.isCharacterDataNode,"node"),getNodeIndex:ab("nodeIndex",f.getNodeIndex,"node"),getLength:ab("nodeLength",f.getNodeLength,"node"),containsPositions:ab("containsPositions",N,"node"),isWhitespace:ab("isWhitespace",V,"node"),isCollapsedWhitespace:ab("isCollapsedWhitespace",W,"node"),getComputedDisplay:ab("computedDisplay",I,"node"),isCollapsed:ab("collapsed",X,"node"),isIgnored:ab("ignored",Y,"node"),next:ab("nextPos",T,"node"),previous:ab("previous",U,"node"),getTextNodeInfo:ab("textNodeInfo",function(a){var b=null,c=!1,d=F(a.parentNode,"whiteSpace"),e=d=="pre-line";if(e)b=k,c=!0;else if(d=="normal"||d=="nowrap")b=j,c=!0;return{node:a,text:a.data,spaceRegex:b,collapseSpaces:c,preLine:e}},"node"),hasInnerText:ab("hasInnerText",function(a,b){var c=this.session,d=c.getPosition(a.parentNode,this.getNodeIndex()+1),e=c.getPosition(a,0),f=b?d:e,g=b?e:d;while(f!==g){f.prepopulateChar();if(f.isDefinitelyNonEmpty())return!0;f=b?f.previousVisible():f.nextVisible()}return!1},"node"),getTrailingSpace:ab("trailingSpace",function(a){if(a.tagName.toLowerCase()=="br")return"";switch(this.getComputedDisplay()){case"inline":var b=a.lastChild;while(b){if(!Y(b))return b.nodeType==1?this.session.getNodeWrapper(b).getTrailingSpace():"";b=b.previousSibling}break;case"inline-block":case"inline-table":case"none":case"table-column":case"table-column-group":break;case"table-cell":return" ";default:return this.hasInnerText(!0)?"\n":""}return""},"node"),getLeadingSpace:ab("leadingSpace",function(a){switch(this.getComputedDisplay()){case"inline":case"inline-block":case"inline-table":case"none":case"table-column":case"table-column-group":case"table-cell":break;default:return this.hasInnerText(!1)?"\n":""}return""},"node")});var mb={character:"",characterType:db,isBr:!1,prepopulateChar:function(){var a=this;if(!a.prepopulatedChar){var b=a.node,c=a.offset,d="",e=db,f=!1;if(c>0)if(b.nodeType==3){var g=b.data,h=g.charAt(c-1),i=a.nodeWrapper.getTextNodeInfo(),j=i.spaceRegex;i.collapseSpaces?j.test(h)?c>1&&j.test(g.charAt(c-2))||(i.preLine&&g.charAt(c)==="\n"?(d=" ",e=jb):(d=" ",e=gb)):(d=h,e=eb,f=!0):(d=h,e=fb,f=!0)}else{var k=b.childNodes[c-1];k&&k.nodeType==1&&!X(k)&&(k.tagName.toLowerCase()=="br"?(d="\n",a.isBr=!0,e=gb,f=!1):a.checkForTrailingSpace=!0);if(!d){var l=b.childNodes[c];l&&l.nodeType==1&&!X(l)&&(a.checkForLeadingSpace=!0)}}a.prepopulatedChar=!0,a.character=d,a.characterType=e,a.isCharInvariant=f}},isDefinitelyNonEmpty:function(){var a=this.characterType;return a==eb||a==fb},resolveLeadingAndTrailingSpaces:function(){this.prepopulatedChar||this.prepopulateChar();if(this.checkForTrailingSpace){var a=this.session.getNodeWrapper(this.node.childNodes[this.offset-1]).getTrailingSpace();a&&(this.isTrailingSpace=!0,this.character=a,this.characterType=gb),this.checkForTrailingSpace=!1}if(this.checkForLeadingSpace){var b=this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace();b&&(this.isLeadingSpace=!0,this.character=b,this.characterType=gb),this.checkForLeadingSpace=!1}},getPrecedingUncollapsedPosition:function(a){var b=this,c;while(b=b.previousVisible()){c=b.getCharacter(a);if(c!=="")return b}return null},getCharacter:function(a){function j(){return h||(g=i.getPrecedingUncollapsedPosition(a),h=!0),g}this.resolveLeadingAndTrailingSpaces();if(this.isCharInvariant)return this.character;var b=["character",a.includeSpaceBeforeBr,a.includeBlockContentTrailingSpace,a.includePreLineTrailingSpace].join("_"),c=this.cache.get(b);if(c!==null)return c;var d="",e=this.characterType==gb,f,g,h=!1,i=this;if(e){if(this.character!=" "||!!j()&&!g.isTrailingSpace&&g.character!="\n")if(this.character=="\n"&&this.isLeadingSpace)j()&&g.character!="\n"&&(d="\n");else{f=this.nextUncollapsed();if(f){f.isBr?this.type=ib:f.isTrailingSpace&&f.character=="\n"&&(this.type=hb);if(f.character==="\n"){if(this.type!=ib||!!a.includeSpaceBeforeBr)if(this.type!=hb||!f.isTrailingSpace||!!a.includeBlockContentTrailingSpace)if(this.type!=jb||f.type!=eb||!!a.includePreLineTrailingSpace)this.character==="\n"?f.isTrailingSpace?this.isTrailingSpace||!this.isBr:d="\n":this.character===" "&&(d=" ")}else d=this.character}}}else this.character!=="\n"||!!(f=this.nextUncollapsed())&&!f.isTrailingSpace;return this.cache.set(b,d),d},equals:function(a){return!!a&&this.node===a.node&&this.offset===a.offset},inspect:lb,toString:function(){return this.character}};kb.prototype=mb,h(mb,{next:ab("nextPos",function(a){var b=a.nodeWrapper,c=a.node,d=a.offset,e=b.session;if(!c)return null;var f,g,h;return d==b.getLength()?(f=c.parentNode,g=f?b.getNodeIndex()+1:0):b.isCharacterDataNode()?(f=c,g=d+1):(h=c.childNodes[d],e.getNodeWrapper(h).containsPositions()?(f=h,g=0):(f=c,g=d+1)),f?e.getPosition(f,g):null}),previous:ab("previous",function(a){var b=a.nodeWrapper,c=a.node,d=a.offset,e=b.session,g,h,i;return d==0?(g=c.parentNode,h=g?b.getNodeIndex():0):b.isCharacterDataNode()?(g=c,h=d-1):(i=c.childNodes[d-1],e.getNodeWrapper(i).containsPositions()?(g=i,h=f.getNodeLength(i)):(g=c,h=d-1)),g?e.getPosition(g,h):null}),nextVisible:ab("nextVisible",function(a){var b=a.next();if(!b)return null;var c=b.nodeWrapper,d=b.node,e=b;return c.isCollapsed()&&(e=c.session.getPosition(d.parentNode,c.getNodeIndex()+1)),e}),nextUncollapsed:ab("nextUncollapsed",function(a){var b=a;while(b=b.nextVisible()){b.resolveLeadingAndTrailingSpaces();if(b.character!=="")return b}return null}),previousVisible:ab("previousVisible",function(a){var b=a.previous();if(!b)return null;var c=b.nodeWrapper,d=b.node,e=b;return c.isCollapsed()&&(e=c.session.getPosition(d.parentNode,c.getNodeIndex())),e})});var nb=null,ob=function(){function a(a){var b=new Z;return{get:function(c){var d=b.get(c[a]);if(d)for(var e=0,f;f=d[e++];)if(f.node===c)return f;return null},set:function(c){var d=c.node[a],e=b.get(d)||b.set(d,[]);e.push(c)}}}function c(){this.initCaches()}var b=g.isHostProperty(document.documentElement,"uniqueID");return c.prototype={initCaches:function(){this.elementCache=b?function(){var a=new Z;return{get:function(b){return a.get(b.uniqueID)},set:function(b){a.set(b.node.uniqueID,b)}}}():a("tagName"),this.textNodeCache=a("data"),this.otherNodeCache=a("nodeName")},getNodeWrapper:function(a){var b;switch(a.nodeType){case 1:b=this.elementCache;break;case 3:b=this.textNodeCache;break;default:b=this.otherNodeCache}var c=b.get(a);return c||(c=new bb(a,this),b.set(c)),c},getPosition:function(a,b){return this.getNodeWrapper(a).getPosition(b)},getRangeBoundaryPosition:function(a,b){var c=b?"start":"end";return this.getPosition(a[c+"Container"],a[c+"Offset"])},detach:function(){this.elementCache=this.textNodeCache=this.otherNodeCache=null}},c}();h(f,{nextNode:T,previousNode:U});var tb=Array.prototype.indexOf?function(a,b){return a.indexOf(b)}:function(a,b){for(var c=0,d=a.length;cg&&f(g,h,!1);if(b.includeTrailingSpace)while(m.test(a[i]))++i;f(h,i,!0),g=i}return g0)k=f(a.chars.concat(b),c);return k.shift()},previousStartToken:function(){var a,b;while(m.length==1&&!(a=m[0]).isWord&&(b=g(!1)).length>0)m=f(b.reverse().concat(a.chars),c);return m.pop()},dispose:function(){d.dispose(),e.dispose(),k=m=null}}}function wb(a,b,c,f,g){var h=0,i,j=a,k,l,m=Math.abs(c),n;if(c!==0){var o=c<0;switch(b){case d:k=tb(a,o,null,f);while((i=k.next())&&h0){q=r(l,m);break}}else o=!0}else if((l=h.indexOf(b))!=-1){q=r(l,l+b.length);break}console.log(h.replace(/\s/g,function(a){return"["+a.charCodeAt(0)+"]"}),l)}return o&&(q=r(l,m)),g.dispose(),q}function Bb(a){return function(){var b=!!ob,c=rb(),d=[c].concat(g.toArray(arguments)),e=a.apply(this,d);return b||sb(),e}}function Cb(a,b){return Bb(function(c,e,f,g){typeof f=="undefined"&&(f=e,e=d),g=y(g,D);var h=A(g.characterOptions),i=z(g.wordOptions),j=a;b&&(j=f>=0,this.collapse(!j));var k=wb(c.getRangeBoundaryPosition(this,j),e,f,h,i),l=k.position;return this[j?"setStart":"setEnd"](l.node,l.offset),k.unitsMoved})}function Db(a){return Bb(function(b,c){c=A(c);var d,e=xb(b,this,c,!a),f=0;while((d=e.next())&&l.test(d.character))++f;e.dispose();var g=f>0;return g&&this[a?"moveStart":"moveEnd"]("character",a?f:-f,{characterOptions:c}),g})}function Eb(a){return Bb(function(b,c){var d=!1;return this.changeEachRange(function(b){d=b[a](c)||d}),d})}var c="undefined",d="character",e="word",f=a.dom,g=a.util,h=g.extend,i=f.getBody,j=/^[ \t\f\r\n]+$/,k=/^[ \t\f\r]+$/,l=/^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/,m=/^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/,n=/^[\n-\r\u0085\u2028\u2029]$/,o="en",p=a.Selection.isDirectionBackward,q=!1,r=!1,s=!1,t=!0;(function(){var b=document.createElement("div");b.contentEditable="true",b.innerHTML="

    1

    ";var c=i(document),d=b.firstChild,e=a.getSelection();c.appendChild(b),e.collapse(d.lastChild,2),e.setStart(d.firstChild,0),q=(""+e).length==1,b.innerHTML="1
    ",e.collapse(b,2),e.setStart(b.firstChild,0),r=(""+e).length==1,b.innerHTML="1

    1

    ",e.collapse(b,2),e.setStart(b.firstChild,0),s=(""+e).length==1,c.removeChild(b),e.removeAllRanges()})();var v={includeBlockContentTrailingSpace:!0,includeSpaceBeforeBr:!0,includeSpaceBeforeBlock:!0,includePreLineTrailingSpace:!0},w={includeBlockContentTrailingSpace:!t,includeSpaceBeforeBr:!r,includeSpaceBeforeBlock:!s,includePreLineTrailingSpace:!0},x={en:{wordRegex:/[a-z0-9]+('[a-z0-9]+)*/gi,includeTrailingSpace:!1,tokenizer:u}},C={caseSensitive:!1,withinRange:null,wholeWordsOnly:!1,wrap:!1,direction:"forward",wordOptions:null,characterOptions:null},D={wordOptions:null,characterOptions:null},E={wordOptions:null,characterOptions:null,trim:!1,trimStart:!0,trimEnd:!0},F={wordOptions:null,characterOptions:null,direction:"forward"},G=f.getComputedStyleProperty,H;(function(){var a=document.createElement("table"),b=i(document);b.appendChild(a),H=G(a,"display")=="block",b.removeChild(a)})(),a.features.tableCssDisplayBlock=H;var I={table:"table",caption:"table-caption",colgroup:"table-column-group",col:"table-column",thead:"table-header-group",tbody:"table-row-group",tfoot:"table-footer-group",tr:"table-row",td:"table-cell",th:"table-cell"};Y.prototype={get:function(a){return this.store.hasOwnProperty(a)?this.store[a]:null},set:function(a,b){return this.store[a]=b}};var Z=0,$=0;a.report=function(){console.log("Cached: "+Z+", uncached: "+$)};var bb={getPosition:function(a){var b=this.positions;return b.get(a)||b.set(a,new lb(this,a))},toString:function(){return"[NodeWrapper("+f.inspectNode(this.node)+")]"}};ab.prototype=bb;var cb="EMPTY",db="NON_SPACE",eb="UNCOLLAPSIBLE_SPACE",fb="COLLAPSIBLE_SPACE",gb="TRAILING_SPACE_BEFORE_BLOCK",hb="TRAILING_SPACE_IN_BLOCK",ib="TRAILING_SPACE_BEFORE_BR",jb="PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK",kb="TRAILING_LINE_BREAK_AFTER_BR";h(bb,{isCharacterDataNode:_("isCharacterDataNode",f.isCharacterDataNode,"node"),getNodeIndex:_("nodeIndex",f.getNodeIndex,"node"),getLength:_("nodeLength",f.getNodeLength,"node"),containsPositions:_("containsPositions",O,"node"),isWhitespace:_("isWhitespace",U,"node"),isCollapsedWhitespace:_("isCollapsedWhitespace",V,"node"),getComputedDisplay:_("computedDisplay",J,"node"),isCollapsed:_("collapsed",W,"node"),isIgnored:_("ignored",X,"node"),next:_("nextPos",S,"node"),previous:_("previous",T,"node"),getTextNodeInfo:_("textNodeInfo",function(a){var b=null,c=!1,d=G(a.parentNode,"whiteSpace"),e=d=="pre-line";if(e)b=k,c=!0;else if(d=="normal"||d=="nowrap")b=j,c=!0;return{node:a,text:a.data,spaceRegex:b,collapseSpaces:c,preLine:e}},"node"),hasInnerText:_("hasInnerText",function(a,b){var c=this.session,d=c.getPosition(a.parentNode,this.getNodeIndex()+1),e=c.getPosition(a,0),f=b?d:e,g=b?e:d;while(f!==g){f.prepopulateChar();if(f.isDefinitelyNonEmpty())return!0;f=b?f.previousVisible():f.nextVisible()}return!1},"node"),isRenderedBlock:_("isRenderedBlock",function(a){var b=a.getElementsByTagName("br");for(var c=0,d=b.length;c0)if(b.nodeType==3){var g=b.data,h=g.charAt(c-1),i=a.nodeWrapper.getTextNodeInfo(),j=i.spaceRegex;i.collapseSpaces?j.test(h)?c>1&&j.test(g.charAt(c-2))||(i.preLine&&g.charAt(c)==="\n"?(d=" ",e=jb):(d=" ",e=fb)):(d=h,e=db,f=!0):(d=h,e=eb,f=!0)}else{var k=b.childNodes[c-1];k&&k.nodeType==1&&!W(k)&&(k.tagName.toLowerCase()=="br"?(d="\n",a.isBr=!0,e=fb,f=!1):a.checkForTrailingSpace=!0);if(!d){var l=b.childNodes[c];l&&l.nodeType==1&&!W(l)&&(a.checkForLeadingSpace=!0)}}a.prepopulatedChar=!0,a.character=d,a.characterType=e,a.isCharInvariant=f}},isDefinitelyNonEmpty:function(){var a=this.characterType;return a==db||a==eb},resolveLeadingAndTrailingSpaces:function(){this.prepopulatedChar||this.prepopulateChar();if(this.checkForTrailingSpace){var a=this.session.getNodeWrapper(this.node.childNodes[this.offset-1]).getTrailingSpace();a&&(this.isTrailingSpace=!0,this.character=a,this.characterType=fb),this.checkForTrailingSpace=!1}if(this.checkForLeadingSpace){var b=this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace();b&&(this.isLeadingSpace=!0,this.character=b,this.characterType=fb),this.checkForLeadingSpace=!1}},getPrecedingUncollapsedPosition:function(a){var b=this,c;while(b=b.previousVisible()){c=b.getCharacter(a);if(c!=="")return b}return null},getCharacter:function(a){function j(){return h||(g=i.getPrecedingUncollapsedPosition(a),h=!0),g}this.resolveLeadingAndTrailingSpaces();if(this.isCharInvariant)return this.character;var b=["character",a.includeSpaceBeforeBr,a.includeBlockContentTrailingSpace,a.includePreLineTrailingSpace].join("_"),c=this.cache.get(b);if(c!==null)return c;var d="",e=this.characterType==fb,f,g,h=!1,i=this;if(e){if(this.character!=" "||!!j()&&!g.isTrailingSpace&&g.character!="\n")if(this.character=="\n"&&this.isLeadingSpace)j()&&g.character!="\n"&&(d="\n");else{f=this.nextUncollapsed();if(f){f.isBr?this.type=ib:f.isTrailingSpace&&f.character=="\n"?this.type=hb:f.isLeadingSpace&&f.character=="\n"&&(this.type=gb);if(f.character==="\n"){if(this.type!=ib||!!a.includeSpaceBeforeBr)if(this.type!=gb||!!a.includeSpaceBeforeBlock)if(this.type!=hb||!f.isTrailingSpace||!!a.includeBlockContentTrailingSpace)if(this.type!=jb||f.type!=db||!!a.includePreLineTrailingSpace)this.character==="\n"?f.isTrailingSpace?this.isTrailingSpace||this.isBr&&(f.type=kb,j()&&g.isLeadingSpace&&g.character=="\n"&&(f.character="")):d="\n":this.character===" "&&(d=" ")}else d=this.character}}}else this.character!=="\n"||!!(f=this.nextUncollapsed())&&!f.isTrailingSpace;return this.cache.set(b,d),d},equals:function(a){return!!a&&this.node===a.node&&this.offset===a.offset},inspect:mb,toString:function(){return this.character}};lb.prototype=nb,h(nb,{next:_("nextPos",function(a){var b=a.nodeWrapper,c=a.node,d=a.offset,e=b.session;if(!c)return null;var f,g,h;return d==b.getLength()?(f=c.parentNode,g=f?b.getNodeIndex()+1:0):b.isCharacterDataNode()?(f=c,g=d+1):(h=c.childNodes[d],e.getNodeWrapper(h).containsPositions()?(f=h,g=0):(f=c,g=d+1)),f?e.getPosition(f,g):null}),previous:_("previous",function(a){var b=a.nodeWrapper,c=a.node,d=a.offset,e=b.session,g,h,i;return d==0?(g=c.parentNode,h=g?b.getNodeIndex():0):b.isCharacterDataNode()?(g=c,h=d-1):(i=c.childNodes[d-1],e.getNodeWrapper(i).containsPositions()?(g=i,h=f.getNodeLength(i)):(g=c,h=d-1)),g?e.getPosition(g,h):null}),nextVisible:_("nextVisible",function(a){var b=a.next();if(!b)return null;var c=b.nodeWrapper,d=b.node,e=b;return c.isCollapsed()&&(e=c.session.getPosition(d.parentNode,c.getNodeIndex()+1)),e}),nextUncollapsed:_("nextUncollapsed",function(a){var b=a;while(b=b.nextVisible()){b.resolveLeadingAndTrailingSpaces();if(b.character!=="")return b}return null}),previousVisible:_("previousVisible",function(a){var b=a.previous();if(!b)return null;var c=b.nodeWrapper,d=b.node,e=b;return c.isCollapsed()&&(e=c.session.getPosition(d.parentNode,c.getNodeIndex())),e})});var ob=null,pb=function(){function a(a){var b=new Y;return{get:function(c){var d=b.get(c[a]);if(d)for(var e=0,f;f=d[e++];)if(f.node===c)return f;return null},set:function(c){var d=c.node[a],e=b.get(d)||b.set(d,[]);e.push(c)}}}function c(){this.initCaches()}var b=g.isHostProperty(document.documentElement,"uniqueID");return c.prototype={initCaches:function(){this.elementCache=b?function(){var a=new Y;return{get:function(b){return a.get(b.uniqueID)},set:function(b){a.set(b.node.uniqueID,b)}}}():a("tagName"),this.textNodeCache=a("data"),this.otherNodeCache=a("nodeName")},getNodeWrapper:function(a){var b;switch(a.nodeType){case 1:b=this.elementCache;break;case 3:b=this.textNodeCache;break;default:b=this.otherNodeCache}var c=b.get(a);return c||(c=new ab(a,this),b.set(c)),c},getPosition:function(a,b){return this.getNodeWrapper(a).getPosition(b)},getRangeBoundaryPosition:function(a,b){var c=b?"start":"end";return this.getPosition(a[c+"Container"],a[c+"Offset"])},detach:function(){this.elementCache=this.textNodeCache=this.otherNodeCache=null}},c}();h(f,{nextNode:S,previousNode:T});var ub=Array.prototype.indexOf?function(a,b){return a.indexOf(b)}:function(a,b){for(var c=0,d=a.length;c[" + node.childNodes.length + "][" + node.innerHTML.slice(0, 20) + "]"; + return "<" + node.nodeName + idAttr + ">[" + getNodeIndex(node) + "][" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]"; } return node.nodeName; } @@ -853,9 +913,7 @@ rangy.createModule("DomUtil", function(api, module) { api.DOMException = DOMException; }); -rangy.createModule("DomRange", function(api, module) { - api.requireModules( ["DomUtil"] ); - +rangy.createCoreModule("DomRange", ["DomUtil"], function(api, module) { var dom = api.dom; var util = api.util; var DomPosition = dom.DomPosition; @@ -1016,9 +1074,25 @@ rangy.createModule("DomRange", function(api, module) { var nodes = []; iterateSubtree(new RangeIterator(range, false), function(node) { - if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) { - nodes.push(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; } @@ -1376,9 +1450,7 @@ rangy.createModule("DomRange", function(api, module) { var s2s = 0, s2e = 1, e2e = 2, e2s = 3; var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; - function RangePrototype() {} - - RangePrototype.prototype = { + util.extend(api.rangePrototype, { compareBoundaryPoints: function(how, range) { assertRangeValid(this); assertSameDocumentOrFragment(this.startContainer, range.startContainer); @@ -1487,15 +1559,15 @@ rangy.createModule("DomRange", function(api, module) { if (sc === this.endContainer && isCharacterDataNode(sc)) { return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; } else { - var textBits = [], iterator = new RangeIterator(this, true); + 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) { - textBits.push(node.data); + textParts.push(node.data); } }); iterator.detach(); - return textBits.join(""); + return textParts.join(""); } }, @@ -1737,7 +1809,7 @@ rangy.createModule("DomRange", function(api, module) { inspect: function() { return inspect(this); } - }; + }); function copyComparisonConstantsToObject(obj) { obj.START_TO_START = s2s; @@ -1827,7 +1899,10 @@ rangy.createModule("DomRange", function(api, module) { } } - constructor.prototype = new RangePrototype(); + // Set up inheritance + var F = function() {}; + F.prototype = api.rangePrototype; + constructor.prototype = new F(); util.extend(constructor.prototype, { setStart: function(node, offset) { @@ -2059,8 +2134,6 @@ rangy.createModule("DomRange", function(api, module) { createPrototypeRange(Range, updateBoundaries, detach); - api.rangePrototype = RangePrototype.prototype; - util.extend(Range, { rangeProperties: rangeProperties, RangeIterator: RangeIterator, @@ -2079,9 +2152,7 @@ rangy.createModule("DomRange", function(api, module) { api.DomRange = Range; api.RangeException = RangeException; }); -rangy.createModule("WrappedRange", function(api, module) { - api.requireModules( ["DomUtil", "DomRange"] ); - +rangy.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { var WrappedRange, WrappedTextRange; var dom = api.dom; var util = api.util; @@ -2258,20 +2329,11 @@ rangy.createModule("WrappedRange", function(api, module) { /*--------------------------------------------------------------------------------------------------------*/ - // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to - // the 0th character of the text node - range.selectNodeContents(testTextNode); - if (range.startContainer == testTextNode && range.endContainer == testTextNode && - range.startOffset == 0 && range.endOffset == testTextNode.length) { - rangeProto.selectNodeContents = function(node) { - this.nativeRange.selectNodeContents(node); - updateRangeProperties(this); - }; - } else { - rangeProto.selectNodeContents = function(node) { - this.setStartAndEnd(node, 0, dom.getNodeLength(node)); - }; - } + // 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)); + }; /*--------------------------------------------------------------------------------------------------------*/ @@ -2369,19 +2431,19 @@ rangy.createModule("WrappedRange", function(api, module) { 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): + 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): -
    • | a
    • b |
    +
    • | a
    • b |
    - var range = document.selection.createRange(); - alert(range.parentElement().id); // Should alert "ul" but alerts "b" + var range = document.selection.createRange(); + alert(range.parentElement().id); // Should alert "ul" but alerts "b" - This method returns the common ancestor node of the following: - - the parentElement() of the textRange - - the parentElement() of the textRange after calling collapse(true) - - the parentElement() of the textRange after calling collapse(false) - */ + This method returns the common ancestor node of the following: + - the parentElement() of the textRange + - the parentElement() of the textRange after calling collapse(true) + - the parentElement() of the textRange after calling collapse(false) + */ var getTextRangeContainerElement = function(textRange) { var parentEl = textRange.parentElement(); var range = textRange.duplicate(); @@ -2485,35 +2547,35 @@ rangy.createModule("WrappedRange", function(api, module) { if (/[\r\n]/.test(boundaryNode.data)) { /* - For the particular case of a boundary within a text node containing rendered line breaks (within a
    -                     element, for example), we need a slightly complicated approach to get the boundary's offset in IE. The
    -                     facts:
    -
    -                     - Each line break is represented as \r in the text node's data/nodeValue properties
    -                     - Each line break is represented as \r\n in the TextRange's 'text' property
    -                     - The 'text' property of the TextRange does not contain trailing line breaks
    -
    -                     To get round the problem presented by the final fact above, we can use the fact that TextRange's
    -                     moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
    -                     the same as the number of characters it was instructed to move. The simplest approach is to use this to
    -                     store the characters moved when moving both the start and end of the range to the start of the document
    -                     body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
    -                     However, this is extremely slow when the document is large and the range is near the end of it. Clearly
    -                     doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
    -                     problem.
    -
    -                     Another approach that works is to use moveStart() to move the start boundary of the range up to the end
    -                     boundary one character at a time and incrementing a counter with the value returned by the moveStart()
    -                     call. However, the check for whether the start boundary has reached the end boundary is expensive, so
    -                     this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
    -                     the range within the document).
    -
    -                     The method below is a hybrid of the two methods above. It uses the fact that a string containing the
    -                     TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
    -                     text of the TextRange, so the start of the range is moved that length initially and then a character at
    -                     a time to make up for any trailing line breaks not contained in the 'text' property. This has good
    -                     performance in most situations compared to the previous two methods.
    -                     */
    +                    For the particular case of a boundary within a text node containing rendered line breaks (within a 
    +                    element, for example), we need a slightly complicated approach to get the boundary's offset in IE. The
    +                    facts:
    +                    
    +                    - Each line break is represented as \r in the text node's data/nodeValue properties
    +                    - Each line break is represented as \r\n in the TextRange's 'text' property
    +                    - The 'text' property of the TextRange does not contain trailing line breaks
    +                    
    +                    To get round the problem presented by the final fact above, we can use the fact that TextRange's
    +                    moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
    +                    the same as the number of characters it was instructed to move. The simplest approach is to use this to
    +                    store the characters moved when moving both the start and end of the range to the start of the document
    +                    body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
    +                    However, this is extremely slow when the document is large and the range is near the end of it. Clearly
    +                    doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
    +                    problem.
    +                    
    +                    Another approach that works is to use moveStart() to move the start boundary of the range up to the end
    +                    boundary one character at a time and incrementing a counter with the value returned by the moveStart()
    +                    call. However, the check for whether the start boundary has reached the end boundary is expensive, so
    +                    this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
    +                    the range within the document).
    +                    
    +                    The method below is a hybrid of the two methods above. It uses the fact that a string containing the
    +                    TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
    +                    text of the TextRange, so the start of the range is moved that length initially and then a character at
    +                    a time to make up for any trailing line breaks not contained in the 'text' property. This has good
    +                    performance in most situations compared to the previous two methods.
    +                    */
                         var tempRange = workingRange.duplicate();
                         var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
     
    @@ -2707,12 +2769,11 @@ rangy.createModule("WrappedRange", function(api, module) {
     });
     // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
     // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
    -rangy.createModule("WrappedSelection", function(api, module) {
    -    api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
    -
    +rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
         api.config.checkSelectionRanges = true;
     
         var BOOLEAN = "boolean";
    +    var NUMBER = "number";
         var dom = api.dom;
         var util = api.util;
         var isHostMethod = util.isHostMethod;
    @@ -2732,7 +2793,7 @@ rangy.createModule("WrappedSelection", function(api, module) {
         // 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") ? (dir == "backward") : !!dir;
    +        return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
         }
     
         function getWindow(win, methodName) {
    @@ -2755,6 +2816,14 @@ rangy.createModule("WrappedSelection", function(api, module) {
         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
    @@ -2798,16 +2867,25 @@ rangy.createModule("WrappedSelection", function(api, module) {
         // 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");
    +    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) {
    +            typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
     
             (function() {
                 // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
    @@ -2819,6 +2897,16 @@ rangy.createModule("WrappedSelection", function(api, module) {
                 // 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";
    @@ -2834,20 +2922,35 @@ rangy.createModule("WrappedSelection", function(api, module) {
                     sel.removeAllRanges();
     
                     // Test whether the native selection is capable of supporting multiple ranges
    -                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);
    +                if (!selectionHasMultipleRanges) {
    +                    var r2 = r1.cloneRange();
    +                    r1.setStart(textNode, 0);
    +                    r2.setEnd(textNode, 3);
    +                    r2.setStart(textNode, 2);
    +                    sel.addRange(r1);
    +                    sel.addRange(r2);
    +
    +                    selectionSupportsMultipleRanges = (sel.rangeCount == 2);
    +                    r2.detach();
    +                }
     
                     // Clean up
                     body.removeChild(testEl);
                     sel.removeAllRanges();
                     r1.detach();
    -                r2.detach();
    +
    +                for (i = 0; i < originalSelectionRangeCount; ++i) {
    +                    if (i == 0 && originalSelectionBackward) {
    +                        if (addRangeBackwardToNative) {
    +                            addRangeBackwardToNative(sel, originalSelectionRanges[i]);
    +                        } else {
    +                            api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because browser does not support Selection.extend");
    +                            sel.addRange(originalSelectionRanges[i])
    +                        }
    +                    } else {
    +                        sel.addRange(originalSelectionRanges[i])
    +                    }
    +                }
                 }
             })();
         }
    @@ -3035,6 +3138,8 @@ rangy.createModule("WrappedSelection", function(api, module) {
             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;
    @@ -3043,7 +3148,7 @@ rangy.createModule("WrappedSelection", function(api, module) {
     
         var cachedRangySelections = [];
     
    -    function findCachedSelection(win, action) {
    +    function actOnCachedSelection(win, action) {
             var i = cachedRangySelections.length, cached, sel;
             while (i--) {
                 cached = cachedRangySelections[i];
    @@ -3074,7 +3179,7 @@ rangy.createModule("WrappedSelection", function(api, module) {
     
             win = getWindow(win, "getNativeSelection");
     
    -        var sel = findCachedSelection(win);
    +        var sel = actOnCachedSelection(win);
             var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
             if (sel) {
                 sel.nativeSelection = nativeSel;
    @@ -3100,12 +3205,12 @@ rangy.createModule("WrappedSelection", function(api, module) {
             // Ensure that the selection becomes of type "Control"
             var doc = getDocument(ranges[0].startContainer);
             var controlRange = getBody(doc).createControlRange();
    -        for (var i = 0, el; i < rangeCount; ++i) {
    +        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 the one of the specified Ranges could not be added to control selection (does it have layout?)");
    +                throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)");
                 }
             }
             controlRange.select();
    @@ -3122,11 +3227,7 @@ rangy.createModule("WrappedSelection", function(api, module) {
             };
     
             var addRangeBackward = function(sel, range) {
    -            var doc = DomRange.getRangeDocument(range);
    -            var endRange = api.createRange(doc);
    -            endRange.collapseToPoint(range.endContainer, range.endOffset);
    -            sel.nativeSelection.addRange(getNativeRange(endRange));
    -            sel.nativeSelection.extend(range.startContainer, range.startOffset);
    +            addRangeBackwardToNative(sel.nativeSelection, range);
                 sel.refresh();
             };
     
    @@ -3231,7 +3332,7 @@ rangy.createModule("WrappedSelection", function(api, module) {
                 if (this.docSelection.type == CONTROL) {
                     addRangeToControlSelection(this, range);
                 } else {
    -                WrappedRange.rangeToTextRange(range).select();
    +                api.WrappedTextRange.rangeToTextRange(range).select();
                     this._ranges[0] = range;
                     this.rangeCount = 1;
                     this.isCollapsed = this._ranges[0].collapsed;
    @@ -3282,7 +3383,7 @@ rangy.createModule("WrappedSelection", function(api, module) {
                     updateEmptySelection(sel);
                 }
             };
    -    } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
    +    } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) {
             refreshSelection = function(sel) {
                 if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
                     updateControlSelection(sel);
    @@ -3350,7 +3451,7 @@ rangy.createModule("WrappedSelection", function(api, module) {
             var ranges = sel.getAllRanges();
             sel.removeAllRanges();
             for (var i = 0, len = ranges.length; i < len; ++i) {
    -            if (!api.rangesEqual(range, ranges[i])) {
    +            if (!rangesEqual(range, ranges[i])) {
                     sel.addRange(ranges[i]);
                 }
             }
    @@ -3395,13 +3496,7 @@ rangy.createModule("WrappedSelection", function(api, module) {
         // Detecting if a selection is backward
         var selectionIsBackward;
         if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
    -        selectionIsBackward = function(sel) {
    -            var backward = false;
    -            if (sel.anchorNode) {
    -                backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
    -            }
    -            return backward;
    -        };
    +        selectionIsBackward = winSelectionIsBackward;
     
             selProto.isBackward = function() {
                 return selectionIsBackward(this);
    @@ -3465,12 +3560,13 @@ rangy.createModule("WrappedSelection", function(api, module) {
             assertNodeInSameDocument(this, node);
             var range = api.createRange(node);
             range.selectNodeContents(node);
    -        this.removeAllRanges();
    -        this.addRange(range);
    +        console.log("before", range.inspect());
    +        this.setSingleRange(range);
    +        console.log("after", this._ranges[0].inspect());
         };
     
         selProto.deleteFromDocument = function() {
    -        // Sepcial behaviour required for Control selections
    +        // Sepcial behaviour required for IE's control selections
             if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
                 var controlRange = this.docSelection.createRange();
                 var element;
    @@ -3541,7 +3637,7 @@ rangy.createModule("WrappedSelection", function(api, module) {
         selProto.setStart = createStartOrEndSetter(true);
         selProto.setEnd = createStartOrEndSetter(false);
         
    -    // Add cheeky select() method to Range prototype
    +    // Add select() method to Range prototype. Any existing selection will be removed.
         api.rangePrototype.select = function(direction) {
             getSelection( this.getDocument() ).setSingleRange(this, direction);
         };
    @@ -3618,12 +3714,12 @@ rangy.createModule("WrappedSelection", function(api, module) {
         };
     
         selProto.detach = function() {
    -        findCachedSelection(this.win, "delete");
    +        actOnCachedSelection(this.win, "delete");
             deleteProperties(this);
         };
     
         WrappedSelection.detachAll = function() {
    -        findCachedSelection(null, "deleteAll");
    +        actOnCachedSelection(null, "deleteAll");
         };
     
         WrappedSelection.inspect = inspect;
    diff --git a/dev/uncompressed/rangy-cssclassapplier.js b/dev/uncompressed/rangy-cssclassapplier.js
    index 488ccea..78fef0a 100644
    --- a/dev/uncompressed/rangy-cssclassapplier.js
    +++ b/dev/uncompressed/rangy-cssclassapplier.js
    @@ -9,12 +9,10 @@
      *
      * Copyright 2013, Tim Down
      * Licensed under the MIT license.
    - * Version: 1.3alpha.772
    - * Build date: 26 February 2013
    + * Version: 1.3alpha.799
    + * Build date: 27 November 2013
      */
    -rangy.createModule("CssClassApplier", function(api, module) {
    -    api.requireModules( ["WrappedSelection", "WrappedRange"] );
    -
    +rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) {
         var dom = api.dom;
         var DomPosition = dom.DomPosition;
         var contains = dom.arrayContains;
    @@ -22,6 +20,17 @@ rangy.createModule("CssClassApplier", function(api, module) {
     
         var defaultTagName = "span";
     
    +    function each(obj, func) {
    +        for (var i in obj) {
    +            if (obj.hasOwnProperty(i)) {
    +                if (func(i, obj[i]) === false) {
    +                    return false;
    +                }
    +            }
    +        }
    +        return true;
    +    }
    +    
         function trim(str) {
             return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
         }
    @@ -64,17 +73,12 @@ rangy.createModule("CssClassApplier", function(api, module) {
             return getSortedClassName(el1) == getSortedClassName(el2);
         }
     
    -    function compareRanges(r1, r2) {
    -        return r1.compareBoundaryPoints(r2.START_TO_START, r2);
    -    }
    -
         function movePosition(position, oldParent, oldIndex, newParent, newIndex) {
             var node = position.node, offset = position.offset;
    -
             var newNode = node, newOffset = offset;
     
             if (node == newParent && offset > newIndex) {
    -            newOffset++;
    +            ++newOffset;
             }
     
             if (node == oldParent && (offset == oldIndex  || offset == oldIndex + 1)) {
    @@ -83,12 +87,18 @@ rangy.createModule("CssClassApplier", function(api, module) {
             }
     
             if (node == oldParent && offset > oldIndex + 1) {
    -            newOffset--;
    +            --newOffset;
             }
     
             position.node = newNode;
             position.offset = newOffset;
         }
    +    
    +    function movePositionWhenRemovingNode(position, parentNode, index) {
    +        if (position.node == parentNode && position.offset > index) {
    +            --position.offset;
    +        }
    +    }
     
         function movePreservingPositions(node, newParent, newIndex, positionsToPreserve) {
             // For convenience, allow newIndex to be -1 to mean "insert at the end".
    @@ -110,6 +120,18 @@ rangy.createModule("CssClassApplier", function(api, module) {
                 newParent.insertBefore(node, newParent.childNodes[newIndex]);
             }
         }
    +    
    +    function removePreservingPositions(node, positionsToPreserve) {
    +
    +        var oldParent = node.parentNode;
    +        var oldIndex = dom.getNodeIndex(node);
    +
    +        for (var i = 0, position; position = positionsToPreserve[i++]; ) {
    +            movePositionWhenRemovingNode(position, oldParent, oldIndex);
    +        }
    +
    +        node.parentNode.removeChild(node);
    +    }
     
         function moveChildrenPreservingPositions(node, newParent, newIndex, removeNode, positionsToPreserve) {
             var child, children = [];
    @@ -165,6 +187,7 @@ rangy.createModule("CssClassApplier", function(api, module) {
                 name = attr1.name;
                 if (name != "class") {
                     attr2 = el2.attributes.getNamedItem(name);
    +                if ( (attr1 === null) != (attr2 === null) ) return false;
                     if (attr1.specified != attr2.specified) return false;
                     if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
                 }
    @@ -182,40 +205,32 @@ rangy.createModule("CssClassApplier", function(api, module) {
             return false;
         }
     
    -    function elementHasProps(el, props) {
    -        var propValue;
    -        for (var p in props) {
    -            if (props.hasOwnProperty(p)) {
    -                propValue = props[p];
    -                if (typeof propValue == "object") {
    -                    if (!elementHasProps(el[p], propValue)) {
    -                        return false;
    -                    }
    -                } else if (el[p] !== propValue) {
    +    function elementHasProperties(el, props) {
    +        each(props, function(p, propValue) {
    +            if (typeof propValue == "object") {
    +                if (!elementHasProperties(el[p], propValue)) {
                         return false;
                     }
    +            } else if (el[p] !== propValue) {
    +                return false;
                 }
    -        }
    +        });
             return true;
         }
     
         var getComputedStyleProperty = dom.getComputedStyleProperty;
    -    var isEditableElement;
    -
    -    (function() {
    +    var isEditableElement = (function() {
             var testEl = document.createElement("div");
    -        if (typeof testEl.isContentEditable == "boolean") {
    -            isEditableElement = function(node) {
    +        return typeof testEl.isContentEditable == "boolean" ?
    +            function (node) {
                     return node && node.nodeType == 1 && node.isContentEditable;
    -            };
    -        } else {
    -            isEditableElement = function(node) {
    +            } :
    +            function (node) {
                     if (!node || node.nodeType != 1 || node.contentEditable == "false") {
                         return false;
                     }
                     return node.contentEditable == "true" || isEditableElement(node.parentNode);
                 };
    -        }
         })();
     
         function isEditingHost(node) {
    @@ -328,10 +343,8 @@ rangy.createModule("CssClassApplier", function(api, module) {
     
                 while ( (child = descendantNode.childNodes[descendantOffset]) ) {
                     movePreservingPositions(child, newNode, newChildIndex++, positionsToPreserve);
    -                //newNode.appendChild(child);
                 }
                 movePreservingPositions(newNode, parentNode, dom.getNodeIndex(descendantNode) + 1, positionsToPreserve);
    -            //dom.insertAfter(newNode, descendantNode);
                 return (descendantNode == node) ? newNode : splitNodeAt(node, parentNode, dom.getNodeIndex(newNode), positionsToPreserve);
             } else if (node != descendantNode) {
                 newNode = descendantNode.parentNode;
    @@ -356,11 +369,11 @@ rangy.createModule("CssClassApplier", function(api, module) {
         }
     
         function createAdjacentMergeableTextNodeGetter(forward) {
    -        var propName = forward ? "nextSibling" : "previousSibling";
    +        var siblingPropName = forward ? "nextSibling" : "previousSibling";
     
             return function(textNode, checkParentElement) {
                 var el = textNode.parentNode;
    -            var adjacentNode = textNode[propName];
    +            var adjacentNode = textNode[siblingPropName];
                 if (adjacentNode) {
                     // Can merge if the node's previous/next sibling is a text node
                     if (adjacentNode && adjacentNode.nodeType == 3) {
    @@ -368,9 +381,12 @@ rangy.createModule("CssClassApplier", function(api, module) {
                     }
                 } else if (checkParentElement) {
                     // Compare text node parent element with its sibling
    -                adjacentNode = el[propName];
    -                if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)/* && adjacentNode.hasChildNodes()*/) {
    -                    return adjacentNode[forward ? "firstChild" : "lastChild"];
    +                adjacentNode = el[siblingPropName];
    +                if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) {
    +                    var adjacentNodeChild = adjacentNode[forward ? "firstChild" : "lastChild"];
    +                    if (adjacentNodeChild && adjacentNodeChild.nodeType == 3) {
    +                        return adjacentNodeChild;
    +                    }
                     }
                 }
                 return null;
    @@ -431,34 +447,35 @@ rangy.createModule("CssClassApplier", function(api, module) {
             },
     
             toString: function() {
    -            var textBits = [];
    +            var textParts = [];
                 for (var i = 0, len = this.textNodes.length; i < len; ++i) {
    -                textBits[i] = "'" + this.textNodes[i].data + "'";
    +                textParts[i] = "'" + this.textNodes[i].data + "'";
                 }
    -            return "[Merge(" + textBits.join(",") + ")]";
    +            return "[Merge(" + textParts.join(",") + ")]";
             }
         };
     
         var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly", "useExistingElements",
    -        "removeEmptyElements"];
    +        "removeEmptyElements", "onElementCreate"];
     
    -    // TODO: Populate this with every attribute name that corresponds to a property with a different name
    +    // TODO: Populate this with every attribute name that corresponds to a property with a different name. Really??
         var attrNamesForProperties = {};
     
         function ClassApplier(cssClass, options, tagNames) {
    -        this.cssClass = cssClass;
    -        var normalize, i, len, propName;
    +        var normalize, i, len, propName, applier = this;
    +        applier.cssClass = cssClass;
     
    -        var elementPropertiesFromOptions = null;
    +        var elementPropertiesFromOptions = null, elementAttributes = {};
     
             // Initialize from options object
             if (typeof options == "object" && options !== null) {
                 tagNames = options.tagNames;
                 elementPropertiesFromOptions = options.elementProperties;
    +            elementAttributes = options.elementAttributes;
     
                 for (i = 0; propName = optionProperties[i++]; ) {
                     if (options.hasOwnProperty(propName)) {
    -                    this[propName] = options[propName];
    +                    applier[propName] = options[propName];
                     }
                 }
                 normalize = options.normalize;
    @@ -467,46 +484,52 @@ rangy.createModule("CssClassApplier", function(api, module) {
             }
     
             // Backward compatibility: the second parameter can also be a Boolean indicating to normalize after unapplying
    -        this.normalize = (typeof normalize == "undefined") ? true : normalize;
    +        applier.normalize = (typeof normalize == "undefined") ? true : normalize;
     
             // Initialize element properties and attribute exceptions
    -        this.attrExceptions = [];
    -        var el = document.createElement(this.elementTagName);
    -        this.elementProperties = this.copyPropertiesToElement(elementPropertiesFromOptions, el, true);
    +        applier.attrExceptions = [];
    +        var el = document.createElement(applier.elementTagName);
    +        applier.elementProperties = applier.copyPropertiesToElement(elementPropertiesFromOptions, el, true);
    +        each(elementAttributes, function(attrName) {
    +            applier.attrExceptions.push(attrName);
    +        });
    +        applier.elementAttributes = elementAttributes;
     
    -        this.elementSortedClassName = this.elementProperties.hasOwnProperty("className") ?
    -            this.elementProperties.className : cssClass;
    +        applier.elementSortedClassName = applier.elementProperties.hasOwnProperty("className") ?
    +            applier.elementProperties.className : cssClass;
     
             // Initialize tag names
    -        this.applyToAnyTagName = false;
    +        applier.applyToAnyTagName = false;
             var type = typeof tagNames;
             if (type == "string") {
                 if (tagNames == "*") {
    -                this.applyToAnyTagName = true;
    +                applier.applyToAnyTagName = true;
                 } else {
    -                this.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
    +                applier.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
                 }
             } else if (type == "object" && typeof tagNames.length == "number") {
    -            this.tagNames = [];
    +            applier.tagNames = [];
                 for (i = 0, len = tagNames.length; i < len; ++i) {
                     if (tagNames[i] == "*") {
    -                    this.applyToAnyTagName = true;
    +                    applier.applyToAnyTagName = true;
                     } else {
    -                    this.tagNames.push(tagNames[i].toLowerCase());
    +                    applier.tagNames.push(tagNames[i].toLowerCase());
                     }
                 }
             } else {
    -            this.tagNames = [this.elementTagName];
    +            applier.tagNames = [applier.elementTagName];
             }
         }
     
         ClassApplier.prototype = {
             elementTagName: defaultTagName,
             elementProperties: {},
    +        elementAttributes: {},
             ignoreWhiteSpace: true,
             applyToEditableOnly: false,
             useExistingElements: true,
             removeEmptyElements: true,
    +        onElementCreate: null,
     
             copyPropertiesToElement: function(props, el, createCopy) {
                 var s, elStyle, elProps = {}, elPropsStyle, propValue, elPropValue, attrName;
    @@ -549,7 +572,7 @@ rangy.createModule("CssClassApplier", function(api, module) {
                             if (createCopy) {
                                 elProps[p] = el[p];
     
    -                            // Not all properties map to identically named attributes
    +                            // Not all properties map to identically-named attributes
                                 attrName = attrNamesForProperties.hasOwnProperty(p) ? attrNamesForProperties[p] : p;
                                 this.attrExceptions.push(attrName);
                             }
    @@ -559,6 +582,14 @@ rangy.createModule("CssClassApplier", function(api, module) {
     
                 return createCopy ? elProps : "";
             },
    +        
    +        copyAttributesToElement: function(attrs, el) {
    +            for (var attrName in attrs) {
    +                if (attrs.hasOwnProperty(attrName)) {
    +                    el.setAttribute(attrName, attrs[attrName]);
    +                }
    +            }
    +        },
     
             hasClass: function(node) {
                 return node.nodeType == 1 &&
    @@ -644,7 +675,11 @@ rangy.createModule("CssClassApplier", function(api, module) {
             createContainer: function(doc) {
                 var el = doc.createElement(this.elementTagName);
                 this.copyPropertiesToElement(this.elementProperties, el, false);
    +            this.copyAttributesToElement(this.elementAttributes, el);
                 addClass(el, this.cssClass);
    +            if (this.onElementCreate) {
    +                this.onElementCreate(el, this);
    +            }
                 return el;
             },
     
    @@ -653,7 +688,7 @@ rangy.createModule("CssClassApplier", function(api, module) {
                 if (parent.childNodes.length == 1 &&
                         this.useExistingElements &&
                         contains(this.tagNames, parent.tagName.toLowerCase()) &&
    -                    elementHasProps(parent, this.elementProperties)) {
    +                    elementHasProperties(parent, this.elementProperties)) {
     
                     addClass(parent, this.cssClass);
                 } else {
    @@ -666,7 +701,7 @@ rangy.createModule("CssClassApplier", function(api, module) {
             isRemovable: function(el) {
                 return el.tagName.toLowerCase() == this.elementTagName
                     && getSortedClassName(el) == this.elementSortedClassName
    -                && elementHasProps(el, this.elementProperties)
    +                && elementHasProperties(el, this.elementProperties)
                     && !elementHasNonClassAttributes(el, this.attrExceptions)
                     && this.isModifiable(el);
             },
    @@ -684,9 +719,15 @@ rangy.createModule("CssClassApplier", function(api, module) {
                     return applier.isEmptyContainer(el);
                 });
                 
    +            var rangesToPreserve = [range]
    +            var positionsToPreserve = getRangeBoundaries(rangesToPreserve);
    +            
                 for (var i = 0, node; node = nodesToRemove[i++]; ) {
    -                node.parentNode.removeChild(node);
    +                removePreservingPositions(node, positionsToPreserve);
                 }
    +
    +            // Update the range from the preserved boundary positions
    +            updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
             },
     
             undoToTextNode: function(textNode, range, ancestorWithClass, positionsToPreserve) {
    @@ -816,6 +857,7 @@ rangy.createModule("CssClassApplier", function(api, module) {
                 sel.setRanges(ranges);
             },
     
    +/*
             getTextSelectedByRange: function(textNode, range) {
                 var textRange = range.cloneRange();
                 textRange.selectNodeContents(textNode);
    @@ -826,6 +868,7 @@ rangy.createModule("CssClassApplier", function(api, module) {
     
                 return text;
             },
    +*/
     
             isAppliedToRange: function(range) {
                 if (range.collapsed || range.toString() == "") {
    @@ -845,6 +888,9 @@ rangy.createModule("CssClassApplier", function(api, module) {
     
             isAppliedToRanges: function(ranges) {
                 var i = ranges.length;
    +            if (i == 0) {
    +                return false;
    +            }
                 while (i--) {
                     if (!this.isAppliedToRange(ranges[i])) {
                         return false;
    @@ -866,6 +912,7 @@ rangy.createModule("CssClassApplier", function(api, module) {
                 }
             },
     
    +/*
             toggleRanges: function(ranges) {
                 if (this.isAppliedToRanges(ranges)) {
                     this.undoToRanges(ranges);
    @@ -873,6 +920,7 @@ rangy.createModule("CssClassApplier", function(api, module) {
                     this.applyToRanges(ranges);
                 }
             },
    +*/
     
             toggleSelection: function(win) {
                 if (this.isAppliedToSelection(win)) {
    @@ -894,6 +942,7 @@ rangy.createModule("CssClassApplier", function(api, module) {
                 return elements;
             },
     
    +/*
             getElementsWithClassIntersectingSelection: function(win) {
                 var sel = api.getSelection(win);
                 var elements = [];
    @@ -908,6 +957,7 @@ rangy.createModule("CssClassApplier", function(api, module) {
                 });
                 return elements;
             },
    +*/
     
             detach: function() {}
         };
    diff --git a/dev/uncompressed/rangy-highlighter.js b/dev/uncompressed/rangy-highlighter.js
    index e506cb0..b426025 100644
    --- a/dev/uncompressed/rangy-highlighter.js
    +++ b/dev/uncompressed/rangy-highlighter.js
    @@ -6,12 +6,10 @@
      *
      * Copyright 2013, Tim Down
      * Licensed under the MIT license.
    - * Version: 1.3alpha.772
    - * Build date: 26 February 2013
    + * Version: 1.3alpha.799
    + * Build date: 27 November 2013
      */
    -rangy.createModule("Highlighter", function(api, module) {
    -    api.requireModules( ["CssClassApplier"] );
    -
    +rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) {
         var dom = api.dom;
         var contains = dom.arrayContains;
         var getBody = dom.getBody;
    @@ -316,7 +314,7 @@ rangy.createModule("Highlighter", function(api, module) {
                     }
                 }
     
    -            var charRange, highlightCharRange, highlightRange, merged;
    +            var charRange, highlightCharRange, merged;
                 for (i = 0, len = charRanges.length; i < len; ++i) {
                     charRange = charRanges[i];
                     merged = false;
    @@ -385,7 +383,6 @@ rangy.createModule("Highlighter", function(api, module) {
                 var converter = this.converter;
                 selection = selection || api.getSelection();
                 var classApplier = this.classAppliers[className];
    -            var highlights = this.highlights;
                 var doc = selection.win.document;
                 var containerElement = containerElementId ? doc.getElementById(containerElementId) : getBody(doc);
     
    @@ -415,11 +412,16 @@ rangy.createModule("Highlighter", function(api, module) {
                 var intersectingHighlights = this.getIntersectingHighlights( selection.getAllRanges() );
                 this.removeHighlights(intersectingHighlights);
                 selection.removeAllRanges();
    +            return intersectingHighlights;
             },
     
    -        selectionOverlapsHighlight: function(selection) {
    +        getHighlightsInSelection: function(selection) {
                 selection = selection || api.getSelection();
    -            return this.getIntersectingHighlights(selection.getAllRanges()).length > 0;
    +            return this.getIntersectingHighlights(selection.getAllRanges());
    +        },
    +
    +        selectionOverlapsHighlight: function(selection) {
    +            return this.getHighlightsInSelection(selection).length > 0;
             },
     
             serialize: function(options) {
    diff --git a/dev/uncompressed/rangy-selectionsaverestore.js b/dev/uncompressed/rangy-selectionsaverestore.js
    index 407394f..e0b1dc1 100644
    --- a/dev/uncompressed/rangy-selectionsaverestore.js
    +++ b/dev/uncompressed/rangy-selectionsaverestore.js
    @@ -9,12 +9,10 @@
      *
      * Copyright 2013, Tim Down
      * Licensed under the MIT license.
    - * Version: 1.3alpha.772
    - * Build date: 26 February 2013
    + * Version: 1.3alpha.799
    + * Build date: 27 November 2013
      */
    -rangy.createModule("SaveRestore", function(api, module) {
    -    api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
    -
    +rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
         var dom = api.dom;
     
         var markerTextChar = "\ufeff";
    diff --git a/dev/uncompressed/rangy-serializer.js b/dev/uncompressed/rangy-serializer.js
    index 811fcc5..b8b9fc4 100644
    --- a/dev/uncompressed/rangy-serializer.js
    +++ b/dev/uncompressed/rangy-serializer.js
    @@ -10,11 +10,10 @@
      *
      * Copyright 2013, Tim Down
      * Licensed under the MIT license.
    - * Version: 1.3alpha.772
    - * Build date: 26 February 2013
    + * Version: 1.3alpha.799
    + * Build date: 27 November 2013
      */
    -rangy.createModule("Serializer", function(api, module) {
    -    api.requireModules( ["WrappedSelection", "WrappedRange"] );
    +rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) {
         var UNDEF = "undefined";
     
         // encodeURIComponent and decodeURIComponent are required for cookie handling
    @@ -121,22 +120,22 @@ rangy.createModule("Serializer", function(api, module) {
         }
     
         function serializePosition(node, offset, rootNode) {
    -        var pathBits = [], n = node;
    +        var pathParts = [], n = node;
             rootNode = rootNode || dom.getDocument(node).documentElement;
             while (n && n != rootNode) {
    -            pathBits.push(dom.getNodeIndex(n, true));
    +            pathParts.push(dom.getNodeIndex(n, true));
                 n = n.parentNode;
             }
    -        return pathBits.join("/") + ":" + offset;
    +        return pathParts.join("/") + ":" + offset;
         }
     
         function deserializePosition(serialized, rootNode, doc) {
             if (!rootNode) {
                 rootNode = (doc || document).documentElement;
             }
    -        var bits = serialized.split(":");
    +        var parts = serialized.split(":");
             var node = rootNode;
    -        var nodeIndices = bits[0] ? bits[0].split("/") : [], i = nodeIndices.length, nodeIndex;
    +        var nodeIndices = parts[0] ? parts[0].split("/") : [], i = nodeIndices.length, nodeIndex;
     
             while (i--) {
                 nodeIndex = parseInt(nodeIndices[i], 10);
    @@ -148,7 +147,7 @@ rangy.createModule("Serializer", function(api, module) {
                 }
             }
     
    -        return new dom.DomPosition(node, parseInt(bits[1], 10));
    +        return new dom.DomPosition(node, parseInt(parts[1], 10));
         }
     
         function serializeRange(range, omitChecksum, rootNode) {
    @@ -165,6 +164,8 @@ rangy.createModule("Serializer", function(api, module) {
             return serialized;
         }
     
    +    var deserializeRegex = /^([^,]+),([^,\{]+)(\{([^}]+)\})?$/;
    +    
         function deserializeRange(serialized, rootNode, doc) {
             if (rootNode) {
                 doc = doc || dom.getDocument(rootNode);
    @@ -172,7 +173,7 @@ rangy.createModule("Serializer", function(api, module) {
                 doc = doc || document;
                 rootNode = doc.documentElement;
             }
    -        var result = /^([^,]+),([^,\{]+)(\{([^}]+)\})?$/.exec(serialized);
    +        var result = deserializeRegex.exec(serialized);
             var checksum = result[4], rootNodeChecksum = getElementChecksum(rootNode);
             if (checksum && checksum !== getElementChecksum(rootNode)) {
                 throw module.createError("deserializeRange(): checksums of serialized range root node (" + checksum +
    @@ -188,7 +189,7 @@ rangy.createModule("Serializer", function(api, module) {
             if (!rootNode) {
                 rootNode = (doc || document).documentElement;
             }
    -        var result = /^([^,]+),([^,]+)(\{([^}]+)\})?$/.exec(serialized);
    +        var result = deserializeRegex.exec(serialized);
             var checksum = result[3];
             return !checksum || checksum === getElementChecksum(rootNode);
         }
    diff --git a/dev/uncompressed/rangy-textrange.js b/dev/uncompressed/rangy-textrange.js
    index e649c35..8fabf96 100644
    --- a/dev/uncompressed/rangy-textrange.js
    +++ b/dev/uncompressed/rangy-textrange.js
    @@ -26,8 +26,8 @@
      *
      * Copyright 2013, Tim Down
      * Licensed under the MIT license.
    - * Version: 1.3alpha.772
    - * Build date: 26 February 2013
    + * Version: 1.3alpha.799
    + * Build date: 27 November 2013
      */
     
     /**
    @@ -63,9 +63,7 @@
      * Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be
      * feature-tested
      */
    -rangy.createModule("TextRange", function(api, module) {
    -    api.requireModules( ["WrappedSelection"] );
    -
    +rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) {
         var UNDEF = "undefined";
         var CHARACTER = "character", WORD = "word";
         var dom = api.dom, util = api.util;
    @@ -87,6 +85,7 @@ rangy.createModule("TextRange", function(api, module) {
         // but not other browsers). Also test whether trailing spaces before 
    elements are collapsed. var trailingSpaceInBlockCollapses = false; var trailingSpaceBeforeBrCollapses = false; + var trailingSpaceBeforeBlockCollapses = false; var trailingSpaceBeforeLineBreakInPreLineCollapses = true; (function() { @@ -106,8 +105,13 @@ rangy.createModule("TextRange", function(api, module) { sel.collapse(el, 2); sel.setStart(el.firstChild, 0); trailingSpaceBeforeBrCollapses = ("" + sel).length == 1; - body.removeChild(el); + el.innerHTML = "1

    1

    "; + sel.collapse(el, 2); + sel.setStart(el.firstChild, 0); + trailingSpaceBeforeBlockCollapses = ("" + sel).length == 1; + + body.removeChild(el); sel.removeAllRanges(); })(); @@ -164,15 +168,17 @@ rangy.createModule("TextRange", function(api, module) { var defaultCharacterOptions = { includeBlockContentTrailingSpace: true, includeSpaceBeforeBr: true, + includeSpaceBeforeBlock: true, includePreLineTrailingSpace: true }; var defaultCaretCharacterOptions = { includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses, includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses, + includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses, includePreLineTrailingSpace: true }; - + var defaultWordOptions = { "en": { wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi, @@ -337,26 +343,6 @@ rangy.createModule("TextRange", function(api, module) { return getAncestors(node).concat([node]); } - // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI - function isHtmlNode(node) { - var ns; - return typeof (ns = node.namespaceURI) == UNDEF || (ns === null || ns == "http://www.w3.org/1999/xhtml"); - } - - function isHtmlElement(node, tagNames) { - if (!node || node.nodeType != 1 || !isHtmlNode(node)) { - return false; - } - switch (typeof tagNames) { - case "string": - return node.tagName.toLowerCase() == tagNames.toLowerCase(); - case "object": - return new RegExp("^(" + tagNames.join("|S") + ")$", "i").test(node.tagName); - default: - return true; - } - } - function nextNodeDescendants(node) { while (node && !node.nextSibling) { node = node.parentNode; @@ -390,8 +376,6 @@ rangy.createModule("TextRange", function(api, module) { return null; } - - // Adpated from Aryeh's code. // "A whitespace node is either a Text node whose data is the empty string; or // a Text node whose data consists only of one or more tabs (0x0009), line @@ -530,10 +514,11 @@ rangy.createModule("TextRange", function(api, module) { NON_SPACE = "NON_SPACE", UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE", COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE", + TRAILING_SPACE_BEFORE_BLOCK = "TRAILING_SPACE_BEFORE_BLOCK", TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK", TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR", - PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK"; - + PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK", + TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR"; extend(nodeProto, { isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"), @@ -620,6 +605,17 @@ rangy.createModule("TextRange", function(api, module) { return false; }, "node"), + + isRenderedBlock: createCachingGetter("isRenderedBlock", function(el) { + // Ensure that a block element containing a
    is considered to have inner text + var brs = el.getElementsByTagName("br"); + for (var i = 0, len = brs.length; i < len; ++i) { + if (!isCollapsedNode(brs[i])) { + return true; + } + } + return this.hasInnerText(); + }, "node"), getTrailingSpace: createCachingGetter("trailingSpace", function(el) { if (el.tagName.toLowerCase() == "br") { @@ -644,7 +640,7 @@ rangy.createModule("TextRange", function(api, module) { case "table-cell": return "\t"; default: - return this.hasInnerText(true) ? "\n" : ""; + return this.isRenderedBlock(true) ? "\n" : ""; } } return ""; @@ -661,7 +657,7 @@ rangy.createModule("TextRange", function(api, module) { case "table-cell": break; default: - return this.hasInnerText(false) ? "\n" : ""; + return this.isRenderedBlock(false) ? "\n" : ""; } return ""; }, "node") @@ -854,15 +850,31 @@ rangy.createModule("TextRange", function(api, module) { this.type = TRAILING_SPACE_BEFORE_BR; } else if (nextPos.isTrailingSpace && nextPos.character == "\n") { this.type = TRAILING_SPACE_IN_BLOCK; + } else if (nextPos.isLeadingSpace && nextPos.character == "\n") { + this.type = TRAILING_SPACE_BEFORE_BLOCK; } + if (nextPos.character === "\n") { if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) { + } else if (this.type == TRAILING_SPACE_BEFORE_BLOCK && !characterOptions.includeSpaceBeforeBlock) { } else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) { } else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) { } else if (this.character === "\n") { if (nextPos.isTrailingSpace) { if (this.isTrailingSpace) { } else if (this.isBr) { + nextPos.type = TRAILING_LINE_BREAK_AFTER_BR; + + if (getPreviousPos() && previousPos.isLeadingSpace && previousPos.character == "\n") { + nextPos.character = ""; + } else { + //character = "\n"; + //nextPos + /* + nextPos.character = ""; + character = "\n"; + */ + } } } else { character = "\n"; @@ -1437,6 +1449,8 @@ rangy.createModule("TextRange", function(api, module) { chars.push(pos); text += currentChar; } + + //console.log("text " + text) if (isRegex) { result = searchTerm.exec(text); @@ -1457,6 +1471,7 @@ rangy.createModule("TextRange", function(api, module) { returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length); break; } + console.log(text.replace(/\s/g, function(m) { return "[" + m.charCodeAt(0) + "]"}), matchStartIndex) } // Check whether regex match extends to the end of the range @@ -1824,6 +1839,7 @@ rangy.createModule("TextRange", function(api, module) { characterRange = rangeInfo.characterRange; range = api.createRange(containerNode); range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions); + console.log("New selected range: " + range.inspect()) this.addRange(range, rangeInfo.backward); } } From f54e11306f622a3721afc588864202c55fdef004 Mon Sep 17 00:00:00 2001 From: timdown Date: Sat, 7 Dec 2013 00:50:02 +0000 Subject: [PATCH 21/23] Fixed TextRange demo git-svn-id: http://rangy.googlecode.com/svn/trunk@803 88e730d5-b869-a254-3594-6732aba23865 --- demos/textrange.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demos/textrange.html b/demos/textrange.html index 2a15d7a..1c5366b 100644 --- a/demos/textrange.html +++ b/demos/textrange.html @@ -57,9 +57,9 @@ function initFind() { // Enable buttons - var cssClassApplierModule = rangy.modules.CssClassApplier; + var cssClassApplierModule = rangy.modules.ClassApplier; if (rangy.supported && cssClassApplierModule && cssClassApplierModule.supported) { - searchResultApplier = rangy.createCssClassApplier("searchResult"); + searchResultApplier = rangy.createClassApplier("searchResult"); var searchBox = gEBI("search"), regexCheckBox = gEBI("regex"), @@ -184,7 +184,7 @@ rangy.getSelection().restoreCharacterRanges(containerElement, savedSel); }; - var redItalicApplier = rangy.createCssClassApplier("redItalic"); + var redItalicApplier = rangy.createClassApplier("redItalic"); var textLength = rangy.innerText(containerElement).length; changeFormattingButton.disabled = false; From 5eb6a9ecf3b906c4d5314fa034fc1b17efeaedb8 Mon Sep 17 00:00:00 2001 From: timdown Date: Sat, 7 Dec 2013 00:53:29 +0000 Subject: [PATCH 22/23] Removed console logs git-svn-id: http://rangy.googlecode.com/svn/trunk@804 88e730d5-b869-a254-3594-6732aba23865 --- src/js/core/wrappedselection.js | 2 -- src/js/modules/rangy-textrange.js | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/js/core/wrappedselection.js b/src/js/core/wrappedselection.js index 998d60e..2a2c481 100644 --- a/src/js/core/wrappedselection.js +++ b/src/js/core/wrappedselection.js @@ -797,9 +797,7 @@ rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], functio assertNodeInSameDocument(this, node); var range = api.createRange(node); range.selectNodeContents(node); - console.log("before", range.inspect()); this.setSingleRange(range); - console.log("after", this._ranges[0].inspect()); }; selProto.deleteFromDocument = function() { diff --git a/src/js/modules/rangy-textrange.js b/src/js/modules/rangy-textrange.js index db4608d..508d9fe 100644 --- a/src/js/modules/rangy-textrange.js +++ b/src/js/modules/rangy-textrange.js @@ -486,9 +486,11 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { }; } +/* api.report = function() { console.log("Cached: " + cachedCount + ", uncached: " + uncachedCount); }; +*/ /*----------------------------------------------------------------------------------------------------------------*/ @@ -1545,7 +1547,7 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length); break; } - console.log(text.replace(/\s/g, function(m) { return "[" + m.charCodeAt(0) + "]"}), matchStartIndex) + log.debug(text.replace(/\s/g, function(m) { return "[" + m.charCodeAt(0) + "]"}), matchStartIndex); } // Check whether regex match extends to the end of the range @@ -1919,7 +1921,6 @@ rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { characterRange = rangeInfo.characterRange; range = api.createRange(containerNode); range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions); - console.log("New selected range: " + range.inspect()) log.info("New selected range: " + range.inspect()); this.addRange(range, rangeInfo.backward); } From c7c6e401953f2c9a089b98f82a547ec392bdf9d6 Mon Sep 17 00:00:00 2001 From: timdown Date: Mon, 9 Dec 2013 01:01:22 +0000 Subject: [PATCH 23/23] Error now thrown when deserializing highlights if class applier is not found (issue 186) git-svn-id: http://rangy.googlecode.com/svn/trunk@805 88e730d5-b869-a254-3594-6732aba23865 --- src/js/modules/rangy-highlighter.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/js/modules/rangy-highlighter.js b/src/js/modules/rangy-highlighter.js index bad9783..b977b36 100644 --- a/src/js/modules/rangy-highlighter.js +++ b/src/js/modules/rangy-highlighter.js @@ -482,7 +482,12 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) { ); } - classApplier = this.classAppliers[parts[3]]; + classApplier = this.classAppliers[ parts[3] ]; + + if (!classApplier) { + throw new Error("No class applier found for class '" + parts[3] + "'"); + } + highlight = new Highlight(this.doc, characterRange, classApplier, this.converter, parseInt(parts[2]), containerElementId); highlight.apply(); highlights.push(highlight);