Subversion-Projekte lars-tiefland.content-management

Revision

Blame | Letzte Änderung | Log anzeigen | RSS feed

/**
 * editor_plugin_src.js
 *
 * Copyright 2009, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://tinymce.moxiecode.com/license
 * Contributing: http://tinymce.moxiecode.com/contributing
 *
 * Adds auto-save capability to the TinyMCE text editor to rescue content
 * inadvertently lost. This plugin was originally developed by Speednet
 * and that project can be found here: http://code.google.com/p/tinyautosave/
 *
 * TECHNOLOGY DISCUSSION:
 * 
 * The plugin attempts to use the most advanced features available in the current browser to save
 * as much content as possible.  There are a total of four different methods used to autosave the
 * content.  In order of preference, they are:
 * 
 * 1. localStorage - A new feature of HTML 5, localStorage can store megabytes of data per domain
 * on the client computer. Data stored in the localStorage area has no expiration date, so we must
 * manage expiring the data ourselves.  localStorage is fully supported by IE8, and it is supposed
 * to be working in Firefox 3 and Safari 3.2, but in reality is is flaky in those browsers.  As
 * HTML 5 gets wider support, the AutoSave plugin will use it automatically. In Windows Vista/7,
 * localStorage is stored in the following folder:
 * C:\Users\[username]\AppData\Local\Microsoft\Internet Explorer\DOMStore\[tempFolder]
 * 
 * 2. sessionStorage - A new feature of HTML 5, sessionStorage works similarly to localStorage,
 * except it is designed to expire after a certain amount of time.  Because the specification
 * around expiration date/time is very loosely-described, it is preferrable to use locaStorage and
 * manage the expiration ourselves.  sessionStorage has similar storage characteristics to
 * localStorage, although it seems to have better support by Firefox 3 at the moment.  (That will
 * certainly change as Firefox continues getting better at HTML 5 adoption.)
 * 
 * 3. UserData - A very under-exploited feature of Microsoft Internet Explorer, UserData is a
 * way to store up to 128K of data per "document", or up to 1MB of data per domain, on the client
 * computer.  The feature is available for IE 5+, which makes it available for every version of IE
 * supported by TinyMCE.  The content is persistent across browser restarts and expires on the
 * date/time specified, just like a cookie.  However, the data is not cleared when the user clears
 * cookies on the browser, which makes it well-suited for rescuing autosaved content.  UserData,
 * like other Microsoft IE browser technologies, is implemented as a behavior attached to a
 * specific DOM object, so in this case we attach the behavior to the same DOM element that the
 * TinyMCE editor instance is attached to.
 */

