]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/plugins/autosave/editor_plugin_src.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / tiny_mce / plugins / autosave / editor_plugin_src.js
1 /**
2  * editor_plugin_src.js
3  *
4  * Copyright 2009, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  *
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/
13  *
14  * TECHNOLOGY DISCUSSION:
15  * 
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:
19  * 
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]
27  * 
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.)
34  * 
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.
44  */
45
46 (function(tinymce) {
47         // Setup constants to help the compressor to reduce script size
48         var PLUGIN_NAME = 'autosave',
49                 RESTORE_DRAFT = 'restoredraft',
50                 TRUE = true,
51                 undefined,
52                 unloadHandlerAdded,
53                 Dispatcher = tinymce.util.Dispatcher;
54
55         /**
56          * This plugin adds auto-save capability to the TinyMCE text editor to rescue content
57          * inadvertently lost. By using localStorage.
58          *
59          * @class tinymce.plugins.AutoSave
60          */
61         tinymce.create('tinymce.plugins.AutoSave', {
62                 /**
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.
66                  *
67                  * @method init
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.
70                  */
71                 init : function(ed, url) {
72                         var self = this, settings = ed.settings;
73
74                         self.editor = ed;
75
76                         // Parses the specified time string into a milisecond number 10m, 10s etc.
77                         function parseTime(time) {
78                                 var multipels = {
79                                         s : 1000,
80                                         m : 60000
81                                 };
82
83                                 time = /^(\d+)([ms]?)$/.exec('' + time);
84
85                                 return (time[2] ? multipels[time[2]] : 1) * parseInt(time);
86                         };
87
88                         // Default config
89                         tinymce.each({
90                                 ask_before_unload : TRUE,
91                                 interval : '30s',
92                                 retention : '20m',
93                                 minlength : 50
94                         }, function(value, key) {
95                                 key = PLUGIN_NAME + '_' + key;
96
97                                 if (settings[key] === undefined)
98                                         settings[key] = value;
99                         });
100
101                         // Parse times
102                         settings.autosave_interval = parseTime(settings.autosave_interval);
103                         settings.autosave_retention = parseTime(settings.autosave_retention);
104
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|&nbsp;|<\/?p[^>]*>|<br[^>]*>/gi, "").length > 0) {
110                                                 // Show confirm dialog if the editor isn't empty
111                                                 ed.windowManager.confirm(
112                                                         PLUGIN_NAME + ".warning_message",
113                                                         function(ok) {
114                                                                 if (ok)
115                                                                         self.restoreDraft();
116                                                         }
117                                                 );
118                                         } else
119                                                 self.restoreDraft();
120                                 }
121                         });
122
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;
126
127                                 if (controlManager.get(RESTORE_DRAFT))
128                                         controlManager.setDisabled(RESTORE_DRAFT, !self.hasDraft());
129                         });
130
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);
136
137                                         // Auto save contents each interval time
138                                         setInterval(function() {
139                                                 self.storeDraft();
140                                                 ed.nodeChanged();
141                                         }, settings.autosave_interval);
142                                 }
143                         });
144
145                         /**
146                          * This event gets fired when a draft is stored to local storage.
147                          *
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.
151                          */
152                         self.onStoreDraft = new Dispatcher(self);
153
154                         /**
155                          * This event gets fired when a draft is restored from local storage.
156                          *
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.
160                          */
161                         self.onRestoreDraft = new Dispatcher(self);
162
163                         /**
164                          * This event gets fired when a draft removed/expired.
165                          *
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.
169                          */
170                         self.onRemoveDraft = new Dispatcher(self);
171
172                         // Add ask before unload dialog only add one unload handler
173                         if (!unloadHandlerAdded) {
174                                 window.onbeforeunload = tinymce.plugins.AutoSave._beforeUnloadHandler;
175                                 unloadHandlerAdded = TRUE;
176                         }
177                 },
178
179                 /**
180                  * Returns information about the plugin as a name/value array.
181                  * The current keys are longname, author, authorurl, infourl and version.
182                  *
183                  * @method getInfo
184                  * @return {Object} Name/value array containing information about the plugin.
185                  */
186                 getInfo : function() {
187                         return {
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
193                         };
194                 },
195
196                 /**
197                  * Returns an expiration date UTC string.
198                  *
199                  * @method getExpDate
200                  * @return {String} Expiration date UTC string.
201                  */
202                 getExpDate : function() {
203                         return new Date(
204                                 new Date().getTime() + this.editor.settings.autosave_retention
205                         ).toUTCString();
206                 },
207
208                 /**
209                  * This method will setup the storage engine. If the browser has support for it.
210                  *
211                  * @method setupStorage
212                  */
213                 setupStorage : function(ed) {
214                         var self = this, testKey = PLUGIN_NAME + '_test', testVal = "OK";
215
216                         self.key = PLUGIN_NAME + ed.id;
217
218                         // Loop though each storage engine type until we find one that works
219                         tinymce.each([
220                                 function() {
221                                         // Try HTML5 Local Storage
222                                         if (localStorage) {
223                                                 localStorage.setItem(testKey, testVal);
224
225                                                 if (localStorage.getItem(testKey) === testVal) {
226                                                         localStorage.removeItem(testKey);
227
228                                                         return localStorage;
229                                                 }
230                                         }
231                                 },
232
233                                 function() {
234                                         // Try HTML5 Session Storage
235                                         if (sessionStorage) {
236                                                 sessionStorage.setItem(testKey, testVal);
237
238                                                 if (sessionStorage.getItem(testKey) === testVal) {
239                                                         sessionStorage.removeItem(testKey);
240
241                                                         return sessionStorage;
242                                                 }
243                                         }
244                                 },
245
246                                 function() {
247                                         // Try IE userData
248                                         if (tinymce.isIE) {
249                                                 ed.getElement().style.behavior = "url('#default#userData')";
250
251                                                 // Fake localStorage on old IE
252                                                 return {
253                                                         autoExpires : TRUE,
254
255                                                         setItem : function(key, value) {
256                                                                 var userDataElement = ed.getElement();
257
258                                                                 userDataElement.setAttribute(key, value);
259                                                                 userDataElement.expires = self.getExpDate();
260
261                                                                 try {
262                                                                         userDataElement.save("TinyMCE");
263                                                                 } catch (e) {
264                                                                         // Ignore, saving might fail if "Userdata Persistence" is disabled in IE
265                                                                 }
266                                                         },
267
268                                                         getItem : function(key) {
269                                                                 var userDataElement = ed.getElement();
270
271                                                                 try {
272                                                                         userDataElement.load("TinyMCE");
273                                                                         return userDataElement.getAttribute(key);
274                                                                 } catch (e) {
275                                                                         // Ignore, loading might fail if "Userdata Persistence" is disabled in IE
276                                                                         return null;
277                                                                 }
278                                                         },
279
280                                                         removeItem : function(key) {
281                                                                 ed.getElement().removeAttribute(key);
282                                                         }
283                                                 };
284                                         }
285                                 },
286                         ], function(setup) {
287                                 // Try executing each function to find a suitable storage engine
288                                 try {
289                                         self.storage = setup();
290
291                                         if (self.storage)
292                                                 return false;
293                                 } catch (e) {
294                                         // Ignore
295                                 }
296                         });
297                 },
298
299                 /**
300                  * This method will store the current contents in the the storage engine.
301                  *
302                  * @method storeDraft
303                  */
304                 storeDraft : function() {
305                         var self = this, storage = self.storage, editor = self.editor, expires, content;
306
307                         // Is the contents dirty
308                         if (storage) {
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())
312                                         return;
313
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();
318
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);
322
323                                         self.storage.setItem(self.key, content);
324                                         self.onStoreDraft.dispatch(self, {
325                                                 expires : expires,
326                                                 content : content
327                                         });
328                                 }
329                         }
330                 },
331
332                 /**
333                  * This method will restore the contents from the storage engine back to the editor.
334                  *
335                  * @method restoreDraft
336                  */
337                 restoreDraft : function() {
338                         var self = this, storage = self.storage, content;
339
340                         if (storage) {
341                                 content = storage.getItem(self.key);
342
343                                 if (content) {
344                                         self.editor.setContent(content);
345                                         self.onRestoreDraft.dispatch(self, {
346                                                 content : content
347                                         });
348                                 }
349                         }
350                 },
351
352                 /**
353                  * This method will return true/false if there is a local storage draft available.
354                  *
355                  * @method hasDraft
356                  * @return {boolean} true/false state if there is a local draft.
357                  */
358                 hasDraft : function() {
359                         var self = this, storage = self.storage, expDate, exists;
360
361                         if (storage) {
362                                 // Does the item exist at all
363                                 exists = !!storage.getItem(self.key);
364                                 if (exists) {
365                                         // Storage needs autoexpire
366                                         if (!self.storage.autoExpires) {
367                                                 expDate = new Date(storage.getItem(self.key + "_expires"));
368
369                                                 // Contents hasn't expired
370                                                 if (new Date().getTime() < expDate.getTime())
371                                                         return TRUE;
372
373                                                 // Remove it if it has
374                                                 self.removeDraft();
375                                         } else
376                                                 return TRUE;
377                                 }
378                         }
379
380                         return false;
381                 },
382
383                 /**
384                  * Removes the currently stored draft.
385                  *
386                  * @method removeDraft
387                  */
388                 removeDraft : function() {
389                         var self = this, storage = self.storage, key = self.key, content;
390
391                         if (storage) {
392                                 // Get current contents and remove the existing draft
393                                 content = storage.getItem(key);
394                                 storage.removeItem(key);
395                                 storage.removeItem(key + "_expires");
396
397                                 // Dispatch remove event if we had any contents
398                                 if (content) {
399                                         self.onRemoveDraft.dispatch(self, {
400                                                 content : content
401                                         });
402                                 }
403                         }
404                 },
405
406                 "static" : {
407                         // Internal unload handler will be called before the page is unloaded
408                         _beforeUnloadHandler : function(e) {
409                                 var msg;
410
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();
415
416                                         // Never ask in fullscreen mode
417                                         if (ed.getParam("fullscreen_is_enabled"))
418                                                 return;
419
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");
423                                 });
424
425                                 return msg;
426                         }
427                 }
428         });
429
430         tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSave);
431 })(tinymce);