PageRenderTime 58ms CodeModel.GetById 22ms app.highlight 29ms RepoModel.GetById 1ms app.codeStats 0ms

/pigeoncms/Plugins/fckeditor/editor/_source/classes/fckdomrange.js

http://pigeoncms.googlecode.com/
JavaScript | 935 lines | 616 code | 154 blank | 165 comment | 209 complexity | d8820e89b1d475768f14ffc0a6e5c427 MD5 | raw file
  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
 25var FCKDomRange = function( sourceWindow )
 26{
 27	this.Window = sourceWindow ;
 28	this._Cache = {} ;
 29}
 30
 31FCKDomRange.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} ;