PageRenderTime 46ms CodeModel.GetById 15ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 0ms

/BlogEngine/BlogEngine.NET/editors/tiny_mce_3_4_3_1/plugins/autosave/editor_plugin_src.js

#
JavaScript | 431 lines | 218 code | 59 blank | 154 comment | 30 complexity | 488be19b108e54386d5059266d1efc9f MD5 | raw file
  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;
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);