### Eclipse Workspace Patch 1.0
#P ckeditor3.0
Index: _source/plugins/undoredo/plugin.js
===================================================================
--- _source/plugins/undoredo/plugin.js	(revision 0)
+++ _source/plugins/undoredo/plugin.js	(revision 0)
@@ -0,0 +1,279 @@
+/**
+ * @fileOverview Undo/Redo system for saving shapshot for document modification
+ *               and other recordable changes.
+ * @see #2763, #2, #915
+ * @description
+ *      <p>
+ *      <span style="border-collapse: separate; color: rgb(0, 0, 0);
+ *      font-family: Verdana; font-size: 13px; font-style: normal; font-variant:
+ *      normal; font-weight: normal; letter-spacing: normal; line-height:
+ *      normal; orphans: 2; text-indent: 0px; text-transform: none; white-space:
+ *      normal; widows: 2; word-spacing: 0px;" class="Apple-style-span">
+ *      <p>
+ *      Undo/Redo system need to be ported from v2, the features could be summed
+ *      up as below:
+ *      </p>
+ *      <ul>
+ *      <li>Undo system restore/retrieve document status from both selection
+ *      and content</li>
+ *      <li>All commands that modify the document could support undo/redo
+ *      feature,but each command has chance to optionaly declare whether would
+ *      hooked with undo systemwhen they're registed.</li>
+ *      </ul>
+ *      <blockquote><blockquote>
+ *      <p>
+ *      <strong>Note</strong>: Since from v3 all keystroke will pre-bind to
+ *      command, so keystrokes also hooked with undo system through command
+ *      interface.
+ *      </p>
+ *      </blockquote></blockquote>
+ *      <ul>
+ *      <li>Undo feature itself performed as a command, A button plugin is
+ *      required for interfacing this command to end user and keystroke pair of
+ *      'Ctrl-Z' and 'Ctrl-Y' too.</li>
+ *      <li>Support empty/reset all the undo snapshots for clean up, and A
+ *      button plugin is required for interfacing this<span
+ *      class="Apple-converted-space">&nbsp;</span><i>reset</i></li>
+ *      <li>snapshot for undo system could be recorded in two forms:
+ *      <ol>
+ *      <li>One snapshot for each record action, this apply for almost every
+ *      functional commands, but a few special keystroke commands that also
+ *      being recorded in this way including:
+ *      <ol style="list-style-type: lower-alpha;" class="loweralpha">
+ *      <li>'Enter'</li>
+ *      <li>'ShiftEnter'</li>
+ *      <li>'CtrlBackspace'</li>
+ *      <li>'Delete'</li>
+ *      <li>'Tab'</li>
+ *      <li>'Ctrl-X'</li>
+ *      <li>'Ctrl-V'</li>
+ *      </ol>
+ *      </li>
+ *      <li>One snapshot for a serials of record actions, this apply for most
+ *      keystroke commands.</li>
+ *      </ol>
+ *      </li>
+ *      </ul>
+ *      </span>
+ *      </p>
+ */
+CKEDITOR.plugins.add('undoredo', {
+
+			requires : ['selection', 'wysiwygarea'],
+			beforeInit : function(editor) {
+
+				var urm = new UndoRedoManager(editor);
+				function recordCommand(evt) {
+					var cp /* {FCKEDITOR.command.Pair} */= evt.data;
+					if (cp.command.supportUndoRedo !== false
+							// ingore mode change command
+							&& cp.name !== 'source' && cp.name !== 'wysiwyg'
+							&& urm.enabled) {
+						urm.save(evt.name === 'beforeCommandExec'
+								? true
+								: false);
+					}
+				}
+
+				editor.on('beforeCommandExec', recordCommand);
+				editor.on('afterCommandExec', recordCommand);
+
+				// sensitive to mode change, only appliable for 'wysiwyg' mode
+				editor.on('mode', function(currentMode) {
+							urm.enabled = currentMode.data === 'wysiwyg';
+						});
+
+				editor.addCommand('undo',
+						/**
+						 * Rollback to last modification of this document
+						 * 
+						 * @type CKEDITOR.command status of document
+						 */
+						{
+					exec : function() {
+						if (urm.undo()) {
+							this.fire('AfterUndo');
+							// TODO: fire 'selectionchange'
+						}
+					},
+					supportUndoRedo : false
+				});
+
+				editor.addCommand('redo',
+						/**
+						 * Retrieve to next modification of this document
+						 * 
+						 * @type CKEDITOR.command status of document
+						 */
+						{
+					exec : function() {
+						if (urm.redo()) {
+							this.fire('AfterRedo');
+							// TODO: fire 'selectionchange'
+						}
+					},
+					supportUndoRedo : false
+				});
+			}
+		});
+
+/**
+ * @constructor Main logic for Redo/Undo feature
+ * @related FCKUndo in v2
+ */
+function UndoRedoManager(editor) {
+
+	/**
+	 * @field Whether undo system is usable decided by envoriment
+	 */
+	this.enabled = false;
+
+	var UNDO_NUM_LIMIT = 20;
+
+	/**
+	 * Stack for all the undo and redo snapshots, they're always created/removed
+	 * in consistency.
+	 * 
+	 * @type {Array<DocumentImage>}
+	 * 
+	 */
+	var undoSnaps = [], redoSnaps = [];
+
+	/**
+	 * Current snapshot history index
+	 */
+	var index = 0; // First capture start from 1
+
+	/**
+	 * @private Get the current blockediting mode of this editor
+	 * @param editor
+	 * @param mode
+	 * @return
+	 */
+	function getMode(mode) {
+		return editor._.modes && editor._.modes[mode || editor.mode];
+	};
+
+	/**
+	 * @constructor DocumentImage - A snapshot image which represent the current
+	 *              document status.
+	 */
+	function DocumentImage() {
+
+		var bms, ranges, cWithBm, c;
+
+		c = getMode('wysiwyg').getSnapshotData();
+		bms = editor.document.getSelection().createBookmarks(true);
+		ranges = editor.document.getSelection().getRanges(); // Get ranges
+		// from cache
+		cWithBm = getMode('wysiwyg').getSnapshotData() // Get content along
+		// with bookmark
+		editor.document.getSelection().selectBookmarks(bms); // restore
+		// selection by
+		// the bookmark
+		// and btw we
+		// also
+		// destroy the nodes in document
+		return {
+			'content' : /** {String} original document string */
+			c,
+			'dirtyContent' :
+			/**
+			 * {String} document content string with bookmarks
+			 */
+			cWithBm,
+			'bookmark'/** {Array<CKEDITOR.dom.range.Bookmark>} */
+			: bms,
+			equals :
+			/**
+			 * Compare if two document snapshot are the same, this is only
+			 * content comparison with bookmarks striped.
+			 * 
+			 * @img {DocumentImage} The other image to compare with
+			 */
+			function(img) {
+				return this.content == img.content;
+			}
+		}
+	};
+
+	/**
+	 * @method Save a snapshot of document image for later retrieve, content
+	 *         being saved as raw html string, while selection saved as a
+	 *         lightweight bookmark.
+	 */
+	this.save = function(isFront) {
+		var sns = isFront ? undoSnaps : redoSnaps;
+
+		if (index == UNDO_NUM_LIMIT) {
+			sns.unshift(); // Die out earlier ones.
+		}
+
+		sns.splice(index, sns.length - index); // Drop older snaps
+
+		var img = new DocumentImage();
+		if (sns.length && img.equals(sns[sns.length - 1])) // duplicate
+			// examination
+			return;
+
+		sns.push(img);
+
+		if (!isFront)
+			index++; // increase when command has been fully recorded.
+	}
+
+	function restoreImage(index, isUndo) {
+		var img;
+		img = isUndo ? undoSnaps[index] : redoSnaps[index];
+		if (img.content) {
+			getMode('wysiwyg').loadSnapshotData(img.dirtyContent);
+			if (img.bookmark) {
+				editor.document.getSelection().selectBookmarks(img.bookmark);
+			}
+		}
+	}
+
+	/**
+	 * Check the current state of redo
+	 * 
+	 * @return {Boolean} Whether the document has previous state to retrieve
+	 */
+	this.redoAble = function() {
+		return index < redoSnaps.length;
+	}
+
+	/**
+	 * Check the current state of undo
+	 * 
+	 * @return {Boolean} Whether the document has future state to restore
+	 */
+	this.undoAble = function() {
+		return index > 0
+	}
+	/**
+	 * Perform undo on current index.
+	 * 
+	 * @method
+	 */
+	this.undo = function() {
+		if (this.undoAble()) {
+			restoreImage(--index, true); // retrieve previous one from undo
+			// history
+		} else
+			return false;
+	}
+
+	/**
+	 * Perform redo on current index.
+	 * 
+	 * @method
+	 */
+	this.redo = function() {
+		if (this.redoAble()) {
+			restoreImage(index++, false); // retrieve next one from redo
+			// history
+		} else
+			return false;
+	}
+
+}
Index: _source/plugins/editingblock/plugin.js
===================================================================
--- _source/plugins/editingblock/plugin.js	(revision 2905)
+++ _source/plugins/editingblock/plugin.js	(working copy)
@@ -190,7 +190,7 @@
 		modeEditor.load( holderElement, data || this.getData() );
 
 		this.mode = mode;
