2 * editor_plugin_src.js
\r
4 * Copyright 2009, Moxiecode Systems AB
\r
5 * Released under LGPL License.
\r
7 * License: http://tinymce.moxiecode.com/license
\r
8 * Contributing: http://tinymce.moxiecode.com/contributing
\r
10 * Adds auto-save capability to the TinyMCE text editor to rescue content
\r
11 * inadvertently lost. This plugin was originally developed by Speednet
\r
12 * and that project can be found here: http://code.google.com/p/tinyautosave/
\r
14 * TECHNOLOGY DISCUSSION:
\r
16 * The plugin attempts to use the most advanced features available in the current browser to save
\r
17 * as much content as possible. There are a total of four different methods used to autosave the
\r
18 * content. In order of preference, they are:
\r
20 * 1. localStorage - A new feature of HTML 5, localStorage can store megabytes of data per domain
\r
21 * on the client computer. Data stored in the localStorage area has no expiration date, so we must
\r
22 * manage expiring the data ourselves. localStorage is fully supported by IE8, and it is supposed
\r
23 * to be working in Firefox 3 and Safari 3.2, but in reality is is flaky in those browsers. As
\r
24 * HTML 5 gets wider support, the AutoSave plugin will use it automatically. In Windows Vista/7,
\r
25 * localStorage is stored in the following folder:
\r
26 * C:\Users\[username]\AppData\Local\Microsoft\Internet Explorer\DOMStore\[tempFolder]
\r
28 * 2. sessionStorage - A new feature of HTML 5, sessionStorage works similarly to localStorage,
\r
29 * except it is designed to expire after a certain amount of time. Because the specification
\r
30 * around expiration date/time is very loosely-described, it is preferrable to use locaStorage and
\r
31 * manage the expiration ourselves. sessionStorage has similar storage characteristics to
\r
32 * localStorage, although it seems to have better support by Firefox 3 at the moment. (That will
\r
33 * certainly change as Firefox continues getting better at HTML 5 adoption.)
\r
35 * 3. UserData - A very under-exploited feature of Microsoft Internet Explorer, UserData is a
\r
36 * way to store up to 128K of data per "document", or up to 1MB of data per domain, on the client
\r
37 * computer. The feature is available for IE 5+, which makes it available for every version of IE
\r
38 * supported by TinyMCE. The content is persistent across browser restarts and expires on the
\r
39 * date/time specified, just like a cookie. However, the data is not cleared when the user clears
\r
40 * cookies on the browser, which makes it well-suited for rescuing autosaved content. UserData,
\r
41 * like other Microsoft IE browser technologies, is implemented as a behavior attached to a
\r
42 * specific DOM object, so in this case we attach the behavior to the same DOM element that the
\r
43 * TinyMCE editor instance is attached to.
\r
46 (function(tinymce) {
\r
47 // Setup constants to help the compressor to reduce script size
\r
48 var PLUGIN_NAME = 'autosave',
\r
49 RESTORE_DRAFT = 'restoredraft',
\r
53 Dispatcher = tinymce.util.Dispatcher;
\r
56 * This plugin adds auto-save capability to the TinyMCE text editor to rescue content
\r
57 * inadvertently lost. By using localStorage.
\r
59 * @class tinymce.plugins.AutoSave
\r
61 tinymce.create('tinymce.plugins.AutoSave', {
\r
63 * Initializes the plugin, this will be executed after the plugin has been created.
\r
64 * This call is done before the editor instance has finished it's initialization so use the onInit event
\r
65 * of the editor instance to intercept that event.
\r
68 * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
\r
69 * @param {string} url Absolute URL to where the plugin is located.
\r
71 init : function(ed, url) {
\r
72 var self = this, settings = ed.settings;
\r
76 // Parses the specified time string into a milisecond number 10m, 10s etc.
\r
77 function parseTime(time) {
\r
83 time = /^(\d+)([ms]?)$/.exec('' + time);
\r
85 return (time[2] ? multipels[time[2]] : 1) * parseInt(time);
\r
90 ask_before_unload : TRUE,
\r
94 }, function(value, key) {
\r
95 key = PLUGIN_NAME + '_' + key;
\r
97 if (settings[key] === undefined)
\r
98 settings[key] = value;
\r
102 settings.autosave_interval = parseTime(settings.autosave_interval);
\r
103 settings.autosave_retention = parseTime(settings.autosave_retention);
\r
105 // Register restore button
\r
106 ed.addButton(RESTORE_DRAFT, {
\r
107 title : PLUGIN_NAME + ".restore_content",
\r
108 onclick : function() {
\r
109 if (ed.getContent({draft: true}).replace(/\s| |<\/?p[^>]*>|<br[^>]*>/gi, "").length > 0) {
\r
110 // Show confirm dialog if the editor isn't empty
\r
111 ed.windowManager.confirm(
\r
112 PLUGIN_NAME + ".warning_message",
\r
115 self.restoreDraft();
\r
119 self.restoreDraft();
\r
123 // Enable/disable restoredraft button depending on if there is a draft stored or not
\r
124 ed.onNodeChange.add(function() {
\r
125 var controlManager = ed.controlManager;
\r
127 if (controlManager.get(RESTORE_DRAFT))
\r
128 controlManager.setDisabled(RESTORE_DRAFT, !self.hasDraft());
\r
131 ed.onInit.add(function() {
\r
132 // Check if the user added the restore button, then setup auto storage logic
\r
133 if (ed.controlManager.get(RESTORE_DRAFT)) {
\r
134 // Setup storage engine
\r
135 self.setupStorage(ed);
\r
137 // Auto save contents each interval time
\r
138 setInterval(function() {
\r
141 }, settings.autosave_interval);
\r
146 * This event gets fired when a draft is stored to local storage.
\r
148 * @event onStoreDraft
\r
149 * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
\r
150 * @param {Object} draft Draft object containing the HTML contents of the editor.
\r
152 self.onStoreDraft = new Dispatcher(self);
\r
155 * This event gets fired when a draft is restored from local storage.
\r
157 * @event onStoreDraft
\r
158 * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
\r
159 * @param {Object} draft Draft object containing the HTML contents of the editor.
\r
161 self.onRestoreDraft = new Dispatcher(self);
\r
164 * This event gets fired when a draft removed/expired.
\r
166 * @event onRemoveDraft
\r
167 * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
\r
168 * @param {Object} draft Draft object containing the HTML contents of the editor.
\r
170 self.onRemoveDraft = new Dispatcher(self);
\r
172 // Add ask before unload dialog only add one unload handler
\r
173 if (!unloadHandlerAdded) {
\r
174 window.onbeforeunload = tinymce.plugins.AutoSave._beforeUnloadHandler;
\r
175 unloadHandlerAdded = TRUE;
\r
180 * Returns information about the plugin as a name/value array.
\r
181 * The current keys are longname, author, authorurl, infourl and version.
\r
184 * @return {Object} Name/value array containing information about the plugin.
\r
186 getInfo : function() {
\r
188 longname : 'Auto save',
\r
189 author : 'Moxiecode Systems AB',
\r
190 authorurl : 'http://tinymce.moxiecode.com',
\r
191 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave',
\r
192 version : tinymce.majorVersion + "." + tinymce.minorVersion
\r
197 * Returns an expiration date UTC string.
\r
199 * @method getExpDate
\r
200 * @return {String} Expiration date UTC string.
\r
202 getExpDate : function() {
\r
204 new Date().getTime() + this.editor.settings.autosave_retention
\r
209 * This method will setup the storage engine. If the browser has support for it.
\r
211 * @method setupStorage
\r
213 setupStorage : function(ed) {
\r
214 var self = this, testKey = PLUGIN_NAME + '_test', testVal = "OK";
\r
216 self.key = PLUGIN_NAME + ed.id;
\r
218 // Loop though each storage engine type until we find one that works
\r
221 // Try HTML5 Local Storage
\r
222 if (localStorage) {
\r
223 localStorage.setItem(testKey, testVal);
\r
225 if (localStorage.getItem(testKey) === testVal) {
\r
226 localStorage.removeItem(testKey);
\r
228 return localStorage;
\r
234 // Try HTML5 Session Storage
\r
235 if (sessionStorage) {
\r
236 sessionStorage.setItem(testKey, testVal);
\r
238 if (sessionStorage.getItem(testKey) === testVal) {
\r
239 sessionStorage.removeItem(testKey);
\r
241 return sessionStorage;
\r
248 if (tinymce.isIE) {
\r
249 ed.getElement().style.behavior = "url('#default#userData')";
\r
251 // Fake localStorage on old IE
\r
253 autoExpires : TRUE,
\r
255 setItem : function(key, value) {
\r
256 var userDataElement = ed.getElement();
\r
258 userDataElement.setAttribute(key, value);
\r
259 userDataElement.expires = self.getExpDate();
\r
260 userDataElement.save("TinyMCE");
\r
263 getItem : function(key) {
\r
264 var userDataElement = ed.getElement();
\r
266 userDataElement.load("TinyMCE");
\r
268 return userDataElement.getAttribute(key);
\r
271 removeItem : function(key) {
\r
272 ed.getElement().removeAttribute(key);
\r
277 ], function(setup) {
\r
278 // Try executing each function to find a suitable storage engine
\r
280 self.storage = setup();
\r
291 * This method will store the current contents in the the storage engine.
\r
293 * @method storeDraft
\r
295 storeDraft : function() {
\r
296 var self = this, storage = self.storage, editor = self.editor, expires, content;
\r
298 // Is the contents dirty
\r
300 // If there is no existing key and the contents hasn't been changed since
\r
301 // it's original value then there is no point in saving a draft
\r
302 if (!storage.getItem(self.key) && !editor.isDirty())
\r
305 // Store contents if the contents if longer than the minlength of characters
\r
306 content = editor.getContent({draft: true});
\r
307 if (content.length > editor.settings.autosave_minlength) {
\r
308 expires = self.getExpDate();
\r
310 // Store expiration date if needed IE userData has auto expire built in
\r
311 if (!self.storage.autoExpires)
\r
312 self.storage.setItem(self.key + "_expires", expires);
\r
314 self.storage.setItem(self.key, content);
\r
315 self.onStoreDraft.dispatch(self, {
\r
324 * This method will restore the contents from the storage engine back to the editor.
\r
326 * @method restoreDraft
\r
328 restoreDraft : function() {
\r
329 var self = this, storage = self.storage;
\r
332 content = storage.getItem(self.key);
\r
335 self.editor.setContent(content);
\r
336 self.onRestoreDraft.dispatch(self, {
\r
344 * This method will return true/false if there is a local storage draft available.
\r
347 * @return {boolean} true/false state if there is a local draft.
\r
349 hasDraft : function() {
\r
350 var self = this, storage = self.storage, expDate, exists;
\r
353 // Does the item exist at all
\r
354 exists = !!storage.getItem(self.key);
\r
356 // Storage needs autoexpire
\r
357 if (!self.storage.autoExpires) {
\r
358 expDate = new Date(storage.getItem(self.key + "_expires"));
\r
360 // Contents hasn't expired
\r
361 if (new Date().getTime() < expDate.getTime())
\r
364 // Remove it if it has
\r
365 self.removeDraft();
\r
375 * Removes the currently stored draft.
\r
377 * @method removeDraft
\r
379 removeDraft : function() {
\r
380 var self = this, storage = self.storage, key = self.key, content;
\r
383 // Get current contents and remove the existing draft
\r
384 content = storage.getItem(key);
\r
385 storage.removeItem(key);
\r
386 storage.removeItem(key + "_expires");
\r
388 // Dispatch remove event if we had any contents
\r
390 self.onRemoveDraft.dispatch(self, {
\r
398 // Internal unload handler will be called before the page is unloaded
\r
399 _beforeUnloadHandler : function(e) {
\r
402 tinymce.each(tinyMCE.editors, function(ed) {
\r
403 // Store a draft for each editor instance
\r
404 if (ed.plugins.autosave)
\r
405 ed.plugins.autosave.storeDraft();
\r
407 // Never ask in fullscreen mode
\r
408 if (ed.getParam("fullscreen_is_enabled"))
\r
411 // Setup a return message if the editor is dirty
\r
412 if (!msg && ed.isDirty() && ed.getParam("autosave_ask_before_unload"))
\r
413 msg = ed.getLang("autosave.unload_msg");
\r
421 tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSave);
\r