1 /* 2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 (function() 7 { 8 // #### checkSelectionChange : START 9 10 // The selection change check basically saves the element parent tree of 11 // the current node and check it on successive requests. If there is any 12 // change on the tree, then the selectionChange event gets fired. 13 function checkSelectionChange() 14 { 15 try 16 { 17 // In IE, the "selectionchange" event may still get thrown when 18 // releasing the WYSIWYG mode, so we need to check it first. 19 var sel = this.getSelection(); 20 if ( !sel ) 21 return; 22 23 var firstElement = sel.getStartElement(); 24 var currentPath = new CKEDITOR.dom.elementPath( firstElement ); 25 26 if ( !currentPath.compare( this._.selectionPreviousPath ) ) 27 { 28 this._.selectionPreviousPath = currentPath; 29 this.fire( 'selectionChange', { selection : sel, path : currentPath, element : firstElement } ); 30 } 31 } 32 catch (e) 33 {} 34 } 35 36 var checkSelectionChangeTimer, 37 checkSelectionChangeTimeoutPending; 38 39 function checkSelectionChangeTimeout() 40 { 41 // Firing the "OnSelectionChange" event on every key press started to 42 // be too slow. This function guarantees that there will be at least 43 // 200ms delay between selection checks. 44 45 checkSelectionChangeTimeoutPending = true; 46 47 if ( checkSelectionChangeTimer ) 48 return; 49 50 checkSelectionChangeTimeoutExec.call( this ); 51 52 checkSelectionChangeTimer = CKEDITOR.tools.setTimeout( checkSelectionChangeTimeoutExec, 200, this ); 53 } 54 55 function checkSelectionChangeTimeoutExec() 56 { 57 checkSelectionChangeTimer = null; 58 59 if ( checkSelectionChangeTimeoutPending ) 60 { 61 // Call this with a timeout so the browser properly moves the 62 // selection after the mouseup. It happened that the selection was 63 // being moved after the mouseup when clicking inside selected text 64 // with Firefox. 65 CKEDITOR.tools.setTimeout( checkSelectionChange, 0, this ); 66 67 checkSelectionChangeTimeoutPending = false; 68 } 69 } 70 71 // #### checkSelectionChange : END 72 73 var selectAllCmd = 74 { 75 exec : function( editor ) 76 { 77 switch ( editor.mode ) 78 { 79 case 'wysiwyg' : 80 editor.document.$.execCommand( 'SelectAll', false, null ); 81 break; 82 case 'source' : 83 // TODO 84 } 85 }, 86 canUndo : false 87 }; 88 89 CKEDITOR.plugins.add( 'selection', 90 { 91 init : function( editor ) 92 { 93 editor.on( 'contentDom', function() 94 { 95 var doc = editor.document; 96 97 if ( CKEDITOR.env.ie ) 98 { 99 // Other browsers don't loose the selection if the 100 // editor document loose the focus. In IE, we don't 101 // have support for it, so we reproduce it here, other 102 // than firing the selection change event. 103 104 var savedRange, 105 saveEnabled; 106 107 // "onfocusin" is fired before "onfocus". It makes it 108 // possible to restore the selection before click 109 // events get executed. 110 doc.on( 'focusin', function() 111 { 112 // If we have saved a range, restore it at this 113 // point. 114 if ( savedRange ) 115 { 116 // Well not break because of this. 117 try 118 { 119 savedRange.select(); 120 } 121 catch (e) 122 {} 123 124 savedRange = null; 125 } 126 }); 127 128 editor.window.on( 'focus', function() 129 { 130 // Enable selections to be saved. 131 saveEnabled = true; 132 133 saveSelection(); 134 }); 135 136 editor.window.on( 'blur', function() 137 { 138 // Disable selections from being saved. 139 saveEnabled = false; 140 141 // IE may leave the selection still inside the 142 // document. Let's force it to be removed. 143 // TODO: The following has effect for 144 // collapsed selections. 145 editor.document.$.execCommand( 'Unselect' ); 146 }); 147 148 // IE fires the "selectionchange" event when clicking 149 // inside a selection. We don't want to capture that. 150 doc.on( 'mousedown', disableSave ); 151 doc.on( 'mouseup', 152 function() 153 { 154 saveEnabled = true; 155 setTimeout( function() 156 { 157 saveSelection( true ); 158 }, 159 0 ); 160 }); 161 162 doc.on( 'keydown', disableSave ); 163 doc.on( 'keyup', 164 function() 165 { 166 saveEnabled = true; 167 saveSelection(); 168 }); 169 170 171 // IE is the only to provide the "selectionchange" 172 // event. 173 doc.on( 'selectionchange', saveSelection ); 174 175 function disableSave() 176 { 177 saveEnabled = false; 178 } 179 180 function saveSelection( testIt ) 181 { 182 if ( saveEnabled ) 183 { 184 var doc = editor.document, 185 sel = doc && doc.$.selection; 186 187 // There is a very specific case, when clicking 188 // inside a text selection. In that case, the 189 // selection collapses at the clicking point, 190 // but the selection object remains in an 191 // unknown state, making createRange return a 192 // range at the very start of the document. In 193 // such situation we have to test the range, to 194 // be sure it's valid. 195 if ( testIt && sel && sel.type == 'None' ) 196 { 197 // The "InsertImage" command can be used to 198 // test whether the selection is good or not. 199 // If not, it's enough to give some time to 200 // IE to put things in order for us. 201 if ( !doc.$.queryCommandEnabled( 'InsertImage' ) ) 202 { 203 CKEDITOR.tools.setTimeout( saveSelection, 50, this, true ); 204 return; 205 } 206 } 207 208 savedRange = sel && sel.createRange(); 209 210 checkSelectionChangeTimeout.call( editor ); 211 } 212 } 213 } 214 else 215 { 216 // In other browsers, we make the selection change 217 // check based on other events, like clicks or keys 218 // press. 219 220 doc.on( 'mouseup', checkSelectionChangeTimeout, editor ); 221 doc.on( 'keyup', checkSelectionChangeTimeout, editor ); 222 } 223 }); 224 225 editor.addCommand( 'selectAll', selectAllCmd ); 226 editor.ui.addButton( 'SelectAll', 227 { 228 label : editor.lang.selectAll, 229 command : 'selectAll' 230 }); 231 232 editor.selectionChange = checkSelectionChangeTimeout; 233 } 234 }); 235 236 /** 237 * Gets the current selection from the editing area when in WYSIWYG mode. 238 * @returns {CKEDITOR.dom.selection} A selection object or null if not on 239 * WYSIWYG mode or no selection is available. 240 * @example 241 * var selection = CKEDITOR.instances.editor1.<b>getSelection()</b>; 242 * alert( selection.getType() ); 243 */ 244 CKEDITOR.editor.prototype.getSelection = function() 245 { 246 return this.document && this.document.getSelection(); 247 }; 248 249 CKEDITOR.editor.prototype.forceNextSelectionCheck = function() 250 { 251 delete this._.selectionPreviousPath; 252 }; 253 254 /** 255 * Gets the current selection from the document. 256 * @returns {CKEDITOR.dom.selection} A selection object. 257 * @example 258 * var selection = CKEDITOR.instances.editor1.document.<b>getSelection()</b>; 259 * alert( selection.getType() ); 260 */ 261 CKEDITOR.dom.document.prototype.getSelection = function() 262 { 263 var sel = new CKEDITOR.dom.selection( this ); 264 return ( !sel || sel.isInvalid ) ? null : sel; 265 }; 266 267 /** 268 * No selection. 269 * @constant 270 * @example 271 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE ) 272 * alert( 'Nothing is selected' ); 273 */ 274 CKEDITOR.SELECTION_NONE = 1; 275 276 /** 277 * Text or collapsed selection. 278 * @constant 279 * @example 280 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT ) 281 * alert( 'Text is selected' ); 282 */ 283 CKEDITOR.SELECTION_TEXT = 2; 284 285 /** 286 * Element selection. 287 * @constant 288 * @example 289 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT ) 290 * alert( 'An element is selected' ); 291 */ 292 CKEDITOR.SELECTION_ELEMENT = 3; 293 294 /** 295 * Manipulates the selection in a DOM document. 296 * @constructor 297 * @example 298 */ 299 CKEDITOR.dom.selection = function( document ) 300 { 301 var lockedSelection = document.getCustomData( 'cke_locked_selection' ); 302 303 if ( lockedSelection ) 304 return lockedSelection; 305 306 this.document = document; 307 this.isLocked = false; 308 this._ = 309 { 310 cache : {} 311 }; 312 313 /** 314 * IE BUG: The selection's document may be a different document than the 315 * editor document. Return null if that's the case. 316 */ 317 if ( CKEDITOR.env.ie ) 318 { 319 var range = this.getNative().createRange(); 320 if ( !range 321 || ( range.item && range.item(0).ownerDocument != this.document.$ ) 322 || ( range.parentElement && range.parentElement().ownerDocument != this.document.$ ) ) 323 { 324 this.isInvalid = true; 325 } 326 } 327 328 return this; 329 }; 330 331 var styleObjectElements = 332 { 333 img:1,hr:1,li:1,table:1,tr:1,td:1,embed:1,object:1,ol:1,ul:1, 334 a:1, input:1, form:1, select:1, textarea:1, button:1, fieldset:1, th:1, thead:1, tfoot:1 335 }; 336 337 CKEDITOR.dom.selection.prototype = 338 { 339 /** 340 * Gets the native selection object from the browser. 341 * @function 342 * @returns {Object} The native selection object. 343 * @example 344 * var selection = editor.getSelection().<b>getNative()</b>; 345 */ 346 getNative : 347 CKEDITOR.env.ie ? 348 function() 349 { 350 return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.$.selection ); 351 } 352 : 353 function() 354 { 355 return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.getWindow().$.getSelection() ); 356 }, 357 358 /** 359 * Gets the type of the current selection. The following values are 360 * available: 361 * <ul> 362 * <li>{@link CKEDITOR.SELECTION_NONE} (1): No selection.</li> 363 * <li>{@link CKEDITOR.SELECTION_TEXT} (2): Text is selected or 364 * collapsed selection.</li> 365 * <li>{@link CKEDITOR.SELECTION_ELEMENT} (3): A element 366 * selection.</li> 367 * </ul> 368 * @function 369 * @returns {Number} One of the following constant values: 370 * {@link CKEDITOR.SELECTION_NONE}, {@link CKEDITOR.SELECTION_TEXT} or 371 * {@link CKEDITOR.SELECTION_ELEMENT}. 372 * @example 373 * if ( editor.getSelection().<b>getType()</b> == CKEDITOR.SELECTION_TEXT ) 374 * alert( 'Text is selected' ); 375 */ 376 getType : 377 CKEDITOR.env.ie ? 378 function() 379 { 380 var cache = this._.cache; 381 if ( cache.type ) 382 return cache.type; 383 384 var type = CKEDITOR.SELECTION_NONE; 385 386 try 387 { 388 var sel = this.getNative(), 389 ieType = sel.type; 390 391 if ( ieType == 'Text' ) 392 type = CKEDITOR.SELECTION_TEXT; 393 394 if ( ieType == 'Control' ) 395 type = CKEDITOR.SELECTION_ELEMENT; 396 397 // It is possible that we can still get a text range 398 // object even when type == 'None' is returned by IE. 399 // So we'd better check the object returned by 400 // createRange() rather than by looking at the type. 401 if ( sel.createRange().parentElement ) 402 type = CKEDITOR.SELECTION_TEXT; 403 } 404 catch(e) {} 405 406 return ( cache.type = type ); 407 } 408 : 409 function() 410 { 411 var cache = this._.cache; 412 if ( cache.type ) 413 return cache.type; 414 415 var type = CKEDITOR.SELECTION_TEXT; 416 417 var sel = this.getNative(); 418 419 if ( !sel ) 420 type = CKEDITOR.SELECTION_NONE; 421 else if ( sel.rangeCount == 1 ) 422 { 423 // Check if the actual selection is a control (IMG, 424 // TABLE, HR, etc...). 425 426 var range = sel.getRangeAt(0), 427 startContainer = range.startContainer; 428 429 if ( startContainer == range.endContainer 430 && startContainer.nodeType == 1 431 && ( range.endOffset - range.startOffset ) == 1 432 && styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] ) 433 { 434 type = CKEDITOR.SELECTION_ELEMENT; 435 } 436 } 437 438 return ( cache.type = type ); 439 }, 440 441 getRanges : 442 CKEDITOR.env.ie ? 443 ( function() 444 { 445 // Finds the container and offset for a specific boundary 446 // of an IE range. 447 var getBoundaryInformation = function( range, start ) 448 { 449 // Creates a collapsed range at the requested boundary. 450 range = range.duplicate(); 451 range.collapse( start ); 452 453 // Gets the element that encloses the range entirely. 454 var parent = range.parentElement(); 455 var siblings = parent.childNodes; 456 457 var testRange; 458 459 for ( var i = 0 ; i < siblings.length ; i++ ) 460 { 461 var child = siblings[ i ]; 462 if ( child.nodeType == 1 ) 463 { 464 testRange = range.duplicate(); 465 466 testRange.moveToElementText( child ); 467 testRange.collapse(); 468 469 var comparison = testRange.compareEndPoints( 'StartToStart', range ); 470 471 if ( comparison > 0 ) 472 break; 473 else if ( comparison === 0 ) 474 return { 475 container : parent, 476 offset : i 477 }; 478 479 testRange = null; 480 } 481 } 482 483 if ( !testRange ) 484 { 485 testRange = range.duplicate(); 486 testRange.moveToElementText( parent ); 487 testRange.collapse( false ); 488 } 489 490 testRange.setEndPoint( 'StartToStart', range ); 491 var distance = testRange.text.length; 492 493 while ( distance > 0 ) 494 distance -= siblings[ --i ].nodeValue.length; 495 496 if ( distance === 0 ) 497 { 498 return { 499 container : parent, 500 offset : i 501 }; 502 } 503 else 504 { 505 return { 506 container : siblings[ i ], 507 offset : -distance 508 }; 509 } 510 }; 511 512 return function() 513 { 514 var cache = this._.cache; 515 if ( cache.ranges ) 516 return cache.ranges; 517 518 // IE doesn't have range support (in the W3C way), so we 519 // need to do some magic to transform selections into 520 // CKEDITOR.dom.range instances. 521 522 var sel = this.getNative(), 523 nativeRange = sel && sel.createRange(), 524 type = this.getType(), 525 range; 526 527 if ( !sel ) 528 return []; 529 530 if ( type == CKEDITOR.SELECTION_TEXT ) 531 { 532 range = new CKEDITOR.dom.range( this.document ); 533 534 var boundaryInfo = getBoundaryInformation( nativeRange, true ); 535 range.setStart( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); 536 537 boundaryInfo = getBoundaryInformation( nativeRange ); 538 range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); 539 540 return ( cache.ranges = [ range ] ); 541 } 542 else if ( type == CKEDITOR.SELECTION_ELEMENT ) 543 { 544 var retval = this._.cache.ranges = []; 545 546 for ( var i = 0 ; i < nativeRange.length ; i++ ) 547 { 548 var element = nativeRange.item( i ), 549 parentElement = element.parentNode, 550 j = 0; 551 552 range = new CKEDITOR.dom.range( this.document ); 553 554 for (; j < parentElement.childNodes.length && parentElement.childNodes[j] != element ; j++ ) 555 { /*jsl:pass*/ } 556 557 range.setStart( new CKEDITOR.dom.node( parentElement ), j ); 558 range.setEnd( new CKEDITOR.dom.node( parentElement ), j + 1 ); 559 retval.push( range ); 560 } 561 562 return retval; 563 } 564 565 return ( cache.ranges = [] ); 566 }; 567 })() 568 : 569 function() 570 { 571 var cache = this._.cache; 572 if ( cache.ranges ) 573 return cache.ranges; 574 575 // On browsers implementing the W3C range, we simply 576 // tranform the native ranges in CKEDITOR.dom.range 577 // instances. 578 579 var ranges = []; 580 var sel = this.getNative(); 581 582 if ( !sel ) 583 return []; 584 585 for ( var i = 0 ; i < sel.rangeCount ; i++ ) 586 { 587 var nativeRange = sel.getRangeAt( i ); 588 var range = new CKEDITOR.dom.range( this.document ); 589 590 range.setStart( new CKEDITOR.dom.node( nativeRange.startContainer ), nativeRange.startOffset ); 591 range.setEnd( new CKEDITOR.dom.node( nativeRange.endContainer ), nativeRange.endOffset ); 592 ranges.push( range ); 593 } 594 595 return ( cache.ranges = ranges ); 596 }, 597 598 /** 599 * Gets the DOM element in which the selection starts. 600 * @returns {CKEDITOR.dom.element} The element at the beginning of the 601 * selection. 602 * @example 603 * var element = editor.getSelection().<b>getStartElement()</b>; 604 * alert( element.getName() ); 605 */ 606 getStartElement : function() 607 { 608 var cache = this._.cache; 609 if ( cache.startElement !== undefined ) 610 return cache.startElement; 611 612 var node, 613 sel = this.getNative(); 614 615 switch ( this.getType() ) 616 { 617 case CKEDITOR.SELECTION_ELEMENT : 618 return this.getSelectedElement(); 619 620 case CKEDITOR.SELECTION_TEXT : 621 622 var range = this.getRanges()[0]; 623 624 if ( range ) 625 { 626 if ( !range.collapsed ) 627 { 628 range.optimize(); 629 630 // Decrease the range content to exclude particial 631 // selected node on the start which doesn't have 632 // visual impact. ( #3231 ) 633 while( true ) 634 { 635 var startContainer = range.startContainer, 636 startOffset = range.startOffset; 637 if ( startOffset == ( startContainer.getChildCount ? 638 startContainer.getChildCount() : startContainer.getLength() ) ) 639 range.setStartAfter( startContainer ); 640 else break; 641 } 642 643 node = range.startContainer; 644 645 if ( node.type != CKEDITOR.NODE_ELEMENT ) 646 return node.getParent(); 647 648 node = node.getChild( range.startOffset ); 649 650 if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) 651 return range.startContainer; 652 653 var child = node.getFirst(); 654 while ( child && child.type == CKEDITOR.NODE_ELEMENT ) 655 { 656 node = child; 657 child = child.getFirst(); 658 } 659 660 return node; 661 } 662 } 663 664 if ( CKEDITOR.env.ie ) 665 { 666 range = sel.createRange(); 667 range.collapse( true ); 668 669 node = range.parentElement(); 670 } 671 else 672 { 673 node = sel.anchorNode; 674 675 if ( node.nodeType != 1 ) 676 node = node.parentNode; 677 } 678 } 679 680 return cache.startElement = ( node ? new CKEDITOR.dom.element( node ) : null ); 681 }, 682 683 /** 684 * Gets the current selected element. 685 * @returns {CKEDITOR.dom.element} The selected element. Null if no 686 * selection is available or the selection type is not 687 * {@link CKEDITOR.SELECTION_ELEMENT}. 688 * @example 689 * var element = editor.getSelection().<b>getSelectedElement()</b>; 690 * alert( element.getName() ); 691 */ 692 getSelectedElement : function() 693 { 694 var cache = this._.cache; 695 if ( cache.selectedElement !== undefined ) 696 return cache.selectedElement; 697 698 var node; 699 700 if ( this.getType() == CKEDITOR.SELECTION_ELEMENT ) 701 { 702 var sel = this.getNative(); 703 704 if ( CKEDITOR.env.ie ) 705 { 706 try 707 { 708 node = sel.createRange().item(0); 709 } 710 catch(e) {} 711 } 712 else 713 { 714 var range = sel.getRangeAt( 0 ); 715 node = range.startContainer.childNodes[ range.startOffset ]; 716 } 717 } 718 719 return cache.selectedElement = ( node ? new CKEDITOR.dom.element( node ) : null ); 720 }, 721 722 lock : function() 723 { 724 // Call all cacheable function. 725 this.getRanges(); 726 this.getStartElement(); 727 this.getSelectedElement(); 728 729 // The native selection is not available when locked. 730 this._.cache.nativeSel = {}; 731 732 this.isLocked = true; 733 734 // Save this selection inside the DOM document. 735 this.document.setCustomData( 'cke_locked_selection', this ); 736 }, 737 738 unlock : function( restore ) 739 { 740 var doc = this.document, 741 lockedSelection = doc.getCustomData( 'cke_locked_selection' ); 742 743 if ( lockedSelection ) 744 { 745 doc.setCustomData( 'cke_locked_selection', null ); 746 747 if ( restore ) 748 { 749 var selectedElement = lockedSelection.getSelectedElement(), 750 ranges = !selectedElement && lockedSelection.getRanges(); 751 752 this.isLocked = false; 753 this.reset(); 754 755 doc.getBody().focus(); 756 757 if ( selectedElement ) 758 this.selectElement( selectedElement ); 759 else 760 this.selectRanges( ranges ); 761 } 762 } 763 764 if ( !lockedSelection || !restore ) 765 { 766 this.isLocked = false; 767 this.reset(); 768 } 769 }, 770 771 reset : function() 772 { 773 this._.cache = {}; 774 }, 775 776 selectElement : function( element ) 777 { 778 if ( this.isLocked ) 779 { 780 var range = new CKEDITOR.dom.range(); 781 range.setStartBefore( element ); 782 range.setEndAfter( element ); 783 784 this._.cache.selectedElement = element; 785 this._.cache.startElement = element; 786 this._.cache.ranges = [ range ]; 787 this._.cache.type = CKEDITOR.SELECTION_ELEMENT; 788 789 return; 790 } 791 792 if ( CKEDITOR.env.ie ) 793 { 794 this.getNative().empty(); 795 796 try 797 { 798 // Try to select the node as a control. 799 range = this.document.$.body.createControlRange(); 800 range.addElement( element.$ ); 801 range.select(); 802 } 803 catch(e) 804 { 805 // If failed, select it as a text range. 806 range = this.document.$.body.createTextRange(); 807 range.moveToElementText( element.$ ); 808 range.select(); 809 } 810 811 this.reset(); 812 } 813 else 814 { 815 // Create the range for the element. 816 range = this.document.$.createRange(); 817 range.selectNode( element.$ ); 818 819 // Select the range. 820 var sel = this.getNative(); 821 sel.removeAllRanges(); 822 sel.addRange( range ); 823 824 this.reset(); 825 } 826 }, 827 828 selectRanges : function( ranges ) 829 { 830 if ( this.isLocked ) 831 { 832 this._.cache.selectedElement = null; 833 this._.cache.startElement = ranges[ 0 ].getTouchedStartNode(); 834 this._.cache.ranges = ranges; 835 this._.cache.type = CKEDITOR.SELECTION_TEXT; 836 837 return; 838 } 839 840 if ( CKEDITOR.env.ie ) 841 { 842 // IE doesn't accept multiple ranges selection, so we just 843 // select the first one. 844 if ( ranges[ 0 ] ) 845 ranges[ 0 ].select(); 846 847 this.reset(); 848 } 849 else 850 { 851 var sel = this.getNative(); 852 sel.removeAllRanges(); 853 854 for ( var i = 0 ; i < ranges.length ; i++ ) 855 { 856 var range = ranges[ i ]; 857 var nativeRange = this.document.$.createRange(); 858 nativeRange.setStart( range.startContainer.$, range.startOffset ); 859 nativeRange.setEnd( range.endContainer.$, range.endOffset ); 860 861 // Select the range. 862 sel.addRange( nativeRange ); 863 } 864 865 this.reset(); 866 } 867 }, 868 869 createBookmarks : function( serializable ) 870 { 871 var retval = [], 872 ranges = this.getRanges(), 873 length = ranges.length, 874 bookmark; 875 for ( var i = 0; i < length ; i++ ) 876 { 877 retval.push( bookmark = ranges[ i ].createBookmark( serializable, true ) ); 878 879 serializable = bookmark.serializable; 880 881 var bookmarkStart = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode, 882 bookmarkEnd = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode; 883 884 // Updating the offset values for rest of ranges which have been mangled(#3256). 885 for ( var j = i + 1 ; j < length ; j++ ) 886 { 887 var dirtyRange = ranges[ j ], 888 rangeStart = dirtyRange.startContainer, 889 rangeEnd = dirtyRange.endContainer; 890 891 rangeStart.equals( bookmarkStart.getParent() ) && dirtyRange.startOffset++; 892 rangeStart.equals( bookmarkEnd.getParent() ) && dirtyRange.startOffset++; 893 rangeEnd.equals( bookmarkStart.getParent() ) && dirtyRange.endOffset++; 894 rangeEnd.equals( bookmarkEnd.getParent() ) && dirtyRange.endOffset++; 895 } 896 } 897 898 return retval; 899 }, 900 901 createBookmarks2 : function( normalized ) 902 { 903 var bookmarks = [], 904 ranges = this.getRanges(); 905 906 for ( var i = 0 ; i < ranges.length ; i++ ) 907 bookmarks.push( ranges[i].createBookmark2( normalized ) ); 908 909 return bookmarks; 910 }, 911 912 selectBookmarks : function( bookmarks ) 913 { 914 var ranges = []; 915 for ( var i = 0 ; i < bookmarks.length ; i++ ) 916 { 917 var range = new CKEDITOR.dom.range( this.document ); 918 range.moveToBookmark( bookmarks[i] ); 919 ranges.push( range ); 920 } 921 this.selectRanges( ranges ); 922 return this; 923 } 924 }; 925 })(); 926 927 CKEDITOR.dom.range.prototype.select = 928 CKEDITOR.env.ie ? 929 // V2 930 function() 931 { 932 var collapsed = this.collapsed; 933 var isStartMakerAlone; 934 var dummySpan; 935 936 var bookmark = this.createBookmark(); 937 938 // Create marker tags for the start and end boundaries. 939 var startNode = bookmark.startNode; 940 941 var endNode; 942 if ( !collapsed ) 943 endNode = bookmark.endNode; 944 945 // Create the main range which will be used for the selection. 946 var ieRange = this.document.$.body.createTextRange(); 947 948 // Position the range at the start boundary. 949 ieRange.moveToElementText( startNode.$ ); 950 ieRange.moveStart( 'character', 1 ); 951 952 if ( endNode ) 953 { 954 // Create a tool range for the end. 955 var ieRangeEnd = this.document.$.body.createTextRange(); 956 957 // Position the tool range at the end. 958 ieRangeEnd.moveToElementText( endNode.$ ); 959 960 // Move the end boundary of the main range to match the tool range. 961 ieRange.setEndPoint( 'EndToEnd', ieRangeEnd ); 962 ieRange.moveEnd( 'character', -1 ); 963 } 964 else 965 { 966 // The isStartMakerAlone logic comes from V2. It guarantees that the lines 967 // will expand and that the cursor will be blinking on the right place. 968 // Actually, we are using this flag just to avoid using this hack in all 969 // situations, but just on those needed. 970 971 // But, in V3, somehow it is not interested on working whe hitting SHIFT+ENTER 972 // inside text. So, let's jsut leave the hack happen always. 973 974 // I'm still leaving the code here just in case. We may find some other IE 975 // weirdness and uncommenting this stuff may be useful. 976 977 // isStartMakerAlone = ( !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) ) 978 // && !startNode.hasNext(); 979 980 // Append a temporary <span></span> before the selection. 981 // This is needed to avoid IE destroying selections inside empty 982 // inline elements, like <b></b> (#253). 983 // It is also needed when placing the selection right after an inline 984 // element to avoid the selection moving inside of it. 985 dummySpan = this.document.createElement( 'span' ); 986 dummySpan.setHtml( '' ); // Zero Width No-Break Space (U+FEFF). See #1359. 987 dummySpan.insertBefore( startNode ); 988 989 // if ( isStartMakerAlone ) 990 // { 991 // To expand empty blocks or line spaces after <br>, we need 992 // instead to have any char, which will be later deleted using the 993 // selection. 994 // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359) 995 this.document.createText( '\ufeff' ).insertBefore( startNode ); 996 // } 997 } 998 999 // Remove the markers (reset the position, because of the changes in the DOM tree). 1000 this.setStartBefore( startNode ); 1001 startNode.remove(); 1002 1003 if ( collapsed ) 1004 { 1005 // if ( isStartMakerAlone ) 1006 // { 1007 // Move the selection start to include the temporary \ufeff. 1008 ieRange.moveStart( 'character', -1 ); 1009 1010 ieRange.select(); 1011 1012 // Remove our temporary stuff. 1013 this.document.$.selection.clear(); 1014 // } 1015 // else 1016 // ieRange.select(); 1017 1018 dummySpan.remove(); 1019 } 1020 else 1021 { 1022 this.setEndBefore( endNode ); 1023 endNode.remove(); 1024 ieRange.select(); 1025 } 1026 } 1027 : 1028 function() 1029 { 1030 var startContainer = this.startContainer; 1031 1032 // If we have a collapsed range, inside an empty element, we must add 1033 // something to it, otherwise the caret will not be visible. 1034 if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() ) 1035 startContainer.append( new CKEDITOR.dom.text( '' ) ); 1036 1037 var nativeRange = this.document.$.createRange(); 1038 nativeRange.setStart( startContainer.$, this.startOffset ); 1039 1040 try 1041 { 1042 nativeRange.setEnd( this.endContainer.$, this.endOffset ); 1043 } 1044 catch ( e ) 1045 { 1046 // There is a bug in Firefox implementation (it would be too easy 1047 // otherwise). The new start can't be after the end (W3C says it can). 1048 // So, let's create a new range and collapse it to the desired point. 1049 if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 ) 1050 { 1051 this.collapse( true ); 1052 nativeRange.setEnd( this.endContainer.$, this.endOffset ); 1053 } 1054 else 1055 throw( e ); 1056 } 1057 1058 var selection = this.document.getSelection().getNative(); 1059 selection.removeAllRanges(); 1060 selection.addRange( nativeRange ); 1061 }; 1062