commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-old / civicrm / packages / tinymce / jscripts / tiny_mce / plugins / autosave / editor_plugin_src.js
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, content;
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);