Mercurial > public > sg101
comparison static/js/tiny_mce/plugins/autosave/editor_plugin_src.js @ 312:88b2b9cb8c1f
Fixing #142; cut over to the django.contrib.staticfiles app.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Thu, 27 Jan 2011 02:56:10 +0000 |
parents | |
children | 6c182ceb7147 |
comparison
equal
deleted
inserted
replaced
311:b1c39788e511 | 312:88b2b9cb8c1f |
---|---|
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| |<\/?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 userDataElement.save("TinyMCE"); | |
261 }, | |
262 | |
263 getItem : function(key) { | |
264 var userDataElement = ed.getElement(); | |
265 | |
266 userDataElement.load("TinyMCE"); | |
267 | |
268 return userDataElement.getAttribute(key); | |
269 }, | |
270 | |
271 removeItem : function(key) { | |
272 ed.getElement().removeAttribute(key); | |
273 } | |
274 }; | |
275 } | |
276 }, | |
277 ], function(setup) { | |
278 // Try executing each function to find a suitable storage engine | |
279 try { | |
280 self.storage = setup(); | |
281 | |
282 if (self.storage) | |
283 return false; | |
284 } catch (e) { | |
285 // Ignore | |
286 } | |
287 }); | |
288 }, | |
289 | |
290 /** | |
291 * This method will store the current contents in the the storage engine. | |
292 * | |
293 * @method storeDraft | |
294 */ | |
295 storeDraft : function() { | |
296 var self = this, storage = self.storage, editor = self.editor, expires, content; | |
297 | |
298 // Is the contents dirty | |
299 if (storage) { | |
300 // If there is no existing key and the contents hasn't been changed since | |
301 // it's original value then there is no point in saving a draft | |
302 if (!storage.getItem(self.key) && !editor.isDirty()) | |
303 return; | |
304 | |
305 // Store contents if the contents if longer than the minlength of characters | |
306 content = editor.getContent({draft: true}); | |
307 if (content.length > editor.settings.autosave_minlength) { | |
308 expires = self.getExpDate(); | |
309 | |
310 // Store expiration date if needed IE userData has auto expire built in | |
311 if (!self.storage.autoExpires) | |
312 self.storage.setItem(self.key + "_expires", expires); | |
313 | |
314 self.storage.setItem(self.key, content); | |
315 self.onStoreDraft.dispatch(self, { | |
316 expires : expires, | |
317 content : content | |
318 }); | |
319 } | |
320 } | |
321 }, | |
322 | |
323 /** | |
324 * This method will restore the contents from the storage engine back to the editor. | |
325 * | |
326 * @method restoreDraft | |
327 */ | |
328 restoreDraft : function() { | |
329 var self = this, storage = self.storage; | |
330 | |
331 if (storage) { | |
332 content = storage.getItem(self.key); | |
333 | |
334 if (content) { | |
335 self.editor.setContent(content); | |
336 self.onRestoreDraft.dispatch(self, { | |
337 content : content | |
338 }); | |
339 } | |
340 } | |
341 }, | |
342 | |
343 /** | |
344 * This method will return true/false if there is a local storage draft available. | |
345 * | |
346 * @method hasDraft | |
347 * @return {boolean} true/false state if there is a local draft. | |
348 */ | |
349 hasDraft : function() { | |
350 var self = this, storage = self.storage, expDate, exists; | |
351 | |
352 if (storage) { | |
353 // Does the item exist at all | |
354 exists = !!storage.getItem(self.key); | |
355 if (exists) { | |
356 // Storage needs autoexpire | |
357 if (!self.storage.autoExpires) { | |
358 expDate = new Date(storage.getItem(self.key + "_expires")); | |
359 | |
360 // Contents hasn't expired | |
361 if (new Date().getTime() < expDate.getTime()) | |
362 return TRUE; | |
363 | |
364 // Remove it if it has | |
365 self.removeDraft(); | |
366 } else | |
367 return TRUE; | |
368 } | |
369 } | |
370 | |
371 return false; | |
372 }, | |
373 | |
374 /** | |
375 * Removes the currently stored draft. | |
376 * | |
377 * @method removeDraft | |
378 */ | |
379 removeDraft : function() { | |
380 var self = this, storage = self.storage, key = self.key, content; | |
381 | |
382 if (storage) { | |
383 // Get current contents and remove the existing draft | |
384 content = storage.getItem(key); | |
385 storage.removeItem(key); | |
386 storage.removeItem(key + "_expires"); | |
387 | |
388 // Dispatch remove event if we had any contents | |
389 if (content) { | |
390 self.onRemoveDraft.dispatch(self, { | |
391 content : content | |
392 }); | |
393 } | |
394 } | |
395 }, | |
396 | |
397 "static" : { | |
398 // Internal unload handler will be called before the page is unloaded | |
399 _beforeUnloadHandler : function(e) { | |
400 var msg; | |
401 | |
402 tinymce.each(tinyMCE.editors, function(ed) { | |
403 // Store a draft for each editor instance | |
404 if (ed.plugins.autosave) | |
405 ed.plugins.autosave.storeDraft(); | |
406 | |
407 // Never ask in fullscreen mode | |
408 if (ed.getParam("fullscreen_is_enabled")) | |
409 return; | |
410 | |
411 // Setup a return message if the editor is dirty | |
412 if (!msg && ed.isDirty() && ed.getParam("autosave_ask_before_unload")) | |
413 msg = ed.getLang("autosave.unload_msg"); | |
414 }); | |
415 | |
416 return msg; | |
417 } | |
418 } | |
419 }); | |
420 | |
421 tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSave); | |
422 })(tinymce); |