vendor/assets/ckeditor/ckeditor/_source/plugins/undo/plugin.js in ckeditor-rails-0.0.4 vs vendor/assets/ckeditor/ckeditor/_source/plugins/undo/plugin.js in ckeditor-rails-0.0.5

- old
+ new

@@ -1,580 +1,593 @@ -/* -Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.html or http://ckeditor.com/license -*/ - -/** - * @fileOverview Undo/Redo system for saving shapshot for document modification - * and other recordable changes. - */ - -(function() -{ - CKEDITOR.plugins.add( 'undo', - { - requires : [ 'selection', 'wysiwygarea' ], - - init : function( editor ) - { - var undoManager = new UndoManager( editor ); - - var undoCommand = editor.addCommand( 'undo', - { - exec : function() - { - if ( undoManager.undo() ) - { - editor.selectionChange(); - this.fire( 'afterUndo' ); - } - }, - state : CKEDITOR.TRISTATE_DISABLED, - canUndo : false - }); - - var redoCommand = editor.addCommand( 'redo', - { - exec : function() - { - if ( undoManager.redo() ) - { - editor.selectionChange(); - this.fire( 'afterRedo' ); - } - }, - state : CKEDITOR.TRISTATE_DISABLED, - canUndo : false - }); - - undoManager.onChange = function() - { - undoCommand.setState( undoManager.undoable() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); - redoCommand.setState( undoManager.redoable() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); - }; - - function recordCommand( event ) - { - // If the command hasn't been marked to not support undo. - if ( undoManager.enabled && event.data.command.canUndo !== false ) - undoManager.save(); - } - - // We'll save snapshots before and after executing a command. - editor.on( 'beforeCommandExec', recordCommand ); - editor.on( 'afterCommandExec', recordCommand ); - - // Save snapshots before doing custom changes. - editor.on( 'saveSnapshot', function() - { - undoManager.save(); - }); - - // Registering keydown on every document recreation.(#3844) - editor.on( 'contentDom', function() - { - editor.document.on( 'keydown', function( event ) - { - // Do not capture CTRL hotkeys. - if ( !event.data.$.ctrlKey && !event.data.$.metaKey ) - undoManager.type( event ); - }); - }); - - // Always save an undo snapshot - the previous mode might have - // changed editor contents. - editor.on( 'beforeModeUnload', function() - { - editor.mode == 'wysiwyg' && undoManager.save( true ); - }); - - // Make the undo manager available only in wysiwyg mode. - editor.on( 'mode', function() - { - undoManager.enabled = editor.readOnly ? false : editor.mode == 'wysiwyg'; - undoManager.onChange(); - }); - - editor.ui.addButton( 'Undo', - { - label : editor.lang.undo, - command : 'undo' - }); - - editor.ui.addButton( 'Redo', - { - label : editor.lang.redo, - command : 'redo' - }); - - editor.resetUndo = function() - { - // Reset the undo stack. - undoManager.reset(); - - // Create the first image. - editor.fire( 'saveSnapshot' ); - }; - - /** - * Update the undo stacks with any subsequent DOM changes after this call. - * @name CKEDITOR.editor#updateUndo - * @example - * function() - * { - * editor.fire( 'updateSnapshot' ); - * ... - * // Ask to include subsequent (in this call stack) DOM changes to be - * // considered as part of the first snapshot. - * editor.fire( 'updateSnapshot' ); - * editor.document.body.append(...); - * ... - * } - */ - editor.on( 'updateSnapshot', function() - { - if ( undoManager.currentImage && new Image( editor ).equals( undoManager.currentImage ) ) - setTimeout( function() { undoManager.update(); }, 0 ); - }); - } - }); - - CKEDITOR.plugins.undo = {}; - - /** - * Undo snapshot which represents the current document status. - * @name CKEDITOR.plugins.undo.Image - * @param editor The editor instance on which the image is created. - */ - var Image = CKEDITOR.plugins.undo.Image = function( editor ) - { - this.editor = editor; - - editor.fire( 'beforeUndoImage' ); - - var contents = editor.getSnapshot(), - selection = contents && editor.getSelection(); - - // In IE, we need to remove the expando attributes. - CKEDITOR.env.ie && contents && ( contents = contents.replace( /\s+data-cke-expando=".*?"/g, '' ) ); - - this.contents = contents; - this.bookmarks = selection && selection.createBookmarks2( true ); - - editor.fire( 'afterUndoImage' ); - }; - - // Attributes that browser may changing them when setting via innerHTML. - var protectedAttrs = /\b(?:href|src|name)="[^"]*?"/gi; - - Image.prototype = - { - equals : function( otherImage, contentOnly ) - { - - var thisContents = this.contents, - otherContents = otherImage.contents; - - // For IE6/7 : Comparing only the protected attribute values but not the original ones.(#4522) - if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ) - { - thisContents = thisContents.replace( protectedAttrs, '' ); - otherContents = otherContents.replace( protectedAttrs, '' ); - } - - if ( thisContents != otherContents ) - return false; - - if ( contentOnly ) - return true; - - var bookmarksA = this.bookmarks, - bookmarksB = otherImage.bookmarks; - - if ( bookmarksA || bookmarksB ) - { - if ( !bookmarksA || !bookmarksB || bookmarksA.length != bookmarksB.length ) - return false; - - for ( var i = 0 ; i < bookmarksA.length ; i++ ) - { - var bookmarkA = bookmarksA[ i ], - bookmarkB = bookmarksB[ i ]; - - if ( - bookmarkA.startOffset != bookmarkB.startOffset || - bookmarkA.endOffset != bookmarkB.endOffset || - !CKEDITOR.tools.arrayCompare( bookmarkA.start, bookmarkB.start ) || - !CKEDITOR.tools.arrayCompare( bookmarkA.end, bookmarkB.end ) ) - { - return false; - } - } - } - - return true; - } - }; - - /** - * @constructor Main logic for Redo/Undo feature. - */ - function UndoManager( editor ) - { - this.editor = editor; - - // Reset the undo stack. - this.reset(); - } - - - var editingKeyCodes = { /*Backspace*/ 8:1, /*Delete*/ 46:1 }, - modifierKeyCodes = { /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1 }, - navigationKeyCodes = { 37:1, 38:1, 39:1, 40:1 }; // Arrows: L, T, R, B - - UndoManager.prototype = - { - /** - * Process undo system regard keystrikes. - * @param {CKEDITOR.dom.event} event - */ - type : function( event ) - { - var keystroke = event && event.data.getKey(), - isModifierKey = keystroke in modifierKeyCodes, - isEditingKey = keystroke in editingKeyCodes, - wasEditingKey = this.lastKeystroke in editingKeyCodes, - sameAsLastEditingKey = isEditingKey && keystroke == this.lastKeystroke, - // Keystrokes which navigation through contents. - isReset = keystroke in navigationKeyCodes, - wasReset = this.lastKeystroke in navigationKeyCodes, - - // Keystrokes which just introduce new contents. - isContent = ( !isEditingKey && !isReset ), - - // Create undo snap for every different modifier key. - modifierSnapshot = ( isEditingKey && !sameAsLastEditingKey ), - // Create undo snap on the following cases: - // 1. Just start to type . - // 2. Typing some content after a modifier. - // 3. Typing some content after make a visible selection. - startedTyping = !( isModifierKey || this.typing ) - || ( isContent && ( wasEditingKey || wasReset ) ); - - if ( startedTyping || modifierSnapshot ) - { - var beforeTypeImage = new Image( this.editor ); - - // Use setTimeout, so we give the necessary time to the - // browser to insert the character into the DOM. - CKEDITOR.tools.setTimeout( function() - { - var currentSnapshot = this.editor.getSnapshot(); - - // In IE, we need to remove the expando attributes. - if ( CKEDITOR.env.ie ) - currentSnapshot = currentSnapshot.replace( /\s+data-cke-expando=".*?"/g, '' ); - - if ( beforeTypeImage.contents != currentSnapshot ) - { - // It's safe to now indicate typing state. - this.typing = true; - - // This's a special save, with specified snapshot - // and without auto 'fireChange'. - if ( !this.save( false, beforeTypeImage, false ) ) - // Drop future snapshots. - this.snapshots.splice( this.index + 1, this.snapshots.length - this.index - 1 ); - - this.hasUndo = true; - this.hasRedo = false; - - this.typesCount = 1; - this.modifiersCount = 1; - - this.onChange(); - } - }, - 0, this - ); - } - - this.lastKeystroke = keystroke; - - // Create undo snap after typed too much (over 25 times). - if ( isEditingKey ) - { - this.typesCount = 0; - this.modifiersCount++; - - if ( this.modifiersCount > 25 ) - { - this.save( false, null, false ); - this.modifiersCount = 1; - } - } - else if ( !isReset ) - { - this.modifiersCount = 0; - this.typesCount++; - - if ( this.typesCount > 25 ) - { - this.save( false, null, false ); - this.typesCount = 1; - } - } - - }, - - reset : function() // Reset the undo stack. - { - /** - * Remember last pressed key. - */ - this.lastKeystroke = 0; - - /** - * Stack for all the undo and redo snapshots, they're always created/removed - * in consistency. - */ - this.snapshots = []; - - /** - * Current snapshot history index. - */ - this.index = -1; - - this.limit = this.editor.config.undoStackSize || 20; - - this.currentImage = null; - - this.hasUndo = false; - this.hasRedo = false; - - this.resetType(); - }, - - /** - * Reset all states about typing. - * @see UndoManager.type - */ - resetType : function() - { - this.typing = false; - delete this.lastKeystroke; - this.typesCount = 0; - this.modifiersCount = 0; - }, - fireChange : function() - { - this.hasUndo = !!this.getNextImage( true ); - this.hasRedo = !!this.getNextImage( false ); - // Reset typing - this.resetType(); - this.onChange(); - }, - - /** - * Save a snapshot of document image for later retrieve. - */ - save : function( onContentOnly, image, autoFireChange ) - { - var snapshots = this.snapshots; - - // Get a content image. - if ( !image ) - image = new Image( this.editor ); - - // Do nothing if it was not possible to retrieve an image. - if ( image.contents === false ) - return false; - - // Check if this is a duplicate. In such case, do nothing. - if ( this.currentImage && image.equals( this.currentImage, onContentOnly ) ) - return false; - - // Drop future snapshots. - snapshots.splice( this.index + 1, snapshots.length - this.index - 1 ); - - // If we have reached the limit, remove the oldest one. - if ( snapshots.length == this.limit ) - snapshots.shift(); - - // Add the new image, updating the current index. - this.index = snapshots.push( image ) - 1; - - this.currentImage = image; - - if ( autoFireChange !== false ) - this.fireChange(); - return true; - }, - - restoreImage : function( image ) - { - this.editor.loadSnapshot( image.contents ); - - if ( image.bookmarks ) - this.editor.getSelection().selectBookmarks( image.bookmarks ); - else if ( CKEDITOR.env.ie ) - { - // IE BUG: If I don't set the selection to *somewhere* after setting - // document contents, then IE would create an empty paragraph at the bottom - // the next time the document is modified. - var $range = this.editor.document.getBody().$.createTextRange(); - $range.collapse( true ); - $range.select(); - } - - this.index = image.index; - - // Update current image with the actual editor - // content, since actualy content may differ from - // the original snapshot due to dom change. (#4622) - this.update(); - this.fireChange(); - }, - - // Get the closest available image. - getNextImage : function( isUndo ) - { - var snapshots = this.snapshots, - currentImage = this.currentImage, - image, i; - - if ( currentImage ) - { - if ( isUndo ) - { - for ( i = this.index - 1 ; i >= 0 ; i-- ) - { - image = snapshots[ i ]; - if ( !currentImage.equals( image, true ) ) - { - image.index = i; - return image; - } - } - } - else - { - for ( i = this.index + 1 ; i < snapshots.length ; i++ ) - { - image = snapshots[ i ]; - if ( !currentImage.equals( image, true ) ) - { - image.index = i; - return image; - } - } - } - } - - return null; - }, - - /** - * Check the current redo state. - * @return {Boolean} Whether the document has previous state to - * retrieve. - */ - redoable : function() - { - return this.enabled && this.hasRedo; - }, - - /** - * Check the current undo state. - * @return {Boolean} Whether the document has future state to restore. - */ - undoable : function() - { - return this.enabled && this.hasUndo; - }, - - /** - * Perform undo on current index. - */ - undo : function() - { - if ( this.undoable() ) - { - this.save( true ); - - var image = this.getNextImage( true ); - if ( image ) - return this.restoreImage( image ), true; - } - - return false; - }, - - /** - * Perform redo on current index. - */ - redo : function() - { - if ( this.redoable() ) - { - // Try to save. If no changes have been made, the redo stack - // will not change, so it will still be redoable. - this.save( true ); - - // If instead we had changes, we can't redo anymore. - if ( this.redoable() ) - { - var image = this.getNextImage( false ); - if ( image ) - return this.restoreImage( image ), true; - } - } - - return false; - }, - - /** - * Update the last snapshot of the undo stack with the current editor content. - */ - update : function() - { - this.snapshots.splice( this.index, 1, ( this.currentImage = new Image( this.editor ) ) ); - } - }; -})(); - -/** - * The number of undo steps to be saved. The higher this setting value the more - * memory is used for it. - * @name CKEDITOR.config.undoStackSize - * @type Number - * @default 20 - * @example - * config.undoStackSize = 50; - */ - -/** - * Fired when the editor is about to save an undo snapshot. This event can be - * fired by plugins and customizations to make the editor saving undo snapshots. - * @name CKEDITOR.editor#saveSnapshot - * @event - */ - -/** - * Fired before an undo image is to be taken. An undo image represents the - * editor state at some point. It's saved into an undo store, so the editor is - * able to recover the editor state on undo and redo operations. - * @name CKEDITOR.editor#beforeUndoImage - * @since 3.5.3 - * @see CKEDITOR.editor#afterUndoImage - * @event - */ - -/** - * Fired after an undo image is taken. An undo image represents the - * editor state at some point. It's saved into an undo store, so the editor is - * able to recover the editor state on undo and redo operations. - * @name CKEDITOR.editor#afterUndoImage - * @since 3.5.3 - * @see CKEDITOR.editor#beforeUndoImage - * @event - */ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Undo/Redo system for saving shapshot for document modification + * and other recordable changes. + */ + +(function() +{ + CKEDITOR.plugins.add( 'undo', + { + requires : [ 'selection', 'wysiwygarea' ], + + init : function( editor ) + { + var undoManager = new UndoManager( editor ); + + var undoCommand = editor.addCommand( 'undo', + { + exec : function() + { + if ( undoManager.undo() ) + { + editor.selectionChange(); + this.fire( 'afterUndo' ); + } + }, + state : CKEDITOR.TRISTATE_DISABLED, + canUndo : false + }); + + var redoCommand = editor.addCommand( 'redo', + { + exec : function() + { + if ( undoManager.redo() ) + { + editor.selectionChange(); + this.fire( 'afterRedo' ); + } + }, + state : CKEDITOR.TRISTATE_DISABLED, + canUndo : false + }); + + undoManager.onChange = function() + { + undoCommand.setState( undoManager.undoable() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); + redoCommand.setState( undoManager.redoable() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); + }; + + function recordCommand( event ) + { + // If the command hasn't been marked to not support undo. + if ( undoManager.enabled && event.data.command.canUndo !== false ) + undoManager.save(); + } + + // We'll save snapshots before and after executing a command. + editor.on( 'beforeCommandExec', recordCommand ); + editor.on( 'afterCommandExec', recordCommand ); + + // Save snapshots before doing custom changes. + editor.on( 'saveSnapshot', function( evt ) + { + undoManager.save( evt.data && evt.data.contentOnly ); + }); + + // Registering keydown on every document recreation.(#3844) + editor.on( 'contentDom', function() + { + editor.document.on( 'keydown', function( event ) + { + // Do not capture CTRL hotkeys. + if ( !event.data.$.ctrlKey && !event.data.$.metaKey ) + undoManager.type( event ); + }); + }); + + // Always save an undo snapshot - the previous mode might have + // changed editor contents. + editor.on( 'beforeModeUnload', function() + { + editor.mode == 'wysiwyg' && undoManager.save( true ); + }); + + // Make the undo manager available only in wysiwyg mode. + editor.on( 'mode', function() + { + undoManager.enabled = editor.readOnly ? false : editor.mode == 'wysiwyg'; + undoManager.onChange(); + }); + + editor.ui.addButton( 'Undo', + { + label : editor.lang.undo, + command : 'undo' + }); + + editor.ui.addButton( 'Redo', + { + label : editor.lang.redo, + command : 'redo' + }); + + editor.resetUndo = function() + { + // Reset the undo stack. + undoManager.reset(); + + // Create the first image. + editor.fire( 'saveSnapshot' ); + }; + + /** + * Amend the top of undo stack (last undo image) with the current DOM changes. + * @name CKEDITOR.editor#updateUndo + * @example + * function() + * { + * editor.fire( 'saveSnapshot' ); + * editor.document.body.append(...); + * // Make new changes following the last undo snapshot part of it. + * editor.fire( 'updateSnapshot' ); + * ... + * } + */ + editor.on( 'updateSnapshot', function() + { + if ( undoManager.currentImage ) + undoManager.update(); + }); + } + }); + + CKEDITOR.plugins.undo = {}; + + /** + * Undo snapshot which represents the current document status. + * @name CKEDITOR.plugins.undo.Image + * @param editor The editor instance on which the image is created. + */ + var Image = CKEDITOR.plugins.undo.Image = function( editor ) + { + this.editor = editor; + + editor.fire( 'beforeUndoImage' ); + + var contents = editor.getSnapshot(), + selection = contents && editor.getSelection(); + + // In IE, we need to remove the expando attributes. + CKEDITOR.env.ie && contents && ( contents = contents.replace( /\s+data-cke-expando=".*?"/g, '' ) ); + + this.contents = contents; + this.bookmarks = selection && selection.createBookmarks2( true ); + + editor.fire( 'afterUndoImage' ); + }; + + // Attributes that browser may changing them when setting via innerHTML. + var protectedAttrs = /\b(?:href|src|name)="[^"]*?"/gi; + + Image.prototype = + { + equals : function( otherImage, contentOnly ) + { + + var thisContents = this.contents, + otherContents = otherImage.contents; + + // For IE6/7 : Comparing only the protected attribute values but not the original ones.(#4522) + if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ) + { + thisContents = thisContents.replace( protectedAttrs, '' ); + otherContents = otherContents.replace( protectedAttrs, '' ); + } + + if ( thisContents != otherContents ) + return false; + + if ( contentOnly ) + return true; + + var bookmarksA = this.bookmarks, + bookmarksB = otherImage.bookmarks; + + if ( bookmarksA || bookmarksB ) + { + if ( !bookmarksA || !bookmarksB || bookmarksA.length != bookmarksB.length ) + return false; + + for ( var i = 0 ; i < bookmarksA.length ; i++ ) + { + var bookmarkA = bookmarksA[ i ], + bookmarkB = bookmarksB[ i ]; + + if ( + bookmarkA.startOffset != bookmarkB.startOffset || + bookmarkA.endOffset != bookmarkB.endOffset || + !CKEDITOR.tools.arrayCompare( bookmarkA.start, bookmarkB.start ) || + !CKEDITOR.tools.arrayCompare( bookmarkA.end, bookmarkB.end ) ) + { + return false; + } + } + } + + return true; + } + }; + + /** + * @constructor Main logic for Redo/Undo feature. + */ + function UndoManager( editor ) + { + this.editor = editor; + + // Reset the undo stack. + this.reset(); + } + + + var editingKeyCodes = { /*Backspace*/ 8:1, /*Delete*/ 46:1 }, + modifierKeyCodes = { /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1 }, + navigationKeyCodes = { 37:1, 38:1, 39:1, 40:1 }; // Arrows: L, T, R, B + + UndoManager.prototype = + { + /** + * Process undo system regard keystrikes. + * @param {CKEDITOR.dom.event} event + */ + type : function( event ) + { + var keystroke = event && event.data.getKey(), + isModifierKey = keystroke in modifierKeyCodes, + isEditingKey = keystroke in editingKeyCodes, + wasEditingKey = this.lastKeystroke in editingKeyCodes, + sameAsLastEditingKey = isEditingKey && keystroke == this.lastKeystroke, + // Keystrokes which navigation through contents. + isReset = keystroke in navigationKeyCodes, + wasReset = this.lastKeystroke in navigationKeyCodes, + + // Keystrokes which just introduce new contents. + isContent = ( !isEditingKey && !isReset ), + + // Create undo snap for every different modifier key. + modifierSnapshot = ( isEditingKey && !sameAsLastEditingKey ), + // Create undo snap on the following cases: + // 1. Just start to type . + // 2. Typing some content after a modifier. + // 3. Typing some content after make a visible selection. + startedTyping = !( isModifierKey || this.typing ) + || ( isContent && ( wasEditingKey || wasReset ) ); + + if ( startedTyping || modifierSnapshot ) + { + var beforeTypeImage = new Image( this.editor ), + beforeTypeCount = this.snapshots.length; + + // Use setTimeout, so we give the necessary time to the + // browser to insert the character into the DOM. + CKEDITOR.tools.setTimeout( function() + { + var currentSnapshot = this.editor.getSnapshot(); + + // In IE, we need to remove the expando attributes. + if ( CKEDITOR.env.ie ) + currentSnapshot = currentSnapshot.replace( /\s+data-cke-expando=".*?"/g, '' ); + + // If changes have taken place, while not been captured yet (#8459), + // compensate the snapshot. + if ( beforeTypeImage.contents != currentSnapshot && + beforeTypeCount == this.snapshots.length ) + { + // It's safe to now indicate typing state. + this.typing = true; + + // This's a special save, with specified snapshot + // and without auto 'fireChange'. + if ( !this.save( false, beforeTypeImage, false ) ) + // Drop future snapshots. + this.snapshots.splice( this.index + 1, this.snapshots.length - this.index - 1 ); + + this.hasUndo = true; + this.hasRedo = false; + + this.typesCount = 1; + this.modifiersCount = 1; + + this.onChange(); + } + }, + 0, this + ); + } + + this.lastKeystroke = keystroke; + + // Create undo snap after typed too much (over 25 times). + if ( isEditingKey ) + { + this.typesCount = 0; + this.modifiersCount++; + + if ( this.modifiersCount > 25 ) + { + this.save( false, null, false ); + this.modifiersCount = 1; + } + } + else if ( !isReset ) + { + this.modifiersCount = 0; + this.typesCount++; + + if ( this.typesCount > 25 ) + { + this.save( false, null, false ); + this.typesCount = 1; + } + } + + }, + + reset : function() // Reset the undo stack. + { + /** + * Remember last pressed key. + */ + this.lastKeystroke = 0; + + /** + * Stack for all the undo and redo snapshots, they're always created/removed + * in consistency. + */ + this.snapshots = []; + + /** + * Current snapshot history index. + */ + this.index = -1; + + this.limit = this.editor.config.undoStackSize || 20; + + this.currentImage = null; + + this.hasUndo = false; + this.hasRedo = false; + + this.resetType(); + }, + + /** + * Reset all states about typing. + * @see UndoManager.type + */ + resetType : function() + { + this.typing = false; + delete this.lastKeystroke; + this.typesCount = 0; + this.modifiersCount = 0; + }, + fireChange : function() + { + this.hasUndo = !!this.getNextImage( true ); + this.hasRedo = !!this.getNextImage( false ); + // Reset typing + this.resetType(); + this.onChange(); + }, + + /** + * Save a snapshot of document image for later retrieve. + */ + save : function( onContentOnly, image, autoFireChange ) + { + var snapshots = this.snapshots; + + // Get a content image. + if ( !image ) + image = new Image( this.editor ); + + // Do nothing if it was not possible to retrieve an image. + if ( image.contents === false ) + return false; + + // Check if this is a duplicate. In such case, do nothing. + if ( this.currentImage && image.equals( this.currentImage, onContentOnly ) ) + return false; + + // Drop future snapshots. + snapshots.splice( this.index + 1, snapshots.length - this.index - 1 ); + + // If we have reached the limit, remove the oldest one. + if ( snapshots.length == this.limit ) + snapshots.shift(); + + // Add the new image, updating the current index. + this.index = snapshots.push( image ) - 1; + + this.currentImage = image; + + if ( autoFireChange !== false ) + this.fireChange(); + return true; + }, + + restoreImage : function( image ) + { + // Bring editor focused to restore selection. + var editor = this.editor, + sel; + + if ( image.bookmarks ) + { + editor.focus(); + // Retrieve the selection beforehand. (#8324) + sel = editor.getSelection(); + } + + this.editor.loadSnapshot( image.contents ); + + if ( image.bookmarks ) + sel.selectBookmarks( image.bookmarks ); + else if ( CKEDITOR.env.ie ) + { + // IE BUG: If I don't set the selection to *somewhere* after setting + // document contents, then IE would create an empty paragraph at the bottom + // the next time the document is modified. + var $range = this.editor.document.getBody().$.createTextRange(); + $range.collapse( true ); + $range.select(); + } + + this.index = image.index; + + // Update current image with the actual editor + // content, since actualy content may differ from + // the original snapshot due to dom change. (#4622) + this.update(); + this.fireChange(); + }, + + // Get the closest available image. + getNextImage : function( isUndo ) + { + var snapshots = this.snapshots, + currentImage = this.currentImage, + image, i; + + if ( currentImage ) + { + if ( isUndo ) + { + for ( i = this.index - 1 ; i >= 0 ; i-- ) + { + image = snapshots[ i ]; + if ( !currentImage.equals( image, true ) ) + { + image.index = i; + return image; + } + } + } + else + { + for ( i = this.index + 1 ; i < snapshots.length ; i++ ) + { + image = snapshots[ i ]; + if ( !currentImage.equals( image, true ) ) + { + image.index = i; + return image; + } + } + } + } + + return null; + }, + + /** + * Check the current redo state. + * @return {Boolean} Whether the document has previous state to + * retrieve. + */ + redoable : function() + { + return this.enabled && this.hasRedo; + }, + + /** + * Check the current undo state. + * @return {Boolean} Whether the document has future state to restore. + */ + undoable : function() + { + return this.enabled && this.hasUndo; + }, + + /** + * Perform undo on current index. + */ + undo : function() + { + if ( this.undoable() ) + { + this.save( true ); + + var image = this.getNextImage( true ); + if ( image ) + return this.restoreImage( image ), true; + } + + return false; + }, + + /** + * Perform redo on current index. + */ + redo : function() + { + if ( this.redoable() ) + { + // Try to save. If no changes have been made, the redo stack + // will not change, so it will still be redoable. + this.save( true ); + + // If instead we had changes, we can't redo anymore. + if ( this.redoable() ) + { + var image = this.getNextImage( false ); + if ( image ) + return this.restoreImage( image ), true; + } + } + + return false; + }, + + /** + * Update the last snapshot of the undo stack with the current editor content. + */ + update : function() + { + this.snapshots.splice( this.index, 1, ( this.currentImage = new Image( this.editor ) ) ); + } + }; +})(); + +/** + * The number of undo steps to be saved. The higher this setting value the more + * memory is used for it. + * @name CKEDITOR.config.undoStackSize + * @type Number + * @default 20 + * @example + * config.undoStackSize = 50; + */ + +/** + * Fired when the editor is about to save an undo snapshot. This event can be + * fired by plugins and customizations to make the editor saving undo snapshots. + * @name CKEDITOR.editor#saveSnapshot + * @event + */ + +/** + * Fired before an undo image is to be taken. An undo image represents the + * editor state at some point. It's saved into an undo store, so the editor is + * able to recover the editor state on undo and redo operations. + * @name CKEDITOR.editor#beforeUndoImage + * @since 3.5.3 + * @see CKEDITOR.editor#afterUndoImage + * @event + */ + +/** + * Fired after an undo image is taken. An undo image represents the + * editor state at some point. It's saved into an undo store, so the editor is + * able to recover the editor state on undo and redo operations. + * @name CKEDITOR.editor#afterUndoImage + * @since 3.5.3 + * @see CKEDITOR.editor#beforeUndoImage + * @event + */