/*\r
-Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
For licensing, see LICENSE.html or http://ckeditor.com/license\r
*/\r
\r
// Create the first image.\r
editor.fire( 'saveSnapshot' );\r
};\r
+\r
+ /**\r
+ * Update the undo stacks with any subsequent DOM changes after this call.\r
+ * @name CKEDITOR.editor#updateUndo\r
+ * @example\r
+ * function()\r
+ * {\r
+ * editor.fire( 'updateSnapshot' );\r
+ * ...\r
+ * // Ask to include subsequent (in this call stack) DOM changes to be\r
+ * // considered as part of the first snapshot.\r
+ * editor.fire( 'updateSnapshot' );\r
+ * editor.document.body.append(...);\r
+ * ...\r
+ * }\r
+ */\r
+ editor.on( 'updateSnapshot', function()\r
+ {\r
+ if ( undoManager.currentImage && new Image( editor ).equals( undoManager.currentImage ) )\r
+ setTimeout( function() { undoManager.update(); }, 0 );\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
+ var contents = editor.getSnapshot(),\r
+ selection = contents && editor.getSelection();\r
+\r
+ // In IE, we need to remove the expando attributes.\r
+ CKEDITOR.env.ie && contents && ( contents = contents.replace( /\s+data-cke-expando=".*?"/g, '' ) );\r
\r
- this.contents = editor.getSnapshot();\r
+ this.contents = contents;\r
this.bookmarks = selection && selection.createBookmarks2( true );\r
+ };\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
+ // 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
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
*/\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
\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
{\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
}\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
\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
*/\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
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
\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
}\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
* @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