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.
- checksums.yaml +7 -0
- data/lib/rangy-rails/version.rb +1 -1
- data/vendor/assets/javascripts/rangy-core.js +3731 -3
- data/vendor/assets/javascripts/rangy-cssclassapplier.js +973 -3
- data/vendor/assets/javascripts/rangy-highlighter.js +489 -3
- data/vendor/assets/javascripts/rangy-position.js +524 -15
- data/vendor/assets/javascripts/rangy-selectionsaverestore.js +223 -3
- data/vendor/assets/javascripts/rangy-serializer.js +282 -3
- data/vendor/assets/javascripts/rangy-textrange.js +1883 -3
- metadata +13 -13
@@ -9,7 +9,977 @@
|
|
9
9
|
*
|
10
10
|
* Copyright 2013, Tim Down
|
11
11
|
* Licensed under the MIT license.
|
12
|
-
* Version: 1.3alpha.
|
13
|
-
* Build date:
|
12
|
+
* Version: 1.3alpha.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
|
+
});
|