Nation Notes module contributed by Z&H Healthcare.
[openemr.git] / library / custom_template / ckeditor / _source / plugins / undo / plugin.js
blob60d0adbf0be56f46300a07d236d50e816594c0d7
1 /*
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
4 */
6 /**
7  * @fileOverview Undo/Redo system for saving shapshot for document modification
8  *              and other recordable changes.
9  */
11 (function()
13         CKEDITOR.plugins.add( 'undo',
14         {
15                 requires : [ 'selection', 'wysiwygarea' ],
17                 init : function( editor )
18                 {
19                         var undoManager = new UndoManager( editor );
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                                 });
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                                 });
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                         };
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                         }
62                         // We'll save snapshots before and after executing a command.
63                         editor.on( 'beforeCommandExec', recordCommand );
64                         editor.on( 'afterCommandExec', recordCommand );
66                         // Save snapshots before doing custom changes.
67                         editor.on( 'saveSnapshot', function()
68                                 {
69                                         undoManager.save();
70                                 });
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                                 });
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                                 });
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                                 });
97                         editor.ui.addButton( 'Undo',
98                                 {
99                                         label : editor.lang.undo,
100                                         command : 'undo'
101                                 });
103                         editor.ui.addButton( 'Redo',
104                                 {
105                                         label : editor.lang.redo,
106                                         command : 'redo'
107                                 });
109                         editor.resetUndo = function()
110                         {
111                                 // Reset the undo stack.
112                                 undoManager.reset();
114                                 // Create the first image.
115                                 editor.fire( 'saveSnapshot' );
116                         };
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         });
141         CKEDITOR.plugins.undo = {};
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();
154                 // In IE, we need to remove the expando attributes.
155                 CKEDITOR.env.ie && contents && ( contents = contents.replace( /\s+data-cke-expando=".*?"/g, '' ) );
157                 this.contents   = contents;
158                 this.bookmarks  = selection && selection.createBookmarks2( true );
159         };
161         // Attributes that browser may changing them when setting via innerHTML.
162         var protectedAttrs = /\b(?:href|src|name)="[^"]*?"/gi;
164         Image.prototype =
165         {
166                 equals : function( otherImage, contentOnly )
167                 {
169                         var thisContents = this.contents,
170                                 otherContents = otherImage.contents;
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                         }
179                         if ( thisContents != otherContents )
180                                 return false;
182                         if ( contentOnly )
183                                 return true;
185                         var bookmarksA = this.bookmarks,
186                                 bookmarksB = otherImage.bookmarks;
188                         if ( bookmarksA || bookmarksB )
189                         {
190                                 if ( !bookmarksA || !bookmarksB || bookmarksA.length != bookmarksB.length )
191                                         return false;
193                                 for ( var i = 0 ; i < bookmarksA.length ; i++ )
194                                 {
195                                         var bookmarkA = bookmarksA[ i ],
196                                                 bookmarkB = bookmarksB[ i ];
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                         }
209                         return true;
210                 }
211         };
213         /**
214          * @constructor Main logic for Redo/Undo feature.
215          */
216         function UndoManager( editor )
217         {
218                 this.editor = editor;
220                 // Reset the undo stack.
221                 this.reset();
222         }
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
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,
246                                 // Keystrokes which just introduce new contents.
247                                 isContent = ( !isEditingKey && !isReset ),
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 ) );
258                         if ( startedTyping || modifierSnapshot )
259                         {
260                                 var beforeTypeImage = new Image( this.editor );
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();
268                                                 // In IE, we need to remove the expando attributes.
269                                                 if ( CKEDITOR.env.ie )
270                                                         currentSnapshot = currentSnapshot.replace( /\s+data-cke-expando=".*?"/g, '' );
272                                                 if ( beforeTypeImage.contents != currentSnapshot )
273                                                 {
274                                                         // It's safe to now indicate typing state.
275                                                         this.typing = true;
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 );
283                                                         this.hasUndo = true;
284                                                         this.hasRedo = false;
286                                                         this.typesCount = 1;
287                                                         this.modifiersCount = 1;
289                                                         this.onChange();
290                                                 }
291                                         },
292                                         0, this
293                                 );
294                         }
296                         this.lastKeystroke = keystroke;
298                         // Create undo snap after typed too much (over 25 times).
299                         if ( isEditingKey )
300                         {
301                                 this.typesCount = 0;
302                                 this.modifiersCount++;
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++;
315                                 if ( this.typesCount > 25 )
316                                 {
317                                         this.save( false, null, false );
318                                         this.typesCount = 1;
319                                 }
320                         }
322                 },
324                 reset : function()      // Reset the undo stack.
325                 {
326                         /**
327                          * Remember last pressed key.
328                          */
329                         this.lastKeystroke = 0;
331                         /**
332                          * Stack for all the undo and redo snapshots, they're always created/removed
333                          * in consistency.
334                          */
335                         this.snapshots = [];
337                         /**
338                          * Current snapshot history index.
339                          */
340                         this.index = -1;
342                         this.limit = this.editor.config.undoStackSize || 20;
344                         this.currentImage = null;
346                         this.hasUndo = false;
347                         this.hasRedo = false;
349                         this.resetType();
350                 },
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                 },
372                 /**
373                  * Save a snapshot of document image for later retrieve.
374                  */
375                 save : function( onContentOnly, image, autoFireChange )
376                 {
377                         var snapshots = this.snapshots;
379                         // Get a content image.
380                         if ( !image )
381                                 image = new Image( this.editor );
383                         // Do nothing if it was not possible to retrieve an image.
384                         if ( image.contents === false )
385                                 return false;
387                         // Check if this is a duplicate. In such case, do nothing.
388                         if ( this.currentImage && image.equals( this.currentImage, onContentOnly ) )
389                                 return false;
391                         // Drop future snapshots.
392                         snapshots.splice( this.index + 1, snapshots.length - this.index - 1 );
394                         // If we have reached the limit, remove the oldest one.
395                         if ( snapshots.length == this.limit )
396                                 snapshots.shift();
398                         // Add the new image, updating the current index.
399                         this.index = snapshots.push( image ) - 1;
401                         this.currentImage = image;
403                         if ( autoFireChange !== false )
404                                 this.fireChange();
405                         return true;
406                 },
408                 restoreImage : function( image )
409                 {
410                         this.editor.loadSnapshot( image.contents );
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                         }
424                         this.index = image.index;
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                 },
433                 // Get the closest available image.
434                 getNextImage : function( isUndo )
435                 {
436                         var snapshots = this.snapshots,
437                                 currentImage = this.currentImage,
438                                 image, i;
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                         }
468                         return null;
469                 },
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                 },
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                 },
490                 /**
491                  * Perform undo on current index.
492                  */
493                 undo : function()
494                 {
495                         if ( this.undoable() )
496                         {
497                                 this.save( true );
499                                 var image = this.getNextImage( true );
500                                 if ( image )
501                                         return this.restoreImage( image ), true;
502                         }
504                         return false;
505                 },
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 );
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                         }
527                         return false;
528                 },
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 })();
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  */
550  * Fired when the editor is about to save an undo snapshot. This event can be
551  * fired by plugins and customizations to make the editor saving undo snapshots.
552  * @name CKEDITOR.editor#saveSnapshot
553  * @event
554  */