JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.3
[ckeditor.git] / _source / plugins / undo / plugin.js
index e013a9d..f4ad1a3 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
-Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.\r
 For licensing, see LICENSE.html or http://ckeditor.com/license\r
 */\r
 \r
@@ -64,9 +64,9 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        editor.on( 'afterCommandExec', recordCommand );\r
 \r
                        // Save snapshots before doing custom changes.\r
-                       editor.on( 'saveSnapshot', function()\r
+                       editor.on( 'saveSnapshot', function( evt )\r
                                {\r
-                                       undoManager.save();\r
+                                       undoManager.save( evt.data && evt.data.contentOnly );\r
                                });\r
 \r
                        // Registering keydown on every document recreation.(#3844)\r
@@ -90,7 +90,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        // Make the undo manager available only in wysiwyg mode.\r
                        editor.on( 'mode', function()\r
                                {\r
-                                       undoManager.enabled = editor.mode == 'wysiwyg';\r
+                                       undoManager.enabled = editor.readOnly ? false : editor.mode == 'wysiwyg';\r
                                        undoManager.onChange();\r
                                });\r
 \r
@@ -114,27 +114,72 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                                // Create the first image.\r
                                editor.fire( 'saveSnapshot' );\r
                        };\r
+\r
+                       /**\r
+                        * Amend the top of undo stack (last undo image) with the current DOM changes.\r
+                        * @name CKEDITOR.editor#updateUndo\r
+                        * @example\r
+                        * function()\r
+                        * {\r
+                        *  editor.fire( 'saveSnapshot' );\r
+                        *      editor.document.body.append(...);\r
+                        *  // Make new changes following the last undo snapshot part of it.\r
+                        *      editor.fire( 'updateSnapshot' );\r
+                        * ...\r
+                        * }\r
+                        */\r
+                       editor.on( 'updateSnapshot', function()\r
+                       {\r
+                               if ( undoManager.currentImage )\r
+                                       undoManager.update();\r
+                       });\r
                }\r
        });\r
 \r
-       // Gets a snapshot image which represent the current document status.\r
-       function Image( editor )\r
+       CKEDITOR.plugins.undo = {};\r
+\r
+       /**\r
+        * Undo snapshot which represents the current document status.\r
+        * @name CKEDITOR.plugins.undo.Image\r
+        * @param editor The editor instance on which the image is created.\r
+        */\r
+       var Image = CKEDITOR.plugins.undo.Image = function( editor )\r
        {\r
-               var selection = editor.getSelection();\r
+               this.editor = editor;\r
 \r
-               this.contents   = editor.getSnapshot();\r
-               this.bookmarks  = selection && selection.createBookmarks2( true );\r
+               editor.fire( 'beforeUndoImage' );\r
+\r
+               var contents = editor.getSnapshot(),\r
+                       selection       = contents && editor.getSelection();\r
 \r
                // In IE, we need to remove the expando attributes.\r
-               if ( CKEDITOR.env.ie )\r
-                       this.contents = this.contents.replace( /\s+_cke_expando=".*?"/g, '' );\r
-       }\r
+               CKEDITOR.env.ie && contents && ( contents = contents.replace( /\s+data-cke-expando=".*?"/g, '' ) );\r
+\r
+               this.contents   = contents;\r
+               this.bookmarks  = selection && selection.createBookmarks2( true );\r
+\r
+               editor.fire( 'afterUndoImage' );\r
+       };\r
+\r
+       // Attributes that browser may changing them when setting via innerHTML.\r
+       var protectedAttrs = /\b(?:href|src|name)="[^"]*?"/gi;\r
 \r
        Image.prototype =\r
        {\r
                equals : function( otherImage, contentOnly )\r
                {\r
-                       if ( this.contents != otherImage.contents )\r
+\r
+                       var thisContents = this.contents,\r
+                               otherContents = otherImage.contents;\r
+\r
+                       // For IE6/7 : Comparing only the protected attribute values but not the original ones.(#4522)\r
+                       if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) )\r
+                       {\r
+                               thisContents = thisContents.replace( protectedAttrs, '' );\r
+                               otherContents = otherContents.replace( protectedAttrs, '' );\r
+                       }\r
+\r
+                       if ( thisContents != otherContents )\r
                                return false;\r
 \r
                        if ( contentOnly )\r
@@ -179,6 +224,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                this.reset();\r
        }\r
 \r
