[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/sites/all/libraries/ckeditor/_source/plugins/selection/ -> plugin.js (source)

   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>&#65279;</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( '&#65279;' );    // 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  } )();


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7