* Added feature #8799: Move htmlArea RTE undo/redo handling to own extension
authorStanislas Rolland <typo3@sjbr.ca>
Mon, 23 Jun 2008 16:55:04 +0000 (16:55 +0000)
committerStanislas Rolland <typo3@sjbr.ca>
Mon, 23 Jun 2008 16:55:04 +0000 (16:55 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@3838 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
typo3/sysext/rtehtmlarea/ChangeLog
typo3/sysext/rtehtmlarea/ext_localconf.php
typo3/sysext/rtehtmlarea/extensions/UndoRedo/class.tx_rtehtmlarea_undoredo.php [new file with mode: 0644]
typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/htmlarea.css [new file with mode: 0644]
typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/images/redo.gif [new file with mode: 0644]
typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/images/undo.gif [new file with mode: 0644]
typo3/sysext/rtehtmlarea/htmlarea/htmlarea.js
typo3/sysext/rtehtmlarea/htmlarea/plugins/UndoRedo/locallang.xml [new file with mode: 0644]
typo3/sysext/rtehtmlarea/htmlarea/plugins/UndoRedo/undo-redo.js [new file with mode: 0644]

index 1aae496..b6e491c 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2008-06-23  Stanislas Rolland  <typo3@sjbr.ca>
+
+       * Added feature #8799: Move htmlArea RTE undo/redo handling to own extension
+
 2008-06-22  Oliver Hader  <oliver@typo3.org>
 
        * Fixed bug #8724: getElementsByClassName does not work properly in Firefox 3 and Safari 3.1 (thanks to Helmut Hummel)
index 7f97d7a..f4cb73a 100644 (file)
@@ -1,3 +1,7 @@
+2008-06-23  Stanislas Rolland  <typo3@sjbr.ca>
+
+       * Added feature #8799: Move htmlArea RTE undo/redo handling to own extension
+
 2008-06-21  Stanislas Rolland  <typo3@sjbr.ca>
 
        * Added feature #8790: htmlArea RTE image selection should honour hooking browsers
index df8ec4c..f36f05b 100644 (file)
@@ -149,6 +149,10 @@ $TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['ContextMenu'] = array();
 $TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['ContextMenu']['objectReference'] = 'EXT:'.$_EXTKEY.'/extensions/ContextMenu/class.tx_rtehtmlarea_contextmenu.php:&tx_rtehtmlarea_contextmenu';
 $TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['ContextMenu']['addIconsToSkin'] = 0;
 $TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['ContextMenu']['disableInFE'] = 0;
+$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['UndoRedo'] = array();
+$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['UndoRedo']['objectReference'] = 'EXT:'.$_EXTKEY.'/extensions/UndoRedo/class.tx_rtehtmlarea_undoredo.php:&tx_rtehtmlarea_undoredo';
+$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['UndoRedo']['addIconsToSkin'] = 0;
+$TYPO3_CONF_VARS['EXTCONF']['rtehtmlarea']['plugins']['UndoRedo']['disableInFE'] = 0;
 
 $_EXTCONF = unserialize($_EXTCONF);    // unserializing the configuration so we can use it here:
 
diff --git a/typo3/sysext/rtehtmlarea/extensions/UndoRedo/class.tx_rtehtmlarea_undoredo.php b/typo3/sysext/rtehtmlarea/extensions/UndoRedo/class.tx_rtehtmlarea_undoredo.php
new file mode 100644 (file)
index 0000000..29bbea7
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2008 Stanislas Rolland <typo3(arobas)sjbr.ca>
+*  All rights reserved
+*
+*  This script is part of the Typo3 project. The Typo3 project is
+*  free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 2 of the License, or
+*  (at your option) any later version.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*
+*  This script is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+/**
+ * Undo Redo plugin for htmlArea RTE
+ *
+ * @author Stanislas Rolland <typo3(arobas)sjbr.ca>
+ *
+ * TYPO3 SVN ID: $Id: class.tx_rtehtmlarea_undoredo.php 2985 2008-01-31 11:37:57Z ingmars $
+ *
+ */
+
+require_once(t3lib_extMgm::extPath('rtehtmlarea').'class.tx_rtehtmlareaapi.php');
+
+class tx_rtehtmlarea_undoredo extends tx_rtehtmlareaapi {
+
+       protected $extensionKey = 'rtehtmlarea';        // The key of the extension that is extending htmlArea RTE
+       protected $pluginName = 'UndoRedo';             // The name of the plugin registered by the extension
+       protected $relativePathToLocallangFile = '';    // Path to this main locallang file of the extension relative to the extension dir.
+       protected $relativePathToSkin = 'extensions/InlineElements/skin/htmlarea.css';          // Path to the skin (css) file relative to the extension dir.
+       protected $htmlAreaRTE;                         // Reference to the invoking object
+       protected $thisConfig;                          // Reference to RTE PageTSConfig
+       protected $toolbar;                             // Reference to RTE toolbar array
+       protected $LOCAL_LANG;                          // Frontend language array
+       
+       protected $pluginButtons = 'undo,redo';
+       protected $convertToolbarForHtmlAreaArray = array (
+               'undo'  => 'Undo',
+               'redo'  => 'Redo',
+               );
+       
+       /**
+        * Return JS configuration of the htmlArea plugins registered by the extension
+        *
+        * @param       integer         Relative id of the RTE editing area in the form
+        *
+        * @return string               JS configuration for registered plugins
+        *
+        * The returned string will be a set of JS instructions defining the configuration that will be provided to the plugin(s)
+        * Each of the instructions should be of the form:
+        *      RTEarea['.$RTEcounter.']["buttons"]["button-id"]["property"] = "value";
+        */
+       public function buildJavascriptConfiguration($RTEcounter) {
+               $registerRTEinJavascriptString = '';
+               return $registerRTEinJavascriptString;
+       }
+
+} // end of class
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/rtehtmlarea/extensions/ContextMenu/class.tx_rtehtmlarea_undoredo.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/rtehtmlarea/extensions/ContextMenu/class.tx_rtehtmlarea_undoredo.php']);
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/htmlarea.css b/typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/htmlarea.css
new file mode 100644 (file)
index 0000000..eeb52aa
--- /dev/null
@@ -0,0 +1,4 @@
+/* Selectors for the Undo Redo plugin of htmlArea RTE */
+/* TYPO3 SVN ID: $Id$ */
+.htmlarea .toolbar .Undo {background-image:url("images/undo.gif");}
+.htmlarea .toolbar .Redo {background-image:url("images/redo.gif");}
diff --git a/typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/images/redo.gif b/typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/images/redo.gif
new file mode 100644 (file)
index 0000000..2820ef1
Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/images/redo.gif differ
diff --git a/typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/images/undo.gif b/typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/images/undo.gif
new file mode 100644 (file)
index 0000000..9bf5025
Binary files /dev/null and b/typo3/sysext/rtehtmlarea/extensions/UndoRedo/skin/images/undo.gif differ
index f7a9ecd..4b20df7 100644 (file)
@@ -66,9 +66,6 @@ var HTMLArea = function(textarea, config) {
                this._editMode = "wysiwyg";
                this.plugins = {};
                this._timerToolbar = null;
-               this._undoQueue = new Array();
-               this._undoPos = -1;
-               this._customUndo = true;
                this.doctype = '';
                this.eventHandlers = {};
        }
@@ -258,10 +255,6 @@ HTMLArea.Config = function () {
        this.height = "auto";
                // enable creation of a status bar?
        this.statusBar = true;
-               // maximum size of the undo queue
-       this.undoSteps = 20;
-               // the time interval at which undo samples are taken: 1/2 sec.
-       this.undoTimeout = 500;
                // whether the toolbar should be included in the size or not.
        this.sizeIncludesToolbar = true;
                // if true then HTMLArea will retrieve the full HTML, starting with the <HTML> tag.
@@ -297,8 +290,6 @@ HTMLArea.Config = function () {
                InsertHorizontalRule:   ["Horizontal Rule", "ed_hr.gif",false, function(editor) {editor.execCommand("InsertHorizontalRule");}],
                HtmlMode:               ["Toggle HTML Source", "ed_html.gif", true, function(editor) {editor.execCommand("HtmlMode");}],
                SelectAll:              ["SelectAll", "", true, function(editor) {editor.execCommand("SelectAll");}, null, true, false],
-               Undo:                   ["Undo the last action", "ed_undo.gif", false, function(editor) {editor.execCommand("Undo");}],
-               Redo:                   ["Redo the last action", "ed_redo.gif", false, function(editor) {editor.execCommand("Redo");}],
                Cut:                    ["Cut selection", "ed_cut.gif", false, function(editor) {editor.execCommand("Cut");}],
                Copy:                   ["Copy selection", "ed_copy.gif", false, function(editor) {editor.execCommand("Copy");}],
                Paste:                  ["Paste from clipboard", "ed_paste.gif", false, function(editor) {editor.execCommand("Paste");}]
@@ -306,9 +297,7 @@ HTMLArea.Config = function () {
                // Default hotkeys
        this.hotKeyList = {
                a:      { cmd:  "SelectAll",    action: null},
-               v:      { cmd:  "Paste",        action: null},
-               z:      { cmd:  "Undo",         action: null},
-               y:      { cmd:  "Redo",         action: null}
+               v:      { cmd:  "Paste",        action: null}
        };
 
                // Initialize tooltips from the I18N module, generate correct image path
@@ -1120,9 +1109,6 @@ HTMLArea.prototype.stylesLoaded = function() {
        doc._editorNo = this._editorNumber;
        if (HTMLArea.is_ie) doc.documentElement._editorNo = this._editorNumber;
 
-               // Start undo snapshots
-       if (this._customUndo) this._timerUndo = window.setInterval("HTMLArea.undoTakeSnapshot(" + this._editorNumber + ");", this.config.undoTimeout);
-
                // intercept events for updating the toolbar & for keyboard handlers
        HTMLArea._addEvents((HTMLArea.is_ie ? doc.body : doc), ["keydown","keypress","mousedown","mouseup","drag"], HTMLArea._editorEvent, true);
 
@@ -1198,9 +1184,6 @@ HTMLArea.removeEditorEvents = function(ev) {
                        RTEarea[editorNumber].editor = null;
                                // save the HTML content into the original textarea for submit, back/forward, etc.
                        editor._textArea.value = editor.getHTML();
-                               // release undo/redo snapshots
-                       window.clearInterval(editor._timerUndo);
-                       editor._undoQueue = null;
                                // do final cleanup
                        HTMLArea.cleanup(editor);
                }
@@ -1241,10 +1224,14 @@ HTMLArea.cleanup = function (editor) {
        }
        HTMLArea.onload = null;
 
-               // cleaning plugin handlers
+               // cleaning plugins
        for (var plugin in editor.plugins) {
                if (editor.plugins.hasOwnProperty(plugin)) {
                        var pluginInstance = editor.plugins[plugin].instance;
+                       if (typeof(pluginInstance.onClose) === "function") {
+                               pluginInstance.onClose();
+                       }
+                       pluginInstance.onClose = null;
                        pluginInstance.onChange = null;
                        pluginInstance.onButtonPress = null;
                        pluginInstance.onGenerate = null;
@@ -1528,117 +1515,6 @@ HTMLArea.prototype.focusEditor = function() {
        return this._doc;
 };
 
-HTMLArea.undoTakeSnapshot = function(editorNumber) {
-       var editor = RTEarea[editorNumber].editor;
-       if (editor._doc) {
-               editor._undoTakeSnapshot();
-       }
-};
-
-/*
- * Take a snapshot of the current contents for undo
- */
-HTMLArea.prototype._undoTakeSnapshot = function () {
-       var currentTime = (new Date()).getTime();
-       var newSnapshot = false;
-       if (this._undoPos >= this.config.undoSteps) {
-                       // Remove the first element
-               this._undoQueue.shift();
-               --this._undoPos;
-       }
-               // New undo slot should be used if this is first undoTakeSnapshot call or if undoTimeout is elapsed
-       if (this._undoPos < 0 || this._undoQueue[this._undoPos].time < currentTime - this.config.undoTimeout) {
-               ++this._undoPos;
-               newSnapshot = true;
-       }
-               // Get the html text
-       var txt = this.getInnerHTML();
-       
-       if (newSnapshot) {
-                       // If previous slot contains the same text, a new one should not be used
-               if (this._undoPos == 0  || this._undoQueue[this._undoPos - 1].text != txt) {
-                       this._undoQueue[this._undoPos] = this.buildUndoSnapshot();
-                       this._undoQueue[this._undoPos].time = currentTime;
-                       this._undoQueue.length = this._undoPos + 1;
-                       if (this._undoPos == 1) {
-                               this.updateToolbar();
-                       }
-               } else {
-                       this._undoPos--;
-               }
-       } else {
-               if (this._undoQueue[this._undoPos].text != txt){
-                       var snapshot = this.buildUndoSnapshot();
-                       this._undoQueue[this._undoPos].text = snapshot.txt;
-                       this._undoQueue[this._undoPos].bookmark = snapshot.bookmark;
-                       this._undoQueue[this._undoPos].bookmarkedText = snapshot.bookmarkedText;
-                       this._undoQueue.length = this._undoPos + 1;
-               }
-       }
-};
-
-HTMLArea.prototype.buildUndoSnapshot = function () {
-       var text, bookmark = null, bookmarkedText = null;
-                       // Insert a bookmark
-       if (this.getMode() === "wysiwyg" && this.isEditable()) {
-               var selection = this._getSelection();
-               if ((HTMLArea.is_gecko && !HTMLArea.is_opera) || (HTMLArea.is_ie && selection.type.toLowerCase() != "control")) {
-                       try { // catch error in FF when the selection contains no usable range
-                               bookmark = this.getBookmark(this._createRange(selection));
-                       } catch (e) {
-                               bookmark = null;
-                       }
-               }
-       }
-               // Get the bookmarked html text and remove the bookmark
-       if (bookmark) {
-               bookmarkedText = this.getInnerHTML();
-               this.moveToBookmark(bookmark);
-       }
-               // Get the html text
-       var text = this.getInnerHTML();
-       return {
-               text            : text,
-               bookmark        : bookmark,
-               bookmarkedText  : bookmarkedText
-       };
-};
-
-HTMLArea.prototype.undo = function () {
-       if (this._undoPos > 0) {
-                       // Make sure we would not loose any changes
-               this._undoTakeSnapshot();
-               var bookmark = this._undoQueue[--this._undoPos].bookmark;
-               if (bookmark && this._undoPos) {
-                       this.setHTML(this._undoQueue[this._undoPos].bookmarkedText);
-                       this.focusEditor();
-                       this.selectRange(this.moveToBookmark(bookmark));
-                       this.scrollToCaret();
-               } else {
-                       this.setHTML(this._undoQueue[this._undoPos].text);
-               }
-       }
-};
-
-HTMLArea.prototype.redo = function () {
-       if (this._undoPos < this._undoQueue.length - 1) {
-                       // Make sure we would not loose any changes
-               this._undoTakeSnapshot();
-                       // Previous call could make undo queue shorter
-               if (this._undoPos < this._undoQueue.length - 1) {
-                       var bookmark = this._undoQueue[++this._undoPos].bookmark;
-                       if (bookmark) {
-                               this.setHTML(this._undoQueue[this._undoPos].bookmarkedText);
-                               this.focusEditor();
-                               this.selectRange(this.moveToBookmark(bookmark));
-                               this.scrollToCaret();
-                       } else {
-                               this.setHTML(this._undoQueue[this._undoPos].text);
-                       }
-               }
-       }
-};
-
 /*
  * Update the enabled/disabled/active state of the toolbar elements
  */
@@ -1780,21 +1656,12 @@ HTMLArea.prototype.updateToolbar = function(noStatus) {
                                                }
                                        }
                                        break;
-                               case "Undo":
-                                       btn.state("enabled", !text && (!this._customUndo || this._undoPos > 0));
-                                       break;
-                               case "Redo":
-                                       btn.state("enabled", !text && (!this._customUndo || this._undoPos < this._undoQueue.length-1));
-                                       break;
                                default:
                                        break;
                        }
                }
        }
-
-       if (this._customUndo) {
-               this._undoTakeSnapshot();
-       }
+       
        for (var pluginId in this.plugins) {
                if (this.plugins.hasOwnProperty(pluginId)) {
                        var pluginInstance = this.plugins[pluginId].instance;
@@ -1935,11 +1802,6 @@ HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
                case "HtmlMode" :
                        this.setMode();
                        break;
-               case "Undo"     :
-               case "Redo"     :
-                       if(this._customUndo) this[cmdID.toLowerCase()]();
-                               else this._doc.execCommand(cmdID,UI,param);
-                       break;
                case "Cut"      :
                case "Copy"     :
                case "Paste"    :
@@ -2010,8 +1872,6 @@ HTMLArea._editorEvent = function(ev) {
                                if (!cmd) return true;
                                switch (cmd) {
                                        case "SelectAll":
-                                       case "Undo"     :
-                                       case "Redo"     :
                                                cmd = editor.config.hotKeyList[key].cmd;
                                                editor.execCommand(cmd, false, null);
                                                HTMLArea._stopEvent(ev);
diff --git a/typo3/sysext/rtehtmlarea/htmlarea/plugins/UndoRedo/locallang.xml b/typo3/sysext/rtehtmlarea/htmlarea/plugins/UndoRedo/locallang.xml
new file mode 100644 (file)
index 0000000..fa3cc4f
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<!-- TYPO3 SVN ID: $Id: locallang.xml 2985 2008-01-31 11:37:57Z ingmars $ -->
+<T3locallang>
+       <meta type="array">
+               <description>Labels for Undo Redo plugin of htmlArea RTE</description>
+               <type>module</type>
+       </meta>
+       <data type="array">
+               <languageKey index="default" type="array">
+               </languageKey>
+       </data>
+       <orig_hash type="array">
+               <languageKey index="default" type="array">
+               </languageKey>
+       </orig_hash>
+       <orig_text type="array">
+               <languageKey index="default" type="array">
+               </languageKey>
+       </orig_text>
+</T3locallang>
\ No newline at end of file
diff --git a/typo3/sysext/rtehtmlarea/htmlarea/plugins/UndoRedo/undo-redo.js b/typo3/sysext/rtehtmlarea/htmlarea/plugins/UndoRedo/undo-redo.js
new file mode 100644 (file)
index 0000000..1a3372a
--- /dev/null
@@ -0,0 +1,293 @@
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2008 Stanislas Rolland <typo3(arobas)sjbr.ca>
+*  All rights reserved
+*
+*  This script is part of the TYPO3 project. The TYPO3 project is
+*  free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 2 of the License, or
+*  (at your option) any later version.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*  A copy is found in the textfile GPL.txt and important notices to the license
+*  from the author is found in LICENSE.txt distributed with these scripts.
+*
+*
+*  This script is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  This script is a modified version of a script published under the htmlArea License.
+*  A copy of the htmlArea License may be found in the textfile HTMLAREA_LICENSE.txt.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+/*
+ * Undo Redo Plugin for TYPO3 htmlArea RTE
+ *
+ * TYPO3 SVN ID: $Id: find-replace.js 3437 2008-03-16 16:22:11Z flyguide $
+ */
+UndoRedo = HTMLArea.Plugin.extend({
+       
+       constructor : function (editor, pluginName) {
+               this.base(editor, pluginName);
+       },
+       
+       /*
+        * This function gets called by the class constructor
+        */
+       configurePlugin : function (editor) {
+               
+               this.pageTSconfiguration = this.editorConfiguration.buttons.undo;
+               this.customUndo = true;
+               this.undoQueue = new Array();
+               this.undoPosition = -1;
+                       // Maximum size of the undo queue
+               this.undoSteps = 25;
+                       // The time interval at which undo samples are taken: 1/2 sec.
+               this.undoTimeout = 500;
+               this.undoTimer;
+               
+               /*
+                * Registering plugin "About" information
+                */
+               var pluginInformation = {
+                       version         : "1.0",
+                       developer       : "Stanislas Rolland",
+                       developerUrl    : "http://www.sjbr.ca",
+                       copyrightOwner  : "Stanislas Rolland",
+                       sponsor         : "SJBR",
+                       sponsorUrl      : "http://www.sjbr.ca",
+                       license         : "GPL"
+               };
+               this.registerPluginInformation(pluginInformation);
+               
+               /*
+                * Registering the buttons
+                */
+               var buttonList = this.buttonList, buttonId;
+               for (var i = 0; i < buttonList.length; ++i) {
+                       var button = buttonList[i];
+                       buttonId = button[0];
+                       var buttonConfiguration = {
+                               id              : buttonId,
+                               tooltip         : this.localize(buttonId.toLowerCase()),
+                               action          : "onButtonPress",
+                               context         : button[1],
+                               hotKey          : (this.editorConfiguration.buttons[buttonId.toLowerCase()]?this.editorConfiguration.buttons[buttonId.toLowerCase()].hotKey:button[2])
+                       };
+                       this.registerButton(buttonConfiguration);
+               }
+               
+               return true;
+       },
+       
+       /*
+        * The list of buttons added by this plugin
+        */
+       buttonList : [
+               ["Undo", null, "z"],
+               ["Redo", null, "y"]
+       ],
+       
+       /*
+        * This function gets called when the editor is generated
+        */
+       onGenerate : function () {
+                       // Start undo snapshots
+               if (this.customUndo) {
+                       var takeSnapshotFunctRef = this.makeFunctionReference("takeSnapshot");
+                       this.undoTimer = window.setInterval(takeSnapshotFunctRef, this.undoTimeout);
+               }
+       },
+       
+       /*
+        * This function gets called when the editor is closing
+        */
+       onClose : function () {
+                       // Clear snapshot interval
+               window.clearInterval(this.undoTimer);
+                       // Release undo/redo snapshots
+               this.undoQueue = null;
+       },
+       
+       /*
+        * Take a snapshot of the current contents for undo
+        */
+       takeSnapshot : function () {
+               var currentTime = (new Date()).getTime();
+               var newSnapshot = false;
+               if (this.undoPosition >= this.undoSteps) {
+                               // Remove the first element
+                       this.undoQueue.shift();
+                       --this.undoPosition;
+               }
+                       // New undo slot should be used if this is first takeSnapshot call or if undoTimeout is elapsed
+               if (this.undoPosition < 0 || this.undoQueue[this.undoPosition].time < currentTime - this.undoTimeout) {
+                       ++this.undoPosition;
+                       newSnapshot = true;
+               }
+                       // Get the html text
+               var text = this.editor.getInnerHTML();
+               
+               if (newSnapshot) {
+                               // If previous slot contains the same text, a new one should not be used
+                       if (this.undoPosition == 0  || this.undoQueue[this.undoPosition - 1].text != text) {
+                               this.undoQueue[this.undoPosition] = this.buildSnapshot();
+                               this.undoQueue[this.undoPosition].time = currentTime;
+                               this.undoQueue.length = this.undoPosition + 1;
+                               this.updateButtonsState();
+                       } else {
+                               --this.undoPosition;
+                       }
+               } else {
+                       if (this.undoQueue[this.undoPosition].text != text){
+                               var snapshot = this.buildSnapshot();
+                               this.undoQueue[this.undoPosition].text = snapshot.text;
+                               this.undoQueue[this.undoPosition].bookmark = snapshot.bookmark;
+                               this.undoQueue[this.undoPosition].bookmarkedText = snapshot.bookmarkedText;
+                               this.undoQueue.length = this.undoPosition + 1;
+                       }
+               }
+       },
+       
+       /*
+        * Build the snapshot entry
+        *
+        * @return      object  a snapshot entry with three components:
+        *                              - text (the content of the RTE without any bookmark),
+        *                              - bookmark (the bookmark),
+        *                              - bookmarkedText (the content of the RTE including the bookmark)
+        */
+       buildSnapshot : function () {
+               var bookmark = null, bookmarkedText = null;
+                       // Insert a bookmark
+               if (this.editor.getMode() == "wysiwyg" && this.editor.isEditable()) {
+                       var selection = this.editor._getSelection();
+                       if ((HTMLArea.is_gecko && !HTMLArea.is_opera) || (HTMLArea.is_ie && selection.type.toLowerCase() != "control")) {
+                               try { // catch error in FF when the selection contains no usable range
+                                       bookmark = this.editor.getBookmark(this.editor._createRange(selection));
+                               } catch (e) {
+                                       bookmark = null;
+                               }
+                       }
+               }
+                       // Get the bookmarked html text and remove the bookmark
+               if (bookmark) {
+                       bookmarkedText = this.editor.getInnerHTML();
+                       this.editor.moveToBookmark(bookmark);
+               }
+               return {
+                       text            : this.editor.getInnerHTML(),
+                       bookmark        : bookmark,
+                       bookmarkedText  : bookmarkedText
+               };
+       },
+       
+       /*
+        * Execute the undo request
+        */
+       undo : function () {
+               if (this.undoPosition > 0) {
+                               // Make sure we would not loose any changes
+                       this.takeSnapshot();
+                       this.setContent(--this.undoPosition);
+                       this.updateButtonsState();
+               }
+       },
+       
+       /*
+        * Execute the redo request
+        */
+       redo : function () {
+               if (this.undoPosition < this.undoQueue.length - 1) {
+                               // Make sure we would not loose any changes
+                       this.takeSnapshot();
+                               // Previous call could make undo queue shorter
+                       if (this.undoPosition < this.undoQueue.length - 1) {
+                               this.setContent(++this.undoPosition);
+                               this.updateButtonsState();
+                       }
+               }
+       },
+       
+       /*
+        * Set content using undo queue position
+        */
+       setContent : function (undoPosition) {
+               var bookmark = this.undoQueue[undoPosition].bookmark;
+               if (bookmark) {
+                       this.editor.setHTML(this.undoQueue[undoPosition].bookmarkedText);
+                       this.editor.focusEditor();
+                       this.editor.selectRange(this.editor.moveToBookmark(bookmark));
+                       this.editor.scrollToCaret();
+               } else {
+                       this.editor.setHTML(this.undoQueue[undoPosition].text);
+               }
+       },
+       
+       /*
+        * This function gets called when the toolbar is updated
+        */
+       onUpdateToolbar : function () {
+               this.updateButtonsState();
+       },
+       
+       /*
+        * Update the state of the undo/redo buttons
+        */
+       updateButtonsState : function () {
+               if (this.editor.getMode() == "wysiwyg" && this.editor.isEditable()) {
+                       if (this.customUndo) {
+                               if (this.isButtonInToolbar("Undo")) {
+                                       this.editor._toolbarObjects.Undo.state("enabled", this.undoPosition > 0);
+                               }
+                               if (this.isButtonInToolbar("Redo")) {
+                                       this.editor._toolbarObjects.Redo.state("enabled", this.undoPosition < this.undoQueue.length-1);
+                               }
+                       } else {
+                               try {
+                                       if (this.isButtonInToolbar("Undo")) {
+                                               this.editor._toolbarObjects.Undo.state("enabled", this.editor._doc.queryCommandEnabled("Undo"));
+                                       }
+                                       if (this.isButtonInToolbar("Redo")) {
+                                               this.editor._toolbarObjects.Redo.state("enabled", this.editor._doc.queryCommandEnabled("Redo"));
+                                       }
+                               } catch (e) {
+                                       if (this.isButtonInToolbar("Undo")) {
+                                               this.editor._toolbarObjects.Undo.state("enabled", false);
+                                       }
+                                       if (this.isButtonInToolbar("Redo")) {
+                                               this.editor._toolbarObjects.Redo.state("enabled", false);
+                                       }
+                               }
+                       }
+               }
+       },
+       
+       /*
+        * This function gets called when the button was pressed.
+        *
+        * @param       object          editor: the editor instance
+        * @param       string          id: the button id or the key
+        *
+        * @return      boolean         false if action is completed
+        */
+       onButtonPress : function (editor, id) {
+                       // Could be a button or its hotkey
+               var buttonId = this.translateHotKey(id);
+               buttonId = buttonId ? buttonId : id;
+               if (this.isButtonInToolbar(buttonId) && !this.editor._toolbarObjects[buttonId].disabled) {
+                       if (this.customUndo) {
+                               this[buttonId.toLowerCase()]();
+                       } else {
+                               this.editor._doc.execCommand(buttonId, false, null);
+                       }
+               }
+               return false;
+       }
+});