-		this.fire( 'mode' );
+		this.fire( 'mode' , mode);
 	};
 
 	/**
Index: _source/core/dom/range.js
===================================================================
--- _source/core/dom/range.js	(revision 2916)
+++ _source/core/dom/range.js	(working copy)
@@ -424,13 +424,15 @@
 			return docFrag;
 		},
 
-		// This is an "intrusive" way to create a bookmark. It includes <span> tags
-		// in the range boundaries. The advantage of it is that it is possible to
-		// handle DOM mutations when moving back to the bookmark.
-		// Attention: the inclusion of nodes in the DOM is a design choice and
-		// should not be changed as there are other points in the code that may be
-		// using those nodes to perform operations. See GetBookmarkNode.
-		createBookmark : function()
+		/**
+		 * This is an "intrusive" way to create a bookmark. It includes <span>
+		 * tags in the range boundaries. The advantage of it is that it is
+		 * possible to handle DOM mutations when moving back to the bookmark.
+		 * Attention: the inclusion of nodes in the DOM is a design choice.
+		 * 
+		 * @param serializeable {Boolean} If set to true will record the bookmark with startNode/endNode IDs for serializing purpose.
+		 */
+		createBookmark : function(serializeable)
 		{
 			var startNode, endNode;
 			var clone;
@@ -451,12 +453,12 @@
 
 				clone = this.clone();
 				clone.collapse();
-				clone.insertNode( endNode );
+				clone.insertNode( endNode );        //CONFUSE: This insert will not influence current selection. 
 			}
 
 			clone = this.clone();
 			clone.collapse( true );
