| [ 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 /** 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 */
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 |