annotate static/js/tiny_mce/plugins/autosave/editor_plugin_src.js @ 539:2f0a372c92b4

Update the copyright year in the base template.
author Brian Neal <bgneal@gmail.com>
date Sat, 07 Jan 2012 02:12:52 +0000
parents 6c182ceb7147
children
rev   line source
bgneal@312 1 /**
bgneal@312 2 * editor_plugin_src.js
bgneal@312 3 *
bgneal@312 4 * Copyright 2009, Moxiecode Systems AB
bgneal@312 5 * Released under LGPL License.
bgneal@312 6 *
bgneal@312 7 * License: http://tinymce.moxiecode.com/license
bgneal@312 8 * Contributing: http://tinymce.moxiecode.com/contributing
bgneal@312 9 *
bgneal@312 10 * Adds auto-save capability to the TinyMCE text editor to rescue content
bgneal@312 11 * inadvertently lost. This plugin was originally developed by Speednet
bgneal@312 12 * and that project can be found here: http://code.google.com/p/tinyautosave/
bgneal@312 13 *
bgneal@312 14 * TECHNOLOGY DISCUSSION:
bgneal@312 15 *
bgneal@312 16 * The plugin attempts to use the most advanced features available in the current browser to save
bgneal@312 17 * as much content as possible. There are a total of four different methods used to autosave the
bgneal@312 18 * content. In order of preference, they are:
bgneal@312 19 *
bgneal@312 20 * 1. localStorage - A new feature of HTML 5, localStorage can store megabytes of data per domain
bgneal@312 21 * on the client computer. Data stored in the localStorage area has no expiration date, so we must
bgneal@312 22 * manage expiring the data ourselves. localStorage is fully supported by IE8, and it is supposed
bgneal@312 23 * to be working in Firefox 3 and Safari 3.2, but in reality is is flaky in those browsers. As
bgneal@312 24 * HTML 5 gets wider support, the AutoSave plugin will use it automatically. In Windows Vista/7,
bgneal@312 25 * localStorage is stored in the following folder:
bgneal@312 26 * C:\Users\[username]\AppData\Local\Microsoft\Internet Explorer\DOMStore\[tempFolder]
bgneal@312 27 *
bgneal@312 28 * 2. sessionStorage - A new feature of HTML 5, sessionStorage works similarly to localStorage,
bgneal@312 29 * except it is designed to expire after a certain amount of time. Because the specification
bgneal@312 30 * around expiration date/time is very loosely-described, it is preferrable to use locaStorage and
bgneal@312 31 * manage the expiration ourselves. sessionStorage has similar storage characteristics to
bgneal@312 32 * localStorage, although it seems to have better support by Firefox 3 at the moment. (That will
bgneal@312 33 * certainly change as Firefox continues getting better at HTML 5 adoption.)
bgneal@312 34 *
bgneal@312 35 * 3. UserData - A very under-exploited feature of Microsoft Internet Explorer, UserData is a
bgneal@312 36 * way to store up to 128K of data per "document", or up to 1MB of data per domain, on the client
bgneal@312 37 * computer. The feature is available for IE 5+, which makes it available for every version of IE
bgneal@312 38 * supported by TinyMCE. The content is persistent across browser restarts and expires on the
bgneal@312 39 * date/time specified, just like a cookie. However, the data is not cleared when the user clears
bgneal@312 40 * cookies on the browser, which makes it well-suited for rescuing autosaved content. UserData,
bgneal@312 41 * like other Microsoft IE browser technologies, is implemented as a behavior attached to a
bgneal@312 42 * specific DOM object, so in this case we attach the behavior to the same DOM element that the
bgneal@312 43 * TinyMCE editor instance is attached to.
bgneal@312 44 */
bgneal@312 45
bgneal@312 46 (function(tinymce) {
bgneal@312 47 // Setup constants to help the compressor to reduce script size
bgneal@312 48 var PLUGIN_NAME = 'autosave',
bgneal@312 49 RESTORE_DRAFT = 'restoredraft',
bgneal@312 50 TRUE = true,
bgneal@312 51 undefined,
bgneal@312 52 unloadHandlerAdded,
bgneal@312 53 Dispatcher = tinymce.util.Dispatcher;
bgneal@312 54
bgneal@312 55 /**
bgneal@312 56 * This plugin adds auto-save capability to the TinyMCE text editor to rescue content
bgneal@312 57 * inadvertently lost. By using localStorage.
bgneal@312 58 *
bgneal@312 59 * @class tinymce.plugins.AutoSave
bgneal@312 60 */
bgneal@312 61 tinymce.create('tinymce.plugins.AutoSave', {
bgneal@312 62 /**
bgneal@312 63 * Initializes the plugin, this will be executed after the plugin has been created.
bgneal@312 64 * This call is done before the editor instance has finished it's initialization so use the onInit event
bgneal@312 65 * of the editor instance to intercept that event.
bgneal@312 66 *
bgneal@312 67 * @method init
bgneal@312 68 * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
bgneal@312 69 * @param {string} url Absolute URL to where the plugin is located.
bgneal@312 70 */
bgneal@312 71 init : function(ed, url) {
bgneal@312 72 var self = this, settings = ed.settings;
bgneal@312 73
bgneal@312 74 self.editor = ed;
bgneal@312 75
bgneal@312 76 // Parses the specified time string into a milisecond number 10m, 10s etc.
bgneal@312 77 function parseTime(time) {
bgneal@312 78 var multipels = {
bgneal@312 79 s : 1000,
bgneal@312 80 m : 60000
bgneal@312 81 };
bgneal@312 82
bgneal@312 83 time = /^(\d+)([ms]?)$/.exec('' + time);
bgneal@312 84
bgneal@312 85 return (time[2] ? multipels[time[2]] : 1) * parseInt(time);
bgneal@312 86 };
bgneal@312 87
bgneal@312 88 // Default config
bgneal@312 89 tinymce.each({
bgneal@312 90 ask_before_unload : TRUE,
bgneal@312 91 interval : '30s',
bgneal@312 92 retention : '20m',
bgneal@312 93 minlength : 50
bgneal@312 94 }, function(value, key) {
bgneal@312 95 key = PLUGIN_NAME + '_' + key;
bgneal@312 96
bgneal@312 97 if (settings[key] === undefined)
bgneal@312 98 settings[key] = value;
bgneal@312 99 });
bgneal@312 100
bgneal@312 101 // Parse times
bgneal@312 102 settings.autosave_interval = parseTime(settings.autosave_interval);
bgneal@312 103 settings.autosave_retention = parseTime(settings.autosave_retention);
bgneal@312 104
bgneal@312 105 // Register restore button
bgneal@312 106 ed.addButton(RESTORE_DRAFT, {
bgneal@312 107 title : PLUGIN_NAME + ".restore_content",
bgneal@312 108 onclick : function() {
bgneal@312 109 if (ed.getContent({draft: true}).replace(/\s|&nbsp;|<\/?p[^>]*>|<br[^>]*>/gi, "").length > 0) {
bgneal@312 110 // Show confirm dialog if the editor isn't empty
bgneal@312 111 ed.windowManager.confirm(
bgneal@312 112 PLUGIN_NAME + ".warning_message",
bgneal@312 113 function(ok) {
bgneal@312 114 if (ok)
bgneal@312 115 self.restoreDraft();
bgneal@312 116 }
bgneal@312 117 );
bgneal@312 118 } else
bgneal@312 119 self.restoreDraft();
bgneal@312 120 }
bgneal@312 121 });
bgneal@312 122
bgneal@312 123 // Enable/disable restoredraft button depending on if there is a draft stored or not
bgneal@312 124 ed.onNodeChange.add(function() {
bgneal@312 125 var controlManager = ed.controlManager;
bgneal@312 126
bgneal@312 127 if (controlManager.get(RESTORE_DRAFT))
bgneal@312 128 controlManager.setDisabled(RESTORE_DRAFT, !self.hasDraft());
bgneal@312 129 });
bgneal@312 130
bgneal@312 131 ed.onInit.add(function() {
bgneal@312 132 // Check if the user added the restore button, then setup auto storage logic
bgneal@312 133 if (ed.controlManager.get(RESTORE_DRAFT)) {
bgneal@312 134 // Setup storage engine
bgneal@312 135 self.setupStorage(ed);
bgneal@312 136
bgneal@312 137 // Auto save contents each interval time
bgneal@312 138 setInterval(function() {
bgneal@312 139 self.storeDraft();
bgneal@312 140 ed.nodeChanged();
bgneal@312 141 }, settings.autosave_interval);
bgneal@312 142 }
bgneal@312 143 });
bgneal@312 144
bgneal@312 145 /**
bgneal@312 146 * This event gets fired when a draft is stored to local storage.
bgneal@312 147 *
bgneal@312 148 * @event onStoreDraft
bgneal@312 149 * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
bgneal@312 150 * @param {Object} draft Draft object containing the HTML contents of the editor.
bgneal@312 151 */
bgneal@312 152 self.onStoreDraft = new Dispatcher(self);
bgneal@312 153
bgneal@312 154 /**
bgneal@312 155 * This event gets fired when a draft is restored from local storage.
bgneal@312 156 *
bgneal@312 157 * @event onStoreDraft
bgneal@312 158 * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
bgneal@312 159 * @param {Object} draft Draft object containing the HTML contents of the editor.
bgneal@312 160 */
bgneal@312 161 self.onRestoreDraft = new Dispatcher(self);
bgneal@312 162
bgneal@312 163 /**
bgneal@312 164 * This event gets fired when a draft removed/expired.
bgneal@312 165 *
bgneal@312 166 * @event onRemoveDraft
bgneal@312 167 * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
bgneal@312 168 * @param {Object} draft Draft object containing the HTML contents of the editor.
bgneal@312 169 */
bgneal@312 170 self.onRemoveDraft = new Dispatcher(self);
bgneal@312 171
bgneal@312 172 // Add ask before unload dialog only add one unload handler
bgneal@312 173 if (!unloadHandlerAdded) {
bgneal@312 174 window.onbeforeunload = tinymce.plugins.AutoSave._beforeUnloadHandler;
bgneal@312 175 unloadHandlerAdded = TRUE;
bgneal@312 176 }
bgneal@312 177 },
bgneal@312 178
bgneal@312 179 /**
bgneal@312 180 * Returns information about the plugin as a name/value array.
bgneal@312 181 * The current keys are longname, author, authorurl, infourl and version.
bgneal@312 182 *
bgneal@312 183 * @method getInfo
bgneal@312 184 * @return {Object} Name/value array containing information about the plugin.
bgneal@312 185 */
bgneal@312 186 getInfo : function() {
bgneal@312 187 return {
bgneal@312 188 longname : 'Auto save',
bgneal@312 189 author : 'Moxiecode Systems AB',
bgneal@312 190 authorurl : 'http://tinymce.moxiecode.com',
bgneal@312 191 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave',
bgneal@312 192 version : tinymce.majorVersion + "." + tinymce.minorVersion
bgneal@312 193 };
bgneal@312 194 },
bgneal@312 195
bgneal@312 196 /**
bgneal@312 197 * Returns an expiration date UTC string.
bgneal@312 198 *
bgneal@312 199 * @method getExpDate
bgneal@312 200 * @return {String} Expiration date UTC string.
bgneal@312 201 */
bgneal@312 202 getExpDate : function() {
bgneal@312 203 return new Date(
bgneal@312 204 new Date().getTime() + this.editor.settings.autosave_retention
bgneal@312 205 ).toUTCString();
bgneal@312 206 },
bgneal@312 207
bgneal@312 208 /**
bgneal@312 209 * This method will setup the storage engine. If the browser has support for it.
bgneal@312 210 *
bgneal@312 211 * @method setupStorage
bgneal@312 212 */
bgneal@312 213 setupStorage : function(ed) {
bgneal@312 214 var self = this, testKey = PLUGIN_NAME + '_test', testVal = "OK";
bgneal@312 215
bgneal@312 216 self.key = PLUGIN_NAME + ed.id;
bgneal@312 217
bgneal@312 218 // Loop though each storage engine type until we find one that works
bgneal@312 219 tinymce.each([
bgneal@312 220 function() {
bgneal@312 221 // Try HTML5 Local Storage
bgneal@312 222 if (localStorage) {
bgneal@312 223 localStorage.setItem(testKey, testVal);
bgneal@312 224
bgneal@312 225 if (localStorage.getItem(testKey) === testVal) {
bgneal@312 226 localStorage.removeItem(testKey);
bgneal@312 227
bgneal@312 228 return localStorage;
bgneal@312 229 }
bgneal@312 230 }
bgneal@312 231 },
bgneal@312 232
bgneal@312 233 function() {
bgneal@312 234 // Try HTML5 Session Storage
bgneal@312 235 if (sessionStorage) {
bgneal@312 236 sessionStorage.setItem(testKey, testVal);
bgneal@312 237
bgneal@312 238 if (sessionStorage.getItem(testKey) === testVal) {
bgneal@312 239 sessionStorage.removeItem(testKey);
bgneal@312 240
bgneal@312 241 return sessionStorage;
bgneal@312 242 }
bgneal@312 243 }
bgneal@312 244 },
bgneal@312 245
bgneal@312 246 function() {
bgneal@312 247 // Try IE userData
bgneal@312 248 if (tinymce.isIE) {
bgneal@312 249 ed.getElement().style.behavior = "url('#default#userData')";
bgneal@312 250
bgneal@312 251 // Fake localStorage on old IE
bgneal@312 252 return {
bgneal@312 253 autoExpires : TRUE,
bgneal@312 254
bgneal@312 255 setItem : function(key, value) {
bgneal@312 256 var userDataElement = ed.getElement();
bgneal@312 257
bgneal@312 258 userDataElement.setAttribute(key, value);
bgneal@312 259 userDataElement.expires = self.getExpDate();
bgneal@442 260
bgneal@442 261 try {
bgneal@442 262 userDataElement.save("TinyMCE");
bgneal@442 263 } catch (e) {
bgneal@442 264 // Ignore, saving might fail if "Userdata Persistence" is disabled in IE
bgneal@442 265 }
bgneal@312 266 },
bgneal@312 267
bgneal@312 268 getItem : function(key) {
bgneal@312 269 var userDataElement = ed.getElement();
bgneal@312 270
bgneal@442 271 try {
bgneal@442 272 userDataElement.load("TinyMCE");
bgneal@442 273 return userDataElement.getAttribute(key);
bgneal@442 274 } catch (e) {
bgneal@442 275 // Ignore, loading might fail if "Userdata Persistence" is disabled in IE
bgneal@442 276 return null;
bgneal@442 277 }
bgneal@312 278 },
bgneal@312 279
bgneal@312 280 removeItem : function(key) {
bgneal@312 281 ed.getElement().removeAttribute(key);
bgneal@312 282 }
bgneal@312 283 };
bgneal@312 284 }
bgneal@312 285 },
bgneal@312 286 ], function(setup) {
bgneal@312 287 // Try executing each function to find a suitable storage engine
bgneal@312 288 try {
bgneal@312 289 self.storage = setup();
bgneal@312 290
bgneal@312 291 if (self.storage)
bgneal@312 292 return false;
bgneal@312 293 } catch (e) {
bgneal@312 294 // Ignore
bgneal@312 295 }
bgneal@312 296 });
bgneal@312 297 },
bgneal@312 298
bgneal@312 299 /**
bgneal@312 300 * This method will store the current contents in the the storage engine.
bgneal@312 301 *
bgneal@312 302 * @method storeDraft
bgneal@312 303 */
bgneal@312 304 storeDraft : function() {
bgneal@312 305 var self = this, storage = self.storage, editor = self.editor, expires, content;
bgneal@312 306
bgneal@312 307 // Is the contents dirty
bgneal@312 308 if (storage) {
bgneal@312 309 // If there is no existing key and the contents hasn't been changed since
bgneal@312 310 // it's original value then there is no point in saving a draft
bgneal@312 311 if (!storage.getItem(self.key) && !editor.isDirty())
bgneal@312 312 return;
bgneal@312 313
bgneal@312 314 // Store contents if the contents if longer than the minlength of characters
bgneal@312 315 content = editor.getContent({draft: true});
bgneal@312 316 if (content.length > editor.settings.autosave_minlength) {
bgneal@312 317 expires = self.getExpDate();
bgneal@312 318
bgneal@312 319 // Store expiration date if needed IE userData has auto expire built in
bgneal@312 320 if (!self.storage.autoExpires)
bgneal@312 321 self.storage.setItem(self.key + "_expires", expires);
bgneal@312 322
bgneal@312 323 self.storage.setItem(self.key, content);
bgneal@312 324 self.onStoreDraft.dispatch(self, {
bgneal@312 325 expires : expires,
bgneal@312 326 content : content
bgneal@312 327 });
bgneal@312 328 }
bgneal@312 329 }
bgneal@312 330 },
bgneal@312 331
bgneal@312 332 /**
bgneal@312 333 * This method will restore the contents from the storage engine back to the editor.
bgneal@312 334 *
bgneal@312 335 * @method restoreDraft
bgneal@312 336 */
bgneal@312 337 restoreDraft : function() {
bgneal@312 338 var self = this, storage = self.storage;
bgneal@312 339
bgneal@312 340 if (storage) {
bgneal@312 341 content = storage.getItem(self.key);
bgneal@312 342
bgneal@312 343 if (content) {
bgneal@312 344 self.editor.setContent(content);
bgneal@312 345 self.onRestoreDraft.dispatch(self, {
bgneal@312 346 content : content
bgneal@312 347 });
bgneal@312 348 }
bgneal@312 349 }
bgneal@312 350 },
bgneal@312 351
bgneal@312 352 /**
bgneal@312 353 * This method will return true/false if there is a local storage draft available.
bgneal@312 354 *
bgneal@312 355 * @method hasDraft
bgneal@312 356 * @return {boolean} true/false state if there is a local draft.
bgneal@312 357 */
bgneal@312 358 hasDraft : function() {
bgneal@312 359 var self = this, storage = self.storage, expDate, exists;
bgneal@312 360
bgneal@312 361 if (storage) {
bgneal@312 362 // Does the item exist at all
bgneal@312 363 exists = !!storage.getItem(self.key);
bgneal@312 364 if (exists) {
bgneal@312 365 // Storage needs autoexpire
bgneal@312 366 if (!self.storage.autoExpires) {
bgneal@312 367 expDate = new Date(storage.getItem(self.key + "_expires"));
bgneal@312 368
bgneal@312 369 // Contents hasn't expired
bgneal@312 370 if (new Date().getTime() < expDate.getTime())
bgneal@312 371 return TRUE;
bgneal@312 372
bgneal@312 373 // Remove it if it has
bgneal@312 374 self.removeDraft();
bgneal@312 375 } else
bgneal@312 376 return TRUE;
bgneal@312 377 }
bgneal@312 378 }
bgneal@312 379
bgneal@312 380 return false;
bgneal@312 381 },
bgneal@312 382
bgneal@312 383 /**
bgneal@312 384 * Removes the currently stored draft.
bgneal@312 385 *
bgneal@312 386 * @method removeDraft
bgneal@312 387 */
bgneal@312 388 removeDraft : function() {
bgneal@312 389 var self = this, storage = self.storage, key = self.key, content;
bgneal@312 390
bgneal@312 391 if (storage) {
bgneal@312 392 // Get current contents and remove the existing draft
bgneal@312 393 content = storage.getItem(key);
bgneal@312 394 storage.removeItem(key);
bgneal@312 395 storage.removeItem(key + "_expires");
bgneal@312 396
bgneal@312 397 // Dispatch remove event if we had any contents
bgneal@312 398 if (content) {
bgneal@312 399 self.onRemoveDraft.dispatch(self, {
bgneal@312 400 content : content
bgneal@312 401 });
bgneal@312 402 }
bgneal@312 403 }
bgneal@312 404 },
bgneal@312 405
bgneal@312 406 "static" : {
bgneal@312 407 // Internal unload handler will be called before the page is unloaded
bgneal@312 408 _beforeUnloadHandler : function(e) {
bgneal@312 409 var msg;
bgneal@312 410
bgneal@312 411 tinymce.each(tinyMCE.editors, function(ed) {
bgneal@312 412 // Store a draft for each editor instance
bgneal@312 413 if (ed.plugins.autosave)
bgneal@312 414 ed.plugins.autosave.storeDraft();
bgneal@312 415
bgneal@312 416 // Never ask in fullscreen mode
bgneal@312 417 if (ed.getParam("fullscreen_is_enabled"))
bgneal@312 418 return;
bgneal@312 419
bgneal@312 420 // Setup a return message if the editor is dirty
bgneal@312 421 if (!msg && ed.isDirty() && ed.getParam("autosave_ask_before_unload"))
bgneal@312 422 msg = ed.getLang("autosave.unload_msg");
bgneal@312 423 });
bgneal@312 424
bgneal@312 425 return msg;
bgneal@312 426 }
bgneal@312 427 }
bgneal@312 428 });
bgneal@312 429
bgneal@312 430 tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSave);
bgneal@312 431 })(tinymce);