-			clone.insertNode( startNode );
+			clone.insertNode( startNode );       //CONFUSE: This insert will fade current selection out.
 
 			// Update the range position.
 			if ( endNode )
@@ -466,28 +468,41 @@
 			}
 			else
 				this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
-
+            
+            if(serializeable)
+            {
+                startNode.setAttribute('id',(new Date()).valueOf() + Math.floor(Math.random()*1000) + 'S');
+                if(endNode)
+                    endNode.setAttribute('id',(new Date()).valueOf() + Math.floor(Math.random()*1000) + 'E');
+            }
 			return {
-				startNode : startNode,
-				endNode : endNode
+				startNode : serializeable? startNode.getAttribute('id') : startNode,
+				endNode : endNode? ( serializeable? endNode.getAttribute('id') : endNode) : null  
 			};
 		},
-
+        
+        /**
+         * Move the range boundaries to where the bookmarks indicated.
+         * @see CKEDITOR.dom.range::createBookmark
+         * @param bookmark {Object} 
+         */
 		moveToBookmark : function( bookmark )
 		{
+            var st =  bookmark.startNode, ed = bookmark.endNode;
+            
 			// Set the range start at the bookmark start node position.
-			this.setStartBefore( bookmark.startNode );
+			this.setStartBefore(typeof st === 'string'? (st = this.document.getById(st)): st);
+            
 
 			// Remove it, because it may interfere in the setEndBefore call.
-			bookmark.startNode.remove();
+			st.remove();
 
 			// Set the range end at the bookmark end node position, or simply
 			// collapse it if it is not available.
-			var endNode = bookmark.endNode;
-			if ( endNode )
+			if ( ed )
 			{
-				this.setEndBefore( endNode );
-				endNode.remove();
+				this.setEndBefore( typeof ed  === 'string'? (ed = this.document.getById(ed) ): ed );
+				ed.remove();
 			}
 			else
 				this.collapse( true );
Index: _source/core/editor.js
===================================================================
--- _source/core/editor.js	(revision 2880)
+++ _source/core/editor.js	(working copy)
@@ -361,8 +361,18 @@
 		execCommand : function( commandName, data )
 		{
 			var command = this.getCommand( commandName );
+            var cp =
+            /**
+             * Name-command pair to trace commands behavious
+             * @type FCKEDITOR.command.Pair
+             */
+                {name: commandName, command:command};  
 			if ( command && command.state != CKEDITOR.TRISTATE_DISABLED )
-				return command.exec( this, data );
+            {
+                this.fire('beforeCommandExec', cp);                
+				command.exec( this, data );
+                this.fire('afterCommandExec', cp);
+            }
 
 			// throw 'Unknown command name "' + commandName + '"';
 			return false;
Index: _source/plugins/selection/plugin.js
===================================================================
--- _source/plugins/selection/plugin.js	(revision 2905)
+++ _source/plugins/selection/plugin.js	(working copy)
@@ -630,13 +630,16 @@
 						sel.addRange( nativeRange );
 					}
 				},
