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