rangy-rails 1.3alpha.804.0 → 1.3.1.pre.dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,983 +3,1101 @@
3
3
  * Adds, removes and toggles classes on Ranges and Selections
4
4
  *
5
5
  * Part of Rangy, a cross-browser JavaScript range and selection library
6
- * http://code.google.com/p/rangy/
6
+ * https://github.com/timdown/rangy
7
7
  *
8
8
  * Depends on Rangy core.
9
9
  *
10
- * Copyright 2013, Tim Down
10
+ * Copyright 2015, Tim Down
11
11
  * Licensed under the MIT license.
12
- * Version: 1.3alpha.804
13
- * Build date: 8 December 2013
12
+ * Version: 1.3.1-dev
13
+ * Build date: 20 May 2015
14
14
  */
15
- rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) {
16
- var dom = api.dom;
17
- var DomPosition = dom.DomPosition;
18
- var contains = dom.arrayContains;
19
-
20
-
21
- var defaultTagName = "span";
22
-
23
- function each(obj, func) {
24
- for (var i in obj) {
25
- if (obj.hasOwnProperty(i)) {
26
- if (func(i, obj[i]) === false) {
27
- return false;
15
+ (function(factory, root) {
16
+ if (typeof define == "function" && define.amd) {
17
+ // AMD. Register as an anonymous module with a dependency on Rangy.
18
+ define(["./rangy-core"], factory);
19
+ } else if (typeof module != "undefined" && typeof exports == "object") {
20
+ // Node/CommonJS style
21
+ module.exports = factory( require("rangy") );
22
+ } else {
23
+ // No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
24
+ factory(root.rangy);
25
+ }
26
+ })(function(rangy) {
27
+ rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) {
28
+ var dom = api.dom;
29
+ var DomPosition = dom.DomPosition;
30
+ var contains = dom.arrayContains;
31
+ var util = api.util;
32
+ var forEach = util.forEach;
33
+
34
+
35
+ var defaultTagName = "span";
36
+ var createElementNSSupported = util.isHostMethod(document, "createElementNS");
37
+
38
+ function each(obj, func) {
39
+ for (var i in obj) {
40
+ if (obj.hasOwnProperty(i)) {
41
+ if (func(i, obj[i]) === false) {
42
+ return false;
43
+ }
28
44
  }
29
45
  }
46
+ return true;
30
47
  }
31
- return true;
32
- }
33
-
34
- function trim(str) {
35
- return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
36
- }
37
-
38
- function hasClass(el, cssClass) {
39
- return el.className && new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)").test(el.className);
40
- }
41
48
 
42
- function addClass(el, cssClass) {
43
- if (el.className) {
44
- if (!hasClass(el, cssClass)) {
45
- el.className += " " + cssClass;
46
- }
47
- } else {
48
- el.className = cssClass;
49
+ function trim(str) {
50
+ return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
49
51
  }
50
- }
51
52
 
52
- var removeClass = (function() {
53
- function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
54
- return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
53
+ function classNameContainsClass(fullClassName, className) {
54
+ return !!fullClassName && new RegExp("(?:^|\\s)" + className + "(?:\\s|$)").test(fullClassName);
55
55
  }
56
56
 
57
- return function(el, cssClass) {
58
- if (el.className) {
59
- el.className = el.className.replace(new RegExp("(^|\\s)" + cssClass + "(\\s|$)"), replacer);
57
+ // Inefficient, inelegant nonsense for IE's svg element, which has no classList and non-HTML className implementation
58
+ function hasClass(el, className) {
59
+ if (typeof el.classList == "object") {
60
+ return el.classList.contains(className);
61
+ } else {
62
+ var classNameSupported = (typeof el.className == "string");
63
+ var elClass = classNameSupported ? el.className : el.getAttribute("class");
64
+ return classNameContainsClass(elClass, className);
60
65
  }
61
- };
62
- })();
63
-
64
- function sortClassName(className) {
65
- return className.split(/\s+/).sort().join(" ");
66
- }
66
+ }
67
67
 
68
- function getSortedClassName(el) {
69
- return sortClassName(el.className);
70
- }
68
+ function addClass(el, className) {
69
+ if (typeof el.classList == "object") {
70
+ el.classList.add(className);
71
+ } else {
72
+ var classNameSupported = (typeof el.className == "string");
73
+ var elClass = classNameSupported ? el.className : el.getAttribute("class");
74
+ if (elClass) {
75
+ if (!classNameContainsClass(elClass, className)) {
76
+ elClass += " " + className;
77
+ }
78
+ } else {
79
+ elClass = className;
80
+ }
81
+ if (classNameSupported) {
82
+ el.className = elClass;
83
+ } else {
84
+ el.setAttribute("class", elClass);
85
+ }
86
+ }
87
+ }
71
88
 
72
- function haveSameClasses(el1, el2) {
73
- return getSortedClassName(el1) == getSortedClassName(el2);
74
- }
89
+ var removeClass = (function() {
90
+ function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
91
+ return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
92
+ }
75
93
 
76
- function movePosition(position, oldParent, oldIndex, newParent, newIndex) {
77
- var node = position.node, offset = position.offset;
78
- var newNode = node, newOffset = offset;
94
+ return function(el, className) {
95
+ if (typeof el.classList == "object") {
96
+ el.classList.remove(className);
97
+ } else {
98
+ var classNameSupported = (typeof el.className == "string");
99
+ var elClass = classNameSupported ? el.className : el.getAttribute("class");
100
+ elClass = elClass.replace(new RegExp("(^|\\s)" + className + "(\\s|$)"), replacer);
101
+ if (classNameSupported) {
102
+ el.className = elClass;
103
+ } else {
104
+ el.setAttribute("class", elClass);
105
+ }
106
+ }
107
+ };
108
+ })();
79
109
 
80
- if (node == newParent && offset > newIndex) {
81
- ++newOffset;
110
+ function getClass(el) {
111
+ var classNameSupported = (typeof el.className == "string");
112
+ return classNameSupported ? el.className : el.getAttribute("class");
82
113
  }
83
114
 
84
- if (node == oldParent && (offset == oldIndex || offset == oldIndex + 1)) {
85
- newNode = newParent;
86
- newOffset += newIndex - oldIndex;
115
+ function sortClassName(className) {
116
+ return className && className.split(/\s+/).sort().join(" ");
87
117
  }
88
118
 
89
- if (node == oldParent && offset > oldIndex + 1) {
90
- --newOffset;
119
+ function getSortedClassName(el) {
120
+ return sortClassName( getClass(el) );
91
121
  }
92
122
 
93
- position.node = newNode;
94
- position.offset = newOffset;
95
- }
96
-
97
- function movePositionWhenRemovingNode(position, parentNode, index) {
98
- if (position.node == parentNode && position.offset > index) {
99
- --position.offset;
123
+ function haveSameClasses(el1, el2) {
124
+ return getSortedClassName(el1) == getSortedClassName(el2);
100
125
  }
101
- }
102
126
 
103
- function movePreservingPositions(node, newParent, newIndex, positionsToPreserve) {
104
- // For convenience, allow newIndex to be -1 to mean "insert at the end".
105
- if (newIndex == -1) {
106
- newIndex = newParent.childNodes.length;
127
+ function hasAllClasses(el, className) {
128
+ var classes = className.split(/\s+/);
129
+ for (var i = 0, len = classes.length; i < len; ++i) {
130
+ if (!hasClass(el, trim(classes[i]))) {
131
+ return false;
132
+ }
133
+ }
134
+ return true;
107
135
  }
108
136
 
109
- var oldParent = node.parentNode;
110
- var oldIndex = dom.getNodeIndex(node);
111
-
112
- for (var i = 0, position; position = positionsToPreserve[i++]; ) {
113
- movePosition(position, oldParent, oldIndex, newParent, newIndex);
137
+ function canTextBeStyled(textNode) {
138
+ var parent = textNode.parentNode;
139
+ return (parent && parent.nodeType == 1 && !/^(textarea|style|script|select|iframe)$/i.test(parent.nodeName));
114
140
  }
115
141
 
116
- // Now actually move the node.
117
- if (newParent.childNodes.length == newIndex) {
118
- newParent.appendChild(node);
119
- } else {
120
- newParent.insertBefore(node, newParent.childNodes[newIndex]);
121
- }
122
- }
123
-
124
- function removePreservingPositions(node, positionsToPreserve) {
142
+ function movePosition(position, oldParent, oldIndex, newParent, newIndex) {
143
+ var posNode = position.node, posOffset = position.offset;
144
+ var newNode = posNode, newOffset = posOffset;
125
145
 
126
- var oldParent = node.parentNode;
127
- var oldIndex = dom.getNodeIndex(node);
146
+ if (posNode == newParent && posOffset > newIndex) {
147
+ ++newOffset;
148
+ }
128
149
 
129
- for (var i = 0, position; position = positionsToPreserve[i++]; ) {
130
- movePositionWhenRemovingNode(position, oldParent, oldIndex);
131
- }
150
+ if (posNode == oldParent && (posOffset == oldIndex || posOffset == oldIndex + 1)) {
151
+ newNode = newParent;
152
+ newOffset += newIndex - oldIndex;
153
+ }
132
154
 
133
- node.parentNode.removeChild(node);
134
- }
155
+ if (posNode == oldParent && posOffset > oldIndex + 1) {
156
+ --newOffset;
157
+ }
135
158
 
136
- function moveChildrenPreservingPositions(node, newParent, newIndex, removeNode, positionsToPreserve) {
137
- var child, children = [];
138
- while ( (child = node.firstChild) ) {
139
- movePreservingPositions(child, newParent, newIndex++, positionsToPreserve);
140
- children.push(child);
159
+ position.node = newNode;
160
+ position.offset = newOffset;
141
161
  }
142
- if (removeNode) {
143
- node.parentNode.removeChild(node);
144
- }
145
- return children;
146
- }
147
162
 
148
- function replaceWithOwnChildrenPreservingPositions(element, positionsToPreserve) {
149
- return moveChildrenPreservingPositions(element, element.parentNode, dom.getNodeIndex(element), true, positionsToPreserve);
150
- }
163
+ function movePositionWhenRemovingNode(position, parentNode, index) {
164
+ if (position.node == parentNode && position.offset > index) {
165
+ --position.offset;
166
+ }
167
+ }
151
168
 
152
- function rangeSelectsAnyText(range, textNode) {
153
- var textNodeRange = range.cloneRange();
154
- textNodeRange.selectNodeContents(textNode);
169
+ function movePreservingPositions(node, newParent, newIndex, positionsToPreserve) {
170
+ // For convenience, allow newIndex to be -1 to mean "insert at the end".
171
+ if (newIndex == -1) {
172
+ newIndex = newParent.childNodes.length;
173
+ }
155
174
 
156
- var intersectionRange = textNodeRange.intersection(range);
157
- var text = intersectionRange ? intersectionRange.toString() : "";
158
- textNodeRange.detach();
175
+ var oldParent = node.parentNode;
176
+ var oldIndex = dom.getNodeIndex(node);
159
177
 
160
- return text != "";
161
- }
178
+ forEach(positionsToPreserve, function(position) {
179
+ movePosition(position, oldParent, oldIndex, newParent, newIndex);
180
+ });
162
181
 
163
- function getEffectiveTextNodes(range) {
164
- var nodes = range.getNodes([3]);
165
-
166
- // Optimization as per issue 145
167
-
168
- // Remove non-intersecting text nodes from the start of the range
169
- var start = 0, node;
170
- while ( (node = nodes[start]) && !rangeSelectsAnyText(range, node) ) {
171
- ++start;
182
+ // Now actually move the node.
183
+ if (newParent.childNodes.length == newIndex) {
184
+ newParent.appendChild(node);
185
+ } else {
186
+ newParent.insertBefore(node, newParent.childNodes[newIndex]);
187
+ }
172
188
  }
173
189
 
174
- // Remove non-intersecting text nodes from the start of the range
175
- var end = nodes.length - 1;
176
- while ( (node = nodes[end]) && !rangeSelectsAnyText(range, node) ) {
177
- --end;
190
+ function removePreservingPositions(node, positionsToPreserve) {
191
+
192
+ var oldParent = node.parentNode;
193
+ var oldIndex = dom.getNodeIndex(node);
194
+
195
+ forEach(positionsToPreserve, function(position) {
196
+ movePositionWhenRemovingNode(position, oldParent, oldIndex);
197
+ });
198
+
199
+ dom.removeNode(node);
178
200
  }
179
-
180
- return nodes.slice(start, end + 1);
181
- }
182
201
 
183
- function elementsHaveSameNonClassAttributes(el1, el2) {
184
- if (el1.attributes.length != el2.attributes.length) return false;
185
- for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
186
- attr1 = el1.attributes[i];
187
- name = attr1.name;
188
- if (name != "class") {
189
- attr2 = el2.attributes.getNamedItem(name);
190
- if ( (attr1 === null) != (attr2 === null) ) return false;
191
- if (attr1.specified != attr2.specified) return false;
192
- if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
202
+ function moveChildrenPreservingPositions(node, newParent, newIndex, removeNode, positionsToPreserve) {
203
+ var child, children = [];
204
+ while ( (child = node.firstChild) ) {
205
+ movePreservingPositions(child, newParent, newIndex++, positionsToPreserve);
206
+ children.push(child);
193
207
  }
208
+ if (removeNode) {
209
+ removePreservingPositions(node, positionsToPreserve);
210
+ }
211
+ return children;
194
212
  }
195
- return true;
196
- }
197
213
 
198
- function elementHasNonClassAttributes(el, exceptions) {
199
- for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) {
200
- attrName = el.attributes[i].name;
201
- if ( !(exceptions && contains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") {
202
- return true;
203
- }
214
+ function replaceWithOwnChildrenPreservingPositions(element, positionsToPreserve) {
215
+ return moveChildrenPreservingPositions(element, element.parentNode, dom.getNodeIndex(element), true, positionsToPreserve);
204
216
  }
205
- return false;
206
- }
207
217
 
208
- function elementHasProperties(el, props) {
209
- each(props, function(p, propValue) {
210
- if (typeof propValue == "object") {
211
- if (!elementHasProperties(el[p], propValue)) {
212
- return false;
213
- }
214
- } else if (el[p] !== propValue) {
215
- return false;
216
- }
217
- });
218
- return true;
219
- }
218
+ function rangeSelectsAnyText(range, textNode) {
219
+ var textNodeRange = range.cloneRange();
220
+ textNodeRange.selectNodeContents(textNode);
220
221
 
221
- var getComputedStyleProperty = dom.getComputedStyleProperty;
222
- var isEditableElement = (function() {
223
- var testEl = document.createElement("div");
224
- return typeof testEl.isContentEditable == "boolean" ?
225
- function (node) {
226
- return node && node.nodeType == 1 && node.isContentEditable;
227
- } :
228
- function (node) {
229
- if (!node || node.nodeType != 1 || node.contentEditable == "false") {
230
- return false;
231
- }
232
- return node.contentEditable == "true" || isEditableElement(node.parentNode);
233
- };
234
- })();
222
+ var intersectionRange = textNodeRange.intersection(range);
223
+ var text = intersectionRange ? intersectionRange.toString() : "";
235
224
 
236
- function isEditingHost(node) {
237
- var parent;
238
- return node && node.nodeType == 1
239
- && (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on")
240
- || (isEditableElement(node) && !isEditableElement(node.parentNode)));
241
- }
225
+ return text != "";
226
+ }
242
227
 
243
- function isEditable(node) {
244
- return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
245
- }
228
+ function getEffectiveTextNodes(range) {
229
+ var nodes = range.getNodes([3]);
246
230
 
247
- var inlineDisplayRegex = /^inline(-block|-table)?$/i;
231
+ // Optimization as per issue 145
248
232
 
249
- function isNonInlineElement(node) {
250
- return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
251
- }
233
+ // Remove non-intersecting text nodes from the start of the range
234
+ var start = 0, node;
235
+ while ( (node = nodes[start]) && !rangeSelectsAnyText(range, node) ) {
236
+ ++start;
237
+ }
252
238
 
253
- // White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html)
254
- var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/;
239
+ // Remove non-intersecting text nodes from the start of the range
240
+ var end = nodes.length - 1;
241
+ while ( (node = nodes[end]) && !rangeSelectsAnyText(range, node) ) {
242
+ --end;
243
+ }
255
244
 
256
- function isUnrenderedWhiteSpaceNode(node) {
257
- if (node.data.length == 0) {
258
- return true;
245
+ return nodes.slice(start, end + 1);
259
246
  }
260
- if (htmlNonWhiteSpaceRegex.test(node.data)) {
261
- return false;
247
+
248
+ function elementsHaveSameNonClassAttributes(el1, el2) {
249
+ if (el1.attributes.length != el2.attributes.length) return false;
250
+ for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
251
+ attr1 = el1.attributes[i];
252
+ name = attr1.name;
253
+ if (name != "class") {
254
+ attr2 = el2.attributes.getNamedItem(name);
255
+ if ( (attr1 === null) != (attr2 === null) ) return false;
256
+ if (attr1.specified != attr2.specified) return false;
257
+ if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
258
+ }
259
+ }
260
+ return true;
262
261
  }
263
- var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
264
- switch (cssWhiteSpace) {
265
- case "pre":
266
- case "pre-wrap":
267
- case "-moz-pre-wrap":
268
- return false;
269
- case "pre-line":
270
- if (/[\r\n]/.test(node.data)) {
271
- return false;
262
+
263
+ function elementHasNonClassAttributes(el, exceptions) {
264
+ for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) {
265
+ attrName = el.attributes[i].name;
266
+ if ( !(exceptions && contains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") {
267
+ return true;
272
268
  }
269
+ }
270
+ return false;
273
271
  }
274
272
 
275
- // We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a
276
- // non-inline element, it will not be rendered. This seems to be a good enough definition.
277
- return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling);
278
- }
273
+ var getComputedStyleProperty = dom.getComputedStyleProperty;
274
+ var isEditableElement = (function() {
275
+ var testEl = document.createElement("div");
276
+ return typeof testEl.isContentEditable == "boolean" ?
277
+ function (node) {
278
+ return node && node.nodeType == 1 && node.isContentEditable;
279
+ } :
280
+ function (node) {
281
+ if (!node || node.nodeType != 1 || node.contentEditable == "false") {
282
+ return false;
283
+ }
284
+ return node.contentEditable == "true" || isEditableElement(node.parentNode);
285
+ };
286
+ })();
287
+
288
+ function isEditingHost(node) {
289
+ var parent;
290
+ return node && node.nodeType == 1 &&
291
+ (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on") ||
292
+ (isEditableElement(node) && !isEditableElement(node.parentNode)));
293
+ }
279
294
 
280
- function getRangeBoundaries(ranges) {
281
- var positions = [], i, range;
282
- for (i = 0; range = ranges[i++]; ) {
283
- positions.push(
284
- new DomPosition(range.startContainer, range.startOffset),
285
- new DomPosition(range.endContainer, range.endOffset)
286
- );
295
+ function isEditable(node) {
296
+ return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
287
297
  }
288
- return positions;
289
- }
290
298
 
291
- function updateRangesFromBoundaries(ranges, positions) {
292
- for (var i = 0, range, start, end, len = ranges.length; i < len; ++i) {
293
- range = ranges[i];
294
- start = positions[i * 2];
295
- end = positions[i * 2 + 1];
296
- range.setStartAndEnd(start.node, start.offset, end.node, end.offset);
299
+ var inlineDisplayRegex = /^inline(-block|-table)?$/i;
300
+
301
+ function isNonInlineElement(node) {
302
+ return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
297
303
  }
298
- }
299
304
 
300
- function isSplitPoint(node, offset) {
301
- if (dom.isCharacterDataNode(node)) {
302
- if (offset == 0) {
303
- return !!node.previousSibling;
304
- } else if (offset == node.length) {
305
- return !!node.nextSibling;
306
- } else {
305
+ // White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html)
306
+ var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/;
307
+
308
+ function isUnrenderedWhiteSpaceNode(node) {
309
+ if (node.data.length == 0) {
307
310
  return true;
308
311
  }
309
- }
312
+ if (htmlNonWhiteSpaceRegex.test(node.data)) {
313
+ return false;
314
+ }
315
+ var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
316
+ switch (cssWhiteSpace) {
317
+ case "pre":
318
+ case "pre-wrap":
319
+ case "-moz-pre-wrap":
320
+ return false;
321
+ case "pre-line":
322
+ if (/[\r\n]/.test(node.data)) {
323
+ return false;
324
+ }
325
+ }
310
326
 
311
- return offset > 0 && offset < node.childNodes.length;
312
- }
327
+ // We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a
328
+ // non-inline element, it will not be rendered. This seems to be a good enough definition.
329
+ return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling);
330
+ }
313
331
 
314
- function splitNodeAt(node, descendantNode, descendantOffset, positionsToPreserve) {
315
- var newNode, parentNode;
316
- var splitAtStart = (descendantOffset == 0);
332
+ function getRangeBoundaries(ranges) {
333
+ var positions = [], i, range;
334
+ for (i = 0; range = ranges[i++]; ) {
335
+ positions.push(
336
+ new DomPosition(range.startContainer, range.startOffset),
337
+ new DomPosition(range.endContainer, range.endOffset)
338
+ );
339
+ }
340
+ return positions;
341
+ }
317
342
 
318
- if (dom.isAncestorOf(descendantNode, node)) {
319
- return node;
343
+ function updateRangesFromBoundaries(ranges, positions) {
344
+ for (var i = 0, range, start, end, len = ranges.length; i < len; ++i) {
345
+ range = ranges[i];
346
+ start = positions[i * 2];
347
+ end = positions[i * 2 + 1];
348
+ range.setStartAndEnd(start.node, start.offset, end.node, end.offset);
349
+ }
320
350
  }
321
351
 
322
- if (dom.isCharacterDataNode(descendantNode)) {
323
- var descendantIndex = dom.getNodeIndex(descendantNode);
324
- if (descendantOffset == 0) {
325
- descendantOffset = descendantIndex;
326
- } else if (descendantOffset == descendantNode.length) {
327
- descendantOffset = descendantIndex + 1;
328
- } else {
329
- throw module.createError("splitNodeAt() should not be called with offset in the middle of a data node ("
330
- + descendantOffset + " in " + descendantNode.data);
352
+ function isSplitPoint(node, offset) {
353
+ if (dom.isCharacterDataNode(node)) {
354
+ if (offset == 0) {
355
+ return !!node.previousSibling;
356
+ } else if (offset == node.length) {
357
+ return !!node.nextSibling;
358
+ } else {
359
+ return true;
360
+ }
331
361
  }
332
- descendantNode = descendantNode.parentNode;
362
+
363
+ return offset > 0 && offset < node.childNodes.length;
333
364
  }
334
365
 
335
- if (isSplitPoint(descendantNode, descendantOffset)) {
336
- // descendantNode is now guaranteed not to be a text or other character node
337
- newNode = descendantNode.cloneNode(false);
338
- parentNode = descendantNode.parentNode;
339
- if (newNode.id) {
340
- newNode.removeAttribute("id");
366
+ function splitNodeAt(node, descendantNode, descendantOffset, positionsToPreserve) {
367
+ var newNode, parentNode;
368
+ var splitAtStart = (descendantOffset == 0);
369
+
370
+ if (dom.isAncestorOf(descendantNode, node)) {
371
+ return node;
341
372
  }
342
- var child, newChildIndex = 0;
343
373
 
344
- while ( (child = descendantNode.childNodes[descendantOffset]) ) {
345
- movePreservingPositions(child, newNode, newChildIndex++, positionsToPreserve);
374
+ if (dom.isCharacterDataNode(descendantNode)) {
375
+ var descendantIndex = dom.getNodeIndex(descendantNode);
376
+ if (descendantOffset == 0) {
377
+ descendantOffset = descendantIndex;
378
+ } else if (descendantOffset == descendantNode.length) {
379
+ descendantOffset = descendantIndex + 1;
380
+ } else {
381
+ throw module.createError("splitNodeAt() should not be called with offset in the middle of a data node (" +
382
+ descendantOffset + " in " + descendantNode.data);
383
+ }
384
+ descendantNode = descendantNode.parentNode;
346
385
  }
347
- movePreservingPositions(newNode, parentNode, dom.getNodeIndex(descendantNode) + 1, positionsToPreserve);
348
- return (descendantNode == node) ? newNode : splitNodeAt(node, parentNode, dom.getNodeIndex(newNode), positionsToPreserve);
349
- } else if (node != descendantNode) {
350
- newNode = descendantNode.parentNode;
351
386
 
352
- // Work out a new split point in the parent node
353
- var newNodeIndex = dom.getNodeIndex(descendantNode);
387
+ if (isSplitPoint(descendantNode, descendantOffset)) {
388
+ // descendantNode is now guaranteed not to be a text or other character node
389
+ newNode = descendantNode.cloneNode(false);
390
+ parentNode = descendantNode.parentNode;
391
+ if (newNode.id) {
392
+ newNode.removeAttribute("id");
393
+ }
394
+ var child, newChildIndex = 0;
395
+
396
+ while ( (child = descendantNode.childNodes[descendantOffset]) ) {
397
+ movePreservingPositions(child, newNode, newChildIndex++, positionsToPreserve);
398
+ }
399
+ movePreservingPositions(newNode, parentNode, dom.getNodeIndex(descendantNode) + 1, positionsToPreserve);
400
+ return (descendantNode == node) ? newNode : splitNodeAt(node, parentNode, dom.getNodeIndex(newNode), positionsToPreserve);
401
+ } else if (node != descendantNode) {
402
+ newNode = descendantNode.parentNode;
403
+
404
+ // Work out a new split point in the parent node
405
+ var newNodeIndex = dom.getNodeIndex(descendantNode);
354
406
 
355
- if (!splitAtStart) {
356
- newNodeIndex++;
407
+ if (!splitAtStart) {
408
+ newNodeIndex++;
409
+ }
410
+ return splitNodeAt(node, newNode, newNodeIndex, positionsToPreserve);
357
411
  }
358
- return splitNodeAt(node, newNode, newNodeIndex, positionsToPreserve);
412
+ return node;
359
413
  }
360
- return node;
361
- }
362
414
 
363
- function areElementsMergeable(el1, el2) {
364
- return el1.tagName == el2.tagName
365
- && haveSameClasses(el1, el2)
366
- && elementsHaveSameNonClassAttributes(el1, el2)
367
- && getComputedStyleProperty(el1, "display") == "inline"
368
- && getComputedStyleProperty(el2, "display") == "inline";
369
- }
415
+ function areElementsMergeable(el1, el2) {
416
+ return el1.namespaceURI == el2.namespaceURI &&
417
+ el1.tagName.toLowerCase() == el2.tagName.toLowerCase() &&
418
+ haveSameClasses(el1, el2) &&
419
+ elementsHaveSameNonClassAttributes(el1, el2) &&
420
+ getComputedStyleProperty(el1, "display") == "inline" &&
421
+ getComputedStyleProperty(el2, "display") == "inline";
422
+ }
423
+
424
+ function createAdjacentMergeableTextNodeGetter(forward) {
425
+ var siblingPropName = forward ? "nextSibling" : "previousSibling";
370
426
 
371
- function createAdjacentMergeableTextNodeGetter(forward) {
372
- var siblingPropName = forward ? "nextSibling" : "previousSibling";
373
-
374
- return function(textNode, checkParentElement) {
375
- var el = textNode.parentNode;
376
- var adjacentNode = textNode[siblingPropName];
377
- if (adjacentNode) {
378
- // Can merge if the node's previous/next sibling is a text node
379
- if (adjacentNode && adjacentNode.nodeType == 3) {
380
- return adjacentNode;
381
- }
382
- } else if (checkParentElement) {
383
- // Compare text node parent element with its sibling
384
- adjacentNode = el[siblingPropName];
385
- if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) {
386
- var adjacentNodeChild = adjacentNode[forward ? "firstChild" : "lastChild"];
387
- if (adjacentNodeChild && adjacentNodeChild.nodeType == 3) {
388
- return adjacentNodeChild;
427
+ return function(textNode, checkParentElement) {
428
+ var el = textNode.parentNode;
429
+ var adjacentNode = textNode[siblingPropName];
430
+ if (adjacentNode) {
431
+ // Can merge if the node's previous/next sibling is a text node
432
+ if (adjacentNode && adjacentNode.nodeType == 3) {
433
+ return adjacentNode;
434
+ }
435
+ } else if (checkParentElement) {
436
+ // Compare text node parent element with its sibling
437
+ adjacentNode = el[siblingPropName];
438
+ if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) {
439
+ var adjacentNodeChild = adjacentNode[forward ? "firstChild" : "lastChild"];
440
+ if (adjacentNodeChild && adjacentNodeChild.nodeType == 3) {
441
+ return adjacentNodeChild;
442
+ }
389
443
  }
390
444
  }
391
- }
392
- return null;
393
- };
394
- }
395
-
396
- var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
397
- getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
445
+ return null;
446
+ };
447
+ }
398
448
 
449
+ var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
450
+ getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
399
451
 
400
- function Merge(firstNode) {
401
- this.isElementMerge = (firstNode.nodeType == 1);
402
- this.textNodes = [];
403
- var firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
404
- if (firstTextNode) {
405
- this.textNodes[0] = firstTextNode;
452
+
453
+ function Merge(firstNode) {
454
+ this.isElementMerge = (firstNode.nodeType == 1);
455
+ this.textNodes = [];
456
+ var firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
457
+ if (firstTextNode) {
458
+ this.textNodes[0] = firstTextNode;
459
+ }
406
460
  }
407
- }
408
461
 
409
- Merge.prototype = {
410
- doMerge: function(positionsToPreserve) {
411
- var textNodes = this.textNodes;
412
- var firstTextNode = textNodes[0];
413
- if (textNodes.length > 1) {
414
- var textParts = [], combinedTextLength = 0, textNode, parent;
415
- for (var i = 0, len = textNodes.length, j, position; i < len; ++i) {
416
- textNode = textNodes[i];
417
- parent = textNode.parentNode;
418
- if (i > 0) {
419
- parent.removeChild(textNode);
420
- if (!parent.hasChildNodes()) {
421
- parent.parentNode.removeChild(parent);
422
- }
423
- if (positionsToPreserve) {
424
- for (j = 0; position = positionsToPreserve[j++]; ) {
425
- // Handle case where position is inside the text node being merged into a preceding node
426
- if (position.node == textNode) {
427
- position.node = firstTextNode;
428
- position.offset += combinedTextLength;
429
- }
462
+ Merge.prototype = {
463
+ doMerge: function(positionsToPreserve) {
464
+ var textNodes = this.textNodes;
465
+ var firstTextNode = textNodes[0];
466
+ if (textNodes.length > 1) {
467
+ var firstTextNodeIndex = dom.getNodeIndex(firstTextNode);
468
+ var textParts = [], combinedTextLength = 0, textNode, parent;
469
+ forEach(textNodes, function(textNode, i) {
470
+ parent = textNode.parentNode;
471
+ if (i > 0) {
472
+ parent.removeChild(textNode);
473
+ if (!parent.hasChildNodes()) {
474
+ dom.removeNode(parent);
475
+ }
476
+ if (positionsToPreserve) {
477
+ forEach(positionsToPreserve, function(position) {
478
+ // Handle case where position is inside the text node being merged into a preceding node
479
+ if (position.node == textNode) {
480
+ position.node = firstTextNode;
481
+ position.offset += combinedTextLength;
482
+ }
483
+ // Handle case where both text nodes precede the position within the same parent node
484
+ if (position.node == parent && position.offset > firstTextNodeIndex) {
485
+ --position.offset;
486
+ if (position.offset == firstTextNodeIndex + 1 && i < len - 1) {
487
+ position.node = firstTextNode;
488
+ position.offset = combinedTextLength;
489
+ }
490
+ }
491
+ });
430
492
  }
431
493
  }
432
- }
433
- textParts[i] = textNode.data;
434
- combinedTextLength += textNode.data.length;
494
+ textParts[i] = textNode.data;
495
+ combinedTextLength += textNode.data.length;
496
+ });
497
+ firstTextNode.data = textParts.join("");
435
498
  }
436
- firstTextNode.data = textParts.join("");
437
- }
438
- return firstTextNode.data;
439
- },
499
+ return firstTextNode.data;
500
+ },
440
501
 
441
- getLength: function() {
442
- var i = this.textNodes.length, len = 0;
443
- while (i--) {
444
- len += this.textNodes[i].length;
445
- }
446
- return len;
447
- },
448
-
449
- toString: function() {
450
- var textParts = [];
451
- for (var i = 0, len = this.textNodes.length; i < len; ++i) {
452
- textParts[i] = "'" + this.textNodes[i].data + "'";
502
+ getLength: function() {
503
+ var i = this.textNodes.length, len = 0;
504
+ while (i--) {
505
+ len += this.textNodes[i].length;
506
+ }
507
+ return len;
508
+ },
509
+
510
+ toString: function() {
511
+ var textParts = [];
512
+ forEach(this.textNodes, function(textNode, i) {
513
+ textParts[i] = "'" + textNode.data + "'";
514
+ });
515
+ return "[Merge(" + textParts.join(",") + ")]";
453
516
  }
454
- return "[Merge(" + textParts.join(",") + ")]";
455
- }
456
- };
517
+ };
457
518
 
458
- var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly", "useExistingElements",
459
- "removeEmptyElements", "onElementCreate"];
519
+ var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly", "useExistingElements",
520
+ "removeEmptyElements", "onElementCreate"];
460
521
 
461
- // TODO: Populate this with every attribute name that corresponds to a property with a different name. Really??
462
- var attrNamesForProperties = {};
522
+ // TODO: Populate this with every attribute name that corresponds to a property with a different name. Really??
523
+ var attrNamesForProperties = {};
463
524
 
464
- function ClassApplier(cssClass, options, tagNames) {
465
- var normalize, i, len, propName, applier = this;
466
- applier.cssClass = cssClass;
525
+ function ClassApplier(className, options, tagNames) {
526
+ var normalize, i, len, propName, applier = this;
527
+ applier.cssClass = applier.className = className; // cssClass property is for backward compatibility
467
528
 
468
- var elementPropertiesFromOptions = null, elementAttributes = {};
529
+ var elementPropertiesFromOptions = null, elementAttributes = {};
469
530
 
470
- // Initialize from options object
471
- if (typeof options == "object" && options !== null) {
472
- tagNames = options.tagNames;
473
- elementPropertiesFromOptions = options.elementProperties;
474
- elementAttributes = options.elementAttributes;
531
+ // Initialize from options object
532
+ if (typeof options == "object" && options !== null) {
533
+ if (typeof options.elementTagName !== "undefined") {
534
+ options.elementTagName = options.elementTagName.toLowerCase();
535
+ }
536
+ tagNames = options.tagNames;
537
+ elementPropertiesFromOptions = options.elementProperties;
538
+ elementAttributes = options.elementAttributes;
475
539
 
476
- for (i = 0; propName = optionProperties[i++]; ) {
477
- if (options.hasOwnProperty(propName)) {
478
- applier[propName] = options[propName];
540
+ for (i = 0; propName = optionProperties[i++]; ) {
541
+ if (options.hasOwnProperty(propName)) {
542
+ applier[propName] = options[propName];
543
+ }
479
544
  }
480
- }
481
- normalize = options.normalize;
482
- } else {
483
- normalize = options;
484
- }
485
-
486
- // Backward compatibility: the second parameter can also be a Boolean indicating to normalize after unapplying
487
- applier.normalize = (typeof normalize == "undefined") ? true : normalize;
488
-
489
- // Initialize element properties and attribute exceptions
490
- applier.attrExceptions = [];
491
- var el = document.createElement(applier.elementTagName);
492
- applier.elementProperties = applier.copyPropertiesToElement(elementPropertiesFromOptions, el, true);
493
- each(elementAttributes, function(attrName) {
494
- applier.attrExceptions.push(attrName);
495
- });
496
- applier.elementAttributes = elementAttributes;
497
-
498
- applier.elementSortedClassName = applier.elementProperties.hasOwnProperty("className") ?
499
- applier.elementProperties.className : cssClass;
500
-
501
- // Initialize tag names
502
- applier.applyToAnyTagName = false;
503
- var type = typeof tagNames;
504
- if (type == "string") {
505
- if (tagNames == "*") {
506
- applier.applyToAnyTagName = true;
545
+ normalize = options.normalize;
507
546
  } else {
508
- applier.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
547
+ normalize = options;
509
548
  }
510
- } else if (type == "object" && typeof tagNames.length == "number") {
511
- applier.tagNames = [];
512
- for (i = 0, len = tagNames.length; i < len; ++i) {
513
- if (tagNames[i] == "*") {
549
+
550
+ // Backward compatibility: the second parameter can also be a Boolean indicating to normalize after unapplying
551
+ applier.normalize = (typeof normalize == "undefined") ? true : normalize;
552
+
553
+ // Initialize element properties and attribute exceptions
554
+ applier.attrExceptions = [];
555
+ var el = document.createElement(applier.elementTagName);
556
+ applier.elementProperties = applier.copyPropertiesToElement(elementPropertiesFromOptions, el, true);
557
+ each(elementAttributes, function(attrName, attrValue) {
558
+ applier.attrExceptions.push(attrName);
559
+ // Ensure each attribute value is a string
560
+ elementAttributes[attrName] = "" + attrValue;
561
+ });
562
+ applier.elementAttributes = elementAttributes;
563
+
564
+ applier.elementSortedClassName = applier.elementProperties.hasOwnProperty("className") ?
565
+ sortClassName(applier.elementProperties.className + " " + className) : className;
566
+
567
+ // Initialize tag names
568
+ applier.applyToAnyTagName = false;
569
+ var type = typeof tagNames;
570
+ if (type == "string") {
571
+ if (tagNames == "*") {
514
572
  applier.applyToAnyTagName = true;
515
573
  } else {
516
- applier.tagNames.push(tagNames[i].toLowerCase());
574
+ applier.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
575
+ }
576
+ } else if (type == "object" && typeof tagNames.length == "number") {
577
+ applier.tagNames = [];
578
+ for (i = 0, len = tagNames.length; i < len; ++i) {
579
+ if (tagNames[i] == "*") {
580
+ applier.applyToAnyTagName = true;
581
+ } else {
582
+ applier.tagNames.push(tagNames[i].toLowerCase());
583
+ }
517
584
  }
585
+ } else {
586
+ applier.tagNames = [applier.elementTagName];
518
587
  }
519
- } else {
520
- applier.tagNames = [applier.elementTagName];
521
588
  }
522
- }
523
589
 
524
- ClassApplier.prototype = {
525
- elementTagName: defaultTagName,
526
- elementProperties: {},
527
- elementAttributes: {},
528
- ignoreWhiteSpace: true,
529
- applyToEditableOnly: false,
530
- useExistingElements: true,
531
- removeEmptyElements: true,
532
- onElementCreate: null,
533
-
534
- copyPropertiesToElement: function(props, el, createCopy) {
535
- var s, elStyle, elProps = {}, elPropsStyle, propValue, elPropValue, attrName;
536
-
537
- for (var p in props) {
538
- if (props.hasOwnProperty(p)) {
539
- propValue = props[p];
540
- elPropValue = el[p];
541
-
542
- // Special case for class. The copied properties object has the applier's CSS class as well as its
543
- // own to simplify checks when removing styling elements
544
- if (p == "className") {
545
- addClass(el, propValue);
546
- addClass(el, this.cssClass);
547
- el[p] = sortClassName(el[p]);
548
- if (createCopy) {
549
- elProps[p] = el[p];
590
+ ClassApplier.prototype = {
591
+ elementTagName: defaultTagName,
592
+ elementProperties: {},
593
+ elementAttributes: {},
594
+ ignoreWhiteSpace: true,
595
+ applyToEditableOnly: false,
596
+ useExistingElements: true,
597
+ removeEmptyElements: true,
598
+ onElementCreate: null,
599
+
600
+ copyPropertiesToElement: function(props, el, createCopy) {
601
+ var s, elStyle, elProps = {}, elPropsStyle, propValue, elPropValue, attrName;
602
+
603
+ for (var p in props) {
604
+ if (props.hasOwnProperty(p)) {
605
+ propValue = props[p];
606
+ elPropValue = el[p];
607
+
608
+ // Special case for class. The copied properties object has the applier's class as well as its own
609
+ // to simplify checks when removing styling elements
610
+ if (p == "className") {
611
+ addClass(el, propValue);
612
+ addClass(el, this.className);
613
+ el[p] = sortClassName(el[p]);
614
+ if (createCopy) {
615
+ elProps[p] = propValue;
616
+ }
550
617
  }
551
- }
552
618
 
553
- // Special case for style
554
- else if (p == "style") {
555
- elStyle = elPropValue;
556
- if (createCopy) {
557
- elProps[p] = elPropsStyle = {};
558
- }
559
- for (s in props[p]) {
560
- elStyle[s] = propValue[s];
619
+ // Special case for style
620
+ else if (p == "style") {
621
+ elStyle = elPropValue;
561
622
  if (createCopy) {
562
- elPropsStyle[s] = elStyle[s];
623
+ elProps[p] = elPropsStyle = {};
624
+ }
625
+ for (s in props[p]) {
626
+ if (props[p].hasOwnProperty(s)) {
627
+ elStyle[s] = propValue[s];
628
+ if (createCopy) {
629
+ elPropsStyle[s] = elStyle[s];
630
+ }
631
+ }
632
+ }
633
+ this.attrExceptions.push(p);
634
+ } else {
635
+ el[p] = propValue;
636
+ // Copy the property back from the dummy element so that later comparisons to check whether
637
+ // elements may be removed are checking against the right value. For example, the href property
638
+ // of an element returns a fully qualified URL even if it was previously assigned a relative
639
+ // URL.
640
+ if (createCopy) {
641
+ elProps[p] = el[p];
642
+
643
+ // Not all properties map to identically-named attributes
644
+ attrName = attrNamesForProperties.hasOwnProperty(p) ? attrNamesForProperties[p] : p;
645
+ this.attrExceptions.push(attrName);
563
646
  }
564
- }
565
- this.attrExceptions.push(p);
566
- } else {
567
- el[p] = propValue;
568
- // Copy the property back from the dummy element so that later comparisons to check whether
569
- // elements may be removed are checking against the right value. For example, the href property
570
- // of an element returns a fully qualified URL even if it was previously assigned a relative
571
- // URL.
572
- if (createCopy) {
573
- elProps[p] = el[p];
574
-
575
- // Not all properties map to identically-named attributes
576
- attrName = attrNamesForProperties.hasOwnProperty(p) ? attrNamesForProperties[p] : p;
577
- this.attrExceptions.push(attrName);
578
647
  }
579
648
  }
580
649
  }
581
- }
582
650
 
583
- return createCopy ? elProps : "";
584
- },
585
-
586
- copyAttributesToElement: function(attrs, el) {
587
- for (var attrName in attrs) {
588
- if (attrs.hasOwnProperty(attrName)) {
589
- el.setAttribute(attrName, attrs[attrName]);
651
+ return createCopy ? elProps : "";
652
+ },
653
+
654
+ copyAttributesToElement: function(attrs, el) {
655
+ for (var attrName in attrs) {
656
+ if (attrs.hasOwnProperty(attrName) && !/^class(?:Name)?$/i.test(attrName)) {
657
+ el.setAttribute(attrName, attrs[attrName]);
658
+ }
590
659
  }
591
- }
592
- },
660
+ },
661
+
662
+ appliesToElement: function(el) {
663
+ return contains(this.tagNames, el.tagName.toLowerCase());
664
+ },
665
+
666
+ getEmptyElements: function(range) {
667
+ var applier = this;
668
+ return range.getNodes([1], function(el) {
669
+ return applier.appliesToElement(el) && !el.hasChildNodes();
670
+ });
671
+ },
672
+
673
+ hasClass: function(node) {
674
+ return node.nodeType == 1 &&
675
+ (this.applyToAnyTagName || this.appliesToElement(node)) &&
676
+ hasClass(node, this.className);
677
+ },
678
+
679
+ getSelfOrAncestorWithClass: function(node) {
680
+ while (node) {
681
+ if (this.hasClass(node)) {
682
+ return node;
683
+ }
684
+ node = node.parentNode;
685
+ }
686
+ return null;
687
+ },
593
688
 
594
- hasClass: function(node) {
595
- return node.nodeType == 1 &&
596
- contains(this.tagNames, node.tagName.toLowerCase()) &&
597
- hasClass(node, this.cssClass);
598
- },
689
+ isModifiable: function(node) {
690
+ return !this.applyToEditableOnly || isEditable(node);
691
+ },
599
692
 
600
- getSelfOrAncestorWithClass: function(node) {
601
- while (node) {
602
- if (this.hasClass(node)) {
603
- return node;
604
- }
605
- node = node.parentNode;
606
- }
607
- return null;
608
- },
693
+ // White space adjacent to an unwrappable node can be ignored for wrapping
694
+ isIgnorableWhiteSpaceNode: function(node) {
695
+ return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node);
696
+ },
609
697
 
610
- isModifiable: function(node) {
611
- return !this.applyToEditableOnly || isEditable(node);
612
- },
698
+ // Normalizes nodes after applying a class to a Range.
699
+ postApply: function(textNodes, range, positionsToPreserve, isUndo) {
700
+ var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
613
701
 
614
- // White space adjacent to an unwrappable node can be ignored for wrapping
615
- isIgnorableWhiteSpaceNode: function(node) {
616
- return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node);
617
- },
702
+ var merges = [], currentMerge;
618
703
 
619
- // Normalizes nodes after applying a CSS class to a Range.
620
- postApply: function(textNodes, range, positionsToPreserve, isUndo) {
621
- var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
704
+ var rangeStartNode = firstNode, rangeEndNode = lastNode;
705
+ var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
622
706
 
623
- var merges = [], currentMerge;
707
+ var textNode, precedingTextNode;
624
708
 
625
- var rangeStartNode = firstNode, rangeEndNode = lastNode;
626
- var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
709
+ // Check for every required merge and create a Merge object for each
710
+ forEach(textNodes, function(textNode) {
711
+ precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo);
712
+ if (precedingTextNode) {
713
+ if (!currentMerge) {
714
+ currentMerge = new Merge(precedingTextNode);
715
+ merges.push(currentMerge);
716
+ }
717
+ currentMerge.textNodes.push(textNode);
718
+ if (textNode === firstNode) {
719
+ rangeStartNode = currentMerge.textNodes[0];
720
+ rangeStartOffset = rangeStartNode.length;
721
+ }
722
+ if (textNode === lastNode) {
723
+ rangeEndNode = currentMerge.textNodes[0];
724
+ rangeEndOffset = currentMerge.getLength();
725
+ }
726
+ } else {
727
+ currentMerge = null;
728
+ }
729
+ });
627
730
 
628
- var textNode, precedingTextNode;
731
+ // Test whether the first node after the range needs merging
732
+ var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
629
733
 
630
- // Check for every required merge and create a Merge object for each
631
- for (var i = 0, len = textNodes.length; i < len; ++i) {
632
- textNode = textNodes[i];
633
- precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo);
634
- if (precedingTextNode) {
734
+ if (nextTextNode) {
635
735
  if (!currentMerge) {
636
- currentMerge = new Merge(precedingTextNode);
736
+ currentMerge = new Merge(lastNode);
637
737
  merges.push(currentMerge);
638
738
  }
639
- currentMerge.textNodes.push(textNode);
640
- if (textNode === firstNode) {
641
- rangeStartNode = currentMerge.textNodes[0];
642
- rangeStartOffset = rangeStartNode.length;
643
- }
644
- if (textNode === lastNode) {
645
- rangeEndNode = currentMerge.textNodes[0];
646
- rangeEndOffset = currentMerge.getLength();
647
- }
648
- } else {
649
- currentMerge = null;
739
+ currentMerge.textNodes.push(nextTextNode);
650
740
  }
651
- }
652
741
 
653
- // Test whether the first node after the range needs merging
654
- var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
742
+ // Apply the merges
743
+ if (merges.length) {
744
+ for (i = 0, len = merges.length; i < len; ++i) {
745
+ merges[i].doMerge(positionsToPreserve);
746
+ }
655
747
 
656
- if (nextTextNode) {
657
- if (!currentMerge) {
658
- currentMerge = new Merge(lastNode);
659
- merges.push(currentMerge);
748
+ // Set the range boundaries
749
+ range.setStartAndEnd(rangeStartNode, rangeStartOffset, rangeEndNode, rangeEndOffset);
660
750
  }
661
- currentMerge.textNodes.push(nextTextNode);
662
- }
663
-
664
- // Apply the merges
665
- if (merges.length) {
666
- for (i = 0, len = merges.length; i < len; ++i) {
667
- merges[i].doMerge(positionsToPreserve);
751
+ },
752
+
753
+ createContainer: function(parentNode) {
754
+ var doc = dom.getDocument(parentNode);
755
+ var namespace;
756
+ var el = createElementNSSupported && !dom.isHtmlNamespace(parentNode) && (namespace = parentNode.namespaceURI) ?
757
+ doc.createElementNS(parentNode.namespaceURI, this.elementTagName) :
758
+ doc.createElement(this.elementTagName);
759
+
760
+ this.copyPropertiesToElement(this.elementProperties, el, false);
761
+ this.copyAttributesToElement(this.elementAttributes, el);
762
+ addClass(el, this.className);
763
+ if (this.onElementCreate) {
764
+ this.onElementCreate(el, this);
668
765
  }
766
+ return el;
767
+ },
669
768
 
670
- // Set the range boundaries
671
- range.setStartAndEnd(rangeStartNode, rangeStartOffset, rangeEndNode, rangeEndOffset);
672
- }
673
- },
674
-
675
- createContainer: function(doc) {
676
- var el = doc.createElement(this.elementTagName);
677
- this.copyPropertiesToElement(this.elementProperties, el, false);
678
- this.copyAttributesToElement(this.elementAttributes, el);
679
- addClass(el, this.cssClass);
680
- if (this.onElementCreate) {
681
- this.onElementCreate(el, this);
682
- }
683
- return el;
684
- },
769
+ elementHasProperties: function(el, props) {
770
+ var applier = this;
771
+ return each(props, function(p, propValue) {
772
+ if (p == "className") {
773
+ // For checking whether we should reuse an existing element, we just want to check that the element
774
+ // has all the classes specified in the className property. When deciding whether the element is
775
+ // removable when unapplying a class, there is separate special handling to check whether the
776
+ // element has extra classes so the same simple check will do.
777
+ return hasAllClasses(el, propValue);
778
+ } else if (typeof propValue == "object") {
779
+ if (!applier.elementHasProperties(el[p], propValue)) {
780
+ return false;
781
+ }
782
+ } else if (el[p] !== propValue) {
783
+ return false;
784
+ }
785
+ });
786
+ },
685
787
 
686
- applyToTextNode: function(textNode, positionsToPreserve) {
687
- var parent = textNode.parentNode;
688
- if (parent.childNodes.length == 1 &&
689
- this.useExistingElements &&
690
- contains(this.tagNames, parent.tagName.toLowerCase()) &&
691
- elementHasProperties(parent, this.elementProperties)) {
788
+ elementHasAttributes: function(el, attrs) {
789
+ return each(attrs, function(name, value) {
790
+ if (el.getAttribute(name) !== value) {
791
+ return false;
792
+ }
793
+ });
794
+ },
795
+
796
+ applyToTextNode: function(textNode, positionsToPreserve) {
797
+
798
+ // Check whether the text node can be styled. Text within a <style> or <script> element, for example,
799
+ // should not be styled. See issue 283.
800
+ if (canTextBeStyled(textNode)) {
801
+ var parent = textNode.parentNode;
802
+ if (parent.childNodes.length == 1 &&
803
+ this.useExistingElements &&
804
+ this.appliesToElement(parent) &&
805
+ this.elementHasProperties(parent, this.elementProperties) &&
806
+ this.elementHasAttributes(parent, this.elementAttributes)) {
807
+
808
+ addClass(parent, this.className);
809
+ } else {
810
+ var textNodeParent = textNode.parentNode;
811
+ var el = this.createContainer(textNodeParent);
812
+ textNodeParent.insertBefore(el, textNode);
813
+ el.appendChild(textNode);
814
+ }
815
+ }
692
816
 
693
- addClass(parent, this.cssClass);
694
- } else {
695
- var el = this.createContainer(dom.getDocument(textNode));
696
- textNode.parentNode.insertBefore(el, textNode);
697
- el.appendChild(textNode);
698
- }
699
- },
700
-
701
- isRemovable: function(el) {
702
- return el.tagName.toLowerCase() == this.elementTagName
703
- && getSortedClassName(el) == this.elementSortedClassName
704
- && elementHasProperties(el, this.elementProperties)
705
- && !elementHasNonClassAttributes(el, this.attrExceptions)
706
- && this.isModifiable(el);
707
- },
708
-
709
- isEmptyContainer: function(el) {
710
- var childNodeCount = el.childNodes.length;
711
- return el.nodeType == 1
712
- && this.isRemovable(el)
713
- && (childNodeCount == 0 || (childNodeCount == 1 && this.isEmptyContainer(el.firstChild)));
714
- },
715
-
716
- removeEmptyContainers: function(range) {
717
- var applier = this;
718
- var nodesToRemove = range.getNodes([1], function(el) {
719
- return applier.isEmptyContainer(el);
720
- });
721
-
722
- var rangesToPreserve = [range]
723
- var positionsToPreserve = getRangeBoundaries(rangesToPreserve);
724
-
725
- for (var i = 0, node; node = nodesToRemove[i++]; ) {
726
- removePreservingPositions(node, positionsToPreserve);
727
- }
817
+ },
818
+
819
+ isRemovable: function(el) {
820
+ return el.tagName.toLowerCase() == this.elementTagName &&
821
+ getSortedClassName(el) == this.elementSortedClassName &&
822
+ this.elementHasProperties(el, this.elementProperties) &&
823
+ !elementHasNonClassAttributes(el, this.attrExceptions) &&
824
+ this.elementHasAttributes(el, this.elementAttributes) &&
825
+ this.isModifiable(el);
826
+ },
827
+
828
+ isEmptyContainer: function(el) {
829
+ var childNodeCount = el.childNodes.length;
830
+ return el.nodeType == 1 &&
831
+ this.isRemovable(el) &&
832
+ (childNodeCount == 0 || (childNodeCount == 1 && this.isEmptyContainer(el.firstChild)));
833
+ },
834
+
835
+ removeEmptyContainers: function(range) {
836
+ var applier = this;
837
+ var nodesToRemove = range.getNodes([1], function(el) {
838
+ return applier.isEmptyContainer(el);
839
+ });
840
+
841
+ var rangesToPreserve = [range];
842
+ var positionsToPreserve = getRangeBoundaries(rangesToPreserve);
843
+
844
+ forEach(nodesToRemove, function(node) {
845
+ removePreservingPositions(node, positionsToPreserve);
846
+ });
847
+
848
+ // Update the range from the preserved boundary positions
849
+ updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
850
+ },
851
+
852
+ undoToTextNode: function(textNode, range, ancestorWithClass, positionsToPreserve) {
853
+ if (!range.containsNode(ancestorWithClass)) {
854
+ // Split out the portion of the ancestor from which we can remove the class
855
+ //var parent = ancestorWithClass.parentNode, index = dom.getNodeIndex(ancestorWithClass);
856
+ var ancestorRange = range.cloneRange();
857
+ ancestorRange.selectNode(ancestorWithClass);
858
+ if (ancestorRange.isPointInRange(range.endContainer, range.endOffset)) {
859
+ splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset, positionsToPreserve);
860
+ range.setEndAfter(ancestorWithClass);
861
+ }
862
+ if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)) {
863
+ ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, positionsToPreserve);
864
+ }
865
+ }
728
866
 
729
- // Update the range from the preserved boundary positions
730
- updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
731
- },
867
+ if (this.isRemovable(ancestorWithClass)) {
868
+ replaceWithOwnChildrenPreservingPositions(ancestorWithClass, positionsToPreserve);
869
+ } else {
870
+ removeClass(ancestorWithClass, this.className);
871
+ }
872
+ },
732
873
 
733
- undoToTextNode: function(textNode, range, ancestorWithClass, positionsToPreserve) {
734
- if (!range.containsNode(ancestorWithClass)) {
735
- // Split out the portion of the ancestor from which we can remove the CSS class
736
- //var parent = ancestorWithClass.parentNode, index = dom.getNodeIndex(ancestorWithClass);
737
- var ancestorRange = range.cloneRange();
738
- ancestorRange.selectNode(ancestorWithClass);
739
- if (ancestorRange.isPointInRange(range.endContainer, range.endOffset)) {
740
- splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset, positionsToPreserve);
741
- range.setEndAfter(ancestorWithClass);
874
+ splitAncestorWithClass: function(container, offset, positionsToPreserve) {
875
+ var ancestorWithClass = this.getSelfOrAncestorWithClass(container);
876
+ if (ancestorWithClass) {
877
+ splitNodeAt(ancestorWithClass, container, offset, positionsToPreserve);
742
878
  }
743
- if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)) {
744
- ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, positionsToPreserve);
879
+ },
880
+
881
+ undoToAncestor: function(ancestorWithClass, positionsToPreserve) {
882
+ if (this.isRemovable(ancestorWithClass)) {
883
+ replaceWithOwnChildrenPreservingPositions(ancestorWithClass, positionsToPreserve);
884
+ } else {
885
+ removeClass(ancestorWithClass, this.className);
745
886
  }
746
- }
747
- if (this.isRemovable(ancestorWithClass)) {
748
- replaceWithOwnChildrenPreservingPositions(ancestorWithClass, positionsToPreserve);
749
- } else {
750
- removeClass(ancestorWithClass, this.cssClass);
751
- }
752
- },
887
+ },
753
888
 
754
- applyToRange: function(range, rangesToPreserve) {
755
- rangesToPreserve = rangesToPreserve || [];
889
+ applyToRange: function(range, rangesToPreserve) {
890
+ var applier = this;
891
+ rangesToPreserve = rangesToPreserve || [];
756
892
 
757
- // Create an array of range boundaries to preserve
758
- var positionsToPreserve = getRangeBoundaries(rangesToPreserve || []);
759
-
760
- range.splitBoundariesPreservingPositions(positionsToPreserve);
893
+ // Create an array of range boundaries to preserve
894
+ var positionsToPreserve = getRangeBoundaries(rangesToPreserve || []);
761
895
 
762
- // Tidy up the DOM by removing empty containers
763
- if (this.removeEmptyElements) {
764
- this.removeEmptyContainers(range);
765
- }
896
+ range.splitBoundariesPreservingPositions(positionsToPreserve);
766
897
 
767
- var textNodes = getEffectiveTextNodes(range);
898
+ // Tidy up the DOM by removing empty containers
899
+ if (applier.removeEmptyElements) {
900
+ applier.removeEmptyContainers(range);
901
+ }
768
902
 
769
- if (textNodes.length) {
770
- for (var i = 0, textNode; textNode = textNodes[i++]; ) {
771
- if (!this.isIgnorableWhiteSpaceNode(textNode) && !this.getSelfOrAncestorWithClass(textNode)
772
- && this.isModifiable(textNode)) {
773
- this.applyToTextNode(textNode, positionsToPreserve);
903
+ var textNodes = getEffectiveTextNodes(range);
904
+
905
+ if (textNodes.length) {
906
+ forEach(textNodes, function(textNode) {
907
+ if (!applier.isIgnorableWhiteSpaceNode(textNode) && !applier.getSelfOrAncestorWithClass(textNode) &&
908
+ applier.isModifiable(textNode)) {
909
+ applier.applyToTextNode(textNode, positionsToPreserve);
910
+ }
911
+ });
912
+ var lastTextNode = textNodes[textNodes.length - 1];
913
+ range.setStartAndEnd(textNodes[0], 0, lastTextNode, lastTextNode.length);
914
+ if (applier.normalize) {
915
+ applier.postApply(textNodes, range, positionsToPreserve, false);
774
916
  }
775
- }
776
- textNode = textNodes[textNodes.length - 1];
777
- range.setStartAndEnd(textNodes[0], 0, textNode, textNode.length);
778
- if (this.normalize) {
779
- this.postApply(textNodes, range, positionsToPreserve, false);
917
+
918
+ // Update the ranges from the preserved boundary positions
919
+ updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
780
920
  }
781
921
 
782
- // Update the ranges from the preserved boundary positions
783
- updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
784
- }
785
- },
922
+ // Apply classes to any appropriate empty elements
923
+ var emptyElements = applier.getEmptyElements(range);
786
924
 
787
- applyToRanges: function(ranges) {
925
+ forEach(emptyElements, function(el) {
926
+ addClass(el, applier.className);
927
+ });
928
+ },
788
929
 
789
- var i = ranges.length;
790
- while (i--) {
791
- this.applyToRange(ranges[i], ranges);
792
- }
930
+ applyToRanges: function(ranges) {
793
931
 
932
+ var i = ranges.length;
933
+ while (i--) {
934
+ this.applyToRange(ranges[i], ranges);
935
+ }
794
936
 
795
- return ranges;
796
- },
797
937
 
798
- applyToSelection: function(win) {
799
- var sel = api.getSelection(win);
800
- sel.setRanges( this.applyToRanges(sel.getAllRanges()) );
801
- },
938
+ return ranges;
939
+ },
802
940
 
803
- undoToRange: function(range, rangesToPreserve) {
804
- // Create an array of range boundaries to preserve
805
- rangesToPreserve = rangesToPreserve || [];
806
- var positionsToPreserve = getRangeBoundaries(rangesToPreserve);
941
+ applyToSelection: function(win) {
942
+ var sel = api.getSelection(win);
943
+ sel.setRanges( this.applyToRanges(sel.getAllRanges()) );
944
+ },
807
945
 
946
+ undoToRange: function(range, rangesToPreserve) {
947
+ var applier = this;
948
+ // Create an array of range boundaries to preserve
949
+ rangesToPreserve = rangesToPreserve || [];
950
+ var positionsToPreserve = getRangeBoundaries(rangesToPreserve);
808
951
 
809
- range.splitBoundariesPreservingPositions(positionsToPreserve);
810
952
 
811
- // Tidy up the DOM by removing empty containers
812
- if (this.removeEmptyElements) {
813
- this.removeEmptyContainers(range, positionsToPreserve);
814
- }
953
+ range.splitBoundariesPreservingPositions(positionsToPreserve);
815
954
 
816
- var textNodes = getEffectiveTextNodes(range);
817
- var textNode, ancestorWithClass;
818
- var lastTextNode = textNodes[textNodes.length - 1];
955
+ // Tidy up the DOM by removing empty containers
956
+ if (applier.removeEmptyElements) {
957
+ applier.removeEmptyContainers(range, positionsToPreserve);
958
+ }
819
959
 
820
- if (textNodes.length) {
821
- for (var i = 0, len = textNodes.length; i < len; ++i) {
822
- textNode = textNodes[i];
823
- ancestorWithClass = this.getSelfOrAncestorWithClass(textNode);
824
- if (ancestorWithClass && this.isModifiable(textNode)) {
825
- this.undoToTextNode(textNode, range, ancestorWithClass, positionsToPreserve);
960
+ var textNodes = getEffectiveTextNodes(range);
961
+ var textNode, ancestorWithClass;
962
+ var lastTextNode = textNodes[textNodes.length - 1];
963
+
964
+ if (textNodes.length) {
965
+ applier.splitAncestorWithClass(range.endContainer, range.endOffset, positionsToPreserve);
966
+ applier.splitAncestorWithClass(range.startContainer, range.startOffset, positionsToPreserve);
967
+ for (var i = 0, len = textNodes.length; i < len; ++i) {
968
+ textNode = textNodes[i];
969
+ ancestorWithClass = applier.getSelfOrAncestorWithClass(textNode);
970
+ if (ancestorWithClass && applier.isModifiable(textNode)) {
971
+ applier.undoToAncestor(ancestorWithClass, positionsToPreserve);
972
+ }
826
973
  }
827
-
828
974
  // Ensure the range is still valid
829
975
  range.setStartAndEnd(textNodes[0], 0, lastTextNode, lastTextNode.length);
830
- }
831
976
 
832
977
 
833
- if (this.normalize) {
834
- this.postApply(textNodes, range, positionsToPreserve, true);
835
- }
978
+ if (applier.normalize) {
979
+ applier.postApply(textNodes, range, positionsToPreserve, true);
980
+ }
836
981
 
837
- // Update the ranges from the preserved boundary positions
838
- updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
839
- }
840
- },
982
+ // Update the ranges from the preserved boundary positions
983
+ updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
984
+ }
841
985
 
842
- undoToRanges: function(ranges) {
843
- // Get ranges returned in document order
844
- var i = ranges.length;
986
+ // Remove class from any appropriate empty elements
987
+ var emptyElements = applier.getEmptyElements(range);
845
988
 
846
- while (i--) {
847
- this.undoToRange(ranges[i], ranges);
848
- }
989
+ forEach(emptyElements, function(el) {
990
+ removeClass(el, applier.className);
991
+ });
992
+ },
849
993
 
850
- return ranges;
851
- },
994
+ undoToRanges: function(ranges) {
995
+ // Get ranges returned in document order
996
+ var i = ranges.length;
852
997
 
853
- undoToSelection: function(win) {
854
- var sel = api.getSelection(win);
855
- var ranges = api.getSelection(win).getAllRanges();
856
- this.undoToRanges(ranges);
857
- sel.setRanges(ranges);
858
- },
998
+ while (i--) {
999
+ this.undoToRange(ranges[i], ranges);
1000
+ }
859
1001
 
860
- /*
861
- getTextSelectedByRange: function(textNode, range) {
862
- var textRange = range.cloneRange();
863
- textRange.selectNodeContents(textNode);
1002
+ return ranges;
1003
+ },
864
1004
 
865
- var intersectionRange = textRange.intersection(range);
866
- var text = intersectionRange ? intersectionRange.toString() : "";
867
- textRange.detach();
868
-
869
- return text;
870
- },
871
- */
1005
+ undoToSelection: function(win) {
1006
+ var sel = api.getSelection(win);
1007
+ var ranges = api.getSelection(win).getAllRanges();
1008
+ this.undoToRanges(ranges);
1009
+ sel.setRanges(ranges);
1010
+ },
872
1011
 
873
- isAppliedToRange: function(range) {
874
- if (range.collapsed || range.toString() == "") {
875
- return !!this.getSelfOrAncestorWithClass(range.commonAncestorContainer);
876
- } else {
877
- var textNodes = range.getNodes( [3] );
878
- if (textNodes.length)
879
- for (var i = 0, textNode; textNode = textNodes[i++]; ) {
880
- if (!this.isIgnorableWhiteSpaceNode(textNode) && rangeSelectsAnyText(range, textNode)
881
- && this.isModifiable(textNode) && !this.getSelfOrAncestorWithClass(textNode)) {
882
- return false;
1012
+ isAppliedToRange: function(range) {
1013
+ if (range.collapsed || range.toString() == "") {
1014
+ return !!this.getSelfOrAncestorWithClass(range.commonAncestorContainer);
1015
+ } else {
1016
+ var textNodes = range.getNodes( [3] );
1017
+ if (textNodes.length)
1018
+ for (var i = 0, textNode; textNode = textNodes[i++]; ) {
1019
+ if (!this.isIgnorableWhiteSpaceNode(textNode) && rangeSelectsAnyText(range, textNode) &&
1020
+ this.isModifiable(textNode) && !this.getSelfOrAncestorWithClass(textNode)) {
1021
+ return false;
1022
+ }
883
1023
  }
1024
+ return true;
884
1025
  }
885
- return true;
886
- }
887
- },
1026
+ },
888
1027
 
889
- isAppliedToRanges: function(ranges) {
890
- var i = ranges.length;
891
- if (i == 0) {
892
- return false;
893
- }
894
- while (i--) {
895
- if (!this.isAppliedToRange(ranges[i])) {
1028
+ isAppliedToRanges: function(ranges) {
1029
+ var i = ranges.length;
1030
+ if (i == 0) {
896
1031
  return false;
897
1032
  }
898
- }
899
- return true;
900
- },
901
-
902
- isAppliedToSelection: function(win) {
903
- var sel = api.getSelection(win);
904
- return this.isAppliedToRanges(sel.getAllRanges());
905
- },
1033
+ while (i--) {
1034
+ if (!this.isAppliedToRange(ranges[i])) {
1035
+ return false;
1036
+ }
1037
+ }
1038
+ return true;
1039
+ },
906
1040
 
907
- toggleRange: function(range) {
908
- if (this.isAppliedToRange(range)) {
909
- this.undoToRange(range);
910
- } else {
911
- this.applyToRange(range);
912
- }
913
- },
1041
+ isAppliedToSelection: function(win) {
1042
+ var sel = api.getSelection(win);
1043
+ return this.isAppliedToRanges(sel.getAllRanges());
1044
+ },
914
1045
 
915
- /*
916
- toggleRanges: function(ranges) {
917
- if (this.isAppliedToRanges(ranges)) {
918
- this.undoToRanges(ranges);
919
- } else {
920
- this.applyToRanges(ranges);
921
- }
922
- },
923
- */
1046
+ toggleRange: function(range) {
1047
+ if (this.isAppliedToRange(range)) {
1048
+ this.undoToRange(range);
1049
+ } else {
1050
+ this.applyToRange(range);
1051
+ }
1052
+ },
924
1053
 
925
- toggleSelection: function(win) {
926
- if (this.isAppliedToSelection(win)) {
927
- this.undoToSelection(win);
928
- } else {
929
- this.applyToSelection(win);
930
- }
931
- },
932
-
933
- getElementsWithClassIntersectingRange: function(range) {
934
- var elements = [];
935
- var applier = this;
936
- range.getNodes([3], function(textNode) {
937
- var el = applier.getSelfOrAncestorWithClass(textNode);
938
- if (el && !contains(elements, el)) {
939
- elements.push(el);
1054
+ toggleSelection: function(win) {
1055
+ if (this.isAppliedToSelection(win)) {
1056
+ this.undoToSelection(win);
1057
+ } else {
1058
+ this.applyToSelection(win);
940
1059
  }
941
- });
942
- return elements;
943
- },
944
-
945
- /*
946
- getElementsWithClassIntersectingSelection: function(win) {
947
- var sel = api.getSelection(win);
948
- var elements = [];
949
- var applier = this;
950
- sel.eachRange(function(range) {
951
- var rangeElements = applier.getElementsWithClassIntersectingRange(range);
952
- for (var i = 0, el; el = rangeElements[i++]; ) {
953
- if (!contains(elements, el)) {
1060
+ },
1061
+
1062
+ getElementsWithClassIntersectingRange: function(range) {
1063
+ var elements = [];
1064
+ var applier = this;
1065
+ range.getNodes([3], function(textNode) {
1066
+ var el = applier.getSelfOrAncestorWithClass(textNode);
1067
+ if (el && !contains(elements, el)) {
954
1068
  elements.push(el);
955
1069
  }
956
- }
957
- });
958
- return elements;
959
- },
960
- */
1070
+ });
1071
+ return elements;
1072
+ },
961
1073
 
962
- detach: function() {}
963
- };
1074
+ detach: function() {}
1075
+ };
964
1076
 
965
- function createClassApplier(cssClass, options, tagNames) {
966
- return new ClassApplier(cssClass, options, tagNames);
967
- }
1077
+ function createClassApplier(className, options, tagNames) {
1078
+ return new ClassApplier(className, options, tagNames);
1079
+ }
1080
+
1081
+ ClassApplier.util = {
1082
+ hasClass: hasClass,
1083
+ addClass: addClass,
1084
+ removeClass: removeClass,
1085
+ getClass: getClass,
1086
+ hasSameClasses: haveSameClasses,
1087
+ hasAllClasses: hasAllClasses,
1088
+ replaceWithOwnChildren: replaceWithOwnChildrenPreservingPositions,
1089
+ elementsHaveSameNonClassAttributes: elementsHaveSameNonClassAttributes,
1090
+ elementHasNonClassAttributes: elementHasNonClassAttributes,
1091
+ splitNodeAt: splitNodeAt,
1092
+ isEditableElement: isEditableElement,
1093
+ isEditingHost: isEditingHost,
1094
+ isEditable: isEditable
1095
+ };
968
1096
 
969
- ClassApplier.util = {
970
- hasClass: hasClass,
971
- addClass: addClass,
972
- removeClass: removeClass,
973
- hasSameClasses: haveSameClasses,
974
- replaceWithOwnChildren: replaceWithOwnChildrenPreservingPositions,
975
- elementsHaveSameNonClassAttributes: elementsHaveSameNonClassAttributes,
976
- elementHasNonClassAttributes: elementHasNonClassAttributes,
977
- splitNodeAt: splitNodeAt,
978
- isEditableElement: isEditableElement,
979
- isEditingHost: isEditingHost,
980
- isEditable: isEditable
981
- };
982
-
983
- api.CssClassApplier = api.ClassApplier = ClassApplier;
984
- api.createCssClassApplier = api.createClassApplier = createClassApplier;
985
- });
1097
+ api.CssClassApplier = api.ClassApplier = ClassApplier;
1098
+ api.createClassApplier = createClassApplier;
1099
+ util.createAliasForDeprecatedMethod(api, "createCssClassApplier", "createClassApplier", module);
1100
+ });
1101
+
1102
+ return rangy;
1103
+ }, this);