import rt 3.8.7
[freeside.git] / rt / share / html / NoAuth / RichText / FCKeditor / editor / _source / classes / fckw3crange.js
1 /*\r
2  * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
3  * Copyright (C) 2003-2009 Frederico Caldeira Knabben\r
4  *\r
5  * == BEGIN LICENSE ==\r
6  *\r
7  * Licensed under the terms of any of the following licenses at your\r
8  * choice:\r
9  *\r
10  *  - GNU General Public License Version 2 or later (the "GPL")\r
11  *    http://www.gnu.org/licenses/gpl.html\r
12  *\r
13  *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
14  *    http://www.gnu.org/licenses/lgpl.html\r
15  *\r
16  *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
17  *    http://www.mozilla.org/MPL/MPL-1.1.html\r
18  *\r
19  * == END LICENSE ==\r
20  *\r
21  * This class partially implements the W3C DOM Range for browser that don't\r
22  * support the standards (like IE):\r
23  * http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html\r
24  */\r
25 \r
26 var FCKW3CRange = function( parentDocument )\r
27 {\r
28         this._Document = parentDocument ;\r
29 \r
30         this.startContainer     = null ;\r
31         this.startOffset        = null ;\r
32         this.endContainer       = null ;\r
33         this.endOffset          = null ;\r
34         this.collapsed          = true ;\r
35 }\r
36 \r
37 FCKW3CRange.CreateRange = function( parentDocument )\r
38 {\r
39         // We could opt to use the Range implementation of the browsers. The problem\r
40         // is that every browser have different bugs on their implementations,\r
41         // mostly related to different interpretations of the W3C specifications.\r
42         // So, for now, let's use our implementation and pray for browsers fixings\r
43         // soon. Otherwise will go crazy on trying to find out workarounds.\r
44         /*\r
45         // Get the browser implementation of the range, if available.\r
46         if ( parentDocument.createRange )\r
47         {\r
48                 var range = parentDocument.createRange() ;\r
49                 if ( typeof( range.startContainer ) != 'undefined' )\r
50                         return range ;\r
51         }\r
52         */\r
53         return new FCKW3CRange( parentDocument ) ;\r
54 }\r
55 \r
56 FCKW3CRange.CreateFromRange = function( parentDocument, sourceRange )\r
57 {\r
58         var range = FCKW3CRange.CreateRange( parentDocument ) ;\r
59         range.setStart( sourceRange.startContainer, sourceRange.startOffset ) ;\r
60         range.setEnd( sourceRange.endContainer, sourceRange.endOffset ) ;\r
61         return range ;\r
62 }\r
63 \r
64 FCKW3CRange.prototype =\r
65 {\r
66 \r
67         _UpdateCollapsed : function()\r
68         {\r
69       this.collapsed = ( this.startContainer == this.endContainer && this.startOffset == this.endOffset ) ;\r
70         },\r
71 \r
72         // W3C requires a check for the new position. If it is after the end\r
73         // boundary, the range should be collapsed to the new start. It seams we\r
74         // will not need this check for our use of this class so we can ignore it for now.\r
75         setStart : function( refNode, offset )\r
76         {\r
77                 this.startContainer     = refNode ;\r
78                 this.startOffset        = offset ;\r
79 \r
80                 if ( !this.endContainer )\r
81                 {\r
82                         this.endContainer       = refNode ;\r
83                         this.endOffset          = offset ;\r
84                 }\r
85 \r
86                 this._UpdateCollapsed() ;\r
87         },\r
88 \r
89         // W3C requires a check for the new position. If it is before the start\r
90         // boundary, the range should be collapsed to the new end. It seams we\r
91         // will not need this check for our use of this class so we can ignore it for now.\r
92         setEnd : function( refNode, offset )\r
93         {\r
94                 this.endContainer       = refNode ;\r
95                 this.endOffset          = offset ;\r
96 \r
97                 if ( !this.startContainer )\r
98                 {\r
99                         this.startContainer     = refNode ;\r
100                         this.startOffset        = offset ;\r
101                 }\r
102 \r
103                 this._UpdateCollapsed() ;\r
104         },\r
105 \r
106         setStartAfter : function( refNode )\r
107         {\r
108                 this.setStart( refNode.parentNode, FCKDomTools.GetIndexOf( refNode ) + 1 ) ;\r
109         },\r
110 \r
111         setStartBefore : function( refNode )\r
112         {\r
113                 this.setStart( refNode.parentNode, FCKDomTools.GetIndexOf( refNode ) ) ;\r
114         },\r
115 \r
116         setEndAfter : function( refNode )\r
117         {\r
118                 this.setEnd( refNode.parentNode, FCKDomTools.GetIndexOf( refNode ) + 1 ) ;\r
119         },\r
120 \r
121         setEndBefore : function( refNode )\r
122         {\r
123                 this.setEnd( refNode.parentNode, FCKDomTools.GetIndexOf( refNode ) ) ;\r
124         },\r
125 \r
126         collapse : function( toStart )\r
127         {\r
128                 if ( toStart )\r
129                 {\r
130                         this.endContainer       = this.startContainer ;\r
131                         this.endOffset          = this.startOffset ;\r
132                 }\r
133                 else\r
134                 {\r
135                         this.startContainer     = this.endContainer ;\r
136                         this.startOffset        = this.endOffset ;\r
137                 }\r
138 \r
139                 this.collapsed = true ;\r
140         },\r
141 \r
142         selectNodeContents : function( refNode )\r
143         {\r
144                 this.setStart( refNode, 0 ) ;\r
145                 this.setEnd( refNode, refNode.nodeType == 3 ? refNode.data.length : refNode.childNodes.length ) ;\r
146         },\r
147 \r
148         insertNode : function( newNode )\r
149         {\r
150                 var startContainer = this.startContainer ;\r
151                 var startOffset = this.startOffset ;\r
152 \r
153                 // If we are in a text node.\r
154                 if ( startContainer.nodeType == 3 )\r
155                 {\r
156                         startContainer.splitText( startOffset ) ;\r
157 \r
158                         // Check if it is necessary to update the end boundary.\r
159                         if ( startContainer == this.endContainer )\r
160                                 this.setEnd( startContainer.nextSibling, this.endOffset - this.startOffset ) ;\r
161 \r
162                         // Insert the new node it after the text node.\r
163                         FCKDomTools.InsertAfterNode( startContainer, newNode ) ;\r
164 \r
165                         return ;\r
166                 }\r
167                 else\r
168                 {\r
169                         // Simply insert the new node before the current start node.\r
170                         startContainer.insertBefore( newNode, startContainer.childNodes[ startOffset ] || null ) ;\r
171 \r
172                         // Check if it is necessary to update the end boundary.\r
173                         if ( startContainer == this.endContainer )\r
174                         {\r
175                                 this.endOffset++ ;\r
176                                 this.collapsed = false ;\r
177                         }\r
178                 }\r
179         },\r
180 \r
181         deleteContents : function()\r
182         {\r
183                 if ( this.collapsed )\r
184                         return ;\r
185 \r
186                 this._ExecContentsAction( 0 ) ;\r
187         },\r
188 \r
189         extractContents : function()\r
190         {\r
191                 var docFrag = new FCKDocumentFragment( this._Document ) ;\r
192 \r
193                 if ( !this.collapsed )\r
194                         this._ExecContentsAction( 1, docFrag ) ;\r
195 \r
196                 return docFrag ;\r
197         },\r
198 \r
199         // The selection may be lost when cloning (due to the splitText() call).\r
200         cloneContents : function()\r
201         {\r
202                 var docFrag = new FCKDocumentFragment( this._Document ) ;\r
203 \r
204                 if ( !this.collapsed )\r
205                         this._ExecContentsAction( 2, docFrag ) ;\r
206 \r
207                 return docFrag ;\r
208         },\r
209 \r
210         _ExecContentsAction : function( action, docFrag )\r
211         {\r
212                 var startNode   = this.startContainer ;\r
213                 var endNode             = this.endContainer ;\r
214 \r
215                 var startOffset = this.startOffset ;\r
216                 var endOffset   = this.endOffset ;\r
217 \r
218                 var removeStartNode     = false ;\r
219                 var removeEndNode       = false ;\r
220 \r
221                 // Check the start and end nodes and make the necessary removals or changes.\r
222 \r
223                 // Start from the end, otherwise DOM mutations (splitText) made in the\r
224                 // start boundary may interfere on the results here.\r
225 \r
226                 // For text containers, we must simply split the node and point to the\r
227                 // second part. The removal will be handled by the rest of the code .\r
228                 if ( endNode.nodeType == 3 )\r
229                         endNode = endNode.splitText( endOffset ) ;\r
230                 else\r
231                 {\r
232                         // If the end container has children and the offset is pointing\r
233                         // to a child, then we should start from it.\r
234                         if ( endNode.childNodes.length > 0 )\r
235                         {\r
236                                 // If the offset points after the last node.\r
237                                 if ( endOffset > endNode.childNodes.length - 1 )\r
238                                 {\r
239                                         // Let's create a temporary node and mark it for removal.\r
240                                         endNode = FCKDomTools.InsertAfterNode( endNode.lastChild, this._Document.createTextNode('') ) ;\r
241                                         removeEndNode = true ;\r
242                                 }\r
243                                 else\r
244                                         endNode = endNode.childNodes[ endOffset ] ;\r
245                         }\r
246                 }\r
247 \r
248                 // For text containers, we must simply split the node. The removal will\r
249                 // be handled by the rest of the code .\r
250                 if ( startNode.nodeType == 3 )\r
251                 {\r
252                         startNode.splitText( startOffset ) ;\r
253 \r
254                         // In cases the end node is the same as the start node, the above\r
255                         // splitting will also split the end, so me must move the end to\r
256                         // the second part of the split.\r
257                         if ( startNode == endNode )\r
258                                 endNode = startNode.nextSibling ;\r
259                 }\r
260                 else\r
261                 {\r
262                         // If the start container has children and the offset is pointing\r
263                         // to a child, then we should start from its previous sibling.\r
264 \r
265                         // If the offset points to the first node, we don't have a\r
266                         // sibling, so let's use the first one, but mark it for removal.\r
267                         if ( startOffset == 0 )\r
268                         {\r
269                                 // Let's create a temporary node and mark it for removal.\r
270                                 startNode = startNode.insertBefore( this._Document.createTextNode(''), startNode.firstChild ) ;\r
271                                 removeStartNode = true ;\r
272                         }\r
273                         else if ( startOffset > startNode.childNodes.length - 1 )\r
274                         {\r
275                                 // Let's create a temporary node and mark it for removal.\r
276                                 startNode = startNode.appendChild( this._Document.createTextNode('') ) ;\r
277                                 removeStartNode = true ;\r
278                         }\r
279                         else\r
280                                 startNode = startNode.childNodes[ startOffset ].previousSibling ;\r
281                 }\r
282 \r
283                 // Get the parent nodes tree for the start and end boundaries.\r
284                 var startParents        = FCKDomTools.GetParents( startNode ) ;\r
285                 var endParents          = FCKDomTools.GetParents( endNode ) ;\r
286 \r
287                 // Compare them, to find the top most siblings.\r
288                 var i, topStart, topEnd ;\r
289 \r
290                 for ( i = 0 ; i < startParents.length ; i++ )\r
291                 {\r
292                         topStart        = startParents[i] ;\r
293                         topEnd          = endParents[i] ;\r
294 \r
295                         // The compared nodes will match until we find the top most\r
296                         // siblings (different nodes that have the same parent).\r
297                         // "i" will hold the index in the parents array for the top\r
298                         // most element.\r
299                         if ( topStart != topEnd )\r
300                                 break ;\r
301                 }\r
302 \r
303                 var clone, levelStartNode, levelClone, currentNode, currentSibling ;\r
304 \r
305                 if ( docFrag )\r
306                         clone = docFrag.RootNode ;\r
307 \r
308                 // Remove all successive sibling nodes for every node in the\r
309                 // startParents tree.\r
310                 for ( var j = i ; j < startParents.length ; j++ )\r
311                 {\r
312                         levelStartNode = startParents[j] ;\r
313 \r
314                         // For Extract and Clone, we must clone this level.\r
315                         if ( clone && levelStartNode != startNode )             // action = 0 = Delete\r
316                                 levelClone = clone.appendChild( levelStartNode.cloneNode( levelStartNode == startNode ) ) ;\r
317 \r
318                         currentNode = levelStartNode.nextSibling ;\r
319 \r
320                         while( currentNode )\r
321                         {\r
322                                 // Stop processing when the current node matches a node in the\r
323                                 // endParents tree or if it is the endNode.\r
324                                 if ( currentNode == endParents[j] || currentNode == endNode )\r
325                                         break ;\r
326 \r
327                                 // Cache the next sibling.\r
328                                 currentSibling = currentNode.nextSibling ;\r
329 \r
330                                 // If cloning, just clone it.\r
331                                 if ( action == 2 )      // 2 = Clone\r
332                                         clone.appendChild( currentNode.cloneNode( true ) ) ;\r
333                                 else\r
334                                 {\r
335                                         // Both Delete and Extract will remove the node.\r
336                                         currentNode.parentNode.removeChild( currentNode ) ;\r
337 \r
338                                         // When Extracting, move the removed node to the docFrag.\r
339                                         if ( action == 1 )      // 1 = Extract\r
340                                                 clone.appendChild( currentNode ) ;\r
341                                 }\r
342 \r
343                                 currentNode = currentSibling ;\r
344                         }\r
345 \r
346                         if ( clone )\r
347                                 clone = levelClone ;\r
348                 }\r
349 \r
350                 if ( docFrag )\r
351                         clone = docFrag.RootNode ;\r
352 \r
353                 // Remove all previous sibling nodes for every node in the\r
354                 // endParents tree.\r
355                 for ( var k = i ; k < endParents.length ; k++ )\r
356                 {\r
357                         levelStartNode = endParents[k] ;\r
358 \r
359                         // For Extract and Clone, we must clone this level.\r
360                         if ( action > 0 && levelStartNode != endNode )          // action = 0 = Delete\r
361                                 levelClone = clone.appendChild( levelStartNode.cloneNode( levelStartNode == endNode ) ) ;\r
362 \r
363                         // The processing of siblings may have already been done by the parent.\r
364                         if ( !startParents[k] || levelStartNode.parentNode != startParents[k].parentNode )\r
365                         {\r
366                                 currentNode = levelStartNode.previousSibling ;\r
367 \r
368                                 while( currentNode )\r
369                                 {\r
370                                         // Stop processing when the current node matches a node in the\r
371                                         // startParents tree or if it is the startNode.\r
372                                         if ( currentNode == startParents[k] || currentNode == startNode )\r
373                                                 break ;\r
374 \r
375                                         // Cache the next sibling.\r
376                                         currentSibling = currentNode.previousSibling ;\r
377 \r
378                                         // If cloning, just clone it.\r
379                                         if ( action == 2 )      // 2 = Clone\r
380                                                 clone.insertBefore( currentNode.cloneNode( true ), clone.firstChild ) ;\r
381                                         else\r
382                                         {\r
383                                                 // Both Delete and Extract will remove the node.\r
384                                                 currentNode.parentNode.removeChild( currentNode ) ;\r
385 \r
386                                                 // When Extracting, mode the removed node to the docFrag.\r
387                                                 if ( action == 1 )      // 1 = Extract\r
388                                                         clone.insertBefore( currentNode, clone.firstChild ) ;\r
389                                         }\r
390 \r
391                                         currentNode = currentSibling ;\r
392                                 }\r
393                         }\r
394 \r
395                         if ( clone )\r
396                                 clone = levelClone ;\r
397                 }\r
398 \r
399                 if ( action == 2 )              // 2 = Clone.\r
400                 {\r
401                         // No changes in the DOM should be done, so fix the split text (if any).\r
402 \r
403                         var startTextNode = this.startContainer ;\r
404                         if ( startTextNode.nodeType == 3 )\r
405                         {\r
406                                 startTextNode.data += startTextNode.nextSibling.data ;\r
407                                 startTextNode.parentNode.removeChild( startTextNode.nextSibling ) ;\r
408                         }\r
409 \r
410                         var endTextNode = this.endContainer ;\r
411                         if ( endTextNode.nodeType == 3 && endTextNode.nextSibling )\r
412                         {\r
413                                 endTextNode.data += endTextNode.nextSibling.data ;\r
414                                 endTextNode.parentNode.removeChild( endTextNode.nextSibling ) ;\r
415                         }\r
416                 }\r
417                 else\r
418                 {\r
419                         // Collapse the range.\r
420 \r
421                         // If a node has been partially selected, collapse the range between\r
422                         // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).\r
423                         if ( topStart && topEnd && ( startNode.parentNode != topStart.parentNode || endNode.parentNode != topEnd.parentNode ) )\r
424                         {\r
425                                 var endIndex = FCKDomTools.GetIndexOf( topEnd ) ;\r
426 \r
427                                 // If the start node is to be removed, we must correct the\r
428                                 // index to reflect the removal.\r
429                                 if ( removeStartNode && topEnd.parentNode == startNode.parentNode )\r
430                                         endIndex-- ;\r
431 \r
432                                 this.setStart( topEnd.parentNode, endIndex ) ;\r
433                         }\r
434 \r
435                         // Collapse it to the start.\r
436                         this.collapse( true ) ;\r
437                 }\r
438 \r
439                 // Cleanup any marked node.\r
440                 if( removeStartNode )\r
441                         startNode.parentNode.removeChild( startNode ) ;\r
442 \r
443                 if( removeEndNode && endNode.parentNode )\r
444                         endNode.parentNode.removeChild( endNode ) ;\r
445         },\r
446 \r
447         cloneRange : function()\r
448         {\r
449                 return FCKW3CRange.CreateFromRange( this._Document, this ) ;\r
450         }\r
451 } ;\r