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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ });