rangy-rails 1.3alpha.772.0 → 1.3alpha.780.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +9 -6
- data/lib/rangy-rails/version.rb +1 -1
- data/vendor/assets/javascripts/rangy-core.js +3 -3637
- data/vendor/assets/javascripts/rangy-cssclassapplier.js +3 -923
- data/vendor/assets/javascripts/rangy-highlighter.js +3 -487
- data/vendor/assets/javascripts/rangy-position.js +15 -0
- data/vendor/assets/javascripts/rangy-selectionsaverestore.js +3 -225
- data/vendor/assets/javascripts/rangy-serializer.js +3 -281
- data/vendor/assets/javascripts/rangy-textrange.js +3 -1867
- metadata +3 -2
data/README.md
CHANGED
@@ -28,12 +28,15 @@ Easy! Just edit `app/assets/javascripts/application.js` and adjust it to your he
|
|
28
28
|
|
29
29
|
Here's an example configuration that would include Rangy and all of it's modules:
|
30
30
|
|
31
|
-
//= require rangy-core
|
32
|
-
//= require rangy-cssclassapplier
|
33
|
-
//= require rangy-highlighter
|
34
|
-
//= require rangy-
|
35
|
-
//= require rangy-
|
36
|
-
//= require rangy-
|
31
|
+
//= require rangy-core
|
32
|
+
//= require rangy-cssclassapplier
|
33
|
+
//= require rangy-highlighter
|
34
|
+
//= require rangy-position
|
35
|
+
//= require rangy-selectionsaverestore
|
36
|
+
//= require rangy-serializer
|
37
|
+
//= require rangy-textrange
|
38
|
+
|
39
|
+
Normally you would only include the modules you're actually using in your project.
|
37
40
|
|
38
41
|
## Contributing
|
39
42
|
|
data/lib/rangy-rails/version.rb
CHANGED
@@ -4,3641 +4,7 @@
|
|
4
4
|
*
|
5
5
|
* Copyright 2013, Tim Down
|
6
6
|
* Licensed under the MIT license.
|
7
|
-
* Version: 1.3alpha.
|
8
|
-
* Build date:
|
7
|
+
* Version: 1.3alpha.780M
|
8
|
+
* Build date: 17 May 2013
|
9
9
|
*/
|
10
|
-
|
11
|
-
var rangy;
|
12
|
-
rangy = rangy || (function() {
|
13
|
-
|
14
|
-
var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";
|
15
|
-
|
16
|
-
// Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START
|
17
|
-
// are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.
|
18
|
-
var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
|
19
|
-
"commonAncestorContainer"];
|
20
|
-
|
21
|
-
// Minimal set of methods required for DOM Level 2 Range compliance
|
22
|
-
var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
|
23
|
-
"setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
|
24
|
-
"extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];
|
25
|
-
|
26
|
-
var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];
|
27
|
-
|
28
|
-
// Subset of TextRange's full set of methods that we're interested in
|
29
|
-
var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select",
|
30
|
-
"setEndPoint", "getBoundingClientRect"];
|
31
|
-
|
32
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
33
|
-
|
34
|
-
// Trio of functions taken from Peter Michaux's article:
|
35
|
-
// http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
|
36
|
-
function isHostMethod(o, p) {
|
37
|
-
var t = typeof o[p];
|
38
|
-
return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
|
39
|
-
}
|
40
|
-
|
41
|
-
function isHostObject(o, p) {
|
42
|
-
return !!(typeof o[p] == OBJECT && o[p]);
|
43
|
-
}
|
44
|
-
|
45
|
-
function isHostProperty(o, p) {
|
46
|
-
return typeof o[p] != UNDEFINED;
|
47
|
-
}
|
48
|
-
|
49
|
-
// Creates a convenience function to save verbose repeated calls to tests functions
|
50
|
-
function createMultiplePropertyTest(testFunc) {
|
51
|
-
return function(o, props) {
|
52
|
-
var i = props.length;
|
53
|
-
while (i--) {
|
54
|
-
if (!testFunc(o, props[i])) {
|
55
|
-
return false;
|
56
|
-
}
|
57
|
-
}
|
58
|
-
return true;
|
59
|
-
};
|
60
|
-
}
|
61
|
-
|
62
|
-
// Next trio of functions are a convenience to save verbose repeated calls to previous two functions
|
63
|
-
var areHostMethods = createMultiplePropertyTest(isHostMethod);
|
64
|
-
var areHostObjects = createMultiplePropertyTest(isHostObject);
|
65
|
-
var areHostProperties = createMultiplePropertyTest(isHostProperty);
|
66
|
-
|
67
|
-
function isTextRange(range) {
|
68
|
-
return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
|
69
|
-
}
|
70
|
-
|
71
|
-
function getBody(doc) {
|
72
|
-
return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
|
73
|
-
}
|
74
|
-
|
75
|
-
var modules = {};
|
76
|
-
|
77
|
-
var api = {
|
78
|
-
version: "1.3alpha.772",
|
79
|
-
initialized: false,
|
80
|
-
supported: true,
|
81
|
-
|
82
|
-
util: {
|
83
|
-
isHostMethod: isHostMethod,
|
84
|
-
isHostObject: isHostObject,
|
85
|
-
isHostProperty: isHostProperty,
|
86
|
-
areHostMethods: areHostMethods,
|
87
|
-
areHostObjects: areHostObjects,
|
88
|
-
areHostProperties: areHostProperties,
|
89
|
-
isTextRange: isTextRange,
|
90
|
-
getBody: getBody
|
91
|
-
},
|
92
|
-
|
93
|
-
features: {},
|
94
|
-
|
95
|
-
modules: modules,
|
96
|
-
config: {
|
97
|
-
alertOnFail: true,
|
98
|
-
alertOnWarn: false,
|
99
|
-
preferTextRange: false
|
100
|
-
}
|
101
|
-
};
|
102
|
-
|
103
|
-
function consoleLog(msg) {
|
104
|
-
if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
|
105
|
-
window.console.log(msg);
|
106
|
-
}
|
107
|
-
}
|
108
|
-
|
109
|
-
function alertOrLog(msg, shouldAlert) {
|
110
|
-
if (shouldAlert) {
|
111
|
-
window.alert(msg);
|
112
|
-
} else {
|
113
|
-
consoleLog(msg);
|
114
|
-
}
|
115
|
-
}
|
116
|
-
|
117
|
-
function fail(reason) {
|
118
|
-
api.initialized = true;
|
119
|
-
api.supported = false;
|
120
|
-
alertOrLog("Rangy is not supported on this page in your browser. Reason: " + reason, api.config.alertOnFail);
|
121
|
-
}
|
122
|
-
|
123
|
-
api.fail = fail;
|
124
|
-
|
125
|
-
function warn(msg) {
|
126
|
-
alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn);
|
127
|
-
}
|
128
|
-
|
129
|
-
api.warn = warn;
|
130
|
-
|
131
|
-
// Add utility extend() method
|
132
|
-
if ({}.hasOwnProperty) {
|
133
|
-
api.util.extend = function(obj, props, deep) {
|
134
|
-
var o, p;
|
135
|
-
for (var i in props) {
|
136
|
-
if (props.hasOwnProperty(i)) {
|
137
|
-
o = obj[i];
|
138
|
-
p = props[i];
|
139
|
-
//if (deep) alert([o !== null, typeof o == "object", p !== null, typeof p == "object"])
|
140
|
-
if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
|
141
|
-
api.util.extend(o, p, true);
|
142
|
-
}
|
143
|
-
obj[i] = p;
|
144
|
-
}
|
145
|
-
}
|
146
|
-
return obj;
|
147
|
-
};
|
148
|
-
} else {
|
149
|
-
fail("hasOwnProperty not supported");
|
150
|
-
}
|
151
|
-
|
152
|
-
// Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
|
153
|
-
(function() {
|
154
|
-
var el = document.createElement("div");
|
155
|
-
el.appendChild(document.createElement("span"));
|
156
|
-
var slice = [].slice;
|
157
|
-
var toArray;
|
158
|
-
try {
|
159
|
-
if (slice.call(el.childNodes, 0)[0].nodeType == 1) {
|
160
|
-
toArray = function(arrayLike) {
|
161
|
-
return slice.call(arrayLike, 0);
|
162
|
-
};
|
163
|
-
}
|
164
|
-
} catch (e) {}
|
165
|
-
|
166
|
-
if (!toArray) {
|
167
|
-
toArray = function(arrayLike) {
|
168
|
-
var arr = [];
|
169
|
-
for (var i = 0, len = arrayLike.length; i < len; ++i) {
|
170
|
-
arr[i] = arrayLike[i];
|
171
|
-
}
|
172
|
-
return arr;
|
173
|
-
};
|
174
|
-
}
|
175
|
-
|
176
|
-
api.util.toArray = toArray;
|
177
|
-
})();
|
178
|
-
|
179
|
-
|
180
|
-
// Very simple event handler wrapper function that doesn't attempt to solve issue such as "this" handling or
|
181
|
-
// normalization of event properties
|
182
|
-
var addListener;
|
183
|
-
if (isHostMethod(document, "addEventListener")) {
|
184
|
-
addListener = function(obj, eventType, listener) {
|
185
|
-
obj.addEventListener(eventType, listener, false);
|
186
|
-
};
|
187
|
-
} else if (isHostMethod(document, "attachEvent")) {
|
188
|
-
addListener = function(obj, eventType, listener) {
|
189
|
-
obj.attachEvent("on" + eventType, listener);
|
190
|
-
};
|
191
|
-
} else {
|
192
|
-
fail("Document does not have required addEventListener or attachEvent method");
|
193
|
-
}
|
194
|
-
|
195
|
-
api.util.addListener = addListener;
|
196
|
-
|
197
|
-
var initListeners = [];
|
198
|
-
|
199
|
-
function getErrorDesc(ex) {
|
200
|
-
return ex.message || ex.description || String(ex);
|
201
|
-
}
|
202
|
-
|
203
|
-
// Initialization
|
204
|
-
function init() {
|
205
|
-
if (api.initialized) {
|
206
|
-
return;
|
207
|
-
}
|
208
|
-
var testRange;
|
209
|
-
var implementsDomRange = false, implementsTextRange = false;
|
210
|
-
|
211
|
-
// First, perform basic feature tests
|
212
|
-
|
213
|
-
if (isHostMethod(document, "createRange")) {
|
214
|
-
testRange = document.createRange();
|
215
|
-
if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
|
216
|
-
implementsDomRange = true;
|
217
|
-
}
|
218
|
-
testRange.detach();
|
219
|
-
}
|
220
|
-
|
221
|
-
var body = getBody(document);
|
222
|
-
if (!body || body.nodeName.toLowerCase() != "body") {
|
223
|
-
fail("No body element found");
|
224
|
-
return;
|
225
|
-
}
|
226
|
-
|
227
|
-
if (body && isHostMethod(body, "createTextRange")) {
|
228
|
-
testRange = body.createTextRange();
|
229
|
-
if (isTextRange(testRange)) {
|
230
|
-
implementsTextRange = true;
|
231
|
-
}
|
232
|
-
}
|
233
|
-
|
234
|
-
if (!implementsDomRange && !implementsTextRange) {
|
235
|
-
fail("Neither Range nor TextRange are available");
|
236
|
-
return;
|
237
|
-
}
|
238
|
-
|
239
|
-
api.initialized = true;
|
240
|
-
api.features = {
|
241
|
-
implementsDomRange: implementsDomRange,
|
242
|
-
implementsTextRange: implementsTextRange
|
243
|
-
};
|
244
|
-
|
245
|
-
// Initialize modules
|
246
|
-
var module, errorMessage;
|
247
|
-
for (var moduleName in modules) {
|
248
|
-
if ( (module = modules[moduleName]) instanceof Module ) {
|
249
|
-
module.init();
|
250
|
-
}
|
251
|
-
}
|
252
|
-
|
253
|
-
// Call init listeners
|
254
|
-
for (var i = 0, len = initListeners.length; i < len; ++i) {
|
255
|
-
try {
|
256
|
-
initListeners[i](api);
|
257
|
-
} catch (ex) {
|
258
|
-
errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex);
|
259
|
-
consoleLog(errorMessage);
|
260
|
-
}
|
261
|
-
}
|
262
|
-
}
|
263
|
-
|
264
|
-
// Allow external scripts to initialize this library in case it's loaded after the document has loaded
|
265
|
-
api.init = init;
|
266
|
-
|
267
|
-
// Execute listener immediately if already initialized
|
268
|
-
api.addInitListener = function(listener) {
|
269
|
-
if (api.initialized) {
|
270
|
-
listener(api);
|
271
|
-
} else {
|
272
|
-
initListeners.push(listener);
|
273
|
-
}
|
274
|
-
};
|
275
|
-
|
276
|
-
var createMissingNativeApiListeners = [];
|
277
|
-
|
278
|
-
api.addCreateMissingNativeApiListener = function(listener) {
|
279
|
-
createMissingNativeApiListeners.push(listener);
|
280
|
-
};
|
281
|
-
|
282
|
-
function createMissingNativeApi(win) {
|
283
|
-
win = win || window;
|
284
|
-
init();
|
285
|
-
|
286
|
-
// Notify listeners
|
287
|
-
for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) {
|
288
|
-
createMissingNativeApiListeners[i](win);
|
289
|
-
}
|
290
|
-
}
|
291
|
-
|
292
|
-
api.createMissingNativeApi = createMissingNativeApi;
|
293
|
-
|
294
|
-
function Module(name, initializer) {
|
295
|
-
this.name = name;
|
296
|
-
this.initialized = false;
|
297
|
-
this.supported = false;
|
298
|
-
this.init = initializer;
|
299
|
-
}
|
300
|
-
|
301
|
-
Module.prototype = {
|
302
|
-
fail: function(reason) {
|
303
|
-
this.initialized = true;
|
304
|
-
this.supported = false;
|
305
|
-
throw new Error("Module '" + this.name + "' failed to load: " + reason);
|
306
|
-
},
|
307
|
-
|
308
|
-
warn: function(msg) {
|
309
|
-
api.warn("Module " + this.name + ": " + msg);
|
310
|
-
},
|
311
|
-
|
312
|
-
deprecationNotice: function(deprecated, replacement) {
|
313
|
-
api.warn("DEPRECATED: " + deprecated + " in module " + this.name + "is deprecated. Please use "
|
314
|
-
+ replacement + " instead");
|
315
|
-
},
|
316
|
-
|
317
|
-
createError: function(msg) {
|
318
|
-
return new Error("Error in Rangy " + this.name + " module: " + msg);
|
319
|
-
}
|
320
|
-
};
|
321
|
-
|
322
|
-
api.createModule = function(name, initFunc) {
|
323
|
-
var module = new Module(name, function() {
|
324
|
-
if (!module.initialized) {
|
325
|
-
module.initialized = true;
|
326
|
-
try {
|
327
|
-
initFunc(api, module);
|
328
|
-
module.supported = true;
|
329
|
-
} catch (ex) {
|
330
|
-
var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);
|
331
|
-
consoleLog(errorMessage);
|
332
|
-
}
|
333
|
-
}
|
334
|
-
});
|
335
|
-
modules[name] = module;
|
336
|
-
};
|
337
|
-
|
338
|
-
api.requireModules = function(moduleNames) {
|
339
|
-
for (var i = 0, len = moduleNames.length, module, moduleName; i < len; ++i) {
|
340
|
-
moduleName = moduleNames[i];
|
341
|
-
|
342
|
-
module = modules[moduleName];
|
343
|
-
if (!module || !(module instanceof Module)) {
|
344
|
-
throw new Error("required module '" + moduleName + "' not found");
|
345
|
-
}
|
346
|
-
|
347
|
-
module.init();
|
348
|
-
|
349
|
-
if (!module.supported) {
|
350
|
-
throw new Error("required module '" + moduleName + "' not supported");
|
351
|
-
}
|
352
|
-
}
|
353
|
-
};
|
354
|
-
|
355
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
356
|
-
|
357
|
-
// Wait for document to load before running tests
|
358
|
-
|
359
|
-
var docReady = false;
|
360
|
-
|
361
|
-
var loadHandler = function(e) {
|
362
|
-
if (!docReady) {
|
363
|
-
docReady = true;
|
364
|
-
if (!api.initialized) {
|
365
|
-
init();
|
366
|
-
}
|
367
|
-
}
|
368
|
-
};
|
369
|
-
|
370
|
-
// Test whether we have window and document objects that we will need
|
371
|
-
if (typeof window == UNDEFINED) {
|
372
|
-
fail("No window found");
|
373
|
-
return;
|
374
|
-
}
|
375
|
-
if (typeof document == UNDEFINED) {
|
376
|
-
fail("No document found");
|
377
|
-
return;
|
378
|
-
}
|
379
|
-
|
380
|
-
if (isHostMethod(document, "addEventListener")) {
|
381
|
-
document.addEventListener("DOMContentLoaded", loadHandler, false);
|
382
|
-
}
|
383
|
-
|
384
|
-
// Add a fallback in case the DOMContentLoaded event isn't supported
|
385
|
-
addListener(window, "load", loadHandler);
|
386
|
-
|
387
|
-
return api;
|
388
|
-
})();
|
389
|
-
|
390
|
-
rangy.createModule("DomUtil", function(api, module) {
|
391
|
-
var UNDEF = "undefined";
|
392
|
-
var util = api.util;
|
393
|
-
|
394
|
-
// Perform feature tests
|
395
|
-
if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
|
396
|
-
module.fail("document missing a Node creation method");
|
397
|
-
}
|
398
|
-
|
399
|
-
if (!util.isHostMethod(document, "getElementsByTagName")) {
|
400
|
-
module.fail("document missing getElementsByTagName method");
|
401
|
-
}
|
402
|
-
|
403
|
-
var el = document.createElement("div");
|
404
|
-
if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
|
405
|
-
!util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
|
406
|
-
module.fail("Incomplete Element implementation");
|
407
|
-
}
|
408
|
-
|
409
|
-
// innerHTML is required for Range's createContextualFragment method
|
410
|
-
if (!util.isHostProperty(el, "innerHTML")) {
|
411
|
-
module.fail("Element is missing innerHTML property");
|
412
|
-
}
|
413
|
-
|
414
|
-
var textNode = document.createTextNode("test");
|
415
|
-
if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
|
416
|
-
!util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
|
417
|
-
!util.areHostProperties(textNode, ["data"]))) {
|
418
|
-
module.fail("Incomplete Text Node implementation");
|
419
|
-
}
|
420
|
-
|
421
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
422
|
-
|
423
|
-
// Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
|
424
|
-
// able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
|
425
|
-
// contains just the document as a single element and the value searched for is the document.
|
426
|
-
var arrayContains = /*Array.prototype.indexOf ?
|
427
|
-
function(arr, val) {
|
428
|
-
return arr.indexOf(val) > -1;
|
429
|
-
}:*/
|
430
|
-
|
431
|
-
function(arr, val) {
|
432
|
-
var i = arr.length;
|
433
|
-
while (i--) {
|
434
|
-
if (arr[i] === val) {
|
435
|
-
return true;
|
436
|
-
}
|
437
|
-
}
|
438
|
-
return false;
|
439
|
-
};
|
440
|
-
|
441
|
-
// Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
|
442
|
-
function isHtmlNamespace(node) {
|
443
|
-
var ns;
|
444
|
-
return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
|
445
|
-
}
|
446
|
-
|
447
|
-
function parentElement(node) {
|
448
|
-
var parent = node.parentNode;
|
449
|
-
return (parent.nodeType == 1) ? parent : null;
|
450
|
-
}
|
451
|
-
|
452
|
-
function getNodeIndex(node) {
|
453
|
-
var i = 0;
|
454
|
-
while( (node = node.previousSibling) ) {
|
455
|
-
++i;
|
456
|
-
}
|
457
|
-
return i;
|
458
|
-
}
|
459
|
-
|
460
|
-
function getNodeLength(node) {
|
461
|
-
switch (node.nodeType) {
|
462
|
-
case 7:
|
463
|
-
case 10:
|
464
|
-
return 0;
|
465
|
-
case 3:
|
466
|
-
case 8:
|
467
|
-
return node.length;
|
468
|
-
default:
|
469
|
-
return node.childNodes.length;
|
470
|
-
}
|
471
|
-
}
|
472
|
-
|
473
|
-
function getCommonAncestor(node1, node2) {
|
474
|
-
var ancestors = [], n;
|
475
|
-
for (n = node1; n; n = n.parentNode) {
|
476
|
-
ancestors.push(n);
|
477
|
-
}
|
478
|
-
|
479
|
-
for (n = node2; n; n = n.parentNode) {
|
480
|
-
if (arrayContains(ancestors, n)) {
|
481
|
-
return n;
|
482
|
-
}
|
483
|
-
}
|
484
|
-
|
485
|
-
return null;
|
486
|
-
}
|
487
|
-
|
488
|
-
function isAncestorOf(ancestor, descendant, selfIsAncestor) {
|
489
|
-
var n = selfIsAncestor ? descendant : descendant.parentNode;
|
490
|
-
while (n) {
|
491
|
-
if (n === ancestor) {
|
492
|
-
return true;
|
493
|
-
} else {
|
494
|
-
n = n.parentNode;
|
495
|
-
}
|
496
|
-
}
|
497
|
-
return false;
|
498
|
-
}
|
499
|
-
|
500
|
-
function isOrIsAncestorOf(ancestor, descendant) {
|
501
|
-
return isAncestorOf(ancestor, descendant, true);
|
502
|
-
}
|
503
|
-
|
504
|
-
function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
|
505
|
-
var p, n = selfIsAncestor ? node : node.parentNode;
|
506
|
-
while (n) {
|
507
|
-
p = n.parentNode;
|
508
|
-
if (p === ancestor) {
|
509
|
-
return n;
|
510
|
-
}
|
511
|
-
n = p;
|
512
|
-
}
|
513
|
-
return null;
|
514
|
-
}
|
515
|
-
|
516
|
-
function isCharacterDataNode(node) {
|
517
|
-
var t = node.nodeType;
|
518
|
-
return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
|
519
|
-
}
|
520
|
-
|
521
|
-
function isTextOrCommentNode(node) {
|
522
|
-
if (!node) {
|
523
|
-
return false;
|
524
|
-
}
|
525
|
-
var t = node.nodeType;
|
526
|
-
return t == 3 || t == 8 ; // Text or Comment
|
527
|
-
}
|
528
|
-
|
529
|
-
function insertAfter(node, precedingNode) {
|
530
|
-
var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
|
531
|
-
if (nextNode) {
|
532
|
-
parent.insertBefore(node, nextNode);
|
533
|
-
} else {
|
534
|
-
parent.appendChild(node);
|
535
|
-
}
|
536
|
-
return node;
|
537
|
-
}
|
538
|
-
|
539
|
-
// Note that we cannot use splitText() because it is bugridden in IE 9.
|
540
|
-
function splitDataNode(node, index, positionsToPreserve) {
|
541
|
-
var newNode = node.cloneNode(false);
|
542
|
-
newNode.deleteData(0, index);
|
543
|
-
node.deleteData(index, node.length - index);
|
544
|
-
insertAfter(newNode, node);
|
545
|
-
|
546
|
-
// Preserve positions
|
547
|
-
if (positionsToPreserve) {
|
548
|
-
for (var i = 0, position; position = positionsToPreserve[i++]; ) {
|
549
|
-
// Handle case where position was inside the portion of node after the split point
|
550
|
-
if (position.node == node && position.offset > index) {
|
551
|
-
position.node = newNode;
|
552
|
-
position.offset -= index;
|
553
|
-
}
|
554
|
-
// Handle the case where the position is a node offset within node's parent
|
555
|
-
else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {
|
556
|
-
++position.offset;
|
557
|
-
}
|
558
|
-
}
|
559
|
-
}
|
560
|
-
return newNode;
|
561
|
-
}
|
562
|
-
|
563
|
-
function getDocument(node) {
|
564
|
-
if (node.nodeType == 9) {
|
565
|
-
return node;
|
566
|
-
} else if (typeof node.ownerDocument != UNDEF) {
|
567
|
-
return node.ownerDocument;
|
568
|
-
} else if (typeof node.document != UNDEF) {
|
569
|
-
return node.document;
|
570
|
-
} else if (node.parentNode) {
|
571
|
-
return getDocument(node.parentNode);
|
572
|
-
} else {
|
573
|
-
throw module.createError("getDocument: no document found for node");
|
574
|
-
}
|
575
|
-
}
|
576
|
-
|
577
|
-
function getWindow(node) {
|
578
|
-
var doc = getDocument(node);
|
579
|
-
if (typeof doc.defaultView != UNDEF) {
|
580
|
-
return doc.defaultView;
|
581
|
-
} else if (typeof doc.parentWindow != UNDEF) {
|
582
|
-
return doc.parentWindow;
|
583
|
-
} else {
|
584
|
-
throw module.createError("Cannot get a window object for node");
|
585
|
-
}
|
586
|
-
}
|
587
|
-
|
588
|
-
function getIframeDocument(iframeEl) {
|
589
|
-
if (typeof iframeEl.contentDocument != UNDEF) {
|
590
|
-
return iframeEl.contentDocument;
|
591
|
-
} else if (typeof iframeEl.contentWindow != UNDEF) {
|
592
|
-
return iframeEl.contentWindow.document;
|
593
|
-
} else {
|
594
|
-
throw module.createError("getIframeDocument: No Document object found for iframe element");
|
595
|
-
}
|
596
|
-
}
|
597
|
-
|
598
|
-
function getIframeWindow(iframeEl) {
|
599
|
-
if (typeof iframeEl.contentWindow != UNDEF) {
|
600
|
-
return iframeEl.contentWindow;
|
601
|
-
} else if (typeof iframeEl.contentDocument != UNDEF) {
|
602
|
-
return iframeEl.contentDocument.defaultView;
|
603
|
-
} else {
|
604
|
-
throw module.createError("getIframeWindow: No Window object found for iframe element");
|
605
|
-
}
|
606
|
-
}
|
607
|
-
|
608
|
-
// This looks bad. Is it worth it?
|
609
|
-
function isWindow(obj) {
|
610
|
-
return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document");
|
611
|
-
}
|
612
|
-
|
613
|
-
function getContentDocument(obj, module, methodName) {
|
614
|
-
var doc;
|
615
|
-
|
616
|
-
if (!obj) {
|
617
|
-
doc = document;
|
618
|
-
}
|
619
|
-
|
620
|
-
// Test if a DOM node has been passed and obtain a document object for it if so
|
621
|
-
else if (util.isHostProperty(obj, "nodeType")) {
|
622
|
-
doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe")
|
623
|
-
? getIframeDocument(obj) : getDocument(obj);
|
624
|
-
}
|
625
|
-
|
626
|
-
// Test if the doc parameter appears to be a Window object
|
627
|
-
else if (isWindow(obj)) {
|
628
|
-
doc = obj.document;
|
629
|
-
}
|
630
|
-
|
631
|
-
if (!doc) {
|
632
|
-
throw module.createError(methodName + "(): Parameter must be a Window object or DOM node");
|
633
|
-
}
|
634
|
-
|
635
|
-
return doc;
|
636
|
-
}
|
637
|
-
|
638
|
-
function getRootContainer(node) {
|
639
|
-
var parent;
|
640
|
-
while ( (parent = node.parentNode) ) {
|
641
|
-
node = parent;
|
642
|
-
}
|
643
|
-
return node;
|
644
|
-
}
|
645
|
-
|
646
|
-
function comparePoints(nodeA, offsetA, nodeB, offsetB) {
|
647
|
-
// See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
|
648
|
-
var nodeC, root, childA, childB, n;
|
649
|
-
if (nodeA == nodeB) {
|
650
|
-
// Case 1: nodes are the same
|
651
|
-
return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
|
652
|
-
} else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
|
653
|
-
// Case 2: node C (container B or an ancestor) is a child node of A
|
654
|
-
return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
|
655
|
-
} else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
|
656
|
-
// Case 3: node C (container A or an ancestor) is a child node of B
|
657
|
-
return getNodeIndex(nodeC) < offsetB ? -1 : 1;
|
658
|
-
} else {
|
659
|
-
// Case 4: containers are siblings or descendants of siblings
|
660
|
-
root = getCommonAncestor(nodeA, nodeB);
|
661
|
-
childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
|
662
|
-
childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
|
663
|
-
|
664
|
-
if (childA === childB) {
|
665
|
-
// This shouldn't be possible
|
666
|
-
throw module.createError("comparePoints got to case 4 and childA and childB are the same!");
|
667
|
-
} else {
|
668
|
-
n = root.firstChild;
|
669
|
-
while (n) {
|
670
|
-
if (n === childA) {
|
671
|
-
return -1;
|
672
|
-
} else if (n === childB) {
|
673
|
-
return 1;
|
674
|
-
}
|
675
|
-
n = n.nextSibling;
|
676
|
-
}
|
677
|
-
}
|
678
|
-
}
|
679
|
-
}
|
680
|
-
|
681
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
682
|
-
|
683
|
-
// Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
|
684
|
-
var crashyTextNodes = false;
|
685
|
-
|
686
|
-
function isBrokenNode(node) {
|
687
|
-
try {
|
688
|
-
node.parentNode;
|
689
|
-
return false;
|
690
|
-
} catch (e) {
|
691
|
-
return true;
|
692
|
-
}
|
693
|
-
}
|
694
|
-
|
695
|
-
(function() {
|
696
|
-
var el = document.createElement("b");
|
697
|
-
el.innerHTML = "1";
|
698
|
-
var textNode = el.firstChild;
|
699
|
-
el.innerHTML = "<br>";
|
700
|
-
crashyTextNodes = isBrokenNode(textNode);
|
701
|
-
|
702
|
-
api.features.crashyTextNodes = crashyTextNodes;
|
703
|
-
})();
|
704
|
-
|
705
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
706
|
-
|
707
|
-
function inspectNode(node) {
|
708
|
-
if (!node) {
|
709
|
-
return "[No node]";
|
710
|
-
}
|
711
|
-
if (crashyTextNodes && isBrokenNode(node)) {
|
712
|
-
return "[Broken node]";
|
713
|
-
}
|
714
|
-
if (isCharacterDataNode(node)) {
|
715
|
-
return '"' + node.data + '"';
|
716
|
-
}
|
717
|
-
if (node.nodeType == 1) {
|
718
|
-
var idAttr = node.id ? ' id="' + node.id + '"' : "";
|
719
|
-
return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "][" + node.innerHTML.slice(0, 20) + "]";
|
720
|
-
}
|
721
|
-
return node.nodeName;
|
722
|
-
}
|
723
|
-
|
724
|
-
function fragmentFromNodeChildren(node) {
|
725
|
-
var fragment = getDocument(node).createDocumentFragment(), child;
|
726
|
-
while ( (child = node.firstChild) ) {
|
727
|
-
fragment.appendChild(child);
|
728
|
-
}
|
729
|
-
return fragment;
|
730
|
-
}
|
731
|
-
|
732
|
-
var getComputedStyleProperty;
|
733
|
-
if (typeof window.getComputedStyle != UNDEF) {
|
734
|
-
getComputedStyleProperty = function(el, propName) {
|
735
|
-
return getWindow(el).getComputedStyle(el, null)[propName];
|
736
|
-
};
|
737
|
-
} else if (typeof document.documentElement.currentStyle != UNDEF) {
|
738
|
-
getComputedStyleProperty = function(el, propName) {
|
739
|
-
return el.currentStyle[propName];
|
740
|
-
};
|
741
|
-
} else {
|
742
|
-
module.fail("No means of obtaining computed style properties found");
|
743
|
-
}
|
744
|
-
|
745
|
-
function NodeIterator(root) {
|
746
|
-
this.root = root;
|
747
|
-
this._next = root;
|
748
|
-
}
|
749
|
-
|
750
|
-
NodeIterator.prototype = {
|
751
|
-
_current: null,
|
752
|
-
|
753
|
-
hasNext: function() {
|
754
|
-
return !!this._next;
|
755
|
-
},
|
756
|
-
|
757
|
-
next: function() {
|
758
|
-
var n = this._current = this._next;
|
759
|
-
var child, next;
|
760
|
-
if (this._current) {
|
761
|
-
child = n.firstChild;
|
762
|
-
if (child) {
|
763
|
-
this._next = child;
|
764
|
-
} else {
|
765
|
-
next = null;
|
766
|
-
while ((n !== this.root) && !(next = n.nextSibling)) {
|
767
|
-
n = n.parentNode;
|
768
|
-
}
|
769
|
-
this._next = next;
|
770
|
-
}
|
771
|
-
}
|
772
|
-
return this._current;
|
773
|
-
},
|
774
|
-
|
775
|
-
detach: function() {
|
776
|
-
this._current = this._next = this.root = null;
|
777
|
-
}
|
778
|
-
};
|
779
|
-
|
780
|
-
function createIterator(root) {
|
781
|
-
return new NodeIterator(root);
|
782
|
-
}
|
783
|
-
|
784
|
-
function DomPosition(node, offset) {
|
785
|
-
this.node = node;
|
786
|
-
this.offset = offset;
|
787
|
-
}
|
788
|
-
|
789
|
-
DomPosition.prototype = {
|
790
|
-
equals: function(pos) {
|
791
|
-
return !!pos && this.node === pos.node && this.offset == pos.offset;
|
792
|
-
},
|
793
|
-
|
794
|
-
inspect: function() {
|
795
|
-
return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
|
796
|
-
},
|
797
|
-
|
798
|
-
toString: function() {
|
799
|
-
return this.inspect();
|
800
|
-
}
|
801
|
-
};
|
802
|
-
|
803
|
-
function DOMException(codeName) {
|
804
|
-
this.code = this[codeName];
|
805
|
-
this.codeName = codeName;
|
806
|
-
this.message = "DOMException: " + this.codeName;
|
807
|
-
}
|
808
|
-
|
809
|
-
DOMException.prototype = {
|
810
|
-
INDEX_SIZE_ERR: 1,
|
811
|
-
HIERARCHY_REQUEST_ERR: 3,
|
812
|
-
WRONG_DOCUMENT_ERR: 4,
|
813
|
-
NO_MODIFICATION_ALLOWED_ERR: 7,
|
814
|
-
NOT_FOUND_ERR: 8,
|
815
|
-
NOT_SUPPORTED_ERR: 9,
|
816
|
-
INVALID_STATE_ERR: 11
|
817
|
-
};
|
818
|
-
|
819
|
-
DOMException.prototype.toString = function() {
|
820
|
-
return this.message;
|
821
|
-
};
|
822
|
-
|
823
|
-
api.dom = {
|
824
|
-
arrayContains: arrayContains,
|
825
|
-
isHtmlNamespace: isHtmlNamespace,
|
826
|
-
parentElement: parentElement,
|
827
|
-
getNodeIndex: getNodeIndex,
|
828
|
-
getNodeLength: getNodeLength,
|
829
|
-
getCommonAncestor: getCommonAncestor,
|
830
|
-
isAncestorOf: isAncestorOf,
|
831
|
-
isOrIsAncestorOf: isOrIsAncestorOf,
|
832
|
-
getClosestAncestorIn: getClosestAncestorIn,
|
833
|
-
isCharacterDataNode: isCharacterDataNode,
|
834
|
-
isTextOrCommentNode: isTextOrCommentNode,
|
835
|
-
insertAfter: insertAfter,
|
836
|
-
splitDataNode: splitDataNode,
|
837
|
-
getDocument: getDocument,
|
838
|
-
getWindow: getWindow,
|
839
|
-
getIframeWindow: getIframeWindow,
|
840
|
-
getIframeDocument: getIframeDocument,
|
841
|
-
getBody: util.getBody,
|
842
|
-
isWindow: isWindow,
|
843
|
-
getContentDocument: getContentDocument,
|
844
|
-
getRootContainer: getRootContainer,
|
845
|
-
comparePoints: comparePoints,
|
846
|
-
isBrokenNode: isBrokenNode,
|
847
|
-
inspectNode: inspectNode,
|
848
|
-
getComputedStyleProperty: getComputedStyleProperty,
|
849
|
-
fragmentFromNodeChildren: fragmentFromNodeChildren,
|
850
|
-
createIterator: createIterator,
|
851
|
-
DomPosition: DomPosition
|
852
|
-
};
|
853
|
-
|
854
|
-
api.DOMException = DOMException;
|
855
|
-
});
|
856
|
-
rangy.createModule("DomRange", function(api, module) {
|
857
|
-
api.requireModules( ["DomUtil"] );
|
858
|
-
|
859
|
-
var dom = api.dom;
|
860
|
-
var util = api.util;
|
861
|
-
var DomPosition = dom.DomPosition;
|
862
|
-
var DOMException = api.DOMException;
|
863
|
-
|
864
|
-
var isCharacterDataNode = dom.isCharacterDataNode;
|
865
|
-
var getNodeIndex = dom.getNodeIndex;
|
866
|
-
var isOrIsAncestorOf = dom.isOrIsAncestorOf;
|
867
|
-
var getDocument = dom.getDocument;
|
868
|
-
var comparePoints = dom.comparePoints;
|
869
|
-
var splitDataNode = dom.splitDataNode;
|
870
|
-
var getClosestAncestorIn = dom.getClosestAncestorIn;
|
871
|
-
var getNodeLength = dom.getNodeLength;
|
872
|
-
var arrayContains = dom.arrayContains;
|
873
|
-
var getRootContainer = dom.getRootContainer;
|
874
|
-
var crashyTextNodes = api.features.crashyTextNodes;
|
875
|
-
|
876
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
877
|
-
|
878
|
-
// Utility functions
|
879
|
-
|
880
|
-
function isNonTextPartiallySelected(node, range) {
|
881
|
-
return (node.nodeType != 3) &&
|
882
|
-
(isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));
|
883
|
-
}
|
884
|
-
|
885
|
-
function getRangeDocument(range) {
|
886
|
-
return range.document || getDocument(range.startContainer);
|
887
|
-
}
|
888
|
-
|
889
|
-
function getBoundaryBeforeNode(node) {
|
890
|
-
return new DomPosition(node.parentNode, getNodeIndex(node));
|
891
|
-
}
|
892
|
-
|
893
|
-
function getBoundaryAfterNode(node) {
|
894
|
-
return new DomPosition(node.parentNode, getNodeIndex(node) + 1);
|
895
|
-
}
|
896
|
-
|
897
|
-
function insertNodeAtPosition(node, n, o) {
|
898
|
-
var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
|
899
|
-
if (isCharacterDataNode(n)) {
|
900
|
-
if (o == n.length) {
|
901
|
-
dom.insertAfter(node, n);
|
902
|
-
} else {
|
903
|
-
n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o));
|
904
|
-
}
|
905
|
-
} else if (o >= n.childNodes.length) {
|
906
|
-
n.appendChild(node);
|
907
|
-
} else {
|
908
|
-
n.insertBefore(node, n.childNodes[o]);
|
909
|
-
}
|
910
|
-
return firstNodeInserted;
|
911
|
-
}
|
912
|
-
|
913
|
-
function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {
|
914
|
-
assertRangeValid(rangeA);
|
915
|
-
assertRangeValid(rangeB);
|
916
|
-
|
917
|
-
if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {
|
918
|
-
throw new DOMException("WRONG_DOCUMENT_ERR");
|
919
|
-
}
|
920
|
-
|
921
|
-
var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),
|
922
|
-
endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);
|
923
|
-
|
924
|
-
return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
|
925
|
-
}
|
926
|
-
|
927
|
-
function cloneSubtree(iterator) {
|
928
|
-
var partiallySelected;
|
929
|
-
for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
|
930
|
-
partiallySelected = iterator.isPartiallySelectedSubtree();
|
931
|
-
node = node.cloneNode(!partiallySelected);
|
932
|
-
if (partiallySelected) {
|
933
|
-
subIterator = iterator.getSubtreeIterator();
|
934
|
-
node.appendChild(cloneSubtree(subIterator));
|
935
|
-
subIterator.detach(true);
|
936
|
-
}
|
937
|
-
|
938
|
-
if (node.nodeType == 10) { // DocumentType
|
939
|
-
throw new DOMException("HIERARCHY_REQUEST_ERR");
|
940
|
-
}
|
941
|
-
frag.appendChild(node);
|
942
|
-
}
|
943
|
-
return frag;
|
944
|
-
}
|
945
|
-
|
946
|
-
function iterateSubtree(rangeIterator, func, iteratorState) {
|
947
|
-
var it, n;
|
948
|
-
iteratorState = iteratorState || { stop: false };
|
949
|
-
for (var node, subRangeIterator; node = rangeIterator.next(); ) {
|
950
|
-
if (rangeIterator.isPartiallySelectedSubtree()) {
|
951
|
-
if (func(node) === false) {
|
952
|
-
iteratorState.stop = true;
|
953
|
-
return;
|
954
|
-
} else {
|
955
|
-
// The node is partially selected by the Range, so we can use a new RangeIterator on the portion of
|
956
|
-
// the node selected by the Range.
|
957
|
-
subRangeIterator = rangeIterator.getSubtreeIterator();
|
958
|
-
iterateSubtree(subRangeIterator, func, iteratorState);
|
959
|
-
subRangeIterator.detach(true);
|
960
|
-
if (iteratorState.stop) {
|
961
|
-
return;
|
962
|
-
}
|
963
|
-
}
|
964
|
-
} else {
|
965
|
-
// The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
|
966
|
-
// descendants
|
967
|
-
it = dom.createIterator(node);
|
968
|
-
while ( (n = it.next()) ) {
|
969
|
-
if (func(n) === false) {
|
970
|
-
iteratorState.stop = true;
|
971
|
-
return;
|
972
|
-
}
|
973
|
-
}
|
974
|
-
}
|
975
|
-
}
|
976
|
-
}
|
977
|
-
|
978
|
-
function deleteSubtree(iterator) {
|
979
|
-
var subIterator;
|
980
|
-
while (iterator.next()) {
|
981
|
-
if (iterator.isPartiallySelectedSubtree()) {
|
982
|
-
subIterator = iterator.getSubtreeIterator();
|
983
|
-
deleteSubtree(subIterator);
|
984
|
-
subIterator.detach(true);
|
985
|
-
} else {
|
986
|
-
iterator.remove();
|
987
|
-
}
|
988
|
-
}
|
989
|
-
}
|
990
|
-
|
991
|
-
function extractSubtree(iterator) {
|
992
|
-
for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
|
993
|
-
|
994
|
-
if (iterator.isPartiallySelectedSubtree()) {
|
995
|
-
node = node.cloneNode(false);
|
996
|
-
subIterator = iterator.getSubtreeIterator();
|
997
|
-
node.appendChild(extractSubtree(subIterator));
|
998
|
-
subIterator.detach(true);
|
999
|
-
} else {
|
1000
|
-
iterator.remove();
|
1001
|
-
}
|
1002
|
-
if (node.nodeType == 10) { // DocumentType
|
1003
|
-
throw new DOMException("HIERARCHY_REQUEST_ERR");
|
1004
|
-
}
|
1005
|
-
frag.appendChild(node);
|
1006
|
-
}
|
1007
|
-
return frag;
|
1008
|
-
}
|
1009
|
-
|
1010
|
-
function getNodesInRange(range, nodeTypes, filter) {
|
1011
|
-
var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
|
1012
|
-
var filterExists = !!filter;
|
1013
|
-
if (filterNodeTypes) {
|
1014
|
-
regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
|
1015
|
-
}
|
1016
|
-
|
1017
|
-
var nodes = [];
|
1018
|
-
iterateSubtree(new RangeIterator(range, false), function(node) {
|
1019
|
-
if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) {
|
1020
|
-
nodes.push(node);
|
1021
|
-
}
|
1022
|
-
});
|
1023
|
-
return nodes;
|
1024
|
-
}
|
1025
|
-
|
1026
|
-
function inspect(range) {
|
1027
|
-
var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
|
1028
|
-
return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
|
1029
|
-
dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
|
1030
|
-
}
|
1031
|
-
|
1032
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
1033
|
-
|
1034
|
-
// RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
|
1035
|
-
|
1036
|
-
function RangeIterator(range, clonePartiallySelectedTextNodes) {
|
1037
|
-
this.range = range;
|
1038
|
-
this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
|
1039
|
-
|
1040
|
-
|
1041
|
-
if (!range.collapsed) {
|
1042
|
-
this.sc = range.startContainer;
|
1043
|
-
this.so = range.startOffset;
|
1044
|
-
this.ec = range.endContainer;
|
1045
|
-
this.eo = range.endOffset;
|
1046
|
-
var root = range.commonAncestorContainer;
|
1047
|
-
|
1048
|
-
if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
|
1049
|
-
this.isSingleCharacterDataNode = true;
|
1050
|
-
this._first = this._last = this._next = this.sc;
|
1051
|
-
} else {
|
1052
|
-
this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?
|
1053
|
-
this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
|
1054
|
-
this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?
|
1055
|
-
this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
|
1056
|
-
}
|
1057
|
-
}
|
1058
|
-
}
|
1059
|
-
|
1060
|
-
RangeIterator.prototype = {
|
1061
|
-
_current: null,
|
1062
|
-
_next: null,
|
1063
|
-
_first: null,
|
1064
|
-
_last: null,
|
1065
|
-
isSingleCharacterDataNode: false,
|
1066
|
-
|
1067
|
-
reset: function() {
|
1068
|
-
this._current = null;
|
1069
|
-
this._next = this._first;
|
1070
|
-
},
|
1071
|
-
|
1072
|
-
hasNext: function() {
|
1073
|
-
return !!this._next;
|
1074
|
-
},
|
1075
|
-
|
1076
|
-
next: function() {
|
1077
|
-
// Move to next node
|
1078
|
-
var current = this._current = this._next;
|
1079
|
-
if (current) {
|
1080
|
-
this._next = (current !== this._last) ? current.nextSibling : null;
|
1081
|
-
|
1082
|
-
// Check for partially selected text nodes
|
1083
|
-
if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
|
1084
|
-
if (current === this.ec) {
|
1085
|
-
(current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
|
1086
|
-
}
|
1087
|
-
if (this._current === this.sc) {
|
1088
|
-
(current = current.cloneNode(true)).deleteData(0, this.so);
|
1089
|
-
}
|
1090
|
-
}
|
1091
|
-
}
|
1092
|
-
|
1093
|
-
return current;
|
1094
|
-
},
|
1095
|
-
|
1096
|
-
remove: function() {
|
1097
|
-
var current = this._current, start, end;
|
1098
|
-
|
1099
|
-
if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
|
1100
|
-
start = (current === this.sc) ? this.so : 0;
|
1101
|
-
end = (current === this.ec) ? this.eo : current.length;
|
1102
|
-
if (start != end) {
|
1103
|
-
current.deleteData(start, end - start);
|
1104
|
-
}
|
1105
|
-
} else {
|
1106
|
-
if (current.parentNode) {
|
1107
|
-
current.parentNode.removeChild(current);
|
1108
|
-
} else {
|
1109
|
-
}
|
1110
|
-
}
|
1111
|
-
},
|
1112
|
-
|
1113
|
-
// Checks if the current node is partially selected
|
1114
|
-
isPartiallySelectedSubtree: function() {
|
1115
|
-
var current = this._current;
|
1116
|
-
return isNonTextPartiallySelected(current, this.range);
|
1117
|
-
},
|
1118
|
-
|
1119
|
-
getSubtreeIterator: function() {
|
1120
|
-
var subRange;
|
1121
|
-
if (this.isSingleCharacterDataNode) {
|
1122
|
-
subRange = this.range.cloneRange();
|
1123
|
-
subRange.collapse(false);
|
1124
|
-
} else {
|
1125
|
-
subRange = new Range(getRangeDocument(this.range));
|
1126
|
-
var current = this._current;
|
1127
|
-
var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current);
|
1128
|
-
|
1129
|
-
if (isOrIsAncestorOf(current, this.sc)) {
|
1130
|
-
startContainer = this.sc;
|
1131
|
-
startOffset = this.so;
|
1132
|
-
}
|
1133
|
-
if (isOrIsAncestorOf(current, this.ec)) {
|
1134
|
-
endContainer = this.ec;
|
1135
|
-
endOffset = this.eo;
|
1136
|
-
}
|
1137
|
-
|
1138
|
-
updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
|
1139
|
-
}
|
1140
|
-
return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
|
1141
|
-
},
|
1142
|
-
|
1143
|
-
detach: function(detachRange) {
|
1144
|
-
if (detachRange) {
|
1145
|
-
this.range.detach();
|
1146
|
-
}
|
1147
|
-
this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
|
1148
|
-
}
|
1149
|
-
};
|
1150
|
-
|
1151
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
1152
|
-
|
1153
|
-
// Exceptions
|
1154
|
-
|
1155
|
-
function RangeException(codeName) {
|
1156
|
-
this.code = this[codeName];
|
1157
|
-
this.codeName = codeName;
|
1158
|
-
this.message = "RangeException: " + this.codeName;
|
1159
|
-
}
|
1160
|
-
|
1161
|
-
RangeException.prototype = {
|
1162
|
-
BAD_BOUNDARYPOINTS_ERR: 1,
|
1163
|
-
INVALID_NODE_TYPE_ERR: 2
|
1164
|
-
};
|
1165
|
-
|
1166
|
-
RangeException.prototype.toString = function() {
|
1167
|
-
return this.message;
|
1168
|
-
};
|
1169
|
-
|
1170
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
1171
|
-
|
1172
|
-
var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
|
1173
|
-
var rootContainerNodeTypes = [2, 9, 11];
|
1174
|
-
var readonlyNodeTypes = [5, 6, 10, 12];
|
1175
|
-
var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
|
1176
|
-
var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
|
1177
|
-
|
1178
|
-
function createAncestorFinder(nodeTypes) {
|
1179
|
-
return function(node, selfIsAncestor) {
|
1180
|
-
var t, n = selfIsAncestor ? node : node.parentNode;
|
1181
|
-
while (n) {
|
1182
|
-
t = n.nodeType;
|
1183
|
-
if (arrayContains(nodeTypes, t)) {
|
1184
|
-
return n;
|
1185
|
-
}
|
1186
|
-
n = n.parentNode;
|
1187
|
-
}
|
1188
|
-
return null;
|
1189
|
-
};
|
1190
|
-
}
|
1191
|
-
|
1192
|
-
var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
|
1193
|
-
var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
|
1194
|
-
var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
|
1195
|
-
|
1196
|
-
function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
|
1197
|
-
if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
|
1198
|
-
throw new RangeException("INVALID_NODE_TYPE_ERR");
|
1199
|
-
}
|
1200
|
-
}
|
1201
|
-
|
1202
|
-
function assertNotDetached(range) {
|
1203
|
-
if (!range.startContainer) {
|
1204
|
-
throw new DOMException("INVALID_STATE_ERR");
|
1205
|
-
}
|
1206
|
-
}
|
1207
|
-
|
1208
|
-
function assertValidNodeType(node, invalidTypes) {
|
1209
|
-
if (!arrayContains(invalidTypes, node.nodeType)) {
|
1210
|
-
throw new RangeException("INVALID_NODE_TYPE_ERR");
|
1211
|
-
}
|
1212
|
-
}
|
1213
|
-
|
1214
|
-
function assertValidOffset(node, offset) {
|
1215
|
-
if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
|
1216
|
-
throw new DOMException("INDEX_SIZE_ERR");
|
1217
|
-
}
|
1218
|
-
}
|
1219
|
-
|
1220
|
-
function assertSameDocumentOrFragment(node1, node2) {
|
1221
|
-
if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
|
1222
|
-
throw new DOMException("WRONG_DOCUMENT_ERR");
|
1223
|
-
}
|
1224
|
-
}
|
1225
|
-
|
1226
|
-
function assertNodeNotReadOnly(node) {
|
1227
|
-
if (getReadonlyAncestor(node, true)) {
|
1228
|
-
throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
|
1229
|
-
}
|
1230
|
-
}
|
1231
|
-
|
1232
|
-
function assertNode(node, codeName) {
|
1233
|
-
if (!node) {
|
1234
|
-
throw new DOMException(codeName);
|
1235
|
-
}
|
1236
|
-
}
|
1237
|
-
|
1238
|
-
function isOrphan(node) {
|
1239
|
-
return (crashyTextNodes && dom.isBrokenNode(node)) ||
|
1240
|
-
!arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
|
1241
|
-
}
|
1242
|
-
|
1243
|
-
function isValidOffset(node, offset) {
|
1244
|
-
return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);
|
1245
|
-
}
|
1246
|
-
|
1247
|
-
function isRangeValid(range) {
|
1248
|
-
return (!!range.startContainer && !!range.endContainer
|
1249
|
-
&& !isOrphan(range.startContainer)
|
1250
|
-
&& !isOrphan(range.endContainer)
|
1251
|
-
&& isValidOffset(range.startContainer, range.startOffset)
|
1252
|
-
&& isValidOffset(range.endContainer, range.endOffset));
|
1253
|
-
}
|
1254
|
-
|
1255
|
-
function assertRangeValid(range) {
|
1256
|
-
assertNotDetached(range);
|
1257
|
-
if (!isRangeValid(range)) {
|
1258
|
-
throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
|
1259
|
-
}
|
1260
|
-
}
|
1261
|
-
|
1262
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
1263
|
-
|
1264
|
-
// Test the browser's innerHTML support to decide how to implement createContextualFragment
|
1265
|
-
var styleEl = document.createElement("style");
|
1266
|
-
var htmlParsingConforms = false;
|
1267
|
-
try {
|
1268
|
-
styleEl.innerHTML = "<b>x</b>";
|
1269
|
-
htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
|
1270
|
-
} catch (e) {
|
1271
|
-
// IE 6 and 7 throw
|
1272
|
-
}
|
1273
|
-
|
1274
|
-
api.features.htmlParsingConforms = htmlParsingConforms;
|
1275
|
-
|
1276
|
-
var createContextualFragment = htmlParsingConforms ?
|
1277
|
-
|
1278
|
-
// Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
|
1279
|
-
// discussion and base code for this implementation at issue 67.
|
1280
|
-
// Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
|
1281
|
-
// Thanks to Aleks Williams.
|
1282
|
-
function(fragmentStr) {
|
1283
|
-
// "Let node the context object's start's node."
|
1284
|
-
var node = this.startContainer;
|
1285
|
-
var doc = getDocument(node);
|
1286
|
-
|
1287
|
-
// "If the context object's start's node is null, raise an INVALID_STATE_ERR
|
1288
|
-
// exception and abort these steps."
|
1289
|
-
if (!node) {
|
1290
|
-
throw new DOMException("INVALID_STATE_ERR");
|
1291
|
-
}
|
1292
|
-
|
1293
|
-
// "Let element be as follows, depending on node's interface:"
|
1294
|
-
// Document, Document Fragment: null
|
1295
|
-
var el = null;
|
1296
|
-
|
1297
|
-
// "Element: node"
|
1298
|
-
if (node.nodeType == 1) {
|
1299
|
-
el = node;
|
1300
|
-
|
1301
|
-
// "Text, Comment: node's parentElement"
|
1302
|
-
} else if (isCharacterDataNode(node)) {
|
1303
|
-
el = dom.parentElement(node);
|
1304
|
-
}
|
1305
|
-
|
1306
|
-
// "If either element is null or element's ownerDocument is an HTML document
|
1307
|
-
// and element's local name is "html" and element's namespace is the HTML
|
1308
|
-
// namespace"
|
1309
|
-
if (el === null || (
|
1310
|
-
el.nodeName == "HTML"
|
1311
|
-
&& dom.isHtmlNamespace(getDocument(el).documentElement)
|
1312
|
-
&& dom.isHtmlNamespace(el)
|
1313
|
-
)) {
|
1314
|
-
|
1315
|
-
// "let element be a new Element with "body" as its local name and the HTML
|
1316
|
-
// namespace as its namespace.""
|
1317
|
-
el = doc.createElement("body");
|
1318
|
-
} else {
|
1319
|
-
el = el.cloneNode(false);
|
1320
|
-
}
|
1321
|
-
|
1322
|
-
// "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
|
1323
|
-
// "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
|
1324
|
-
// "In either case, the algorithm must be invoked with fragment as the input
|
1325
|
-
// and element as the context element."
|
1326
|
-
el.innerHTML = fragmentStr;
|
1327
|
-
|
1328
|
-
// "If this raises an exception, then abort these steps. Otherwise, let new
|
1329
|
-
// children be the nodes returned."
|
1330
|
-
|
1331
|
-
// "Let fragment be a new DocumentFragment."
|
1332
|
-
// "Append all new children to fragment."
|
1333
|
-
// "Return fragment."
|
1334
|
-
return dom.fragmentFromNodeChildren(el);
|
1335
|
-
} :
|
1336
|
-
|
1337
|
-
// In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
|
1338
|
-
// previous versions of Rangy used (with the exception of using a body element rather than a div)
|
1339
|
-
function(fragmentStr) {
|
1340
|
-
assertNotDetached(this);
|
1341
|
-
var doc = getRangeDocument(this);
|
1342
|
-
var el = doc.createElement("body");
|
1343
|
-
el.innerHTML = fragmentStr;
|
1344
|
-
|
1345
|
-
return dom.fragmentFromNodeChildren(el);
|
1346
|
-
};
|
1347
|
-
|
1348
|
-
function splitRangeBoundaries(range, positionsToPreserve) {
|
1349
|
-
assertRangeValid(range);
|
1350
|
-
|
1351
|
-
var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
|
1352
|
-
var startEndSame = (sc === ec);
|
1353
|
-
|
1354
|
-
if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
|
1355
|
-
splitDataNode(ec, eo, positionsToPreserve);
|
1356
|
-
}
|
1357
|
-
|
1358
|
-
if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
|
1359
|
-
sc = splitDataNode(sc, so, positionsToPreserve);
|
1360
|
-
if (startEndSame) {
|
1361
|
-
eo -= so;
|
1362
|
-
ec = sc;
|
1363
|
-
} else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
|
1364
|
-
eo++;
|
1365
|
-
}
|
1366
|
-
so = 0;
|
1367
|
-
}
|
1368
|
-
range.setStartAndEnd(sc, so, ec, eo);
|
1369
|
-
}
|
1370
|
-
|
1371
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
1372
|
-
|
1373
|
-
var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
|
1374
|
-
"commonAncestorContainer"];
|
1375
|
-
|
1376
|
-
var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
|
1377
|
-
var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
|
1378
|
-
|
1379
|
-
function RangePrototype() {}
|
1380
|
-
|
1381
|
-
RangePrototype.prototype = {
|
1382
|
-
compareBoundaryPoints: function(how, range) {
|
1383
|
-
assertRangeValid(this);
|
1384
|
-
assertSameDocumentOrFragment(this.startContainer, range.startContainer);
|
1385
|
-
|
1386
|
-
var nodeA, offsetA, nodeB, offsetB;
|
1387
|
-
var prefixA = (how == e2s || how == s2s) ? "start" : "end";
|
1388
|
-
var prefixB = (how == s2e || how == s2s) ? "start" : "end";
|
1389
|
-
nodeA = this[prefixA + "Container"];
|
1390
|
-
offsetA = this[prefixA + "Offset"];
|
1391
|
-
nodeB = range[prefixB + "Container"];
|
1392
|
-
offsetB = range[prefixB + "Offset"];
|
1393
|
-
return comparePoints(nodeA, offsetA, nodeB, offsetB);
|
1394
|
-
},
|
1395
|
-
|
1396
|
-
insertNode: function(node) {
|
1397
|
-
assertRangeValid(this);
|
1398
|
-
assertValidNodeType(node, insertableNodeTypes);
|
1399
|
-
assertNodeNotReadOnly(this.startContainer);
|
1400
|
-
|
1401
|
-
if (isOrIsAncestorOf(node, this.startContainer)) {
|
1402
|
-
throw new DOMException("HIERARCHY_REQUEST_ERR");
|
1403
|
-
}
|
1404
|
-
|
1405
|
-
// No check for whether the container of the start of the Range is of a type that does not allow
|
1406
|
-
// children of the type of node: the browser's DOM implementation should do this for us when we attempt
|
1407
|
-
// to add the node
|
1408
|
-
|
1409
|
-
var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
|
1410
|
-
this.setStartBefore(firstNodeInserted);
|
1411
|
-
},
|
1412
|
-
|
1413
|
-
cloneContents: function() {
|
1414
|
-
assertRangeValid(this);
|
1415
|
-
|
1416
|
-
var clone, frag;
|
1417
|
-
if (this.collapsed) {
|
1418
|
-
return getRangeDocument(this).createDocumentFragment();
|
1419
|
-
} else {
|
1420
|
-
if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {
|
1421
|
-
clone = this.startContainer.cloneNode(true);
|
1422
|
-
clone.data = clone.data.slice(this.startOffset, this.endOffset);
|
1423
|
-
frag = getRangeDocument(this).createDocumentFragment();
|
1424
|
-
frag.appendChild(clone);
|
1425
|
-
return frag;
|
1426
|
-
} else {
|
1427
|
-
var iterator = new RangeIterator(this, true);
|
1428
|
-
clone = cloneSubtree(iterator);
|
1429
|
-
iterator.detach();
|
1430
|
-
}
|
1431
|
-
return clone;
|
1432
|
-
}
|
1433
|
-
},
|
1434
|
-
|
1435
|
-
canSurroundContents: function() {
|
1436
|
-
assertRangeValid(this);
|
1437
|
-
assertNodeNotReadOnly(this.startContainer);
|
1438
|
-
assertNodeNotReadOnly(this.endContainer);
|
1439
|
-
|
1440
|
-
// Check if the contents can be surrounded. Specifically, this means whether the range partially selects
|
1441
|
-
// no non-text nodes.
|
1442
|
-
var iterator = new RangeIterator(this, true);
|
1443
|
-
var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
|
1444
|
-
(iterator._last && isNonTextPartiallySelected(iterator._last, this)));
|
1445
|
-
iterator.detach();
|
1446
|
-
return !boundariesInvalid;
|
1447
|
-
},
|
1448
|
-
|
1449
|
-
surroundContents: function(node) {
|
1450
|
-
assertValidNodeType(node, surroundNodeTypes);
|
1451
|
-
|
1452
|
-
if (!this.canSurroundContents()) {
|
1453
|
-
throw new RangeException("BAD_BOUNDARYPOINTS_ERR");
|
1454
|
-
}
|
1455
|
-
|
1456
|
-
// Extract the contents
|
1457
|
-
var content = this.extractContents();
|
1458
|
-
|
1459
|
-
// Clear the children of the node
|
1460
|
-
if (node.hasChildNodes()) {
|
1461
|
-
while (node.lastChild) {
|
1462
|
-
node.removeChild(node.lastChild);
|
1463
|
-
}
|
1464
|
-
}
|
1465
|
-
|
1466
|
-
// Insert the new node and add the extracted contents
|
1467
|
-
insertNodeAtPosition(node, this.startContainer, this.startOffset);
|
1468
|
-
node.appendChild(content);
|
1469
|
-
|
1470
|
-
this.selectNode(node);
|
1471
|
-
},
|
1472
|
-
|
1473
|
-
cloneRange: function() {
|
1474
|
-
assertRangeValid(this);
|
1475
|
-
var range = new Range(getRangeDocument(this));
|
1476
|
-
var i = rangeProperties.length, prop;
|
1477
|
-
while (i--) {
|
1478
|
-
prop = rangeProperties[i];
|
1479
|
-
range[prop] = this[prop];
|
1480
|
-
}
|
1481
|
-
return range;
|
1482
|
-
},
|
1483
|
-
|
1484
|
-
toString: function() {
|
1485
|
-
assertRangeValid(this);
|
1486
|
-
var sc = this.startContainer;
|
1487
|
-
if (sc === this.endContainer && isCharacterDataNode(sc)) {
|
1488
|
-
return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
|
1489
|
-
} else {
|
1490
|
-
var textBits = [], iterator = new RangeIterator(this, true);
|
1491
|
-
iterateSubtree(iterator, function(node) {
|
1492
|
-
// Accept only text or CDATA nodes, not comments
|
1493
|
-
if (node.nodeType == 3 || node.nodeType == 4) {
|
1494
|
-
textBits.push(node.data);
|
1495
|
-
}
|
1496
|
-
});
|
1497
|
-
iterator.detach();
|
1498
|
-
return textBits.join("");
|
1499
|
-
}
|
1500
|
-
},
|
1501
|
-
|
1502
|
-
// The methods below are all non-standard. The following batch were introduced by Mozilla but have since
|
1503
|
-
// been removed from Mozilla.
|
1504
|
-
|
1505
|
-
compareNode: function(node) {
|
1506
|
-
assertRangeValid(this);
|
1507
|
-
|
1508
|
-
var parent = node.parentNode;
|
1509
|
-
var nodeIndex = getNodeIndex(node);
|
1510
|
-
|
1511
|
-
if (!parent) {
|
1512
|
-
throw new DOMException("NOT_FOUND_ERR");
|
1513
|
-
}
|
1514
|
-
|
1515
|
-
var startComparison = this.comparePoint(parent, nodeIndex),
|
1516
|
-
endComparison = this.comparePoint(parent, nodeIndex + 1);
|
1517
|
-
|
1518
|
-
if (startComparison < 0) { // Node starts before
|
1519
|
-
return (endComparison > 0) ? n_b_a : n_b;
|
1520
|
-
} else {
|
1521
|
-
return (endComparison > 0) ? n_a : n_i;
|
1522
|
-
}
|
1523
|
-
},
|
1524
|
-
|
1525
|
-
comparePoint: function(node, offset) {
|
1526
|
-
assertRangeValid(this);
|
1527
|
-
assertNode(node, "HIERARCHY_REQUEST_ERR");
|
1528
|
-
assertSameDocumentOrFragment(node, this.startContainer);
|
1529
|
-
|
1530
|
-
if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
|
1531
|
-
return -1;
|
1532
|
-
} else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
|
1533
|
-
return 1;
|
1534
|
-
}
|
1535
|
-
return 0;
|
1536
|
-
},
|
1537
|
-
|
1538
|
-
createContextualFragment: createContextualFragment,
|
1539
|
-
|
1540
|
-
toHtml: function() {
|
1541
|
-
assertRangeValid(this);
|
1542
|
-
var container = this.commonAncestorContainer.parentNode.cloneNode(false);
|
1543
|
-
container.appendChild(this.cloneContents());
|
1544
|
-
return container.innerHTML;
|
1545
|
-
},
|
1546
|
-
|
1547
|
-
// touchingIsIntersecting determines whether this method considers a node that borders a range intersects
|
1548
|
-
// with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
|
1549
|
-
intersectsNode: function(node, touchingIsIntersecting) {
|
1550
|
-
assertRangeValid(this);
|
1551
|
-
assertNode(node, "NOT_FOUND_ERR");
|
1552
|
-
if (getDocument(node) !== getRangeDocument(this)) {
|
1553
|
-
return false;
|
1554
|
-
}
|
1555
|
-
|
1556
|
-
var parent = node.parentNode, offset = getNodeIndex(node);
|
1557
|
-
assertNode(parent, "NOT_FOUND_ERR");
|
1558
|
-
|
1559
|
-
var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),
|
1560
|
-
endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
|
1561
|
-
|
1562
|
-
return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
|
1563
|
-
},
|
1564
|
-
|
1565
|
-
isPointInRange: function(node, offset) {
|
1566
|
-
assertRangeValid(this);
|
1567
|
-
assertNode(node, "HIERARCHY_REQUEST_ERR");
|
1568
|
-
assertSameDocumentOrFragment(node, this.startContainer);
|
1569
|
-
|
1570
|
-
return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
|
1571
|
-
(comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
|
1572
|
-
},
|
1573
|
-
|
1574
|
-
// The methods below are non-standard and invented by me.
|
1575
|
-
|
1576
|
-
// Sharing a boundary start-to-end or end-to-start does not count as intersection.
|
1577
|
-
intersectsRange: function(range) {
|
1578
|
-
return rangesIntersect(this, range, false);
|
1579
|
-
},
|
1580
|
-
|
1581
|
-
// Sharing a boundary start-to-end or end-to-start does count as intersection.
|
1582
|
-
intersectsOrTouchesRange: function(range) {
|
1583
|
-
return rangesIntersect(this, range, true);
|
1584
|
-
},
|
1585
|
-
|
1586
|
-
intersection: function(range) {
|
1587
|
-
if (this.intersectsRange(range)) {
|
1588
|
-
var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
|
1589
|
-
endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
|
1590
|
-
|
1591
|
-
var intersectionRange = this.cloneRange();
|
1592
|
-
if (startComparison == -1) {
|
1593
|
-
intersectionRange.setStart(range.startContainer, range.startOffset);
|
1594
|
-
}
|
1595
|
-
if (endComparison == 1) {
|
1596
|
-
intersectionRange.setEnd(range.endContainer, range.endOffset);
|
1597
|
-
}
|
1598
|
-
return intersectionRange;
|
1599
|
-
}
|
1600
|
-
return null;
|
1601
|
-
},
|
1602
|
-
|
1603
|
-
union: function(range) {
|
1604
|
-
if (this.intersectsOrTouchesRange(range)) {
|
1605
|
-
var unionRange = this.cloneRange();
|
1606
|
-
if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
|
1607
|
-
unionRange.setStart(range.startContainer, range.startOffset);
|
1608
|
-
}
|
1609
|
-
if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
|
1610
|
-
unionRange.setEnd(range.endContainer, range.endOffset);
|
1611
|
-
}
|
1612
|
-
return unionRange;
|
1613
|
-
} else {
|
1614
|
-
throw new RangeException("Ranges do not intersect");
|
1615
|
-
}
|
1616
|
-
},
|
1617
|
-
|
1618
|
-
containsNode: function(node, allowPartial) {
|
1619
|
-
if (allowPartial) {
|
1620
|
-
return this.intersectsNode(node, false);
|
1621
|
-
} else {
|
1622
|
-
return this.compareNode(node) == n_i;
|
1623
|
-
}
|
1624
|
-
},
|
1625
|
-
|
1626
|
-
containsNodeContents: function(node) {
|
1627
|
-
return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;
|
1628
|
-
},
|
1629
|
-
|
1630
|
-
containsRange: function(range) {
|
1631
|
-
var intersection = this.intersection(range);
|
1632
|
-
return intersection !== null && range.equals(intersection);
|
1633
|
-
},
|
1634
|
-
|
1635
|
-
containsNodeText: function(node) {
|
1636
|
-
var nodeRange = this.cloneRange();
|
1637
|
-
nodeRange.selectNode(node);
|
1638
|
-
var textNodes = nodeRange.getNodes([3]);
|
1639
|
-
if (textNodes.length > 0) {
|
1640
|
-
nodeRange.setStart(textNodes[0], 0);
|
1641
|
-
var lastTextNode = textNodes.pop();
|
1642
|
-
nodeRange.setEnd(lastTextNode, lastTextNode.length);
|
1643
|
-
var contains = this.containsRange(nodeRange);
|
1644
|
-
nodeRange.detach();
|
1645
|
-
return contains;
|
1646
|
-
} else {
|
1647
|
-
return this.containsNodeContents(node);
|
1648
|
-
}
|
1649
|
-
},
|
1650
|
-
|
1651
|
-
getNodes: function(nodeTypes, filter) {
|
1652
|
-
assertRangeValid(this);
|
1653
|
-
return getNodesInRange(this, nodeTypes, filter);
|
1654
|
-
},
|
1655
|
-
|
1656
|
-
getDocument: function() {
|
1657
|
-
return getRangeDocument(this);
|
1658
|
-
},
|
1659
|
-
|
1660
|
-
collapseBefore: function(node) {
|
1661
|
-
assertNotDetached(this);
|
1662
|
-
|
1663
|
-
this.setEndBefore(node);
|
1664
|
-
this.collapse(false);
|
1665
|
-
},
|
1666
|
-
|
1667
|
-
collapseAfter: function(node) {
|
1668
|
-
assertNotDetached(this);
|
1669
|
-
|
1670
|
-
this.setStartAfter(node);
|
1671
|
-
this.collapse(true);
|
1672
|
-
},
|
1673
|
-
|
1674
|
-
getBookmark: function(containerNode) {
|
1675
|
-
var doc = getRangeDocument(this);
|
1676
|
-
var preSelectionRange = api.createRange(doc);
|
1677
|
-
containerNode = containerNode || dom.getBody(doc);
|
1678
|
-
preSelectionRange.selectNodeContents(containerNode);
|
1679
|
-
var range = this.intersection(preSelectionRange);
|
1680
|
-
var start = 0, end = 0;
|
1681
|
-
if (range) {
|
1682
|
-
preSelectionRange.setEnd(range.startContainer, range.startOffset);
|
1683
|
-
start = preSelectionRange.toString().length;
|
1684
|
-
end = start + range.toString().length;
|
1685
|
-
preSelectionRange.detach();
|
1686
|
-
}
|
1687
|
-
|
1688
|
-
return {
|
1689
|
-
start: start,
|
1690
|
-
end: end,
|
1691
|
-
containerNode: containerNode
|
1692
|
-
};
|
1693
|
-
},
|
1694
|
-
|
1695
|
-
moveToBookmark: function(bookmark) {
|
1696
|
-
var containerNode = bookmark.containerNode;
|
1697
|
-
var charIndex = 0;
|
1698
|
-
this.setStart(containerNode, 0);
|
1699
|
-
this.collapse(true);
|
1700
|
-
var nodeStack = [containerNode], node, foundStart = false, stop = false;
|
1701
|
-
var nextCharIndex, i, childNodes;
|
1702
|
-
|
1703
|
-
while (!stop && (node = nodeStack.pop())) {
|
1704
|
-
if (node.nodeType == 3) {
|
1705
|
-
nextCharIndex = charIndex + node.length;
|
1706
|
-
if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {
|
1707
|
-
this.setStart(node, bookmark.start - charIndex);
|
1708
|
-
foundStart = true;
|
1709
|
-
}
|
1710
|
-
if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {
|
1711
|
-
this.setEnd(node, bookmark.end - charIndex);
|
1712
|
-
stop = true;
|
1713
|
-
}
|
1714
|
-
charIndex = nextCharIndex;
|
1715
|
-
} else {
|
1716
|
-
childNodes = node.childNodes;
|
1717
|
-
i = childNodes.length;
|
1718
|
-
while (i--) {
|
1719
|
-
nodeStack.push(childNodes[i]);
|
1720
|
-
}
|
1721
|
-
}
|
1722
|
-
}
|
1723
|
-
},
|
1724
|
-
|
1725
|
-
getName: function() {
|
1726
|
-
return "DomRange";
|
1727
|
-
},
|
1728
|
-
|
1729
|
-
equals: function(range) {
|
1730
|
-
return Range.rangesEqual(this, range);
|
1731
|
-
},
|
1732
|
-
|
1733
|
-
isValid: function() {
|
1734
|
-
return isRangeValid(this);
|
1735
|
-
},
|
1736
|
-
|
1737
|
-
inspect: function() {
|
1738
|
-
return inspect(this);
|
1739
|
-
}
|
1740
|
-
};
|
1741
|
-
|
1742
|
-
function copyComparisonConstantsToObject(obj) {
|
1743
|
-
obj.START_TO_START = s2s;
|
1744
|
-
obj.START_TO_END = s2e;
|
1745
|
-
obj.END_TO_END = e2e;
|
1746
|
-
obj.END_TO_START = e2s;
|
1747
|
-
|
1748
|
-
obj.NODE_BEFORE = n_b;
|
1749
|
-
obj.NODE_AFTER = n_a;
|
1750
|
-
obj.NODE_BEFORE_AND_AFTER = n_b_a;
|
1751
|
-
obj.NODE_INSIDE = n_i;
|
1752
|
-
}
|
1753
|
-
|
1754
|
-
function copyComparisonConstants(constructor) {
|
1755
|
-
copyComparisonConstantsToObject(constructor);
|
1756
|
-
copyComparisonConstantsToObject(constructor.prototype);
|
1757
|
-
}
|
1758
|
-
|
1759
|
-
function createRangeContentRemover(remover, boundaryUpdater) {
|
1760
|
-
return function() {
|
1761
|
-
assertRangeValid(this);
|
1762
|
-
|
1763
|
-
var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
|
1764
|
-
|
1765
|
-
var iterator = new RangeIterator(this, true);
|
1766
|
-
|
1767
|
-
// Work out where to position the range after content removal
|
1768
|
-
var node, boundary;
|
1769
|
-
if (sc !== root) {
|
1770
|
-
node = getClosestAncestorIn(sc, root, true);
|
1771
|
-
boundary = getBoundaryAfterNode(node);
|
1772
|
-
sc = boundary.node;
|
1773
|
-
so = boundary.offset;
|
1774
|
-
}
|
1775
|
-
|
1776
|
-
// Check none of the range is read-only
|
1777
|
-
iterateSubtree(iterator, assertNodeNotReadOnly);
|
1778
|
-
|
1779
|
-
iterator.reset();
|
1780
|
-
|
1781
|
-
// Remove the content
|
1782
|
-
var returnValue = remover(iterator);
|
1783
|
-
iterator.detach();
|
1784
|
-
|
1785
|
-
// Move to the new position
|
1786
|
-
boundaryUpdater(this, sc, so, sc, so);
|
1787
|
-
|
1788
|
-
return returnValue;
|
1789
|
-
};
|
1790
|
-
}
|
1791
|
-
|
1792
|
-
function createPrototypeRange(constructor, boundaryUpdater, detacher) {
|
1793
|
-
function createBeforeAfterNodeSetter(isBefore, isStart) {
|
1794
|
-
return function(node) {
|
1795
|
-
assertNotDetached(this);
|
1796
|
-
assertValidNodeType(node, beforeAfterNodeTypes);
|
1797
|
-
assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
|
1798
|
-
|
1799
|
-
var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
|
1800
|
-
(isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
|
1801
|
-
};
|
1802
|
-
}
|
1803
|
-
|
1804
|
-
function setRangeStart(range, node, offset) {
|
1805
|
-
var ec = range.endContainer, eo = range.endOffset;
|
1806
|
-
if (node !== range.startContainer || offset !== range.startOffset) {
|
1807
|
-
// Check the root containers of the range and the new boundary, and also check whether the new boundary
|
1808
|
-
// is after the current end. In either case, collapse the range to the new position
|
1809
|
-
if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) {
|
1810
|
-
ec = node;
|
1811
|
-
eo = offset;
|
1812
|
-
}
|
1813
|
-
boundaryUpdater(range, node, offset, ec, eo);
|
1814
|
-
}
|
1815
|
-
}
|
1816
|
-
|
1817
|
-
function setRangeEnd(range, node, offset) {
|
1818
|
-
var sc = range.startContainer, so = range.startOffset;
|
1819
|
-
if (node !== range.endContainer || offset !== range.endOffset) {
|
1820
|
-
// Check the root containers of the range and the new boundary, and also check whether the new boundary
|
1821
|
-
// is after the current end. In either case, collapse the range to the new position
|
1822
|
-
if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {
|
1823
|
-
sc = node;
|
1824
|
-
so = offset;
|
1825
|
-
}
|
1826
|
-
boundaryUpdater(range, sc, so, node, offset);
|
1827
|
-
}
|
1828
|
-
}
|
1829
|
-
|
1830
|
-
constructor.prototype = new RangePrototype();
|
1831
|
-
|
1832
|
-
util.extend(constructor.prototype, {
|
1833
|
-
setStart: function(node, offset) {
|
1834
|
-
assertNotDetached(this);
|
1835
|
-
assertNoDocTypeNotationEntityAncestor(node, true);
|
1836
|
-
assertValidOffset(node, offset);
|
1837
|
-
|
1838
|
-
setRangeStart(this, node, offset);
|
1839
|
-
},
|
1840
|
-
|
1841
|
-
setEnd: function(node, offset) {
|
1842
|
-
assertNotDetached(this);
|
1843
|
-
assertNoDocTypeNotationEntityAncestor(node, true);
|
1844
|
-
assertValidOffset(node, offset);
|
1845
|
-
|
1846
|
-
setRangeEnd(this, node, offset);
|
1847
|
-
},
|
1848
|
-
|
1849
|
-
/**
|
1850
|
-
* Convenience method to set a range's start and end boundaries. Overloaded as follows:
|
1851
|
-
* - Two parameters (node, offset) creates a collapsed range at that position
|
1852
|
-
* - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
|
1853
|
-
* startOffset and ending at endOffset
|
1854
|
-
* - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
|
1855
|
-
* startNode and ending at endOffset in endNode
|
1856
|
-
*/
|
1857
|
-
setStartAndEnd: function() {
|
1858
|
-
assertNotDetached(this);
|
1859
|
-
|
1860
|
-
var args = arguments;
|
1861
|
-
var sc = args[0], so = args[1], ec = sc, eo = so;
|
1862
|
-
|
1863
|
-
switch (args.length) {
|
1864
|
-
case 3:
|
1865
|
-
eo = args[2];
|
1866
|
-
break;
|
1867
|
-
case 4:
|
1868
|
-
ec = args[2];
|
1869
|
-
eo = args[3];
|
1870
|
-
break;
|
1871
|
-
}
|
1872
|
-
|
1873
|
-
boundaryUpdater(this, sc, so, ec, eo);
|
1874
|
-
},
|
1875
|
-
|
1876
|
-
setBoundary: function(node, offset, isStart) {
|
1877
|
-
this["set" + (isStart ? "Start" : "End")](node, offset);
|
1878
|
-
},
|
1879
|
-
|
1880
|
-
setStartBefore: createBeforeAfterNodeSetter(true, true),
|
1881
|
-
setStartAfter: createBeforeAfterNodeSetter(false, true),
|
1882
|
-
setEndBefore: createBeforeAfterNodeSetter(true, false),
|
1883
|
-
setEndAfter: createBeforeAfterNodeSetter(false, false),
|
1884
|
-
|
1885
|
-
collapse: function(isStart) {
|
1886
|
-
assertRangeValid(this);
|
1887
|
-
if (isStart) {
|
1888
|
-
boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
|
1889
|
-
} else {
|
1890
|
-
boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
|
1891
|
-
}
|
1892
|
-
},
|
1893
|
-
|
1894
|
-
selectNodeContents: function(node) {
|
1895
|
-
assertNotDetached(this);
|
1896
|
-
assertNoDocTypeNotationEntityAncestor(node, true);
|
1897
|
-
|
1898
|
-
boundaryUpdater(this, node, 0, node, getNodeLength(node));
|
1899
|
-
},
|
1900
|
-
|
1901
|
-
selectNode: function(node) {
|
1902
|
-
assertNotDetached(this);
|
1903
|
-
assertNoDocTypeNotationEntityAncestor(node, false);
|
1904
|
-
assertValidNodeType(node, beforeAfterNodeTypes);
|
1905
|
-
|
1906
|
-
var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
|
1907
|
-
boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
|
1908
|
-
},
|
1909
|
-
|
1910
|
-
extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
|
1911
|
-
|
1912
|
-
deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
|
1913
|
-
|
1914
|
-
canSurroundContents: function() {
|
1915
|
-
assertRangeValid(this);
|
1916
|
-
assertNodeNotReadOnly(this.startContainer);
|
1917
|
-
assertNodeNotReadOnly(this.endContainer);
|
1918
|
-
|
1919
|
-
// Check if the contents can be surrounded. Specifically, this means whether the range partially selects
|
1920
|
-
// no non-text nodes.
|
1921
|
-
var iterator = new RangeIterator(this, true);
|
1922
|
-
var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
|
1923
|
-
(iterator._last && isNonTextPartiallySelected(iterator._last, this)));
|
1924
|
-
iterator.detach();
|
1925
|
-
return !boundariesInvalid;
|
1926
|
-
},
|
1927
|
-
|
1928
|
-
detach: function() {
|
1929
|
-
detacher(this);
|
1930
|
-
},
|
1931
|
-
|
1932
|
-
splitBoundaries: function() {
|
1933
|
-
splitRangeBoundaries(this);
|
1934
|
-
},
|
1935
|
-
|
1936
|
-
splitBoundariesPreservingPositions: function(positionsToPreserve) {
|
1937
|
-
splitRangeBoundaries(this, positionsToPreserve);
|
1938
|
-
},
|
1939
|
-
|
1940
|
-
normalizeBoundaries: function() {
|
1941
|
-
assertRangeValid(this);
|
1942
|
-
|
1943
|
-
var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
|
1944
|
-
|
1945
|
-
var mergeForward = function(node) {
|
1946
|
-
var sibling = node.nextSibling;
|
1947
|
-
if (sibling && sibling.nodeType == node.nodeType) {
|
1948
|
-
ec = node;
|
1949
|
-
eo = node.length;
|
1950
|
-
node.appendData(sibling.data);
|
1951
|
-
sibling.parentNode.removeChild(sibling);
|
1952
|
-
}
|
1953
|
-
};
|
1954
|
-
|
1955
|
-
var mergeBackward = function(node) {
|
1956
|
-
var sibling = node.previousSibling;
|
1957
|
-
if (sibling && sibling.nodeType == node.nodeType) {
|
1958
|
-
sc = node;
|
1959
|
-
var nodeLength = node.length;
|
1960
|
-
so = sibling.length;
|
1961
|
-
node.insertData(0, sibling.data);
|
1962
|
-
sibling.parentNode.removeChild(sibling);
|
1963
|
-
if (sc == ec) {
|
1964
|
-
eo += so;
|
1965
|
-
ec = sc;
|
1966
|
-
} else if (ec == node.parentNode) {
|
1967
|
-
var nodeIndex = getNodeIndex(node);
|
1968
|
-
if (eo == nodeIndex) {
|
1969
|
-
ec = node;
|
1970
|
-
eo = nodeLength;
|
1971
|
-
} else if (eo > nodeIndex) {
|
1972
|
-
eo--;
|
1973
|
-
}
|
1974
|
-
}
|
1975
|
-
}
|
1976
|
-
};
|
1977
|
-
|
1978
|
-
var normalizeStart = true;
|
1979
|
-
|
1980
|
-
if (isCharacterDataNode(ec)) {
|
1981
|
-
if (ec.length == eo) {
|
1982
|
-
mergeForward(ec);
|
1983
|
-
}
|
1984
|
-
} else {
|
1985
|
-
if (eo > 0) {
|
1986
|
-
var endNode = ec.childNodes[eo - 1];
|
1987
|
-
if (endNode && isCharacterDataNode(endNode)) {
|
1988
|
-
mergeForward(endNode);
|
1989
|
-
}
|
1990
|
-
}
|
1991
|
-
normalizeStart = !this.collapsed;
|
1992
|
-
}
|
1993
|
-
|
1994
|
-
if (normalizeStart) {
|
1995
|
-
if (isCharacterDataNode(sc)) {
|
1996
|
-
if (so == 0) {
|
1997
|
-
mergeBackward(sc);
|
1998
|
-
}
|
1999
|
-
} else {
|
2000
|
-
if (so < sc.childNodes.length) {
|
2001
|
-
var startNode = sc.childNodes[so];
|
2002
|
-
if (startNode && isCharacterDataNode(startNode)) {
|
2003
|
-
mergeBackward(startNode);
|
2004
|
-
}
|
2005
|
-
}
|
2006
|
-
}
|
2007
|
-
} else {
|
2008
|
-
sc = ec;
|
2009
|
-
so = eo;
|
2010
|
-
}
|
2011
|
-
|
2012
|
-
boundaryUpdater(this, sc, so, ec, eo);
|
2013
|
-
},
|
2014
|
-
|
2015
|
-
collapseToPoint: function(node, offset) {
|
2016
|
-
assertNotDetached(this);
|
2017
|
-
assertNoDocTypeNotationEntityAncestor(node, true);
|
2018
|
-
assertValidOffset(node, offset);
|
2019
|
-
this.setStartAndEnd(node, offset);
|
2020
|
-
}
|
2021
|
-
});
|
2022
|
-
|
2023
|
-
copyComparisonConstants(constructor);
|
2024
|
-
}
|
2025
|
-
|
2026
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
2027
|
-
|
2028
|
-
// Updates commonAncestorContainer and collapsed after boundary change
|
2029
|
-
function updateCollapsedAndCommonAncestor(range) {
|
2030
|
-
range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
|
2031
|
-
range.commonAncestorContainer = range.collapsed ?
|
2032
|
-
range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
|
2033
|
-
}
|
2034
|
-
|
2035
|
-
function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
|
2036
|
-
range.startContainer = startContainer;
|
2037
|
-
range.startOffset = startOffset;
|
2038
|
-
range.endContainer = endContainer;
|
2039
|
-
range.endOffset = endOffset;
|
2040
|
-
range.document = dom.getDocument(startContainer);
|
2041
|
-
|
2042
|
-
updateCollapsedAndCommonAncestor(range);
|
2043
|
-
}
|
2044
|
-
|
2045
|
-
function detach(range) {
|
2046
|
-
assertNotDetached(range);
|
2047
|
-
range.startContainer = range.startOffset = range.endContainer = range.endOffset = range.document = null;
|
2048
|
-
range.collapsed = range.commonAncestorContainer = null;
|
2049
|
-
}
|
2050
|
-
|
2051
|
-
function Range(doc) {
|
2052
|
-
this.startContainer = doc;
|
2053
|
-
this.startOffset = 0;
|
2054
|
-
this.endContainer = doc;
|
2055
|
-
this.endOffset = 0;
|
2056
|
-
this.document = doc;
|
2057
|
-
updateCollapsedAndCommonAncestor(this);
|
2058
|
-
}
|
2059
|
-
|
2060
|
-
createPrototypeRange(Range, updateBoundaries, detach);
|
2061
|
-
|
2062
|
-
api.rangePrototype = RangePrototype.prototype;
|
2063
|
-
|
2064
|
-
util.extend(Range, {
|
2065
|
-
rangeProperties: rangeProperties,
|
2066
|
-
RangeIterator: RangeIterator,
|
2067
|
-
copyComparisonConstants: copyComparisonConstants,
|
2068
|
-
createPrototypeRange: createPrototypeRange,
|
2069
|
-
inspect: inspect,
|
2070
|
-
getRangeDocument: getRangeDocument,
|
2071
|
-
rangesEqual: function(r1, r2) {
|
2072
|
-
return r1.startContainer === r2.startContainer &&
|
2073
|
-
r1.startOffset === r2.startOffset &&
|
2074
|
-
r1.endContainer === r2.endContainer &&
|
2075
|
-
r1.endOffset === r2.endOffset;
|
2076
|
-
}
|
2077
|
-
});
|
2078
|
-
|
2079
|
-
api.DomRange = Range;
|
2080
|
-
api.RangeException = RangeException;
|
2081
|
-
});
|
2082
|
-
rangy.createModule("WrappedRange", function(api, module) {
|
2083
|
-
api.requireModules( ["DomUtil", "DomRange"] );
|
2084
|
-
|
2085
|
-
var WrappedRange, WrappedTextRange;
|
2086
|
-
var dom = api.dom;
|
2087
|
-
var util = api.util;
|
2088
|
-
var DomPosition = dom.DomPosition;
|
2089
|
-
var DomRange = api.DomRange;
|
2090
|
-
var getBody = dom.getBody;
|
2091
|
-
var getContentDocument = dom.getContentDocument;
|
2092
|
-
var isCharacterDataNode = dom.isCharacterDataNode;
|
2093
|
-
|
2094
|
-
|
2095
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
2096
|
-
|
2097
|
-
if (api.features.implementsDomRange) {
|
2098
|
-
// This is a wrapper around the browser's native DOM Range. It has two aims:
|
2099
|
-
// - Provide workarounds for specific browser bugs
|
2100
|
-
// - provide convenient extensions, which are inherited from Rangy's DomRange
|
2101
|
-
|
2102
|
-
(function() {
|
2103
|
-
var rangeProto;
|
2104
|
-
var rangeProperties = DomRange.rangeProperties;
|
2105
|
-
|
2106
|
-
function updateRangeProperties(range) {
|
2107
|
-
var i = rangeProperties.length, prop;
|
2108
|
-
while (i--) {
|
2109
|
-
prop = rangeProperties[i];
|
2110
|
-
range[prop] = range.nativeRange[prop];
|
2111
|
-
}
|
2112
|
-
// Fix for broken collapsed property in IE 9.
|
2113
|
-
range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
|
2114
|
-
}
|
2115
|
-
|
2116
|
-
function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
|
2117
|
-
var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
|
2118
|
-
var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
|
2119
|
-
var nativeRangeDifferent = !range.equals(range.nativeRange);
|
2120
|
-
|
2121
|
-
// Always set both boundaries for the benefit of IE9 (see issue 35)
|
2122
|
-
if (startMoved || endMoved || nativeRangeDifferent) {
|
2123
|
-
range.setEnd(endContainer, endOffset);
|
2124
|
-
range.setStart(startContainer, startOffset);
|
2125
|
-
}
|
2126
|
-
}
|
2127
|
-
|
2128
|
-
function detach(range) {
|
2129
|
-
range.nativeRange.detach();
|
2130
|
-
range.detached = true;
|
2131
|
-
var i = rangeProperties.length;
|
2132
|
-
while (i--) {
|
2133
|
-
range[ rangeProperties[i] ] = null;
|
2134
|
-
}
|
2135
|
-
}
|
2136
|
-
|
2137
|
-
var createBeforeAfterNodeSetter;
|
2138
|
-
|
2139
|
-
WrappedRange = function(range) {
|
2140
|
-
if (!range) {
|
2141
|
-
throw module.createError("WrappedRange: Range must be specified");
|
2142
|
-
}
|
2143
|
-
this.nativeRange = range;
|
2144
|
-
updateRangeProperties(this);
|
2145
|
-
};
|
2146
|
-
|
2147
|
-
DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);
|
2148
|
-
|
2149
|
-
rangeProto = WrappedRange.prototype;
|
2150
|
-
|
2151
|
-
rangeProto.selectNode = function(node) {
|
2152
|
-
this.nativeRange.selectNode(node);
|
2153
|
-
updateRangeProperties(this);
|
2154
|
-
};
|
2155
|
-
|
2156
|
-
rangeProto.cloneContents = function() {
|
2157
|
-
return this.nativeRange.cloneContents();
|
2158
|
-
};
|
2159
|
-
|
2160
|
-
// Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
|
2161
|
-
// insertNode() is never delegated to the native range.
|
2162
|
-
|
2163
|
-
rangeProto.surroundContents = function(node) {
|
2164
|
-
this.nativeRange.surroundContents(node);
|
2165
|
-
updateRangeProperties(this);
|
2166
|
-
};
|
2167
|
-
|
2168
|
-
rangeProto.collapse = function(isStart) {
|
2169
|
-
this.nativeRange.collapse(isStart);
|
2170
|
-
updateRangeProperties(this);
|
2171
|
-
};
|
2172
|
-
|
2173
|
-
rangeProto.cloneRange = function() {
|
2174
|
-
return new WrappedRange(this.nativeRange.cloneRange());
|
2175
|
-
};
|
2176
|
-
|
2177
|
-
rangeProto.refresh = function() {
|
2178
|
-
updateRangeProperties(this);
|
2179
|
-
};
|
2180
|
-
|
2181
|
-
rangeProto.toString = function() {
|
2182
|
-
return this.nativeRange.toString();
|
2183
|
-
};
|
2184
|
-
|
2185
|
-
// Create test range and node for feature detection
|
2186
|
-
|
2187
|
-
var testTextNode = document.createTextNode("test");
|
2188
|
-
getBody(document).appendChild(testTextNode);
|
2189
|
-
var range = document.createRange();
|
2190
|
-
|
2191
|
-
/*--------------------------------------------------------------------------------------------------------*/
|
2192
|
-
|
2193
|
-
// Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
|
2194
|
-
// correct for it
|
2195
|
-
|
2196
|
-
range.setStart(testTextNode, 0);
|
2197
|
-
range.setEnd(testTextNode, 0);
|
2198
|
-
|
2199
|
-
try {
|
2200
|
-
range.setStart(testTextNode, 1);
|
2201
|
-
|
2202
|
-
rangeProto.setStart = function(node, offset) {
|
2203
|
-
this.nativeRange.setStart(node, offset);
|
2204
|
-
updateRangeProperties(this);
|
2205
|
-
};
|
2206
|
-
|
2207
|
-
rangeProto.setEnd = function(node, offset) {
|
2208
|
-
this.nativeRange.setEnd(node, offset);
|
2209
|
-
updateRangeProperties(this);
|
2210
|
-
};
|
2211
|
-
|
2212
|
-
createBeforeAfterNodeSetter = function(name) {
|
2213
|
-
return function(node) {
|
2214
|
-
this.nativeRange[name](node);
|
2215
|
-
updateRangeProperties(this);
|
2216
|
-
};
|
2217
|
-
};
|
2218
|
-
|
2219
|
-
} catch(ex) {
|
2220
|
-
|
2221
|
-
rangeProto.setStart = function(node, offset) {
|
2222
|
-
try {
|
2223
|
-
this.nativeRange.setStart(node, offset);
|
2224
|
-
} catch (ex) {
|
2225
|
-
this.nativeRange.setEnd(node, offset);
|
2226
|
-
this.nativeRange.setStart(node, offset);
|
2227
|
-
}
|
2228
|
-
updateRangeProperties(this);
|
2229
|
-
};
|
2230
|
-
|
2231
|
-
rangeProto.setEnd = function(node, offset) {
|
2232
|
-
try {
|
2233
|
-
this.nativeRange.setEnd(node, offset);
|
2234
|
-
} catch (ex) {
|
2235
|
-
this.nativeRange.setStart(node, offset);
|
2236
|
-
this.nativeRange.setEnd(node, offset);
|
2237
|
-
}
|
2238
|
-
updateRangeProperties(this);
|
2239
|
-
};
|
2240
|
-
|
2241
|
-
createBeforeAfterNodeSetter = function(name, oppositeName) {
|
2242
|
-
return function(node) {
|
2243
|
-
try {
|
2244
|
-
this.nativeRange[name](node);
|
2245
|
-
} catch (ex) {
|
2246
|
-
this.nativeRange[oppositeName](node);
|
2247
|
-
this.nativeRange[name](node);
|
2248
|
-
}
|
2249
|
-
updateRangeProperties(this);
|
2250
|
-
};
|
2251
|
-
};
|
2252
|
-
}
|
2253
|
-
|
2254
|
-
rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
|
2255
|
-
rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
|
2256
|
-
rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
|
2257
|
-
rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
|
2258
|
-
|
2259
|
-
/*--------------------------------------------------------------------------------------------------------*/
|
2260
|
-
|
2261
|
-
// Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
|
2262
|
-
// the 0th character of the text node
|
2263
|
-
range.selectNodeContents(testTextNode);
|
2264
|
-
if (range.startContainer == testTextNode && range.endContainer == testTextNode &&
|
2265
|
-
range.startOffset == 0 && range.endOffset == testTextNode.length) {
|
2266
|
-
rangeProto.selectNodeContents = function(node) {
|
2267
|
-
this.nativeRange.selectNodeContents(node);
|
2268
|
-
updateRangeProperties(this);
|
2269
|
-
};
|
2270
|
-
} else {
|
2271
|
-
rangeProto.selectNodeContents = function(node) {
|
2272
|
-
this.setStartAndEnd(node, 0, dom.getNodeLength(node));
|
2273
|
-
};
|
2274
|
-
}
|
2275
|
-
|
2276
|
-
/*--------------------------------------------------------------------------------------------------------*/
|
2277
|
-
|
2278
|
-
// Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for
|
2279
|
-
// constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
|
2280
|
-
|
2281
|
-
range.selectNodeContents(testTextNode);
|
2282
|
-
range.setEnd(testTextNode, 3);
|
2283
|
-
|
2284
|
-
var range2 = document.createRange();
|
2285
|
-
range2.selectNodeContents(testTextNode);
|
2286
|
-
range2.setEnd(testTextNode, 4);
|
2287
|
-
range2.setStart(testTextNode, 2);
|
2288
|
-
|
2289
|
-
if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&
|
2290
|
-
range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
|
2291
|
-
// This is the wrong way round, so correct for it
|
2292
|
-
|
2293
|
-
rangeProto.compareBoundaryPoints = function(type, range) {
|
2294
|
-
range = range.nativeRange || range;
|
2295
|
-
if (type == range.START_TO_END) {
|
2296
|
-
type = range.END_TO_START;
|
2297
|
-
} else if (type == range.END_TO_START) {
|
2298
|
-
type = range.START_TO_END;
|
2299
|
-
}
|
2300
|
-
return this.nativeRange.compareBoundaryPoints(type, range);
|
2301
|
-
};
|
2302
|
-
} else {
|
2303
|
-
rangeProto.compareBoundaryPoints = function(type, range) {
|
2304
|
-
return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
|
2305
|
-
};
|
2306
|
-
}
|
2307
|
-
|
2308
|
-
/*--------------------------------------------------------------------------------------------------------*/
|
2309
|
-
|
2310
|
-
// Test for IE 9 deleteContents() and extractContents() bug and correct it. See issue 107.
|
2311
|
-
|
2312
|
-
var el = document.createElement("div");
|
2313
|
-
el.innerHTML = "123";
|
2314
|
-
var textNode = el.firstChild;
|
2315
|
-
var body = getBody(document);
|
2316
|
-
body.appendChild(el);
|
2317
|
-
|
2318
|
-
range.setStart(textNode, 1);
|
2319
|
-
range.setEnd(textNode, 2);
|
2320
|
-
range.deleteContents();
|
2321
|
-
|
2322
|
-
if (textNode.data == "13") {
|
2323
|
-
// Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
|
2324
|
-
// extractContents()
|
2325
|
-
rangeProto.deleteContents = function() {
|
2326
|
-
this.nativeRange.deleteContents();
|
2327
|
-
updateRangeProperties(this);
|
2328
|
-
};
|
2329
|
-
|
2330
|
-
rangeProto.extractContents = function() {
|
2331
|
-
var frag = this.nativeRange.extractContents();
|
2332
|
-
updateRangeProperties(this);
|
2333
|
-
return frag;
|
2334
|
-
};
|
2335
|
-
} else {
|
2336
|
-
}
|
2337
|
-
|
2338
|
-
body.removeChild(el);
|
2339
|
-
body = null;
|
2340
|
-
|
2341
|
-
/*--------------------------------------------------------------------------------------------------------*/
|
2342
|
-
|
2343
|
-
// Test for existence of createContextualFragment and delegate to it if it exists
|
2344
|
-
if (util.isHostMethod(range, "createContextualFragment")) {
|
2345
|
-
rangeProto.createContextualFragment = function(fragmentStr) {
|
2346
|
-
return this.nativeRange.createContextualFragment(fragmentStr);
|
2347
|
-
};
|
2348
|
-
}
|
2349
|
-
|
2350
|
-
/*--------------------------------------------------------------------------------------------------------*/
|
2351
|
-
|
2352
|
-
// Clean up
|
2353
|
-
getBody(document).removeChild(testTextNode);
|
2354
|
-
range.detach();
|
2355
|
-
range2.detach();
|
2356
|
-
|
2357
|
-
rangeProto.getName = function() {
|
2358
|
-
return "WrappedRange";
|
2359
|
-
};
|
2360
|
-
|
2361
|
-
api.WrappedRange = WrappedRange;
|
2362
|
-
|
2363
|
-
api.createNativeRange = function(doc) {
|
2364
|
-
doc = getContentDocument(doc, module, "createNativeRange");
|
2365
|
-
return doc.createRange();
|
2366
|
-
};
|
2367
|
-
})();
|
2368
|
-
}
|
2369
|
-
|
2370
|
-
if (api.features.implementsTextRange) {
|
2371
|
-
/*
|
2372
|
-
This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
|
2373
|
-
method. For example, in the following (where pipes denote the selection boundaries):
|
2374
|
-
|
2375
|
-
<ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
|
2376
|
-
|
2377
|
-
var range = document.selection.createRange();
|
2378
|
-
alert(range.parentElement().id); // Should alert "ul" but alerts "b"
|
2379
|
-
|
2380
|
-
This method returns the common ancestor node of the following:
|
2381
|
-
- the parentElement() of the textRange
|
2382
|
-
- the parentElement() of the textRange after calling collapse(true)
|
2383
|
-
- the parentElement() of the textRange after calling collapse(false)
|
2384
|
-
*/
|
2385
|
-
var getTextRangeContainerElement = function(textRange) {
|
2386
|
-
var parentEl = textRange.parentElement();
|
2387
|
-
var range = textRange.duplicate();
|
2388
|
-
range.collapse(true);
|
2389
|
-
var startEl = range.parentElement();
|
2390
|
-
range = textRange.duplicate();
|
2391
|
-
range.collapse(false);
|
2392
|
-
var endEl = range.parentElement();
|
2393
|
-
var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
|
2394
|
-
|
2395
|
-
return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
|
2396
|
-
};
|
2397
|
-
|
2398
|
-
var textRangeIsCollapsed = function(textRange) {
|
2399
|
-
return textRange.compareEndPoints("StartToEnd", textRange) == 0;
|
2400
|
-
};
|
2401
|
-
|
2402
|
-
// Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as
|
2403
|
-
// an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has
|
2404
|
-
// grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling
|
2405
|
-
// for inputs and images, plus optimizations.
|
2406
|
-
var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
|
2407
|
-
var workingRange = textRange.duplicate();
|
2408
|
-
workingRange.collapse(isStart);
|
2409
|
-
var containerElement = workingRange.parentElement();
|
2410
|
-
|
2411
|
-
// Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
|
2412
|
-
// check for that
|
2413
|
-
if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
|
2414
|
-
containerElement = wholeRangeContainerElement;
|
2415
|
-
}
|
2416
|
-
|
2417
|
-
|
2418
|
-
// Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
|
2419
|
-
// similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
|
2420
|
-
if (!containerElement.canHaveHTML) {
|
2421
|
-
var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
|
2422
|
-
return {
|
2423
|
-
boundaryPosition: pos,
|
2424
|
-
nodeInfo: {
|
2425
|
-
nodeIndex: pos.offset,
|
2426
|
-
containerElement: pos.node
|
2427
|
-
}
|
2428
|
-
};
|
2429
|
-
}
|
2430
|
-
|
2431
|
-
var workingNode = dom.getDocument(containerElement).createElement("span");
|
2432
|
-
|
2433
|
-
// Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
|
2434
|
-
// Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
|
2435
|
-
if (workingNode.parentNode) {
|
2436
|
-
workingNode.parentNode.removeChild(workingNode);
|
2437
|
-
}
|
2438
|
-
|
2439
|
-
var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
|
2440
|
-
var previousNode, nextNode, boundaryPosition, boundaryNode;
|
2441
|
-
var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;
|
2442
|
-
var childNodeCount = containerElement.childNodes.length;
|
2443
|
-
var end = childNodeCount;
|
2444
|
-
|
2445
|
-
// Check end first. Code within the loop assumes that the endth child node of the container is definitely
|
2446
|
-
// after the range boundary.
|
2447
|
-
var nodeIndex = end;
|
2448
|
-
|
2449
|
-
while (true) {
|
2450
|
-
if (nodeIndex == childNodeCount) {
|
2451
|
-
containerElement.appendChild(workingNode);
|
2452
|
-
} else {
|
2453
|
-
containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
|
2454
|
-
}
|
2455
|
-
workingRange.moveToElementText(workingNode);
|
2456
|
-
comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
|
2457
|
-
if (comparison == 0 || start == end) {
|
2458
|
-
break;
|
2459
|
-
} else if (comparison == -1) {
|
2460
|
-
if (end == start + 1) {
|
2461
|
-
// We know the endth child node is after the range boundary, so we must be done.
|
2462
|
-
break;
|
2463
|
-
} else {
|
2464
|
-
start = nodeIndex;
|
2465
|
-
}
|
2466
|
-
} else {
|
2467
|
-
end = (end == start + 1) ? start : nodeIndex;
|
2468
|
-
}
|
2469
|
-
nodeIndex = Math.floor((start + end) / 2);
|
2470
|
-
containerElement.removeChild(workingNode);
|
2471
|
-
}
|
2472
|
-
|
2473
|
-
|
2474
|
-
// We've now reached or gone past the boundary of the text range we're interested in
|
2475
|
-
// so have identified the node we want
|
2476
|
-
boundaryNode = workingNode.nextSibling;
|
2477
|
-
|
2478
|
-
if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) {
|
2479
|
-
// This is a character data node (text, comment, cdata). The working range is collapsed at the start of the
|
2480
|
-
// node containing the text range's boundary, so we move the end of the working range to the boundary point
|
2481
|
-
// and measure the length of its text to get the boundary's offset within the node.
|
2482
|
-
workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
|
2483
|
-
|
2484
|
-
var offset;
|
2485
|
-
|
2486
|
-
if (/[\r\n]/.test(boundaryNode.data)) {
|
2487
|
-
/*
|
2488
|
-
For the particular case of a boundary within a text node containing rendered line breaks (within a <pre>
|
2489
|
-
element, for example), we need a slightly complicated approach to get the boundary's offset in IE. The
|
2490
|
-
facts:
|
2491
|
-
|
2492
|
-
- Each line break is represented as \r in the text node's data/nodeValue properties
|
2493
|
-
- Each line break is represented as \r\n in the TextRange's 'text' property
|
2494
|
-
- The 'text' property of the TextRange does not contain trailing line breaks
|
2495
|
-
|
2496
|
-
To get round the problem presented by the final fact above, we can use the fact that TextRange's
|
2497
|
-
moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
|
2498
|
-
the same as the number of characters it was instructed to move. The simplest approach is to use this to
|
2499
|
-
store the characters moved when moving both the start and end of the range to the start of the document
|
2500
|
-
body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
|
2501
|
-
However, this is extremely slow when the document is large and the range is near the end of it. Clearly
|
2502
|
-
doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
|
2503
|
-
problem.
|
2504
|
-
|
2505
|
-
Another approach that works is to use moveStart() to move the start boundary of the range up to the end
|
2506
|
-
boundary one character at a time and incrementing a counter with the value returned by the moveStart()
|
2507
|
-
call. However, the check for whether the start boundary has reached the end boundary is expensive, so
|
2508
|
-
this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
|
2509
|
-
the range within the document).
|
2510
|
-
|
2511
|
-
The method below is a hybrid of the two methods above. It uses the fact that a string containing the
|
2512
|
-
TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
|
2513
|
-
text of the TextRange, so the start of the range is moved that length initially and then a character at
|
2514
|
-
a time to make up for any trailing line breaks not contained in the 'text' property. This has good
|
2515
|
-
performance in most situations compared to the previous two methods.
|
2516
|
-
*/
|
2517
|
-
var tempRange = workingRange.duplicate();
|
2518
|
-
var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
|
2519
|
-
|
2520
|
-
offset = tempRange.moveStart("character", rangeLength);
|
2521
|
-
while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
|
2522
|
-
offset++;
|
2523
|
-
tempRange.moveStart("character", 1);
|
2524
|
-
}
|
2525
|
-
} else {
|
2526
|
-
offset = workingRange.text.length;
|
2527
|
-
}
|
2528
|
-
boundaryPosition = new DomPosition(boundaryNode, offset);
|
2529
|
-
} else {
|
2530
|
-
|
2531
|
-
// If the boundary immediately follows a character data node and this is the end boundary, we should favour
|
2532
|
-
// a position within that, and likewise for a start boundary preceding a character data node
|
2533
|
-
previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
|
2534
|
-
nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
|
2535
|
-
if (nextNode && isCharacterDataNode(nextNode)) {
|
2536
|
-
boundaryPosition = new DomPosition(nextNode, 0);
|
2537
|
-
} else if (previousNode && isCharacterDataNode(previousNode)) {
|
2538
|
-
boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
|
2539
|
-
} else {
|
2540
|
-
boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
|
2541
|
-
}
|
2542
|
-
}
|
2543
|
-
|
2544
|
-
// Clean up
|
2545
|
-
workingNode.parentNode.removeChild(workingNode);
|
2546
|
-
|
2547
|
-
return {
|
2548
|
-
boundaryPosition: boundaryPosition,
|
2549
|
-
nodeInfo: {
|
2550
|
-
nodeIndex: nodeIndex,
|
2551
|
-
containerElement: containerElement
|
2552
|
-
}
|
2553
|
-
};
|
2554
|
-
};
|
2555
|
-
|
2556
|
-
// Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
|
2557
|
-
// This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
|
2558
|
-
// (http://code.google.com/p/ierange/)
|
2559
|
-
var createBoundaryTextRange = function(boundaryPosition, isStart) {
|
2560
|
-
var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
|
2561
|
-
var doc = dom.getDocument(boundaryPosition.node);
|
2562
|
-
var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
|
2563
|
-
var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
|
2564
|
-
|
2565
|
-
if (nodeIsDataNode) {
|
2566
|
-
boundaryNode = boundaryPosition.node;
|
2567
|
-
boundaryParent = boundaryNode.parentNode;
|
2568
|
-
} else {
|
2569
|
-
childNodes = boundaryPosition.node.childNodes;
|
2570
|
-
boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
|
2571
|
-
boundaryParent = boundaryPosition.node;
|
2572
|
-
}
|
2573
|
-
|
2574
|
-
// Position the range immediately before the node containing the boundary
|
2575
|
-
workingNode = doc.createElement("span");
|
2576
|
-
|
2577
|
-
// Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
|
2578
|
-
// element rather than immediately before or after it
|
2579
|
-
workingNode.innerHTML = "&#feff;";
|
2580
|
-
|
2581
|
-
// insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
|
2582
|
-
// for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
|
2583
|
-
if (boundaryNode) {
|
2584
|
-
boundaryParent.insertBefore(workingNode, boundaryNode);
|
2585
|
-
} else {
|
2586
|
-
boundaryParent.appendChild(workingNode);
|
2587
|
-
}
|
2588
|
-
|
2589
|
-
workingRange.moveToElementText(workingNode);
|
2590
|
-
workingRange.collapse(!isStart);
|
2591
|
-
|
2592
|
-
// Clean up
|
2593
|
-
boundaryParent.removeChild(workingNode);
|
2594
|
-
|
2595
|
-
// Move the working range to the text offset, if required
|
2596
|
-
if (nodeIsDataNode) {
|
2597
|
-
workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
|
2598
|
-
}
|
2599
|
-
|
2600
|
-
return workingRange;
|
2601
|
-
};
|
2602
|
-
|
2603
|
-
/*------------------------------------------------------------------------------------------------------------*/
|
2604
|
-
|
2605
|
-
// This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
|
2606
|
-
// prototype
|
2607
|
-
|
2608
|
-
WrappedTextRange = function(textRange) {
|
2609
|
-
this.textRange = textRange;
|
2610
|
-
this.refresh();
|
2611
|
-
};
|
2612
|
-
|
2613
|
-
WrappedTextRange.prototype = new DomRange(document);
|
2614
|
-
|
2615
|
-
WrappedTextRange.prototype.refresh = function() {
|
2616
|
-
var start, end, startBoundary;
|
2617
|
-
|
2618
|
-
// TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
|
2619
|
-
var rangeContainerElement = getTextRangeContainerElement(this.textRange);
|
2620
|
-
|
2621
|
-
if (textRangeIsCollapsed(this.textRange)) {
|
2622
|
-
end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
|
2623
|
-
true).boundaryPosition;
|
2624
|
-
} else {
|
2625
|
-
startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
|
2626
|
-
start = startBoundary.boundaryPosition;
|
2627
|
-
|
2628
|
-
// An optimization used here is that if the start and end boundaries have the same parent element, the
|
2629
|
-
// search scope for the end boundary can be limited to exclude the portion of the element that precedes
|
2630
|
-
// the start boundary
|
2631
|
-
end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
|
2632
|
-
startBoundary.nodeInfo).boundaryPosition;
|
2633
|
-
}
|
2634
|
-
|
2635
|
-
this.setStart(start.node, start.offset);
|
2636
|
-
this.setEnd(end.node, end.offset);
|
2637
|
-
};
|
2638
|
-
|
2639
|
-
WrappedTextRange.prototype.getName = function() {
|
2640
|
-
return "WrappedTextRange";
|
2641
|
-
};
|
2642
|
-
|
2643
|
-
DomRange.copyComparisonConstants(WrappedTextRange);
|
2644
|
-
|
2645
|
-
WrappedTextRange.rangeToTextRange = function(range) {
|
2646
|
-
if (range.collapsed) {
|
2647
|
-
return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
|
2648
|
-
} else {
|
2649
|
-
var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
|
2650
|
-
var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
|
2651
|
-
var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
|
2652
|
-
textRange.setEndPoint("StartToStart", startRange);
|
2653
|
-
textRange.setEndPoint("EndToEnd", endRange);
|
2654
|
-
return textRange;
|
2655
|
-
}
|
2656
|
-
};
|
2657
|
-
|
2658
|
-
api.WrappedTextRange = WrappedTextRange;
|
2659
|
-
|
2660
|
-
// IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
|
2661
|
-
// implementation to use by default.
|
2662
|
-
if (!api.features.implementsDomRange || api.config.preferTextRange) {
|
2663
|
-
// Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
|
2664
|
-
var globalObj = (function() { return this; })();
|
2665
|
-
if (typeof globalObj.Range == "undefined") {
|
2666
|
-
globalObj.Range = WrappedTextRange;
|
2667
|
-
}
|
2668
|
-
|
2669
|
-
api.createNativeRange = function(doc) {
|
2670
|
-
doc = getContentDocument(doc, module, "createNativeRange");
|
2671
|
-
return getBody(doc).createTextRange();
|
2672
|
-
};
|
2673
|
-
|
2674
|
-
api.WrappedRange = WrappedTextRange;
|
2675
|
-
}
|
2676
|
-
}
|
2677
|
-
|
2678
|
-
api.createRange = function(doc) {
|
2679
|
-
doc = getContentDocument(doc, module, "createRange");
|
2680
|
-
return new api.WrappedRange(api.createNativeRange(doc));
|
2681
|
-
};
|
2682
|
-
|
2683
|
-
api.createRangyRange = function(doc) {
|
2684
|
-
doc = getContentDocument(doc, module, "createRangyRange");
|
2685
|
-
return new DomRange(doc);
|
2686
|
-
};
|
2687
|
-
|
2688
|
-
api.createIframeRange = function(iframeEl) {
|
2689
|
-
module.deprecationNotice("createIframeRange()", "createRange(iframeEl)");
|
2690
|
-
return api.createRange(iframeEl);
|
2691
|
-
};
|
2692
|
-
|
2693
|
-
api.createIframeRangyRange = function(iframeEl) {
|
2694
|
-
module.deprecationNotice("createIframeRangyRange()", "createRangyRange(iframeEl)");
|
2695
|
-
return api.createRangyRange(iframeEl);
|
2696
|
-
};
|
2697
|
-
|
2698
|
-
api.addCreateMissingNativeApiListener(function(win) {
|
2699
|
-
var doc = win.document;
|
2700
|
-
if (typeof doc.createRange == "undefined") {
|
2701
|
-
doc.createRange = function() {
|
2702
|
-
return api.createRange(doc);
|
2703
|
-
};
|
2704
|
-
}
|
2705
|
-
doc = win = null;
|
2706
|
-
});
|
2707
|
-
});
|
2708
|
-
// This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
|
2709
|
-
// in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
|
2710
|
-
rangy.createModule("WrappedSelection", function(api, module) {
|
2711
|
-
api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
|
2712
|
-
|
2713
|
-
api.config.checkSelectionRanges = true;
|
2714
|
-
|
2715
|
-
var BOOLEAN = "boolean";
|
2716
|
-
var dom = api.dom;
|
2717
|
-
var util = api.util;
|
2718
|
-
var isHostMethod = util.isHostMethod;
|
2719
|
-
var DomRange = api.DomRange;
|
2720
|
-
var WrappedRange = api.WrappedRange;
|
2721
|
-
var DOMException = api.DOMException;
|
2722
|
-
var DomPosition = dom.DomPosition;
|
2723
|
-
var getNativeSelection;
|
2724
|
-
var selectionIsCollapsed;
|
2725
|
-
var features = api.features;
|
2726
|
-
var CONTROL = "Control";
|
2727
|
-
var getDocument = dom.getDocument;
|
2728
|
-
var getBody = dom.getBody;
|
2729
|
-
var rangesEqual = DomRange.rangesEqual;
|
2730
|
-
|
2731
|
-
|
2732
|
-
// Utility function to support direction parameters in the API that may be a string ("backward" or "forward") or a
|
2733
|
-
// Boolean (true for backwards).
|
2734
|
-
function isDirectionBackward(dir) {
|
2735
|
-
return (typeof dir == "string") ? (dir == "backward") : !!dir;
|
2736
|
-
}
|
2737
|
-
|
2738
|
-
function getWindow(win, methodName) {
|
2739
|
-
if (!win) {
|
2740
|
-
return window;
|
2741
|
-
} else if (dom.isWindow(win)) {
|
2742
|
-
return win;
|
2743
|
-
} else if (win instanceof WrappedSelection) {
|
2744
|
-
return win.win;
|
2745
|
-
} else {
|
2746
|
-
var doc = dom.getContentDocument(win, module, methodName);
|
2747
|
-
return dom.getWindow(doc);
|
2748
|
-
}
|
2749
|
-
}
|
2750
|
-
|
2751
|
-
function getWinSelection(winParam) {
|
2752
|
-
return getWindow(winParam, "getWinSelection").getSelection();
|
2753
|
-
}
|
2754
|
-
|
2755
|
-
function getDocSelection(winParam) {
|
2756
|
-
return getWindow(winParam, "getDocSelection").document.selection;
|
2757
|
-
}
|
2758
|
-
|
2759
|
-
// Test for the Range/TextRange and Selection features required
|
2760
|
-
// Test for ability to retrieve selection
|
2761
|
-
var implementsWinGetSelection = isHostMethod(window, "getSelection"),
|
2762
|
-
implementsDocSelection = util.isHostObject(document, "selection");
|
2763
|
-
|
2764
|
-
features.implementsWinGetSelection = implementsWinGetSelection;
|
2765
|
-
features.implementsDocSelection = implementsDocSelection;
|
2766
|
-
|
2767
|
-
var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
|
2768
|
-
|
2769
|
-
if (useDocumentSelection) {
|
2770
|
-
getNativeSelection = getDocSelection;
|
2771
|
-
api.isSelectionValid = function(winParam) {
|
2772
|
-
var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
|
2773
|
-
|
2774
|
-
// Check whether the selection TextRange is actually contained within the correct document
|
2775
|
-
return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
|
2776
|
-
};
|
2777
|
-
} else if (implementsWinGetSelection) {
|
2778
|
-
getNativeSelection = getWinSelection;
|
2779
|
-
api.isSelectionValid = function() {
|
2780
|
-
return true;
|
2781
|
-
};
|
2782
|
-
} else {
|
2783
|
-
module.fail("Neither document.selection or window.getSelection() detected.");
|
2784
|
-
}
|
2785
|
-
|
2786
|
-
api.getNativeSelection = getNativeSelection;
|
2787
|
-
|
2788
|
-
var testSelection = getNativeSelection();
|
2789
|
-
var testRange = api.createNativeRange(document);
|
2790
|
-
var body = getBody(document);
|
2791
|
-
|
2792
|
-
// Obtaining a range from a selection
|
2793
|
-
var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
|
2794
|
-
["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
|
2795
|
-
|
2796
|
-
features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
|
2797
|
-
|
2798
|
-
// Test for existence of native selection extend() method
|
2799
|
-
var selectionHasExtend = isHostMethod(testSelection, "extend");
|
2800
|
-
features.selectionHasExtend = selectionHasExtend;
|
2801
|
-
|
2802
|
-
// Test if rangeCount exists
|
2803
|
-
var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
|
2804
|
-
features.selectionHasRangeCount = selectionHasRangeCount;
|
2805
|
-
|
2806
|
-
var selectionSupportsMultipleRanges = false;
|
2807
|
-
var collapsedNonEditableSelectionsSupported = true;
|
2808
|
-
|
2809
|
-
if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
|
2810
|
-
typeof testSelection.rangeCount == "number" && features.implementsDomRange) {
|
2811
|
-
|
2812
|
-
(function() {
|
2813
|
-
// Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
|
2814
|
-
// performed on the current document's selection. See issue 109.
|
2815
|
-
|
2816
|
-
// Note also that if a selection previously existed, it is wiped by these tests. This should usually be fine
|
2817
|
-
// because initialization usually happens when the document loads, but could be a problem for a script that
|
2818
|
-
// loads and initializes Rangy later. If anyone complains, code could be added to save and restore the
|
2819
|
-
// selection.
|
2820
|
-
var sel = window.getSelection();
|
2821
|
-
if (sel) {
|
2822
|
-
var body = getBody(document);
|
2823
|
-
var testEl = body.appendChild( document.createElement("div") );
|
2824
|
-
testEl.contentEditable = "false";
|
2825
|
-
var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
|
2826
|
-
|
2827
|
-
// Test whether the native selection will allow a collapsed selection within a non-editable element
|
2828
|
-
var r1 = document.createRange();
|
2829
|
-
|
2830
|
-
r1.setStart(textNode, 1);
|
2831
|
-
r1.collapse(true);
|
2832
|
-
sel.addRange(r1);
|
2833
|
-
collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
|
2834
|
-
sel.removeAllRanges();
|
2835
|
-
|
2836
|
-
// Test whether the native selection is capable of supporting multiple ranges
|
2837
|
-
var r2 = r1.cloneRange();
|
2838
|
-
r1.setStart(textNode, 0);
|
2839
|
-
r2.setEnd(textNode, 3);
|
2840
|
-
r2.setStart(textNode, 2);
|
2841
|
-
sel.addRange(r1);
|
2842
|
-
sel.addRange(r2);
|
2843
|
-
|
2844
|
-
selectionSupportsMultipleRanges = (sel.rangeCount == 2);
|
2845
|
-
|
2846
|
-
// Clean up
|
2847
|
-
body.removeChild(testEl);
|
2848
|
-
sel.removeAllRanges();
|
2849
|
-
r1.detach();
|
2850
|
-
r2.detach();
|
2851
|
-
}
|
2852
|
-
})();
|
2853
|
-
}
|
2854
|
-
|
2855
|
-
features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
|
2856
|
-
features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
|
2857
|
-
|
2858
|
-
// ControlRanges
|
2859
|
-
var implementsControlRange = false, testControlRange;
|
2860
|
-
|
2861
|
-
if (body && isHostMethod(body, "createControlRange")) {
|
2862
|
-
testControlRange = body.createControlRange();
|
2863
|
-
if (util.areHostProperties(testControlRange, ["item", "add"])) {
|
2864
|
-
implementsControlRange = true;
|
2865
|
-
}
|
2866
|
-
}
|
2867
|
-
features.implementsControlRange = implementsControlRange;
|
2868
|
-
|
2869
|
-
// Selection collapsedness
|
2870
|
-
if (selectionHasAnchorAndFocus) {
|
2871
|
-
selectionIsCollapsed = function(sel) {
|
2872
|
-
return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
|
2873
|
-
};
|
2874
|
-
} else {
|
2875
|
-
selectionIsCollapsed = function(sel) {
|
2876
|
-
return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
|
2877
|
-
};
|
2878
|
-
}
|
2879
|
-
|
2880
|
-
function updateAnchorAndFocusFromRange(sel, range, backward) {
|
2881
|
-
var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
|
2882
|
-
sel.anchorNode = range[anchorPrefix + "Container"];
|
2883
|
-
sel.anchorOffset = range[anchorPrefix + "Offset"];
|
2884
|
-
sel.focusNode = range[focusPrefix + "Container"];
|
2885
|
-
sel.focusOffset = range[focusPrefix + "Offset"];
|
2886
|
-
}
|
2887
|
-
|
2888
|
-
function updateAnchorAndFocusFromNativeSelection(sel) {
|
2889
|
-
var nativeSel = sel.nativeSelection;
|
2890
|
-
sel.anchorNode = nativeSel.anchorNode;
|
2891
|
-
sel.anchorOffset = nativeSel.anchorOffset;
|
2892
|
-
sel.focusNode = nativeSel.focusNode;
|
2893
|
-
sel.focusOffset = nativeSel.focusOffset;
|
2894
|
-
}
|
2895
|
-
|
2896
|
-
function updateEmptySelection(sel) {
|
2897
|
-
sel.anchorNode = sel.focusNode = null;
|
2898
|
-
sel.anchorOffset = sel.focusOffset = 0;
|
2899
|
-
sel.rangeCount = 0;
|
2900
|
-
sel.isCollapsed = true;
|
2901
|
-
sel._ranges.length = 0;
|
2902
|
-
}
|
2903
|
-
|
2904
|
-
function getNativeRange(range) {
|
2905
|
-
var nativeRange;
|
2906
|
-
if (range instanceof DomRange) {
|
2907
|
-
nativeRange = api.createNativeRange(range.getDocument());
|
2908
|
-
nativeRange.setEnd(range.endContainer, range.endOffset);
|
2909
|
-
nativeRange.setStart(range.startContainer, range.startOffset);
|
2910
|
-
} else if (range instanceof WrappedRange) {
|
2911
|
-
nativeRange = range.nativeRange;
|
2912
|
-
} else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
|
2913
|
-
nativeRange = range;
|
2914
|
-
}
|
2915
|
-
return nativeRange;
|
2916
|
-
}
|
2917
|
-
|
2918
|
-
function rangeContainsSingleElement(rangeNodes) {
|
2919
|
-
if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
|
2920
|
-
return false;
|
2921
|
-
}
|
2922
|
-
for (var i = 1, len = rangeNodes.length; i < len; ++i) {
|
2923
|
-
if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
|
2924
|
-
return false;
|
2925
|
-
}
|
2926
|
-
}
|
2927
|
-
return true;
|
2928
|
-
}
|
2929
|
-
|
2930
|
-
function getSingleElementFromRange(range) {
|
2931
|
-
var nodes = range.getNodes();
|
2932
|
-
if (!rangeContainsSingleElement(nodes)) {
|
2933
|
-
throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
|
2934
|
-
}
|
2935
|
-
return nodes[0];
|
2936
|
-
}
|
2937
|
-
|
2938
|
-
// Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
|
2939
|
-
function isTextRange(range) {
|
2940
|
-
return !!range && typeof range.text != "undefined";
|
2941
|
-
}
|
2942
|
-
|
2943
|
-
function updateFromTextRange(sel, range) {
|
2944
|
-
// Create a Range from the selected TextRange
|
2945
|
-
var wrappedRange = new WrappedRange(range);
|
2946
|
-
sel._ranges = [wrappedRange];
|
2947
|
-
|
2948
|
-
updateAnchorAndFocusFromRange(sel, wrappedRange, false);
|
2949
|
-
sel.rangeCount = 1;
|
2950
|
-
sel.isCollapsed = wrappedRange.collapsed;
|
2951
|
-
}
|
2952
|
-
|
2953
|
-
function updateControlSelection(sel) {
|
2954
|
-
// Update the wrapped selection based on what's now in the native selection
|
2955
|
-
sel._ranges.length = 0;
|
2956
|
-
if (sel.docSelection.type == "None") {
|
2957
|
-
updateEmptySelection(sel);
|
2958
|
-
} else {
|
2959
|
-
var controlRange = sel.docSelection.createRange();
|
2960
|
-
if (isTextRange(controlRange)) {
|
2961
|
-
// This case (where the selection type is "Control" and calling createRange() on the selection returns
|
2962
|
-
// a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
|
2963
|
-
// ControlRange have been removed from the ControlRange and removed from the document.
|
2964
|
-
updateFromTextRange(sel, controlRange);
|
2965
|
-
} else {
|
2966
|
-
sel.rangeCount = controlRange.length;
|
2967
|
-
var range, doc = getDocument(controlRange.item(0));
|
2968
|
-
for (var i = 0; i < sel.rangeCount; ++i) {
|
2969
|
-
range = api.createRange(doc);
|
2970
|
-
range.selectNode(controlRange.item(i));
|
2971
|
-
sel._ranges.push(range);
|
2972
|
-
}
|
2973
|
-
sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
|
2974
|
-
updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
|
2975
|
-
}
|
2976
|
-
}
|
2977
|
-
}
|
2978
|
-
|
2979
|
-
function addRangeToControlSelection(sel, range) {
|
2980
|
-
var controlRange = sel.docSelection.createRange();
|
2981
|
-
var rangeElement = getSingleElementFromRange(range);
|
2982
|
-
|
2983
|
-
// Create a new ControlRange containing all the elements in the selected ControlRange plus the element
|
2984
|
-
// contained by the supplied range
|
2985
|
-
var doc = getDocument(controlRange.item(0));
|
2986
|
-
var newControlRange = getBody(doc).createControlRange();
|
2987
|
-
for (var i = 0, len = controlRange.length; i < len; ++i) {
|
2988
|
-
newControlRange.add(controlRange.item(i));
|
2989
|
-
}
|
2990
|
-
try {
|
2991
|
-
newControlRange.add(rangeElement);
|
2992
|
-
} catch (ex) {
|
2993
|
-
throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
|
2994
|
-
}
|
2995
|
-
newControlRange.select();
|
2996
|
-
|
2997
|
-
// Update the wrapped selection based on what's now in the native selection
|
2998
|
-
updateControlSelection(sel);
|
2999
|
-
}
|
3000
|
-
|
3001
|
-
var getSelectionRangeAt;
|
3002
|
-
|
3003
|
-
if (isHostMethod(testSelection, "getRangeAt")) {
|
3004
|
-
// try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
|
3005
|
-
// Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
|
3006
|
-
// lesson to us all, especially me.
|
3007
|
-
getSelectionRangeAt = function(sel, index) {
|
3008
|
-
try {
|
3009
|
-
return sel.getRangeAt(index);
|
3010
|
-
} catch (ex) {
|
3011
|
-
return null;
|
3012
|
-
}
|
3013
|
-
};
|
3014
|
-
} else if (selectionHasAnchorAndFocus) {
|
3015
|
-
getSelectionRangeAt = function(sel) {
|
3016
|
-
var doc = getDocument(sel.anchorNode);
|
3017
|
-
var range = api.createRange(doc);
|
3018
|
-
range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
|
3019
|
-
|
3020
|
-
// Handle the case when the selection was selected backwards (from the end to the start in the
|
3021
|
-
// document)
|
3022
|
-
if (range.collapsed !== this.isCollapsed) {
|
3023
|
-
range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
|
3024
|
-
}
|
3025
|
-
|
3026
|
-
return range;
|
3027
|
-
};
|
3028
|
-
}
|
3029
|
-
|
3030
|
-
function WrappedSelection(selection, docSelection, win) {
|
3031
|
-
this.nativeSelection = selection;
|
3032
|
-
this.docSelection = docSelection;
|
3033
|
-
this._ranges = [];
|
3034
|
-
this.win = win;
|
3035
|
-
this.refresh();
|
3036
|
-
}
|
3037
|
-
|
3038
|
-
function deleteProperties(sel) {
|
3039
|
-
sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
|
3040
|
-
sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
|
3041
|
-
sel.detached = true;
|
3042
|
-
}
|
3043
|
-
|
3044
|
-
var cachedRangySelections = [];
|
3045
|
-
|
3046
|
-
function findCachedSelection(win, action) {
|
3047
|
-
var i = cachedRangySelections.length, cached, sel;
|
3048
|
-
while (i--) {
|
3049
|
-
cached = cachedRangySelections[i];
|
3050
|
-
sel = cached.selection;
|
3051
|
-
if (action == "deleteAll") {
|
3052
|
-
deleteProperties(sel);
|
3053
|
-
} else if (cached.win == win) {
|
3054
|
-
if (action == "delete") {
|
3055
|
-
cachedRangySelections.splice(i, 1);
|
3056
|
-
return true;
|
3057
|
-
} else {
|
3058
|
-
return sel;
|
3059
|
-
}
|
3060
|
-
}
|
3061
|
-
}
|
3062
|
-
if (action == "deleteAll") {
|
3063
|
-
cachedRangySelections.length = 0;
|
3064
|
-
}
|
3065
|
-
return null;
|
3066
|
-
}
|
3067
|
-
|
3068
|
-
var getSelection = function(win) {
|
3069
|
-
// Check if the parameter is a Rangy Selection object
|
3070
|
-
if (win && win instanceof WrappedSelection) {
|
3071
|
-
win.refresh();
|
3072
|
-
return win;
|
3073
|
-
}
|
3074
|
-
|
3075
|
-
win = getWindow(win, "getNativeSelection");
|
3076
|
-
|
3077
|
-
var sel = findCachedSelection(win);
|
3078
|
-
var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
|
3079
|
-
if (sel) {
|
3080
|
-
sel.nativeSelection = nativeSel;
|
3081
|
-
sel.docSelection = docSel;
|
3082
|
-
sel.refresh();
|
3083
|
-
} else {
|
3084
|
-
sel = new WrappedSelection(nativeSel, docSel, win);
|
3085
|
-
cachedRangySelections.push( { win: win, selection: sel } );
|
3086
|
-
}
|
3087
|
-
return sel;
|
3088
|
-
};
|
3089
|
-
|
3090
|
-
api.getSelection = getSelection;
|
3091
|
-
|
3092
|
-
api.getIframeSelection = function(iframeEl) {
|
3093
|
-
module.deprecationNotice("getIframeSelection()", "getSelection(iframeEl)");
|
3094
|
-
return api.getSelection(dom.getIframeWindow(iframeEl));
|
3095
|
-
};
|
3096
|
-
|
3097
|
-
var selProto = WrappedSelection.prototype;
|
3098
|
-
|
3099
|
-
function createControlSelection(sel, ranges) {
|
3100
|
-
// Ensure that the selection becomes of type "Control"
|
3101
|
-
var doc = getDocument(ranges[0].startContainer);
|
3102
|
-
var controlRange = getBody(doc).createControlRange();
|
3103
|
-
for (var i = 0, el; i < rangeCount; ++i) {
|
3104
|
-
el = getSingleElementFromRange(ranges[i]);
|
3105
|
-
try {
|
3106
|
-
controlRange.add(el);
|
3107
|
-
} catch (ex) {
|
3108
|
-
throw module.createError("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
|
3109
|
-
}
|
3110
|
-
}
|
3111
|
-
controlRange.select();
|
3112
|
-
|
3113
|
-
// Update the wrapped selection based on what's now in the native selection
|
3114
|
-
updateControlSelection(sel);
|
3115
|
-
}
|
3116
|
-
|
3117
|
-
// Selecting a range
|
3118
|
-
if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
|
3119
|
-
selProto.removeAllRanges = function() {
|
3120
|
-
this.nativeSelection.removeAllRanges();
|
3121
|
-
updateEmptySelection(this);
|
3122
|
-
};
|
3123
|
-
|
3124
|
-
var addRangeBackward = function(sel, range) {
|
3125
|
-
var doc = DomRange.getRangeDocument(range);
|
3126
|
-
var endRange = api.createRange(doc);
|
3127
|
-
endRange.collapseToPoint(range.endContainer, range.endOffset);
|
3128
|
-
sel.nativeSelection.addRange(getNativeRange(endRange));
|
3129
|
-
sel.nativeSelection.extend(range.startContainer, range.startOffset);
|
3130
|
-
sel.refresh();
|
3131
|
-
};
|
3132
|
-
|
3133
|
-
if (selectionHasRangeCount) {
|
3134
|
-
selProto.addRange = function(range, direction) {
|
3135
|
-
if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
|
3136
|
-
addRangeToControlSelection(this, range);
|
3137
|
-
} else {
|
3138
|
-
if (isDirectionBackward(direction) && selectionHasExtend) {
|
3139
|
-
addRangeBackward(this, range);
|
3140
|
-
} else {
|
3141
|
-
var previousRangeCount;
|
3142
|
-
if (selectionSupportsMultipleRanges) {
|
3143
|
-
previousRangeCount = this.rangeCount;
|
3144
|
-
} else {
|
3145
|
-
this.removeAllRanges();
|
3146
|
-
previousRangeCount = 0;
|
3147
|
-
}
|
3148
|
-
// Clone the native range so that changing the selected range does not affect the selection.
|
3149
|
-
// This is contrary to the spec but is the only way to achieve consistency between browsers. See
|
3150
|
-
// issue 80.
|
3151
|
-
this.nativeSelection.addRange(getNativeRange(range).cloneRange());
|
3152
|
-
|
3153
|
-
// Check whether adding the range was successful
|
3154
|
-
this.rangeCount = this.nativeSelection.rangeCount;
|
3155
|
-
|
3156
|
-
if (this.rangeCount == previousRangeCount + 1) {
|
3157
|
-
// The range was added successfully
|
3158
|
-
|
3159
|
-
// Check whether the range that we added to the selection is reflected in the last range extracted from
|
3160
|
-
// the selection
|
3161
|
-
if (api.config.checkSelectionRanges) {
|
3162
|
-
var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
|
3163
|
-
if (nativeRange && !rangesEqual(nativeRange, range)) {
|
3164
|
-
// Happens in WebKit with, for example, a selection placed at the start of a text node
|
3165
|
-
range = new WrappedRange(nativeRange);
|
3166
|
-
}
|
3167
|
-
}
|
3168
|
-
this._ranges[this.rangeCount - 1] = range;
|
3169
|
-
updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
|
3170
|
-
this.isCollapsed = selectionIsCollapsed(this);
|
3171
|
-
} else {
|
3172
|
-
// The range was not added successfully. The simplest thing is to refresh
|
3173
|
-
this.refresh();
|
3174
|
-
}
|
3175
|
-
}
|
3176
|
-
}
|
3177
|
-
};
|
3178
|
-
} else {
|
3179
|
-
selProto.addRange = function(range, direction) {
|
3180
|
-
if (isDirectionBackward(direction) && selectionHasExtend) {
|
3181
|
-
addRangeBackward(this, range);
|
3182
|
-
} else {
|
3183
|
-
this.nativeSelection.addRange(getNativeRange(range));
|
3184
|
-
this.refresh();
|
3185
|
-
}
|
3186
|
-
};
|
3187
|
-
}
|
3188
|
-
|
3189
|
-
selProto.setRanges = function(ranges) {
|
3190
|
-
if (implementsControlRange && ranges.length > 1) {
|
3191
|
-
createControlSelection(this, ranges);
|
3192
|
-
} else {
|
3193
|
-
this.removeAllRanges();
|
3194
|
-
for (var i = 0, len = ranges.length; i < len; ++i) {
|
3195
|
-
this.addRange(ranges[i]);
|
3196
|
-
}
|
3197
|
-
}
|
3198
|
-
};
|
3199
|
-
} else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
|
3200
|
-
implementsControlRange && useDocumentSelection) {
|
3201
|
-
|
3202
|
-
selProto.removeAllRanges = function() {
|
3203
|
-
// Added try/catch as fix for issue #21
|
3204
|
-
try {
|
3205
|
-
this.docSelection.empty();
|
3206
|
-
|
3207
|
-
// Check for empty() not working (issue #24)
|
3208
|
-
if (this.docSelection.type != "None") {
|
3209
|
-
// Work around failure to empty a control selection by instead selecting a TextRange and then
|
3210
|
-
// calling empty()
|
3211
|
-
var doc;
|
3212
|
-
if (this.anchorNode) {
|
3213
|
-
doc = getDocument(this.anchorNode);
|
3214
|
-
} else if (this.docSelection.type == CONTROL) {
|
3215
|
-
var controlRange = this.docSelection.createRange();
|
3216
|
-
if (controlRange.length) {
|
3217
|
-
doc = getDocument( controlRange.item(0) );
|
3218
|
-
}
|
3219
|
-
}
|
3220
|
-
if (doc) {
|
3221
|
-
var textRange = getBody(doc).createTextRange();
|
3222
|
-
textRange.select();
|
3223
|
-
this.docSelection.empty();
|
3224
|
-
}
|
3225
|
-
}
|
3226
|
-
} catch(ex) {}
|
3227
|
-
updateEmptySelection(this);
|
3228
|
-
};
|
3229
|
-
|
3230
|
-
selProto.addRange = function(range) {
|
3231
|
-
if (this.docSelection.type == CONTROL) {
|
3232
|
-
addRangeToControlSelection(this, range);
|
3233
|
-
} else {
|
3234
|
-
WrappedRange.rangeToTextRange(range).select();
|
3235
|
-
this._ranges[0] = range;
|
3236
|
-
this.rangeCount = 1;
|
3237
|
-
this.isCollapsed = this._ranges[0].collapsed;
|
3238
|
-
updateAnchorAndFocusFromRange(this, range, false);
|
3239
|
-
}
|
3240
|
-
};
|
3241
|
-
|
3242
|
-
selProto.setRanges = function(ranges) {
|
3243
|
-
this.removeAllRanges();
|
3244
|
-
var rangeCount = ranges.length;
|
3245
|
-
if (rangeCount > 1) {
|
3246
|
-
createControlSelection(this, ranges);
|
3247
|
-
} else if (rangeCount) {
|
3248
|
-
this.addRange(ranges[0]);
|
3249
|
-
}
|
3250
|
-
};
|
3251
|
-
} else {
|
3252
|
-
module.fail("No means of selecting a Range or TextRange was found");
|
3253
|
-
return false;
|
3254
|
-
}
|
3255
|
-
|
3256
|
-
selProto.getRangeAt = function(index) {
|
3257
|
-
if (index < 0 || index >= this.rangeCount) {
|
3258
|
-
throw new DOMException("INDEX_SIZE_ERR");
|
3259
|
-
} else {
|
3260
|
-
// Clone the range to preserve selection-range independence. See issue 80.
|
3261
|
-
return this._ranges[index].cloneRange();
|
3262
|
-
}
|
3263
|
-
};
|
3264
|
-
|
3265
|
-
var refreshSelection;
|
3266
|
-
|
3267
|
-
if (useDocumentSelection) {
|
3268
|
-
refreshSelection = function(sel) {
|
3269
|
-
var range;
|
3270
|
-
if (api.isSelectionValid(sel.win)) {
|
3271
|
-
range = sel.docSelection.createRange();
|
3272
|
-
} else {
|
3273
|
-
range = getBody(sel.win.document).createTextRange();
|
3274
|
-
range.collapse(true);
|
3275
|
-
}
|
3276
|
-
|
3277
|
-
if (sel.docSelection.type == CONTROL) {
|
3278
|
-
updateControlSelection(sel);
|
3279
|
-
} else if (isTextRange(range)) {
|
3280
|
-
updateFromTextRange(sel, range);
|
3281
|
-
} else {
|
3282
|
-
updateEmptySelection(sel);
|
3283
|
-
}
|
3284
|
-
};
|
3285
|
-
} else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
|
3286
|
-
refreshSelection = function(sel) {
|
3287
|
-
if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
|
3288
|
-
updateControlSelection(sel);
|
3289
|
-
} else {
|
3290
|
-
sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
|
3291
|
-
if (sel.rangeCount) {
|
3292
|
-
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
|
3293
|
-
sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
|
3294
|
-
}
|
3295
|
-
updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
|
3296
|
-
sel.isCollapsed = selectionIsCollapsed(sel);
|
3297
|
-
} else {
|
3298
|
-
updateEmptySelection(sel);
|
3299
|
-
}
|
3300
|
-
}
|
3301
|
-
};
|
3302
|
-
} else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
|
3303
|
-
refreshSelection = function(sel) {
|
3304
|
-
var range, nativeSel = sel.nativeSelection;
|
3305
|
-
if (nativeSel.anchorNode) {
|
3306
|
-
range = getSelectionRangeAt(nativeSel, 0);
|
3307
|
-
sel._ranges = [range];
|
3308
|
-
sel.rangeCount = 1;
|
3309
|
-
updateAnchorAndFocusFromNativeSelection(sel);
|
3310
|
-
sel.isCollapsed = selectionIsCollapsed(sel);
|
3311
|
-
} else {
|
3312
|
-
updateEmptySelection(sel);
|
3313
|
-
}
|
3314
|
-
};
|
3315
|
-
} else {
|
3316
|
-
module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
|
3317
|
-
return false;
|
3318
|
-
}
|
3319
|
-
|
3320
|
-
selProto.refresh = function(checkForChanges) {
|
3321
|
-
var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
|
3322
|
-
var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
|
3323
|
-
|
3324
|
-
refreshSelection(this);
|
3325
|
-
if (checkForChanges) {
|
3326
|
-
// Check the range count first
|
3327
|
-
var i = oldRanges.length;
|
3328
|
-
if (i != this._ranges.length) {
|
3329
|
-
return true;
|
3330
|
-
}
|
3331
|
-
|
3332
|
-
// Now check the direction. Checking the anchor position is the same is enough since we're checking all the
|
3333
|
-
// ranges after this
|
3334
|
-
if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
|
3335
|
-
return true;
|
3336
|
-
}
|
3337
|
-
|
3338
|
-
// Finally, compare each range in turn
|
3339
|
-
while (i--) {
|
3340
|
-
if (!rangesEqual(oldRanges[i], this._ranges[i])) {
|
3341
|
-
return true;
|
3342
|
-
}
|
3343
|
-
}
|
3344
|
-
return false;
|
3345
|
-
}
|
3346
|
-
};
|
3347
|
-
|
3348
|
-
// Removal of a single range
|
3349
|
-
var removeRangeManually = function(sel, range) {
|
3350
|
-
var ranges = sel.getAllRanges();
|
3351
|
-
sel.removeAllRanges();
|
3352
|
-
for (var i = 0, len = ranges.length; i < len; ++i) {
|
3353
|
-
if (!api.rangesEqual(range, ranges[i])) {
|
3354
|
-
sel.addRange(ranges[i]);
|
3355
|
-
}
|
3356
|
-
}
|
3357
|
-
if (!sel.rangeCount) {
|
3358
|
-
updateEmptySelection(sel);
|
3359
|
-
}
|
3360
|
-
};
|
3361
|
-
|
3362
|
-
if (implementsControlRange) {
|
3363
|
-
selProto.removeRange = function(range) {
|
3364
|
-
if (this.docSelection.type == CONTROL) {
|
3365
|
-
var controlRange = this.docSelection.createRange();
|
3366
|
-
var rangeElement = getSingleElementFromRange(range);
|
3367
|
-
|
3368
|
-
// Create a new ControlRange containing all the elements in the selected ControlRange minus the
|
3369
|
-
// element contained by the supplied range
|
3370
|
-
var doc = getDocument(controlRange.item(0));
|
3371
|
-
var newControlRange = getBody(doc).createControlRange();
|
3372
|
-
var el, removed = false;
|
3373
|
-
for (var i = 0, len = controlRange.length; i < len; ++i) {
|
3374
|
-
el = controlRange.item(i);
|
3375
|
-
if (el !== rangeElement || removed) {
|
3376
|
-
newControlRange.add(controlRange.item(i));
|
3377
|
-
} else {
|
3378
|
-
removed = true;
|
3379
|
-
}
|
3380
|
-
}
|
3381
|
-
newControlRange.select();
|
3382
|
-
|
3383
|
-
// Update the wrapped selection based on what's now in the native selection
|
3384
|
-
updateControlSelection(this);
|
3385
|
-
} else {
|
3386
|
-
removeRangeManually(this, range);
|
3387
|
-
}
|
3388
|
-
};
|
3389
|
-
} else {
|
3390
|
-
selProto.removeRange = function(range) {
|
3391
|
-
removeRangeManually(this, range);
|
3392
|
-
};
|
3393
|
-
}
|
3394
|
-
|
3395
|
-
// Detecting if a selection is backward
|
3396
|
-
var selectionIsBackward;
|
3397
|
-
if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
|
3398
|
-
selectionIsBackward = function(sel) {
|
3399
|
-
var backward = false;
|
3400
|
-
if (sel.anchorNode) {
|
3401
|
-
backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
|
3402
|
-
}
|
3403
|
-
return backward;
|
3404
|
-
};
|
3405
|
-
|
3406
|
-
selProto.isBackward = function() {
|
3407
|
-
return selectionIsBackward(this);
|
3408
|
-
};
|
3409
|
-
} else {
|
3410
|
-
selectionIsBackward = selProto.isBackward = function() {
|
3411
|
-
return false;
|
3412
|
-
};
|
3413
|
-
}
|
3414
|
-
|
3415
|
-
// Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
|
3416
|
-
selProto.isBackwards = selProto.isBackward;
|
3417
|
-
|
3418
|
-
// Selection stringifier
|
3419
|
-
// This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
|
3420
|
-
// The current spec does not yet define this method.
|
3421
|
-
selProto.toString = function() {
|
3422
|
-
var rangeTexts = [];
|
3423
|
-
for (var i = 0, len = this.rangeCount; i < len; ++i) {
|
3424
|
-
rangeTexts[i] = "" + this._ranges[i];
|
3425
|
-
}
|
3426
|
-
return rangeTexts.join("");
|
3427
|
-
};
|
3428
|
-
|
3429
|
-
function assertNodeInSameDocument(sel, node) {
|
3430
|
-
if (sel.win.document != getDocument(node)) {
|
3431
|
-
throw new DOMException("WRONG_DOCUMENT_ERR");
|
3432
|
-
}
|
3433
|
-
}
|
3434
|
-
|
3435
|
-
// No current browser conforms fully to the spec for this method, so Rangy's own method is always used
|
3436
|
-
selProto.collapse = function(node, offset) {
|
3437
|
-
assertNodeInSameDocument(this, node);
|
3438
|
-
var range = api.createRange(node);
|
3439
|
-
range.collapseToPoint(node, offset);
|
3440
|
-
this.setSingleRange(range);
|
3441
|
-
this.isCollapsed = true;
|
3442
|
-
};
|
3443
|
-
|
3444
|
-
selProto.collapseToStart = function() {
|
3445
|
-
if (this.rangeCount) {
|
3446
|
-
var range = this._ranges[0];
|
3447
|
-
this.collapse(range.startContainer, range.startOffset);
|
3448
|
-
} else {
|
3449
|
-
throw new DOMException("INVALID_STATE_ERR");
|
3450
|
-
}
|
3451
|
-
};
|
3452
|
-
|
3453
|
-
selProto.collapseToEnd = function() {
|
3454
|
-
if (this.rangeCount) {
|
3455
|
-
var range = this._ranges[this.rangeCount - 1];
|
3456
|
-
this.collapse(range.endContainer, range.endOffset);
|
3457
|
-
} else {
|
3458
|
-
throw new DOMException("INVALID_STATE_ERR");
|
3459
|
-
}
|
3460
|
-
};
|
3461
|
-
|
3462
|
-
// The spec is very specific on how selectAllChildren should be implemented so the native implementation is
|
3463
|
-
// never used by Rangy.
|
3464
|
-
selProto.selectAllChildren = function(node) {
|
3465
|
-
assertNodeInSameDocument(this, node);
|
3466
|
-
var range = api.createRange(node);
|
3467
|
-
range.selectNodeContents(node);
|
3468
|
-
this.removeAllRanges();
|
3469
|
-
this.addRange(range);
|
3470
|
-
};
|
3471
|
-
|
3472
|
-
selProto.deleteFromDocument = function() {
|
3473
|
-
// Sepcial behaviour required for Control selections
|
3474
|
-
if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
|
3475
|
-
var controlRange = this.docSelection.createRange();
|
3476
|
-
var element;
|
3477
|
-
while (controlRange.length) {
|
3478
|
-
element = controlRange.item(0);
|
3479
|
-
controlRange.remove(element);
|
3480
|
-
element.parentNode.removeChild(element);
|
3481
|
-
}
|
3482
|
-
this.refresh();
|
3483
|
-
} else if (this.rangeCount) {
|
3484
|
-
var ranges = this.getAllRanges();
|
3485
|
-
if (ranges.length) {
|
3486
|
-
this.removeAllRanges();
|
3487
|
-
for (var i = 0, len = ranges.length; i < len; ++i) {
|
3488
|
-
ranges[i].deleteContents();
|
3489
|
-
}
|
3490
|
-
// The spec says nothing about what the selection should contain after calling deleteContents on each
|
3491
|
-
// range. Firefox moves the selection to where the final selected range was, so we emulate that
|
3492
|
-
this.addRange(ranges[len - 1]);
|
3493
|
-
}
|
3494
|
-
}
|
3495
|
-
};
|
3496
|
-
|
3497
|
-
// The following are non-standard extensions
|
3498
|
-
selProto.eachRange = function(func, returnValue) {
|
3499
|
-
for (var i = 0, len = this._ranges.length; i < len; ++i) {
|
3500
|
-
if ( func( this.getRangeAt(i) ) ) {
|
3501
|
-
return returnValue;
|
3502
|
-
}
|
3503
|
-
}
|
3504
|
-
};
|
3505
|
-
|
3506
|
-
selProto.getAllRanges = function() {
|
3507
|
-
var ranges = [];
|
3508
|
-
this.eachRange(function(range) {
|
3509
|
-
ranges.push(range);
|
3510
|
-
});
|
3511
|
-
return ranges;
|
3512
|
-
};
|
3513
|
-
|
3514
|
-
selProto.setSingleRange = function(range, direction) {
|
3515
|
-
this.removeAllRanges();
|
3516
|
-
this.addRange(range, direction);
|
3517
|
-
};
|
3518
|
-
|
3519
|
-
selProto.callMethodOnEachRange = function(methodName, params) {
|
3520
|
-
var results = [];
|
3521
|
-
this.eachRange( function(range) {
|
3522
|
-
results.push( range[methodName].apply(range, params) );
|
3523
|
-
} );
|
3524
|
-
return results;
|
3525
|
-
};
|
3526
|
-
|
3527
|
-
function createStartOrEndSetter(isStart) {
|
3528
|
-
return function(node, offset) {
|
3529
|
-
var range;
|
3530
|
-
if (this.rangeCount) {
|
3531
|
-
range = this.getRangeAt(0);
|
3532
|
-
range["set" + (isStart ? "Start" : "End")](node, offset);
|
3533
|
-
} else {
|
3534
|
-
range = api.createRange(this.win.document);
|
3535
|
-
range.setStartAndEnd(node, offset);
|
3536
|
-
}
|
3537
|
-
this.setSingleRange(range, this.isBackward());
|
3538
|
-
};
|
3539
|
-
}
|
3540
|
-
|
3541
|
-
selProto.setStart = createStartOrEndSetter(true);
|
3542
|
-
selProto.setEnd = createStartOrEndSetter(false);
|
3543
|
-
|
3544
|
-
// Add cheeky select() method to Range prototype
|
3545
|
-
api.rangePrototype.select = function(direction) {
|
3546
|
-
getSelection( this.getDocument() ).setSingleRange(this, direction);
|
3547
|
-
};
|
3548
|
-
|
3549
|
-
selProto.changeEachRange = function(func) {
|
3550
|
-
var ranges = [];
|
3551
|
-
var backward = this.isBackward();
|
3552
|
-
|
3553
|
-
this.eachRange(function(range) {
|
3554
|
-
func(range);
|
3555
|
-
ranges.push(range);
|
3556
|
-
});
|
3557
|
-
|
3558
|
-
this.removeAllRanges();
|
3559
|
-
if (backward && ranges.length == 1) {
|
3560
|
-
this.addRange(ranges[0], "backward");
|
3561
|
-
} else {
|
3562
|
-
this.setRanges(ranges);
|
3563
|
-
}
|
3564
|
-
};
|
3565
|
-
|
3566
|
-
selProto.containsNode = function(node, allowPartial) {
|
3567
|
-
return this.eachRange( function(range) {
|
3568
|
-
return range.containsNode(node, allowPartial);
|
3569
|
-
}, true );
|
3570
|
-
};
|
3571
|
-
|
3572
|
-
selProto.getBookmark = function(containerNode) {
|
3573
|
-
return {
|
3574
|
-
backward: this.isBackward(),
|
3575
|
-
rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
|
3576
|
-
};
|
3577
|
-
};
|
3578
|
-
|
3579
|
-
selProto.moveToBookmark = function(bookmark) {
|
3580
|
-
var selRanges = [];
|
3581
|
-
for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
|
3582
|
-
range = api.createRange(this.win);
|
3583
|
-
range.moveToBookmark(rangeBookmark);
|
3584
|
-
selRanges.push(range);
|
3585
|
-
}
|
3586
|
-
if (bookmark.backward) {
|
3587
|
-
this.setSingleRange(selRanges[0], "backward");
|
3588
|
-
} else {
|
3589
|
-
this.setRanges(selRanges);
|
3590
|
-
}
|
3591
|
-
};
|
3592
|
-
|
3593
|
-
selProto.toHtml = function() {
|
3594
|
-
return this.callMethodOnEachRange("toHtml").join("");
|
3595
|
-
};
|
3596
|
-
|
3597
|
-
function inspect(sel) {
|
3598
|
-
var rangeInspects = [];
|
3599
|
-
var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
|
3600
|
-
var focus = new DomPosition(sel.focusNode, sel.focusOffset);
|
3601
|
-
var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
|
3602
|
-
|
3603
|
-
if (typeof sel.rangeCount != "undefined") {
|
3604
|
-
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
|
3605
|
-
rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
|
3606
|
-
}
|
3607
|
-
}
|
3608
|
-
return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
|
3609
|
-
")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
|
3610
|
-
}
|
3611
|
-
|
3612
|
-
selProto.getName = function() {
|
3613
|
-
return "WrappedSelection";
|
3614
|
-
};
|
3615
|
-
|
3616
|
-
selProto.inspect = function() {
|
3617
|
-
return inspect(this);
|
3618
|
-
};
|
3619
|
-
|
3620
|
-
selProto.detach = function() {
|
3621
|
-
findCachedSelection(this.win, "delete");
|
3622
|
-
deleteProperties(this);
|
3623
|
-
};
|
3624
|
-
|
3625
|
-
WrappedSelection.detachAll = function() {
|
3626
|
-
findCachedSelection(null, "deleteAll");
|
3627
|
-
};
|
3628
|
-
|
3629
|
-
WrappedSelection.inspect = inspect;
|
3630
|
-
WrappedSelection.isDirectionBackward = isDirectionBackward;
|
3631
|
-
|
3632
|
-
api.Selection = WrappedSelection;
|
3633
|
-
|
3634
|
-
api.selectionPrototype = selProto;
|
3635
|
-
|
3636
|
-
api.addCreateMissingNativeApiListener(function(win) {
|
3637
|
-
if (typeof win.getSelection == "undefined") {
|
3638
|
-
win.getSelection = function() {
|
3639
|
-
return getSelection(win);
|
3640
|
-
};
|
3641
|
-
}
|
3642
|
-
win = null;
|
3643
|
-
});
|
3644
|
-
});
|
10
|
+
(function(a){function j(a,b){var e=typeof a[b];return e==d||e==c&&!!a[b]||e=="unknown"}function k(a,b){return typeof a[b]==c&&!!a[b]}function l(a,b){return typeof a[b]!=e}function m(a){return function(b,c){var d=c.length;while(d--)if(!a(b,c[d]))return!1;return!0}}function q(a){return a&&n(a,i)&&p(a,h)}function r(a){return k(a,"body")?a.body:a.getElementsByTagName("body")[0]}function u(a){k(window,"console")&&j(window.console,"log")&&window.console.log(a)}function v(a,b){b?window.alert(a):u(a)}function w(a){t.initialized=!0,t.supported=!1,v("Rangy is not supported on this page in your browser. Reason: "+a,t.config.alertOnFail)}function x(a){v("Rangy warning: "+a,t.config.alertOnWarn)}function A(a){return a.message||a.description||String(a)}function B(){if(t.initialized)return;var a,b=!1,c=!1;j(document,"createRange")&&(a=document.createRange(),n(a,g)&&p(a,f)&&(b=!0),a.detach());var d=r(document);if(!d||d.nodeName.toLowerCase()!="body"){w("No body element found");return}d&&j(d,"createTextRange")&&(a=d.createTextRange(),q(a)&&(c=!0));if(!b&&!c){w("Neither Range nor TextRange are available");return}t.initialized=!0,t.features={implementsDomRange:b,implementsTextRange:c};var e,h;for(var i in s)(e=s[i])instanceof E&&e.init(e,t);for(var k=0,l=z.length;k<l;++k)try{z[k](t)}catch(m){h="Rangy init listener threw an exception. Continuing. Detail: "+A(m),u(h)}}function D(a){a=a||window,B();for(var b=0,c=C.length;b<c;++b)C[b](a)}function E(a,b,c){this.name=a,this.dependencies=b,this.initialized=!1,this.supported=!1,this.initializer=c}function F(a,b,c,d){var e=new E(b,c,function(a){if(!a.initialized){a.initialized=!0;try{d(t,a),a.supported=!0}catch(c){var e="Module '"+b+"' failed to load: "+A(c);u(e)}}});s[b]=e}function G(){}function H(){}var b=typeof a.define=="function"&&a.define.amd,c="object",d="function",e="undefined",f=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],g=["setStart","setStartBefore","setStartAfter","setEnd","setEndBefore","setEndAfter","collapse","selectNode","selectNodeContents","compareBoundaryPoints","deleteContents","extractContents","cloneContents","insertNode","surroundContents","cloneRange","toString","detach"],h=["boundingHeight","boundingLeft","boundingTop","boundingWidth","htmlText","text"],i=["collapse","compareEndPoints","duplicate","moveToElementText","parentElement","select","setEndPoint","getBoundingClientRect"],n=m(j),o=m(k),p=m(l),s={},t={version:"1.3alpha.780M",initialized:!1,supported:!0,util:{isHostMethod:j,isHostObject:k,isHostProperty:l,areHostMethods:n,areHostObjects:o,areHostProperties:p,isTextRange:q,getBody:r},features:{},modules:s,config:{alertOnFail:!0,alertOnWarn:!1,preferTextRange:!1}};t.fail=w,t.warn=x,{}.hasOwnProperty?t.util.extend=function(a,b,c){var d,e;for(var f in b)b.hasOwnProperty(f)&&(d=a[f],e=b[f],c&&d!==null&&typeof d=="object"&&e!==null&&typeof e=="object"&&t.util.extend(d,e,!0),a[f]=e);return a}:w("hasOwnProperty not supported"),function(){var a=document.createElement("div");a.appendChild(document.createElement("span"));var b=[].slice,c;try{b.call(a.childNodes,0)[0].nodeType==1&&(c=function(a){return b.call(a,0)})}catch(d){}c||(c=function(a){var b=[];for(var c=0,d=a.length;c<d;++c)b[c]=a[c];return b}),t.util.toArray=c}();var y;j(document,"addEventListener")?y=function(a,b,c){a.addEventListener(b,c,!1)}:j(document,"attachEvent")?y=function(a,b,c){a.attachEvent("on"+b,c)}:w("Document does not have required addEventListener or attachEvent method"),t.util.addListener=y;var z=[];t.init=B,t.addInitListener=function(a){t.initialized?a(t):z.push(a)};var C=[];t.addCreateMissingNativeApiListener=function(a){C.push(a)},t.createMissingNativeApi=D,E.prototype={init:function(a){var b=this.dependencies||[];for(var c=0,d=b.length,e,f;c<d;++c){f=b[c],e=s[f];if(!e||!(e instanceof E))throw new Error("required module '"+f+"' not found");e.init();if(!e.supported)throw new Error("required module '"+f+"' not supported")}this.initializer(this)},fail:function(a){throw this.initialized=!0,this.supported=!1,new Error("Module '"+this.name+"' failed to load: "+a)},warn:function(a){t.warn("Module "+this.name+": "+a)},deprecationNotice:function(a,b){t.warn("DEPRECATED: "+a+" in module "+this.name+"is deprecated. Please use "+b+" instead")},createError:function(a){return new Error("Error in Rangy "+this.name+" module: "+a)}},t.createModule=function(a){var b,c;arguments.length==2?(b=arguments[1],c=[]):(b=arguments[2],c=arguments[1]),F(!1,a,c,b)},t.createCoreModule=function(a,b,c){F(!0,a,b,c)},t.RangePrototype=G,t.rangePrototype=new G,t.selectionPrototype=new H;var I=!1,J=function(a){I||(I=!0,t.initialized||B())};if(typeof window==e){w("No window found");return}if(typeof document==e){w("No document found");return}j(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",J,!1),y(window,"load",J),b&&a.define(function(){return t.amd=!0,t}),a.rangy=t})(this),rangy.createCoreModule("DomUtil",[],function(a,b){function h(a){var b;return typeof a.namespaceURI==c||(b=a.namespaceURI)===null||b=="http://www.w3.org/1999/xhtml"}function i(a){var b=a.parentNode;return b.nodeType==1?b:null}function j(a){var b=0;while(a=a.previousSibling)++b;return b}function k(a){switch(a.nodeType){case 7:case 10:return 0;case 3:case 8:return a.length;default:return a.childNodes.length}}function l(a,b){var c=[],d;for(d=a;d;d=d.parentNode)c.push(d);for(d=b;d;d=d.parentNode)if(g(c,d))return d;return null}function m(a,b,c){var d=c?b:b.parentNode;while(d){if(d===a)return!0;d=d.parentNode}return!1}function n(a,b){return m(a,b,!0)}function o(a,b,c){var d,e=c?a:a.parentNode;while(e){d=e.parentNode;if(d===b)return e;e=d}return null}function p(a){var b=a.nodeType;return b==3||b==4||b==8}function q(a){if(!a)return!1;var b=a.nodeType;return b==3||b==8}function r(a,b){var c=b.nextSibling,d=b.parentNode;return c?d.insertBefore(a,c):d.appendChild(a),a}function s(a,b,c){var d=a.cloneNode(!1);d.deleteData(0,b),a.deleteData(b,a.length-b),r(d,a);if(c)for(var e=0,f;f=c[e++];)f.node==a&&f.offset>b?(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<e?-1:1;if(f=o(d,a,!0))return c<=j(f)?-1:1;if(f=o(a,d,!0))return j(f)<e?-1:1;g=l(a,d),h=a===g?g:o(a,g,!0),i=d===g?g:o(d,g,!0);if(h===i)throw b.createError("comparePoints got to case 4 and childA and childB are the same!");k=g.firstChild;while(k){if(k===h)return-1;if(k===i)return 1;k=k.nextSibling}}function C(a){try{return a.parentNode,!1}catch(b){return!0}}function D(a){if(!a)return"[No node]";if(B&&C(a))return"[Broken node]";if(p(a))return'"'+a.data+'"';if(a.nodeType==1){var b=a.id?' id="'+a.id+'"':"";return"<"+a.nodeName+b+">["+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="<br>",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 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&&f<e.length&&l(e,f,b),g(c)&&d>0&&d<c.length&&(c=l(c,d,b),i?(f-=d,e=c):e==c.parentNode&&f>=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(b<a.childNodes.length){var l=a.childNodes[b];l&&g(l)&&i(l)}}else a=d,b=e;c(this,a,b,d,e)},collapseToPoint:function(a,b){P(this),O(a,!0),R(a,b),this.setStartAndEnd(a,b)}}),mb(b)}function pb(a){a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset,a.commonAncestorContainer=a.collapsed?a.startContainer:c.getCommonAncestor(a.startContainer,a.endContainer)}function qb(a,b,d,e,f){a.startContainer=b,a.startOffset=d,a.endContainer=e,a.endOffset=f,a.document=c.getDocument(b),pb(a)}function rb(a){P(a),a.startContainer=a.startOffset=a.endContainer=a.endOffset=a.document=null,a.collapsed=a.commonAncestorContainer=null}function sb(a){this.startContainer=a,this.startOffset=0,this.endContainer=a,this.endOffset=0,this.document=a,pb(this)}var c=a.dom,d=a.util,e=c.DomPosition,f=a.DOMException,g=c.isCharacterDataNode,h=c.getNodeIndex,i=c.isOrIsAncestorOf,j=c.getDocument,k=c.comparePoints,l=c.splitDataNode,m=c.getClosestAncestorIn,n=c.getNodeLength,o=c.arrayContains,p=c.getRootContainer,q=a.features.crashyTextNodes;D.prototype={_current:null,_next:null,_first:null,_last:null,isSingleCharacterDataNode:!1,reset:function(){this._current=null,this._next=this._first},hasNext:function(){return!!this._next},next:function(){var a=this._current=this._next;return a&&(this._next=a!==this._last?a.nextSibling:null,g(a)&&this.clonePartiallySelectedTextNodes&&(a===this.ec&&(a=a.cloneNode(!0)).deleteData(this.eo,a.length-this.eo),this._current===this.sc&&(a=a.cloneNode(!0)).deleteData(0,this.so))),a},remove:function(){var a=this._current,b,c;!g(a)||a!==this.sc&&a!==this.ec?a.parentNode&&a.parentNode.removeChild(a):(b=a===this.sc?this.so:0,c=a===this.ec?this.eo:a.length,b!=c&&a.deleteData(b,c-b))},isPartiallySelectedSubtree:function(){var a=this._current;return r(a,this.range)},getSubtreeIterator:function(){var a;if(this.isSingleCharacterDataNode)a=this.range.cloneRange(),a.collapse(!1);else{a=new sb(s(this.range));var b=this._current,c=b,d=0,e=b,f=n(b);i(b,this.sc)&&(c=this.sc,d=this.so),i(b,this.ec)&&(e=this.ec,f=this.eo),qb(a,c,d,e,f)}return new D(a,this.clonePartiallySelectedTextNodes)},detach:function(a){a&&this.range.detach(),this.range=this._current=this._next=this._first=this._last=this.sc=this.so=this.ec=this.eo=null}},E.prototype={BAD_BOUNDARYPOINTS_ERR:1,INVALID_NODE_TYPE_ERR:2},E.prototype.toString=function(){return this.message};var F=[1,3,4,5,7,8,10],G=[2,9,11],H=[5,6,10,12],I=[1,3,4,5,7,8,10,11],J=[1,3,4,5,7,8],L=K([9,11]),M=K(H),N=K([6,10,12]),Z=document.createElement("style"),$=!1;try{Z.innerHTML="<b>x</b>",$=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=f<j.length?j[f]:null,d=a.node),h=g.createElement("span"),h.innerHTML="&#feff;",c?d.insertBefore(h,c):d.appendChild(h),l.moveToElementText(h),l.collapse(!b),d.removeChild(h),m&&l[b?"moveStart":"moveEnd"]("character",f),l};d=function(a){this.textRange=a,this.refresh()},d.prototype=new h(document),d.prototype.refresh=function(){var a,b,c,d=l(this.textRange);m(this.textRange)?b=a=n(this.textRange,d,!0,!0).boundaryPosition:(c=n(this.textRange,d,!0,!1),a=c.boundaryPosition,b=n(this.textRange,d,!1,!1,c.nodeInfo).boundaryPosition),this.setStart(a.node,a.offset),this.setEnd(b.node,b.offset)},d.prototype.getName=function(){return"WrappedTextRange"},h.copyComparisonConstants(d),d.rangeToTextRange=function(a){if(a.collapsed)return o(new g(a.startContainer,a.startOffset),!0);var b=o(new g(a.startContainer,a.startOffset),!0),c=o(new g(a.endContainer,a.endOffset),!1),d=i(h.getRangeDocument(a)).createTextRange();return d.setEndPoint("StartToStart",b),d.setEndPoint("EndToEnd",c),d},a.WrappedTextRange=d;if(!a.features.implementsDomRange||a.config.preferTextRange){var p=function(){return this}();typeof p.Range=="undefined"&&(p.Range=d),a.createNativeRange=function(a){return a=j(a,b,"createNativeRange"),i(a).createTextRange()},a.WrappedRange=d}}a.createRange=function(c){return c=j(c,b,"createRange"),new a.WrappedRange(a.createNativeRange(c))},a.createRangyRange=function(a){return a=j(a,b,"createRangyRange"),new h(a)},a.createIframeRange=function(c){return b.deprecationNotice("createIframeRange()","createRange(iframeEl)"),a.createRange(c)},a.createIframeRangyRange=function(c){return b.deprecationNotice("createIframeRangyRange()","createRangyRange(iframeEl)"),a.createRangyRange(c)},a.addCreateMissingNativeApiListener(function(b){var c=b.document;typeof c.createRange=="undefined"&&(c.createRange=function(){return a.createRange(c)}),c=b=null})}),rangy.createCoreModule("WrappedSelection",["DomRange","WrappedRange"],function(a,b){function r(a){return typeof a=="string"?a=="backward":!!a}function s(a,c){if(!a)return window;if(d.isWindow(a))return a;if(a instanceof T)return a.win;var e=d.getContentDocument(a,b,c);return d.getWindow(e)}function t(a){return s(a,"getWinSelection").getSelection()}function u(a){return s(a,"getDocSelection").document.selection}function I(a,b,c){var d=c?"end":"start",e=c?"start":"end";a.anchorNode=b[d+"Container"],a.anchorOffset=b[d+"Offset"],a.focusNode=b[e+"Container"],a.focusOffset=b[e+"Offset"]}function J(a){var b=a.nativeSelection;a.anchorNode=b.anchorNode,a.anchorOffset=b.anchorOffset,a.focusNode=b.focusNode,a.focusOffset=b.focusOffset}function K(a){a.anchorNode=a.focusNode=null,a.anchorOffset=a.focusOffset=0,a.rangeCount=0,a.isCollapsed=!0,a._ranges.length=0}function L(b){var c;return b instanceof g?(c=a.createNativeRange(b.getDocument()),c.setEnd(b.endContainer,b.endOffset),c.setStart(b.startContainer,b.startOffset)):b instanceof h?c=b.nativeRange:m.implementsDomRange&&b instanceof d.getWindow(b.startContainer).Range&&(c=b),c}function M(a){if(!a.length||a[0].nodeType!=1)return!1;for(var b=1,c=a.length;b<c;++b)if(!d.isAncestorOf(a[0],a[b]))return!1;return!0}function N(a){var c=a.getNodes();if(!M(c))throw b.createError("getSingleElementFromRange: range "+a.inspect()+" did not consist of a single element");return c[0]}function O(a){return!!a&&typeof a.text!="undefined"}function P(a,b){var c=new h(b);a._ranges=[c],I(a,c,!1),a.rangeCount=1,a.isCollapsed=c.collapsed}function Q(b){b._ranges.length=0;if(b.docSelection.type=="None")K(b);else{var c=b.docSelection.createRange();if(O(c))P(b,c);else{b.rangeCount=c.length;var d,e=o(c.item(0));for(var f=0;f<b.rangeCount;++f)d=a.createRange(e),d.selectNode(c.item(f)),b._ranges.push(d);b.isCollapsed=b.rangeCount==1&&b._ranges[0].collapsed,I(b,b._ranges[b.rangeCount-1],!1)}}}function R(a,c){var d=a.docSelection.createRange(),e=N(c),f=o(d.item(0)),g=p(f).createControlRange();for(var h=0,i=d.length;h<i;++h)g.add(d.item(h));try{g.add(e)}catch(j){throw b.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)")}g.select(),Q(a)}function T(a,b,c){this.nativeSelection=a,this.docSelection=b,this._ranges=[],this.win=c,this.refresh()}function U(a){a.win=a.anchorNode=a.focusNode=a._ranges=null,a.rangeCount=a.anchorOffset=a.focusOffset=0,a.detached=!0}function W(a,b){var c=V.length,d,e;while(c--){d=V[c],e=d.selection;if(b=="deleteAll")U(e);else if(d.win==a)return b=="delete"?(V.splice(c,1),!0):e}return b=="deleteAll"&&(V.length=0),null}function Z(a,c){var d=o(c[0].startContainer),e=p(d).createControlRange();for(var f=0,g,h=c.length;f<h;++f){g=N(c[f]);try{e.add(g)}catch(i){throw b.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)")}}e.select(),Q(a)}function cb(a,b){if(a.win.document!=o(b))throw new i("WRONG_DOCUMENT_ERR")}function db(b){return function(c,d){var e;this.rangeCount?(e=this.getRangeAt(0),e["set"+(b?"Start":"End")](c,d)):(e=a.createRange(this.win.document),e.setStartAndEnd(c,d)),this.setSingleRange(e,this.isBackward())}}function eb(a){var b=[],c=new j(a.anchorNode,a.anchorOffset),d=new j(a.focusNode,a.focusOffset),e=typeof a.getName=="function"?a.getName():"Selection";if(typeof a.rangeCount!="undefined")for(var f=0,h=a.rangeCount;f<h;++f)b[f]=g.inspect(a.getRangeAt(f));return"["+e+"(Ranges: "+b.join(", ")+")(anchor: "+c.inspect()+", focus: "+d.inspect()+"]"}a.config.checkSelectionRanges=!0;var c="boolean",d=a.dom,e=a.util,f=e.isHostMethod,g=a.DomRange,h=a.WrappedRange,i=a.DOMException,j=d.DomPosition,k,l,m=a.features,n="Control",o=d.getDocument,p=d.getBody,q=g.rangesEqual,v=f(window,"getSelection"),w=e.isHostObject(document,"selection");m.implementsWinGetSelection=v,m.implementsDocSelection=w;var x=w&&(!v||a.config.preferTextRange);x?(k=u,a.isSelectionValid=function(a){var b=s(a,"isSelectionValid").document,c=b.selection;return c.type!="None"||o(c.createRange().parentElement())==b}):v?(k=t,a.isSelectionValid=function(){return!0}):b.fail("Neither document.selection or window.getSelection() detected."),a.getNativeSelection=k;var y=k(),z=a.createNativeRange(document),A=p(document),B=e.areHostProperties(y,["anchorNode","focusNode","anchorOffset","focusOffset"]);m.selectionHasAnchorAndFocus=B;var C=f(y,"extend");m.selectionHasExtend=C;var D=typeof y.rangeCount=="number";m.selectionHasRangeCount=D;var E=!1,F=!0;e.areHostMethods(y,["addRange","getRangeAt","removeAllRanges"])&&typeof y.rangeCount=="number"&&m.implementsDomRange&&function(){var a=window.getSelection();if(a){var b=p(document),c=b.appendChild(document.createElement("div"));c.contentEditable="false";var d=c.appendChild(document.createTextNode("\u00a0\u00a0\u00a0")),e=document.createRange();e.setStart(d,1),e.collapse(!0),a.addRange(e),F=a.rangeCount==1,a.removeAllRanges();var f=e.cloneRange();e.setStart(d,0),f.setEnd(d,3),f.setStart(d,2),a.addRange(e),a.addRange(f),E=a.rangeCount==2,b.removeChild(c),a.removeAllRanges(),e.detach(),f.detach()}}(),m.selectionSupportsMultipleRanges=E,m.collapsedNonEditableSelectionsSupported=F;var G=!1,H;A&&f(A,"createControlRange")&&(H=A.createControlRange(),e.areHostProperties(H,["item","add"])&&(G=!0)),m.implementsControlRange=G,B?l=function(a){return a.anchorNode===a.focusNode&&a.anchorOffset===a.focusOffset}:l=function(a){return a.rangeCount?a.getRangeAt(a.rangeCount-1).collapsed:!1};var S;f(y,"getRangeAt")?S=function(a,b){try{return a.getRangeAt(b)}catch(c){return null}}:B&&(S=function(b){var c=o(b.anchorNode),d=a.createRange(c);return d.setStartAndEnd(b.anchorNode,b.anchorOffset,b.focusNode,b.focusOffset),d.collapsed!==this.isCollapsed&&d.setStartAndEnd(b.focusNode,b.focusOffset,b.anchorNode,b.anchorOffset),d}),T.prototype=a.selectionPrototype;var V=[],X=function(a){if(a&&a instanceof T)return a.refresh(),a;a=s(a,"getNativeSelection");var b=W(a),c=k(a),d=w?u(a):null;return b?(b.nativeSelection=c,b.docSelection=d,b.refresh()):(b=new T(c,d,a),V.push({win:a,selection:b})),b};a.getSelection=X,a.getIframeSelection=function(c){return b.deprecationNotice("getIframeSelection()","getSelection(iframeEl)"),a.getSelection(d.getIframeWindow(c))};var Y=T.prototype;if(!x&&B&&e.areHostMethods(y,["removeAllRanges","addRange"])){Y.removeAllRanges=function(){this.nativeSelection.removeAllRanges(),K(this)};var $=function(b,c){var d=g.getRangeDocument(c),e=a.createRange(d);e.collapseToPoint(c.endContainer,c.endOffset),b.nativeSelection.addRange(L(e)),b.nativeSelection.extend(c.startContainer,c.startOffset),b.refresh()};D?Y.addRange=function(b,c){if(G&&w&&this.docSelection.type==n)R(this,b);else if(r(c)&&C)$(this,b);else{var d;E?d=this.rangeCount:(this.removeAllRanges(),d=0),this.nativeSelection.addRange(L(b).cloneRange()),this.rangeCount=this.nativeSelection.rangeCount;if(this.rangeCount==d+1){if(a.config.checkSelectionRanges){var e=S(this.nativeSelection,this.rangeCount-1);e&&!q(e,b)&&(b=new h(e))}this._ranges[this.rangeCount-1]=b,I(this,b,bb(this.nativeSelection)),this.isCollapsed=l(this)}else this.refresh()}}:Y.addRange=function(a,b){r(b)&&C?$(this,a):(this.nativeSelection.addRange(L(a)),this.refresh())},Y.setRanges=function(a){if(G&&a.length>1)Z(this,a);else{this.removeAllRanges();for(var b=0,c=a.length;b<c;++b)this.addRange(a[b])}}}else{if(!(f(y,"empty")&&f(z,"select")&&G&&x))return b.fail("No means of selecting a Range or TextRange was found"),!1;Y.removeAllRanges=function(){try{this.docSelection.empty();if(this.docSelection.type!="None"){var a;if(this.anchorNode)a=o(this.anchorNode);else if(this.docSelection.type==n){var b=this.docSelection.createRange();b.length&&(a=o(b.item(0)))}if(a){var c=p(a).createTextRange();c.select(),this.docSelection.empty()}}}catch(d){}K(this)},Y.addRange=function(b){this.docSelection.type==n?R(this,b):(a.WrappedTextRange.rangeToTextRange(b).select(),this._ranges[0]=b,this.rangeCount=1,this.isCollapsed=this._ranges[0].collapsed,I(this,b,!1))},Y.setRanges=function(a){this.removeAllRanges();var b=a.length;b>1?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;c<d;++c)b._ranges[c]=new a.WrappedRange(b.nativeSelection.getRangeAt(c));I(b,b._ranges[b.rangeCount-1],bb(b.nativeSelection)),b.isCollapsed=l(b)}else K(b)}};else{if(!B||typeof y.isCollapsed!=c||typeof z.collapsed!=c||!m.implementsDomRange)return b.fail("No means of obtaining a Range or TextRange from the user's selection was found"),!1;_=function(a){var b,c=a.nativeSelection;c.anchorNode?(b=S(c,0),a._ranges=[b],a.rangeCount=1,J(a),a.isCollapsed=l(a)):K(a)}}Y.refresh=function(a){var b=a?this._ranges.slice(0):null,c=this.anchorNode,d=this.anchorOffset;_(this);if(a){var e=b.length;if(e!=this._ranges.length)return!0;if(this.anchorNode!=c||this.anchorOffset!=d)return!0;while(e--)if(!q(b[e],this._ranges[e]))return!0;return!1}};var ab=function(a,b){var c=a.getAllRanges();a.removeAllRanges();for(var d=0,e=c.length;d<e;++d)q(b,c[d])||a.addRange(c[d]);a.rangeCount||K(a)};G?Y.removeRange=function(a){if(this.docSelection.type==n){var b=this.docSelection.createRange(),c=N(a),d=o(b.item(0)),e=p(d).createControlRange(),f,g=!1;for(var h=0,i=b.length;h<i;++h)f=b.item(h),f!==c||g?e.add(b.item(h)):g=!0;e.select(),Q(this)}else ab(this,a)}:Y.removeRange=function(a){ab(this,a)};var bb;!x&&B&&m.implementsDomRange?(bb=function(a){var b=!1;return a.anchorNode&&(b=d.comparePoints(a.anchorNode,a.anchorOffset,a.focusNode,a.focusOffset)==1),b},Y.isBackward=function(){return bb(this)}):bb=Y.isBackward=function(){return!1},Y.isBackwards=Y.isBackward,Y.toString=function(){var a=[];for(var b=0,c=this.rangeCount;b<c;++b)a[b]=""+this._ranges[b];return a.join("")},Y.collapse=function(b,c){cb(this,b);var d=a.createRange(b);d.collapseToPoint(b,c),this.setSingleRange(d),this.isCollapsed=!0},Y.collapseToStart=function(){if(!this.rangeCount)throw new i("INVALID_STATE_ERR");var a=this._ranges[0];this.collapse(a.startContainer,a.startOffset)},Y.collapseToEnd=function(){if(!this.rangeCount)throw new i("INVALID_STATE_ERR");var a=this._ranges[this.rangeCount-1];this.collapse(a.endContainer,a.endOffset)},Y.selectAllChildren=function(b){cb(this,b);var c=a.createRange(b);c.selectNodeContents(b),console.log("before",c.inspect()),this.setSingleRange(c),console.log("after",this._ranges[0].inspect())},Y.deleteFromDocument=function(){if(G&&w&&this.docSelection.type==n){var a=this.docSelection.createRange(),b;while(a.length)b=a.item(0),a.remove(b),b.parentNode.removeChild(b);this.refresh()}else if(this.rangeCount){var c=this.getAllRanges();if(c.length){this.removeAllRanges();for(var d=0,e=c.length;d<e;++d)c[d].deleteContents();this.addRange(c[e-1])}}},Y.eachRange=function(a,b){for(var c=0,d=this._ranges.length;c<d;++c)if(a(this.getRangeAt(c)))return b},Y.getAllRanges=function(){var a=[];return this.eachRange(function(b){a.push(b)}),a},Y.setSingleRange=function(a,b){this.removeAllRanges(),this.addRange(a,b)},Y.callMethodOnEachRange=function(a,b){var c=[];return this.eachRange(function(d){c.push(d[a].apply(d,b))}),c},Y.setStart=db(!0),Y.setEnd=db(!1),a.rangePrototype.select=function(a){X(this.getDocument()).setSingleRange(this,a)},Y.changeEachRange=function(a){var b=[],c=this.isBackward();this.eachRange(function(c){a(c),b.push(c)}),this.removeAllRanges(),c&&b.length==1?this.addRange(b[0],"backward"):this.setRanges(b)},Y.containsNode=function(a,b){return this.eachRange(function(c){return c.containsNode(a,b)},!0)},Y.getBookmark=function(a){return{backward:this.isBackward(),rangeBookmarks:this.callMethodOnEachRange("getBookmark",[a])}},Y.moveToBookmark=function(b){var c=[];for(var d=0,e,f;e=b.rangeBookmarks[d++];)f=a.createRange(this.win),f.moveToBookmark(e),c.push(f);b.backward?this.setSingleRange(c[0],"backward"):this.setRanges(c)},Y.toHtml=function(){return this.callMethodOnEachRange("toHtml").join("")},Y.getName=function(){return"WrappedSelection"},Y.inspect=function(){return eb(this)},Y.detach=function(){W(this.win,"delete"),U(this)},T.detachAll=function(){W(null,"deleteAll")},T.inspect=eb,T.isDirectionBackward=r,a.Selection=T,a.selectionPrototype=Y,a.addCreateMissingNativeApiListener(function(a){typeof a.getSelection=="undefined"&&(a.getSelection=function(){return X(a)}),a=null})})
|