+\r
+       var editingKeyCodes = { /*Backspace*/ 8:1, /*Delete*/ 46:1 },\r
+               modifierKeyCodes = { /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1 },\r
+               navigationKeyCodes = { 37:1, 38:1, 39:1, 40:1 };  // Arrows: L, T, R, B\r
+\r
        UndoManager.prototype =\r
        {\r
                /**\r
@@ -187,36 +237,31 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                 */\r
                type : function( event )\r
                {\r
-                       var keystroke = event && event.data.getKeystroke(),\r
-\r
-                               // Backspace, Delete\r
-                               modifierCodes = { 8:1, 46:1 },\r
-                               // Keystrokes which will modify the contents.\r
-                               isModifier = keystroke in modifierCodes,\r
-                               wasModifier = this.lastKeystroke in modifierCodes,\r
-                               lastWasSameModifier = isModifier && keystroke == this.lastKeystroke,\r
-\r
-                               // Arrows: L, T, R, B\r
-                               resetTypingCodes = { 37:1, 38:1, 39:1, 40:1 },\r
+                       var keystroke = event && event.data.getKey(),\r
+                               isModifierKey = keystroke in modifierKeyCodes,\r
+                               isEditingKey = keystroke in editingKeyCodes,\r
+                               wasEditingKey = this.lastKeystroke in editingKeyCodes,\r
+                               sameAsLastEditingKey = isEditingKey && keystroke == this.lastKeystroke,\r
                                // Keystrokes which navigation through contents.\r
-                               isReset = keystroke in resetTypingCodes,\r
-                               wasReset = this.lastKeystroke in resetTypingCodes,\r
+                               isReset = keystroke in navigationKeyCodes,\r
+                               wasReset = this.lastKeystroke in navigationKeyCodes,\r
 \r
                                // Keystrokes which just introduce new contents.\r
-                               isContent = ( !isModifier && !isReset ),\r
+                               isContent = ( !isEditingKey && !isReset ),\r
 \r
                                // Create undo snap for every different modifier key.\r
-                               modifierSnapshot = ( isModifier && !lastWasSameModifier ),\r
+                               modifierSnapshot = ( isEditingKey && !sameAsLastEditingKey ),\r
                                // Create undo snap on the following cases:\r
-                               // 1. Just start to type.\r
+                               // 1. Just start to type .\r
                                // 2. Typing some content after a modifier.\r
                                // 3. Typing some content after make a visible selection.\r
-                               startedTyping = !this.typing\r
-                                       || ( isContent && ( wasModifier || wasReset ) );\r
+                               startedTyping = !( isModifierKey || this.typing )\r
+                                       || ( isContent && ( wasEditingKey || wasReset ) );\r
 \r
                        if ( startedTyping || modifierSnapshot )\r
                        {\r
-                               var beforeTypeImage = new Image( this.editor );\r
+                               var beforeTypeImage = new Image( this.editor ),\r
+                                       beforeTypeCount = this.snapshots.length;\r
 \r
                                // Use setTimeout, so we give the necessary time to the\r
                                // browser to insert the character into the DOM.\r
@@ -226,10 +271,16 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 \r
                                                // In IE, we need to remove the expando attributes.\r
                                                if ( CKEDITOR.env.ie )\r
-                                                       currentSnapshot = currentSnapshot.replace( /\s+_cke_expando=".*?"/g, '' );\r
+                                                       currentSnapshot = currentSnapshot.replace( /\s+data-cke-expando=".*?"/g, '' );\r
 \r
-                                               if ( beforeTypeImage.contents != currentSnapshot )\r
+                                               // If changes have taken place, while not been captured yet (#8459),\r
+                                               // compensate the snapshot.\r
+                                               if ( beforeTypeImage.contents != currentSnapshot &&\r
+                                                        beforeTypeCount == this.snapshots.length )\r
                                                {\r
+                                                       // It's safe to now indicate typing state.\r
+                                                       this.typing = true;\r
+\r
                                                        // This's a special save, with specified snapshot\r
                                                        // and without auto 'fireChange'.\r
                                                        if ( !this.save( false, beforeTypeImage, false ) )\r
@@ -250,15 +301,16 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        }\r
 \r
                        this.lastKeystroke = keystroke;\r
+\r
                        // Create undo snap after typed too much (over 25 times).\r
-                       if ( isModifier )\r
+                       if ( isEditingKey )\r
                        {\r
                                this.typesCount = 0;\r
                                this.modifiersCount++;\r
 \r
                                if ( this.modifiersCount > 25 )\r
                                {\r
-                                       this.save();\r
+                                       this.save( false, null, false );\r
                                        this.modifiersCount = 1;\r
                                }\r
                        }\r
@@ -269,12 +321,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 \r
                                if ( this.typesCount > 25 )\r
                                {\r
-                                       this.save();\r
+                                       this.save( false, null, false );\r
                                        this.typesCount = 1;\r
                                }\r
                        }\r
 \r
-                       this.typing = true;\r
                },\r
 \r
                reset : function()      // Reset the undo stack.\r
@@ -295,7 +346,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                         */\r
                        this.index = -1;\r
 \r
-                       this.limit = this.editor.config.undoStackSize;\r
+                       this.limit = this.editor.config.undoStackSize || 20;\r
 \r
                        this.currentImage = null;\r
 \r
@@ -336,6 +387,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        if ( !image )\r
                                image = new Image( this.editor );\r
 \r
+                       // Do nothing if it was not possible to retrieve an image.\r
+                       if ( image.contents === false )\r
+                               return false;\r
+\r
                        // Check if this is a duplicate. In such case, do nothing.\r
                        if ( this.currentImage && image.equals( this.currentImage, onContentOnly ) )\r
                                return false;\r
@@ -359,10 +414,21 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 \r
                restoreImage : function( image )\r
                {\r
+                       // Bring editor focused to restore selection.\r
+                       var editor = this.editor,\r
+                               sel;\r
+\r
+                       if ( image.bookmarks )\r
+                       {\r
+                               editor.focus();\r
+                               // Retrieve the selection beforehand. (#8324)\r
+                               sel = editor.getSelection();\r
+                       }\r
+\r
                        this.editor.loadSnapshot( image.contents );\r
 \r
                        if ( image.bookmarks )\r
-                               this.editor.getSelection().selectBookmarks( image.bookmarks );\r
+                               sel.selectBookmarks( image.bookmarks );\r
                        else if ( CKEDITOR.env.ie )\r
                        {\r
                                // IE BUG: If I don't set the selection to *somewhere* after setting\r
@@ -375,8 +441,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 \r
                        this.index = image.index;\r
 \r
-                       this.currentImage = image;\r
-\r
+                       // Update current image with the actual editor\r
+                       // content, since actualy content may differ from\r
+                       // the original snapshot due to dom change. (#4622)\r
+                       this.update();\r
                        this.fireChange();\r
                },\r
 \r
@@ -475,6 +543,14 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        }\r
 \r
                        return false;\r
+               },\r
+\r
+               /**\r
+                * Update the last snapshot of the undo stack with the current editor content.\r
+                */\r
+               update : function()\r
+               {\r
+                       this.snapshots.splice( this.index, 1, ( this.currentImage = new Image( this.editor ) ) );\r
                }\r
        };\r
 })();\r
