PluginProbe
Wp-Insert / 1.7.2
Wp-Insert v1.7.2
trunk 1.0 1.1 1.2 1.2.1 1.2.2 1.3.0 1.3.1 1.3.3 1.4 1.5.0 1.5.1 1.5.2 1.5.3 1.6.0 1.6.1 1.6.2 1.7 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 2.0 All 63 releases
wp-insert / fckeditor / editor / _source / classes / fckdomrange.js

fckdomrange.js in Wp-Insert 1.7.2, at fckeditor/editor/_source/classes/fckdomrange.js

936 lines 27.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
3 * Copyright (C) 2003-2009 Frederico Caldeira Knabben
4 *
5 * == BEGIN LICENSE ==
6 *
7 * Licensed under the terms of any of the following licenses at your
8 * choice:
9 *
10 * - GNU General Public License Version 2 or later (the "GPL")
11 * http://www.gnu.org/licenses/gpl.html
12 *
13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
14 * http://www.gnu.org/licenses/lgpl.html
15 *
16 * - Mozilla Public License Version 1.1 or later (the "MPL")
17 * http://www.mozilla.org/MPL/MPL-1.1.html
18 *
19 * == END LICENSE ==
20 *
21 * Class for working with a selection range, much like the W3C DOM Range, but
22 * it is not intended to be an implementation of the W3C interface.
23 */
24
25 var FCKDomRange = function( sourceWindow )
26 {
27 this.Window = sourceWindow ;
28 this._Cache = {} ;
29 }
30
31 FCKDomRange.prototype =
32 {
33
34 _UpdateElementInfo : function()
35 {
36 var innerRange = this._Range ;
37
38 if ( !innerRange )
39 this.Release( true ) ;
40 else
41 {
42 // For text nodes, the node itself is the StartNode.
43 var eStart = innerRange.startContainer ;
44
45 var oElementPath = new FCKElementPath( eStart ) ;
46 this.StartNode = eStart.nodeType == 3 ? eStart : eStart.childNodes[ innerRange.startOffset ] ;
47 this.StartContainer = eStart ;
48 this.StartBlock = oElementPath.Block ;
49 this.StartBlockLimit = oElementPath.BlockLimit ;
50
51 if ( innerRange.collapsed )
52 {
53 this.EndNode = this.StartNode ;
54 this.EndContainer = this.StartContainer ;
55 this.EndBlock = this.StartBlock ;
56 this.EndBlockLimit = this.StartBlockLimit ;
57 }
58 else
59 {
60 var eEnd = innerRange.endContainer ;
61
62 if ( eStart != eEnd )
63 oElementPath = new FCKElementPath( eEnd ) ;
64
65 // The innerRange.endContainer[ innerRange.endOffset ] is not
66 // usually part of the range, but the marker for the range end. So,
67 // let's get the previous available node as the real end.
68 var eEndNode = eEnd ;
69 if ( innerRange.endOffset == 0 )
70 {
71 while ( eEndNode && !eEndNode.previousSibling )
72 eEndNode = eEndNode.parentNode ;
73
74 if ( eEndNode )
75 eEndNode = eEndNode.previousSibling ;
76 }
77 else if ( eEndNode.nodeType == 1 )
78 eEndNode = eEndNode.childNodes[ innerRange.endOffset - 1 ] ;
79
80 this.EndNode = eEndNode ;
81 this.EndContainer = eEnd ;
82 this.EndBlock = oElementPath.Block ;
83 this.EndBlockLimit = oElementPath.BlockLimit ;
84 }
85 }
86
87 this._Cache = {} ;
88 },
89
90 CreateRange : function()
91 {
92 return new FCKW3CRange( this.Window.document ) ;
93 },
94
95 DeleteContents : function()
96 {
97 if ( this._Range )
98 {
99 this._Range.deleteContents() ;
100 this._UpdateElementInfo() ;
101 }
102 },
103
104 ExtractContents : function()
105 {
106 if ( this._Range )
107 {
108 var docFrag = this._Range.extractContents() ;
109 this._UpdateElementInfo() ;
110 return docFrag ;
111 }
112 return null ;
113 },
114
115 CheckIsCollapsed : function()
116 {
117 if ( this._Range )
118 return this._Range.collapsed ;
119
120 return false ;
121 },
122
123 Collapse : function( toStart )
124 {
125 if ( this._Range )
126 this._Range.collapse( toStart ) ;
127
128 this._UpdateElementInfo() ;
129 },
130
131 Clone : function()
132 {
133 var oClone = FCKTools.CloneObject( this ) ;
134
135 if ( this._Range )
136 oClone._Range = this._Range.cloneRange() ;
137
138 return oClone ;
139 },
140
141 MoveToNodeContents : function( targetNode )
142 {
143 if ( !this._Range )
144 this._Range = this.CreateRange() ;
145
146 this._Range.selectNodeContents( targetNode ) ;
147
148 this._UpdateElementInfo() ;
149 },
150
151 MoveToElementStart : function( targetElement )
152 {
153 this.SetStart(targetElement,1) ;
154 this.SetEnd(targetElement,1) ;
155 },
156
157 // Moves to the first editing point inside a element. For example, in a
158 // element tree like "<p><b><i></i></b> Text</p>", the start editing point
159 // is "<p><b><i>^</i></b> Text</p>" (inside <i>).
160 MoveToElementEditStart : function( targetElement )
161 {
162 var editableElement ;
163
164 while ( targetElement && targetElement.nodeType == 1 )
165 {
166 if ( FCKDomTools.CheckIsEditable( targetElement ) )
167 editableElement = targetElement ;
168 else if ( editableElement )
169 break ; // If we already found an editable element, stop the loop.
170
171 targetElement = targetElement.firstChild ;
172 }
173
174 if ( editableElement )
175 this.MoveToElementStart( editableElement ) ;
176 },
177
178 InsertNode : function( node )
179 {
180 if ( this._Range )
181 this._Range.insertNode( node ) ;
182 },
183
184 CheckIsEmpty : function()
185 {
186 if ( this.CheckIsCollapsed() )
187 return true ;
188
189 // Inserts the contents of the range in a div tag.
190 var eToolDiv = this.Window.document.createElement( 'div' ) ;
191 this._Range.cloneContents().AppendTo( eToolDiv ) ;
192
193 FCKDomTools.TrimNode( eToolDiv ) ;
194
195 return ( eToolDiv.innerHTML.length == 0 ) ;
196 },
197
198 /**
199 * Checks if the start boundary of the current range is "visually" (like a
200 * selection caret) at the beginning of the block. It means that some
201 * things could be brefore the range, like spaces or empty inline elements,
202 * but it would still be considered at the beginning of the block.
203 */
204 CheckStartOfBlock : function()
205 {
206 var cache = this._Cache ;
207 var bIsStartOfBlock = cache.IsStartOfBlock ;
208
209 if ( bIsStartOfBlock != undefined )
210 return bIsStartOfBlock ;
211
212 // Take the block reference.
213 var block = this.StartBlock || this.StartBlockLimit ;
214
215 var container = this._Range.startContainer ;
216 var offset = this._Range.startOffset ;
217 var currentNode ;
218
219 if ( offset > 0 )
220 {
221 // First, check the start container. If it is a text node, get the
222 // substring of the node value before the range offset.
223 if ( container.nodeType == 3 )
224 {
225 var textValue = container.nodeValue.substr( 0, offset ).Trim() ;
226
227 // If we have some text left in the container, we are not at
228 // the end for the block.
229 if ( textValue.length != 0 )
230 return cache.IsStartOfBlock = false ;
231 }
232 else
233 currentNode = container.childNodes[ offset - 1 ] ;
234 }
235
236 // We'll not have a currentNode if the container was a text node, or
237 // the offset is zero.
238 if ( !currentNode )
239 currentNode = FCKDomTools.GetPreviousSourceNode( container, true, null, block ) ;
240
241 while ( currentNode )
242 {
243 switch ( currentNode.nodeType )
244 {
245 case 1 :
246 // It's not an inline element.
247 if ( !FCKListsLib.InlineChildReqElements[ currentNode.nodeName.toLowerCase() ] )
248 return cache.IsStartOfBlock = false ;
249
250 break ;
251
252 case 3 :
253 // It's a text node with real text.
254 if ( currentNode.nodeValue.Trim().length > 0 )
255 return cache.IsStartOfBlock = false ;
256 }
257
258 currentNode = FCKDomTools.GetPreviousSourceNode( currentNode, false, null, block ) ;
259 }
260
261 return cache.IsStartOfBlock = true ;
262 },
263
264 /**
265 * Checks if the end boundary of the current range is "visually" (like a
266 * selection caret) at the end of the block. It means that some things
267 * could be after the range, like spaces, empty inline elements, or a
268 * single <br>, but it would still be considered at the end of the block.
269 */
270 CheckEndOfBlock : function( refreshSelection )
271 {
272 var isEndOfBlock = this._Cache.IsEndOfBlock ;
273
274 if ( isEndOfBlock != undefined )
275 return isEndOfBlock ;
276
277 // Take the block reference.
278 var block = this.EndBlock || this.EndBlockLimit ;
279
280 var container = this._Range.endContainer ;
281 var offset = this._Range.endOffset ;
282 var currentNode ;
283
284 // First, check the end container. If it is a text node, get the
285 // substring of the node value after the range offset.
286 if ( container.nodeType == 3 )
287 {
288 var textValue = container.nodeValue ;
289 if ( offset < textValue.length )
290 {
291 textValue = textValue.substr( offset ) ;
292
293 // If we have some text left in the container, we are not at
294 // the end for the block.
295 if ( textValue.Trim().length != 0 )
296 return this._Cache.IsEndOfBlock = false ;
297 }
298 }
299 else
300 currentNode = container.childNodes[ offset ] ;
301
302 // We'll not have a currentNode if the container was a text node, of
303 // the offset is out the container children limits (after it probably).
304 if ( !currentNode )
305 currentNode = FCKDomTools.GetNextSourceNode( container, true, null, block ) ;
306
307 var hadBr = false ;
308
309 while ( currentNode )
310 {
311 switch ( currentNode.nodeType )
312 {
313 case 1 :
314 var nodeName = currentNode.nodeName.toLowerCase() ;
315
316 // It's an inline element.
317 if ( FCKListsLib.InlineChildReqElements[ nodeName ] )
318 break ;
319
320 // It is the first <br> found.
321 if ( nodeName == 'br' && !hadBr )
322 {
323 hadBr = true ;
324 break ;
325 }
326
327 return this._Cache.IsEndOfBlock = false ;
328
329 case 3 :
330 // It's a text node with real text.
331 if ( currentNode.nodeValue.Trim().length > 0 )
332 return this._Cache.IsEndOfBlock = false ;
333 }
334
335 currentNode = FCKDomTools.GetNextSourceNode( currentNode, false, null, block ) ;
336 }
337
338 if ( refreshSelection )
339 this.Select() ;
340
341 return this._Cache.IsEndOfBlock = true ;
342 },
343
344 // This is an "intrusive" way to create a bookmark. It includes <span> tags
345 // in the range boundaries. The advantage of it is that it is possible to
346 // handle DOM mutations when moving back to the bookmark.
347 // Attention: the inclusion of nodes in the DOM is a design choice and
348 // should not be changed as there are other points in the code that may be
349 // using those nodes to perform operations. See GetBookmarkNode.
350 // For performance, includeNodes=true if intended to SelectBookmark.
351 CreateBookmark : function( includeNodes )
352 {
353 // Create the bookmark info (random IDs).
354 var oBookmark =
355 {
356 StartId : (new Date()).valueOf() + Math.floor(Math.random()*1000) + 'S',
357 EndId : (new Date()).valueOf() + Math.floor(Math.random()*1000) + 'E'
358 } ;
359
360 var oDoc = this.Window.document ;
361 var eStartSpan ;
362 var eEndSpan ;
363 var oClone ;
364
365 // For collapsed ranges, add just the start marker.
366 if ( !this.CheckIsCollapsed() )
367 {
368 eEndSpan = oDoc.createElement( 'span' ) ;
369 eEndSpan.style.display = 'none' ;
370 eEndSpan.id = oBookmark.EndId ;
371 eEndSpan.setAttribute( '_fck_bookmark', true ) ;
372
373 // For IE, it must have something inside, otherwise it may be
374 // removed during DOM operations.
375 // if ( FCKBrowserInfo.IsIE )
376 eEndSpan.innerHTML = '&nbsp;' ;
377
378 oClone = this.Clone() ;
379 oClone.Collapse( false ) ;
380 oClone.InsertNode( eEndSpan ) ;
381 }
382
383 eStartSpan = oDoc.createElement( 'span' ) ;
384 eStartSpan.style.display = 'none' ;
385 eStartSpan.id = oBookmark.StartId ;
386 eStartSpan.setAttribute( '_fck_bookmark', true ) ;
387
388 // For IE, it must have something inside, otherwise it may be removed
389 // during DOM operations.
390 // if ( FCKBrowserInfo.IsIE )
391 eStartSpan.innerHTML = '&nbsp;' ;
392
393 oClone = this.Clone() ;
394 oClone.Collapse( true ) ;
395 oClone.InsertNode( eStartSpan ) ;
396
397 if ( includeNodes )
398 {
399 oBookmark.StartNode = eStartSpan ;
400 oBookmark.EndNode = eEndSpan ;
401 }
402
403 // Update the range position.
404 if ( eEndSpan )
405 {
406 this.SetStart( eStartSpan, 4 ) ;
407 this.SetEnd( eEndSpan, 3 ) ;
408 }
409 else
410 this.MoveToPosition( eStartSpan, 4 ) ;
411
412 return oBookmark ;
413 },
414
415 // This one should be a part of a hypothetic "bookmark" object.
416 GetBookmarkNode : function( bookmark, start )
417 {
418 var doc = this.Window.document ;
419
420 if ( start )
421 return bookmark.StartNode || doc.getElementById( bookmark.StartId ) ;
422 else
423 return bookmark.EndNode || doc.getElementById( bookmark.EndId ) ;
424 },
425
426 MoveToBookmark : function( bookmark, preserveBookmark )
427 {
428 var eStartSpan = this.GetBookmarkNode( bookmark, true ) ;
429 var eEndSpan = this.GetBookmarkNode( bookmark, false ) ;
430
431 this.SetStart( eStartSpan, 3 ) ;
432
433 if ( !preserveBookmark )
434 FCKDomTools.RemoveNode( eStartSpan ) ;
435
436 // If collapsed, the end span will not be available.
437 if ( eEndSpan )
438 {
439 this.SetEnd( eEndSpan, 3 ) ;
440
441 if ( !preserveBookmark )
442 FCKDomTools.RemoveNode( eEndSpan ) ;
443 }
444 else
445 this.Collapse( true ) ;
446
447 this._UpdateElementInfo() ;
448 },
449
450 // Non-intrusive bookmark algorithm
451 CreateBookmark2 : function()
452 {
453 // If there is no range then get out of here.
454 // It happens on initial load in Safari #962 and if the editor it's hidden also in Firefox
455 if ( ! this._Range )
456 return { "Start" : 0, "End" : 0 } ;
457
458 // First, we record down the offset values
459 var bookmark =
460 {
461 "Start" : [ this._Range.startOffset ],
462 "End" : [ this._Range.endOffset ]
463 } ;
464 // Since we're treating the document tree as normalized, we need to backtrack the text lengths
465 // of previous text nodes into the offset value.
466 var curStart = this._Range.startContainer.previousSibling ;
467 var curEnd = this._Range.endContainer.previousSibling ;
468
469 // Also note that the node that we use for "address base" would change during backtracking.
470 var addrStart = this._Range.startContainer ;
471 var addrEnd = this._Range.endContainer ;
472 while ( curStart && curStart.nodeType == 3 && addrStart.nodeType == 3 )
473 {
474 bookmark.Start[0] += curStart.length ;
475 addrStart = curStart ;
476 curStart = curStart.previousSibling ;
477 }
478 while ( curEnd && curEnd.nodeType == 3 && addrEnd.nodeType == 3 )
479 {
480 bookmark.End[0] += curEnd.length ;
481 addrEnd = curEnd ;
482 curEnd = curEnd.previousSibling ;
483 }
484
485 // If the object pointed to by the startOffset and endOffset are text nodes, we need
486 // to backtrack and add in the text offset to the bookmark addresses.
487 if ( addrStart.nodeType == 1 && addrStart.childNodes[bookmark.Start[0]] && addrStart.childNodes[bookmark.Start[0]].nodeType == 3 )
488 {
489 var curNode = addrStart.childNodes[bookmark.Start[0]] ;
490 var offset = 0 ;
491 while ( curNode.previousSibling && curNode.previousSibling.nodeType == 3 )
492 {
493 curNode = curNode.previousSibling ;
494 offset += curNode.length ;
495 }
496 addrStart = curNode ;
497 bookmark.Start[0] = offset ;
498 }
499 if ( addrEnd.nodeType == 1 && addrEnd.childNodes[bookmark.End[0]] && addrEnd.childNodes[bookmark.End[0]].nodeType == 3 )
500 {
501 var curNode = addrEnd.childNodes[bookmark.End[0]] ;
502 var offset = 0 ;
503 while ( curNode.previousSibling && curNode.previousSibling.nodeType == 3 )
504 {
505 curNode = curNode.previousSibling ;
506 offset += curNode.length ;
507 }
508 addrEnd = curNode ;
509 bookmark.End[0] = offset ;
510 }
511
512 // Then, we record down the precise position of the container nodes
513 // by walking up the DOM tree and counting their childNode index
514 bookmark.Start = FCKDomTools.GetNodeAddress( addrStart, true ).concat( bookmark.Start ) ;
515 bookmark.End = FCKDomTools.GetNodeAddress( addrEnd, true ).concat( bookmark.End ) ;
516 return bookmark;
517 },
518
519 MoveToBookmark2 : function( bookmark )
520 {
521 // Reverse the childNode counting algorithm in CreateBookmark2()
522 var curStart = FCKDomTools.GetNodeFromAddress( this.Window.document, bookmark.Start.slice( 0, -1 ), true ) ;
523 var curEnd = FCKDomTools.GetNodeFromAddress( this.Window.document, bookmark.End.slice( 0, -1 ), true ) ;
524
525 // Generate the W3C Range object and update relevant data
526 this.Release( true ) ;
527 this._Range = new FCKW3CRange( this.Window.document ) ;
528 var startOffset = bookmark.Start[ bookmark.Start.length - 1 ] ;
529 var endOffset = bookmark.End[ bookmark.End.length - 1 ] ;
530 while ( curStart.nodeType == 3 && startOffset > curStart.length )
531 {
532 if ( ! curStart.nextSibling || curStart.nextSibling.nodeType != 3 )
533 break ;
534 startOffset -= curStart.length ;
535 curStart = curStart.nextSibling ;
536 }
537 while ( curEnd.nodeType == 3 && endOffset > curEnd.length )
538 {
539 if ( ! curEnd.nextSibling || curEnd.nextSibling.nodeType != 3 )
540 break ;
541 endOffset -= curEnd.length ;
542 curEnd = curEnd.nextSibling ;
543 }
544 this._Range.setStart( curStart, startOffset ) ;
545 this._Range.setEnd( curEnd, endOffset ) ;
546 this._UpdateElementInfo() ;
547 },
548
549 MoveToPosition : function( targetElement, position )
550 {
551 this.SetStart( targetElement, position ) ;
552 this.Collapse( true ) ;
553 },
554
555 /*
556 * Moves the position of the start boundary of the range to a specific position
557 * relatively to a element.
558 * @position:
559 * 1 = After Start <target>^contents</target>
560 * 2 = Before End <target>contents^</target>
561 * 3 = Before Start ^<target>contents</target>
562 * 4 = After End <target>contents</target>^
563 */
564 SetStart : function( targetElement, position, noInfoUpdate )
565 {
566 var oRange = this._Range ;
567 if ( !oRange )
568 oRange = this._Range = this.CreateRange() ;
569
570 switch( position )
571 {
572 case 1 : // After Start <target>^contents</target>
573 oRange.setStart( targetElement, 0 ) ;
574 break ;
575
576 case 2 : // Before End <target>contents^</target>
577 oRange.setStart( targetElement, targetElement.childNodes.length ) ;
578 break ;
579
580 case 3 : // Before Start ^<target>contents</target>
581 oRange.setStartBefore( targetElement ) ;
582 break ;
583
584 case 4 : // After End <target>contents</target>^
585 oRange.setStartAfter( targetElement ) ;
586 }
587
588 if ( !noInfoUpdate )
589 this._UpdateElementInfo() ;
590 },
591
592 /*
593 * Moves the position of the start boundary of the range to a specific position
594 * relatively to a element.
595 * @position:
596 * 1 = After Start <target>^contents</target>
597 * 2 = Before End <target>contents^</target>
598 * 3 = Before Start ^<target>contents</target>
599 * 4 = After End <target>contents</target>^
600 */
601 SetEnd : function( targetElement, position, noInfoUpdate )
602 {
603 var oRange = this._Range ;
604 if ( !oRange )
605 oRange = this._Range = this.CreateRange() ;
606
607 switch( position )
608 {
609 case 1 : // After Start <target>^contents</target>
610 oRange.setEnd( targetElement, 0 ) ;
611 break ;
612
613 case 2 : // Before End <target>contents^</target>
614 oRange.setEnd( targetElement, targetElement.childNodes.length ) ;
615 break ;
616
617 case 3 : // Before Start ^<target>contents</target>
618 oRange.setEndBefore( targetElement ) ;
619 break ;
620
621 case 4 : // After End <target>contents</target>^
622 oRange.setEndAfter( targetElement ) ;
623 }
624
625 if ( !noInfoUpdate )
626 this._UpdateElementInfo() ;
627 },
628
629 Expand : function( unit )
630 {
631 var oNode, oSibling ;
632
633 switch ( unit )
634 {
635 // Expand the range to include all inline parent elements if we are
636 // are in their boundary limits.
637 // For example (where [ ] are the range limits):
638 // Before => Some <b>[<i>Some sample text]</i></b>.
639 // After => Some [<b><i>Some sample text</i></b>].
640 case 'inline_elements' :
641 // Expand the start boundary.
642 if ( this._Range.startOffset == 0 )
643 {
644 oNode = this._Range.startContainer ;
645
646 if ( oNode.nodeType != 1 )
647 oNode = oNode.previousSibling ? null : oNode.parentNode ;
648
649 if ( oNode )
650 {
651 while ( FCKListsLib.InlineNonEmptyElements[ oNode.nodeName.toLowerCase() ] )
652 {
653 this._Range.setStartBefore( oNode ) ;
654
655 if ( oNode != oNode.parentNode.firstChild )
656 break ;
657
658 oNode = oNode.parentNode ;
659 }
660 }
661 }
662
663 // Expand the end boundary.
664 oNode = this._Range.endContainer ;
665 var offset = this._Range.endOffset ;
666
667 if ( ( oNode.nodeType == 3 && offset >= oNode.nodeValue.length ) || ( oNode.nodeType == 1 && offset >= oNode.childNodes.length ) || ( oNode.nodeType != 1 && oNode.nodeType != 3 ) )
668 {
669 if ( oNode.nodeType != 1 )
670 oNode = oNode.nextSibling ? null : oNode.parentNode ;
671
672 if ( oNode )
673 {
674 while ( FCKListsLib.InlineNonEmptyElements[ oNode.nodeName.toLowerCase() ] )
675 {
676 this._Range.setEndAfter( oNode ) ;
677
678 if ( oNode != oNode.parentNode.lastChild )
679 break ;
680
681 oNode = oNode.parentNode ;
682 }
683 }
684 }
685
686 break ;
687
688 case 'block_contents' :
689 case 'list_contents' :
690 var boundarySet = FCKListsLib.BlockBoundaries ;
691 if ( unit == 'list_contents' || FCKConfig.EnterMode == 'br' )
692 boundarySet = FCKListsLib.ListBoundaries ;
693
694 if ( this.StartBlock && FCKConfig.EnterMode != 'br' && unit == 'block_contents' )
695 this.SetStart( this.StartBlock, 1 ) ;
696 else
697 {
698 // Get the start node for the current range.
699 oNode = this._Range.startContainer ;
700
701 // If it is an element, get the node right before of it (in source order).
702 if ( oNode.nodeType == 1 )
703 {
704 var lastNode = oNode.childNodes[ this._Range.startOffset ] ;
705 if ( lastNode )
706 oNode = FCKDomTools.GetPreviousSourceNode( lastNode, true ) ;
707 else
708 oNode = oNode.lastChild || oNode ;
709 }
710
711 // We must look for the left boundary, relative to the range
712 // start, which is limited by a block element.
713 while ( oNode
714 && ( oNode.nodeType != 1
715 || ( oNode != this.StartBlockLimit
716 && !boundarySet[ oNode.nodeName.toLowerCase() ] ) ) )
717 {
718 this._Range.setStartBefore( oNode ) ;
719 oNode = oNode.previousSibling || oNode.parentNode ;
720 }
721 }
722
723 if ( this.EndBlock && FCKConfig.EnterMode != 'br' && unit == 'block_contents' && this.EndBlock.nodeName.toLowerCase() != 'li' )
724 this.SetEnd( this.EndBlock, 2 ) ;
725 else
726 {
727 oNode = this._Range.endContainer ;
728 if ( oNode.nodeType == 1 )
729 oNode = oNode.childNodes[ this._Range.endOffset ] || oNode.lastChild ;
730
731 // We must look for the right boundary, relative to the range
732 // end, which is limited by a block element.
733 while ( oNode
734 && ( oNode.nodeType != 1
735 || ( oNode != this.StartBlockLimit
736 && !boundarySet[ oNode.nodeName.toLowerCase() ] ) ) )
737 {
738 this._Range.setEndAfter( oNode ) ;
739 oNode = oNode.nextSibling || oNode.parentNode ;
740 }
741
742 // In EnterMode='br', the end <br> boundary element must
743 // be included in the expanded range.
744 if ( oNode && oNode.nodeName.toLowerCase() == 'br' )
745 this._Range.setEndAfter( oNode ) ;
746 }
747
748 this._UpdateElementInfo() ;
749 }
750 },
751
752 /**
753 * Split the block element for the current range. It deletes the contents
754 * of the range and splits the block in the collapsed position, resulting
755 * in two sucessive blocks. The range is then positioned in the middle of
756 * them.
757 *
758 * It returns and object with the following properties:
759 * - PreviousBlock : a reference to the block element that preceeds
760 * the range after the split.
761 * - NextBlock : a reference to the block element that follows the
762 * range after the split.
763 * - WasStartOfBlock : a boolean indicating that the range was
764 * originaly at the start of the block.
765 * - WasEndOfBlock : a boolean indicating that the range was originaly
766 * at the end of the block.
767 *
768 * If the range was originaly at the start of the block, no split will happen
769 * and the PreviousBlock value will be null. The same is valid for the
770 * NextBlock value if the range was at the end of the block.
771 */
772 SplitBlock : function( forceBlockTag )
773 {
774 var blockTag = forceBlockTag || FCKConfig.EnterMode ;
775
776 if ( !this._Range )
777 this.MoveToSelection() ;
778
779 // The range boundaries must be in the same "block limit" element.
780 if ( this.StartBlockLimit == this.EndBlockLimit )
781 {
782 // Get the current blocks.
783 var eStartBlock = this.StartBlock ;
784 var eEndBlock = this.EndBlock ;
785 var oElementPath = null ;
786
787 if ( blockTag != 'br' )
788 {
789 if ( !eStartBlock )
790 {
791 eStartBlock = this.FixBlock( true, blockTag ) ;
792 eEndBlock = this.EndBlock ; // FixBlock may have fixed the EndBlock too.
793 }
794
795 if ( !eEndBlock )
796 eEndBlock = this.FixBlock( false, blockTag ) ;
797 }
798
799 // Get the range position.
800 var bIsStartOfBlock = ( eStartBlock != null && this.CheckStartOfBlock() ) ;
801 var bIsEndOfBlock = ( eEndBlock != null && this.CheckEndOfBlock() ) ;
802
803 // Delete the current contents.
804 if ( !this.CheckIsEmpty() )
805 this.DeleteContents() ;
806
807 if ( eStartBlock && eEndBlock && eStartBlock == eEndBlock )
808 {
809 if ( bIsEndOfBlock )
810 {
811 oElementPath = new FCKElementPath( this.StartContainer ) ;
812 this.MoveToPosition( eEndBlock, 4 ) ;
813 eEndBlock = null ;
814 }
815 else if ( bIsStartOfBlock )
816 {
817 oElementPath = new FCKElementPath( this.StartContainer ) ;
818 this.MoveToPosition( eStartBlock, 3 ) ;
819 eStartBlock = null ;
820 }
821 else
822 {
823 // Extract the contents of the block from the selection point to the end of its contents.
824 this.SetEnd( eStartBlock, 2 ) ;
825 var eDocFrag = this.ExtractContents() ;
826
827 // Duplicate the block element after it.
828 eEndBlock = eStartBlock.cloneNode( false ) ;
829 eEndBlock.removeAttribute( 'id', false ) ;
830
831 // Place the extracted contents in the duplicated block.
832 eDocFrag.AppendTo( eEndBlock ) ;
833
834 FCKDomTools.InsertAfterNode( eStartBlock, eEndBlock ) ;
835
836 this.MoveToPosition( eStartBlock, 4 ) ;
837
838 // In Gecko, the last child node must be a bogus <br>.
839 // Note: bogus <br> added under <ul> or <ol> would cause lists to be incorrectly rendered.
840 if ( FCKBrowserInfo.IsGecko &&
841 ! eStartBlock.nodeName.IEquals( ['ul', 'ol'] ) )
842 FCKTools.AppendBogusBr( eStartBlock ) ;
843 }
844 }
845
846 return {
847 PreviousBlock : eStartBlock,
848 NextBlock : eEndBlock,
849 WasStartOfBlock : bIsStartOfBlock,
850 WasEndOfBlock : bIsEndOfBlock,
851 ElementPath : oElementPath
852 } ;
853 }
854
855 return null ;
856 },
857
858 // Transform a block without a block tag in a valid block (orphan text in the body or td, usually).
859 FixBlock : function( isStart, blockTag )
860 {
861 // Bookmark the range so we can restore it later.
862 var oBookmark = this.CreateBookmark() ;
863
864 // Collapse the range to the requested ending boundary.
865 this.Collapse( isStart ) ;
866
867 // Expands it to the block contents.
868 this.Expand( 'block_contents' ) ;
869
870 // Create the fixed block.
871 var oFixedBlock = this.Window.document.createElement( blockTag ) ;
872
873 // Move the contents of the temporary range to the fixed block.
874 this.ExtractContents().AppendTo( oFixedBlock ) ;
875 FCKDomTools.TrimNode( oFixedBlock ) ;
876
877 // If the fixed block is empty (not counting bookmark nodes)
878 // Add a <br /> inside to expand it.
879 if ( FCKDomTools.CheckIsEmptyElement(oFixedBlock, function( element ) { return element.getAttribute('_fck_bookmark') != 'true' ; } )
880 && FCKBrowserInfo.IsGeckoLike )
881 FCKTools.AppendBogusBr( oFixedBlock ) ;
882
883 // Insert the fixed block into the DOM.
884 this.InsertNode( oFixedBlock ) ;
885
886 // Move the range back to the bookmarked place.
887 this.MoveToBookmark( oBookmark ) ;
888
889 return oFixedBlock ;
890 },
891
892 Release : function( preserveWindow )
893 {
894 if ( !preserveWindow )
895 this.Window = null ;
896
897 this.StartNode = null ;
898 this.StartContainer = null ;
899 this.StartBlock = null ;
900 this.StartBlockLimit = null ;
901 this.EndNode = null ;
902 this.EndContainer = null ;
903 this.EndBlock = null ;
904 this.EndBlockLimit = null ;
905 this._Range = null ;
906 this._Cache = null ;
907 },
908
909 CheckHasRange : function()
910 {
911 return !!this._Range ;
912 },
913
914 GetTouchedStartNode : function()
915 {
916 var range = this._Range ;
917 var container = range.startContainer ;
918
919 if ( range.collapsed || container.nodeType != 1 )
920 return container ;
921
922 return container.childNodes[ range.startOffset ] || container ;
923 },
924
925 GetTouchedEndNode : function()
926 {
927 var range = this._Range ;
928 var container = range.endContainer ;
929
930 if ( range.collapsed || container.nodeType != 1 )
931 return container ;
932
933 return container.childNodes[ range.endOffset - 1 ] || container ;
934 }
935 } ;
936