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