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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);