source: trunk/admin/inc/ckeditor/_source/plugins/undo/plugin.js@ 345

Last change on this file since 345 was 239, checked in by luc, 9 years ago

Admin: correzione visulaizzazione immissione dati spoglio per Chrome e Safari - Aggiornamento dell'editor da FCKeditor a CKeditor , accessibili anche a Chrome e Safari.

  • Property svn:executable set to *
File size: 14.7 KB
Line 
1/*
2Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3For 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( evt )
68 {
69 undoManager.save( evt.data && evt.data.contentOnly );
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.readOnly ? false : 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 * Amend the top of undo stack (last undo image) with the current DOM changes.
120 * @name CKEDITOR.editor#updateUndo
121 * @example
122 * function()
123 * {
124 * editor.fire( 'saveSnapshot' );
125 * editor.document.body.append(...);
126 * // Make new changes following the last undo snapshot part of it.
127 * editor.fire( 'updateSnapshot' );
128 * ...
129 * }
130 */
131 editor.on( 'updateSnapshot', function()
132 {
133 if ( undoManager.currentImage )
134 undoManager.update();
135 });
136 }
137 });
138
139 CKEDITOR.plugins.undo = {};
140
141 /**
142 * Undo snapshot which represents the current document status.
143 * @name CKEDITOR.plugins.undo.Image
144 * @param editor The editor instance on which the image is created.
145 */
146 var Image = CKEDITOR.plugins.undo.Image = function( editor )
147 {
148 this.editor = editor;
149
150 editor.fire( 'beforeUndoImage' );
151
152 var contents = editor.getSnapshot(),
153 selection = contents && editor.getSelection();
154
155 // In IE, we need to remove the expando attributes.
156 CKEDITOR.env.ie && contents && ( contents = contents.replace( /\s+data-cke-expando=".*?"/g, '' ) );
157
158 this.contents = contents;
159 this.bookmarks = selection && selection.createBookmarks2( true );
160
161 editor.fire( 'afterUndoImage' );
162 };
163
164 // Attributes that browser may changing them when setting via innerHTML.
165 var protectedAttrs = /\b(?:href|src|name)="[^"]*?"/gi;
166
167 Image.prototype =
168 {
169 equals : function( otherImage, contentOnly )
170 {
171
172 var thisContents = this.contents,
173 otherContents = otherImage.contents;
174
175 // For IE6/7 : Comparing only the protected attribute values but not the original ones.(#4522)
176 if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )
177 {
178 thisContents = thisContents.replace( protectedAttrs, '' );
179 otherContents = otherContents.replace( protectedAttrs, '' );
180 }
181
182 if ( thisContents != otherContents )
183 return false;
184
185 if ( contentOnly )
186 return true;
187
188 var bookmarksA = this.bookmarks,
189 bookmarksB = otherImage.bookmarks;
190
191 if ( bookmarksA || bookmarksB )
192 {
193 if ( !bookmarksA || !bookmarksB || bookmarksA.length != bookmarksB.length )
194 return false;
195
196 for ( var i = 0 ; i < bookmarksA.length ; i++ )
197 {
198 var bookmarkA = bookmarksA[ i ],
199 bookmarkB = bookmarksB[ i ];
200
201 if (
202 bookmarkA.startOffset != bookmarkB.startOffset ||
203 bookmarkA.endOffset != bookmarkB.endOffset ||
204 !CKEDITOR.tools.arrayCompare( bookmarkA.start, bookmarkB.start ) ||
205 !CKEDITOR.tools.arrayCompare( bookmarkA.end, bookmarkB.end ) )
206 {
207 return false;
208 }
209 }
210 }
211
212 return true;
213 }
214 };
215
216 /**
217 * @constructor Main logic for Redo/Undo feature.
218 */
219 function UndoManager( editor )
220 {
221 this.editor = editor;
222
223 // Reset the undo stack.
224 this.reset();
225 }
226
227
228 var editingKeyCodes = { /*Backspace*/ 8:1, /*Delete*/ 46:1 },
229 modifierKeyCodes = { /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1 },
230 navigationKeyCodes = { 37:1, 38:1, 39:1, 40:1 }; // Arrows: L, T, R, B
231
232 UndoManager.prototype =
233 {
234 /**
235 * Process undo system regard keystrikes.
236 * @param {CKEDITOR.dom.event} event
237 */
238 type : function( event )
239 {
240 var keystroke = event && event.data.getKey(),
241 isModifierKey = keystroke in modifierKeyCodes,
242 isEditingKey = keystroke in editingKeyCodes,
243 wasEditingKey = this.lastKeystroke in editingKeyCodes,
244 sameAsLastEditingKey = isEditingKey && keystroke == this.lastKeystroke,
245 // Keystrokes which navigation through contents.
246 isReset = keystroke in navigationKeyCodes,
247 wasReset = this.lastKeystroke in navigationKeyCodes,
248
249 // Keystrokes which just introduce new contents.
250 isContent = ( !isEditingKey && !isReset ),
251
252 // Create undo snap for every different modifier key.
253 modifierSnapshot = ( isEditingKey && !sameAsLastEditingKey ),
254 // Create undo snap on the following cases:
255 // 1. Just start to type .
256 // 2. Typing some content after a modifier.
257 // 3. Typing some content after make a visible selection.
258 startedTyping = !( isModifierKey || this.typing )
259 || ( isContent && ( wasEditingKey || wasReset ) );
260
261 if ( startedTyping || modifierSnapshot )
262 {
263 var beforeTypeImage = new Image( this.editor );
264
265 // Use setTimeout, so we give the necessary time to the
266 // browser to insert the character into the DOM.
267 CKEDITOR.tools.setTimeout( function()
268 {
269 var currentSnapshot = this.editor.getSnapshot();
270
271 // In IE, we need to remove the expando attributes.
272 if ( CKEDITOR.env.ie )
273 currentSnapshot = currentSnapshot.replace( /\s+data-cke-expando=".*?"/g, '' );
274
275 if ( beforeTypeImage.contents != currentSnapshot )
276 {
277 // It's safe to now indicate typing state.
278 this.typing = true;
279
280 // This's a special save, with specified snapshot
281 // and without auto 'fireChange'.
282 if ( !this.save( false, beforeTypeImage, false ) )
283 // Drop future snapshots.
284 this.snapshots.splice( this.index + 1, this.snapshots.length - this.index - 1 );
285
286 this.hasUndo = true;
287 this.hasRedo = false;
288
289 this.typesCount = 1;
290 this.modifiersCount = 1;
291
292 this.onChange();
293 }
294 },
295 0, this
296 );
297 }
298
299 this.lastKeystroke = keystroke;
300
301 // Create undo snap after typed too much (over 25 times).
302 if ( isEditingKey )
303 {
304 this.typesCount = 0;
305 this.modifiersCount++;
306
307 if ( this.modifiersCount > 25 )
308 {
309 this.save( false, null, false );
310 this.modifiersCount = 1;
311 }
312 }
313 else if ( !isReset )
314 {
315 this.modifiersCount = 0;
316 this.typesCount++;
317
318 if ( this.typesCount > 25 )
319 {
320 this.save( false, null, false );
321 this.typesCount = 1;
322 }
323 }
324
325 },
326
327 reset : function() // Reset the undo stack.
328 {
329 /**
330 * Remember last pressed key.
331 */
332 this.lastKeystroke = 0;
333
334 /**
335 * Stack for all the undo and redo snapshots, they're always created/removed
336 * in consistency.
337 */
338 this.snapshots = [];
339
340 /**
341 * Current snapshot history index.
342 */
343 this.index = -1;
344
345 this.limit = this.editor.config.undoStackSize || 20;
346
347 this.currentImage = null;
348
349 this.hasUndo = false;
350 this.hasRedo = false;
351
352 this.resetType();
353 },
354
355 /**
356 * Reset all states about typing.
357 * @see UndoManager.type
358 */
359 resetType : function()
360 {
361 this.typing = false;
362 delete this.lastKeystroke;
363 this.typesCount = 0;
364 this.modifiersCount = 0;
365 },
366 fireChange : function()
367 {
368 this.hasUndo = !!this.getNextImage( true );
369 this.hasRedo = !!this.getNextImage( false );
370 // Reset typing
371 this.resetType();
372 this.onChange();
373 },
374
375 /**
376 * Save a snapshot of document image for later retrieve.
377 */
378 save : function( onContentOnly, image, autoFireChange )
379 {
380 var snapshots = this.snapshots;
381
382 // Get a content image.
383 if ( !image )
384 image = new Image( this.editor );
385
386 // Do nothing if it was not possible to retrieve an image.
387 if ( image.contents === false )
388 return false;
389
390 // Check if this is a duplicate. In such case, do nothing.
391 if ( this.currentImage && image.equals( this.currentImage, onContentOnly ) )
392 return false;
393
394 // Drop future snapshots.
395 snapshots.splice( this.index + 1, snapshots.length - this.index - 1 );
396
397 // If we have reached the limit, remove the oldest one.
398 if ( snapshots.length == this.limit )
399 snapshots.shift();
400
401 // Add the new image, updating the current index.
402 this.index = snapshots.push( image ) - 1;
403
404 this.currentImage = image;
405
406 if ( autoFireChange !== false )
407 this.fireChange();
408 return true;
409 },
410
411 restoreImage : function( image )
412 {
413 this.editor.loadSnapshot( image.contents );
414
415 if ( image.bookmarks )
416 this.editor.getSelection().selectBookmarks( image.bookmarks );
417 else if ( CKEDITOR.env.ie )
418 {
419 // IE BUG: If I don't set the selection to *somewhere* after setting
420 // document contents, then IE would create an empty paragraph at the bottom
421 // the next time the document is modified.
422 var $range = this.editor.document.getBody().$.createTextRange();
423 $range.collapse( true );
424 $range.select();
425 }
426
427 this.index = image.index;
428
429 // Update current image with the actual editor
430 // content, since actualy content may differ from
431 // the original snapshot due to dom change. (#4622)
432 this.update();
433 this.fireChange();
434 },
435
436 // Get the closest available image.
437 getNextImage : function( isUndo )
438 {
439 var snapshots = this.snapshots,
440 currentImage = this.currentImage,
441 image, i;
442
443 if ( currentImage )
444 {
445 if ( isUndo )
446 {
447 for ( i = this.index - 1 ; i >= 0 ; i-- )
448 {
449 image = snapshots[ i ];
450 if ( !currentImage.equals( image, true ) )
451 {
452 image.index = i;
453 return image;
454 }
455 }
456 }
457 else
458 {
459 for ( i = this.index + 1 ; i < snapshots.length ; i++ )
460 {
461 image = snapshots[ i ];
462 if ( !currentImage.equals( image, true ) )
463 {
464 image.index = i;
465 return image;
466 }
467 }
468 }
469 }
470
471 return null;
472 },
473
474 /**
475 * Check the current redo state.
476 * @return {Boolean} Whether the document has previous state to
477 * retrieve.
478 */
479 redoable : function()
480 {
481 return this.enabled && this.hasRedo;
482 },
483
484 /**
485 * Check the current undo state.
486 * @return {Boolean} Whether the document has future state to restore.
487 */
488 undoable : function()
489 {
490 return this.enabled && this.hasUndo;
491 },
492
493 /**
494 * Perform undo on current index.
495 */
496 undo : function()
497 {
498 if ( this.undoable() )
499 {
500 this.save( true );
501
502 var image = this.getNextImage( true );
503 if ( image )
504 return this.restoreImage( image ), true;
505 }
506
507 return false;
508 },
509
510 /**
511 * Perform redo on current index.
512 */
513 redo : function()
514 {
515 if ( this.redoable() )
516 {
517 // Try to save. If no changes have been made, the redo stack
518 // will not change, so it will still be redoable.
519 this.save( true );
520
521 // If instead we had changes, we can't redo anymore.
522 if ( this.redoable() )
523 {
524 var image = this.getNextImage( false );
525 if ( image )
526 return this.restoreImage( image ), true;
527 }
528 }
529
530 return false;
531 },
532
533 /**
534 * Update the last snapshot of the undo stack with the current editor content.
535 */
536 update : function()
537 {
538 this.snapshots.splice( this.index, 1, ( this.currentImage = new Image( this.editor ) ) );
539 }
540 };
541})();
542
543/**
544 * The number of undo steps to be saved. The higher this setting value the more
545 * memory is used for it.
546 * @name CKEDITOR.config.undoStackSize
547 * @type Number
548 * @default 20
549 * @example
550 * config.undoStackSize = 50;
551 */
552
553/**
554 * Fired when the editor is about to save an undo snapshot. This event can be
555 * fired by plugins and customizations to make the editor saving undo snapshots.
556 * @name CKEDITOR.editor#saveSnapshot
557 * @event
558 */
559
560/**
561 * Fired before an undo image is to be taken. An undo image represents the
562 * editor state at some point. It's saved into an undo store, so the editor is
563 * able to recover the editor state on undo and redo operations.
564 * @name CKEDITOR.editor#beforeUndoImage
565 * @since 3.5.3
566 * @see CKEDITOR.editor#afterUndoImage
567 * @event
568 */
569
570/**
571 * Fired after an undo image is taken. An undo image represents the
572 * editor state at some point. It's saved into an undo store, so the editor is
573 * able to recover the editor state on undo and redo operations.
574 * @name CKEDITOR.editor#afterUndoImage
575 * @since 3.5.3
576 * @see CKEDITOR.editor#beforeUndoImage
577 * @event
578 */
Note: See TracBrowser for help on using the repository browser.