| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 /* 2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 CKEDITOR.dom.range = function( document ) 7 { 8 this.startContainer = null; 9 this.startOffset = null; 10 this.endContainer = null; 11 this.endOffset = null; 12 this.collapsed = true; 13 14 this.document = document; 15 }; 16 17 (function() 18 { 19 // Updates the "collapsed" property for the given range object. 20 var updateCollapsed = function( range ) 21 { 22 range.collapsed = ( 23 range.startContainer && 24 range.endContainer && 25 range.startContainer.equals( range.endContainer ) && 26 range.startOffset == range.endOffset ); 27 }; 28 29 // This is a shared function used to delete, extract and clone the range 30 // contents. 31 // V2 32 var execContentsAction = function( range, action, docFrag ) 33 { 34 range.optimizeBookmark(); 35 36 var startNode = range.startContainer; 37 var endNode = range.endContainer; 38 39 var startOffset = range.startOffset; 40 var endOffset = range.endOffset; 41 42 var removeStartNode; 43 var removeEndNode; 44 45 // For text containers, we must simply split the node and point to the 46 // second part. The removal will be handled by the rest of the code . 47 if ( endNode.type == CKEDITOR.NODE_TEXT ) 48 endNode = endNode.split( endOffset ); 49 else 50 { 51 // If the end container has children and the offset is pointing 52 // to a child, then we should start from it. 53 if ( endNode.getChildCount() > 0 ) 54 { 55 // If the offset points after the last node. 56 if ( endOffset >= endNode.getChildCount() ) 57 { 58 // Let's create a temporary node and mark it for removal. 59 endNode = endNode.append( range.document.createText( '' ) ); 60 removeEndNode = true; 61 } 62 else 63 endNode = endNode.getChild( endOffset ); 64 } 65 } 66 67 // For text containers, we must simply split the node. The removal will 68 // be handled by the rest of the code . 69 if ( startNode.type == CKEDITOR.NODE_TEXT ) 70 { 71 startNode.split( startOffset ); 72 73 // In cases the end node is the same as the start node, the above 74 // splitting will also split the end, so me must move the end to 75 // the second part of the split. 76 if ( startNode.equals( endNode ) ) 77 endNode = startNode.getNext(); 78 } 79 else 80 { 81 // If the start container has children and the offset is pointing 82 // to a child, then we should start from its previous sibling. 83 84 // If the offset points to the first node, we don't have a 85 // sibling, so let's use the first one, but mark it for removal. 86 if ( !startOffset ) 87 { 88 // Let's create a temporary node and mark it for removal. 89 startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) ); 90 removeStartNode = true; 91 } 92 else if ( startOffset >= startNode.getChildCount() ) 93 { 94 // Let's create a temporary node and mark it for removal. 95 startNode = startNode.append( range.document.createText( '' ) ); 96 removeStartNode = true; 97 } 98 else 99 startNode = startNode.getChild( startOffset ).getPrevious(); 100 } 101 102 // Get the parent nodes tree for the start and end boundaries. 103 var startParents = startNode.getParents(); 104 var endParents = endNode.getParents(); 105 106 // Compare them, to find the top most siblings. 107 var i, topStart, topEnd; 108 109 for ( i = 0 ; i < startParents.length ; i++ ) 110 { 111 topStart = startParents[ i ]; 112 topEnd = endParents[ i ]; 113 114 // The compared nodes will match until we find the top most 115 // siblings (different nodes that have the same parent). 116 // "i" will hold the index in the parents array for the top 117 // most element. 118 if ( !topStart.equals( topEnd ) ) 119 break; 120 } 121 122 var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling; 123 124 // Remove all successive sibling nodes for every node in the 125 // startParents tree. 126 for ( var j = i ; j < startParents.length ; j++ ) 127 { 128 levelStartNode = startParents[j]; 129 130 // For Extract and Clone, we must clone this level. 131 if ( clone && !levelStartNode.equals( startNode ) ) // action = 0 = Delete 132 levelClone = clone.append( levelStartNode.clone() ); 133 134 currentNode = levelStartNode.getNext(); 135 136 while ( currentNode ) 137 { 138 // Stop processing when the current node matches a node in the 139 // endParents tree or if it is the endNode. 140 if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) ) 141 break; 142 143 // Cache the next sibling. 144 currentSibling = currentNode.getNext(); 145 146 // If cloning, just clone it. 147 if ( action == 2 ) // 2 = Clone 148 clone.append( currentNode.clone( true ) ); 149 else 150 { 151 // Both Delete and Extract will remove the node. 152 currentNode.remove(); 153 154 // When Extracting, move the removed node to the docFrag. 155 if ( action == 1 ) // 1 = Extract 156 clone.append( currentNode ); 157 } 158 159 currentNode = currentSibling; 160 } 161 162 if ( clone ) 163 clone = levelClone; 164 } 165 166 clone = docFrag; 167 168 // Remove all previous sibling nodes for every node in the 169 // endParents tree. 170 for ( var k = i ; k < endParents.length ; k++ ) 171 { 172 levelStartNode = endParents[ k ]; 173 174 // For Extract and Clone, we must clone this level. 175 if ( action > 0 && !levelStartNode.equals( endNode ) ) // action = 0 = Delete 176 levelClone = clone.append( levelStartNode.clone() ); 177 178 // The processing of siblings may have already been done by the parent. 179 if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode ) 180 { 181 currentNode = levelStartNode.getPrevious(); 182 183 while ( currentNode ) 184 { 185 // Stop processing when the current node matches a node in the 186 // startParents tree or if it is the startNode. 187 if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) ) 188 break; 189 190 // Cache the next sibling. 191 currentSibling = currentNode.getPrevious(); 192 193 // If cloning, just clone it. 194 if ( action == 2 ) // 2 = Clone 195 clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ; 196 else 197 { 198 // Both Delete and Extract will remove the node. 199 currentNode.remove(); 200 201 // When Extracting, mode the removed node to the docFrag. 202 if ( action == 1 ) // 1 = Extract 203 clone.$.insertBefore( currentNode.$, clone.$.firstChild ); 204 } 205 206 currentNode = currentSibling; 207 } 208 } 209 210 if ( clone ) 211 clone = levelClone; 212 } 213 214 if ( action == 2 ) // 2 = Clone. 215 { 216 // No changes in the DOM should be done, so fix the split text (if any). 217 218 var startTextNode = range.startContainer; 219 if ( startTextNode.type == CKEDITOR.NODE_TEXT ) 220 { 221 startTextNode.$.data += startTextNode.$.nextSibling.data; 222 startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling ); 223 } 224 225 var endTextNode = range.endContainer; 226 if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling ) 227 { 228 endTextNode.$.data += endTextNode.$.nextSibling.data; 229 endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling ); 230 } 231 } 232 else 233 { 234 // Collapse the range. 235 236 // If a node has been partially selected, collapse the range between 237 // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs). 238 if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) ) 239 { 240 var endIndex = topEnd.getIndex(); 241 242 // If the start node is to be removed, we must correct the 243 // index to reflect the removal. 244 if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode ) 245 endIndex--; 246 247 range.setStart( topEnd.getParent(), endIndex ); 248 } 249 250 // Collapse it to the start. 251 range.collapse( true ); 252 } 253 254 // Cleanup any marked node. 255 if ( removeStartNode ) 256 startNode.remove(); 257 258 if ( removeEndNode && endNode.$.parentNode ) 259 endNode.remove(); 260 }; 261 262 var inlineChildReqElements = { abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 }; 263 264 // Creates the appropriate node evaluator for the dom walker used inside 265 // check(Start|End)OfBlock. 266 function getCheckStartEndBlockEvalFunction( isStart ) 267 { 268 var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true ); 269 return function( node ) 270 { 271 // First ignore bookmark nodes. 272 if ( bookmarkEvaluator( node ) ) 273 return true; 274 275 if ( node.type == CKEDITOR.NODE_TEXT ) 276 { 277 // If there's any visible text, then we're not at the start. 278 if ( CKEDITOR.tools.trim( node.getText() ).length ) 279 return false; 280 } 281 else if ( node.type == CKEDITOR.NODE_ELEMENT ) 282 { 283 // If there are non-empty inline elements (e.g. <img />), then we're not 284 // at the start. 285 if ( !inlineChildReqElements[ node.getName() ] ) 286 { 287 // If we're working at the end-of-block, forgive the first <br /> in non-IE 288 // browsers. 289 if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr ) 290 hadBr = true; 291 else 292 return false; 293 } 294 } 295 return true; 296 }; 297 } 298 299 // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any 300 // text node and non-empty elements unless it's being bookmark text. 301 function elementBoundaryEval( node ) 302 { 303 // Reject any text node unless it's being bookmark 304 // OR it's spaces. (#3883) 305 return node.type != CKEDITOR.NODE_TEXT 306 && node.getName() in CKEDITOR.dtd.$removeEmpty 307 || !CKEDITOR.tools.trim( node.getText() ) 308 || node.getParent().hasAttribute( '_fck_bookmark' ); 309 } 310 311 var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(), 312 bookmarkEval = new CKEDITOR.dom.walker.bookmark(); 313 314 function nonWhitespaceOrBookmarkEval( node ) 315 { 316 // Whitespaces and bookmark nodes are to be ignored. 317 return !whitespaceEval( node ) && !bookmarkEval( node ); 318 } 319 320 CKEDITOR.dom.range.prototype = 321 { 322 clone : function() 323 { 324 var clone = new CKEDITOR.dom.range( this.document ); 325 326 clone.startContainer = this.startContainer; 327 clone.startOffset = this.startOffset; 328 clone.endContainer = this.endContainer; 329 clone.endOffset = this.endOffset; 330 clone.collapsed = this.collapsed; 331 332 return clone; 333 }, 334 335 collapse : function( toStart ) 336 { 337 if ( toStart ) 338 { 339 this.endContainer = this.startContainer; 340 this.endOffset = this.startOffset; 341 } 342 else 343 { 344 this.startContainer = this.endContainer; 345 this.startOffset = this.endOffset; 346 } 347 348 this.collapsed = true; 349 }, 350 351 // The selection may be lost when cloning (due to the splitText() call). 352 cloneContents : function() 353 { 354 var docFrag = new CKEDITOR.dom.documentFragment( this.document ); 355 356 if ( !this.collapsed ) 357 execContentsAction( this, 2, docFrag ); 358 359 return docFrag; 360 }, 361 362 deleteContents : function() 363 { 364 if ( this.collapsed ) 365 return; 366 367 execContentsAction( this, 0 ); 368 }, 369 370 extractContents : function() 371 { 372 var docFrag = new CKEDITOR.dom.documentFragment( this.document ); 373 374 if ( !this.collapsed ) 375 execContentsAction( this, 1, docFrag ); 376 377 return docFrag; 378 }, 379 380 /** 381 * Creates a bookmark object, which can be later used to restore the 382 * range by using the moveToBookmark function. 383 * This is an "intrusive" way to create a bookmark. It includes <span> tags 384 * in the range boundaries. The advantage of it is that it is possible to 385 * handle DOM mutations when moving back to the bookmark. 386 * Attention: the inclusion of nodes in the DOM is a design choice and 387 * should not be changed as there are other points in the code that may be 388 * using those nodes to perform operations. See GetBookmarkNode. 389 * @param {Boolean} [serializable] Indicates that the bookmark nodes 390 * must contain ids, which can be used to restore the range even 391 * when these nodes suffer mutations (like a clonation or innerHTML 392 * change). 393 * @returns {Object} And object representing a bookmark. 394 */ 395 createBookmark : function( serializable ) 396 { 397 var startNode, endNode; 398 var baseId; 399 var clone; 400 var collapsed = this.collapsed; 401 402 startNode = this.document.createElement( 'span' ); 403 startNode.setAttribute( '_fck_bookmark', 1 ); 404 startNode.setStyle( 'display', 'none' ); 405 406 // For IE, it must have something inside, otherwise it may be 407 // removed during DOM operations. 408 startNode.setHtml( ' ' ); 409 410 if ( serializable ) 411 { 412 baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber(); 413 startNode.setAttribute( 'id', baseId + 'S' ); 414 } 415 416 // If collapsed, the endNode will not be created. 417 if ( !collapsed ) 418 { 419 endNode = startNode.clone(); 420 endNode.setHtml( ' ' ); 421 422 if ( serializable ) 423 endNode.setAttribute( 'id', baseId + 'E' ); 424 425 clone = this.clone(); 426 clone.collapse(); 427 clone.insertNode( endNode ); 428 } 429 430 clone = this.clone(); 431 clone.collapse( true ); 432 clone.insertNode( startNode ); 433 434 // Update the range position. 435 if ( endNode ) 436 { 437 this.setStartAfter( startNode ); 438 this.setEndBefore( endNode ); 439 } 440 else 441 this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END ); 442 443 return { 444 startNode : serializable ? baseId + 'S' : startNode, 445 endNode : serializable ? baseId + 'E' : endNode, 446 serializable : serializable, 447 collapsed : collapsed 448 }; 449 }, 450 451 /** 452 * Creates a "non intrusive" and "mutation sensible" bookmark. This 453 * kind of bookmark should be used only when the DOM is supposed to 454 * remain stable after its creation. 455 * @param {Boolean} [normalized] Indicates that the bookmark must 456 * normalized. When normalized, the successive text nodes are 457 * considered a single node. To sucessful load a normalized 458 * bookmark, the DOM tree must be also normalized before calling 459 * moveToBookmark. 460 * @returns {Object} An object representing the bookmark. 461 */ 462 createBookmark2 : function( normalized ) 463 { 464 var startContainer = this.startContainer, 465 endContainer = this.endContainer; 466 467 var startOffset = this.startOffset, 468 endOffset = this.endOffset; 469 470 var collapsed = this.collapsed; 471 472 var child, previous; 473 474 // If there is no range then get out of here. 475 // It happens on initial load in Safari #962 and if the editor it's 476 // hidden also in Firefox 477 if ( !startContainer || !endContainer ) 478 return { start : 0, end : 0 }; 479 480 if ( normalized ) 481 { 482 // Find out if the start is pointing to a text node that will 483 // be normalized. 484 if ( startContainer.type == CKEDITOR.NODE_ELEMENT ) 485 { 486 child = startContainer.getChild( startOffset ); 487 488 // In this case, move the start information to that text 489 // node. 490 if ( child && child.type == CKEDITOR.NODE_TEXT 491 && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT ) 492 { 493 startContainer = child; 494 startOffset = 0; 495 } 496 } 497 498 // Normalize the start. 499 while ( startContainer.type == CKEDITOR.NODE_TEXT 500 && ( previous = startContainer.getPrevious() ) 501 && previous.type == CKEDITOR.NODE_TEXT ) 502 { 503 startContainer = previous; 504 startOffset += previous.getLength(); 505 } 506 507 // Process the end only if not normalized. 508 if ( !collapsed ) 509 { 510 // Find out if the start is pointing to a text node that 511 // will be normalized. 512 if ( endContainer.type == CKEDITOR.NODE_ELEMENT ) 513 { 514 child = endContainer.getChild( endOffset ); 515 516 // In this case, move the start information to that 517 // text node. 518 if ( child && child.type == CKEDITOR.NODE_TEXT 519 && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT ) 520 { 521 endContainer = child; 522 endOffset = 0; 523 } 524 } 525 526 // Normalize the end. 527 while ( endContainer.type == CKEDITOR.NODE_TEXT 528 && ( previous = endContainer.getPrevious() ) 529 && previous.type == CKEDITOR.NODE_TEXT ) 530 { 531 endContainer = previous; 532 endOffset += previous.getLength(); 533 } 534 } 535 } 536 537 return { 538 start : startContainer.getAddress( normalized ), 539 end : collapsed ? null : endContainer.getAddress( normalized ), 540 startOffset : startOffset, 541 endOffset : endOffset, 542 normalized : normalized, 543 collapsed : collapsed, 544 is2 : true // It's a createBookmark2 bookmark. 545 }; 546 }, 547 548 moveToBookmark : function( bookmark ) 549 { 550 if ( bookmark.is2 ) // Created with createBookmark2(). 551 { 552 // Get the start information. 553 var startContainer = this.document.getByAddress( bookmark.start, bookmark.normalized ), 554 startOffset = bookmark.startOffset; 555 556 // Get the end information. 557 var endContainer = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ), 558 endOffset = bookmark.endOffset; 559 560 // Set the start boundary. 561 this.setStart( startContainer, startOffset ); 562 563 // Set the end boundary. If not available, collapse it. 564 if ( endContainer ) 565 this.setEnd( endContainer, endOffset ); 566 else 567 this.collapse( true ); 568 } 569 else // Created with createBookmark(). 570 { 571 var serializable = bookmark.serializable, 572 startNode = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode, 573 endNode = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode; 574 575 // Set the range start at the bookmark start node position. 576 this.setStartBefore( startNode ); 577 578 // Remove it, because it may interfere in the setEndBefore call. 579 startNode.remove(); 580 581 // Set the range end at the bookmark end node position, or simply 582 // collapse it if it is not available. 583 if ( endNode ) 584 { 585 this.setEndBefore( endNode ); 586 endNode.remove(); 587 } 588 else 589 this.collapse( true ); 590 } 591 }, 592 593 getBoundaryNodes : function() 594 { 595 var startNode = this.startContainer, 596 endNode = this.endContainer, 597 startOffset = this.startOffset, 598 endOffset = this.endOffset, 599 childCount; 600 601 if ( startNode.type == CKEDITOR.NODE_ELEMENT ) 602 { 603 childCount = startNode.getChildCount(); 604 if ( childCount > startOffset ) 605 startNode = startNode.getChild( startOffset ); 606 else if ( childCount < 1 ) 607 startNode = startNode.getPreviousSourceNode(); 608 else // startOffset > childCount but childCount is not 0 609 { 610 // Try to take the node just after the current position. 611 startNode = startNode.$; 612 while ( startNode.lastChild ) 613 startNode = startNode.lastChild; 614 startNode = new CKEDITOR.dom.node( startNode ); 615 616 // Normally we should take the next node in DFS order. But it 617 // is also possible that we've already reached the end of 618 // document. 619 startNode = startNode.getNextSourceNode() || startNode; 620 } 621 } 622 if ( endNode.type == CKEDITOR.NODE_ELEMENT ) 623 { 624 childCount = endNode.getChildCount(); 625 if ( childCount > endOffset ) 626 endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true ); 627 else if ( childCount < 1 ) 628 endNode = endNode.getPreviousSourceNode(); 629 else // endOffset > childCount but childCount is not 0 630 { 631 // Try to take the node just before the current position. 632 endNode = endNode.$; 633 while ( endNode.lastChild ) 634 endNode = endNode.lastChild; 635 endNode = new CKEDITOR.dom.node( endNode ); 636 } 637 } 638 639 // Sometimes the endNode will come right before startNode for collapsed 640 // ranges. Fix it. (#3780) 641 if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING ) 642 startNode = endNode; 643 644 return { startNode : startNode, endNode : endNode }; 645 }, 646 647 /** 648 * Find the node which fully contains the range. 649 * @param includeSelf 650 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type. 651 */ 652 getCommonAncestor : function( includeSelf , ignoreTextNode ) 653 { 654 var start = this.startContainer, 655 end = this.endContainer, 656 ancestor; 657 658 if ( start.equals( end ) ) 659 { 660 if ( includeSelf 661 && start.type == CKEDITOR.NODE_ELEMENT 662 && this.startOffset == this.endOffset - 1 ) 663 ancestor = start.getChild( this.startOffset ); 664 else 665 ancestor = start; 666 } 667 else 668 ancestor = start.getCommonAncestor( end ); 669 670 return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor; 671 }, 672 673 /** 674 * Transforms the startContainer and endContainer properties from text 675 * nodes to element nodes, whenever possible. This is actually possible 676 * if either of the boundary containers point to a text node, and its 677 * offset is set to zero, or after the last char in the node. 678 */ 679 optimize : function() 680 { 681 var container = this.startContainer; 682 var offset = this.startOffset; 683 684 if ( container.type != CKEDITOR.NODE_ELEMENT ) 685 { 686 if ( !offset ) 687 this.setStartBefore( container ); 688 else if ( offset >= container.getLength() ) 689 this.setStartAfter( container ); 690 } 691 692 container = this.endContainer; 693 offset = this.endOffset; 694 695 if ( container.type != CKEDITOR.NODE_ELEMENT ) 696 { 697 if ( !offset ) 698 this.setEndBefore( container ); 699 else if ( offset >= container.getLength() ) 700 this.setEndAfter( container ); 701 } 702 }, 703 704 /** 705 * Move the range out of bookmark nodes if they'd been the container. 706 */ 707 optimizeBookmark: function() 708 { 709 var startNode = this.startContainer, 710 endNode = this.endContainer; 711 712 if ( startNode.is && startNode.is( 'span' ) 713 && startNode.hasAttribute( '_fck_bookmark' ) ) 714 this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START ); 715 if ( endNode && endNode.is && endNode.is( 'span' ) 716 && endNode.hasAttribute( '_fck_bookmark' ) ) 717 this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END ); 718 }, 719 720 trim : function( ignoreStart, ignoreEnd ) 721 { 722 var startContainer = this.startContainer, 723 startOffset = this.startOffset, 724 collapsed = this.collapsed; 725 if ( ( !ignoreStart || collapsed ) 726 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT ) 727 { 728 // If the offset is zero, we just insert the new node before 729 // the start. 730 if ( !startOffset ) 731 { 732 startOffset = startContainer.getIndex(); 733 startContainer = startContainer.getParent(); 734 } 735 // If the offset is at the end, we'll insert it after the text 736 // node. 737 else if ( startOffset >= startContainer.getLength() ) 738 { 739 startOffset = startContainer.getIndex() + 1; 740 startContainer = startContainer.getParent(); 741 } 742 // In other case, we split the text node and insert the new 743 // node at the split point. 744 else 745 { 746 var nextText = startContainer.split( startOffset ); 747 748 startOffset = startContainer.getIndex() + 1; 749 startContainer = startContainer.getParent(); 750 751 // Check all necessity of updating the end boundary. 752 if ( this.startContainer.equals( this.endContainer ) ) 753 this.setEnd( nextText, this.endOffset - this.startOffset ); 754 else if ( startContainer.equals( this.endContainer ) ) 755 this.endOffset += 1; 756 } 757 758 this.setStart( startContainer, startOffset ); 759 760 if ( collapsed ) 761 { 762 this.collapse( true ); 763 return; 764 } 765 } 766 767 var endContainer = this.endContainer; 768 var endOffset = this.endOffset; 769 770 if ( !( ignoreEnd || collapsed ) 771 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT ) 772 { 773 // If the offset is zero, we just insert the new node before 774 // the start. 775 if ( !endOffset ) 776 { 777 endOffset = endContainer.getIndex(); 778 endContainer = endContainer.getParent(); 779 } 780 // If the offset is at the end, we'll insert it after the text 781 // node. 782 else if ( endOffset >= endContainer.getLength() ) 783 { 784 endOffset = endContainer.getIndex() + 1; 785 endContainer = endContainer.getParent(); 786 } 787 // In other case, we split the text node and insert the new 788 // node at the split point. 789 else 790 { 791 endContainer.split( endOffset ); 792 793 endOffset = endContainer.getIndex() + 1; 794 endContainer = endContainer.getParent(); 795 } 796 797 this.setEnd( endContainer, endOffset ); 798 } 799 }, 800 801 enlarge : function( unit ) 802 { 803 switch ( unit ) 804 { 805 case CKEDITOR.ENLARGE_ELEMENT : 806 807 if ( this.collapsed ) 808 return; 809 810 // Get the common ancestor. 811 var commonAncestor = this.getCommonAncestor(); 812 813 var body = this.document.getBody(); 814 815 // For each boundary 816 // a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge. 817 // b. Go ahead checking siblings and enlarging the boundary as much as possible until the common ancestor is not reached. After reaching the common ancestor, just save the enlargeable node to be used later. 818 819 var startTop, endTop; 820 821 var enlargeable, sibling, commonReached; 822 823 // Indicates that the node can be added only if whitespace 824 // is available before it. 825 var needsWhiteSpace = false; 826 var isWhiteSpace; 827 var siblingText; 828 829 // Process the start boundary. 830 831 var container = this.startContainer; 832 var offset = this.startOffset; 833 834 if ( container.type == CKEDITOR.NODE_TEXT ) 835 { 836 if ( offset ) 837 { 838 // Check if there is any non-space text before the 839 // offset. Otherwise, container is null. 840 container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container; 841 842 // If we found only whitespace in the node, it 843 // means that we'll need more whitespace to be able 844 // to expand. For example, <i> can be expanded in 845 // "A <i> [B]</i>", but not in "A<i> [B]</i>". 846 needsWhiteSpace = !!container; 847 } 848 849 if ( container ) 850 { 851 if ( !( sibling = container.getPrevious() ) ) 852 enlargeable = container.getParent(); 853 } 854 } 855 else 856 { 857 // If we have offset, get the node preceeding it as the 858 // first sibling to be checked. 859 if ( offset ) 860 sibling = container.getChild( offset - 1 ) || container.getLast(); 861 862 // If there is no sibling, mark the container to be 863 // enlarged. 864 if ( !sibling ) 865 enlargeable = container; 866 } 867 868 while ( enlargeable || sibling ) 869 { 870 if ( enlargeable && !sibling ) 871 { 872 // If we reached the common ancestor, mark the flag 873 // for it. 874 if ( !commonReached && enlargeable.equals( commonAncestor ) ) 875 commonReached = true; 876 877 if ( !body.contains( enlargeable ) ) 878 break; 879 880 // If we don't need space or this element breaks 881 // the line, then enlarge it. 882 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' ) 883 { 884 needsWhiteSpace = false; 885 886 // If the common ancestor has been reached, 887 // we'll not enlarge it immediately, but just 888 // mark it to be enlarged later if the end 889 // boundary also enlarges it. 890 if ( commonReached ) 891 startTop = enlargeable; 892 else 893 this.setStartBefore( enlargeable ); 894 } 895 896 sibling = enlargeable.getPrevious(); 897 } 898 899 // Check all sibling nodes preceeding the enlargeable 900 // node. The node wil lbe enlarged only if none of them 901 // blocks it. 902 while ( sibling ) 903 { 904 // This flag indicates that this node has 905 // whitespaces at the end. 906 isWhiteSpace = false; 907 908 if ( sibling.type == CKEDITOR.NODE_TEXT ) 909 { 910 siblingText = sibling.getText(); 911 912 if ( /[^\s\ufeff]/.test( siblingText ) ) 913 sibling = null; 914 915 isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); 916 } 917 else 918 { 919 // If this is a visible element. 920 // We need to check for the bookmark attribute because IE insists on 921 // rendering the display:none nodes we use for bookmarks. (#3363) 922 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) ) 923 { 924 // We'll accept it only if we need 925 // whitespace, and this is an inline 926 // element with whitespace only. 927 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] ) 928 { 929 // It must contains spaces and inline elements only. 930 931 siblingText = sibling.getText(); 932 933 if ( (/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF) 934 sibling = null; 935 else 936 { 937 var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' ); 938 for ( var i = 0, child ; child = allChildren[ i++ ] ; ) 939 { 940 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] ) 941 { 942 sibling = null; 943 break; 944 } 945 } 946 } 947 948 if ( sibling ) 949 isWhiteSpace = !!siblingText.length; 950 } 951 else 952 sibling = null; 953 } 954 } 955 956 // A node with whitespaces has been found. 957 if ( isWhiteSpace ) 958 { 959 // Enlarge the last enlargeable node, if we 960 // were waiting for spaces. 961 if ( needsWhiteSpace ) 962 { 963 if ( commonReached ) 964 startTop = enlargeable; 965 else if ( enlargeable ) 966 this.setStartBefore( enlargeable ); 967 } 968 else 969 needsWhiteSpace = true; 970 } 971 972 if ( sibling ) 973 { 974 var next = sibling.getPrevious(); 975 976 if ( !enlargeable && !next ) 977 { 978 // Set the sibling as enlargeable, so it's 979 // parent will be get later outside this while. 980 enlargeable = sibling; 981 sibling = null; 982 break; 983 } 984 985 sibling = next; 986 } 987 else 988 { 989 // If sibling has been set to null, then we 990 // need to stop enlarging. 991 enlargeable = null; 992 } 993 } 994 995 if ( enlargeable ) 996 enlargeable = enlargeable.getParent(); 997 } 998 999 // Process the end boundary. This is basically the same 1000 // code used for the start boundary, with small changes to 1001 // make it work in the oposite side (to the right). This 1002 // makes it difficult to reuse the code here. So, fixes to 1003 // the above code are likely to be replicated here. 1004 1005 container = this.endContainer; 1006 offset = this.endOffset; 1007 1008 // Reset the common variables. 1009 enlargeable = sibling = null; 1010 commonReached = needsWhiteSpace = false; 1011 1012 if ( container.type == CKEDITOR.NODE_TEXT ) 1013 { 1014 // Check if there is any non-space text after the 1015 // offset. Otherwise, container is null. 1016 container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container; 1017 1018 // If we found only whitespace in the node, it 1019 // means that we'll need more whitespace to be able 1020 // to expand. For example, <i> can be expanded in 1021 // "A <i> [B]</i>", but not in "A<i> [B]</i>". 1022 needsWhiteSpace = !( container && container.getLength() ); 1023 1024 if ( container ) 1025 { 1026 if ( !( sibling = container.getNext() ) ) 1027 enlargeable = container.getParent(); 1028 } 1029 } 1030 else 1031 { 1032 // Get the node right after the boudary to be checked 1033 // first. 1034 sibling = container.getChild( offset ); 1035 1036 if ( !sibling ) 1037 enlargeable = container; 1038 } 1039 1040 while ( enlargeable || sibling ) 1041 { 1042 if ( enlargeable && !sibling ) 1043 { 1044 if ( !commonReached && enlargeable.equals( commonAncestor ) ) 1045 commonReached = true; 1046 1047 if ( !body.contains( enlargeable ) ) 1048 break; 1049 1050 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' ) 1051 { 1052 needsWhiteSpace = false; 1053 1054 if ( commonReached ) 1055 endTop = enlargeable; 1056 else if ( enlargeable ) 1057 this.setEndAfter( enlargeable ); 1058 } 1059 1060 sibling = enlargeable.getNext(); 1061 } 1062 1063 while ( sibling ) 1064 { 1065 isWhiteSpace = false; 1066 1067 if ( sibling.type == CKEDITOR.NODE_TEXT ) 1068 { 1069 siblingText = sibling.getText(); 1070 1071 if ( /[^\s\ufeff]/.test( siblingText ) ) 1072 sibling = null; 1073 1074 isWhiteSpace = /^[\s\ufeff]/.test( siblingText ); 1075 } 1076 else 1077 { 1078 // If this is a visible element. 1079 // We need to check for the bookmark attribute because IE insists on 1080 // rendering the display:none nodes we use for bookmarks. (#3363) 1081 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) ) 1082 { 1083 // We'll accept it only if we need 1084 // whitespace, and this is an inline 1085 // element with whitespace only. 1086 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] ) 1087 { 1088 // It must contains spaces and inline elements only. 1089 1090 siblingText = sibling.getText(); 1091 1092 if ( (/[^\s\ufeff]/).test( siblingText ) ) 1093 sibling = null; 1094 else 1095 { 1096 allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' ); 1097 for ( i = 0 ; child = allChildren[ i++ ] ; ) 1098 { 1099 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] ) 1100 { 1101 sibling = null; 1102 break; 1103 } 1104 } 1105 } 1106 1107 if ( sibling ) 1108 isWhiteSpace = !!siblingText.length; 1109 } 1110 else 1111 sibling = null; 1112 } 1113 } 1114 1115 if ( isWhiteSpace ) 1116 { 1117 if ( needsWhiteSpace ) 1118 { 1119 if ( commonReached ) 1120 endTop = enlargeable; 1121 else 1122 this.setEndAfter( enlargeable ); 1123 } 1124 } 1125 1126 if ( sibling ) 1127 { 1128 next = sibling.getNext(); 1129 1130 if ( !enlargeable && !next ) 1131 { 1132 enlargeable = sibling; 1133 sibling = null; 1134 break; 1135 } 1136 1137 sibling = next; 1138 } 1139 else 1140 { 1141 // If sibling has been set to null, then we 1142 // need to stop enlarging. 1143 enlargeable = null; 1144 } 1145 } 1146 1147 if ( enlargeable ) 1148 enlargeable = enlargeable.getParent(); 1149 } 1150 1151 // If the common ancestor can be enlarged by both boundaries, then include it also. 1152 if ( startTop && endTop ) 1153 { 1154 commonAncestor = startTop.contains( endTop ) ? endTop : startTop; 1155 1156 this.setStartBefore( commonAncestor ); 1157 this.setEndAfter( commonAncestor ); 1158 } 1159 break; 1160 1161 case CKEDITOR.ENLARGE_BLOCK_CONTENTS: 1162 case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS: 1163 1164 // Enlarging the start boundary. 1165 var walkerRange = new CKEDITOR.dom.range( this.document ); 1166 1167 body = this.document.getBody(); 1168 1169 walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START ); 1170 walkerRange.setEnd( this.startContainer, this.startOffset ); 1171 1172 var walker = new CKEDITOR.dom.walker( walkerRange ), 1173 blockBoundary, // The node on which the enlarging should stop. 1174 tailBr, // In case BR as block boundary. 1175 notBlockBoundary = CKEDITOR.dom.walker.blockBoundary( 1176 ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ), 1177 // Record the encountered 'blockBoundary' for later use. 1178 boundaryGuard = function( node ) 1179 { 1180 var retval = notBlockBoundary( node ); 1181 if ( !retval ) 1182 blockBoundary = node; 1183 return retval; 1184 }, 1185 // Record the encounted 'tailBr' for later use. 1186 tailBrGuard = function( node ) 1187 { 1188 var retval = boundaryGuard( node ); 1189 if ( !retval && node.is && node.is( 'br' ) ) 1190 tailBr = node; 1191 return retval; 1192 }; 1193 1194 walker.guard = boundaryGuard; 1195 1196 enlargeable = walker.lastBackward(); 1197 1198 // It's the body which stop the enlarging if no block boundary found. 1199 blockBoundary = blockBoundary || body; 1200 1201 // Start the range either after the end of found block (<p>...</p>[text) 1202 // or at the start of block (<p>[text...), by comparing the document position 1203 // with 'enlargeable' node. 1204 this.setStartAt( 1205 blockBoundary, 1206 !blockBoundary.is( 'br' ) && 1207 ( !enlargeable && this.checkStartOfBlock() 1208 || enlargeable && blockBoundary.contains( enlargeable ) ) ? 1209 CKEDITOR.POSITION_AFTER_START : 1210 CKEDITOR.POSITION_AFTER_END ); 1211 1212 // Enlarging the end boundary. 1213 walkerRange = this.clone(); 1214 walkerRange.collapse(); 1215 walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END ); 1216 walker = new CKEDITOR.dom.walker( walkerRange ); 1217 1218 // tailBrGuard only used for on range end. 1219 walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? 1220 tailBrGuard : boundaryGuard; 1221 blockBoundary = null; 1222 // End the range right before the block boundary node. 1223 1224 enlargeable = walker.lastForward(); 1225 1226 // It's the body which stop the enlarging if no block boundary found. 1227 blockBoundary = blockBoundary || body; 1228 1229 // Close the range either before the found block start (text]<p>...</p>) or at the block end (...text]</p>) 1230 // by comparing the document position with 'enlargeable' node. 1231 this.setEndAt( 1232 blockBoundary, 1233 ( !enlargeable && this.checkEndOfBlock() 1234 || enlargeable && blockBoundary.contains( enlargeable ) ) ? 1235 CKEDITOR.POSITION_BEFORE_END : 1236 CKEDITOR.POSITION_BEFORE_START ); 1237 // We must include the <br> at the end of range if there's 1238 // one and we're expanding list item contents 1239 if ( tailBr ) 1240 this.setEndAfter( tailBr ); 1241 } 1242 }, 1243 1244 /** 1245 * Descrease the range to make sure that boundaries 1246 * always anchor beside text nodes or innermost element. 1247 * @param {Number} mode ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode. 1248 * <dl> 1249 * <dt>CKEDITOR.SHRINK_ELEMENT</dt> 1250 * <dd>Shrink the range boundaries to the edge of the innermost element.</dd> 1251 * <dt>CKEDITOR.SHRINK_TEXT</dt> 1252 * <dd>Shrink the range boudaries to anchor by the side of enclosed text node, range remains if there's no text nodes on boundaries at all.</dd> 1253 * </dl> 1254 * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node. 1255 */ 1256 shrink : function( mode, selectContents ) 1257 { 1258 // Unable to shrink a collapsed range. 1259 if ( !this.collapsed ) 1260 { 1261 mode = mode || CKEDITOR.SHRINK_TEXT; 1262 1263 var walkerRange = this.clone(); 1264 1265 var startContainer = this.startContainer, 1266 endContainer = this.endContainer, 1267 startOffset = this.startOffset, 1268 endOffset = this.endOffset, 1269 collapsed = this.collapsed; 1270 1271 // Whether the start/end boundary is moveable. 1272 var moveStart = 1, 1273 moveEnd = 1; 1274 1275 if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT ) 1276 { 1277 if ( !startOffset ) 1278 walkerRange.setStartBefore( startContainer ); 1279 else if ( startOffset >= startContainer.getLength( ) ) 1280 walkerRange.setStartAfter( startContainer ); 1281 else 1282 { 1283 // Enlarge the range properly to avoid walker making 1284 // DOM changes caused by triming the text nodes later. 1285 walkerRange.setStartBefore( startContainer ); 1286 moveStart = 0; 1287 } 1288 } 1289 1290 if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT ) 1291 { 1292 if ( !endOffset ) 1293 walkerRange.setEndBefore( endContainer ); 1294 else if ( endOffset >= endContainer.getLength( ) ) 1295 walkerRange.setEndAfter( endContainer ); 1296 else 1297 { 1298 walkerRange.setEndAfter( endContainer ); 1299 moveEnd = 0; 1300 } 1301 } 1302 1303 var walker = new CKEDITOR.dom.walker( walkerRange ); 1304 1305 walker.evaluator = function( node ) 1306 { 1307 return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ? 1308 CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT ); 1309 }; 1310 1311 var currentElement; 1312 walker.guard = function( node, movingOut ) 1313 { 1314 // Stop when we're shrink in element mode while encountering a text node. 1315 if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT ) 1316 return false; 1317 1318 // Stop when we've already walked "through" an element. 1319 if ( movingOut && node.equals( currentElement ) ) 1320 return false; 1321 1322 if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT ) 1323 currentElement = node; 1324 1325 return true; 1326 }; 1327 1328 if ( moveStart ) 1329 { 1330 var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next'](); 1331 textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START ); 1332 } 1333 1334 if ( moveEnd ) 1335 { 1336 walker.reset(); 1337 var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous'](); 1338 textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END ); 1339 } 1340 1341 return !!( moveStart || moveEnd ); 1342 } 1343 }, 1344 1345 /** 1346 * Inserts a node at the start of the range. The range will be expanded 1347 * the contain the node. 1348 */ 1349 insertNode : function( node ) 1350 { 1351 this.optimizeBookmark(); 1352 this.trim( false, true ); 1353 1354 var startContainer = this.startContainer; 1355 var startOffset = this.startOffset; 1356 1357 var nextNode = startContainer.getChild( startOffset ); 1358 1359 if ( nextNode ) 1360 node.insertBefore( nextNode ); 1361 else 1362 startContainer.append( node ); 1363 1364 // Check if we need to update the end boundary. 1365 if ( node.getParent().equals( this.endContainer ) ) 1366 this.endOffset++; 1367 1368 // Expand the range to embrace the new node. 1369 this.setStartBefore( node ); 1370 }, 1371 1372 moveToPosition : function( node, position ) 1373 { 1374 this.setStartAt( node, position ); 1375 this.collapse( true ); 1376 }, 1377 1378 selectNodeContents : function( node ) 1379 { 1380 this.setStart( node, 0 ); 1381 this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() ); 1382 }, 1383 1384 /** 1385 * Sets the start position of a Range. 1386 * @param {CKEDITOR.dom.node} startNode The node to start the range. 1387 * @param {Number} startOffset An integer greater than or equal to zero 1388 * representing the offset for the start of the range from the start 1389 * of startNode. 1390 */ 1391 setStart : function( startNode, startOffset ) 1392 { 1393 // W3C requires a check for the new position. If it is after the end 1394 // boundary, the range should be collapsed to the new start. It seams 1395 // we will not need this check for our use of this class so we can 1396 // ignore it for now. 1397 1398 // Fixing invalid range start inside dtd empty elements. 1399 if( startNode.type == CKEDITOR.NODE_ELEMENT 1400 && CKEDITOR.dtd.$empty[ startNode.getName() ] ) 1401 startNode = startNode.getParent(), startOffset = startNode.getIndex(); 1402 1403 this.startContainer = startNode; 1404 this.startOffset = startOffset; 1405 1406 if ( !this.endContainer ) 1407 { 1408 this.endContainer = startNode; 1409 this.endOffset = startOffset; 1410 } 1411 1412 updateCollapsed( this ); 1413 }, 1414 1415 /** 1416 * Sets the end position of a Range. 1417 * @param {CKEDITOR.dom.node} endNode The node to end the range. 1418 * @param {Number} endOffset An integer greater than or equal to zero 1419 * representing the offset for the end of the range from the start 1420 * of endNode. 1421 */ 1422 setEnd : function( endNode, endOffset ) 1423 { 1424 // W3C requires a check for the new position. If it is before the start 1425 // boundary, the range should be collapsed to the new end. It seams we 1426 // will not need this check for our use of this class so we can ignore 1427 // it for now. 1428 1429 // Fixing invalid range end inside dtd empty elements. 1430 if( endNode.type == CKEDITOR.NODE_ELEMENT 1431 && CKEDITOR.dtd.$empty[ endNode.getName() ] ) 1432 endNode = endNode.getParent(), endOffset = endNode.getIndex() + 1; 1433 1434 this.endContainer = endNode; 1435 this.endOffset = endOffset; 1436 1437 if ( !this.startContainer ) 1438 { 1439 this.startContainer = endNode; 1440 this.startOffset = endOffset; 1441 } 1442 1443 updateCollapsed( this ); 1444 }, 1445 1446 setStartAfter : function( node ) 1447 { 1448 this.setStart( node.getParent(), node.getIndex() + 1 ); 1449 }, 1450 1451 setStartBefore : function( node ) 1452 { 1453 this.setStart( node.getParent(), node.getIndex() ); 1454 }, 1455 1456 setEndAfter : function( node ) 1457 { 1458 this.setEnd( node.getParent(), node.getIndex() + 1 ); 1459 }, 1460 1461 setEndBefore : function( node ) 1462 { 1463 this.setEnd( node.getParent(), node.getIndex() ); 1464 }, 1465 1466 setStartAt : function( node, position ) 1467 { 1468 switch( position ) 1469 { 1470 case CKEDITOR.POSITION_AFTER_START : 1471 this.setStart( node, 0 ); 1472 break; 1473 1474 case CKEDITOR.POSITION_BEFORE_END : 1475 if ( node.type == CKEDITOR.NODE_TEXT ) 1476 this.setStart( node, node.getLength() ); 1477 else 1478 this.setStart( node, node.getChildCount() ); 1479 break; 1480 1481 case CKEDITOR.POSITION_BEFORE_START : 1482 this.setStartBefore( node ); 1483 break; 1484 1485 case CKEDITOR.POSITION_AFTER_END : 1486 this.setStartAfter( node ); 1487 } 1488 1489 updateCollapsed( this ); 1490 }, 1491 1492 setEndAt : function( node, position ) 1493 { 1494 switch( position ) 1495 { 1496 case CKEDITOR.POSITION_AFTER_START : 1497 this.setEnd( node, 0 ); 1498 break; 1499 1500 case CKEDITOR.POSITION_BEFORE_END : 1501 if ( node.type == CKEDITOR.NODE_TEXT ) 1502 this.setEnd( node, node.getLength() ); 1503 else 1504 this.setEnd( node, node.getChildCount() ); 1505 break; 1506 1507 case CKEDITOR.POSITION_BEFORE_START : 1508 this.setEndBefore( node ); 1509 break; 1510 1511 case CKEDITOR.POSITION_AFTER_END : 1512 this.setEndAfter( node ); 1513 } 1514 1515 updateCollapsed( this ); 1516 }, 1517 1518 fixBlock : function( isStart, blockTag ) 1519 { 1520 var bookmark = this.createBookmark(), 1521 fixedBlock = this.document.createElement( blockTag ); 1522 1523 this.collapse( isStart ); 1524 1525 this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS ); 1526 1527 this.extractContents().appendTo( fixedBlock ); 1528 fixedBlock.trim(); 1529 1530 if ( !CKEDITOR.env.ie ) 1531 fixedBlock.appendBogus(); 1532 1533 this.insertNode( fixedBlock ); 1534 1535 this.moveToBookmark( bookmark ); 1536 1537 return fixedBlock; 1538 }, 1539 1540 splitBlock : function( blockTag ) 1541 { 1542 var startPath = new CKEDITOR.dom.elementPath( this.startContainer ), 1543 endPath = new CKEDITOR.dom.elementPath( this.endContainer ); 1544 1545 var startBlockLimit = startPath.blockLimit, 1546 endBlockLimit = endPath.blockLimit; 1547 1548 var startBlock = startPath.block, 1549 endBlock = endPath.block; 1550 1551 var elementPath = null; 1552 // Do nothing if the boundaries are in different block limits. 1553 if ( !startBlockLimit.equals( endBlockLimit ) ) 1554 return null; 1555 1556 // Get or fix current blocks. 1557 if ( blockTag != 'br' ) 1558 { 1559 if ( !startBlock ) 1560 { 1561 startBlock = this.fixBlock( true, blockTag ); 1562 endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block; 1563 } 1564 1565 if ( !endBlock ) 1566 endBlock = this.fixBlock( false, blockTag ); 1567 } 1568 1569 // Get the range position. 1570 var isStartOfBlock = startBlock && this.checkStartOfBlock(), 1571 isEndOfBlock = endBlock && this.checkEndOfBlock(); 1572 1573 // Delete the current contents. 1574 // TODO: Why is 2.x doing CheckIsEmpty()? 1575 this.deleteContents(); 1576 1577 if ( startBlock && startBlock.equals( endBlock ) ) 1578 { 1579 if ( isEndOfBlock ) 1580 { 1581 elementPath = new CKEDITOR.dom.elementPath( this.startContainer ); 1582 this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END ); 1583 endBlock = null; 1584 } 1585 else if ( isStartOfBlock ) 1586 { 1587 elementPath = new CKEDITOR.dom.elementPath( this.startContainer ); 1588 this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START ); 1589 startBlock = null; 1590 } 1591 else 1592 { 1593 endBlock = this.splitElement( startBlock ); 1594 1595 // In Gecko, the last child node must be a bogus <br>. 1596 // Note: bogus <br> added under <ul> or <ol> would cause 1597 // lists to be incorrectly rendered. 1598 if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') ) 1599 startBlock.appendBogus() ; 1600 } 1601 } 1602 1603 return { 1604 previousBlock : startBlock, 1605 nextBlock : endBlock, 1606 wasStartOfBlock : isStartOfBlock, 1607 wasEndOfBlock : isEndOfBlock, 1608 elementPath : elementPath 1609 }; 1610 }, 1611 1612 /** 1613 * Branch the specified element from the collapsed range position and 1614 * place the caret between the two result branches. 1615 * Note: The range must be collapsed and been enclosed by this element. 1616 * @param {CKEDITOR.dom.element} element 1617 * @return {CKEDITOR.dom.element} Root element of the new branch after the split. 1618 */ 1619 splitElement : function( toSplit ) 1620 { 1621 if ( !this.collapsed ) 1622 return null; 1623 1624 // Extract the contents of the block from the selection point to the end 1625 // of its contents. 1626 this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END ); 1627 var documentFragment = this.extractContents(); 1628 1629 // Duplicate the element after it. 1630 var clone = toSplit.clone( false ); 1631 1632 // Place the extracted contents into the duplicated element. 1633 documentFragment.appendTo( clone ); 1634 clone.insertAfter( toSplit ); 1635 this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END ); 1636 return clone; 1637 }, 1638 1639 /** 1640 * Check whether a range boundary is at the inner boundary of a given 1641 * element. 1642 * @param {CKEDITOR.dom.element} element The target element to check. 1643 * @param {Number} checkType The boundary to check for both the range 1644 * and the element. It can be CKEDITOR.START or CKEDITOR.END. 1645 * @returns {Boolean} "true" if the range boundary is at the inner 1646 * boundary of the element. 1647 */ 1648 checkBoundaryOfElement : function( element, checkType ) 1649 { 1650 var checkStart = ( checkType == CKEDITOR.START ); 1651 1652 // Create a copy of this range, so we can manipulate it for our checks. 1653 var walkerRange = this.clone(); 1654 1655 // Collapse the range at the proper size. 1656 walkerRange.collapse( checkStart ); 1657 1658 // Expand the range to element boundary. 1659 walkerRange[ checkStart ? 'setStartAt' : 'setEndAt' ] 1660 ( element, checkStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END ); 1661 1662 // Create the walker, which will check if we have anything useful 1663 // in the range. 1664 var walker = new CKEDITOR.dom.walker( walkerRange ); 1665 walker.evaluator = elementBoundaryEval; 1666 1667 return walker[ checkStart ? 'checkBackward' : 'checkForward' ](); 1668 }, 1669 1670 // Calls to this function may produce changes to the DOM. The range may 1671 // be updated to reflect such changes. 1672 checkStartOfBlock : function() 1673 { 1674 var startContainer = this.startContainer, 1675 startOffset = this.startOffset; 1676 1677 // If the starting node is a text node, and non-empty before the offset, 1678 // then we're surely not at the start of block. 1679 if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT ) 1680 { 1681 var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) ); 1682 if ( textBefore.length ) 1683 return false; 1684 } 1685 1686 // Antecipate the trim() call here, so the walker will not make 1687 // changes to the DOM, which would not get reflected into this 1688 // range otherwise. 1689 this.trim(); 1690 1691 // We need to grab the block element holding the start boundary, so 1692 // let's use an element path for it. 1693 var path = new CKEDITOR.dom.elementPath( this.startContainer ); 1694 1695 // Creates a range starting at the block start until the range start. 1696 var walkerRange = this.clone(); 1697 walkerRange.collapse( true ); 1698 walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START ); 1699 1700 var walker = new CKEDITOR.dom.walker( walkerRange ); 1701 walker.evaluator = getCheckStartEndBlockEvalFunction( true ); 1702 1703 return walker.checkBackward(); 1704 }, 1705 1706 checkEndOfBlock : function() 1707 { 1708 var endContainer = this.endContainer, 1709 endOffset = this.endOffset; 1710 1711 // If the ending node is a text node, and non-empty after the offset, 1712 // then we're surely not at the end of block. 1713 if ( endContainer.type == CKEDITOR.NODE_TEXT ) 1714 { 1715 var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) ); 1716 if ( textAfter.length ) 1717 return false; 1718 } 1719 1720 // Antecipate the trim() call here, so the walker will not make 1721 // changes to the DOM, which would not get reflected into this 1722 // range otherwise. 1723 this.trim(); 1724 1725 // We need to grab the block element holding the start boundary, so 1726 // let's use an element path for it. 1727 var path = new CKEDITOR.dom.elementPath( this.endContainer ); 1728 1729 // Creates a range starting at the block start until the range start. 1730 var walkerRange = this.clone(); 1731 walkerRange.collapse( false ); 1732 walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END ); 1733 1734 var walker = new CKEDITOR.dom.walker( walkerRange ); 1735 walker.evaluator = getCheckStartEndBlockEvalFunction( false ); 1736 1737 return walker.checkForward(); 1738 }, 1739 1740 /** 1741 * Moves the range boundaries to the first/end editing point inside an 1742 * element. For example, in an element tree like 1743 * "<p><b><i></i></b> Text</p>", the start editing point is 1744 * "<p><b><i>^</i></b> Text</p>" (inside <i>). 1745 * @param {CKEDITOR.dom.element} el The element into which look for the 1746 * editing spot. 1747 * @param {Boolean} isMoveToEnd Whether move to the end editable position. 1748 */ 1749 moveToElementEditablePosition : function( el, isMoveToEnd ) 1750 { 1751 var isEditable; 1752 1753 // Empty elements are rejected. 1754 if ( CKEDITOR.dtd.$empty[ el.getName() ] ) 1755 return false; 1756 1757 while ( el && el.type == CKEDITOR.NODE_ELEMENT ) 1758 { 1759 isEditable = el.isEditable(); 1760 1761 // If an editable element is found, move inside it. 1762 if ( isEditable ) 1763 this.moveToPosition( el, isMoveToEnd ? 1764 CKEDITOR.POSITION_BEFORE_END : 1765 CKEDITOR.POSITION_AFTER_START ); 1766 // Stop immediately if we've found a non editable inline element (e.g <img>). 1767 else if ( CKEDITOR.dtd.$inline[ el.getName() ] ) 1768 { 1769 this.moveToPosition( el, isMoveToEnd ? 1770 CKEDITOR.POSITION_AFTER_END : 1771 CKEDITOR.POSITION_BEFORE_START ); 1772 return true; 1773 } 1774 1775 // Non-editable non-inline elements are to be bypassed, getting the next one. 1776 if ( CKEDITOR.dtd.$empty[ el.getName() ] ) 1777 el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval ); 1778 else 1779 el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval ); 1780 1781 // Stop immediately if we've found a text node. 1782 if ( el && el.type == CKEDITOR.NODE_TEXT ) 1783 { 1784 this.moveToPosition( el, isMoveToEnd ? 1785 CKEDITOR.POSITION_AFTER_END : 1786 CKEDITOR.POSITION_BEFORE_START ); 1787 return true; 1788 } 1789 } 1790 1791 return isEditable; 1792 }, 1793 1794 /** 1795 *@see {CKEDITOR.dom.range.moveToElementEditablePosition} 1796 */ 1797 moveToElementEditStart : function( target ) 1798 { 1799 return this.moveToElementEditablePosition( target ); 1800 }, 1801 1802 /** 1803 *@see {CKEDITOR.dom.range.moveToElementEditablePosition} 1804 */ 1805 moveToElementEditEnd : function( target ) 1806 { 1807 return this.moveToElementEditablePosition( target, true ); 1808 }, 1809 1810 /** 1811 * Get the single node enclosed within the range if there's one. 1812 */ 1813 getEnclosedNode : function() 1814 { 1815 var walkerRange = this.clone(); 1816 1817 // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780) 1818 walkerRange.optimize(); 1819 if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT 1820 || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT ) 1821 return null; 1822 1823 var walker = new CKEDITOR.dom.walker( walkerRange ), 1824 isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ), 1825 isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), 1826 evaluator = function( node ) 1827 { 1828 return isNotWhitespaces( node ) && isNotBookmarks( node ); 1829 }; 1830 walkerRange.evaluator = evaluator; 1831 var node = walker.next(); 1832 walker.reset(); 1833 return node && node.equals( walker.previous() ) ? node : null; 1834 }, 1835 1836 getTouchedStartNode : function() 1837 { 1838 var container = this.startContainer ; 1839 1840 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT ) 1841 return container ; 1842 1843 return container.getChild( this.startOffset ) || container ; 1844 }, 1845 1846 getTouchedEndNode : function() 1847 { 1848 var container = this.endContainer ; 1849 1850 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT ) 1851 return container ; 1852 1853 return container.getChild( this.endOffset - 1 ) || container ; 1854 } 1855 }; 1856 })(); 1857 1858 CKEDITOR.POSITION_AFTER_START = 1; // <element>^contents</element> "^text" 1859 CKEDITOR.POSITION_BEFORE_END = 2; // <element>contents^</element> "text^" 1860 CKEDITOR.POSITION_BEFORE_START = 3; // ^<element>contents</element> ^"text" 1861 CKEDITOR.POSITION_AFTER_END = 4; // <element>contents</element>^ "text" 1862 1863 CKEDITOR.ENLARGE_ELEMENT = 1; 1864 CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2; 1865 CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3; 1866 1867 /** 1868 * Check boundary types. 1869 * @see CKEDITOR.dom.range.prototype.checkBoundaryOfElement 1870 */ 1871 CKEDITOR.START = 1; 1872 CKEDITOR.END = 2; 1873 CKEDITOR.STARTEND = 3; 1874 1875 /** 1876 * Shrink range types. 1877 * @see CKEDITOR.dom.range.prototype.shrink 1878 */ 1879 CKEDITOR.SHRINK_ELEMENT = 1; 1880 CKEDITOR.SHRINK_TEXT = 2;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |