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