annotate static/js/tiny_mce/plugins/autosave/editor_plugin_src.js @ 339:b871892264f2

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