Added autosave feature
authorAlexander Ebert <ebert@woltlab.com>
Fri, 29 Jul 2016 12:23:11 +0000 (14:23 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Fri, 29 Jul 2016 12:23:25 +0000 (14:23 +0200)
com.woltlab.wcf/templates/wysiwyg.tpl
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabAlignment.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabAutosave.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/Ui/Message/InlineEditor.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Message/Reply.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Autosave.js [new file with mode: 0644]

index fd5ff2922e5a1da0482e9361ce7fbfc650928594..e7bb151a13dac2ca4f56f758437d79a81e3bf7c9 100644 (file)
@@ -20,6 +20,7 @@
                        {* WoltLab *}
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabAlignment.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabAttachment.js?v={@LAST_UPDATE_TIME}',
+                       '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabAutosave.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabBlock.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabButton.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabCode.js?v={@LAST_UPDATE_TIME}',
@@ -47,7 +48,7 @@
                
                {event name='redactorJavaScript'}
        ], function () {
-               require(['Language', 'WoltLab/WCF/Ui/Redactor/Metacode'], function(Language, UiRedactorMetacode) {
+               require(['Language', 'WoltLab/WCF/Ui/Redactor/Autosave', 'WoltLab/WCF/Ui/Redactor/Metacode'], function(Language, UiRedactorAutosave, UiRedactorMetacode) {
                        Language.addObject({
                                'wcf.editor.code.edit': '{lang}wcf.editor.code.edit{/lang}',
                                'wcf.editor.code.file': '{lang}wcf.editor.code.file{/lang}',
                        var element = elById('{if $wysiwygSelector|isset}{$wysiwygSelector|encodeJS}{else}text{/if}');
                        UiRedactorMetacode.convert(element);
                        
-                       var autosave = elData(element, 'autosave') || '';
+                       var autosave = elData(element, 'autosave') || null;
                        if (autosave) {
-                               element.removeAttribute('data-autosave');
+                               autosave = new UiRedactorAutosave(element);
+                               element.value = autosave.getInitialValue();
                        }
                        
                        var config = {
                                        // WoltLab core
                                        'WoltLabAlignment',
                                        'WoltLabAttachment',
+                                       'WoltLabAutosave',
                                        'WoltLabCode',
                                        'WoltLabColor',
                                        'WoltLabDropdown',
index ee709f0ecddd2e27accad0ff9802719c879df1ba..72a9401da9c2c7192d169dd06d8021c7ce801816 100644 (file)
@@ -1,4 +1,4 @@
-$.Redactor.prototype.WoltLabAlignment  = function() {
+$.Redactor.prototype.WoltLabAlignment = function() {
        "use strict";
        
        return {
diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabAutosave.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabAutosave.js
new file mode 100644 (file)
index 0000000..2c9762a
--- /dev/null
@@ -0,0 +1,32 @@
+$.Redactor.prototype.WoltLabAutosave = function() {
+       "use strict";
+       
+       return {
+               init: function () {
+                       //noinspection JSUnresolvedVariable
+                       if (this.opts.woltlab.autosave) {
+                               //noinspection JSUnresolvedVariable
+                               this.opts.woltlab.autosave.watch(this);
+                               
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'autosaveDestroy_' + this.$element[0].id, this.WoltLabAutosave.destroy.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'autosaveReset_' + this.$element[0].id, this.WoltLabAutosave.reset.bind(this));
+                       }
+               },
+               
+               destroy: function () {
+                       //noinspection JSUnresolvedVariable
+                       if (this.opts.woltlab.autosave) {
+                               //noinspection JSUnresolvedVariable
+                               this.opts.woltlab.autosave.destroy();
+                       }
+               },
+               
+               reset: function () {
+                       //noinspection JSUnresolvedVariable
+                       if (this.opts.woltlab.autosave) {
+                               //noinspection JSUnresolvedVariable
+                               this.opts.woltlab.autosave.clear();
+                       }
+               }
+       };
+};
index 54acb6904e9f3cf7b35036342b524dfa493d8e16..68cef5c9360f04c3620914ddbd6280190f963b0f 100644 (file)
@@ -508,6 +508,7 @@ define(
                 */
                _showMessage: function(data) {
                        var activeElement = this._activeElement;
+                       var editorId = this._getEditorId();
                        var elementData = this._elements.get(activeElement);
                        var attachmentLists = elBySelAll('.attachmentThumbnailList, .attachmentFileList', elementData.messageFooter);
                        
@@ -553,6 +554,8 @@ define(
                        
                        this._updateHistory(this._getHash(this._getObjectId(activeElement)));
                        
+                       EventHandler.fire('com.woltlab.wcf.redactor', 'autosaveDestroy_' + editorId);
+                       
                        UiNotification.show();
                        
                        if (this._options.quoteManager) {
@@ -595,6 +598,7 @@ define(
                 * @protected
                 */
                _destroyEditor: function() {
+                       EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveDestroy_' + this._getEditorId());
                        EventHandler.fire('com.woltlab.wcf.redactor', 'destroy_' + this._getEditorId());
                },
                
index 06f98c6d2fe0424e2577ad1a63f8f51ba1ed6a7a..8a73c4b7ebbac506f541a152b420ac4ab91678a3 100644 (file)
@@ -260,7 +260,7 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                 * @protected
                 */
                _insertMessage: function(data) {
-                       // TODO: clear autosave content and disable it
+                       this._getEditor().WoltLabAutosave.reset();
                        
                        // redirect to new page
                        //noinspection JSUnresolvedVariable
@@ -295,8 +295,6 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                                
                                UiNotification.show(Language.get(this._options.successMessage));
                                
-                               // TODO: resume autosave
-                               
                                if (this._options.quoteManager) {
                                        this._options.quoteManager.countQuotes();
                                }
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Autosave.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Autosave.js
new file mode 100644 (file)
index 0000000..94fffd2
--- /dev/null
@@ -0,0 +1,195 @@
+/**
+ * Manages the autosave process storing the current editor message in the local
+ * storage to recover it on browser crash or accidental navigation.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Redactor/Autosave
+ */
+define([], function() {
+       "use strict";
+       
+       // time between save requests in seconds
+       var _frequency = 15;
+       
+       //noinspection JSUnresolvedVariable
+       var _prefix = 'wsc' + window.WCF_PATH.hashCode() + '-';
+       
+       /**
+        * @param       {Element}       element         textarea element
+        * @constructor
+        */
+       function UiRedactorAutosave(element) { this.init(element); }
+       UiRedactorAutosave.prototype = {
+               /**
+                * Initializes the autosave handler and removes outdated messages from storage.
+                * 
+                * @param       {Element}       element         textarea element
+                */
+               init: function (element) {
+                       this._editor = null;
+                       this._element = element;
+                       this._key = _prefix + elData(this._element, 'autosave');
+                       this._lastMessage = '';
+                       this._timer = null;
+                       
+                       this._cleanup();
+                       
+                       // remove attribute to prevent Redactor's built-in autosave to kick in
+                       this._element.removeAttribute('data-autosave');
+               },
+               
+               /**
+                * Returns the initial value for the textarea, used to inject message
+                * from storage into the editor before initialization.
+                * 
+                * @return      {string}        message content
+                */
+               getInitialValue: function () {
+                       var value = '';
+                       try {
+                               value = window.localStorage.getItem(this._key);
+                       }
+                       catch (e) {
+                               window.console.warn("Unable to access local storage: " + e.message);
+                       }
+                       
+                       try {
+                               value = JSON.parse(value);
+                       }
+                       catch (e) {
+                               value = '';
+                       }
+                       
+                       // check if storage is outdated
+                       if (value !== null && typeof value === 'object') {
+                               var lastEditTime = ~~elData(this._element, 'autosave-last-edit-time');
+                               if (lastEditTime * 1000 > value.timestamp) {
+                                       //noinspection JSUnresolvedVariable
+                                       return this._element.value;
+                               }
+                               
+                               return value.content;
+                       }
+                       
+                       //noinspection JSUnresolvedVariable
+                       return this._element.value;
+               },
+               
+               /**
+                * Enables periodical save of editor contents to local storage.
+                * 
+                * @param       {$.Redactor}    editor  redactor instance
+                */
+               watch: function(editor) {
+                       this._editor = editor;
+                       
+                       if (this._timer !== null) {
+                               throw new Error("Autosave timer is already active.");
+                       }
+                       
+                       this._timer = window.setInterval(this._saveToStorage.bind(this), _frequency * 1000);
+                       
+                       this._saveToStorage();
+               },
+               
+               /**
+                * Disables autosave handler, for use on editor destruction.
+                */
+               destroy: function () {
+                       this.clear();
+                       
+                       this._editor = null;
+                       
+                       window.clearInterval(this._timer);
+                       this._timer = null;
+               },
+               
+               /**
+                * Removed the stored message, for use after a message has been submitted.
+                */
+               clear: function () {
+                       this._lastMessage = '';
+                       
+                       try {
+                               window.localStorage.removeItem(this._key);
+                       }
+                       catch (e) {
+                               window.console.warn("Unable to remove from local storage: " + e.message);
+                       }
+               },
+               
+               /**
+                * Saves the current message to storage unless there was no change.
+                * 
+                * @protected
+                */
+               _saveToStorage: function() {
+                       var content = this._editor.code.get();
+                       if (this._editor.utils.isEmpty(content)) {
+                               content = '';
+                       }
+                       
+                       if (this._lastMessage === content) {
+                               // break if content hasn't changed
+                               return;
+                       }
+                       
+                       try {
+                               window.localStorage.setItem(this._key, JSON.stringify({
+                                       content: content,
+                                       timestamp: Date.now()
+                               }));
+                               
+                               this._lastMessage = content;
+                       }
+                       catch (e) {
+                               window.console.warn("Unable to write to local storage: " + e.message);
+                       }
+               },
+               
+               /**
+                * Removes stored messages older than one week.
+                * 
+                * @protected
+                */
+               _cleanup: function () {
+                       var oneWeekAgo = Date.now() - (7 * 24 * 3600 * 1000);
+                       var key, value;
+                       for (var i = 0, length = window.localStorage.length; i < length; i++) {
+                               key = window.localStorage.key(i);
+                               
+                               // check if key matches our prefix
+                               if (key.indexOf(_prefix) !== 0) {
+                                       continue;
+                               }
+                               
+                               try {
+                                       value = window.localStorage.getItem(key);
+                               }
+                               catch (e) {
+                                       window.console.warn("Unable to access local storage: " + e.message);
+                               }
+                               
+                               try {
+                                       value = JSON.parse(value);
+                               }
+                               catch (e) {
+                                       value = { timestamp: 0 };
+                               }
+                               
+                               if (!value || value.timestamp < oneWeekAgo) {
+                                       try {
+                                               window.localStorage.removeItem(key);
+                                       }
+                                       catch (e) {
+                                               window.console.warn("Unable to remove from local storage: " + e.message);
+                                       }
+                               }
+                       }
+               }
+       };
+       
+       return UiRedactorAutosave;
+});