rangy-rails 1.3alpha.780.0 → 1.3alpha.804.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,7 +9,977 @@
9
9
  *
10
10
  * Copyright 2013, Tim Down
11
11
  * Licensed under the MIT license.
12
- * Version: 1.3alpha.780M
13
- * Build date: 17 May 2013
12
+ * Version: 1.3alpha.804
13
+ * Build date: 8 December 2013
14
14
  */
15
- rangy.createModule("ClassApplier",["WrappedSelection"],function(a,b){function g(a){return a.replace(/^\s\s*/,"").replace(/\s\s*$/,"")}function h(a,b){return a.className&&(new RegExp("(?:^|\\s)"+b+"(?:\\s|$)")).test(a.className)}function i(a,b){a.className?h(a,b)||(a.className+=" "+b):a.className=b}function k(a){return a.split(/\s+/).sort().join(" ")}function l(a){return k(a.className)}function m(a,b){return l(a)==l(b)}function n(a,b){return a.compareBoundaryPoints(b.START_TO_START,b)}function o(a,b,c,d,e){var f=a.node,g=a.offset,h=f,i=g;f==d&&g>e&&i++,f==b&&(g==c||g==c+1)&&(h=d,i+=e-c),f==b&&g>c+1&&i--,a.node=h,a.offset=i}function p(a,b,d,e){d==-1&&(d=b.childNodes.length);var f=a.parentNode,g=c.getNodeIndex(a);for(var h=0,i;i=e[h++];)o(i,f,g,b,d);b.childNodes.length==d?b.appendChild(a):b.insertBefore(a,b.childNodes[d])}function q(a,b,c,d,e){var f,g=[];while(f=a.firstChild)p(f,b,c++,e),g.push(f);return d&&a.parentNode.removeChild(a),g}function r(a,b){return q(a,a.parentNode,c.getNodeIndex(a),!0,b)}function s(a,b){var c=a.cloneRange();c.selectNodeContents(b);var d=c.intersection(a),e=d?d.toString():"";return c.detach(),e!=""}function t(a){var b=a.getNodes([3]),c=0,d;while((d=b[c])&&!s(a,d))++c;var e=b.length-1;while((d=b[e])&&!s(a,d))--e;return b.slice(c,e+1)}function u(a,b){if(a.attributes.length!=b.attributes.length)return!1;for(var c=0,d=a.attributes.length,e,f,g;c<d;++c){e=a.attributes[c],g=e.name;if(g!="class"){f=b.attributes.getNamedItem(g);if(e.specified!=f.specified)return!1;if(e.specified&&e.nodeValue!==f.nodeValue)return!1}}return!0}function v(a,b){for(var c=0,d=a.attributes.length,f;c<d;++c){f=a.attributes[c].name;if((!b||!e(b,f))&&a.attributes[c].specified&&f!="class")return!0}return!1}function w(a,b){var c;for(var d in b)if(b.hasOwnProperty(d)){c=b[d];if(typeof c=="object"){if(!w(a[d],c))return!1}else if(a[d]!==c)return!1}return!0}function z(a){var b;return a&&a.nodeType==1&&((b=a.parentNode)&&b.nodeType==9&&b.designMode=="on"||y(a)&&!y(a.parentNode))}function A(a){return(y(a)||a.nodeType!=1&&y(a.parentNode))&&!z(a)}function C(a){return a&&a.nodeType==1&&!B.test(x(a,"display"))}function E(a){if(a.data.length==0)return!0;if(D.test(a.data))return!1;var b=x(a.parentNode,"whiteSpace");switch(b){case"pre":case"pre-wrap":case"-moz-pre-wrap":return!1;case"pre-line":if(/[\r\n]/.test(a.data))return!1}return C(a.previousSibling)||C(a.nextSibling)}function F(a){var b=[],c,e;for(c=0;e=a[c++];)b.push(new d(e.startContainer,e.startOffset),new d(e.endContainer,e.endOffset));return b}function G(a,b){for(var c=0,d,e,f,g=a.length;c<g;++c)d=a[c],e=b[c*2],f=b[c*2+1],d.setStartAndEnd(e.node,e.offset,f.node,f.offset)}function H(a,b){return c.isCharacterDataNode(a)?b==0?!!a.previousSibling:b==a.length?!!a.nextSibling:!0:b>0&&b<a.childNodes.length}function I(a,d,e,f){var g,h,i=e==0;if(c.isAncestorOf(d,a))return a;if(c.isCharacterDataNode(d)){var j=c.getNodeIndex(d);if(e==0)e=j;else{if(e!=d.length)throw b.createError("splitNodeAt() should not be called with offset in the middle of a data node ("+e+" in "+d.data);e=j+1}d=d.parentNode}if(H(d,e)){g=d.cloneNode(!1),h=d.parentNode,g.id&&g.removeAttribute("id");var k,l=0;while(k=d.childNodes[e])p(k,g,l++,f);return p(g,h,c.getNodeIndex(d)+1,f),d==a?g:I(a,h,c.getNodeIndex(g),f)}if(a!=d){g=d.parentNode;var m=c.getNodeIndex(d);return i||m++,I(a,g,m,f)}return a}function J(a,b){return a.tagName==b.tagName&&m(a,b)&&u(a,b)&&x(a,"display")=="inline"&&x(b,"display")=="inline"}function K(a){var b=a?"nextSibling":"previousSibling";return function(c,d){var e=c.parentNode,f=c[b];if(f){if(f&&f.nodeType==3)return f}else if(d){f=e[b];if(f&&f.nodeType==1&&J(e,f))return f[a?"firstChild":"lastChild"]}return null}}function N(a){this.isElementMerge=a.nodeType==1,this.textNodes=[];var b=this.isElementMerge?a.lastChild:a;b&&(this.textNodes[0]=b)}function Q(a,b,c){this.cssClass=a;var d,e,f,h,i=null;if(typeof b=="object"&&b!==null){c=b.tagNames,i=b.elementProperties;for(e=0;h=O[e++];)b.hasOwnProperty(h)&&(this[h]=b[h]);d=b.normalize}else d=b;this.normalize=typeof d=="undefined"?!0:d,this.attrExceptions=[];var j=document.createElement(this.elementTagName);this.elementProperties=this.copyPropertiesToElement(i,j,!0),this.elementSortedClassName=this.elementProperties.hasOwnProperty("className")?this.elementProperties.className:a,this.applyToAnyTagName=!1;var k=typeof c;if(k=="string")c=="*"?this.applyToAnyTagName=!0:this.tagNames=g(c.toLowerCase()).split(/\s*,\s*/);else if(k=="object"&&typeof c.length=="number"){this.tagNames=[];for(e=0,f=c.length;e<f;++e)c[e]=="*"?this.applyToAnyTagName=!0:this.tagNames.push(c[e].toLowerCase())}else this.tagNames=[this.elementTagName]}function R(a,b,c){return new Q(a,b,c)}var c=a.dom,d=c.DomPosition,e=c.arrayContains,f="span",j=function(){function a(a,b,c){return b&&c?" ":""}return function(b,c){b.className&&(b.className=b.className.replace(new RegExp("(^|\\s)"+c+"(\\s|$)"),a))}}(),x=c.getComputedStyleProperty,y;(function(){var a=document.createElement("div");typeof a.isContentEditable=="boolean"?y=function(a){return a&&a.nodeType==1&&a.isContentEditable}:y=function(a){return!a||a.nodeType!=1||a.contentEditable=="false"?!1:a.contentEditable=="true"||y(a.parentNode)}})();var B=/^inline(-block|-table)?$/i,D=/[^\r\n\t\f \u200B]/,L=K(!1),M=K(!0);N.prototype={doMerge:function(a){var b=this.textNodes,c=b[0];if(b.length>1){var d=[],e=0,f,g;for(var h=0,i=b.length,j,k;h<i;++h){f=b[h],g=f.parentNode;if(h>0){g.removeChild(f),g.hasChildNodes()||g.parentNode.removeChild(g);if(a)for(j=0;k=a[j++];)k.node==f&&(k.node=c,k.offset+=e)}d[h]=f.data,e+=f.data.length}c.data=d.join("")}return c.data},getLength:function(){var a=this.textNodes.length,b=0;while(a--)b+=this.textNodes[a].length;return b},toString:function(){var a=[];for(var b=0,c=this.textNodes.length;b<c;++b)a[b]="'"+this.textNodes[b].data+"'";return"[Merge("+a.join(",")+")]"}};var O=["elementTagName","ignoreWhiteSpace","applyToEditableOnly","useExistingElements","removeEmptyElements"],P={};Q.prototype={elementTagName:f,elementProperties:{},ignoreWhiteSpace:!0,applyToEditableOnly:!1,useExistingElements:!0,removeEmptyElements:!0,copyPropertiesToElement:function(a,b,c){var d,e,f={},g,h,j,l;for(var m in a)if(a.hasOwnProperty(m)){h=a[m],j=b[m];if(m=="className")i(b,h),i(b,this.cssClass),b[m]=k(b[m]),c&&(f[m]=b[m]);else if(m=="style"){e=j,c&&(f[m]=g={});for(d in a[m])e[d]=h[d],c&&(g[d]=e[d]);this.attrExceptions.push(m)}else b[m]=h,c&&(f[m]=b[m],l=P.hasOwnProperty(m)?P[m]:m,this.attrExceptions.push(l))}return c?f:""},hasClass:function(a){return a.nodeType==1&&e(this.tagNames,a.tagName.toLowerCase())&&h(a,this.cssClass)},getSelfOrAncestorWithClass:function(a){while(a){if(this.hasClass(a))return a;a=a.parentNode}return null},isModifiable:function(a){return!this.applyToEditableOnly||A(a)},isIgnorableWhiteSpaceNode:function(a){return this.ignoreWhiteSpace&&a&&a.nodeType==3&&E(a)},postApply:function(a,b,c,d){var e=a[0],f=a[a.length-1],g=[],h,i=e,j=f,k=0,l=f.length,m,n;for(var o=0,p=a.length;o<p;++o)m=a[o],n=L(m,!d),n?(h||(h=new N(n),g.push(h)),h.textNodes.push(m),m===e&&(i=h.textNodes[0],k=i.length),m===f&&(j=h.textNodes[0],l=h.getLength())):h=null;var q=M(f,!d);q&&(h||(h=new N(f),g.push(h)),h.textNodes.push(q));if(g.length){for(o=0,p=g.length;o<p;++o)g[o].doMerge(c);b.setStartAndEnd(i,k,j,l)}},createContainer:function(a){var b=a.createElement(this.elementTagName);return this.copyPropertiesToElement(this.elementProperties,b,!1),i(b,this.cssClass),b},applyToTextNode:function(a,b){var d=a.parentNode;if(d.childNodes.length==1&&this.useExistingElements&&e(this.tagNames,d.tagName.toLowerCase())&&w(d,this.elementProperties))i(d,this.cssClass);else{var f=this.createContainer(c.getDocument(a));a.parentNode.insertBefore(f,a),f.appendChild(a)}},isRemovable:function(a){return a.tagName.toLowerCase()==this.elementTagName&&l(a)==this.elementSortedClassName&&w(a,this.elementProperties)&&!v(a,this.attrExceptions)&&this.isModifiable(a)},isEmptyContainer:function(a){var b=a.childNodes.length;return a.nodeType==1&&this.isRemovable(a)&&(b==0||b==1&&this.isEmptyContainer(a.firstChild))},removeEmptyContainers:function(a){var b=this,c=a.getNodes([1],function(a){return b.isEmptyContainer(a)});for(var d=0,e;e=c[d++];)e.parentNode.removeChild(e)},undoToTextNode:function(a,b,c,d){if(!b.containsNode(c)){var e=b.cloneRange();e.selectNode(c),e.isPointInRange(b.endContainer,b.endOffset)&&(I(c,b.endContainer,b.endOffset,d),b.setEndAfter(c)),e.isPointInRange(b.startContainer,b.startOffset)&&(c=I(c,b.startContainer,b.startOffset,d))}this.isRemovable(c)?r(c,d):j(c,this.cssClass)},applyToRange:function(a,b){b=b||[];var c=F(b||[]);a.splitBoundariesPreservingPositions(c),this.removeEmptyElements&&this.removeEmptyContainers(a);var d=t(a);if(d.length){for(var e=0,f;f=d[e++];)!this.isIgnorableWhiteSpaceNode(f)&&!this.getSelfOrAncestorWithClass(f)&&this.isModifiable(f)&&this.applyToTextNode(f,c);f=d[d.length-1],a.setStartAndEnd(d[0],0,f,f.length),this.normalize&&this.postApply(d,a,c,!1),G(b,c)}},applyToRanges:function(a){var b=a.length;while(b--)this.applyToRange(a[b],a);return a},applyToSelection:function(b){var c=a.getSelection(b);c.setRanges(this.applyToRanges(c.getAllRanges()))},undoToRange:function(a,b){b=b||[];var c=F(b);a.splitBoundariesPreservingPositions(c),this.removeEmptyElements&&this.removeEmptyContainers(a,c);var d=t(a),e,f,g=d[d.length-1];if(d.length){for(var h=0,i=d.length;h<i;++h)e=d[h],f=this.getSelfOrAncestorWithClass(e),f&&this.isModifiable(e)&&this.undoToTextNode(e,a,f,c),a.setStartAndEnd(d[0],0,g,g.length);this.normalize&&this.postApply(d,a,c,!0),G(b,c)}},undoToRanges:function(a){var b=a.length;while(b--)this.undoToRange(a[b],a);return a},undoToSelection:function(b){var c=a.getSelection(b),d=a.getSelection(b).getAllRanges();this.undoToRanges(d),c.setRanges(d)},getTextSelectedByRange:function(a,b){var c=b.cloneRange();c.selectNodeContents(a);var d=c.intersection(b),e=d?d.toString():"";return c.detach(),e},isAppliedToRange:function(a){if(a.collapsed||a.toString()=="")return!!this.getSelfOrAncestorWithClass(a.commonAncestorContainer);var b=a.getNodes([3]);if(b.length)for(var c=0,d;d=b[c++];)if(!this.isIgnorableWhiteSpaceNode(d)&&s(a,d)&&this.isModifiable(d)&&!this.getSelfOrAncestorWithClass(d))return!1;return!0},isAppliedToRanges:function(a){var b=a.length;if(b==0)return!1;while(b--)if(!this.isAppliedToRange(a[b]))return!1;return!0},isAppliedToSelection:function(b){var c=a.getSelection(b);return this.isAppliedToRanges(c.getAllRanges())},toggleRange:function(a){this.isAppliedToRange(a)?this.undoToRange(a):this.applyToRange(a)},toggleRanges:function(a){this.isAppliedToRanges(a)?this.undoToRanges(a):this.applyToRanges(a)},toggleSelection:function(a){this.isAppliedToSelection(a)?this.undoToSelection(a):this.applyToSelection(a)},getElementsWithClassIntersectingRange:function(a){var b=[],c=this;return a.getNodes([3],function(a){var d=c.getSelfOrAncestorWithClass(a);d&&!e(b,d)&&b.push(d)}),b},getElementsWithClassIntersectingSelection:function(b){var c=a.getSelection(b),d=[],f=this;return c.eachRange(function(a){var b=f.getElementsWithClassIntersectingRange(a);for(var c=0,g;g=b[c++];)e(d,g)||d.push(g)}),d},detach:function(){}},Q.util={hasClass:h,addClass:i,removeClass:j,hasSameClasses:m,replaceWithOwnChildren:r,elementsHaveSameNonClassAttributes:u,elementHasNonClassAttributes:v,splitNodeAt:I,isEditableElement:y,isEditingHost:z,isEditable:A},a.CssClassApplier=a.ClassApplier=Q,a.createCssClassApplier=a.createClassApplier=R})
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;
28
+ }
29
+ }
30
+ }
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
+
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
+ }
50
+ }
51
+
52
+ var removeClass = (function() {
53
+ function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
54
+ return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
55
+ }
56
+
57
+ return function(el, cssClass) {
58
+ if (el.className) {
59
+ el.className = el.className.replace(new RegExp("(^|\\s)" + cssClass + "(\\s|$)"), replacer);
60
+ }
61
+ };
62
+ })();
63
+
64
+ function sortClassName(className) {
65
+ return className.split(/\s+/).sort().join(" ");
66
+ }
67
+
68
+ function getSortedClassName(el) {
69
+ return sortClassName(el.className);
70
+ }
71
+
72
+ function haveSameClasses(el1, el2) {
73
+ return getSortedClassName(el1) == getSortedClassName(el2);
74
+ }
75
+
76
+ function movePosition(position, oldParent, oldIndex, newParent, newIndex) {
77
+ var node = position.node, offset = position.offset;
78
+ var newNode = node, newOffset = offset;
79
+
80
+ if (node == newParent && offset > newIndex) {
81
+ ++newOffset;
82
+ }
83
+
84
+ if (node == oldParent && (offset == oldIndex || offset == oldIndex + 1)) {
85
+ newNode = newParent;
86
+ newOffset += newIndex - oldIndex;
87
+ }
88
+
89
+ if (node == oldParent && offset > oldIndex + 1) {
90
+ --newOffset;
91
+ }
92
+
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;
100
+ }
101
+ }
102
+
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;
107
+ }
108
+
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);
114
+ }
115
+
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) {
125
+
126
+ var oldParent = node.parentNode;
127
+ var oldIndex = dom.getNodeIndex(node);
128
+
129
+ for (var i = 0, position; position = positionsToPreserve[i++]; ) {
130
+ movePositionWhenRemovingNode(position, oldParent, oldIndex);
131
+ }
132
+
133
+ node.parentNode.removeChild(node);
134
+ }
135
+
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);
141
+ }
142
+ if (removeNode) {
143
+ node.parentNode.removeChild(node);
144
+ }
145
+ return children;
146
+ }
147
+
148
+ function replaceWithOwnChildrenPreservingPositions(element, positionsToPreserve) {
149
+ return moveChildrenPreservingPositions(element, element.parentNode, dom.getNodeIndex(element), true, positionsToPreserve);
150
+ }
151
+
152
+ function rangeSelectsAnyText(range, textNode) {
153
+ var textNodeRange = range.cloneRange();
154
+ textNodeRange.selectNodeContents(textNode);
155
+
156
+ var intersectionRange = textNodeRange.intersection(range);
157
+ var text = intersectionRange ? intersectionRange.toString() : "";
158
+ textNodeRange.detach();
159
+
160
+ return text != "";
161
+ }
162
+
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;
172
+ }
173
+
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;
178
+ }
179
+
180
+ return nodes.slice(start, end + 1);
181
+ }
182
+
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;
193
+ }
194
+ }
195
+ return true;
196
+ }
197
+
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
+ }
204
+ }
205
+ return false;
206
+ }
207
+
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
+ }
220
+
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
+ })();
235
+
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
+ }
242
+
243
+ function isEditable(node) {
244
+ return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
245
+ }
246
+
247
+ var inlineDisplayRegex = /^inline(-block|-table)?$/i;
248
+
249
+ function isNonInlineElement(node) {
250
+ return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
251
+ }
252
+
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]/;
255
+
256
+ function isUnrenderedWhiteSpaceNode(node) {
257
+ if (node.data.length == 0) {
258
+ return true;
259
+ }
260
+ if (htmlNonWhiteSpaceRegex.test(node.data)) {
261
+ return false;
262
+ }
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;
272
+ }
273
+ }
274
+
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
+ }
279
+
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
+ );
287
+ }
288
+ return positions;
289
+ }
290
+
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);
297
+ }
298
+ }
299
+
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 {
307
+ return true;
308
+ }
309
+ }
310
+
311
+ return offset > 0 && offset < node.childNodes.length;
312
+ }
313
+
314
+ function splitNodeAt(node, descendantNode, descendantOffset, positionsToPreserve) {
315
+ var newNode, parentNode;
316
+ var splitAtStart = (descendantOffset == 0);
317
+
318
+ if (dom.isAncestorOf(descendantNode, node)) {
319
+ return node;
320
+ }
321
+
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);
331
+ }
332
+ descendantNode = descendantNode.parentNode;
333
+ }
334
+
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");
341
+ }
342
+ var child, newChildIndex = 0;
343
+
344
+ while ( (child = descendantNode.childNodes[descendantOffset]) ) {
345
+ movePreservingPositions(child, newNode, newChildIndex++, positionsToPreserve);
346
+ }
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
+
352
+ // Work out a new split point in the parent node
353
+ var newNodeIndex = dom.getNodeIndex(descendantNode);
354
+
355
+ if (!splitAtStart) {
356
+ newNodeIndex++;
357
+ }
358
+ return splitNodeAt(node, newNode, newNodeIndex, positionsToPreserve);
359
+ }
360
+ return node;
361
+ }
362
+
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
+ }
370
+
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;
389
+ }
390
+ }
391
+ }
392
+ return null;
393
+ };
394
+ }
395
+
396
+ var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
397
+ getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
398
+
399
+
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;
406
+ }
407
+ }
408
+
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
+ }
430
+ }
431
+ }
432
+ }
433
+ textParts[i] = textNode.data;
434
+ combinedTextLength += textNode.data.length;
435
+ }
436
+ firstTextNode.data = textParts.join("");
437
+ }
438
+ return firstTextNode.data;
439
+ },
440
+
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 + "'";
453
+ }
454
+ return "[Merge(" + textParts.join(",") + ")]";
455
+ }
456
+ };
457
+
458
+ var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly", "useExistingElements",
459
+ "removeEmptyElements", "onElementCreate"];
460
+
461
+ // TODO: Populate this with every attribute name that corresponds to a property with a different name. Really??
462
+ var attrNamesForProperties = {};
463
+
464
+ function ClassApplier(cssClass, options, tagNames) {
465
+ var normalize, i, len, propName, applier = this;
466
+ applier.cssClass = cssClass;
467
+
468
+ var elementPropertiesFromOptions = null, elementAttributes = {};
469
+
470
+ // Initialize from options object
471
+ if (typeof options == "object" && options !== null) {
472
+ tagNames = options.tagNames;
473
+ elementPropertiesFromOptions = options.elementProperties;
474
+ elementAttributes = options.elementAttributes;
475
+
476
+ for (i = 0; propName = optionProperties[i++]; ) {
477
+ if (options.hasOwnProperty(propName)) {
478
+ applier[propName] = options[propName];
479
+ }
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;
507
+ } else {
508
+ applier.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
509
+ }
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] == "*") {
514
+ applier.applyToAnyTagName = true;
515
+ } else {
516
+ applier.tagNames.push(tagNames[i].toLowerCase());
517
+ }
518
+ }
519
+ } else {
520
+ applier.tagNames = [applier.elementTagName];
521
+ }
522
+ }
523
+
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];
550
+ }
551
+ }
552
+
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];
561
+ if (createCopy) {
562
+ elPropsStyle[s] = elStyle[s];
563
+ }
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
+ }
579
+ }
580
+ }
581
+ }
582
+
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]);
590
+ }
591
+ }
592
+ },
593
+
594
+ hasClass: function(node) {
595
+ return node.nodeType == 1 &&
596
+ contains(this.tagNames, node.tagName.toLowerCase()) &&
597
+ hasClass(node, this.cssClass);
598
+ },
599
+
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
+ },
609
+
610
+ isModifiable: function(node) {
611
+ return !this.applyToEditableOnly || isEditable(node);
612
+ },
613
+
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
+ },
618
+
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];
622
+
623
+ var merges = [], currentMerge;
624
+
625
+ var rangeStartNode = firstNode, rangeEndNode = lastNode;
626
+ var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
627
+
628
+ var textNode, precedingTextNode;
629
+
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) {
635
+ if (!currentMerge) {
636
+ currentMerge = new Merge(precedingTextNode);
637
+ merges.push(currentMerge);
638
+ }
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;
650
+ }
651
+ }
652
+
653
+ // Test whether the first node after the range needs merging
654
+ var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
655
+
656
+ if (nextTextNode) {
657
+ if (!currentMerge) {
658
+ currentMerge = new Merge(lastNode);
659
+ merges.push(currentMerge);
660
+ }
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);
668
+ }
669
+
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
+ },
685
+
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)) {
692
+
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
+ }
728
+
729
+ // Update the range from the preserved boundary positions
730
+ updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
731
+ },
732
+
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);
742
+ }
743
+ if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)) {
744
+ ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, positionsToPreserve);
745
+ }
746
+ }
747
+ if (this.isRemovable(ancestorWithClass)) {
748
+ replaceWithOwnChildrenPreservingPositions(ancestorWithClass, positionsToPreserve);
749
+ } else {
750
+ removeClass(ancestorWithClass, this.cssClass);
751
+ }
752
+ },
753
+
754
+ applyToRange: function(range, rangesToPreserve) {
755
+ rangesToPreserve = rangesToPreserve || [];
756
+
757
+ // Create an array of range boundaries to preserve
758
+ var positionsToPreserve = getRangeBoundaries(rangesToPreserve || []);
759
+
760
+ range.splitBoundariesPreservingPositions(positionsToPreserve);
761
+
762
+ // Tidy up the DOM by removing empty containers
763
+ if (this.removeEmptyElements) {
764
+ this.removeEmptyContainers(range);
765
+ }
766
+
767
+ var textNodes = getEffectiveTextNodes(range);
768
+
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);
774
+ }
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);
780
+ }
781
+
782
+ // Update the ranges from the preserved boundary positions
783
+ updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
784
+ }
785
+ },
786
+
787
+ applyToRanges: function(ranges) {
788
+
789
+ var i = ranges.length;
790
+ while (i--) {
791
+ this.applyToRange(ranges[i], ranges);
792
+ }
793
+
794
+
795
+ return ranges;
796
+ },
797
+
798
+ applyToSelection: function(win) {
799
+ var sel = api.getSelection(win);
800
+ sel.setRanges( this.applyToRanges(sel.getAllRanges()) );
801
+ },
802
+
803
+ undoToRange: function(range, rangesToPreserve) {
804
+ // Create an array of range boundaries to preserve
805
+ rangesToPreserve = rangesToPreserve || [];
806
+ var positionsToPreserve = getRangeBoundaries(rangesToPreserve);
807
+
808
+
809
+ range.splitBoundariesPreservingPositions(positionsToPreserve);
810
+
811
+ // Tidy up the DOM by removing empty containers
812
+ if (this.removeEmptyElements) {
813
+ this.removeEmptyContainers(range, positionsToPreserve);
814
+ }
815
+
816
+ var textNodes = getEffectiveTextNodes(range);
817
+ var textNode, ancestorWithClass;
818
+ var lastTextNode = textNodes[textNodes.length - 1];
819
+
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);
826
+ }
827
+
828
+ // Ensure the range is still valid
829
+ range.setStartAndEnd(textNodes[0], 0, lastTextNode, lastTextNode.length);
830
+ }
831
+
832
+
833
+ if (this.normalize) {
834
+ this.postApply(textNodes, range, positionsToPreserve, true);
835
+ }
836
+
837
+ // Update the ranges from the preserved boundary positions
838
+ updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
839
+ }
840
+ },
841
+
842
+ undoToRanges: function(ranges) {
843
+ // Get ranges returned in document order
844
+ var i = ranges.length;
845
+
846
+ while (i--) {
847
+ this.undoToRange(ranges[i], ranges);
848
+ }
849
+
850
+ return ranges;
851
+ },
852
+
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
+ },
859
+
860
+ /*
861
+ getTextSelectedByRange: function(textNode, range) {
862
+ var textRange = range.cloneRange();
863
+ textRange.selectNodeContents(textNode);
864
+
865
+ var intersectionRange = textRange.intersection(range);
866
+ var text = intersectionRange ? intersectionRange.toString() : "";
867
+ textRange.detach();
868
+
869
+ return text;
870
+ },
871
+ */
872
+
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;
883
+ }
884
+ }
885
+ return true;
886
+ }
887
+ },
888
+
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])) {
896
+ return false;
897
+ }
898
+ }
899
+ return true;
900
+ },
901
+
902
+ isAppliedToSelection: function(win) {
903
+ var sel = api.getSelection(win);
904
+ return this.isAppliedToRanges(sel.getAllRanges());
905
+ },
906
+
907
+ toggleRange: function(range) {
908
+ if (this.isAppliedToRange(range)) {
909
+ this.undoToRange(range);
910
+ } else {
911
+ this.applyToRange(range);
912
+ }
913
+ },
914
+
915
+ /*
916
+ toggleRanges: function(ranges) {
917
+ if (this.isAppliedToRanges(ranges)) {
918
+ this.undoToRanges(ranges);
919
+ } else {
920
+ this.applyToRanges(ranges);
921
+ }
922
+ },
923
+ */
924
+
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);
940
+ }
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)) {
954
+ elements.push(el);
955
+ }
956
+ }
957
+ });
958
+ return elements;
959
+ },
960
+ */
961
+
962
+ detach: function() {}
963
+ };
964
+
965
+ function createClassApplier(cssClass, options, tagNames) {
966
+ return new ClassApplier(cssClass, options, tagNames);
967
+ }
968
+
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
+ });