2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
\r
3 * Copyright (C) 2003-2009 Frederico Caldeira Knabben
\r
5 * == BEGIN LICENSE ==
\r
7 * Licensed under the terms of any of the following licenses at your
\r
10 * - GNU General Public License Version 2 or later (the "GPL")
\r
11 * http://www.gnu.org/licenses/gpl.html
\r
13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
\r
14 * http://www.gnu.org/licenses/lgpl.html
\r
16 * - Mozilla Public License Version 1.1 or later (the "MPL")
\r
17 * http://www.mozilla.org/MPL/MPL-1.1.html
\r
21 * Class for working with a selection range, much like the W3C DOM Range, but
\r
22 * it is not intended to be an implementation of the W3C interface.
\r
25 var FCKDomRange = function( sourceWindow )
\r
27 this.Window = sourceWindow ;
\r
31 FCKDomRange.prototype =
\r
34 _UpdateElementInfo : function()
\r
36 var innerRange = this._Range ;
\r
39 this.Release( true ) ;
\r
42 // For text nodes, the node itself is the StartNode.
\r
43 var eStart = innerRange.startContainer ;
\r
45 var oElementPath = new FCKElementPath( eStart ) ;
\r
46 this.StartNode = eStart.nodeType == 3 ? eStart : eStart.childNodes[ innerRange.startOffset ] ;
\r
47 this.StartContainer = eStart ;
\r
48 this.StartBlock = oElementPath.Block ;
\r
49 this.StartBlockLimit = oElementPath.BlockLimit ;
\r
51 if ( innerRange.collapsed )
\r
53 this.EndNode = this.StartNode ;
\r
54 this.EndContainer = this.StartContainer ;
\r
55 this.EndBlock = this.StartBlock ;
\r
56 this.EndBlockLimit = this.StartBlockLimit ;
\r
60 var eEnd = innerRange.endContainer ;
\r
62 if ( eStart != eEnd )
\r
63 oElementPath = new FCKElementPath( eEnd ) ;
\r
65 // The innerRange.endContainer[ innerRange.endOffset ] is not
\r
66 // usually part of the range, but the marker for the range end. So,
\r
67 // let's get the previous available node as the real end.
\r
68 var eEndNode = eEnd ;
\r
69 if ( innerRange.endOffset == 0 )
\r
71 while ( eEndNode && !eEndNode.previousSibling )
\r
72 eEndNode = eEndNode.parentNode ;
\r
75 eEndNode = eEndNode.previousSibling ;
\r
77 else if ( eEndNode.nodeType == 1 )
\r
78 eEndNode = eEndNode.childNodes[ innerRange.endOffset - 1 ] ;
\r
80 this.EndNode = eEndNode ;
\r
81 this.EndContainer = eEnd ;
\r
82 this.EndBlock = oElementPath.Block ;
\r
83 this.EndBlockLimit = oElementPath.BlockLimit ;
\r
90 CreateRange : function()
\r
92 return new FCKW3CRange( this.Window.document ) ;
\r
95 DeleteContents : function()
\r
99 this._Range.deleteContents() ;
\r
100 this._UpdateElementInfo() ;
\r
104 ExtractContents : function()
\r
108 var docFrag = this._Range.extractContents() ;
\r
109 this._UpdateElementInfo() ;
\r
115 CheckIsCollapsed : function()
\r
118 return this._Range.collapsed ;
\r
123 Collapse : function( toStart )
\r
126 this._Range.collapse( toStart ) ;
\r
128 this._UpdateElementInfo() ;
\r
133 var oClone = FCKTools.CloneObject( this ) ;
\r
136 oClone._Range = this._Range.cloneRange() ;
\r
141 MoveToNodeContents : function( targetNode )
\r
143 if ( !this._Range )
\r
144 this._Range = this.CreateRange() ;
\r
146 this._Range.selectNodeContents( targetNode ) ;
\r
148 this._UpdateElementInfo() ;
\r
151 MoveToElementStart : function( targetElement )
\r
153 this.SetStart(targetElement,1) ;
\r
154 this.SetEnd(targetElement,1) ;
\r
157 // Moves to the first editing point inside a element. For example, in a
\r
158 // element tree like "<p><b><i></i></b> Text</p>", the start editing point
\r
159 // is "<p><b><i>^</i></b> Text</p>" (inside <i>).
\r
160 MoveToElementEditStart : function( targetElement )
\r
162 var editableElement ;
\r
164 while ( targetElement && targetElement.nodeType == 1 )
\r
166 if ( FCKDomTools.CheckIsEditable( targetElement ) )
\r
167 editableElement = targetElement ;
\r
168 else if ( editableElement )
\r
169 break ; // If we already found an editable element, stop the loop.
\r
171 targetElement = targetElement.firstChild ;
\r
174 if ( editableElement )
\r
175 this.MoveToElementStart( editableElement ) ;
\r
178 InsertNode : function( node )
\r
181 this._Range.insertNode( node ) ;
\r
184 CheckIsEmpty : function()
\r
186 if ( this.CheckIsCollapsed() )
\r
189 // Inserts the contents of the range in a div tag.
\r
190 var eToolDiv = this.Window.document.createElement( 'div' ) ;
\r
191 this._Range.cloneContents().AppendTo( eToolDiv ) ;
\r
193 FCKDomTools.TrimNode( eToolDiv ) ;
\r
195 return ( eToolDiv.innerHTML.length == 0 ) ;
\r
199 * Checks if the start boundary of the current range is "visually" (like a
\r
200 * selection caret) at the beginning of the block. It means that some
\r
201 * things could be brefore the range, like spaces or empty inline elements,
\r
202 * but it would still be considered at the beginning of the block.
\r
204 CheckStartOfBlock : function()
\r
206 var cache = this._Cache ;
\r
207 var bIsStartOfBlock = cache.IsStartOfBlock ;
\r
209 if ( bIsStartOfBlock != undefined )
\r
210 return bIsStartOfBlock ;
\r
212 // Take the block reference.
\r
213 var block = this.StartBlock || this.StartBlockLimit ;
\r
215 var container = this._Range.startContainer ;
\r
216 var offset = this._Range.startOffset ;
\r
221 // First, check the start container. If it is a text node, get the
\r
222 // substring of the node value before the range offset.
\r
223 if ( container.nodeType == 3 )
\r
225 var textValue = container.nodeValue.substr( 0, offset ).Trim() ;
\r
227 // If we have some text left in the container, we are not at
\r
228 // the end for the block.
\r
229 if ( textValue.length != 0 )
\r
230 return cache.IsStartOfBlock = false ;
\r
233 currentNode = container.childNodes[ offset - 1 ] ;
\r
236 // We'll not have a currentNode if the container was a text node, or
\r
237 // the offset is zero.
\r
238 if ( !currentNode )
\r
239 currentNode = FCKDomTools.GetPreviousSourceNode( container, true, null, block ) ;
\r
241 while ( currentNode )
\r
243 switch ( currentNode.nodeType )
\r
246 // It's not an inline element.
\r
247 if ( !FCKListsLib.InlineChildReqElements[ currentNode.nodeName.toLowerCase() ] )
\r
248 return cache.IsStartOfBlock = false ;
\r
253 // It's a text node with real text.
\r
254 if ( currentNode.nodeValue.Trim().length > 0 )
\r
255 return cache.IsStartOfBlock = false ;
\r
258 currentNode = FCKDomTools.GetPreviousSourceNode( currentNode, false, null, block ) ;
\r
261 return cache.IsStartOfBlock = true ;
\r
265 * Checks if the end boundary of the current range is "visually" (like a
\r
266 * selection caret) at the end of the block. It means that some things
\r
267 * could be after the range, like spaces, empty inline elements, or a
\r
268 * single <br>, but it would still be considered at the end of the block.
\r
270 CheckEndOfBlock : function( refreshSelection )
\r
272 var isEndOfBlock = this._Cache.IsEndOfBlock ;
\r
274 if ( isEndOfBlock != undefined )
\r
275 return isEndOfBlock ;
\r
277 // Take the block reference.
\r
278 var block = this.EndBlock || this.EndBlockLimit ;
\r
280 var container = this._Range.endContainer ;
\r
281 var offset = this._Range.endOffset ;
\r
284 // First, check the end container. If it is a text node, get the
\r
285 // substring of the node value after the range offset.
\r
286 if ( container.nodeType == 3 )
\r
288 var textValue = container.nodeValue ;
\r
289 if ( offset < textValue.length )
\r
291 textValue = textValue.substr( offset ) ;
\r
293 // If we have some text left in the container, we are not at
\r
294 // the end for the block.
\r
295 if ( textValue.Trim().length != 0 )
\r
296 return this._Cache.IsEndOfBlock = false ;
\r
300 currentNode = container.childNodes[ offset ] ;
\r
302 // We'll not have a currentNode if the container was a text node, of
\r
303 // the offset is out the container children limits (after it probably).
\r
304 if ( !currentNode )
\r
305 currentNode = FCKDomTools.GetNextSourceNode( container, true, null, block ) ;
\r
307 var hadBr = false ;
\r
309 while ( currentNode )
\r
311 switch ( currentNode.nodeType )
\r
314 var nodeName = currentNode.nodeName.toLowerCase() ;
\r
316 // It's an inline element.
\r
317 if ( FCKListsLib.InlineChildReqElements[ nodeName ] )
\r
320 // It is the first <br> found.
\r
321 if ( nodeName == 'br' && !hadBr )
\r
327 return this._Cache.IsEndOfBlock = false ;
\r
330 // It's a text node with real text.
\r
331 if ( currentNode.nodeValue.Trim().length > 0 )
\r
332 return this._Cache.IsEndOfBlock = false ;
\r
335 currentNode = FCKDomTools.GetNextSourceNode( currentNode, false, null, block ) ;
\r
338 if ( refreshSelection )
\r
341 return this._Cache.IsEndOfBlock = true ;
\r
344 // This is an "intrusive" way to create a bookmark. It includes <span> tags
\r
345 // in the range boundaries. The advantage of it is that it is possible to
\r
346 // handle DOM mutations when moving back to the bookmark.
\r
347 // Attention: the inclusion of nodes in the DOM is a design choice and
\r
348 // should not be changed as there are other points in the code that may be
\r
349 // using those nodes to perform operations. See GetBookmarkNode.
\r
350 // For performance, includeNodes=true if intended to SelectBookmark.
\r
351 CreateBookmark : function( includeNodes )
\r
353 // Create the bookmark info (random IDs).
\r
356 StartId : (new Date()).valueOf() + Math.floor(Math.random()*1000) + 'S',
\r
357 EndId : (new Date()).valueOf() + Math.floor(Math.random()*1000) + 'E'
\r
360 var oDoc = this.Window.document ;
\r
365 // For collapsed ranges, add just the start marker.
\r
366 if ( !this.CheckIsCollapsed() )
\r
368 eEndSpan = oDoc.createElement( 'span' ) ;
\r
369 eEndSpan.style.display = 'none' ;
\r
370 eEndSpan.id = oBookmark.EndId ;
\r
371 eEndSpan.setAttribute( '_fck_bookmark', true ) ;
\r
373 // For IE, it must have something inside, otherwise it may be
\r
374 // removed during DOM operations.
\r
375 // if ( FCKBrowserInfo.IsIE )
\r
376 eEndSpan.innerHTML = ' ' ;
\r
378 oClone = this.Clone() ;
\r
379 oClone.Collapse( false ) ;
\r
380 oClone.InsertNode( eEndSpan ) ;
\r
383 eStartSpan = oDoc.createElement( 'span' ) ;
\r
384 eStartSpan.style.display = 'none' ;
\r
385 eStartSpan.id = oBookmark.StartId ;
\r
386 eStartSpan.setAttribute( '_fck_bookmark', true ) ;
\r
388 // For IE, it must have something inside, otherwise it may be removed
\r
389 // during DOM operations.
\r
390 // if ( FCKBrowserInfo.IsIE )
\r
391 eStartSpan.innerHTML = ' ' ;
\r
393 oClone = this.Clone() ;
\r
394 oClone.Collapse( true ) ;
\r
395 oClone.InsertNode( eStartSpan ) ;
\r
397 if ( includeNodes )
\r
399 oBookmark.StartNode = eStartSpan ;
\r
400 oBookmark.EndNode = eEndSpan ;
\r
403 // Update the range position.
\r
406 this.SetStart( eStartSpan, 4 ) ;
\r
407 this.SetEnd( eEndSpan, 3 ) ;
\r
410 this.MoveToPosition( eStartSpan, 4 ) ;
\r
415 // This one should be a part of a hypothetic "bookmark" object.
\r
416 GetBookmarkNode : function( bookmark, start )
\r
418 var doc = this.Window.document ;
\r
421 return bookmark.StartNode || doc.getElementById( bookmark.StartId ) ;
\r
423 return bookmark.EndNode || doc.getElementById( bookmark.EndId ) ;
\r
426 MoveToBookmark : function( bookmark, preserveBookmark )
\r
428 var eStartSpan = this.GetBookmarkNode( bookmark, true ) ;
\r
429 var eEndSpan = this.GetBookmarkNode( bookmark, false ) ;
\r
431 this.SetStart( eStartSpan, 3 ) ;
\r
433 if ( !preserveBookmark )
\r
434 FCKDomTools.RemoveNode( eStartSpan ) ;
\r
436 // If collapsed, the end span will not be available.
\r
439 this.SetEnd( eEndSpan, 3 ) ;
\r
441 if ( !preserveBookmark )
\r
442 FCKDomTools.RemoveNode( eEndSpan ) ;
\r
445 this.Collapse( true ) ;
\r
447 this._UpdateElementInfo() ;
\r
450 // Non-intrusive bookmark algorithm
\r
451 CreateBookmark2 : function()
\r
453 // If there is no range then get out of here.
\r
454 // It happens on initial load in Safari #962 and if the editor it's hidden also in Firefox
\r
455 if ( ! this._Range )
\r
456 return { "Start" : 0, "End" : 0 } ;
\r
458 // First, we record down the offset values
\r
461 "Start" : [ this._Range.startOffset ],
\r
462 "End" : [ this._Range.endOffset ]
\r
464 // Since we're treating the document tree as normalized, we need to backtrack the text lengths
\r
465 // of previous text nodes into the offset value.
\r
466 var curStart = this._Range.startContainer.previousSibling ;
\r
467 var curEnd = this._Range.endContainer.previousSibling ;
\r
469 // Also note that the node that we use for "address base" would change during backtracking.
\r
470 var addrStart = this._Range.startContainer ;
\r
471 var addrEnd = this._Range.endContainer ;
\r
472 while ( curStart && curStart.nodeType == 3 && addrStart.nodeType == 3 )
\r
474 bookmark.Start[0] += curStart.length ;
\r
475 addrStart = curStart ;
\r
476 curStart = curStart.previousSibling ;
\r
478 while ( curEnd && curEnd.nodeType == 3 && addrEnd.nodeType == 3 )
\r
480 bookmark.End[0] += curEnd.length ;
\r
482 curEnd = curEnd.previousSibling ;
\r
485 // If the object pointed to by the startOffset and endOffset are text nodes, we need
\r
486 // to backtrack and add in the text offset to the bookmark addresses.
\r
487 if ( addrStart.nodeType == 1 && addrStart.childNodes[bookmark.Start[0]] && addrStart.childNodes[bookmark.Start[0]].nodeType == 3 )
\r
489 var curNode = addrStart.childNodes[bookmark.Start[0]] ;
\r
491 while ( curNode.previousSibling && curNode.previousSibling.nodeType == 3 )
\r
493 curNode = curNode.previousSibling ;
\r
494 offset += curNode.length ;
\r
496 addrStart = curNode ;
\r
497 bookmark.Start[0] = offset ;
\r
499 if ( addrEnd.nodeType == 1 && addrEnd.childNodes[bookmark.End[0]] && addrEnd.childNodes[bookmark.End[0]].nodeType == 3 )
\r
501 var curNode = addrEnd.childNodes[bookmark.End[0]] ;
\r
503 while ( curNode.previousSibling && curNode.previousSibling.nodeType == 3 )
\r
505 curNode = curNode.previousSibling ;
\r
506 offset += curNode.length ;
\r
508 addrEnd = curNode ;
\r
509 bookmark.End[0] = offset ;
\r
512 // Then, we record down the precise position of the container nodes
\r
513 // by walking up the DOM tree and counting their childNode index
\r
514 bookmark.Start = FCKDomTools.GetNodeAddress( addrStart, true ).concat( bookmark.Start ) ;
\r
515 bookmark.End = FCKDomTools.GetNodeAddress( addrEnd, true ).concat( bookmark.End ) ;
\r
519 MoveToBookmark2 : function( bookmark )
\r
521 // Reverse the childNode counting algorithm in CreateBookmark2()
\r
522 var curStart = FCKDomTools.GetNodeFromAddress( this.Window.document, bookmark.Start.slice( 0, -1 ), true ) ;
\r
523 var curEnd = FCKDomTools.GetNodeFromAddress( this.Window.document, bookmark.End.slice( 0, -1 ), true ) ;
\r
525 // Generate the W3C Range object and update relevant data
\r
526 this.Release( true ) ;
\r
527 this._Range = new FCKW3CRange( this.Window.document ) ;
\r
528 var startOffset = bookmark.Start[ bookmark.Start.length - 1 ] ;
\r
529 var endOffset = bookmark.End[ bookmark.End.length - 1 ] ;
\r
530 while ( curStart.nodeType == 3 && startOffset > curStart.length )
\r
532 if ( ! curStart.nextSibling || curStart.nextSibling.nodeType != 3 )
\r
534 startOffset -= curStart.length ;
\r
535 curStart = curStart.nextSibling ;
\r
537 while ( curEnd.nodeType == 3 && endOffset > curEnd.length )
\r
539 if ( ! curEnd.nextSibling || curEnd.nextSibling.nodeType != 3 )
\r
541 endOffset -= curEnd.length ;
\r
542 curEnd = curEnd.nextSibling ;
\r
544 this._Range.setStart( curStart, startOffset ) ;
\r
545 this._Range.setEnd( curEnd, endOffset ) ;
\r
546 this._UpdateElementInfo() ;
\r
549 MoveToPosition : function( targetElement, position )
\r
551 this.SetStart( targetElement, position ) ;
\r
552 this.Collapse( true ) ;
\r
556 * Moves the position of the start boundary of the range to a specific position
\r
557 * relatively to a element.
\r
559 * 1 = After Start <target>^contents</target>
\r
560 * 2 = Before End <target>contents^</target>
\r
561 * 3 = Before Start ^<target>contents</target>
\r
562 * 4 = After End <target>contents</target>^
\r
564 SetStart : function( targetElement, position, noInfoUpdate )
\r
566 var oRange = this._Range ;
\r
568 oRange = this._Range = this.CreateRange() ;
\r
572 case 1 : // After Start <target>^contents</target>
\r
573 oRange.setStart( targetElement, 0 ) ;
\r
576 case 2 : // Before End <target>contents^</target>
\r
577 oRange.setStart( targetElement, targetElement.childNodes.length ) ;
\r
580 case 3 : // Before Start ^<target>contents</target>
\r
581 oRange.setStartBefore( targetElement ) ;
\r
584 case 4 : // After End <target>contents</target>^
\r
585 oRange.setStartAfter( targetElement ) ;
\r
588 if ( !noInfoUpdate )
\r
589 this._UpdateElementInfo() ;
\r
593 * Moves the position of the start boundary of the range to a specific position
\r
594 * relatively to a element.
\r
596 * 1 = After Start <target>^contents</target>
\r
597 * 2 = Before End <target>contents^</target>
\r
598 * 3 = Before Start ^<target>contents</target>
\r
599 * 4 = After End <target>contents</target>^
\r
601 SetEnd : function( targetElement, position, noInfoUpdate )
\r
603 var oRange = this._Range ;
\r
605 oRange = this._Range = this.CreateRange() ;
\r
609 case 1 : // After Start <target>^contents</target>
\r
610 oRange.setEnd( targetElement, 0 ) ;
\r
613 case 2 : // Before End <target>contents^</target>
\r
614 oRange.setEnd( targetElement, targetElement.childNodes.length ) ;
\r
617 case 3 : // Before Start ^<target>contents</target>
\r
618 oRange.setEndBefore( targetElement ) ;
\r
621 case 4 : // After End <target>contents</target>^
\r
622 oRange.setEndAfter( targetElement ) ;
\r
625 if ( !noInfoUpdate )
\r
626 this._UpdateElementInfo() ;
\r
629 Expand : function( unit )
\r
631 var oNode, oSibling ;
\r
635 // Expand the range to include all inline parent elements if we are
\r
636 // are in their boundary limits.
\r
637 // For example (where [ ] are the range limits):
\r
638 // Before => Some <b>[<i>Some sample text]</i></b>.
\r
639 // After => Some [<b><i>Some sample text</i></b>].
\r
640 case 'inline_elements' :
\r
641 // Expand the start boundary.
\r
642 if ( this._Range.startOffset == 0 )
\r
644 oNode = this._Range.startContainer ;
\r
646 if ( oNode.nodeType != 1 )
\r
647 oNode = oNode.previousSibling ? null : oNode.parentNode ;
\r
651 while ( FCKListsLib.InlineNonEmptyElements[ oNode.nodeName.toLowerCase() ] )
\r
653 this._Range.setStartBefore( oNode ) ;
\r
655 if ( oNode != oNode.parentNode.firstChild )
\r
658 oNode = oNode.parentNode ;
\r
663 // Expand the end boundary.
\r
664 oNode = this._Range.endContainer ;
\r
665 var offset = this._Range.endOffset ;
\r
667 if ( ( oNode.nodeType == 3 && offset >= oNode.nodeValue.length ) || ( oNode.nodeType == 1 && offset >= oNode.childNodes.length ) || ( oNode.nodeType != 1 && oNode.nodeType != 3 ) )
\r
669 if ( oNode.nodeType != 1 )
\r
670 oNode = oNode.nextSibling ? null : oNode.parentNode ;
\r
674 while ( FCKListsLib.InlineNonEmptyElements[ oNode.nodeName.toLowerCase() ] )
\r
676 this._Range.setEndAfter( oNode ) ;
\r
678 if ( oNode != oNode.parentNode.lastChild )
\r
681 oNode = oNode.parentNode ;
\r
688 case 'block_contents' :
\r
689 case 'list_contents' :
\r
690 var boundarySet = FCKListsLib.BlockBoundaries ;
\r
691 if ( unit == 'list_contents' || FCKConfig.EnterMode == 'br' )
\r
692 boundarySet = FCKListsLib.ListBoundaries ;
\r
694 if ( this.StartBlock && FCKConfig.EnterMode != 'br' && unit == 'block_contents' )
\r
695 this.SetStart( this.StartBlock, 1 ) ;
\r
698 // Get the start node for the current range.
\r
699 oNode = this._Range.startContainer ;
\r
701 // If it is an element, get the node right before of it (in source order).
\r
702 if ( oNode.nodeType == 1 )
\r
704 var lastNode = oNode.childNodes[ this._Range.startOffset ] ;
\r
706 oNode = FCKDomTools.GetPreviousSourceNode( lastNode, true ) ;
\r
708 oNode = oNode.lastChild || oNode ;
\r
711 // We must look for the left boundary, relative to the range
\r
712 // start, which is limited by a block element.
\r
714 && ( oNode.nodeType != 1
\r
715 || ( oNode != this.StartBlockLimit
\r
716 && !boundarySet[ oNode.nodeName.toLowerCase() ] ) ) )
\r
718 this._Range.setStartBefore( oNode ) ;
\r
719 oNode = oNode.previousSibling || oNode.parentNode ;
\r
723 if ( this.EndBlock && FCKConfig.EnterMode != 'br' && unit == 'block_contents' && this.EndBlock.nodeName.toLowerCase() != 'li' )
\r
724 this.SetEnd( this.EndBlock, 2 ) ;
\r
727 oNode = this._Range.endContainer ;
\r
728 if ( oNode.nodeType == 1 )
\r
729 oNode = oNode.childNodes[ this._Range.endOffset ] || oNode.lastChild ;
\r
731 // We must look for the right boundary, relative to the range
\r
732 // end, which is limited by a block element.
\r
734 && ( oNode.nodeType != 1
\r
735 || ( oNode != this.StartBlockLimit
\r
736 && !boundarySet[ oNode.nodeName.toLowerCase() ] ) ) )
\r
738 this._Range.setEndAfter( oNode ) ;
\r
739 oNode = oNode.nextSibling || oNode.parentNode ;
\r
742 // In EnterMode='br', the end <br> boundary element must
\r
743 // be included in the expanded range.
\r
744 if ( oNode && oNode.nodeName.toLowerCase() == 'br' )
\r
745 this._Range.setEndAfter( oNode ) ;
\r
748 this._UpdateElementInfo() ;
\r
753 * Split the block element for the current range. It deletes the contents
\r
754 * of the range and splits the block in the collapsed position, resulting
\r
755 * in two sucessive blocks. The range is then positioned in the middle of
\r
758 * It returns and object with the following properties:
\r
759 * - PreviousBlock : a reference to the block element that preceeds
\r
760 * the range after the split.
\r
761 * - NextBlock : a reference to the block element that follows the
\r
762 * range after the split.
\r
763 * - WasStartOfBlock : a boolean indicating that the range was
\r
764 * originaly at the start of the block.
\r
765 * - WasEndOfBlock : a boolean indicating that the range was originaly
\r
766 * at the end of the block.
\r
768 * If the range was originaly at the start of the block, no split will happen
\r
769 * and the PreviousBlock value will be null. The same is valid for the
\r
770 * NextBlock value if the range was at the end of the block.
\r
772 SplitBlock : function( forceBlockTag )
\r
774 var blockTag = forceBlockTag || FCKConfig.EnterMode ;
\r
776 if ( !this._Range )
\r
777 this.MoveToSelection() ;
\r
779 // The range boundaries must be in the same "block limit" element.
\r
780 if ( this.StartBlockLimit == this.EndBlockLimit )
\r
782 // Get the current blocks.
\r
783 var eStartBlock = this.StartBlock ;
\r
784 var eEndBlock = this.EndBlock ;
\r
785 var oElementPath = null ;
\r
787 if ( blockTag != 'br' )
\r
789 if ( !eStartBlock )
\r
791 eStartBlock = this.FixBlock( true, blockTag ) ;
\r
792 eEndBlock = this.EndBlock ; // FixBlock may have fixed the EndBlock too.
\r
796 eEndBlock = this.FixBlock( false, blockTag ) ;
\r
799 // Get the range position.
\r
800 var bIsStartOfBlock = ( eStartBlock != null && this.CheckStartOfBlock() ) ;
\r
801 var bIsEndOfBlock = ( eEndBlock != null && this.CheckEndOfBlock() ) ;
\r
803 // Delete the current contents.
\r
804 if ( !this.CheckIsEmpty() )
\r
805 this.DeleteContents() ;
\r
807 if ( eStartBlock && eEndBlock && eStartBlock == eEndBlock )
\r
809 if ( bIsEndOfBlock )
\r
811 oElementPath = new FCKElementPath( this.StartContainer ) ;
\r
812 this.MoveToPosition( eEndBlock, 4 ) ;
\r
815 else if ( bIsStartOfBlock )
\r
817 oElementPath = new FCKElementPath( this.StartContainer ) ;
\r
818 this.MoveToPosition( eStartBlock, 3 ) ;
\r
819 eStartBlock = null ;
\r
823 // Extract the contents of the block from the selection point to the end of its contents.
\r
824 this.SetEnd( eStartBlock, 2 ) ;
\r
825 var eDocFrag = this.ExtractContents() ;
\r
827 // Duplicate the block element after it.
\r
828 eEndBlock = eStartBlock.cloneNode( false ) ;
\r
829 eEndBlock.removeAttribute( 'id', false ) ;
\r
831 // Place the extracted contents in the duplicated block.
\r
832 eDocFrag.AppendTo( eEndBlock ) ;
\r
834 FCKDomTools.InsertAfterNode( eStartBlock, eEndBlock ) ;
\r
836 this.MoveToPosition( eStartBlock, 4 ) ;
\r
838 // In Gecko, the last child node must be a bogus <br>.
\r
839 // Note: bogus <br> added under <ul> or <ol> would cause lists to be incorrectly rendered.
\r
840 if ( FCKBrowserInfo.IsGecko &&
\r
841 ! eStartBlock.nodeName.IEquals( ['ul', 'ol'] ) )
\r
842 FCKTools.AppendBogusBr( eStartBlock ) ;
\r
847 PreviousBlock : eStartBlock,
\r
848 NextBlock : eEndBlock,
\r
849 WasStartOfBlock : bIsStartOfBlock,
\r
850 WasEndOfBlock : bIsEndOfBlock,
\r
851 ElementPath : oElementPath
\r
858 // Transform a block without a block tag in a valid block (orphan text in the body or td, usually).
\r
859 FixBlock : function( isStart, blockTag )
\r
861 // Bookmark the range so we can restore it later.
\r
862 var oBookmark = this.CreateBookmark() ;
\r
864 // Collapse the range to the requested ending boundary.
\r
865 this.Collapse( isStart ) ;
\r
867 // Expands it to the block contents.
\r
868 this.Expand( 'block_contents' ) ;
\r
870 // Create the fixed block.
\r
871 var oFixedBlock = this.Window.document.createElement( blockTag ) ;
\r
873 // Move the contents of the temporary range to the fixed block.
\r
874 this.ExtractContents().AppendTo( oFixedBlock ) ;
\r
875 FCKDomTools.TrimNode( oFixedBlock ) ;
\r
877 // If the fixed block is empty (not counting bookmark nodes)
\r
878 // Add a <br /> inside to expand it.
\r
879 if ( FCKDomTools.CheckIsEmptyElement(oFixedBlock, function( element ) { return element.getAttribute('_fck_bookmark') != 'true' ; } )
\r
880 && FCKBrowserInfo.IsGeckoLike )
\r
881 FCKTools.AppendBogusBr( oFixedBlock ) ;
\r
883 // Insert the fixed block into the DOM.
\r
884 this.InsertNode( oFixedBlock ) ;
\r
886 // Move the range back to the bookmarked place.
\r
887 this.MoveToBookmark( oBookmark ) ;
\r
889 return oFixedBlock ;
\r
892 Release : function( preserveWindow )
\r
894 if ( !preserveWindow )
\r
895 this.Window = null ;
\r
897 this.StartNode = null ;
\r
898 this.StartContainer = null ;
\r
899 this.StartBlock = null ;
\r
900 this.StartBlockLimit = null ;
\r
901 this.EndNode = null ;
\r
902 this.EndContainer = null ;
\r
903 this.EndBlock = null ;
\r
904 this.EndBlockLimit = null ;
\r
905 this._Range = null ;
\r
906 this._Cache = null ;
\r
909 CheckHasRange : function()
\r
911 return !!this._Range ;
\r
914 GetTouchedStartNode : function()
\r
916 var range = this._Range ;
\r
917 var container = range.startContainer ;
\r
919 if ( range.collapsed || container.nodeType != 1 )
\r
922 return container.childNodes[ range.startOffset ] || container ;
\r
925 GetTouchedEndNode : function()
\r
927 var range = this._Range ;
\r
928 var container = range.endContainer ;
\r
930 if ( range.collapsed || container.nodeType != 1 )
\r
933 return container.childNodes[ range.endOffset - 1 ] || container ;
\r