Ticket #2763: 2763.diff
File 2763.diff, 17.0 KB (added by , 14 years ago) |
---|
-
_source/plugins/undoredo/plugin.js
### Eclipse Workspace Patch 1.0 #P ckeditor3.0
1 /** 2 * @fileOverview Undo/Redo system for saving shapshot for document modification 3 * and other recordable changes. 4 * @see #2763, #2, #915 5 * @description 6 * <p> 7 * <span style="border-collapse: separate; color: rgb(0, 0, 0); 8 * font-family: Verdana; font-size: 13px; font-style: normal; font-variant: 9 * normal; font-weight: normal; letter-spacing: normal; line-height: 10 * normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: 11 * normal; widows: 2; word-spacing: 0px;" class="Apple-style-span"> 12 * <p> 13 * Undo/Redo system need to be ported from v2, the features could be summed 14 * up as below: 15 * </p> 16 * <ul> 17 * <li>Undo system restore/retrieve document status from both selection 18 * and content</li> 19 * <li>All commands that modify the document could support undo/redo 20 * feature,but each command has chance to optionaly declare whether would 21 * hooked with undo systemwhen they're registed.</li> 22 * </ul> 23 * <blockquote><blockquote> 24 * <p> 25 * <strong>Note</strong>: Since from v3 all keystroke will pre-bind to 26 * command, so keystrokes also hooked with undo system through command 27 * interface. 28 * </p> 29 * </blockquote></blockquote> 30 * <ul> 31 * <li>Undo feature itself performed as a command, A button plugin is 32 * required for interfacing this command to end user and keystroke pair of 33 * 'Ctrl-Z' and 'Ctrl-Y' too.</li> 34 * <li>Support empty/reset all the undo snapshots for clean up, and A 35 * button plugin is required for interfacing this<span 36 * class="Apple-converted-space"> </span><i>reset</i></li> 37 * <li>snapshot for undo system could be recorded in two forms: 38 * <ol> 39 * <li>One snapshot for each record action, this apply for almost every 40 * functional commands, but a few special keystroke commands that also 41 * being recorded in this way including: 42 * <ol style="list-style-type: lower-alpha;" class="loweralpha"> 43 * <li>'Enter'</li> 44 * <li>'ShiftEnter'</li> 45 * <li>'CtrlBackspace'</li> 46 * <li>'Delete'</li> 47 * <li>'Tab'</li> 48 * <li>'Ctrl-X'</li> 49 * <li>'Ctrl-V'</li> 50 * </ol> 51 * </li> 52 * <li>One snapshot for a serials of record actions, this apply for most 53 * keystroke commands.</li> 54 * </ol> 55 * </li> 56 * </ul> 57 * </span> 58 * </p> 59 */ 60 CKEDITOR.plugins.add('undoredo', { 61 62 requires : ['selection', 'wysiwygarea'], 63 beforeInit : function(editor) { 64 65 var urm = new UndoRedoManager(editor); 66 function recordCommand(evt) { 67 var cp /* {FCKEDITOR.command.Pair} */= evt.data; 68 if (cp.command.supportUndoRedo !== false 69 // ingore mode change command 70 && cp.name !== 'source' && cp.name !== 'wysiwyg' 71 && urm.enabled) { 72 urm.save(evt.name === 'beforeCommandExec' 73 ? true 74 : false); 75 } 76 } 77 78 editor.on('beforeCommandExec', recordCommand); 79 editor.on('afterCommandExec', recordCommand); 80 81 // sensitive to mode change, only appliable for 'wysiwyg' mode 82 editor.on('mode', function(currentMode) { 83 urm.enabled = currentMode.data === 'wysiwyg'; 84 }); 85 86 editor.addCommand('undo', 87 /** 88 * Rollback to last modification of this document 89 * 90 * @type CKEDITOR.command status of document 91 */ 92 { 93 exec : function() { 94 if (urm.undo()) { 95 this.fire('AfterUndo'); 96 // TODO: fire 'selectionchange' 97 } 98 }, 99 supportUndoRedo : false 100 }); 101 102 editor.addCommand('redo', 103 /** 104 * Retrieve to next modification of this document 105 * 106 * @type CKEDITOR.command status of document 107 */ 108 { 109 exec : function() { 110 if (urm.redo()) { 111 this.fire('AfterRedo'); 112 // TODO: fire 'selectionchange' 113 } 114 }, 115 supportUndoRedo : false 116 }); 117 } 118 }); 119 120 /** 121 * @constructor Main logic for Redo/Undo feature 122 * @related FCKUndo in v2 123 */ 124 function UndoRedoManager(editor) { 125 126 /** 127 * @field Whether undo system is usable decided by envoriment 128 */ 129 this.enabled = false; 130 131 var UNDO_NUM_LIMIT = 20; 132 133 /** 134 * Stack for all the undo and redo snapshots, they're always created/removed 135 * in consistency. 136 * 137 * @type {Array<DocumentImage>} 138 * 139 */ 140 var undoSnaps = [], redoSnaps = []; 141 142 /** 143 * Current snapshot history index 144 */ 145 var index = 0; // First capture start from 1 146 147 /** 148 * @private Get the current blockediting mode of this editor 149 * @param editor 150 * @param mode 151 * @return 152 */ 153 function getMode(mode) { 154 return editor._.modes && editor._.modes[mode || editor.mode]; 155 }; 156 157 /** 158 * @constructor DocumentImage - A snapshot image which represent the current 159 * document status. 160 */ 161 function DocumentImage() { 162 163 var bms, ranges, cWithBm, c; 164 165 c = getMode('wysiwyg').getSnapshotData(); 166 bms = editor.document.getSelection().createBookmarks(true); 167 ranges = editor.document.getSelection().getRanges(); // Get ranges 168 // from cache 169 cWithBm = getMode('wysiwyg').getSnapshotData() // Get content along 170 // with bookmark 171 editor.document.getSelection().selectBookmarks(bms); // restore 172 // selection by 173 // the bookmark 174 // and btw we 175 // also 176 // destroy the nodes in document 177 return { 178 'content' : /** {String} original document string */ 179 c, 180 'dirtyContent' : 181 /** 182 * {String} document content string with bookmarks 183 */ 184 cWithBm, 185 'bookmark'/** {Array<CKEDITOR.dom.range.Bookmark>} */ 186 : bms, 187 equals : 188 /** 189 * Compare if two document snapshot are the same, this is only 190 * content comparison with bookmarks striped. 191 * 192 * @img {DocumentImage} The other image to compare with 193 */ 194 function(img) { 195 return this.content == img.content; 196 } 197 } 198 }; 199 200 /** 201 * @method Save a snapshot of document image for later retrieve, content 202 * being saved as raw html string, while selection saved as a 203 * lightweight bookmark. 204 */ 205 this.save = function(isFront) { 206 var sns = isFront ? undoSnaps : redoSnaps; 207 208 if (index == UNDO_NUM_LIMIT) { 209 sns.unshift(); // Die out earlier ones. 210 } 211 212 sns.splice(index, sns.length - index); // Drop older snaps 213 214 var img = new DocumentImage(); 215 if (sns.length && img.equals(sns[sns.length - 1])) // duplicate 216 // examination 217 return; 218 219 sns.push(img); 220 221 if (!isFront) 222 index++; // increase when command has been fully recorded. 223 } 224 225 function restoreImage(index, isUndo) { 226 var img; 227 img = isUndo ? undoSnaps[index] : redoSnaps[index]; 228 if (img.content) { 229 getMode('wysiwyg').loadSnapshotData(img.dirtyContent); 230 if (img.bookmark) { 231 editor.document.getSelection().selectBookmarks(img.bookmark); 232 } 233 } 234 } 235 236 /** 237 * Check the current state of redo 238 * 239 * @return {Boolean} Whether the document has previous state to retrieve 240 */ 241 this.redoAble = function() { 242 return index < redoSnaps.length; 243 } 244 245 /** 246 * Check the current state of undo 247 * 248 * @return {Boolean} Whether the document has future state to restore 249 */ 250 this.undoAble = function() { 251 return index > 0 252 } 253 /** 254 * Perform undo on current index. 255 * 256 * @method 257 */ 258 this.undo = function() { 259 if (this.undoAble()) { 260 restoreImage(--index, true); // retrieve previous one from undo 261 // history 262 } else 263 return false; 264 } 265 266 /** 267 * Perform redo on current index. 268 * 269 * @method 270 */ 271 this.redo = function() { 272 if (this.redoAble()) { 273 restoreImage(index++, false); // retrieve next one from redo 274 // history 275 } else 276 return false; 277 } 278 279 } -
_source/plugins/editingblock/plugin.js
190 190 modeEditor.load( holderElement, data || this.getData() ); 191 191 192 192 this.mode = mode; 193 this.fire( 'mode' );193 this.fire( 'mode' , mode); 194 194 }; 195 195 196 196 /** -
_source/core/dom/range.js
424 424 return docFrag; 425 425 }, 426 426 427 // This is an "intrusive" way to create a bookmark. It includes <span> tags 428 // in the range boundaries. The advantage of it is that it is possible to 429 // handle DOM mutations when moving back to the bookmark. 430 // Attention: the inclusion of nodes in the DOM is a design choice and 431 // should not be changed as there are other points in the code that may be 432 // using those nodes to perform operations. See GetBookmarkNode. 433 createBookmark : function() 427 /** 428 * This is an "intrusive" way to create a bookmark. It includes <span> 429 * tags in the range boundaries. The advantage of it is that it is 430 * possible to handle DOM mutations when moving back to the bookmark. 431 * Attention: the inclusion of nodes in the DOM is a design choice. 432 * 433 * @param serializeable {Boolean} If set to true will record the bookmark with startNode/endNode IDs for serializing purpose. 434 */ 435 createBookmark : function(serializeable) 434 436 { 435 437 var startNode, endNode; 436 438 var clone; … … 451 453 452 454 clone = this.clone(); 453 455 clone.collapse(); 454 clone.insertNode( endNode ); 456 clone.insertNode( endNode ); //CONFUSE: This insert will not influence current selection. 455 457 } 456 458 457 459 clone = this.clone(); 458 460 clone.collapse( true ); 459 clone.insertNode( startNode ); 461 clone.insertNode( startNode ); //CONFUSE: This insert will fade current selection out. 460 462 461 463 // Update the range position. 462 464 if ( endNode ) … … 466 468 } 467 469 else 468 470 this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END ); 469 471 472 if(serializeable) 473 { 474 startNode.setAttribute('id',(new Date()).valueOf() + Math.floor(Math.random()*1000) + 'S'); 475 if(endNode) 476 endNode.setAttribute('id',(new Date()).valueOf() + Math.floor(Math.random()*1000) + 'E'); 477 } 470 478 return { 471 startNode : s tartNode,472 endNode : endNode 479 startNode : serializeable? startNode.getAttribute('id') : startNode, 480 endNode : endNode? ( serializeable? endNode.getAttribute('id') : endNode) : null 473 481 }; 474 482 }, 475 483 484 /** 485 * Move the range boundaries to where the bookmarks indicated. 486 * @see CKEDITOR.dom.range::createBookmark 487 * @param bookmark {Object} 488 */ 476 489 moveToBookmark : function( bookmark ) 477 490 { 491 var st = bookmark.startNode, ed = bookmark.endNode; 492 478 493 // Set the range start at the bookmark start node position. 479 this.setStartBefore( bookmark.startNode ); 494 this.setStartBefore(typeof st === 'string'? (st = this.document.getById(st)): st); 495 480 496 481 497 // Remove it, because it may interfere in the setEndBefore call. 482 bookmark.startNode.remove();498 st.remove(); 483 499 484 500 // Set the range end at the bookmark end node position, or simply 485 501 // collapse it if it is not available. 486 var endNode = bookmark.endNode; 487 if ( endNode ) 502 if ( ed ) 488 503 { 489 this.setEndBefore( endNode);490 e ndNode.remove();504 this.setEndBefore( typeof ed === 'string'? (ed = this.document.getById(ed) ): ed ); 505 ed.remove(); 491 506 } 492 507 else 493 508 this.collapse( true ); -
_source/core/editor.js
361 361 execCommand : function( commandName, data ) 362 362 { 363 363 var command = this.getCommand( commandName ); 364 var cp = 365 /** 366 * Name-command pair to trace commands behavious 367 * @type FCKEDITOR.command.Pair 368 */ 369 {name: commandName, command:command}; 364 370 if ( command && command.state != CKEDITOR.TRISTATE_DISABLED ) 365 return command.exec( this, data ); 371 { 372 this.fire('beforeCommandExec', cp); 373 command.exec( this, data ); 374 this.fire('afterCommandExec', cp); 375 } 366 376 367 377 // throw 'Unknown command name "' + commandName + '"'; 368 378 return false; -
_source/plugins/selection/plugin.js
630 630 sel.addRange( nativeRange ); 631 631 } 632 632 }, 633 634 createBookmarks : function() 633 /** 634 * This method is a delegate to ceate bookmark for every ranges in this selection. 635 * @see CKEDITOR.dom.range::createBookmark 636 */ 637 createBookmarks : function(serializable) 635 638 { 636 639 var retval = [], 637 640 ranges = this.getRanges(); 638 641 for ( var i = 0 ; i < ranges.length ; i++ ) 639 retval.push( ranges[i].createBookmark( ) );642 retval.push( ranges[i].createBookmark(serializable) ); 640 643 return retval; 641 644 }, 642 645 -
_source/core/commanddefinition.js
49 49 * } 50 50 * }); 51 51 */ 52 53 /** 54 * Whether the command need to be hooked into the redo/undo system. 55 * @name CKEDITOR.commandDefinition.supportUndoRedo 56 * @type {Boolean} If not defined or 'true' both hook into undo system, set it to 'false' explicitly keep it out. 57 * @field 58 * @example 59 * editorInstance.addCommand( 'sample', 60 * { 61 * CKEDITOR.commandDefinition.supportUndoRedo : false //declare this command does not support undo/redo 62 * }); 63 */ 64 -
_source/tests/core/dom/range.html
1507 1507 assert.areSame( 3, range.endOffset, 'range.endOffset' ); 1508 1508 assert.isFalse( range.collapsed, 'range.collapsed' ); 1509 1509 }, 1510 1511 //Test serializable bookmark create and apply 1512 test_bookmark_single_1: function () { 1510 1513 1514 var range = new CKEDITOR.dom.range( doc ); 1515 range.setStartBefore( doc.getById('_Span')); 1516 range.setEndAfter(doc.getById('_P')); 1517 bm = range.createBookmark(true); 1518 range.setStartBefore( doc.getById('_P2')); 1519 range.setEndAfter(doc.getById('_P2')); 1520 range.moveToBookmark(bm); 1521 1522 assert.areSame( document.getElementById( '_P' ), range.startContainer.$ ,'range.startContainer'); 1523 assert.areSame( 1, range.startOffset, 'range.startOffset'); 1524 assert.areSame( document.getElementById( 'playground2' ), range.endContainer.$, 'range.endContainer'); 1525 assert.areSame( 4, range.endOffset, 'range.endOffset' ); 1526 assert.isNull(document.getElementById(bm.startNode), 'bookmark.startNode'); 1527 assert.isNull(document.getElementById(bm.endNode), 'bookmark.endNode'); 1528 1529 1530 1531 }, 1532 1511 1533 ///////////// 1512 1534 1513 1535 setUp : function() -
_source/core/config.js
1 /*1 /* 2 2 * CKEditor - The text editor for Internet - http://ckeditor.com 3 3 * Copyright (C) 2003-2008 Frederico Caldeira Knabben 4 4 * … … 161 161 * @example 162 162 * config.plugins = 'elementspath,toolbar,wysiwygarea'; 163 163 */ 164 plugins : 'basicstyles,button,dialog,elementspath,horizontalrule,htmldataprocessor,keystrokes,removeformat,smiley,link,sourcearea,tab,toolbar,wysiwygarea,forms,image,find,table,specialchar,flash,print,pagebreak, newpage',164 plugins : 'basicstyles,button,dialog,elementspath,horizontalrule,htmldataprocessor,keystrokes,removeformat,smiley,link,sourcearea,tab,toolbar,wysiwygarea,forms,image,find,table,specialchar,flash,print,pagebreak,undoredo,newpage', 165 165 166 166 /** 167 167 * The theme to be used to build the UI. … … 350 350 descriptions : [ ':)', ':(', ';)', ':D', ':/', ':P', 351 351 '', '', '', '', '', '', 352 352 '', ';(', '', '', '', '', 353 ':kiss', '' ,],353 ':kiss', '' ], 354 354 355 355 //TODO: update path 356 356 path : CKEDITOR.basePath + '_source/plugins/smiley/images/',