annotate static/js/tiny_mce/plugins/autosave/editor_plugin_src.js @ 917:0365fdbb4d78

Fix app conflict with messages. Django's messages app label conflicts with our messages app. We can't easily rename our label as that will make us rename database tables. Since our app came first we'll just customize Django messages label. For Django 1.7.7 upgrade.
author Brian Neal <bgneal@gmail.com>
date Mon, 06 Apr 2015 20:02:25 -0500
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);