[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/sites/all/libraries/ckeditor/_source/plugins/undo/ -> 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  /**

   7   * @fileOverview Undo/Redo system for saving shapshot for document modification

   8   *        and other recordable changes.

   9   */
  10  
  11  (function()
  12  {
  13      CKEDITOR.plugins.add( 'undo',
  14      {
  15          requires : [ 'selection', 'wysiwygarea' ],
  16  
  17          init : function( editor )
  18          {
  19              var undoManager = new UndoManager( editor );
  20  
  21              var undoCommand = editor.addCommand( 'undo',
  22                  {
  23                      exec : function()
  24                      {
  25                          if ( undoManager.undo() )
  26                          {
  27                              editor.selectionChange();
  28                              this.fire( 'afterUndo' );
  29                          }
  30                      },
  31                      state : CKEDITOR.TRISTATE_DISABLED,
  32                      canUndo : false
  33                  });
  34  
  35              var redoCommand = editor.addCommand( 'redo',
  36                  {
  37                      exec : function()
  38                      {
  39                          if ( undoManager.redo() )
  40                          {
  41                              editor.selectionChange();
  42                              this.fire( 'afterRedo' );
  43                          }
  44                      },
  45                      state : CKEDITOR.TRISTATE_DISABLED,
  46                      canUndo : false
  47                  });
  48  
  49              undoManager.onChange = function()
  50              {
  51                  undoCommand.setState( undoManager.undoable() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
  52                  redoCommand.setState( undoManager.redoable() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
  53              };
  54  
  55  			function recordCommand( event )
  56              {
  57                  // If the command hasn't been marked to not support undo.

  58                  if ( undoManager.enabled && event.data.command.canUndo !== false )
  59                      undoManager.save();
  60              }
  61  
  62              // We'll save snapshots before and after executing a command.

  63              editor.on( 'beforeCommandExec', recordCommand );
  64              editor.on( 'afterCommandExec', recordCommand );
  65  
  66              // Save snapshots before doing custom changes.

  67              editor.on( 'saveSnapshot', function()
  68                  {
  69                      undoManager.save();
  70                  });
  71  
  72              // Registering keydown on every document recreation.(#3844)

  73              editor.on( 'contentDom', function()
  74                  {
  75                      editor.document.on( 'keydown', function( event )
  76                          {
  77                              // Do not capture CTRL hotkeys.

  78                              if ( !event.data.$.ctrlKey && !event.data.$.metaKey )
  79                                  undoManager.type( event );
  80                          });
  81                  });
  82  
  83              // Always save an undo snapshot - the previous mode might have

  84              // changed editor contents.

  85              editor.on( 'beforeModeUnload', function()
  86                  {
  87                      editor.mode == 'wysiwyg' && undoManager.save( true );
  88                  });
  89  
  90              // Make the undo manager available only in wysiwyg mode.

  91              editor.on( 'mode', function()
  92                  {
  93                      undoManager.enabled = editor.mode == 'wysiwyg';
  94                      undoManager.onChange();
  95                  });
  96  
  97              editor.ui.addButton( 'Undo',
  98                  {
  99                      label : editor.lang.undo,
 100                      command : 'undo'
 101                  });
 102  
 103              editor.ui.addButton( 'Redo',
 104                  {
 105                      label : editor.lang.redo,
 106                      command : 'redo'
 107                  });
 108  
 109              editor.resetUndo = function()
 110              {
 111                  // Reset the undo stack.

 112                  undoManager.reset();
 113  
 114                  // Create the first image.

 115                  editor.fire( 'saveSnapshot' );
 116              };
 117  
 118              /**

 119               * Update the undo stacks with any subsequent DOM changes after this call.

 120               * @name CKEDITOR.editor#updateUndo

 121               * @example

 122               * function()

 123               * {

 124               * editor.fire( 'updateSnapshot' );

 125               * ...

 126               *  // Ask to include subsequent (in this call stack) DOM changes to be

 127               * // considered as part of the first snapshot.

 128               *     editor.fire( 'updateSnapshot' );

 129               *     editor.document.body.append(...);

 130               * ...

 131               * }

 132               */
 133              editor.on( 'updateSnapshot', function()
 134              {
 135                  if ( undoManager.currentImage && new Image( editor ).equals( undoManager.currentImage ) )
 136                      setTimeout( function () { undoManager.update(); }, 0 );
 137              });
 138          }
 139      });
 140  
 141      CKEDITOR.plugins.undo = {};
 142  
 143      /**

 144       * Undo snapshot which represents the current document status.

 145       * @name CKEDITOR.plugins.undo.Image

 146       * @param editor The editor instance on which the image is created.

 147       */
 148      var Image = CKEDITOR.plugins.undo.Image = function( editor )
 149      {
 150          this.editor = editor;
 151          var contents = editor.getSnapshot(),
 152              selection    = contents && editor.getSelection();
 153  
 154          // In IE, we need to remove the expando attributes.

 155          CKEDITOR.env.ie && contents && ( contents = contents.replace( /\s+_cke_expando=".*?"/g, '' ) );
 156  
 157          this.contents    = contents;
 158          this.bookmarks    = selection && selection.createBookmarks2( true );
 159      };
 160  
 161      // Attributes that browser may changing them when setting via innerHTML.

 162      var protectedAttrs = /\b(?:href|src|name)="[^"]*?"/gi;
 163  
 164      Image.prototype =
 165      {
 166          equals : function( otherImage, contentOnly )
 167          {
 168  
 169              var thisContents = this.contents,
 170                  otherContents = otherImage.contents;
 171  
 172              // For IE6/7 : Comparing only the protected attribute values but not the original ones.(#4522)

 173              if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )
 174              {
 175                  thisContents = thisContents.replace( protectedAttrs, '' );
 176                  otherContents = otherContents.replace( protectedAttrs, '' );
 177              }
 178  
 179              if ( thisContents != otherContents )
 180                  return false;
 181  
 182              if ( contentOnly )
 183                  return true;
 184  
 185              var bookmarksA = this.bookmarks,
 186                  bookmarksB = otherImage.bookmarks;
 187  
 188              if ( bookmarksA || bookmarksB )
 189              {
 190                  if ( !bookmarksA || !bookmarksB || bookmarksA.length != bookmarksB.length )
 191                      return false;
 192  
 193                  for ( var i = 0 ; i < bookmarksA.length ; i++ )
 194                  {
 195                      var bookmarkA = bookmarksA[ i ],
 196                          bookmarkB = bookmarksB[ i ];
 197  
 198                      if (
 199                          bookmarkA.startOffset != bookmarkB.startOffset ||
 200                          bookmarkA.endOffset != bookmarkB.endOffset ||
 201                          !CKEDITOR.tools.arrayCompare( bookmarkA.start, bookmarkB.start ) ||
 202                          !CKEDITOR.tools.arrayCompare( bookmarkA.end, bookmarkB.end ) )
 203                      {
 204                          return false;
 205                      }
 206                  }
 207              }
 208  
 209              return true;
 210          }
 211      };
 212  
 213      /**

 214       * @constructor Main logic for Redo/Undo feature.

 215       */
 216  	function UndoManager( editor )
 217      {
 218          this.editor = editor;
 219  
 220          // Reset the undo stack.

 221          this.reset();
 222      }
 223  
 224  
 225      var editingKeyCodes = { /*Backspace*/ 8:1, /*Delete*/ 46:1 },
 226          modifierKeyCodes = { /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1 },
 227          navigationKeyCodes = { 37:1, 38:1, 39:1, 40:1 };  // Arrows: L, T, R, B

 228  
 229      UndoManager.prototype =
 230      {
 231          /**

 232           * Process undo system regard keystrikes.

 233           * @param {CKEDITOR.dom.event} event

 234           */
 235          type : function( event )
 236          {
 237              var keystroke = event && event.data.getKey(),
 238                  isModifierKey = keystroke in modifierKeyCodes,
 239                  isEditingKey = keystroke in editingKeyCodes,
 240                  wasEditingKey = this.lastKeystroke in editingKeyCodes,
 241                  sameAsLastEditingKey = isEditingKey && keystroke == this.lastKeystroke,
 242                  // Keystrokes which navigation through contents.

 243                  isReset = keystroke in navigationKeyCodes,
 244                  wasReset = this.lastKeystroke in navigationKeyCodes,
 245  
 246                  // Keystrokes which just introduce new contents.

 247                  isContent = ( !isEditingKey && !isReset ),
 248  
 249                  // Create undo snap for every different modifier key.

 250                  modifierSnapshot = ( isEditingKey && !sameAsLastEditingKey ),
 251                  // Create undo snap on the following cases:

 252                  // 1. Just start to type .

 253                  // 2. Typing some content after a modifier.

 254                  // 3. Typing some content after make a visible selection.

 255                  startedTyping = !( isModifierKey || this.typing )
 256                      || ( isContent && ( wasEditingKey || wasReset ) );
 257  
 258              if ( startedTyping || modifierSnapshot )
 259              {
 260                  var beforeTypeImage = new Image( this.editor );
 261  
 262                  // Use setTimeout, so we give the necessary time to the

 263                  // browser to insert the character into the DOM.

 264                  CKEDITOR.tools.setTimeout( function()
 265                      {
 266                          var currentSnapshot = this.editor.getSnapshot();
 267  
 268                          // In IE, we need to remove the expando attributes.

 269                          if ( CKEDITOR.env.ie )
 270                              currentSnapshot = currentSnapshot.replace( /\s+_cke_expando=".*?"/g, '' );
 271  
 272                          if ( beforeTypeImage.contents != currentSnapshot )
 273                          {
 274                              // It's safe to now indicate typing state.

 275                              this.typing = true;
 276  
 277                              // This's a special save, with specified snapshot

 278                              // and without auto 'fireChange'.

 279                              if ( !this.save( false, beforeTypeImage, false ) )
 280                                  // Drop future snapshots.

 281                                  this.snapshots.splice( this.index + 1, this.snapshots.length - this.index - 1 );
 282  
 283                              this.hasUndo = true;
 284                              this.hasRedo = false;
 285  
 286                              this.typesCount = 1;
 287                              this.modifiersCount = 1;
 288  
 289                              this.onChange();
 290                          }
 291                      },
 292                      0, this
 293                  );
 294              }
 295  
 296              this.lastKeystroke = keystroke;
 297  
 298              // Create undo snap after typed too much (over 25 times).

 299              if ( isEditingKey )
 300              {
 301                  this.typesCount = 0;
 302                  this.modifiersCount++;
 303  
 304                  if ( this.modifiersCount > 25 )
 305                  {
 306                      this.save( false, null, false );
 307                      this.modifiersCount = 1;
 308                  }
 309              }
 310              else if ( !isReset )
 311              {
 312                  this.modifiersCount = 0;
 313                  this.typesCount++;
 314  
 315                  if ( this.typesCount > 25 )
 316                  {
 317                      this.save( false, null, false );
 318                      this.typesCount = 1;
 319                  }
 320              }
 321  
 322          },
 323  
 324          reset : function()    // Reset the undo stack.
 325          {
 326              /**

 327               * Remember last pressed key.

 328               */
 329              this.lastKeystroke = 0;
 330  
 331              /**

 332               * Stack for all the undo and redo snapshots, they're always created/removed

 333               * in consistency.

 334               */
 335              this.snapshots = [];
 336  
 337              /**

 338               * Current snapshot history index.

 339               */
 340              this.index = -1;
 341  
 342              this.limit = this.editor.config.undoStackSize;
 343  
 344              this.currentImage = null;
 345  
 346              this.hasUndo = false;
 347              this.hasRedo = false;
 348  
 349              this.resetType();
 350          },
 351  
 352          /**

 353           * Reset all states about typing.

 354           * @see  UndoManager.type

 355           */
 356          resetType : function()
 357          {
 358              this.typing = false;
 359              delete this.lastKeystroke;
 360              this.typesCount = 0;
 361              this.modifiersCount = 0;
 362          },
 363          fireChange : function()
 364          {
 365              this.hasUndo = !!this.getNextImage( true );
 366              this.hasRedo = !!this.getNextImage( false );
 367              // Reset typing

 368              this.resetType();
 369              this.onChange();
 370          },
 371  
 372          /**

 373           * Save a snapshot of document image for later retrieve.

 374           */
 375          save : function( onContentOnly, image, autoFireChange )
 376          {
 377              var snapshots = this.snapshots;
 378  
 379              // Get a content image.

 380              if ( !image )
 381                  image = new Image( this.editor );
 382  
 383              // Do nothing if it was not possible to retrieve an image.

 384              if ( image.contents === false )
 385                  return false;
 386  
 387              // Check if this is a duplicate. In such case, do nothing.

 388              if ( this.currentImage && image.equals( this.currentImage, onContentOnly ) )
 389                  return false;
 390  
 391              // Drop future snapshots.

 392              snapshots.splice( this.index + 1, snapshots.length - this.index - 1 );
 393  
 394              // If we have reached the limit, remove the oldest one.

 395              if ( snapshots.length == this.limit )
 396                  snapshots.shift();
 397  
 398              // Add the new image, updating the current index.

 399              this.index = snapshots.push( image ) - 1;
 400  
 401              this.currentImage = image;
 402  
 403              if ( autoFireChange !== false )
 404                  this.fireChange();
 405              return true;
 406          },
 407  
 408          restoreImage : function( image )
 409          {
 410              this.editor.loadSnapshot( image.contents );
 411  
 412              if ( image.bookmarks )
 413                  this.editor.getSelection().selectBookmarks( image.bookmarks );
 414              else if ( CKEDITOR.env.ie )
 415              {
 416                  // IE BUG: If I don't set the selection to *somewhere* after setting

 417                  // document contents, then IE would create an empty paragraph at the bottom

 418                  // the next time the document is modified.

 419                  var $range = this.editor.document.getBody().$.createTextRange();
 420                  $range.collapse( true );
 421                  $range.select();
 422              }
 423  
 424              this.index = image.index;
 425  
 426              // Update current image with the actual editor

 427              // content, since actualy content may differ from

 428              // the original snapshot due to dom change. (#4622)

 429              this.update();
 430              this.fireChange();
 431          },
 432  
 433          // Get the closest available image.

 434          getNextImage : function( isUndo )
 435          {
 436              var snapshots = this.snapshots,
 437                  currentImage = this.currentImage,
 438                  image, i;
 439  
 440              if ( currentImage )
 441              {
 442                  if ( isUndo )
 443                  {
 444                      for ( i = this.index - 1 ; i >= 0 ; i-- )
 445                      {
 446                          image = snapshots[ i ];
 447                          if ( !currentImage.equals( image, true ) )
 448                          {
 449                              image.index = i;
 450                              return image;
 451                          }
 452                      }
 453                  }
 454                  else
 455                  {
 456                      for ( i = this.index + 1 ; i < snapshots.length ; i++ )
 457                      {
 458                          image = snapshots[ i ];
 459                          if ( !currentImage.equals( image, true ) )
 460                          {
 461                              image.index = i;
 462                              return image;
 463                          }
 464                      }
 465                  }
 466              }
 467  
 468              return null;
 469          },
 470  
 471          /**

 472           * Check the current redo state.

 473           * @return {Boolean} Whether the document has previous state to

 474           *        retrieve.

 475           */
 476          redoable : function()
 477          {
 478              return this.enabled && this.hasRedo;
 479          },
 480  
 481          /**

 482           * Check the current undo state.

 483           * @return {Boolean} Whether the document has future state to restore.

 484           */
 485          undoable : function()
 486          {
 487              return this.enabled && this.hasUndo;
 488          },
 489  
 490          /**

 491           * Perform undo on current index.

 492           */
 493          undo : function()
 494          {
 495              if ( this.undoable() )
 496              {
 497                  this.save( true );
 498  
 499                  var image = this.getNextImage( true );
 500                  if ( image )
 501                      return this.restoreImage( image ), true;
 502              }
 503  
 504              return false;
 505          },
 506  
 507          /**

 508           * Perform redo on current index.

 509           */
 510          redo : function()
 511          {
 512              if ( this.redoable() )
 513              {
 514                  // Try to save. If no changes have been made, the redo stack

 515                  // will not change, so it will still be redoable.

 516                  this.save( true );
 517  
 518                  // If instead we had changes, we can't redo anymore.

 519                  if ( this.redoable() )
 520                  {
 521                      var image = this.getNextImage( false );
 522                      if ( image )
 523                          return this.restoreImage( image ), true;
 524                  }
 525              }
 526  
 527              return false;
 528          },
 529  
 530          /**

 531           * Update the last snapshot of the undo stack with the current editor content.

 532           */
 533          update : function()
 534          {
 535              this.snapshots.splice( this.index, 1, ( this.currentImage = new Image( this.editor ) ) );
 536          }
 537      };
 538  })();
 539  
 540  /**

 541   * The number of undo steps to be saved. The higher this setting value the more

 542   * memory is used for it.

 543   * @type Number

 544   * @default 20

 545   * @example

 546   * config.undoStackSize = 50;

 547   */
 548  CKEDITOR.config.undoStackSize = 20;
 549  
 550  /**

 551   * Fired when the editor is about to save an undo snapshot. This event can be

 552   * fired by plugins and customizations to make the editor saving undo snapshots.

 553   * @name CKEDITOR.editor#saveSnapshot

 554   * @event

 555   */


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