Upgrade TinyMCE to v3.4.5
[citadel.git] / webcit / tiny_mce / plugins / autosave / editor_plugin_src.js
index d75d5d5a2128bbebfe03ef5c7d36cf51a09343eb..8311483f9413b21347663de54453843b30336d4b 100644 (file)
-/*\r
- * Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved.\r
+/**\r
+ * editor_plugin_src.js\r
+ *\r
+ * Copyright 2009, Moxiecode Systems AB\r
+ * Released under LGPL License.\r
+ *\r
+ * License: http://tinymce.moxiecode.com/license\r
+ * Contributing: http://tinymce.moxiecode.com/contributing\r
+ *\r
+ * Adds auto-save capability to the TinyMCE text editor to rescue content\r
+ * inadvertently lost. This plugin was originally developed by Speednet\r
+ * and that project can be found here: http://code.google.com/p/tinyautosave/\r
+ *\r
+ * TECHNOLOGY DISCUSSION:\r
+ * \r
+ * The plugin attempts to use the most advanced features available in the current browser to save\r
+ * as much content as possible.  There are a total of four different methods used to autosave the\r
+ * content.  In order of preference, they are:\r
+ * \r
+ * 1. localStorage - A new feature of HTML 5, localStorage can store megabytes of data per domain\r
+ * on the client computer. Data stored in the localStorage area has no expiration date, so we must\r
+ * manage expiring the data ourselves.  localStorage is fully supported by IE8, and it is supposed\r
+ * to be working in Firefox 3 and Safari 3.2, but in reality is is flaky in those browsers.  As\r
+ * HTML 5 gets wider support, the AutoSave plugin will use it automatically. In Windows Vista/7,\r
+ * localStorage is stored in the following folder:\r
+ * C:\Users\[username]\AppData\Local\Microsoft\Internet Explorer\DOMStore\[tempFolder]\r
+ * \r
+ * 2. sessionStorage - A new feature of HTML 5, sessionStorage works similarly to localStorage,\r
+ * except it is designed to expire after a certain amount of time.  Because the specification\r
+ * around expiration date/time is very loosely-described, it is preferrable to use locaStorage and\r
+ * manage the expiration ourselves.  sessionStorage has similar storage characteristics to\r
+ * localStorage, although it seems to have better support by Firefox 3 at the moment.  (That will\r
+ * certainly change as Firefox continues getting better at HTML 5 adoption.)\r
+ * \r
+ * 3. UserData - A very under-exploited feature of Microsoft Internet Explorer, UserData is a\r
+ * way to store up to 128K of data per "document", or up to 1MB of data per domain, on the client\r
+ * computer.  The feature is available for IE 5+, which makes it available for every version of IE\r
+ * supported by TinyMCE.  The content is persistent across browser restarts and expires on the\r
+ * date/time specified, just like a cookie.  However, the data is not cleared when the user clears\r
+ * cookies on the browser, which makes it well-suited for rescuing autosaved content.  UserData,\r
+ * like other Microsoft IE browser technologies, is implemented as a behavior attached to a\r
+ * specific DOM object, so in this case we attach the behavior to the same DOM element that the\r
+ * TinyMCE editor instance is attached to.\r
  */\r
 \r
