| [ 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 (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 || !sel.document.getWindow().$ ) 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 modes : { wysiwyg : 1, source : 1 }, 76 exec : function( editor ) 77 { 78 switch ( editor.mode ) 79 { 80 case 'wysiwyg' : 81 editor.document.$.execCommand( 'SelectAll', false, null ); 82 break; 83 case 'source' : 84 // Select the contents of the textarea 85 var textarea = editor.textarea.$ ; 86 if ( CKEDITOR.env.ie ) 87 { 88 textarea.createTextRange().execCommand( 'SelectAll' ) ; 89 } 90 else 91 { 92 textarea.selectionStart = 0 ; 93 textarea.selectionEnd = textarea.value.length ; 94 } 95 textarea.focus() ; 96 } 97 }, 98 canUndo : false 99 }; 100 101 CKEDITOR.plugins.add( 'selection', 102 { 103 init : function( editor ) 104 { 105 editor.on( 'contentDom', function() 106 { 107 var doc = editor.document, 108 body = doc.getBody(), 109 html = doc.getDocumentElement(); 110 111 if ( CKEDITOR.env.ie ) 112 { 113 // Other browsers don't loose the selection if the 114 // editor document loose the focus. In IE, we don't 115 // have support for it, so we reproduce it here, other 116 // than firing the selection change event. 117 118 var savedRange, 119 saveEnabled, 120 restoreEnabled = 1; 121 122 // "onfocusin" is fired before "onfocus". It makes it 123 // possible to restore the selection before click 124 // events get executed. 125 body.on( 'focusin', function( evt ) 126 { 127 // If there are elements with layout they fire this event but 128 // it must be ignored to allow edit its contents #4682 129 if ( evt.data.$.srcElement.nodeName != 'BODY' ) 130 return; 131 132 // If we have saved a range, restore it at this 133 // point. 134 if ( savedRange ) 135 { 136 if ( restoreEnabled ) 137 { 138 // Well not break because of this. 139 try 140 { 141 savedRange.select(); 142 } 143 catch (e) 144 {} 145 } 146 147 savedRange = null; 148 } 149 }); 150 151 body.on( 'focus', function() 152 { 153 // Enable selections to be saved. 154 saveEnabled = true; 155 156 saveSelection(); 157 }); 158 159 body.on( 'beforedeactivate', function( evt ) 160 { 161 // Ignore this event if it's caused by focus switch between 162 // internal editable control type elements, e.g. layouted paragraph. (#4682) 163 if ( evt.data.$.toElement ) 164 return; 165 166 // Disable selections from being saved. 167 saveEnabled = false; 168 restoreEnabled = 1; 169 }); 170 171 // IE before version 8 will leave cursor blinking inside the document after 172 // editor blurred unless we clean up the selection. (#4716) 173 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 ) 174 { 175 editor.on( 'blur', function( evt ) 176 { 177 editor.document && editor.document.$.selection.empty(); 178 }); 179 } 180 181 // Listening on document element ensures that 182 // scrollbar is included. (#5280) 183 html.on( 'mousedown', function () 184 { 185 // Lock restore selection now, as we have 186 // a followed 'click' event which introduce 187 // new selection. (#5735) 188 restoreEnabled = 0; 189 }); 190 191 html.on( 'mouseup', function () 192 { 193 restoreEnabled = 1; 194 }); 195 196 // In IE6/7 the blinking cursor appears, but contents are 197 // not editable. (#5634) 198 if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.version < 8 || CKEDITOR.env.quirks ) ) 199 { 200 // The 'click' event is not fired when clicking the 201 // scrollbars, so we can use it to check whether 202 // the empty space following <body> has been clicked. 203 html.on( 'click', function( evt ) 204 { 205 if ( evt.data.getTarget().getName() == 'html' ) 206 editor.getSelection().getRanges()[ 0 ].select(); 207 }); 208 } 209 210 // IE fires the "selectionchange" event when clicking 211 // inside a selection. We don't want to capture that. 212 body.on( 'mousedown', function () 213 { 214 disableSave(); 215 }); 216 217 body.on( 'mouseup', 218 function() 219 { 220 saveEnabled = true; 221 setTimeout( function() 222 { 223 saveSelection( true ); 224 }, 225 0 ); 226 }); 227 228 body.on( 'keydown', disableSave ); 229 body.on( 'keyup', 230 function() 231 { 232 saveEnabled = true; 233 saveSelection(); 234 }); 235 236 237 // IE is the only to provide the "selectionchange" 238 // event. 239 doc.on( 'selectionchange', saveSelection ); 240 241 function disableSave() 242 { 243 saveEnabled = false; 244 } 245 246 function saveSelection( testIt ) 247 { 248 if ( saveEnabled ) 249 { 250 var doc = editor.document, 251 sel = editor.getSelection(), 252 nativeSel = sel && sel.getNative(); 253 254 // There is a very specific case, when clicking 255 // inside a text selection. In that case, the 256 // selection collapses at the clicking point, 257 // but the selection object remains in an 258 // unknown state, making createRange return a 259 // range at the very start of the document. In 260 // such situation we have to test the range, to 261 // be sure it's valid. 262 if ( testIt && nativeSel && nativeSel.type == 'None' ) 263 { 264 // The "InsertImage" command can be used to 265 // test whether the selection is good or not. 266 // If not, it's enough to give some time to 267 // IE to put things in order for us. 268 if ( !doc.$.queryCommandEnabled( 'InsertImage' ) ) 269 { 270 CKEDITOR.tools.setTimeout( saveSelection, 50, this, true ); 271 return; 272 } 273 } 274 275 // Avoid saving selection from within text input. (#5747) 276 var parentTag; 277 if ( nativeSel && nativeSel.type && nativeSel.type != 'Control' 278 && ( parentTag = nativeSel.createRange() ) 279 && ( parentTag = parentTag.parentElement() ) 280 && ( parentTag = parentTag.nodeName ) 281 && parentTag.toLowerCase() in { input: 1, textarea : 1 } ) 282 { 283 return; 284 } 285 286 savedRange = nativeSel && sel.getRanges()[ 0 ]; 287 288 checkSelectionChangeTimeout.call( editor ); 289 } 290 } 291 } 292 else 293 { 294 // In other browsers, we make the selection change 295 // check based on other events, like clicks or keys 296 // press. 297 298 doc.on( 'mouseup', checkSelectionChangeTimeout, editor ); 299 doc.on( 'keyup', checkSelectionChangeTimeout, editor ); 300 } 301 }); 302 303 editor.addCommand( 'selectAll', selectAllCmd ); 304 editor.ui.addButton( 'SelectAll', 305 { 306 label : editor.lang.selectAll, 307 command : 'selectAll' 308 }); 309 310 editor.selectionChange = checkSelectionChangeTimeout; 311 } 312 }); 313 314 /** 315 * Gets the current selection from the editing area when in WYSIWYG mode. 316 * @returns {CKEDITOR.dom.selection} A selection object or null if not on 317 * WYSIWYG mode or no selection is available. 318 * @example 319 * var selection = CKEDITOR.instances.editor1.<b>getSelection()</b>; 320 * alert( selection.getType() ); 321 */ 322 CKEDITOR.editor.prototype.getSelection = function() 323 { 324 return this.document && this.document.getSelection(); 325 }; 326 327 CKEDITOR.editor.prototype.forceNextSelectionCheck = function() 328 { 329 delete this._.selectionPreviousPath; 330 }; 331 332 /** 333 * Gets the current selection from the document. 334 * @returns {CKEDITOR.dom.selection} A selection object. 335 * @example 336 * var selection = CKEDITOR.instances.editor1.document.<b>getSelection()</b>; 337 * alert( selection.getType() ); 338 */ 339 CKEDITOR.dom.document.prototype.getSelection = function() 340 { 341 var sel = new CKEDITOR.dom.selection( this ); 342 return ( !sel || sel.isInvalid ) ? null : sel; 343 }; 344 345 /** 346 * No selection. 347 * @constant 348 * @example 349 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE ) 350 * alert( 'Nothing is selected' ); 351 */ 352 CKEDITOR.SELECTION_NONE = 1; 353 354 /** 355 * Text or collapsed selection. 356 * @constant 357 * @example 358 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT ) 359 * alert( 'Text is selected' ); 360 */ 361 CKEDITOR.SELECTION_TEXT = 2; 362 363 /** 364 * Element selection. 365 * @constant 366 * @example 367 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT ) 368 * alert( 'An element is selected' ); 369 */ 370 CKEDITOR.SELECTION_ELEMENT = 3; 371 372 /** 373 * Manipulates the selection in a DOM document. 374 * @constructor 375 * @example 376 */ 377 CKEDITOR.dom.selection = function( document ) 378 { 379 var lockedSelection = document.getCustomData( 'cke_locked_selection' ); 380 381 if ( lockedSelection ) 382 return lockedSelection; 383 384 this.document = document; 385 this.isLocked = false; 386 this._ = 387 { 388 cache : {} 389 }; 390 391 /** 392 * IE BUG: The selection's document may be a different document than the 393 * editor document. Return null if that's the case. 394 */ 395 if ( CKEDITOR.env.ie ) 396 { 397 var range = this.getNative().createRange(); 398 if ( !range 399 || ( range.item && range.item(0).ownerDocument != this.document.$ ) 400 || ( range.parentElement && range.parentElement().ownerDocument != this.document.$ ) ) 401 { 402 this.isInvalid = true; 403 } 404 } 405 406 return this; 407 }; 408 409 var styleObjectElements = 410 { 411 img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1, 412 a:1, input:1, form:1, select:1, textarea:1, button:1, fieldset:1, th:1, thead:1, tfoot:1 413 }; 414 415 CKEDITOR.dom.selection.prototype = 416 { 417 /** 418 * Gets the native selection object from the browser. 419 * @function 420 * @returns {Object} The native selection object. 421 * @example 422 * var selection = editor.getSelection().<b>getNative()</b>; 423 */ 424 getNative : 425 CKEDITOR.env.ie ? 426 function() 427 { 428 return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.$.selection ); 429 } 430 : 431 function() 432 { 433 return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.getWindow().$.getSelection() ); 434 }, 435 436 /** 437 * Gets the type of the current selection. The following values are 438 * available: 439 * <ul> 440 * <li>{@link CKEDITOR.SELECTION_NONE} (1): No selection.</li> 441 * <li>{@link CKEDITOR.SELECTION_TEXT} (2): Text is selected or 442 * collapsed selection.</li> 443 * <li>{@link CKEDITOR.SELECTION_ELEMENT} (3): A element 444 * selection.</li> 445 * </ul> 446 * @function 447 * @returns {Number} One of the following constant values: 448 * {@link CKEDITOR.SELECTION_NONE}, {@link CKEDITOR.SELECTION_TEXT} or 449 * {@link CKEDITOR.SELECTION_ELEMENT}. 450 * @example 451 * if ( editor.getSelection().<b>getType()</b> == CKEDITOR.SELECTION_TEXT ) 452 * alert( 'Text is selected' ); 453 */ 454 getType : 455 CKEDITOR.env.ie ? 456 function() 457 { 458 var cache = this._.cache; 459 if ( cache.type ) 460 return cache.type; 461 462 var type = CKEDITOR.SELECTION_NONE; 463 464 try 465 { 466 var sel = this.getNative(), 467 ieType = sel.type; 468 469 if ( ieType == 'Text' ) 470 type = CKEDITOR.SELECTION_TEXT; 471 472 if ( ieType == 'Control' ) 473 type = CKEDITOR.SELECTION_ELEMENT; 474 475 // It is possible that we can still get a text range 476 // object even when type == 'None' is returned by IE. 477 // So we'd better check the object returned by 478 // createRange() rather than by looking at the type. 479 if ( sel.createRange().parentElement ) 480 type = CKEDITOR.SELECTION_TEXT; 481 } 482 catch(e) {} 483 484 return ( cache.type = type ); 485 } 486 : 487 function() 488 { 489 var cache = this._.cache; 490 if ( cache.type ) 491 return cache.type; 492 493 var type = CKEDITOR.SELECTION_TEXT; 494 495 var sel = this.getNative(); 496 497 if ( !sel ) 498 type = CKEDITOR.SELECTION_NONE; 499 else if ( sel.rangeCount == 1 ) 500 { 501 // Check if the actual selection is a control (IMG, 502 // TABLE, HR, etc...). 503 504 var range = sel.getRangeAt(0), 505 startContainer = range.startContainer; 506 507 if ( startContainer == range.endContainer 508 && startContainer.nodeType == 1 509 && ( range.endOffset - range.startOffset ) == 1 510 && styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] ) 511 { 512 type = CKEDITOR.SELECTION_ELEMENT; 513 } 514 } 515 516 return ( cache.type = type ); 517 }, 518 519 /** 520 * Retrieve the {@link CKEDITOR.dom.range} instances that represent the current selection. 521 * Note: Some browsers returns multiple ranges even on a sequent selection, e.g. Firefox returns 522 * one range for each table cell when one or more table row is selected. 523 * @return {Array} 524 * @example 525 * var ranges = selection.getRanges(); 526 * alert(ranges.length); 527 */ 528 getRanges : (function () 529 { 530 var func = CKEDITOR.env.ie ? 531 ( function() 532 { 533 // Finds the container and offset for a specific boundary 534 // of an IE range. 535 var getBoundaryInformation = function( range, start ) 536 { 537 // Creates a collapsed range at the requested boundary. 538 range = range.duplicate(); 539 range.collapse( start ); 540 541 // Gets the element that encloses the range entirely. 542 var parent = range.parentElement(); 543 var siblings = parent.childNodes; 544 545 var testRange; 546 547 for ( var i = 0 ; i < siblings.length ; i++ ) 548 { 549 var child = siblings[ i ]; 550 if ( child.nodeType == 1 ) 551 { 552 testRange = range.duplicate(); 553 554 testRange.moveToElementText( child ); 555 556 var comparisonStart = testRange.compareEndPoints( 'StartToStart', range ), 557 comparisonEnd = testRange.compareEndPoints( 'EndToStart', range ); 558 559 testRange.collapse(); 560 561 if ( comparisonStart > 0 ) 562 break; 563 // When selection stay at the side of certain self-closing elements, e.g. BR, 564 // our comparison will never shows an equality. (#4824) 565 else if ( !comparisonStart 566 || comparisonEnd == 1 && comparisonStart == -1 ) 567 return { container : parent, offset : i }; 568 else if ( !comparisonEnd ) 569 return { container : parent, offset : i + 1 }; 570 571 testRange = null; 572 } 573 } 574 575 if ( !testRange ) 576 { 577 testRange = range.duplicate(); 578 testRange.moveToElementText( parent ); 579 testRange.collapse( false ); 580 } 581 582 testRange.setEndPoint( 'StartToStart', range ); 583 // IE report line break as CRLF with range.text but 584 // only LF with textnode.nodeValue, normalize them to avoid 585 // breaking character counting logic below. (#3949) 586 var distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; 587 588 try 589 { 590 while ( distance > 0 ) 591 distance -= siblings[ --i ].nodeValue.length; 592 } 593 // Measurement in IE could be somtimes wrong because of <select> element. (#4611) 594 catch( e ) 595 { 596 distance = 0; 597 } 598 599 600 if ( distance === 0 ) 601 { 602 return { 603 container : parent, 604 offset : i 605 }; 606 } 607 else 608 { 609 return { 610 container : siblings[ i ], 611 offset : -distance 612 }; 613 } 614 }; 615 616 return function() 617 { 618 // IE doesn't have range support (in the W3C way), so we 619 // need to do some magic to transform selections into 620 // CKEDITOR.dom.range instances. 621 622 var sel = this.getNative(), 623 nativeRange = sel && sel.createRange(), 624 type = this.getType(), 625 range; 626 627 if ( !sel ) 628 return []; 629 630 if ( type == CKEDITOR.SELECTION_TEXT ) 631 { 632 range = new CKEDITOR.dom.range( this.document ); 633 634 var boundaryInfo = getBoundaryInformation( nativeRange, true ); 635 range.setStart( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); 636 637 boundaryInfo = getBoundaryInformation( nativeRange ); 638 range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); 639 640 return [ range ]; 641 } 642 else if ( type == CKEDITOR.SELECTION_ELEMENT ) 643 { 644 var retval = []; 645 646 for ( var i = 0 ; i < nativeRange.length ; i++ ) 647 { 648 var element = nativeRange.item( i ), 649 parentElement = element.parentNode, 650 j = 0; 651 652 range = new CKEDITOR.dom.range( this.document ); 653 654 for (; j < parentElement.childNodes.length && parentElement.childNodes[j] != element ; j++ ) 655 { /*jsl:pass*/ } 656 657 range.setStart( new CKEDITOR.dom.node( parentElement ), j ); 658 range.setEnd( new CKEDITOR.dom.node( parentElement ), j + 1 ); 659 retval.push( range ); 660 } 661 662 return retval; 663 } 664 665 return []; 666 }; 667 })() 668 : 669 function() 670 { 671 672 // On browsers implementing the W3C range, we simply 673 // tranform the native ranges in CKEDITOR.dom.range 674 // instances. 675 676 var ranges = []; 677 var sel = this.getNative(); 678 679 if ( !sel ) 680 return []; 681 682 for ( var i = 0 ; i < sel.rangeCount ; i++ ) 683 { 684 var nativeRange = sel.getRangeAt( i ); 685 var range = new CKEDITOR.dom.range( this.document ); 686 687 range.setStart( new CKEDITOR.dom.node( nativeRange.startContainer ), nativeRange.startOffset ); 688 range.setEnd( new CKEDITOR.dom.node( nativeRange.endContainer ), nativeRange.endOffset ); 689 ranges.push( range ); 690 } 691 return ranges; 692 }; 693 694 return function( onlyEditables ) 695 { 696 var cache = this._.cache; 697 if ( cache.ranges && !onlyEditables ) 698 return cache.ranges; 699 else if ( !cache.ranges ) 700 cache.ranges = new CKEDITOR.dom.rangeList( func.call( this ) ); 701 702 // Split range into multiple by read-only nodes. 703 if ( onlyEditables ) 704 { 705 var ranges = cache.ranges; 706 for ( var i = 0; i < ranges.length; i++ ) 707 { 708 var range = ranges[ i ]; 709 710 // Drop range spans inside one ready-only node. 711 var parent = range.getCommonAncestor(); 712 if ( parent.isReadOnly()) 713 ranges.splice( i, 1 ); 714 715 if ( range.collapsed ) 716 continue; 717 718 var startContainer = range.startContainer, 719 endContainer = range.endContainer, 720 startOffset = range.startOffset, 721 endOffset = range.endOffset, 722 walkerRange = range.clone(); 723 724 // Range may start inside a non-editable element, restart range 725 // by the end of it. 726 var readOnly; 727 if ( ( readOnly = startContainer.isReadOnly() ) ) 728 range.setStartAfter( readOnly ); 729 730 // Enlarge range start/end with text node to avoid walker 731 // being DOM destructive, it doesn't interfere our checking 732 // of elements below as well. 733 if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT ) 734 { 735 if ( startOffset >= startContainer.getLength() ) 736 walkerRange.setStartAfter( startContainer ); 737 else 738 walkerRange.setStartBefore( startContainer ); 739 } 740 741 if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT ) 742 { 743 if ( !endOffset ) 744 walkerRange.setEndBefore( endContainer ); 745 else 746 walkerRange.setEndAfter( endContainer ); 747 } 748 749 // Looking for non-editable element inside the range. 750 var walker = new CKEDITOR.dom.walker( walkerRange ); 751 walker.evaluator = function( node ) 752 { 753 if ( node.type == CKEDITOR.NODE_ELEMENT 754 && node.getAttribute( 'contenteditable' ) == 'false' ) 755 { 756 var newRange = range.clone(); 757 range.setEndBefore( node ); 758 759 // Drop collapsed range around read-only elements, 760 // it make sure the range list empty when selecting 761 // only non-editable elements. 762 if ( range.collapsed ) 763 ranges.splice( i--, 1 ); 764 765 // Avoid creating invalid range. 766 if ( !( node.getPosition( walkerRange.endContainer ) & CKEDITOR.POSITION_CONTAINS ) ) 767 { 768 newRange.setStartAfter( node ); 769 if ( !newRange.collapsed ) 770 ranges.splice( i + 1, 0, newRange ); 771 } 772 773 return true; 774 } 775 776 return false; 777 }; 778 779 walker.next(); 780 } 781 } 782 783 return cache.ranges; 784 }; 785 })(), 786 787 /** 788 * Gets the DOM element in which the selection starts. 789 * @returns {CKEDITOR.dom.element} The element at the beginning of the 790 * selection. 791 * @example 792 * var element = editor.getSelection().<b>getStartElement()</b>; 793 * alert( element.getName() ); 794 */ 795 getStartElement : function() 796 { 797 var cache = this._.cache; 798 if ( cache.startElement !== undefined ) 799 return cache.startElement; 800 801 var node, 802 sel = this.getNative(); 803 804 switch ( this.getType() ) 805 { 806 case CKEDITOR.SELECTION_ELEMENT : 807 return this.getSelectedElement(); 808 809 case CKEDITOR.SELECTION_TEXT : 810 811 var range = this.getRanges()[0]; 812 813 if ( range ) 814 { 815 if ( !range.collapsed ) 816 { 817 range.optimize(); 818 819 // Decrease the range content to exclude particial 820 // selected node on the start which doesn't have 821 // visual impact. ( #3231 ) 822 while ( true ) 823 { 824 var startContainer = range.startContainer, 825 startOffset = range.startOffset; 826 // Limit the fix only to non-block elements.(#3950) 827 if ( startOffset == ( startContainer.getChildCount ? 828 startContainer.getChildCount() : startContainer.getLength() ) 829 && !startContainer.isBlockBoundary() ) 830 range.setStartAfter( startContainer ); 831 else break; 832 } 833 834 node = range.startContainer; 835 836 if ( node.type != CKEDITOR.NODE_ELEMENT ) 837 return node.getParent(); 838 839 node = node.getChild( range.startOffset ); 840 841 if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) 842 return range.startContainer; 843 844 var child = node.getFirst(); 845 while ( child && child.type == CKEDITOR.NODE_ELEMENT ) 846 { 847 node = child; 848 child = child.getFirst(); 849 } 850 851 return node; 852 } 853 } 854 855 if ( CKEDITOR.env.ie ) 856 { 857 range = sel.createRange(); 858 range.collapse( true ); 859 860 node = range.parentElement(); 861 } 862 else 863 { 864 node = sel.anchorNode; 865 866 if ( node && node.nodeType != 1 ) 867 node = node.parentNode; 868 } 869 } 870 871 return cache.startElement = ( node ? new CKEDITOR.dom.element( node ) : null ); 872 }, 873 874 /** 875 * Gets the current selected element. 876 * @returns {CKEDITOR.dom.element} The selected element. Null if no 877 * selection is available or the selection type is not 878 * {@link CKEDITOR.SELECTION_ELEMENT}. 879 * @example 880 * var element = editor.getSelection().<b>getSelectedElement()</b>; 881 * alert( element.getName() ); 882 */ 883 getSelectedElement : function() 884 { 885 var cache = this._.cache; 886 if ( cache.selectedElement !== undefined ) 887 return cache.selectedElement; 888 889 var self = this; 890 891 var node = CKEDITOR.tools.tryThese( 892 // Is it native IE control type selection? 893 function() 894 { 895 return self.getNative().createRange().item( 0 ); 896 }, 897 // Figure it out by checking if there's a single enclosed 898 // node of the range. 899 function() 900 { 901 var range = self.getRanges()[ 0 ], 902 enclosed, 903 selected; 904 905 // Check first any enclosed element, e.g. <ul>[<li><a href="#">item</a></li>]</ul> 906 for ( var i = 2; i && !( ( enclosed = range.getEnclosedNode() ) 907 && ( enclosed.type == CKEDITOR.NODE_ELEMENT ) 908 && styleObjectElements[ enclosed.getName() ] 909 && ( selected = enclosed ) ); i-- ) 910 { 911 // Then check any deep wrapped element, e.g. [<b><i><img /></i></b>] 912 range.shrink( CKEDITOR.SHRINK_ELEMENT ); 913 } 914 915 return selected.$; 916 }); 917 918 return cache.selectedElement = ( node ? new CKEDITOR.dom.element( node ) : null ); 919 }, 920 921 lock : function() 922 { 923 // Call all cacheable function. 924 this.getRanges(); 925 this.getStartElement(); 926 this.getSelectedElement(); 927 928 // The native selection is not available when locked. 929 this._.cache.nativeSel = {}; 930 931 this.isLocked = true; 932 933 // Save this selection inside the DOM document. 934 this.document.setCustomData( 'cke_locked_selection', this ); 935 }, 936 937 unlock : function( restore ) 938 { 939 var doc = this.document, 940 lockedSelection = doc.getCustomData( 'cke_locked_selection' ); 941 942 if ( lockedSelection ) 943 { 944 doc.setCustomData( 'cke_locked_selection', null ); 945 946 if ( restore ) 947 { 948 var selectedElement = lockedSelection.getSelectedElement(), 949 ranges = !selectedElement && lockedSelection.getRanges(); 950 951 this.isLocked = false; 952 this.reset(); 953 954 doc.getBody().focus(); 955 956 if ( selectedElement ) 957 this.selectElement( selectedElement ); 958 else 959 this.selectRanges( ranges ); 960 } 961 } 962 963 if ( !lockedSelection || !restore ) 964 { 965 this.isLocked = false; 966 this.reset(); 967 } 968 }, 969 970 reset : function() 971 { 972 this._.cache = {}; 973 }, 974 975 /** 976 * Make the current selection of type {@link CKEDITOR.SELECTION_ELEMENT} by enclosing the specified element. 977 * @param element 978 */ 979 selectElement : function( element ) 980 { 981 if ( this.isLocked ) 982 { 983 var range = new CKEDITOR.dom.range( this.document ); 984 range.setStartBefore( element ); 985 range.setEndAfter( element ); 986 987 this._.cache.selectedElement = element; 988 this._.cache.startElement = element; 989 this._.cache.ranges = new CKEDITOR.dom.rangeList( range ); 990 this._.cache.type = CKEDITOR.SELECTION_ELEMENT; 991 992 return; 993 } 994 995 if ( CKEDITOR.env.ie ) 996 { 997 this.getNative().empty(); 998 999 try 1000 { 1001 // Try to select the node as a control. 1002 range = this.document.$.body.createControlRange(); 1003 range.addElement( element.$ ); 1004 range.select(); 1005 } 1006 catch(e) 1007 { 1008 // If failed, select it as a text range. 1009 range = this.document.$.body.createTextRange(); 1010 range.moveToElementText( element.$ ); 1011 range.select(); 1012 } 1013 finally 1014 { 1015 this.document.fire( 'selectionchange' ); 1016 } 1017 1018 this.reset(); 1019 } 1020 else 1021 { 1022 // Create the range for the element. 1023 range = this.document.$.createRange(); 1024 range.selectNode( element.$ ); 1025 1026 // Select the range. 1027 var sel = this.getNative(); 1028 sel.removeAllRanges(); 1029 sel.addRange( range ); 1030 1031 this.reset(); 1032 } 1033 }, 1034 1035 /** 1036 * Adding the specified ranges to document selection preceding 1037 * by clearing up the original selection. 1038 * @param {CKEDITOR.dom.range} ranges 1039 */ 1040 selectRanges : function( ranges ) 1041 { 1042 if ( this.isLocked ) 1043 { 1044 this._.cache.selectedElement = null; 1045 this._.cache.startElement = ranges[ 0 ] && ranges[ 0 ].getTouchedStartNode(); 1046 this._.cache.ranges = new CKEDITOR.dom.rangeList( ranges ); 1047 this._.cache.type = CKEDITOR.SELECTION_TEXT; 1048 1049 return; 1050 } 1051 1052 if ( CKEDITOR.env.ie ) 1053 { 1054 if ( ranges.length > 1 ) 1055 { 1056 // IE doesn't accept multiple ranges selection, so we join all into one. 1057 var last = ranges[ ranges.length -1 ] ; 1058 ranges[ 0 ].setEnd( last.endContainer, last.endOffset ); 1059 ranges.length = 1; 1060 } 1061 1062 if ( ranges[ 0 ] ) 1063 ranges[ 0 ].select(); 1064 1065 this.reset(); 1066 } 1067 else 1068 { 1069 var sel = this.getNative(); 1070 1071 if ( ranges.length ) 1072 sel.removeAllRanges(); 1073 1074 for ( var i = 0 ; i < ranges.length ; i++ ) 1075 { 1076 // Joining sequential ranges introduced by 1077 // readonly elements protection. 1078 if ( i < ranges.length -1 ) 1079 { 1080 var left = ranges[ i ], right = ranges[ i +1 ], 1081 between = left.clone(); 1082 between.setStart( left.endContainer, left.endOffset ); 1083 between.setEnd( right.startContainer, right.startOffset ); 1084 1085 // Don't confused by Firefox adjancent multi-ranges 1086 // introduced by table cells selection. 1087 if ( !between.collapsed ) 1088 { 1089 between.shrink( CKEDITOR.NODE_ELEMENT, true ); 1090 if ( between.getCommonAncestor().isReadOnly()) 1091 { 1092 right.setStart( left.startContainer, left.startOffset ); 1093 ranges.splice( i--, 1 ); 1094 continue; 1095 } 1096 } 1097 } 1098 1099 var range = ranges[ i ]; 1100 var nativeRange = this.document.$.createRange(); 1101 var startContainer = range.startContainer; 1102 1103 // In FF2, if we have a collapsed range, inside an empty 1104 // element, we must add something to it otherwise the caret 1105 // will not be visible. 1106 if ( range.collapsed && 1107 ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) && 1108 startContainer.type == CKEDITOR.NODE_ELEMENT && 1109 !startContainer.getChildCount() ) 1110 { 1111 startContainer.appendText( '' ); 1112 } 1113 1114 nativeRange.setStart( startContainer.$, range.startOffset ); 1115 nativeRange.setEnd( range.endContainer.$, range.endOffset ); 1116 1117 // Select the range. 1118 sel.addRange( nativeRange ); 1119 } 1120 1121 this.reset(); 1122 } 1123 }, 1124 1125 /** 1126 * Create bookmark for every single of this selection range (from #getRanges) 1127 * by calling the {@link CKEDITOR.dom.range.prototype.createBookmark} method, 1128 * with extra cares to avoid interferon among those ranges. Same arguments are 1129 * received as with the underlay range method. 1130 */ 1131 createBookmarks : function( serializable ) 1132 { 1133 return this.getRanges().createBookmarks( serializable ); 1134 }, 1135 1136 /** 1137 * Create bookmark for every single of this selection range (from #getRanges) 1138 * by calling the {@link CKEDITOR.dom.range.prototype.createBookmark2} method, 1139 * with extra cares to avoid interferon among those ranges. Same arguments are 1140 * received as with the underlay range method. 1141 */ 1142 createBookmarks2 : function( normalized ) 1143 { 1144 return this.getRanges().createBookmarks2( normalized ); 1145 }, 1146 1147 /** 1148 * Select the virtual ranges denote by the bookmarks by calling #selectRanges. 1149 * @param bookmarks 1150 */ 1151 selectBookmarks : function( bookmarks ) 1152 { 1153 var ranges = []; 1154 for ( var i = 0 ; i < bookmarks.length ; i++ ) 1155 { 1156 var range = new CKEDITOR.dom.range( this.document ); 1157 range.moveToBookmark( bookmarks[i] ); 1158 ranges.push( range ); 1159 } 1160 this.selectRanges( ranges ); 1161 return this; 1162 }, 1163 1164 /** 1165 * Retrieve the common ancestor node of the first range and the last range. 1166 */ 1167 getCommonAncestor : function() 1168 { 1169 var ranges = this.getRanges(), 1170 startNode = ranges[ 0 ].startContainer, 1171 endNode = ranges[ ranges.length - 1 ].endContainer; 1172 return startNode.getCommonAncestor( endNode ); 1173 }, 1174 1175 /** 1176 * Moving scroll bar to the current selection's start position. 1177 */ 1178 scrollIntoView : function() 1179 { 1180 // If we have split the block, adds a temporary span at the 1181 // range position and scroll relatively to it. 1182 var start = this.getStartElement(); 1183 start.scrollIntoView(); 1184 } 1185 }; 1186 })(); 1187 1188 ( function() 1189 { 1190 var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), 1191 fillerTextRegex = /\ufeff|\u00a0/, 1192 nonCells = { table:1,tbody:1,tr:1 }; 1193 1194 CKEDITOR.dom.range.prototype.select = 1195 CKEDITOR.env.ie ? 1196 // V2 1197 function( forceExpand ) 1198 { 1199 var collapsed = this.collapsed; 1200 var isStartMarkerAlone; 1201 var dummySpan; 1202 1203 // IE doesn't support selecting the entire table row/cell, move the selection into cells, e.g. 1204 // <table><tbody><tr>[<td>cell</b></td>... => <table><tbody><tr><td>[cell</td>... 1205 if ( this.startContainer.type == CKEDITOR.NODE_ELEMENT && this.startContainer.getName() in nonCells 1206 || this.endContainer.type == CKEDITOR.NODE_ELEMENT && this.endContainer.getName() in nonCells ) 1207 { 1208 this.shrink( CKEDITOR.NODE_ELEMENT, true ); 1209 } 1210 1211 var bookmark = this.createBookmark(); 1212 1213 // Create marker tags for the start and end boundaries. 1214 var startNode = bookmark.startNode; 1215 1216 var endNode; 1217 if ( !collapsed ) 1218 endNode = bookmark.endNode; 1219 1220 // Create the main range which will be used for the selection. 1221 var ieRange = this.document.$.body.createTextRange(); 1222 1223 // Position the range at the start boundary. 1224 ieRange.moveToElementText( startNode.$ ); 1225 ieRange.moveStart( 'character', 1 ); 1226 1227 if ( endNode ) 1228 { 1229 // Create a tool range for the end. 1230 var ieRangeEnd = this.document.$.body.createTextRange(); 1231 1232 // Position the tool range at the end. 1233 ieRangeEnd.moveToElementText( endNode.$ ); 1234 1235 // Move the end boundary of the main range to match the tool range. 1236 ieRange.setEndPoint( 'EndToEnd', ieRangeEnd ); 1237 ieRange.moveEnd( 'character', -1 ); 1238 } 1239 else 1240 { 1241 // The isStartMarkerAlone logic comes from V2. It guarantees that the lines 1242 // will expand and that the cursor will be blinking on the right place. 1243 // Actually, we are using this flag just to avoid using this hack in all 1244 // situations, but just on those needed. 1245 var next = startNode.getNext( notWhitespaces ); 1246 isStartMarkerAlone = ( !( next && next.getText && next.getText().match( fillerTextRegex ) ) // already a filler there? 1247 && ( forceExpand || !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) ) ); 1248 1249 // Append a temporary <span></span> before the selection. 1250 // This is needed to avoid IE destroying selections inside empty 1251 // inline elements, like <b></b> (#253). 1252 // It is also needed when placing the selection right after an inline 1253 // element to avoid the selection moving inside of it. 1254 dummySpan = this.document.createElement( 'span' ); 1255 dummySpan.setHtml( '' ); // Zero Width No-Break Space (U+FEFF). See #1359. 1256 dummySpan.insertBefore( startNode ); 1257 1258 if ( isStartMarkerAlone ) 1259 { 1260 // To expand empty blocks or line spaces after <br>, we need 1261 // instead to have any char, which will be later deleted using the 1262 // selection. 1263 // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359) 1264 this.document.createText( '\ufeff' ).insertBefore( startNode ); 1265 } 1266 } 1267 1268 // Remove the markers (reset the position, because of the changes in the DOM tree). 1269 this.setStartBefore( startNode ); 1270 startNode.remove(); 1271 1272 if ( collapsed ) 1273 { 1274 if ( isStartMarkerAlone ) 1275 { 1276 // Move the selection start to include the temporary \ufeff. 1277 ieRange.moveStart( 'character', -1 ); 1278 1279 ieRange.select(); 1280 1281 // Remove our temporary stuff. 1282 this.document.$.selection.clear(); 1283 } 1284 else 1285 ieRange.select(); 1286 1287 this.moveToPosition( dummySpan, CKEDITOR.POSITION_BEFORE_START ); 1288 dummySpan.remove(); 1289 } 1290 else 1291 { 1292 this.setEndBefore( endNode ); 1293 endNode.remove(); 1294 ieRange.select(); 1295 } 1296 1297 this.document.fire( 'selectionchange' ); 1298 } 1299 : 1300 function() 1301 { 1302 var startContainer = this.startContainer; 1303 1304 // If we have a collapsed range, inside an empty element, we must add 1305 // something to it, otherwise the caret will not be visible. 1306 if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() ) 1307 startContainer.append( new CKEDITOR.dom.text( '' ) ); 1308 1309 var nativeRange = this.document.$.createRange(); 1310 nativeRange.setStart( startContainer.$, this.startOffset ); 1311 1312 try 1313 { 1314 nativeRange.setEnd( this.endContainer.$, this.endOffset ); 1315 } 1316 catch ( e ) 1317 { 1318 // There is a bug in Firefox implementation (it would be too easy 1319 // otherwise). The new start can't be after the end (W3C says it can). 1320 // So, let's create a new range and collapse it to the desired point. 1321 if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 ) 1322 { 1323 this.collapse( true ); 1324 nativeRange.setEnd( this.endContainer.$, this.endOffset ); 1325 } 1326 else 1327 throw( e ); 1328 } 1329 1330 var selection = this.document.getSelection().getNative(); 1331 selection.removeAllRanges(); 1332 selection.addRange( nativeRange ); 1333 }; 1334 } )();
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 |