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
@@ -9,927 +9,7 @@
|
|
9
9
|
*
|
10
10
|
* Copyright 2013, Tim Down
|
11
11
|
* Licensed under the MIT license.
|
12
|
-
* Version: 1.3alpha.
|
13
|
-
* Build date:
|
12
|
+
* Version: 1.3alpha.780M
|
13
|
+
* Build date: 17 May 2013
|
14
14
|
*/
|
15
|
-
rangy.createModule("CssClassApplier", function(api, module) {
|
16
|
-
api.requireModules( ["WrappedSelection", "WrappedRange"] );
|
17
|
-
|
18
|
-
var dom = api.dom;
|
19
|
-
var DomPosition = dom.DomPosition;
|
20
|
-
var contains = dom.arrayContains;
|
21
|
-
|
22
|
-
|
23
|
-
var defaultTagName = "span";
|
24
|
-
|
25
|
-
function trim(str) {
|
26
|
-
return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
|
27
|
-
}
|
28
|
-
|
29
|
-
function hasClass(el, cssClass) {
|
30
|
-
return el.className && new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)").test(el.className);
|
31
|
-
}
|
32
|
-
|
33
|
-
function addClass(el, cssClass) {
|
34
|
-
if (el.className) {
|
35
|
-
if (!hasClass(el, cssClass)) {
|
36
|
-
el.className += " " + cssClass;
|
37
|
-
}
|
38
|
-
} else {
|
39
|
-
el.className = cssClass;
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
var removeClass = (function() {
|
44
|
-
function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
|
45
|
-
return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
|
46
|
-
}
|
47
|
-
|
48
|
-
return function(el, cssClass) {
|
49
|
-
if (el.className) {
|
50
|
-
el.className = el.className.replace(new RegExp("(^|\\s)" + cssClass + "(\\s|$)"), replacer);
|
51
|
-
}
|
52
|
-
};
|
53
|
-
})();
|
54
|
-
|
55
|
-
function sortClassName(className) {
|
56
|
-
return className.split(/\s+/).sort().join(" ");
|
57
|
-
}
|
58
|
-
|
59
|
-
function getSortedClassName(el) {
|
60
|
-
return sortClassName(el.className);
|
61
|
-
}
|
62
|
-
|
63
|
-
function haveSameClasses(el1, el2) {
|
64
|
-
return getSortedClassName(el1) == getSortedClassName(el2);
|
65
|
-
}
|
66
|
-
|
67
|
-
function compareRanges(r1, r2) {
|
68
|
-
return r1.compareBoundaryPoints(r2.START_TO_START, r2);
|
69
|
-
}
|
70
|
-
|
71
|
-
function movePosition(position, oldParent, oldIndex, newParent, newIndex) {
|
72
|
-
var node = position.node, offset = position.offset;
|
73
|
-
|
74
|
-
var newNode = node, newOffset = offset;
|
75
|
-
|
76
|
-
if (node == newParent && offset > newIndex) {
|
77
|
-
newOffset++;
|
78
|
-
}
|
79
|
-
|
80
|
-
if (node == oldParent && (offset == oldIndex || offset == oldIndex + 1)) {
|
81
|
-
newNode = newParent;
|
82
|
-
newOffset += newIndex - oldIndex;
|
83
|
-
}
|
84
|
-
|
85
|
-
if (node == oldParent && offset > oldIndex + 1) {
|
86
|
-
newOffset--;
|
87
|
-
}
|
88
|
-
|
89
|
-
position.node = newNode;
|
90
|
-
position.offset = newOffset;
|
91
|
-
}
|
92
|
-
|
93
|
-
function movePreservingPositions(node, newParent, newIndex, positionsToPreserve) {
|
94
|
-
// For convenience, allow newIndex to be -1 to mean "insert at the end".
|
95
|
-
if (newIndex == -1) {
|
96
|
-
newIndex = newParent.childNodes.length;
|
97
|
-
}
|
98
|
-
|
99
|
-
var oldParent = node.parentNode;
|
100
|
-
var oldIndex = dom.getNodeIndex(node);
|
101
|
-
|
102
|
-
for (var i = 0, position; position = positionsToPreserve[i++]; ) {
|
103
|
-
movePosition(position, oldParent, oldIndex, newParent, newIndex);
|
104
|
-
}
|
105
|
-
|
106
|
-
// Now actually move the node.
|
107
|
-
if (newParent.childNodes.length == newIndex) {
|
108
|
-
newParent.appendChild(node);
|
109
|
-
} else {
|
110
|
-
newParent.insertBefore(node, newParent.childNodes[newIndex]);
|
111
|
-
}
|
112
|
-
}
|
113
|
-
|
114
|
-
function moveChildrenPreservingPositions(node, newParent, newIndex, removeNode, positionsToPreserve) {
|
115
|
-
var child, children = [];
|
116
|
-
while ( (child = node.firstChild) ) {
|
117
|
-
movePreservingPositions(child, newParent, newIndex++, positionsToPreserve);
|
118
|
-
children.push(child);
|
119
|
-
}
|
120
|
-
if (removeNode) {
|
121
|
-
node.parentNode.removeChild(node);
|
122
|
-
}
|
123
|
-
return children;
|
124
|
-
}
|
125
|
-
|
126
|
-
function replaceWithOwnChildrenPreservingPositions(element, positionsToPreserve) {
|
127
|
-
return moveChildrenPreservingPositions(element, element.parentNode, dom.getNodeIndex(element), true, positionsToPreserve);
|
128
|
-
}
|
129
|
-
|
130
|
-
function rangeSelectsAnyText(range, textNode) {
|
131
|
-
var textNodeRange = range.cloneRange();
|
132
|
-
textNodeRange.selectNodeContents(textNode);
|
133
|
-
|
134
|
-
var intersectionRange = textNodeRange.intersection(range);
|
135
|
-
var text = intersectionRange ? intersectionRange.toString() : "";
|
136
|
-
textNodeRange.detach();
|
137
|
-
|
138
|
-
return text != "";
|
139
|
-
}
|
140
|
-
|
141
|
-
function getEffectiveTextNodes(range) {
|
142
|
-
var nodes = range.getNodes([3]);
|
143
|
-
|
144
|
-
// Optimization as per issue 145
|
145
|
-
|
146
|
-
// Remove non-intersecting text nodes from the start of the range
|
147
|
-
var start = 0, node;
|
148
|
-
while ( (node = nodes[start]) && !rangeSelectsAnyText(range, node) ) {
|
149
|
-
++start;
|
150
|
-
}
|
151
|
-
|
152
|
-
// Remove non-intersecting text nodes from the start of the range
|
153
|
-
var end = nodes.length - 1;
|
154
|
-
while ( (node = nodes[end]) && !rangeSelectsAnyText(range, node) ) {
|
155
|
-
--end;
|
156
|
-
}
|
157
|
-
|
158
|
-
return nodes.slice(start, end + 1);
|
159
|
-
}
|
160
|
-
|
161
|
-
function elementsHaveSameNonClassAttributes(el1, el2) {
|
162
|
-
if (el1.attributes.length != el2.attributes.length) return false;
|
163
|
-
for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
|
164
|
-
attr1 = el1.attributes[i];
|
165
|
-
name = attr1.name;
|
166
|
-
if (name != "class") {
|
167
|
-
attr2 = el2.attributes.getNamedItem(name);
|
168
|
-
if (attr1.specified != attr2.specified) return false;
|
169
|
-
if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
|
170
|
-
}
|
171
|
-
}
|
172
|
-
return true;
|
173
|
-
}
|
174
|
-
|
175
|
-
function elementHasNonClassAttributes(el, exceptions) {
|
176
|
-
for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) {
|
177
|
-
attrName = el.attributes[i].name;
|
178
|
-
if ( !(exceptions && contains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") {
|
179
|
-
return true;
|
180
|
-
}
|
181
|
-
}
|
182
|
-
return false;
|
183
|
-
}
|
184
|
-
|
185
|
-
function elementHasProps(el, props) {
|
186
|
-
var propValue;
|
187
|
-
for (var p in props) {
|
188
|
-
if (props.hasOwnProperty(p)) {
|
189
|
-
propValue = props[p];
|
190
|
-
if (typeof propValue == "object") {
|
191
|
-
if (!elementHasProps(el[p], propValue)) {
|
192
|
-
return false;
|
193
|
-
}
|
194
|
-
} else if (el[p] !== propValue) {
|
195
|
-
return false;
|
196
|
-
}
|
197
|
-
}
|
198
|
-
}
|
199
|
-
return true;
|
200
|
-
}
|
201
|
-
|
202
|
-
var getComputedStyleProperty = dom.getComputedStyleProperty;
|
203
|
-
var isEditableElement;
|
204
|
-
|
205
|
-
(function() {
|
206
|
-
var testEl = document.createElement("div");
|
207
|
-
if (typeof testEl.isContentEditable == "boolean") {
|
208
|
-
isEditableElement = function(node) {
|
209
|
-
return node && node.nodeType == 1 && node.isContentEditable;
|
210
|
-
};
|
211
|
-
} else {
|
212
|
-
isEditableElement = function(node) {
|
213
|
-
if (!node || node.nodeType != 1 || node.contentEditable == "false") {
|
214
|
-
return false;
|
215
|
-
}
|
216
|
-
return node.contentEditable == "true" || isEditableElement(node.parentNode);
|
217
|
-
};
|
218
|
-
}
|
219
|
-
})();
|
220
|
-
|
221
|
-
function isEditingHost(node) {
|
222
|
-
var parent;
|
223
|
-
return node && node.nodeType == 1
|
224
|
-
&& (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on")
|
225
|
-
|| (isEditableElement(node) && !isEditableElement(node.parentNode)));
|
226
|
-
}
|
227
|
-
|
228
|
-
function isEditable(node) {
|
229
|
-
return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
|
230
|
-
}
|
231
|
-
|
232
|
-
var inlineDisplayRegex = /^inline(-block|-table)?$/i;
|
233
|
-
|
234
|
-
function isNonInlineElement(node) {
|
235
|
-
return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
|
236
|
-
}
|
237
|
-
|
238
|
-
// White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html)
|
239
|
-
var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/;
|
240
|
-
|
241
|
-
function isUnrenderedWhiteSpaceNode(node) {
|
242
|
-
if (node.data.length == 0) {
|
243
|
-
return true;
|
244
|
-
}
|
245
|
-
if (htmlNonWhiteSpaceRegex.test(node.data)) {
|
246
|
-
return false;
|
247
|
-
}
|
248
|
-
var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
|
249
|
-
switch (cssWhiteSpace) {
|
250
|
-
case "pre":
|
251
|
-
case "pre-wrap":
|
252
|
-
case "-moz-pre-wrap":
|
253
|
-
return false;
|
254
|
-
case "pre-line":
|
255
|
-
if (/[\r\n]/.test(node.data)) {
|
256
|
-
return false;
|
257
|
-
}
|
258
|
-
}
|
259
|
-
|
260
|
-
// We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a
|
261
|
-
// non-inline element, it will not be rendered. This seems to be a good enough definition.
|
262
|
-
return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling);
|
263
|
-
}
|
264
|
-
|
265
|
-
function getRangeBoundaries(ranges) {
|
266
|
-
var positions = [], i, range;
|
267
|
-
for (i = 0; range = ranges[i++]; ) {
|
268
|
-
positions.push(
|
269
|
-
new DomPosition(range.startContainer, range.startOffset),
|
270
|
-
new DomPosition(range.endContainer, range.endOffset)
|
271
|
-
);
|
272
|
-
}
|
273
|
-
return positions;
|
274
|
-
}
|
275
|
-
|
276
|
-
function updateRangesFromBoundaries(ranges, positions) {
|
277
|
-
for (var i = 0, range, start, end, len = ranges.length; i < len; ++i) {
|
278
|
-
range = ranges[i];
|
279
|
-
start = positions[i * 2];
|
280
|
-
end = positions[i * 2 + 1];
|
281
|
-
range.setStartAndEnd(start.node, start.offset, end.node, end.offset);
|
282
|
-
}
|
283
|
-
}
|
284
|
-
|
285
|
-
function isSplitPoint(node, offset) {
|
286
|
-
if (dom.isCharacterDataNode(node)) {
|
287
|
-
if (offset == 0) {
|
288
|
-
return !!node.previousSibling;
|
289
|
-
} else if (offset == node.length) {
|
290
|
-
return !!node.nextSibling;
|
291
|
-
} else {
|
292
|
-
return true;
|
293
|
-
}
|
294
|
-
}
|
295
|
-
|
296
|
-
return offset > 0 && offset < node.childNodes.length;
|
297
|
-
}
|
298
|
-
|
299
|
-
function splitNodeAt(node, descendantNode, descendantOffset, positionsToPreserve) {
|
300
|
-
var newNode, parentNode;
|
301
|
-
var splitAtStart = (descendantOffset == 0);
|
302
|
-
|
303
|
-
if (dom.isAncestorOf(descendantNode, node)) {
|
304
|
-
return node;
|
305
|
-
}
|
306
|
-
|
307
|
-
if (dom.isCharacterDataNode(descendantNode)) {
|
308
|
-
var descendantIndex = dom.getNodeIndex(descendantNode);
|
309
|
-
if (descendantOffset == 0) {
|
310
|
-
descendantOffset = descendantIndex;
|
311
|
-
} else if (descendantOffset == descendantNode.length) {
|
312
|
-
descendantOffset = descendantIndex + 1;
|
313
|
-
} else {
|
314
|
-
throw module.createError("splitNodeAt() should not be called with offset in the middle of a data node ("
|
315
|
-
+ descendantOffset + " in " + descendantNode.data);
|
316
|
-
}
|
317
|
-
descendantNode = descendantNode.parentNode;
|
318
|
-
}
|
319
|
-
|
320
|
-
if (isSplitPoint(descendantNode, descendantOffset)) {
|
321
|
-
// descendantNode is now guaranteed not to be a text or other character node
|
322
|
-
newNode = descendantNode.cloneNode(false);
|
323
|
-
parentNode = descendantNode.parentNode;
|
324
|
-
if (newNode.id) {
|
325
|
-
newNode.removeAttribute("id");
|
326
|
-
}
|
327
|
-
var child, newChildIndex = 0;
|
328
|
-
|
329
|
-
while ( (child = descendantNode.childNodes[descendantOffset]) ) {
|
330
|
-
movePreservingPositions(child, newNode, newChildIndex++, positionsToPreserve);
|
331
|
-
//newNode.appendChild(child);
|
332
|
-
}
|
333
|
-
movePreservingPositions(newNode, parentNode, dom.getNodeIndex(descendantNode) + 1, positionsToPreserve);
|
334
|
-
//dom.insertAfter(newNode, descendantNode);
|
335
|
-
return (descendantNode == node) ? newNode : splitNodeAt(node, parentNode, dom.getNodeIndex(newNode), positionsToPreserve);
|
336
|
-
} else if (node != descendantNode) {
|
337
|
-
newNode = descendantNode.parentNode;
|
338
|
-
|
339
|
-
// Work out a new split point in the parent node
|
340
|
-
var newNodeIndex = dom.getNodeIndex(descendantNode);
|
341
|
-
|
342
|
-
if (!splitAtStart) {
|
343
|
-
newNodeIndex++;
|
344
|
-
}
|
345
|
-
return splitNodeAt(node, newNode, newNodeIndex, positionsToPreserve);
|
346
|
-
}
|
347
|
-
return node;
|
348
|
-
}
|
349
|
-
|
350
|
-
function areElementsMergeable(el1, el2) {
|
351
|
-
return el1.tagName == el2.tagName
|
352
|
-
&& haveSameClasses(el1, el2)
|
353
|
-
&& elementsHaveSameNonClassAttributes(el1, el2)
|
354
|
-
&& getComputedStyleProperty(el1, "display") == "inline"
|
355
|
-
&& getComputedStyleProperty(el2, "display") == "inline";
|
356
|
-
}
|
357
|
-
|
358
|
-
function createAdjacentMergeableTextNodeGetter(forward) {
|
359
|
-
var propName = forward ? "nextSibling" : "previousSibling";
|
360
|
-
|
361
|
-
return function(textNode, checkParentElement) {
|
362
|
-
var el = textNode.parentNode;
|
363
|
-
var adjacentNode = textNode[propName];
|
364
|
-
if (adjacentNode) {
|
365
|
-
// Can merge if the node's previous/next sibling is a text node
|
366
|
-
if (adjacentNode && adjacentNode.nodeType == 3) {
|
367
|
-
return adjacentNode;
|
368
|
-
}
|
369
|
-
} else if (checkParentElement) {
|
370
|
-
// Compare text node parent element with its sibling
|
371
|
-
adjacentNode = el[propName];
|
372
|
-
if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)/* && adjacentNode.hasChildNodes()*/) {
|
373
|
-
return adjacentNode[forward ? "firstChild" : "lastChild"];
|
374
|
-
}
|
375
|
-
}
|
376
|
-
return null;
|
377
|
-
};
|
378
|
-
}
|
379
|
-
|
380
|
-
var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
|
381
|
-
getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
|
382
|
-
|
383
|
-
|
384
|
-
function Merge(firstNode) {
|
385
|
-
this.isElementMerge = (firstNode.nodeType == 1);
|
386
|
-
this.textNodes = [];
|
387
|
-
var firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
|
388
|
-
if (firstTextNode) {
|
389
|
-
this.textNodes[0] = firstTextNode;
|
390
|
-
}
|
391
|
-
}
|
392
|
-
|
393
|
-
Merge.prototype = {
|
394
|
-
doMerge: function(positionsToPreserve) {
|
395
|
-
var textNodes = this.textNodes;
|
396
|
-
var firstTextNode = textNodes[0];
|
397
|
-
if (textNodes.length > 1) {
|
398
|
-
var textParts = [], combinedTextLength = 0, textNode, parent;
|
399
|
-
for (var i = 0, len = textNodes.length, j, position; i < len; ++i) {
|
400
|
-
textNode = textNodes[i];
|
401
|
-
parent = textNode.parentNode;
|
402
|
-
if (i > 0) {
|
403
|
-
parent.removeChild(textNode);
|
404
|
-
if (!parent.hasChildNodes()) {
|
405
|
-
parent.parentNode.removeChild(parent);
|
406
|
-
}
|
407
|
-
if (positionsToPreserve) {
|
408
|
-
for (j = 0; position = positionsToPreserve[j++]; ) {
|
409
|
-
// Handle case where position is inside the text node being merged into a preceding node
|
410
|
-
if (position.node == textNode) {
|
411
|
-
position.node = firstTextNode;
|
412
|
-
position.offset += combinedTextLength;
|
413
|
-
}
|
414
|
-
}
|
415
|
-
}
|
416
|
-
}
|
417
|
-
textParts[i] = textNode.data;
|
418
|
-
combinedTextLength += textNode.data.length;
|
419
|
-
}
|
420
|
-
firstTextNode.data = textParts.join("");
|
421
|
-
}
|
422
|
-
return firstTextNode.data;
|
423
|
-
},
|
424
|
-
|
425
|
-
getLength: function() {
|
426
|
-
var i = this.textNodes.length, len = 0;
|
427
|
-
while (i--) {
|
428
|
-
len += this.textNodes[i].length;
|
429
|
-
}
|
430
|
-
return len;
|
431
|
-
},
|
432
|
-
|
433
|
-
toString: function() {
|
434
|
-
var textBits = [];
|
435
|
-
for (var i = 0, len = this.textNodes.length; i < len; ++i) {
|
436
|
-
textBits[i] = "'" + this.textNodes[i].data + "'";
|
437
|
-
}
|
438
|
-
return "[Merge(" + textBits.join(",") + ")]";
|
439
|
-
}
|
440
|
-
};
|
441
|
-
|
442
|
-
var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly", "useExistingElements",
|
443
|
-
"removeEmptyElements"];
|
444
|
-
|
445
|
-
// TODO: Populate this with every attribute name that corresponds to a property with a different name
|
446
|
-
var attrNamesForProperties = {};
|
447
|
-
|
448
|
-
function ClassApplier(cssClass, options, tagNames) {
|
449
|
-
this.cssClass = cssClass;
|
450
|
-
var normalize, i, len, propName;
|
451
|
-
|
452
|
-
var elementPropertiesFromOptions = null;
|
453
|
-
|
454
|
-
// Initialize from options object
|
455
|
-
if (typeof options == "object" && options !== null) {
|
456
|
-
tagNames = options.tagNames;
|
457
|
-
elementPropertiesFromOptions = options.elementProperties;
|
458
|
-
|
459
|
-
for (i = 0; propName = optionProperties[i++]; ) {
|
460
|
-
if (options.hasOwnProperty(propName)) {
|
461
|
-
this[propName] = options[propName];
|
462
|
-
}
|
463
|
-
}
|
464
|
-
normalize = options.normalize;
|
465
|
-
} else {
|
466
|
-
normalize = options;
|
467
|
-
}
|
468
|
-
|
469
|
-
// Backward compatibility: the second parameter can also be a Boolean indicating to normalize after unapplying
|
470
|
-
this.normalize = (typeof normalize == "undefined") ? true : normalize;
|
471
|
-
|
472
|
-
// Initialize element properties and attribute exceptions
|
473
|
-
this.attrExceptions = [];
|
474
|
-
var el = document.createElement(this.elementTagName);
|
475
|
-
this.elementProperties = this.copyPropertiesToElement(elementPropertiesFromOptions, el, true);
|
476
|
-
|
477
|
-
this.elementSortedClassName = this.elementProperties.hasOwnProperty("className") ?
|
478
|
-
this.elementProperties.className : cssClass;
|
479
|
-
|
480
|
-
// Initialize tag names
|
481
|
-
this.applyToAnyTagName = false;
|
482
|
-
var type = typeof tagNames;
|
483
|
-
if (type == "string") {
|
484
|
-
if (tagNames == "*") {
|
485
|
-
this.applyToAnyTagName = true;
|
486
|
-
} else {
|
487
|
-
this.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
|
488
|
-
}
|
489
|
-
} else if (type == "object" && typeof tagNames.length == "number") {
|
490
|
-
this.tagNames = [];
|
491
|
-
for (i = 0, len = tagNames.length; i < len; ++i) {
|
492
|
-
if (tagNames[i] == "*") {
|
493
|
-
this.applyToAnyTagName = true;
|
494
|
-
} else {
|
495
|
-
this.tagNames.push(tagNames[i].toLowerCase());
|
496
|
-
}
|
497
|
-
}
|
498
|
-
} else {
|
499
|
-
this.tagNames = [this.elementTagName];
|
500
|
-
}
|
501
|
-
}
|
502
|
-
|
503
|
-
ClassApplier.prototype = {
|
504
|
-
elementTagName: defaultTagName,
|
505
|
-
elementProperties: {},
|
506
|
-
ignoreWhiteSpace: true,
|
507
|
-
applyToEditableOnly: false,
|
508
|
-
useExistingElements: true,
|
509
|
-
removeEmptyElements: true,
|
510
|
-
|
511
|
-
copyPropertiesToElement: function(props, el, createCopy) {
|
512
|
-
var s, elStyle, elProps = {}, elPropsStyle, propValue, elPropValue, attrName;
|
513
|
-
|
514
|
-
for (var p in props) {
|
515
|
-
if (props.hasOwnProperty(p)) {
|
516
|
-
propValue = props[p];
|
517
|
-
elPropValue = el[p];
|
518
|
-
|
519
|
-
// Special case for class. The copied properties object has the applier's CSS class as well as its
|
520
|
-
// own to simplify checks when removing styling elements
|
521
|
-
if (p == "className") {
|
522
|
-
addClass(el, propValue);
|
523
|
-
addClass(el, this.cssClass);
|
524
|
-
el[p] = sortClassName(el[p]);
|
525
|
-
if (createCopy) {
|
526
|
-
elProps[p] = el[p];
|
527
|
-
}
|
528
|
-
}
|
529
|
-
|
530
|
-
// Special case for style
|
531
|
-
else if (p == "style") {
|
532
|
-
elStyle = elPropValue;
|
533
|
-
if (createCopy) {
|
534
|
-
elProps[p] = elPropsStyle = {};
|
535
|
-
}
|
536
|
-
for (s in props[p]) {
|
537
|
-
elStyle[s] = propValue[s];
|
538
|
-
if (createCopy) {
|
539
|
-
elPropsStyle[s] = elStyle[s];
|
540
|
-
}
|
541
|
-
}
|
542
|
-
this.attrExceptions.push(p);
|
543
|
-
} else {
|
544
|
-
el[p] = propValue;
|
545
|
-
// Copy the property back from the dummy element so that later comparisons to check whether
|
546
|
-
// elements may be removed are checking against the right value. For example, the href property
|
547
|
-
// of an element returns a fully qualified URL even if it was previously assigned a relative
|
548
|
-
// URL.
|
549
|
-
if (createCopy) {
|
550
|
-
elProps[p] = el[p];
|
551
|
-
|
552
|
-
// Not all properties map to identically named attributes
|
553
|
-
attrName = attrNamesForProperties.hasOwnProperty(p) ? attrNamesForProperties[p] : p;
|
554
|
-
this.attrExceptions.push(attrName);
|
555
|
-
}
|
556
|
-
}
|
557
|
-
}
|
558
|
-
}
|
559
|
-
|
560
|
-
return createCopy ? elProps : "";
|
561
|
-
},
|
562
|
-
|
563
|
-
hasClass: function(node) {
|
564
|
-
return node.nodeType == 1 &&
|
565
|
-
contains(this.tagNames, node.tagName.toLowerCase()) &&
|
566
|
-
hasClass(node, this.cssClass);
|
567
|
-
},
|
568
|
-
|
569
|
-
getSelfOrAncestorWithClass: function(node) {
|
570
|
-
while (node) {
|
571
|
-
if (this.hasClass(node)) {
|
572
|
-
return node;
|
573
|
-
}
|
574
|
-
node = node.parentNode;
|
575
|
-
}
|
576
|
-
return null;
|
577
|
-
},
|
578
|
-
|
579
|
-
isModifiable: function(node) {
|
580
|
-
return !this.applyToEditableOnly || isEditable(node);
|
581
|
-
},
|
582
|
-
|
583
|
-
// White space adjacent to an unwrappable node can be ignored for wrapping
|
584
|
-
isIgnorableWhiteSpaceNode: function(node) {
|
585
|
-
return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node);
|
586
|
-
},
|
587
|
-
|
588
|
-
// Normalizes nodes after applying a CSS class to a Range.
|
589
|
-
postApply: function(textNodes, range, positionsToPreserve, isUndo) {
|
590
|
-
var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
|
591
|
-
|
592
|
-
var merges = [], currentMerge;
|
593
|
-
|
594
|
-
var rangeStartNode = firstNode, rangeEndNode = lastNode;
|
595
|
-
var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
|
596
|
-
|
597
|
-
var textNode, precedingTextNode;
|
598
|
-
|
599
|
-
// Check for every required merge and create a Merge object for each
|
600
|
-
for (var i = 0, len = textNodes.length; i < len; ++i) {
|
601
|
-
textNode = textNodes[i];
|
602
|
-
precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo);
|
603
|
-
if (precedingTextNode) {
|
604
|
-
if (!currentMerge) {
|
605
|
-
currentMerge = new Merge(precedingTextNode);
|
606
|
-
merges.push(currentMerge);
|
607
|
-
}
|
608
|
-
currentMerge.textNodes.push(textNode);
|
609
|
-
if (textNode === firstNode) {
|
610
|
-
rangeStartNode = currentMerge.textNodes[0];
|
611
|
-
rangeStartOffset = rangeStartNode.length;
|
612
|
-
}
|
613
|
-
if (textNode === lastNode) {
|
614
|
-
rangeEndNode = currentMerge.textNodes[0];
|
615
|
-
rangeEndOffset = currentMerge.getLength();
|
616
|
-
}
|
617
|
-
} else {
|
618
|
-
currentMerge = null;
|
619
|
-
}
|
620
|
-
}
|
621
|
-
|
622
|
-
// Test whether the first node after the range needs merging
|
623
|
-
var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
|
624
|
-
|
625
|
-
if (nextTextNode) {
|
626
|
-
if (!currentMerge) {
|
627
|
-
currentMerge = new Merge(lastNode);
|
628
|
-
merges.push(currentMerge);
|
629
|
-
}
|
630
|
-
currentMerge.textNodes.push(nextTextNode);
|
631
|
-
}
|
632
|
-
|
633
|
-
// Apply the merges
|
634
|
-
if (merges.length) {
|
635
|
-
for (i = 0, len = merges.length; i < len; ++i) {
|
636
|
-
merges[i].doMerge(positionsToPreserve);
|
637
|
-
}
|
638
|
-
|
639
|
-
// Set the range boundaries
|
640
|
-
range.setStartAndEnd(rangeStartNode, rangeStartOffset, rangeEndNode, rangeEndOffset);
|
641
|
-
}
|
642
|
-
},
|
643
|
-
|
644
|
-
createContainer: function(doc) {
|
645
|
-
var el = doc.createElement(this.elementTagName);
|
646
|
-
this.copyPropertiesToElement(this.elementProperties, el, false);
|
647
|
-
addClass(el, this.cssClass);
|
648
|
-
return el;
|
649
|
-
},
|
650
|
-
|
651
|
-
applyToTextNode: function(textNode, positionsToPreserve) {
|
652
|
-
var parent = textNode.parentNode;
|
653
|
-
if (parent.childNodes.length == 1 &&
|
654
|
-
this.useExistingElements &&
|
655
|
-
contains(this.tagNames, parent.tagName.toLowerCase()) &&
|
656
|
-
elementHasProps(parent, this.elementProperties)) {
|
657
|
-
|
658
|
-
addClass(parent, this.cssClass);
|
659
|
-
} else {
|
660
|
-
var el = this.createContainer(dom.getDocument(textNode));
|
661
|
-
textNode.parentNode.insertBefore(el, textNode);
|
662
|
-
el.appendChild(textNode);
|
663
|
-
}
|
664
|
-
},
|
665
|
-
|
666
|
-
isRemovable: function(el) {
|
667
|
-
return el.tagName.toLowerCase() == this.elementTagName
|
668
|
-
&& getSortedClassName(el) == this.elementSortedClassName
|
669
|
-
&& elementHasProps(el, this.elementProperties)
|
670
|
-
&& !elementHasNonClassAttributes(el, this.attrExceptions)
|
671
|
-
&& this.isModifiable(el);
|
672
|
-
},
|
673
|
-
|
674
|
-
isEmptyContainer: function(el) {
|
675
|
-
var childNodeCount = el.childNodes.length;
|
676
|
-
return el.nodeType == 1
|
677
|
-
&& this.isRemovable(el)
|
678
|
-
&& (childNodeCount == 0 || (childNodeCount == 1 && this.isEmptyContainer(el.firstChild)));
|
679
|
-
},
|
680
|
-
|
681
|
-
removeEmptyContainers: function(range) {
|
682
|
-
var applier = this;
|
683
|
-
var nodesToRemove = range.getNodes([1], function(el) {
|
684
|
-
return applier.isEmptyContainer(el);
|
685
|
-
});
|
686
|
-
|
687
|
-
for (var i = 0, node; node = nodesToRemove[i++]; ) {
|
688
|
-
node.parentNode.removeChild(node);
|
689
|
-
}
|
690
|
-
},
|
691
|
-
|
692
|
-
undoToTextNode: function(textNode, range, ancestorWithClass, positionsToPreserve) {
|
693
|
-
if (!range.containsNode(ancestorWithClass)) {
|
694
|
-
// Split out the portion of the ancestor from which we can remove the CSS class
|
695
|
-
//var parent = ancestorWithClass.parentNode, index = dom.getNodeIndex(ancestorWithClass);
|
696
|
-
var ancestorRange = range.cloneRange();
|
697
|
-
ancestorRange.selectNode(ancestorWithClass);
|
698
|
-
if (ancestorRange.isPointInRange(range.endContainer, range.endOffset)) {
|
699
|
-
splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset, positionsToPreserve);
|
700
|
-
range.setEndAfter(ancestorWithClass);
|
701
|
-
}
|
702
|
-
if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)) {
|
703
|
-
ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, positionsToPreserve);
|
704
|
-
}
|
705
|
-
}
|
706
|
-
if (this.isRemovable(ancestorWithClass)) {
|
707
|
-
replaceWithOwnChildrenPreservingPositions(ancestorWithClass, positionsToPreserve);
|
708
|
-
} else {
|
709
|
-
removeClass(ancestorWithClass, this.cssClass);
|
710
|
-
}
|
711
|
-
},
|
712
|
-
|
713
|
-
applyToRange: function(range, rangesToPreserve) {
|
714
|
-
rangesToPreserve = rangesToPreserve || [];
|
715
|
-
|
716
|
-
// Create an array of range boundaries to preserve
|
717
|
-
var positionsToPreserve = getRangeBoundaries(rangesToPreserve || []);
|
718
|
-
|
719
|
-
range.splitBoundariesPreservingPositions(positionsToPreserve);
|
720
|
-
|
721
|
-
// Tidy up the DOM by removing empty containers
|
722
|
-
if (this.removeEmptyElements) {
|
723
|
-
this.removeEmptyContainers(range);
|
724
|
-
}
|
725
|
-
|
726
|
-
var textNodes = getEffectiveTextNodes(range);
|
727
|
-
|
728
|
-
if (textNodes.length) {
|
729
|
-
for (var i = 0, textNode; textNode = textNodes[i++]; ) {
|
730
|
-
if (!this.isIgnorableWhiteSpaceNode(textNode) && !this.getSelfOrAncestorWithClass(textNode)
|
731
|
-
&& this.isModifiable(textNode)) {
|
732
|
-
this.applyToTextNode(textNode, positionsToPreserve);
|
733
|
-
}
|
734
|
-
}
|
735
|
-
textNode = textNodes[textNodes.length - 1];
|
736
|
-
range.setStartAndEnd(textNodes[0], 0, textNode, textNode.length);
|
737
|
-
if (this.normalize) {
|
738
|
-
this.postApply(textNodes, range, positionsToPreserve, false);
|
739
|
-
}
|
740
|
-
|
741
|
-
// Update the ranges from the preserved boundary positions
|
742
|
-
updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
|
743
|
-
}
|
744
|
-
},
|
745
|
-
|
746
|
-
applyToRanges: function(ranges) {
|
747
|
-
|
748
|
-
var i = ranges.length;
|
749
|
-
while (i--) {
|
750
|
-
this.applyToRange(ranges[i], ranges);
|
751
|
-
}
|
752
|
-
|
753
|
-
|
754
|
-
return ranges;
|
755
|
-
},
|
756
|
-
|
757
|
-
applyToSelection: function(win) {
|
758
|
-
var sel = api.getSelection(win);
|
759
|
-
sel.setRanges( this.applyToRanges(sel.getAllRanges()) );
|
760
|
-
},
|
761
|
-
|
762
|
-
undoToRange: function(range, rangesToPreserve) {
|
763
|
-
// Create an array of range boundaries to preserve
|
764
|
-
rangesToPreserve = rangesToPreserve || [];
|
765
|
-
var positionsToPreserve = getRangeBoundaries(rangesToPreserve);
|
766
|
-
|
767
|
-
|
768
|
-
range.splitBoundariesPreservingPositions(positionsToPreserve);
|
769
|
-
|
770
|
-
// Tidy up the DOM by removing empty containers
|
771
|
-
if (this.removeEmptyElements) {
|
772
|
-
this.removeEmptyContainers(range, positionsToPreserve);
|
773
|
-
}
|
774
|
-
|
775
|
-
var textNodes = getEffectiveTextNodes(range);
|
776
|
-
var textNode, ancestorWithClass;
|
777
|
-
var lastTextNode = textNodes[textNodes.length - 1];
|
778
|
-
|
779
|
-
if (textNodes.length) {
|
780
|
-
for (var i = 0, len = textNodes.length; i < len; ++i) {
|
781
|
-
textNode = textNodes[i];
|
782
|
-
ancestorWithClass = this.getSelfOrAncestorWithClass(textNode);
|
783
|
-
if (ancestorWithClass && this.isModifiable(textNode)) {
|
784
|
-
this.undoToTextNode(textNode, range, ancestorWithClass, positionsToPreserve);
|
785
|
-
}
|
786
|
-
|
787
|
-
// Ensure the range is still valid
|
788
|
-
range.setStartAndEnd(textNodes[0], 0, lastTextNode, lastTextNode.length);
|
789
|
-
}
|
790
|
-
|
791
|
-
|
792
|
-
if (this.normalize) {
|
793
|
-
this.postApply(textNodes, range, positionsToPreserve, true);
|
794
|
-
}
|
795
|
-
|
796
|
-
// Update the ranges from the preserved boundary positions
|
797
|
-
updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
|
798
|
-
}
|
799
|
-
},
|
800
|
-
|
801
|
-
undoToRanges: function(ranges) {
|
802
|
-
// Get ranges returned in document order
|
803
|
-
var i = ranges.length;
|
804
|
-
|
805
|
-
while (i--) {
|
806
|
-
this.undoToRange(ranges[i], ranges);
|
807
|
-
}
|
808
|
-
|
809
|
-
return ranges;
|
810
|
-
},
|
811
|
-
|
812
|
-
undoToSelection: function(win) {
|
813
|
-
var sel = api.getSelection(win);
|
814
|
-
var ranges = api.getSelection(win).getAllRanges();
|
815
|
-
this.undoToRanges(ranges);
|
816
|
-
sel.setRanges(ranges);
|
817
|
-
},
|
818
|
-
|
819
|
-
getTextSelectedByRange: function(textNode, range) {
|
820
|
-
var textRange = range.cloneRange();
|
821
|
-
textRange.selectNodeContents(textNode);
|
822
|
-
|
823
|
-
var intersectionRange = textRange.intersection(range);
|
824
|
-
var text = intersectionRange ? intersectionRange.toString() : "";
|
825
|
-
textRange.detach();
|
826
|
-
|
827
|
-
return text;
|
828
|
-
},
|
829
|
-
|
830
|
-
isAppliedToRange: function(range) {
|
831
|
-
if (range.collapsed || range.toString() == "") {
|
832
|
-
return !!this.getSelfOrAncestorWithClass(range.commonAncestorContainer);
|
833
|
-
} else {
|
834
|
-
var textNodes = range.getNodes( [3] );
|
835
|
-
if (textNodes.length)
|
836
|
-
for (var i = 0, textNode; textNode = textNodes[i++]; ) {
|
837
|
-
if (!this.isIgnorableWhiteSpaceNode(textNode) && rangeSelectsAnyText(range, textNode)
|
838
|
-
&& this.isModifiable(textNode) && !this.getSelfOrAncestorWithClass(textNode)) {
|
839
|
-
return false;
|
840
|
-
}
|
841
|
-
}
|
842
|
-
return true;
|
843
|
-
}
|
844
|
-
},
|
845
|
-
|
846
|
-
isAppliedToRanges: function(ranges) {
|
847
|
-
var i = ranges.length;
|
848
|
-
while (i--) {
|
849
|
-
if (!this.isAppliedToRange(ranges[i])) {
|
850
|
-
return false;
|
851
|
-
}
|
852
|
-
}
|
853
|
-
return true;
|
854
|
-
},
|
855
|
-
|
856
|
-
isAppliedToSelection: function(win) {
|
857
|
-
var sel = api.getSelection(win);
|
858
|
-
return this.isAppliedToRanges(sel.getAllRanges());
|
859
|
-
},
|
860
|
-
|
861
|
-
toggleRange: function(range) {
|
862
|
-
if (this.isAppliedToRange(range)) {
|
863
|
-
this.undoToRange(range);
|
864
|
-
} else {
|
865
|
-
this.applyToRange(range);
|
866
|
-
}
|
867
|
-
},
|
868
|
-
|
869
|
-
toggleRanges: function(ranges) {
|
870
|
-
if (this.isAppliedToRanges(ranges)) {
|
871
|
-
this.undoToRanges(ranges);
|
872
|
-
} else {
|
873
|
-
this.applyToRanges(ranges);
|
874
|
-
}
|
875
|
-
},
|
876
|
-
|
877
|
-
toggleSelection: function(win) {
|
878
|
-
if (this.isAppliedToSelection(win)) {
|
879
|
-
this.undoToSelection(win);
|
880
|
-
} else {
|
881
|
-
this.applyToSelection(win);
|
882
|
-
}
|
883
|
-
},
|
884
|
-
|
885
|
-
getElementsWithClassIntersectingRange: function(range) {
|
886
|
-
var elements = [];
|
887
|
-
var applier = this;
|
888
|
-
range.getNodes([3], function(textNode) {
|
889
|
-
var el = applier.getSelfOrAncestorWithClass(textNode);
|
890
|
-
if (el && !contains(elements, el)) {
|
891
|
-
elements.push(el);
|
892
|
-
}
|
893
|
-
});
|
894
|
-
return elements;
|
895
|
-
},
|
896
|
-
|
897
|
-
getElementsWithClassIntersectingSelection: function(win) {
|
898
|
-
var sel = api.getSelection(win);
|
899
|
-
var elements = [];
|
900
|
-
var applier = this;
|
901
|
-
sel.eachRange(function(range) {
|
902
|
-
var rangeElements = applier.getElementsWithClassIntersectingRange(range);
|
903
|
-
for (var i = 0, el; el = rangeElements[i++]; ) {
|
904
|
-
if (!contains(elements, el)) {
|
905
|
-
elements.push(el);
|
906
|
-
}
|
907
|
-
}
|
908
|
-
});
|
909
|
-
return elements;
|
910
|
-
},
|
911
|
-
|
912
|
-
detach: function() {}
|
913
|
-
};
|
914
|
-
|
915
|
-
function createClassApplier(cssClass, options, tagNames) {
|
916
|
-
return new ClassApplier(cssClass, options, tagNames);
|
917
|
-
}
|
918
|
-
|
919
|
-
ClassApplier.util = {
|
920
|
-
hasClass: hasClass,
|
921
|
-
addClass: addClass,
|
922
|
-
removeClass: removeClass,
|
923
|
-
hasSameClasses: haveSameClasses,
|
924
|
-
replaceWithOwnChildren: replaceWithOwnChildrenPreservingPositions,
|
925
|
-
elementsHaveSameNonClassAttributes: elementsHaveSameNonClassAttributes,
|
926
|
-
elementHasNonClassAttributes: elementHasNonClassAttributes,
|
927
|
-
splitNodeAt: splitNodeAt,
|
928
|
-
isEditableElement: isEditableElement,
|
929
|
-
isEditingHost: isEditingHost,
|
930
|
-
isEditable: isEditable
|
931
|
-
};
|
932
|
-
|
933
|
-
api.CssClassApplier = api.ClassApplier = ClassApplier;
|
934
|
-
api.createCssClassApplier = api.createClassApplier = createClassApplier;
|
935
|
-
});
|
15
|
+
rangy.createModule("ClassApplier",["WrappedSelection"],function(a,b){function g(a){return a.replace(/^\s\s*/,"").replace(/\s\s*$/,"")}function h(a,b){return a.className&&(new RegExp("(?:^|\\s)"+b+"(?:\\s|$)")).test(a.className)}function i(a,b){a.className?h(a,b)||(a.className+=" "+b):a.className=b}function k(a){return a.split(/\s+/).sort().join(" ")}function l(a){return k(a.className)}function m(a,b){return l(a)==l(b)}function n(a,b){return a.compareBoundaryPoints(b.START_TO_START,b)}function o(a,b,c,d,e){var f=a.node,g=a.offset,h=f,i=g;f==d&&g>e&&i++,f==b&&(g==c||g==c+1)&&(h=d,i+=e-c),f==b&&g>c+1&&i--,a.node=h,a.offset=i}function p(a,b,d,e){d==-1&&(d=b.childNodes.length);var f=a.parentNode,g=c.getNodeIndex(a);for(var h=0,i;i=e[h++];)o(i,f,g,b,d);b.childNodes.length==d?b.appendChild(a):b.insertBefore(a,b.childNodes[d])}function q(a,b,c,d,e){var f,g=[];while(f=a.firstChild)p(f,b,c++,e),g.push(f);return d&&a.parentNode.removeChild(a),g}function r(a,b){return q(a,a.parentNode,c.getNodeIndex(a),!0,b)}function s(a,b){var c=a.cloneRange();c.selectNodeContents(b);var d=c.intersection(a),e=d?d.toString():"";return c.detach(),e!=""}function t(a){var b=a.getNodes([3]),c=0,d;while((d=b[c])&&!s(a,d))++c;var e=b.length-1;while((d=b[e])&&!s(a,d))--e;return b.slice(c,e+1)}function u(a,b){if(a.attributes.length!=b.attributes.length)return!1;for(var c=0,d=a.attributes.length,e,f,g;c<d;++c){e=a.attributes[c],g=e.name;if(g!="class"){f=b.attributes.getNamedItem(g);if(e.specified!=f.specified)return!1;if(e.specified&&e.nodeValue!==f.nodeValue)return!1}}return!0}function v(a,b){for(var c=0,d=a.attributes.length,f;c<d;++c){f=a.attributes[c].name;if((!b||!e(b,f))&&a.attributes[c].specified&&f!="class")return!0}return!1}function w(a,b){var c;for(var d in b)if(b.hasOwnProperty(d)){c=b[d];if(typeof c=="object"){if(!w(a[d],c))return!1}else if(a[d]!==c)return!1}return!0}function z(a){var b;return a&&a.nodeType==1&&((b=a.parentNode)&&b.nodeType==9&&b.designMode=="on"||y(a)&&!y(a.parentNode))}function A(a){return(y(a)||a.nodeType!=1&&y(a.parentNode))&&!z(a)}function C(a){return a&&a.nodeType==1&&!B.test(x(a,"display"))}function E(a){if(a.data.length==0)return!0;if(D.test(a.data))return!1;var b=x(a.parentNode,"whiteSpace");switch(b){case"pre":case"pre-wrap":case"-moz-pre-wrap":return!1;case"pre-line":if(/[\r\n]/.test(a.data))return!1}return C(a.previousSibling)||C(a.nextSibling)}function F(a){var b=[],c,e;for(c=0;e=a[c++];)b.push(new d(e.startContainer,e.startOffset),new d(e.endContainer,e.endOffset));return b}function G(a,b){for(var c=0,d,e,f,g=a.length;c<g;++c)d=a[c],e=b[c*2],f=b[c*2+1],d.setStartAndEnd(e.node,e.offset,f.node,f.offset)}function H(a,b){return c.isCharacterDataNode(a)?b==0?!!a.previousSibling:b==a.length?!!a.nextSibling:!0:b>0&&b<a.childNodes.length}function I(a,d,e,f){var g,h,i=e==0;if(c.isAncestorOf(d,a))return a;if(c.isCharacterDataNode(d)){var j=c.getNodeIndex(d);if(e==0)e=j;else{if(e!=d.length)throw b.createError("splitNodeAt() should not be called with offset in the middle of a data node ("+e+" in "+d.data);e=j+1}d=d.parentNode}if(H(d,e)){g=d.cloneNode(!1),h=d.parentNode,g.id&&g.removeAttribute("id");var k,l=0;while(k=d.childNodes[e])p(k,g,l++,f);return p(g,h,c.getNodeIndex(d)+1,f),d==a?g:I(a,h,c.getNodeIndex(g),f)}if(a!=d){g=d.parentNode;var m=c.getNodeIndex(d);return i||m++,I(a,g,m,f)}return a}function J(a,b){return a.tagName==b.tagName&&m(a,b)&&u(a,b)&&x(a,"display")=="inline"&&x(b,"display")=="inline"}function K(a){var b=a?"nextSibling":"previousSibling";return function(c,d){var e=c.parentNode,f=c[b];if(f){if(f&&f.nodeType==3)return f}else if(d){f=e[b];if(f&&f.nodeType==1&&J(e,f))return f[a?"firstChild":"lastChild"]}return null}}function N(a){this.isElementMerge=a.nodeType==1,this.textNodes=[];var b=this.isElementMerge?a.lastChild:a;b&&(this.textNodes[0]=b)}function Q(a,b,c){this.cssClass=a;var d,e,f,h,i=null;if(typeof b=="object"&&b!==null){c=b.tagNames,i=b.elementProperties;for(e=0;h=O[e++];)b.hasOwnProperty(h)&&(this[h]=b[h]);d=b.normalize}else d=b;this.normalize=typeof d=="undefined"?!0:d,this.attrExceptions=[];var j=document.createElement(this.elementTagName);this.elementProperties=this.copyPropertiesToElement(i,j,!0),this.elementSortedClassName=this.elementProperties.hasOwnProperty("className")?this.elementProperties.className:a,this.applyToAnyTagName=!1;var k=typeof c;if(k=="string")c=="*"?this.applyToAnyTagName=!0:this.tagNames=g(c.toLowerCase()).split(/\s*,\s*/);else if(k=="object"&&typeof c.length=="number"){this.tagNames=[];for(e=0,f=c.length;e<f;++e)c[e]=="*"?this.applyToAnyTagName=!0:this.tagNames.push(c[e].toLowerCase())}else this.tagNames=[this.elementTagName]}function R(a,b,c){return new Q(a,b,c)}var c=a.dom,d=c.DomPosition,e=c.arrayContains,f="span",j=function(){function a(a,b,c){return b&&c?" ":""}return function(b,c){b.className&&(b.className=b.className.replace(new RegExp("(^|\\s)"+c+"(\\s|$)"),a))}}(),x=c.getComputedStyleProperty,y;(function(){var a=document.createElement("div");typeof a.isContentEditable=="boolean"?y=function(a){return a&&a.nodeType==1&&a.isContentEditable}:y=function(a){return!a||a.nodeType!=1||a.contentEditable=="false"?!1:a.contentEditable=="true"||y(a.parentNode)}})();var B=/^inline(-block|-table)?$/i,D=/[^\r\n\t\f \u200B]/,L=K(!1),M=K(!0);N.prototype={doMerge:function(a){var b=this.textNodes,c=b[0];if(b.length>1){var d=[],e=0,f,g;for(var h=0,i=b.length,j,k;h<i;++h){f=b[h],g=f.parentNode;if(h>0){g.removeChild(f),g.hasChildNodes()||g.parentNode.removeChild(g);if(a)for(j=0;k=a[j++];)k.node==f&&(k.node=c,k.offset+=e)}d[h]=f.data,e+=f.data.length}c.data=d.join("")}return c.data},getLength:function(){var a=this.textNodes.length,b=0;while(a--)b+=this.textNodes[a].length;return b},toString:function(){var a=[];for(var b=0,c=this.textNodes.length;b<c;++b)a[b]="'"+this.textNodes[b].data+"'";return"[Merge("+a.join(",")+")]"}};var O=["elementTagName","ignoreWhiteSpace","applyToEditableOnly","useExistingElements","removeEmptyElements"],P={};Q.prototype={elementTagName:f,elementProperties:{},ignoreWhiteSpace:!0,applyToEditableOnly:!1,useExistingElements:!0,removeEmptyElements:!0,copyPropertiesToElement:function(a,b,c){var d,e,f={},g,h,j,l;for(var m in a)if(a.hasOwnProperty(m)){h=a[m],j=b[m];if(m=="className")i(b,h),i(b,this.cssClass),b[m]=k(b[m]),c&&(f[m]=b[m]);else if(m=="style"){e=j,c&&(f[m]=g={});for(d in a[m])e[d]=h[d],c&&(g[d]=e[d]);this.attrExceptions.push(m)}else b[m]=h,c&&(f[m]=b[m],l=P.hasOwnProperty(m)?P[m]:m,this.attrExceptions.push(l))}return c?f:""},hasClass:function(a){return a.nodeType==1&&e(this.tagNames,a.tagName.toLowerCase())&&h(a,this.cssClass)},getSelfOrAncestorWithClass:function(a){while(a){if(this.hasClass(a))return a;a=a.parentNode}return null},isModifiable:function(a){return!this.applyToEditableOnly||A(a)},isIgnorableWhiteSpaceNode:function(a){return this.ignoreWhiteSpace&&a&&a.nodeType==3&&E(a)},postApply:function(a,b,c,d){var e=a[0],f=a[a.length-1],g=[],h,i=e,j=f,k=0,l=f.length,m,n;for(var o=0,p=a.length;o<p;++o)m=a[o],n=L(m,!d),n?(h||(h=new N(n),g.push(h)),h.textNodes.push(m),m===e&&(i=h.textNodes[0],k=i.length),m===f&&(j=h.textNodes[0],l=h.getLength())):h=null;var q=M(f,!d);q&&(h||(h=new N(f),g.push(h)),h.textNodes.push(q));if(g.length){for(o=0,p=g.length;o<p;++o)g[o].doMerge(c);b.setStartAndEnd(i,k,j,l)}},createContainer:function(a){var b=a.createElement(this.elementTagName);return this.copyPropertiesToElement(this.elementProperties,b,!1),i(b,this.cssClass),b},applyToTextNode:function(a,b){var d=a.parentNode;if(d.childNodes.length==1&&this.useExistingElements&&e(this.tagNames,d.tagName.toLowerCase())&&w(d,this.elementProperties))i(d,this.cssClass);else{var f=this.createContainer(c.getDocument(a));a.parentNode.insertBefore(f,a),f.appendChild(a)}},isRemovable:function(a){return a.tagName.toLowerCase()==this.elementTagName&&l(a)==this.elementSortedClassName&&w(a,this.elementProperties)&&!v(a,this.attrExceptions)&&this.isModifiable(a)},isEmptyContainer:function(a){var b=a.childNodes.length;return a.nodeType==1&&this.isRemovable(a)&&(b==0||b==1&&this.isEmptyContainer(a.firstChild))},removeEmptyContainers:function(a){var b=this,c=a.getNodes([1],function(a){return b.isEmptyContainer(a)});for(var d=0,e;e=c[d++];)e.parentNode.removeChild(e)},undoToTextNode:function(a,b,c,d){if(!b.containsNode(c)){var e=b.cloneRange();e.selectNode(c),e.isPointInRange(b.endContainer,b.endOffset)&&(I(c,b.endContainer,b.endOffset,d),b.setEndAfter(c)),e.isPointInRange(b.startContainer,b.startOffset)&&(c=I(c,b.startContainer,b.startOffset,d))}this.isRemovable(c)?r(c,d):j(c,this.cssClass)},applyToRange:function(a,b){b=b||[];var c=F(b||[]);a.splitBoundariesPreservingPositions(c),this.removeEmptyElements&&this.removeEmptyContainers(a);var d=t(a);if(d.length){for(var e=0,f;f=d[e++];)!this.isIgnorableWhiteSpaceNode(f)&&!this.getSelfOrAncestorWithClass(f)&&this.isModifiable(f)&&this.applyToTextNode(f,c);f=d[d.length-1],a.setStartAndEnd(d[0],0,f,f.length),this.normalize&&this.postApply(d,a,c,!1),G(b,c)}},applyToRanges:function(a){var b=a.length;while(b--)this.applyToRange(a[b],a);return a},applyToSelection:function(b){var c=a.getSelection(b);c.setRanges(this.applyToRanges(c.getAllRanges()))},undoToRange:function(a,b){b=b||[];var c=F(b);a.splitBoundariesPreservingPositions(c),this.removeEmptyElements&&this.removeEmptyContainers(a,c);var d=t(a),e,f,g=d[d.length-1];if(d.length){for(var h=0,i=d.length;h<i;++h)e=d[h],f=this.getSelfOrAncestorWithClass(e),f&&this.isModifiable(e)&&this.undoToTextNode(e,a,f,c),a.setStartAndEnd(d[0],0,g,g.length);this.normalize&&this.postApply(d,a,c,!0),G(b,c)}},undoToRanges:function(a){var b=a.length;while(b--)this.undoToRange(a[b],a);return a},undoToSelection:function(b){var c=a.getSelection(b),d=a.getSelection(b).getAllRanges();this.undoToRanges(d),c.setRanges(d)},getTextSelectedByRange:function(a,b){var c=b.cloneRange();c.selectNodeContents(a);var d=c.intersection(b),e=d?d.toString():"";return c.detach(),e},isAppliedToRange:function(a){if(a.collapsed||a.toString()=="")return!!this.getSelfOrAncestorWithClass(a.commonAncestorContainer);var b=a.getNodes([3]);if(b.length)for(var c=0,d;d=b[c++];)if(!this.isIgnorableWhiteSpaceNode(d)&&s(a,d)&&this.isModifiable(d)&&!this.getSelfOrAncestorWithClass(d))return!1;return!0},isAppliedToRanges:function(a){var b=a.length;if(b==0)return!1;while(b--)if(!this.isAppliedToRange(a[b]))return!1;return!0},isAppliedToSelection:function(b){var c=a.getSelection(b);return this.isAppliedToRanges(c.getAllRanges())},toggleRange:function(a){this.isAppliedToRange(a)?this.undoToRange(a):this.applyToRange(a)},toggleRanges:function(a){this.isAppliedToRanges(a)?this.undoToRanges(a):this.applyToRanges(a)},toggleSelection:function(a){this.isAppliedToSelection(a)?this.undoToSelection(a):this.applyToSelection(a)},getElementsWithClassIntersectingRange:function(a){var b=[],c=this;return a.getNodes([3],function(a){var d=c.getSelfOrAncestorWithClass(a);d&&!e(b,d)&&b.push(d)}),b},getElementsWithClassIntersectingSelection:function(b){var c=a.getSelection(b),d=[],f=this;return c.eachRange(function(a){var b=f.getElementsWithClassIntersectingRange(a);for(var c=0,g;g=b[c++];)e(d,g)||d.push(g)}),d},detach:function(){}},Q.util={hasClass:h,addClass:i,removeClass:j,hasSameClasses:m,replaceWithOwnChildren:r,elementsHaveSameNonClassAttributes:u,elementHasNonClassAttributes:v,splitNodeAt:I,isEditableElement:y,isEditingHost:z,isEditable:A},a.CssClassApplier=a.ClassApplier=Q,a.createCssClassApplier=a.createClassApplier=R})
|