-
-		createBookmarks : function()
+        /**
+         * This method is a delegate to ceate bookmark for every ranges in this selection.
+         * @see CKEDITOR.dom.range::createBookmark
+         */
+		createBookmarks : function(serializable)
 		{
 			var retval = [],
 				ranges = this.getRanges();
 			for ( var i = 0 ; i < ranges.length ; i++ )
-				retval.push( ranges[i].createBookmark() );
+				retval.push( ranges[i].createBookmark(serializable) );
 			return retval;
 		},
 
Index: _source/core/commanddefinition.js
===================================================================
--- _source/core/commanddefinition.js	(revision 2880)
+++ _source/core/commanddefinition.js	(working copy)
@@ -49,3 +49,16 @@
  *     }
  * });
  */
+ 
+ /**
+ * Whether the command need to be hooked into the redo/undo system.
+ * @name  CKEDITOR.commandDefinition.supportUndoRedo
+ * @type {Boolean} If not defined or 'true' both hook into undo system, set it to 'false' explicitly  keep it out.  
+ * @field
+ * @example
+ *  editorInstance.addCommand( 'sample',
+ * {
+ *     CKEDITOR.commandDefinition.supportUndoRedo : false    //declare this command does not support undo/redo  
+ * });
+ */
+
Index: _source/tests/core/dom/range.html
===================================================================
--- _source/tests/core/dom/range.html	(revision 2880)
+++ _source/tests/core/dom/range.html	(working copy)
@@ -1507,7 +1507,29 @@
 			assert.areSame( 3, range.endOffset, 'range.endOffset' );
 			assert.isFalse( range.collapsed, 'range.collapsed' );
 		},
+		
+		//Test serializable bookmark create and apply
+		test_bookmark_single_1: function () {
 
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStartBefore( doc.getById('_Span'));
+		    range.setEndAfter(doc.getById('_P'));
+		    bm = range.createBookmark(true);
+		    range.setStartBefore( doc.getById('_P2'));
+		    range.setEndAfter(doc.getById('_P2'));
+		    range.moveToBookmark(bm);
+
+		    assert.areSame( document.getElementById( '_P' ), range.startContainer.$ ,'range.startContainer');
+			assert.areSame( 1, range.startOffset, 'range.startOffset');
+			assert.areSame( document.getElementById( 'playground2' ), range.endContainer.$, 'range.endContainer');
+			assert.areSame( 4, range.endOffset, 'range.endOffset' );
+			assert.isNull(document.getElementById(bm.startNode), 'bookmark.startNode');
+			assert.isNull(document.getElementById(bm.endNode), 'bookmark.endNode');
+			
+		    
+		    
+		},
+
 		/////////////
 
 		setUp : function()
Index: _source/core/config.js
===================================================================
--- _source/core/config.js	(revision 2916)
+++ _source/core/config.js	(working copy)
@@ -1,4 +1,4 @@
-﻿/*
+﻿﻿/*
  * CKEditor - The text editor for Internet - http://ckeditor.com
  * Copyright (C) 2003-2008 Frederico Caldeira Knabben
  *
@@ -161,7 +161,7 @@
 	 * @example
 	 * config.plugins = 'elementspath,toolbar,wysiwygarea';
 	 */
-	plugins : 'basicstyles,button,dialog,elementspath,horizontalrule,htmldataprocessor,keystrokes,removeformat,smiley,link,sourcearea,tab,toolbar,wysiwygarea,forms,image,find,table,specialchar,flash,print,pagebreak,newpage',
+	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',
 
 	/**
 	 * The theme to be used to build the UI.
@@ -350,7 +350,7 @@
 			descriptions : [ ':)', ':(', ';)', ':D', ':/', ':P', 
 			'', '', '', '', '', '',
 			'', ';(', '', '', '', '',
-			':kiss', '', ],
+			':kiss', '' ],
 
 			//TODO: update path
 			path : CKEDITOR.basePath +  '_source/plugins/smiley/images/',
