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 * Controls the [Enter] keystroke behavior in a document.
\r
26 * @targetDocument : the target document.
\r
27 * @enterMode : the behavior for the <Enter> keystroke.
\r
28 * May be "p", "div", "br". Default is "p".
\r
29 * @shiftEnterMode : the behavior for the <Shift>+<Enter> keystroke.
\r
30 * May be "p", "div", "br". Defaults to "br".
\r
32 var FCKEnterKey = function( targetWindow, enterMode, shiftEnterMode, tabSpaces )
\r
34 this.Window = targetWindow ;
\r
35 this.EnterMode = enterMode || 'p' ;
\r
36 this.ShiftEnterMode = shiftEnterMode || 'br' ;
\r
38 // Setup the Keystroke Handler.
\r
39 var oKeystrokeHandler = new FCKKeystrokeHandler( false ) ;
\r
40 oKeystrokeHandler._EnterKey = this ;
\r
41 oKeystrokeHandler.OnKeystroke = FCKEnterKey_OnKeystroke ;
\r
43 oKeystrokeHandler.SetKeystrokes( [
\r
45 [ SHIFT + 13, 'ShiftEnter' ],
\r
46 [ 8 , 'Backspace' ],
\r
47 [ CTRL + 8 , 'CtrlBackspace' ],
\r
53 // Safari by default inserts 4 spaces on TAB, while others make the editor
\r
54 // loose focus. So, we need to handle it here to not include those spaces.
\r
55 if ( tabSpaces > 0 || FCKBrowserInfo.IsSafari )
\r
57 while ( tabSpaces-- )
\r
58 this.TabText += '\xa0' ;
\r
60 oKeystrokeHandler.SetKeystrokes( [ 9, 'Tab' ] );
\r
63 oKeystrokeHandler.AttachToElement( targetWindow.document ) ;
\r
67 function FCKEnterKey_OnKeystroke( keyCombination, keystrokeValue )
\r
69 var oEnterKey = this._EnterKey ;
\r
73 switch ( keystrokeValue )
\r
76 return oEnterKey.DoEnter() ;
\r
79 return oEnterKey.DoShiftEnter() ;
\r
82 return oEnterKey.DoBackspace() ;
\r
85 return oEnterKey.DoDelete() ;
\r
88 return oEnterKey.DoTab() ;
\r
90 case 'CtrlBackspace' :
\r
91 return oEnterKey.DoCtrlBackspace() ;
\r
97 // If for any reason we are not able to handle it, go
\r
98 // ahead with the browser default behavior.
\r
105 * Executes the <Enter> key behavior.
\r
107 FCKEnterKey.prototype.DoEnter = function( mode, hasShift )
\r
109 // Save an undo snapshot before doing anything
\r
110 FCKUndo.SaveUndoStep() ;
\r
112 this._HasShift = ( hasShift === true ) ;
\r
114 var parentElement = FCKSelection.GetParentElement() ;
\r
115 var parentPath = new FCKElementPath( parentElement ) ;
\r
116 var sMode = mode || this.EnterMode ;
\r
118 if ( sMode == 'br' || parentPath.Block && parentPath.Block.tagName.toLowerCase() == 'pre' )
\r
119 return this._ExecuteEnterBr() ;
\r
121 return this._ExecuteEnterBlock( sMode ) ;
\r
125 * Executes the <Shift>+<Enter> key behavior.
\r
127 FCKEnterKey.prototype.DoShiftEnter = function()
\r
129 return this.DoEnter( this.ShiftEnterMode, true ) ;
\r
133 * Executes the <Backspace> key behavior.
\r
135 FCKEnterKey.prototype.DoBackspace = function()
\r
137 var bCustom = false ;
\r
139 // Get the current selection.
\r
140 var oRange = new FCKDomRange( this.Window ) ;
\r
141 oRange.MoveToSelection() ;
\r
144 if ( FCKBrowserInfo.IsIE && this._CheckIsAllContentsIncluded( oRange, this.Window.document.body ) )
\r
146 this._FixIESelectAllBug( oRange ) ;
\r
150 var isCollapsed = oRange.CheckIsCollapsed() ;
\r
152 if ( !isCollapsed )
\r
154 // Bug #327, Backspace with an img selection would activate the default action in IE.
\r
155 // Let's override that with our logic here.
\r
156 if ( FCKBrowserInfo.IsIE && this.Window.document.selection.type.toLowerCase() == "control" )
\r
158 var controls = this.Window.document.selection.createRange() ;
\r
159 for ( var i = controls.length - 1 ; i >= 0 ; i-- )
\r
161 var el = controls.item( i ) ;
\r
162 el.parentNode.removeChild( el ) ;
\r
170 // On IE, it is better for us handle the deletion if the caret is preceeded
\r
171 // by a <br> (#1383).
\r
172 if ( FCKBrowserInfo.IsIE )
\r
174 var previousElement = FCKDomTools.GetPreviousSourceElement( oRange.StartNode, true ) ;
\r
176 if ( previousElement && previousElement.nodeName.toLowerCase() == 'br' )
\r
178 // Create a range that starts after the <br> and ends at the
\r
179 // current range position.
\r
180 var testRange = oRange.Clone() ;
\r
181 testRange.SetStart( previousElement, 4 ) ;
\r
183 // If that range is empty, we can proceed cleaning that <br> manually.
\r
184 if ( testRange.CheckIsEmpty() )
\r
186 previousElement.parentNode.removeChild( previousElement ) ;
\r
192 var oStartBlock = oRange.StartBlock ;
\r
193 var oEndBlock = oRange.EndBlock ;
\r
195 // The selection boundaries must be in the same "block limit" element
\r
196 if ( oRange.StartBlockLimit == oRange.EndBlockLimit && oStartBlock && oEndBlock )
\r
198 if ( !isCollapsed )
\r
200 var bEndOfBlock = oRange.CheckEndOfBlock() ;
\r
202 oRange.DeleteContents() ;
\r
204 if ( oStartBlock != oEndBlock )
\r
206 oRange.SetStart(oEndBlock,1) ;
\r
207 oRange.SetEnd(oEndBlock,1) ;
\r
209 // if ( bEndOfBlock )
\r
210 // oEndBlock.parentNode.removeChild( oEndBlock ) ;
\r
215 bCustom = ( oStartBlock == oEndBlock ) ;
\r
218 if ( oRange.CheckStartOfBlock() )
\r
220 var oCurrentBlock = oRange.StartBlock ;
\r
222 var ePrevious = FCKDomTools.GetPreviousSourceElement( oCurrentBlock, true, [ 'BODY', oRange.StartBlockLimit.nodeName ], ['UL','OL'] ) ;
\r
224 bCustom = this._ExecuteBackspace( oRange, ePrevious, oCurrentBlock ) ;
\r
226 else if ( FCKBrowserInfo.IsGeckoLike )
\r
228 // Firefox and Opera (#1095) loose the selection when executing
\r
229 // CheckStartOfBlock, so we must reselect.
\r
238 FCKEnterKey.prototype.DoCtrlBackspace = function()
\r
240 FCKUndo.SaveUndoStep() ;
\r
241 var oRange = new FCKDomRange( this.Window ) ;
\r
242 oRange.MoveToSelection() ;
\r
243 if ( FCKBrowserInfo.IsIE && this._CheckIsAllContentsIncluded( oRange, this.Window.document.body ) )
\r
245 this._FixIESelectAllBug( oRange ) ;
\r
251 FCKEnterKey.prototype._ExecuteBackspace = function( range, previous, currentBlock )
\r
253 var bCustom = false ;
\r
255 // We could be in a nested LI.
\r
256 if ( !previous && currentBlock && currentBlock.nodeName.IEquals( 'LI' ) && currentBlock.parentNode.parentNode.nodeName.IEquals( 'LI' ) )
\r
258 this._OutdentWithSelection( currentBlock, range ) ;
\r
262 if ( previous && previous.nodeName.IEquals( 'LI' ) )
\r
264 var oNestedList = FCKDomTools.GetLastChild( previous, ['UL','OL'] ) ;
\r
266 while ( oNestedList )
\r
268 previous = FCKDomTools.GetLastChild( oNestedList, 'LI' ) ;
\r
269 oNestedList = FCKDomTools.GetLastChild( previous, ['UL','OL'] ) ;
\r
273 if ( previous && currentBlock )
\r
275 // If we are in a LI, and the previous block is not an LI, we must outdent it.
\r
276 if ( currentBlock.nodeName.IEquals( 'LI' ) && !previous.nodeName.IEquals( 'LI' ) )
\r
278 this._OutdentWithSelection( currentBlock, range ) ;
\r
282 // Take a reference to the parent for post processing cleanup.
\r
283 var oCurrentParent = currentBlock.parentNode ;
\r
285 var sPreviousName = previous.nodeName.toLowerCase() ;
\r
286 if ( FCKListsLib.EmptyElements[ sPreviousName ] != null || sPreviousName == 'table' )
\r
288 FCKDomTools.RemoveNode( previous ) ;
\r
293 // Remove the current block.
\r
294 FCKDomTools.RemoveNode( currentBlock ) ;
\r
296 // Remove any empty tag left by the block removal.
\r
297 while ( oCurrentParent.innerHTML.Trim().length == 0 )
\r
299 var oParent = oCurrentParent.parentNode ;
\r
300 oParent.removeChild( oCurrentParent ) ;
\r
301 oCurrentParent = oParent ;
\r
304 // Cleanup the previous and the current elements.
\r
305 FCKDomTools.LTrimNode( currentBlock ) ;
\r
306 FCKDomTools.RTrimNode( previous ) ;
\r
308 // Append a space to the previous.
\r
309 // Maybe it is not always desirable...
\r
310 // previous.appendChild( this.Window.document.createTextNode( ' ' ) ) ;
\r
312 // Set the range to the end of the previous element and bookmark it.
\r
313 range.SetStart( previous, 2, true ) ;
\r
314 range.Collapse( true ) ;
\r
315 var oBookmark = range.CreateBookmark( true ) ;
\r
317 // Move the contents of the block to the previous element and delete it.
\r
318 // But for some block types (e.g. table), moving the children to the previous block makes no sense.
\r
319 // So a check is needed. (See #1081)
\r
320 if ( ! currentBlock.tagName.IEquals( [ 'TABLE' ] ) )
\r
321 FCKDomTools.MoveChildren( currentBlock, previous ) ;
\r
323 // Place the selection at the bookmark.
\r
324 range.SelectBookmark( oBookmark ) ;
\r
334 * Executes the <Delete> key behavior.
\r
336 FCKEnterKey.prototype.DoDelete = function()
\r
338 // Save an undo snapshot before doing anything
\r
339 // This is to conform with the behavior seen in MS Word
\r
340 FCKUndo.SaveUndoStep() ;
\r
342 // The <Delete> has the same effect as the <Backspace>, so we have the same
\r
343 // results if we just move to the next block and apply the same <Backspace> logic.
\r
345 var bCustom = false ;
\r
347 // Get the current selection.
\r
348 var oRange = new FCKDomRange( this.Window ) ;
\r
349 oRange.MoveToSelection() ;
\r
352 if ( FCKBrowserInfo.IsIE && this._CheckIsAllContentsIncluded( oRange, this.Window.document.body ) )
\r
354 this._FixIESelectAllBug( oRange ) ;
\r
358 // There is just one special case for collapsed selections at the end of a block.
\r
359 if ( oRange.CheckIsCollapsed() && oRange.CheckEndOfBlock( FCKBrowserInfo.IsGeckoLike ) )
\r
361 var oCurrentBlock = oRange.StartBlock ;
\r
362 var eCurrentCell = FCKTools.GetElementAscensor( oCurrentBlock, 'td' );
\r
364 var eNext = FCKDomTools.GetNextSourceElement( oCurrentBlock, true, [ oRange.StartBlockLimit.nodeName ],
\r
365 ['UL','OL','TR'], true ) ;
\r
367 // Bug #1323 : if we're in a table cell, and the next node belongs to a different cell, then don't
\r
368 // delete anything.
\r
369 if ( eCurrentCell )
\r
371 var eNextCell = FCKTools.GetElementAscensor( eNext, 'td' );
\r
372 if ( eNextCell != eCurrentCell )
\r
376 bCustom = this._ExecuteBackspace( oRange, oCurrentBlock, eNext ) ;
\r
384 * Executes the <Tab> key behavior.
\r
386 FCKEnterKey.prototype.DoTab = function()
\r
388 var oRange = new FCKDomRange( this.Window );
\r
389 oRange.MoveToSelection() ;
\r
391 // If the user pressed <tab> inside a table, we should give him the default behavior ( moving between cells )
\r
392 // instead of giving him more non-breaking spaces. (Bug #973)
\r
393 var node = oRange._Range.startContainer ;
\r
396 if ( node.nodeType == 1 )
\r
398 var tagName = node.tagName.toLowerCase() ;
\r
399 if ( tagName == "tr" || tagName == "td" || tagName == "th" || tagName == "tbody" || tagName == "table" )
\r
404 node = node.parentNode ;
\r
407 if ( this.TabText )
\r
409 oRange.DeleteContents() ;
\r
410 oRange.InsertNode( this.Window.document.createTextNode( this.TabText ) ) ;
\r
411 oRange.Collapse( false ) ;
\r
417 FCKEnterKey.prototype._ExecuteEnterBlock = function( blockTag, range )
\r
419 // Get the current selection.
\r
420 var oRange = range || new FCKDomRange( this.Window ) ;
\r
422 var oSplitInfo = oRange.SplitBlock( blockTag ) ;
\r
426 // Get the current blocks.
\r
427 var ePreviousBlock = oSplitInfo.PreviousBlock ;
\r
428 var eNextBlock = oSplitInfo.NextBlock ;
\r
430 var bIsStartOfBlock = oSplitInfo.WasStartOfBlock ;
\r
431 var bIsEndOfBlock = oSplitInfo.WasEndOfBlock ;
\r
433 // If there is one block under a list item, modify the split so that the list item gets split as well. (Bug #1647)
\r
436 if ( eNextBlock.parentNode.nodeName.IEquals( 'li' ) )
\r
438 FCKDomTools.BreakParent( eNextBlock, eNextBlock.parentNode ) ;
\r
439 FCKDomTools.MoveNode( eNextBlock, eNextBlock.nextSibling, true ) ;
\r
442 else if ( ePreviousBlock && ePreviousBlock.parentNode.nodeName.IEquals( 'li' ) )
\r
444 FCKDomTools.BreakParent( ePreviousBlock, ePreviousBlock.parentNode ) ;
\r
445 oRange.MoveToElementEditStart( ePreviousBlock.nextSibling );
\r
446 FCKDomTools.MoveNode( ePreviousBlock, ePreviousBlock.previousSibling ) ;
\r
449 // If we have both the previous and next blocks, it means that the
\r
450 // boundaries were on separated blocks, or none of them where on the
\r
451 // block limits (start/end).
\r
452 if ( !bIsStartOfBlock && !bIsEndOfBlock )
\r
454 // If the next block is an <li> with another list tree as the first child
\r
455 // We'll need to append a placeholder or the list item wouldn't be editable. (Bug #1420)
\r
456 if ( eNextBlock.nodeName.IEquals( 'li' ) && eNextBlock.firstChild
\r
457 && eNextBlock.firstChild.nodeName.IEquals( ['ul', 'ol'] ) )
\r
458 eNextBlock.insertBefore( FCKTools.GetElementDocument( eNextBlock ).createTextNode( '\xa0' ), eNextBlock.firstChild ) ;
\r
459 // Move the selection to the end block.
\r
461 oRange.MoveToElementEditStart( eNextBlock ) ;
\r
465 if ( bIsStartOfBlock && bIsEndOfBlock && ePreviousBlock.tagName.toUpperCase() == 'LI' )
\r
467 oRange.MoveToElementStart( ePreviousBlock ) ;
\r
468 this._OutdentWithSelection( ePreviousBlock, oRange ) ;
\r
475 if ( ePreviousBlock )
\r
477 var sPreviousBlockTag = ePreviousBlock.tagName.toUpperCase() ;
\r
479 // If is a header tag, or we are in a Shift+Enter (#77),
\r
480 // create a new block element (later in the code).
\r
481 if ( !this._HasShift && !(/^H[1-6]$/).test( sPreviousBlockTag ) )
\r
483 // Otherwise, duplicate the previous block.
\r
484 eNewBlock = FCKDomTools.CloneElement( ePreviousBlock ) ;
\r
487 else if ( eNextBlock )
\r
488 eNewBlock = FCKDomTools.CloneElement( eNextBlock ) ;
\r
491 eNewBlock = this.Window.document.createElement( blockTag ) ;
\r
493 // Recreate the inline elements tree, which was available
\r
494 // before the hitting enter, so the same styles will be
\r
495 // available in the new block.
\r
496 var elementPath = oSplitInfo.ElementPath ;
\r
499 for ( var i = 0, len = elementPath.Elements.length ; i < len ; i++ )
\r
501 var element = elementPath.Elements[i] ;
\r
503 if ( element == elementPath.Block || element == elementPath.BlockLimit )
\r
506 if ( FCKListsLib.InlineChildReqElements[ element.nodeName.toLowerCase() ] )
\r
508 element = FCKDomTools.CloneElement( element ) ;
\r
509 FCKDomTools.MoveChildren( eNewBlock, element ) ;
\r
510 eNewBlock.appendChild( element ) ;
\r
515 if ( FCKBrowserInfo.IsGeckoLike )
\r
516 FCKTools.AppendBogusBr( eNewBlock ) ;
\r
518 oRange.InsertNode( eNewBlock ) ;
\r
520 // This is tricky, but to make the new block visible correctly
\r
521 // we must select it.
\r
522 if ( FCKBrowserInfo.IsIE )
\r
524 // Move the selection to the new block.
\r
525 oRange.MoveToElementEditStart( eNewBlock ) ;
\r
529 // Move the selection to the new block.
\r
530 oRange.MoveToElementEditStart( bIsStartOfBlock && !bIsEndOfBlock ? eNextBlock : eNewBlock ) ;
\r
533 if ( FCKBrowserInfo.IsGeckoLike )
\r
537 // If we have split the block, adds a temporary span at the
\r
538 // range position and scroll relatively to it.
\r
539 var tmpNode = this.Window.document.createElement( 'span' ) ;
\r
541 // We need some content for Safari.
\r
542 tmpNode.innerHTML = ' ';
\r
544 oRange.InsertNode( tmpNode ) ;
\r
545 FCKDomTools.ScrollIntoView( tmpNode, false ) ;
\r
546 oRange.DeleteContents() ;
\r
550 // We may use the above scroll logic for the new block case
\r
551 // too, but it gives some weird result with Opera.
\r
552 FCKDomTools.ScrollIntoView( eNextBlock || eNewBlock, false ) ;
\r
559 // Release the resources used by the range.
\r
565 FCKEnterKey.prototype._ExecuteEnterBr = function( blockTag )
\r
567 // Get the current selection.
\r
568 var oRange = new FCKDomRange( this.Window ) ;
\r
569 oRange.MoveToSelection() ;
\r
571 // The selection boundaries must be in the same "block limit" element.
\r
572 if ( oRange.StartBlockLimit == oRange.EndBlockLimit )
\r
574 oRange.DeleteContents() ;
\r
576 // Get the new selection (it is collapsed at this point).
\r
577 oRange.MoveToSelection() ;
\r
579 var bIsStartOfBlock = oRange.CheckStartOfBlock() ;
\r
580 var bIsEndOfBlock = oRange.CheckEndOfBlock() ;
\r
582 var sStartBlockTag = oRange.StartBlock ? oRange.StartBlock.tagName.toUpperCase() : '' ;
\r
584 var bHasShift = this._HasShift ;
\r
585 var bIsPre = false ;
\r
587 if ( !bHasShift && sStartBlockTag == 'LI' )
\r
588 return this._ExecuteEnterBlock( null, oRange ) ;
\r
590 // If we are at the end of a header block.
\r
591 if ( !bHasShift && bIsEndOfBlock && (/^H[1-6]$/).test( sStartBlockTag ) )
\r
593 // Insert a BR after the current paragraph.
\r
594 FCKDomTools.InsertAfterNode( oRange.StartBlock, this.Window.document.createElement( 'br' ) ) ;
\r
596 // The space is required by Gecko only to make the cursor blink.
\r
597 if ( FCKBrowserInfo.IsGecko )
\r
598 FCKDomTools.InsertAfterNode( oRange.StartBlock, this.Window.document.createTextNode( '' ) ) ;
\r
600 // IE and Gecko have different behaviors regarding the position.
\r
601 oRange.SetStart( oRange.StartBlock.nextSibling, FCKBrowserInfo.IsIE ? 3 : 1 ) ;
\r
606 bIsPre = sStartBlockTag.IEquals( 'pre' ) ;
\r
608 eLineBreak = this.Window.document.createTextNode( FCKBrowserInfo.IsIE ? '\r' : '\n' ) ;
\r
610 eLineBreak = this.Window.document.createElement( 'br' ) ;
\r
612 oRange.InsertNode( eLineBreak ) ;
\r
614 // The space is required by Gecko only to make the cursor blink.
\r
615 if ( FCKBrowserInfo.IsGecko )
\r
616 FCKDomTools.InsertAfterNode( eLineBreak, this.Window.document.createTextNode( '' ) ) ;
\r
618 // If we are at the end of a block, we must be sure the bogus node is available in that block.
\r
619 if ( bIsEndOfBlock && FCKBrowserInfo.IsGeckoLike )
\r
620 FCKTools.AppendBogusBr( eLineBreak.parentNode ) ;
\r
622 if ( FCKBrowserInfo.IsIE )
\r
623 oRange.SetStart( eLineBreak, 4 ) ;
\r
625 oRange.SetStart( eLineBreak.nextSibling, 1 ) ;
\r
627 if ( ! FCKBrowserInfo.IsIE )
\r
630 if ( FCKBrowserInfo.IsOpera )
\r
631 dummy = this.Window.document.createElement( 'span' ) ;
\r
633 dummy = this.Window.document.createElement( 'br' ) ;
\r
635 eLineBreak.parentNode.insertBefore( dummy, eLineBreak.nextSibling ) ;
\r
637 FCKDomTools.ScrollIntoView( dummy, false ) ;
\r
639 dummy.parentNode.removeChild( dummy ) ;
\r
643 // This collapse guarantees the cursor will be blinking.
\r
644 oRange.Collapse( true ) ;
\r
646 oRange.Select( bIsPre ) ;
\r
649 // Release the resources used by the range.
\r
655 // Outdents a LI, maintaining the selection defined on a range.
\r
656 FCKEnterKey.prototype._OutdentWithSelection = function( li, range )
\r
658 var oBookmark = range.CreateBookmark() ;
\r
660 FCKListHandler.OutdentListItem( li ) ;
\r
662 range.MoveToBookmark( oBookmark ) ;
\r
666 // Is all the contents under a node included by a range?
\r
667 FCKEnterKey.prototype._CheckIsAllContentsIncluded = function( range, node )
\r
669 var startOk = false ;
\r
670 var endOk = false ;
\r
673 FCKDebug.Output( 'sc='+range.StartContainer.nodeName+
\r
674 ',so='+range._Range.startOffset+
\r
675 ',ec='+range.EndContainer.nodeName+
\r
676 ',eo='+range._Range.endOffset ) ;
\r
678 if ( range.StartContainer == node || range.StartContainer == node.firstChild )
\r
679 startOk = ( range._Range.startOffset == 0 ) ;
\r
681 if ( range.EndContainer == node || range.EndContainer == node.lastChild )
\r
683 var nodeLength = range.EndContainer.nodeType == 3 ? range.EndContainer.length : range.EndContainer.childNodes.length ;
\r
684 endOk = ( range._Range.endOffset == nodeLength ) ;
\r
687 return startOk && endOk ;
\r
691 FCKEnterKey.prototype._FixIESelectAllBug = function( range )
\r
693 var doc = this.Window.document ;
\r
694 doc.body.innerHTML = '' ;
\r
696 if ( FCKConfig.EnterMode.IEquals( ['div', 'p'] ) )
\r
698 editBlock = doc.createElement( FCKConfig.EnterMode ) ;
\r
699 doc.body.appendChild( editBlock ) ;
\r
702 editBlock = doc.body ;
\r
704 range.MoveToNodeContents( editBlock ) ;
\r
705 range.Collapse( true ) ;
\r