@@ -482,9 +558,36 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 /**\r
  * The number of undo steps to be saved. The higher this setting value the more\r
  * memory is used for it.\r
+ * @name CKEDITOR.config.undoStackSize\r
  * @type Number\r
  * @default 20\r
  * @example\r
  * config.undoStackSize = 50;\r
  */\r
-CKEDITOR.config.undoStackSize = 20;\r
+\r
+/**\r
+ * Fired when the editor is about to save an undo snapshot. This event can be\r
+ * fired by plugins and customizations to make the editor saving undo snapshots.\r
+ * @name CKEDITOR.editor#saveSnapshot\r
+ * @event\r
+ */\r
+\r
+/**\r
+ * Fired before an undo image is to be taken. An undo image represents the\r
+ * editor state at some point. It's saved into an undo store, so the editor is\r
+ * able to recover the editor state on undo and redo operations.\r
+ * @name CKEDITOR.editor#beforeUndoImage\r
+ * @since 3.5.3\r
+ * @see CKEDITOR.editor#afterUndoImage\r
+ * @event\r
+ */\r
+\r
+/**\r
+ * Fired after an undo image is taken. An undo image represents the\r
+ * editor state at some point. It's saved into an undo store, so the editor is\r
+ * able to recover the editor state on undo and redo operations.\r
+ * @name CKEDITOR.editor#afterUndoImage\r
+ * @since 3.5.3\r
+ * @see CKEDITOR.editor#beforeUndoImage\r
+ * @event\r
+ */\r