(function(tinymce) {
        // Setup constants to help the compressor to reduce script size
        var PLUGIN_NAME = 'autosave',
                RESTORE_DRAFT = 'restoredraft',
                TRUE = true,
                undefined,
                unloadHandlerAdded,
                Dispatcher = tinymce.util.Dispatcher;

        /**
         * This plugin adds auto-save capability to the TinyMCE text editor to rescue content
         * inadvertently lost. By using localStorage.
         *
         * @class tinymce.plugins.AutoSave
         */
        tinymce.create('tinymce.plugins.AutoSave', {
                /**
                 * Initializes the plugin, this will be executed after the plugin has been created.
                 * This call is done before the editor instance has finished it's initialization so use the onInit event
                 * of the editor instance to intercept that event.
                 *
                 * @method init
                 * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
                 * @param {string} url Absolute URL to where the plugin is located.
                 */
                init : function(ed, url) {
                        var self = this, settings = ed.settings;

                        self.editor = ed;

                        // Parses the specified time string into a milisecond number 10m, 10s etc.
                        function parseTime(time) {
                                var multipels = {
                                        s : 1000,
                                        m : 60000
                                };

                                time = /^(\d+)([ms]?)$/.exec('' + time);

                                return (time[2] ? multipels[time[2]] : 1) * parseInt(time);
                        };

                        // Default config
                        tinymce.each({
                                ask_before_unload : TRUE,
                                interval : '30s',
                                retention : '20m',
                                minlength : 50
                        }, function(value, key) {
                                key = PLUGIN_NAME + '_' + key;

                                if (settings[key] === undefined)
                                        settings[key] = value;
                        });

                        // Parse times
                        settings.autosave_interval = parseTime(settings.autosave_interval);
                        settings.autosave_retention = parseTime(settings.autosave_retention);

                        // Register restore button
                        ed.addButton(RESTORE_DRAFT, {
                                title : PLUGIN_NAME + ".restore_content",
                                onclick : function() {
                                        if (ed.getContent({draft: true}).replace(/\s|&nbsp;|<\/?p[^>]*>|<br[^>]*>/gi, "").length > 0) {
                                                // Show confirm dialog if the editor isn't empty
                                                ed.windowManager.confirm(
                                                        PLUGIN_NAME + ".warning_message",
                                                        function(ok) {
                                                                if (ok)
                                                                        self.restoreDraft();
                                                        }
                                                );
                                        } else
                                                self.restoreDraft();
                                }
                        });

                        // Enable/disable restoredraft button depending on if there is a draft stored or not
                        ed.onNodeChange.add(function() {
                                var controlManager = ed.controlManager;

                                if (controlManager.get(RESTORE_DRAFT))
                                        controlManager.setDisabled(RESTORE_DRAFT, !self.hasDraft());
                        });

                        ed.onInit.add(function() {
                                // Check if the user added the restore button, then setup auto storage logic
                                if (ed.controlManager.get(RESTORE_DRAFT)) {
                                        // Setup storage engine
                                        self.setupStorage(ed);

                                        // Auto save contents each interval time
                                        setInterval(function() {
                                                self.storeDraft();
                                                ed.nodeChanged();
                                        }, settings.autosave_interval);
                                }
                        });

                        /**
                         * This event gets fired when a draft is stored to local storage.
                         *
                         * @event onStoreDraft
                         * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
                         * @param {Object} draft Draft object containing the HTML contents of the editor.
                         */
                        self.onStoreDraft = new Dispatcher(self);

                        /**
                         * This event gets fired when a draft is restored from local storage.
                         *
                         * @event onStoreDraft
                         * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
                         * @param {Object} draft Draft object containing the HTML contents of the editor.
                         */
                        self.onRestoreDraft = new Dispatcher(self);

                        /**
                         * This event gets fired when a draft removed/expired.
                         *
                         * @event onRemoveDraft
                         * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event.
                         * @param {Object} draft Draft object containing the HTML contents of the editor.
                         */
                        self.onRemoveDraft = new Dispatcher(self);

                        // Add ask before unload dialog only add one unload handler
                        if (!unloadHandlerAdded) {
                                window.onbeforeunload = tinymce.plugins.AutoSave._beforeUnloadHandler;
                                unloadHandlerAdded = TRUE;
                        }
                },

                /**
                 * Returns information about the plugin as a name/value array.
                 * The current keys are longname, author, authorurl, infourl and version.
                 *
                 * @method getInfo
                 * @return {Object} Name/value array containing information about the plugin.
                 */
                getInfo : function() {
                        return {
                                longname : 'Auto save',
                                author : 'Moxiecode Systems AB',
                                authorurl : 'http://tinymce.moxiecode.com',
                                infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave',
                                version : tinymce.majorVersion + "." + tinymce.minorVersion
                        };
                },

                /**
                 * Returns an expiration date UTC string.
                 *
                 * @method getExpDate
                 * @return {String} Expiration date UTC string.
                 */
                getExpDate : function() {
                        return new Date(
                                new Date().getTime() + this.editor.settings.autosave_retention
                        ).toUTCString();
                },

                /**
                 * This method will setup the storage engine. If the browser has support for it.
                 *
                 * @method setupStorage
                 */
                setupStorage : function(ed) {
                        var self = this, testKey = PLUGIN_NAME + '_test', testVal = "OK";

                        self.key = PLUGIN_NAME + ed.id;

                        // Loop though each storage engine type until we find one that works
                        tinymce.each([
                                function() {
                                        // Try HTML5 Local Storage
                                        if (localStorage) {
                                                localStorage.setItem(testKey, testVal);

                                                if (localStorage.getItem(testKey) === testVal) {
                                                        localStorage.removeItem(testKey);

                                                        return localStorage;
                                                }
                                        }
                                },

                                function() {
                                        // Try HTML5 Session Storage
                                        if (sessionStorage) {
                                                sessionStorage.setItem(testKey, testVal);

                                                if (sessionStorage.getItem(testKey) === testVal) {
                                                        sessionStorage.removeItem(testKey);

                                                        return sessionStorage;
                                                }
                                        }
                                },

                                function() {
                                        // Try IE userData
                                        if (tinymce.isIE) {
                                                ed.getElement().style.behavior = "url('#default#userData')";

                                                // Fake localStorage on old IE
                                                return {
                                                        autoExpires : TRUE,

                                                        setItem : function(key, value) {
                                                                var userDataElement = ed.getElement();

                                                                userDataElement.setAttribute(key, value);
                                                                userDataElement.expires = self.getExpDate();

                                                                try {
                                                                        userDataElement.save("TinyMCE");
                                                                } catch (e) {
                                                                        // Ignore, saving might fail if "Userdata Persistence" is disabled in IE
                                                                }
                                                        },

                                                        getItem : function(key) {
                                                                var userDataElement = ed.getElement();

                                                                try {
                                                                        userDataElement.load("TinyMCE");
                                                                        return userDataElement.getAttribute(key);
                                                                } catch (e) {
                                                                        // Ignore, loading might fail if "Userdata Persistence" is disabled in IE
                                                                        return null;
                                                                }
                                                        },

                                                        removeItem : function(key) {
                                                                ed.getElement().removeAttribute(key);
                                                        }
                                                };
                                        }
                                },
                        ], function(setup) {
                                // Try executing each function to find a suitable storage engine
                                try {
                                        self.storage = setup();

                                        if (self.storage)
                                                return false;
                                } catch (e) {
                                        // Ignore
                                }
                        });
                },

                /**
                 * This method will store the current contents in the the storage engine.
                 *
                 * @method storeDraft
                 */
                storeDraft : function() {
                        var self = this, storage = self.storage, editor = self.editor, expires, content;

                        // Is the contents dirty
                        if (storage) {
                                // If there is no existing key and the contents hasn't been changed since
                                // it's original value then there is no point in saving a draft
                                if (!storage.getItem(self.key) && !editor.isDirty())
                                        return;

                                // Store contents if the contents if longer than the minlength of characters
                                content = editor.getContent({draft: true});
                                if (content.length > editor.settings.autosave_minlength) {
                                        expires = self.getExpDate();

                                        // Store expiration date if needed IE userData has auto expire built in
                                        if (!self.storage.autoExpires)
                                                self.storage.setItem(self.key + "_expires", expires);

                                        self.storage.setItem(self.key, content);
                                        self.onStoreDraft.dispatch(self, {
                                                expires : expires,
                                                content : content
                                        });
                                }
                        }
                },

                /**
                 * This method will restore the contents from the storage engine back to the editor.
                 *
                 * @method restoreDraft
                 */
                restoreDraft : function() {
                        var self = this, storage = self.storage, content;

                        if (storage) {
                                content = storage.getItem(self.key);

                                if (content) {
                                        self.editor.setContent(content);
                                        self.onRestoreDraft.dispatch(self, {
                                                content : content
                                        });
                                }
                        }
                },

                /**
                 * This method will return true/false if there is a local storage draft available.
                 *
                 * @method hasDraft
                 * @return {boolean} true/false state if there is a local draft.
                 */
                hasDraft : function() {
                        var self = this, storage = self.storage, expDate, exists;

                        if (storage) {
                                // Does the item exist at all
                                exists = !!storage.getItem(self.key);
                                if (exists) {
                                        // Storage needs autoexpire
                                        if (!self.storage.autoExpires) {
                                                expDate = new Date(storage.getItem(self.key + "_expires"));

                                                // Contents hasn't expired
                                                if (new Date().getTime() < expDate.getTime())
                                                        return TRUE;

                                                // Remove it if it has
                                                self.removeDraft();
                                        } else
                                                return TRUE;
                                }
                        }

                        return false;
                },

                /**
                 * Removes the currently stored draft.
                 *
                 * @method removeDraft
                 */
                removeDraft : function() {
                        var self = this, storage = self.storage, key = self.key, content;

                        if (storage) {
                                // Get current contents and remove the existing draft
                                content = storage.getItem(key);
                                storage.removeItem(key);
                                storage.removeItem(key + "_expires");

                                // Dispatch remove event if we had any contents
                                if (content) {
                                        self.onRemoveDraft.dispatch(self, {
                                                content : content
                                        });
                                }
                        }
                },

                "static" : {
                        // Internal unload handler will be called before the page is unloaded
                        _beforeUnloadHandler : function(e) {
                                var msg;

                                tinymce.each(tinyMCE.editors, function(ed) {
                                        // Store a draft for each editor instance
                                        if (ed.plugins.autosave)
                                                ed.plugins.autosave.storeDraft();

                                        // Never ask in fullscreen mode
                                        if (ed.getParam("fullscreen_is_enabled"))
                                                return;

                                        // Setup a return message if the editor is dirty
                                        if (!msg && ed.isDirty() && ed.getParam("autosave_ask_before_unload"))
                                                msg = ed.getLang("autosave.unload_msg");
                                });

                                return msg;
                        }
                }
        });

        tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSave);
})(tinymce);