-(function() {\r
-       tinymce.create('tinymce.plugins.AutoSavePlugin', {\r
+(function(tinymce) {\r
+       // Setup constants to help the compressor to reduce script size\r
+       var PLUGIN_NAME = 'autosave',\r
+               RESTORE_DRAFT = 'restoredraft',\r
+               TRUE = true,\r
+               undefined,\r
+               unloadHandlerAdded,\r
+               Dispatcher = tinymce.util.Dispatcher;\r
+\r
+       /**\r
+        * This plugin adds auto-save capability to the TinyMCE text editor to rescue content\r
+        * inadvertently lost. By using localStorage.\r
+        *\r
+        * @class tinymce.plugins.AutoSave\r
+        */\r
+       tinymce.create('tinymce.plugins.AutoSave', {\r
+               /**\r
+                * Initializes the plugin, this will be executed after the plugin has been created.\r
+                * This call is done before the editor instance has finished it's initialization so use the onInit event\r
+                * of the editor instance to intercept that event.\r
+                *\r
+                * @method init\r
+                * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.\r
+                * @param {string} url Absolute URL to where the plugin is located.\r
+                */\r
                init : function(ed, url) {\r
-                       var t = this;\r
+                       var self = this, settings = ed.settings;\r
+\r
+                       self.editor = ed;\r
+\r
+                       // Parses the specified time string into a milisecond number 10m, 10s etc.\r
+                       function parseTime(time) {\r
+                               var multipels = {\r
+                                       s : 1000,\r
+                                       m : 60000\r
+                               };\r
+\r
+                               time = /^(\d+)([ms]?)$/.exec('' + time);\r
+\r
+                               return (time[2] ? multipels[time[2]] : 1) * parseInt(time);\r
+                       };\r
+\r
+                       // Default config\r
+                       tinymce.each({\r
+                               ask_before_unload : TRUE,\r
+                               interval : '30s',\r
+                               retention : '20m',\r
+                               minlength : 50\r
+                       }, function(value, key) {\r
+                               key = PLUGIN_NAME + '_' + key;\r
+\r
+                               if (settings[key] === undefined)\r
+                                       settings[key] = value;\r
+                       });\r
+\r
+                       // Parse times\r
+                       settings.autosave_interval = parseTime(settings.autosave_interval);\r
+                       settings.autosave_retention = parseTime(settings.autosave_retention);\r
 \r
-                       t.editor = ed;\r
+                       // Register restore button\r
+                       ed.addButton(RESTORE_DRAFT, {\r
+                               title : PLUGIN_NAME + ".restore_content",\r
+                               onclick : function() {\r
+                                       if (ed.getContent({draft: true}).replace(/\s|&nbsp;|<\/?p[^>]*>|<br[^>]*>/gi, "").length > 0) {\r
+                                               // Show confirm dialog if the editor isn't empty\r
+                                               ed.windowManager.confirm(\r
+                                                       PLUGIN_NAME + ".warning_message",\r
+                                                       function(ok) {\r
+                                                               if (ok)\r
+                                                                       self.restoreDraft();\r
+                                                       }\r
+                                               );\r
+                                       } else\r
+                                               self.restoreDraft();\r
+                               }\r
+                       });\r
 \r
-                       window.onbeforeunload = tinymce.plugins.AutoSavePlugin._beforeUnloadHandler;\r
+                       // Enable/disable restoredraft button depending on if there is a draft stored or not\r
+                       ed.onNodeChange.add(function() {\r
+                               var controlManager = ed.controlManager;\r
+\r
+                               if (controlManager.get(RESTORE_DRAFT))\r
+                                       controlManager.setDisabled(RESTORE_DRAFT, !self.hasDraft());\r
+                       });\r
+\r
+                       ed.onInit.add(function() {\r
+                               // Check if the user added the restore button, then setup auto storage logic\r
+                               if (ed.controlManager.get(RESTORE_DRAFT)) {\r
+                                       // Setup storage engine\r
+                                       self.setupStorage(ed);\r
+\r
+                                       // Auto save contents each interval time\r
+                                       setInterval(function() {\r
+                                               self.storeDraft();\r
+                                               ed.nodeChanged();\r
+                                       }, settings.autosave_interval);\r
+                               }\r
+                       });\r
+\r
+                       /**\r
+                        * This event gets fired when a draft is stored to local storage.\r
+                        *\r
+                        * @event onStoreDraft\r
+                        * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.\r
+                        * @param {Object} draft Draft object containing the HTML contents of the editor.\r
+                        */\r
+                       self.onStoreDraft = new Dispatcher(self);\r
+\r
+                       /**\r
+                        * This event gets fired when a draft is restored from local storage.\r
+                        *\r
+                        * @event onStoreDraft\r
+                        * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.\r
+                        * @param {Object} draft Draft object containing the HTML contents of the editor.\r
+                        */\r
+                       self.onRestoreDraft = new Dispatcher(self);\r
+\r
+                       /**\r
+                        * This event gets fired when a draft removed/expired.\r
+                        *\r
+                        * @event onRemoveDraft\r
+                        * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.\r
+                        * @param {Object} draft Draft object containing the HTML contents of the editor.\r
+                        */\r
+                       self.onRemoveDraft = new Dispatcher(self);\r
+\r
+                       // Add ask before unload dialog only add one unload handler\r
+                       if (!unloadHandlerAdded) {\r
+                               window.onbeforeunload = tinymce.plugins.AutoSave._beforeUnloadHandler;\r
+                               unloadHandlerAdded = TRUE;\r
+                       }\r
                },\r
 \r
+               /**\r
+                * Returns information about the plugin as a name/value array.\r
+                * The current keys are longname, author, authorurl, infourl and version.\r
+                *\r
+                * @method getInfo\r
+                * @return {Object} Name/value array containing information about the plugin.\r
+                */\r
                getInfo : function() {\r
                        return {\r
                                longname : 'Auto save',\r
                        };\r
                },\r
 \r
-               // Private plugin internal methods\r
+               /**\r
+                * Returns an expiration date UTC string.\r
+                *\r
+                * @method getExpDate\r
+                * @return {String} Expiration date UTC string.\r
+                */\r
+               getExpDate : function() {\r
+                       return new Date(\r
+                               new Date().getTime() + this.editor.settings.autosave_retention\r
+                       ).toUTCString();\r
+               },\r
+\r
+               /**\r
+                * This method will setup the storage engine. If the browser has support for it.\r
+                *\r
+                * @method setupStorage\r
+                */\r
+               setupStorage : function(ed) {\r
+                       var self = this, testKey = PLUGIN_NAME + '_test', testVal = "OK";\r
+\r
+                       self.key = PLUGIN_NAME + ed.id;\r
+\r
+                       // Loop though each storage engine type until we find one that works\r
+                       tinymce.each([\r
+                               function() {\r
+                                       // Try HTML5 Local Storage\r
+                                       if (localStorage) {\r
+                                               localStorage.setItem(testKey, testVal);\r
+\r
+                                               if (localStorage.getItem(testKey) === testVal) {\r
+                                                       localStorage.removeItem(testKey);\r
+\r
+                                                       return localStorage;\r
+                                               }\r
+                                       }\r
+                               },\r
+\r
+                               function() {\r
+                                       // Try HTML5 Session Storage\r
+                                       if (sessionStorage) {\r
+                                               sessionStorage.setItem(testKey, testVal);\r
+\r
+                                               if (sessionStorage.getItem(testKey) === testVal) {\r
+                                                       sessionStorage.removeItem(testKey);\r
+\r
+                                                       return sessionStorage;\r
+                                               }\r
+                                       }\r
+                               },\r
+\r
+                               function() {\r
+                                       // Try IE userData\r
+                                       if (tinymce.isIE) {\r
+                                               ed.getElement().style.behavior = "url('#default#userData')";\r
+\r
+                                               // Fake localStorage on old IE\r
+                                               return {\r
+                                                       autoExpires : TRUE,\r
+\r
+                                                       setItem : function(key, value) {\r
+                                                               var userDataElement = ed.getElement();\r
+\r
+                                                               userDataElement.setAttribute(key, value);\r
+                                                               userDataElement.expires = self.getExpDate();\r
+\r
+                                                               try {\r
+                                                                       userDataElement.save("TinyMCE");\r
+                                                               } catch (e) {\r
+                                                                       // Ignore, saving might fail if "Userdata Persistence" is disabled in IE\r
+                                                               }\r
+                                                       },\r
+\r
+                                                       getItem : function(key) {\r
+                                                               var userDataElement = ed.getElement();\r
 \r
-               'static' : {\r
-                       _beforeUnloadHandler : function() {\r
+                                                               try {\r
+                                                                       userDataElement.load("TinyMCE");\r
+                                                                       return userDataElement.getAttribute(key);\r
+                                                               } catch (e) {\r
+                                                                       // Ignore, loading might fail if "Userdata Persistence" is disabled in IE\r
+                                                                       return null;\r
+                                                               }\r
+                                                       },\r
+\r
+                                                       removeItem : function(key) {\r
+                                                               ed.getElement().removeAttribute(key);\r
+                                                       }\r
+                                               };\r
+                                       }\r
+                               },\r
+                       ], function(setup) {\r
+                               // Try executing each function to find a suitable storage engine\r
+                               try {\r
+                                       self.storage = setup();\r
+\r
+                                       if (self.storage)\r
+                                               return false;\r
+                               } catch (e) {\r
+                                       // Ignore\r
+                               }\r
+                       });\r
+               },\r
+\r
+               /**\r
+                * This method will store the current contents in the the storage engine.\r
+                *\r
+                * @method storeDraft\r
+                */\r
+               storeDraft : function() {\r
+                       var self = this, storage = self.storage, editor = self.editor, expires, content;\r
+\r
+                       // Is the contents dirty\r
+                       if (storage) {\r
+                               // If there is no existing key and the contents hasn't been changed since\r
+                               // it's original value then there is no point in saving a draft\r
+                               if (!storage.getItem(self.key) && !editor.isDirty())\r
+                                       return;\r
+\r
+                               // Store contents if the contents if longer than the minlength of characters\r
+                               content = editor.getContent({draft: true});\r
+                               if (content.length > editor.settings.autosave_minlength) {\r
+                                       expires = self.getExpDate();\r
+\r
+                                       // Store expiration date if needed IE userData has auto expire built in\r
+                                       if (!self.storage.autoExpires)\r
+                                               self.storage.setItem(self.key + "_expires", expires);\r
+\r
+                                       self.storage.setItem(self.key, content);\r
+                                       self.onStoreDraft.dispatch(self, {\r
+                                               expires : expires,\r
+                                               content : content\r
+                                       });\r
+                               }\r
+                       }\r
+               },\r
+\r
+               /**\r
+                * This method will restore the contents from the storage engine back to the editor.\r
+                *\r
+                * @method restoreDraft\r
+                */\r
+               restoreDraft : function() {\r
+                       var self = this, storage = self.storage, content;\r
+\r
+                       if (storage) {\r
+                               content = storage.getItem(self.key);\r
+\r
+                               if (content) {\r
+                                       self.editor.setContent(content);\r
+                                       self.onRestoreDraft.dispatch(self, {\r
+                                               content : content\r
+                                       });\r
+                               }\r
+                       }\r
+               },\r
+\r
+               /**\r
+                * This method will return true/false if there is a local storage draft available.\r
+                *\r
+                * @method hasDraft\r
+                * @return {boolean} true/false state if there is a local draft.\r
+                */\r
+               hasDraft : function() {\r
+                       var self = this, storage = self.storage, expDate, exists;\r
+\r
+                       if (storage) {\r
+                               // Does the item exist at all\r
+                               exists = !!storage.getItem(self.key);\r
+                               if (exists) {\r
+                                       // Storage needs autoexpire\r
+                                       if (!self.storage.autoExpires) {\r
+                                               expDate = new Date(storage.getItem(self.key + "_expires"));\r
+\r
+                                               // Contents hasn't expired\r
+                                               if (new Date().getTime() < expDate.getTime())\r
+                                                       return TRUE;\r
+\r
+                                               // Remove it if it has\r
+                                               self.removeDraft();\r
+                                       } else\r
+                                               return TRUE;\r
+                               }\r
+                       }\r
+\r
+                       return false;\r
+               },\r
+\r
+               /**\r
+                * Removes the currently stored draft.\r
+                *\r
+                * @method removeDraft\r
+                */\r
+               removeDraft : function() {\r
+                       var self = this, storage = self.storage, key = self.key, content;\r
+\r
+                       if (storage) {\r
+                               // Get current contents and remove the existing draft\r
+                               content = storage.getItem(key);\r
+                               storage.removeItem(key);\r
+                               storage.removeItem(key + "_expires");\r
+\r
+                               // Dispatch remove event if we had any contents\r
+                               if (content) {\r
+                                       self.onRemoveDraft.dispatch(self, {\r
+                                               content : content\r
+                                       });\r
+                               }\r
+                       }\r
+               },\r
+\r
+               "static" : {\r
+                       // Internal unload handler will be called before the page is unloaded\r
+                       _beforeUnloadHandler : function(e) {\r
                                var msg;\r
 \r
                                tinymce.each(tinyMCE.editors, function(ed) {\r
+                                       // Store a draft for each editor instance\r
+                                       if (ed.plugins.autosave)\r
+                                               ed.plugins.autosave.storeDraft();\r
+\r
+                                       // Never ask in fullscreen mode\r
                                        if (ed.getParam("fullscreen_is_enabled"))\r
                                                return;\r
 \r
-                                       if (ed.isDirty()) {\r
+                                       // Setup a return message if the editor is dirty\r
+                                       if (!msg && ed.isDirty() && ed.getParam("autosave_ask_before_unload"))\r
                                                msg = ed.getLang("autosave.unload_msg");\r
-                                               return false;\r
-                                       }\r
                                });\r
 \r
                                return msg;\r
                }\r
        });\r
 \r
-       // Register plugin\r
-       tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSavePlugin);\r
-})();\r
+       tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSave);\r
+})(tinymce);\r