Reworked message inline editor
authorAlexander Ebert <ebert@woltlab.com>
Thu, 20 Aug 2015 10:45:51 +0000 (12:45 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 20 Aug 2015 10:45:51 +0000 (12:45 +0200)
wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wutil.js
wcfsetup/install/files/js/WCF.Message.js
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/js/WoltLab/WCF/Bootstrap.js
wcfsetup/install/files/js/WoltLab/WCF/Dom/Util.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Message/InlineEditor.js [new file with mode: 0644]
wcfsetup/install/files/js/wcf.globalHelper.js
wcfsetup/install/files/style/element/dl.less
wcfsetup/install/files/style/ui/message.less

index 3ab34726c1a8ed90a1eabc86067bec26d63e69ec..856341a6050129f80340291800f90bc4827d477d 100644 (file)
@@ -170,6 +170,17 @@ RedactorPlugins.wbbcode = function() {
                        }).bind(this));
                        
                        WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'fixFormatting_' + $identifier, $.proxy(this.wbbcode.fixFormatting, this));
+                       
+                       WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'destroy_' + $identifier, (function() {
+                               this.wautosave.disable();
+                               this.wautosave.purge();
+                               this.core.destroy();
+                               
+                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.messageOptionsInline', 'submit_' + $identifier);
+                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor', 'destroy_' + $identifier);
+                               
+                               WCF.System.Dependency.Manager.reset('Redactor_' + $identifier);
+                       }).bind(this));
                },
                
                /**
index fee0626a347ee2b352b514c351ca71bd23092989..ade48a047ec23e403351b8723db61acbf76daa39 100644 (file)
@@ -36,6 +36,10 @@ RedactorPlugins.wutil = function() {
                        
                        // convert HTML to BBCode upon submit
                        this.$textarea.parents('form').submit($.proxy(this.wutil.submit, this));
+                       
+                       WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'getText_' + _textarea.id, (function(data) {
+                               data.message = this.wutil.getText();
+                       }).bind(this));
                },
                
                /**
index 1da687b6888c41c3058e85dd548850f9d67691fb..2eadecfbebbec839f5b7d3d0241d3dcbff76238a 100644 (file)
@@ -1509,13 +1509,12 @@ WCF.Message.InlineEditor = Class.extend({
                
                var $messageBody = this._container[this._activeElementID].addClass('jsInvalidQuoteTarget').find('.messageBody');
                $messageBody.children('.icon-spinner').remove();
-               var $content = $messageBody.children('div:eq(0)');
                
                // insert wysiwyg
-               $('' + data.returnValues.template).appendTo($content);
+               $('' + data.returnValues.template).appendTo($messageBody);
                
                // bind buttons
-               var $formSubmit = $content.find('.formSubmit');
+               var $formSubmit = $messageBody.find('.formSubmit');
                var $saveButton = $formSubmit.find('button[data-type=save]').click($.proxy(this._save, this));
                if (this._supportExtendedForm) $formSubmit.find('button[data-type=extended]').click($.proxy(this._prepareExtended, this));
                $formSubmit.find('button[data-type=cancel]').click($.proxy(this._cancel, this));
index 7873dc92d634b6fc20fd0fef74aba8dfbd34ec06..a093cada756b7da55e4fc48f8de09d96d9a3e562 100755 (executable)
@@ -5201,6 +5201,13 @@ WCF.System.Dependency.Manager = {
                        
                        delete this._callbacks[identifier];
                }
+       },
+       
+       reset: function(identifier) {
+               var index = this._loaded.indexOf(identifier);
+               if (index !== -1) {
+                       this._loaded.splice(index, 1);
+               }
        }
 };
 
@@ -5811,14 +5818,10 @@ WCF.System.PushNotification = {
 
 /**
  * System-wide event system.
+ * 
+ * @deprecated 2.2 - please use `EventHandler` instead
  */
 WCF.System.Event = {
-       /**
-        * list of event listeners grouped by identifier and action.
-        * @var object<object>
-        */
-       _listeners: { },
-       
        /**
         * Registers a new event listener.
         * 
@@ -5828,21 +5831,7 @@ WCF.System.Event = {
         * @return      string
         */
        addListener: function(identifier, action, listener) {
-               if (typeof this._listeners[identifier] === 'undefined') {
-                       this._listeners[identifier] = { };
-               }
-               
-               if (typeof this._listeners[identifier][action] === 'undefined') {
-                       this._listeners[identifier][action] = [ ];
-               }
-               
-               var $uuid = WCF.getUUID();
-               this._listeners[identifier][action].push({
-                       callback: listener,
-                       uuid: $uuid
-               });
-               
-               return $uuid;
+               return window.__wcf_bc_eventHandler.add(identifier, action, listener);
        },
        
        /**
@@ -5854,17 +5843,7 @@ WCF.System.Event = {
         * @return      boolean
         */
        removeListener: function(identifier, action, uuid) {
-               if (this._listeners[identifier] && this._listeners[identifier][action]) {
-                       for (var $i = 0; $i < this._listeners[identifier][action].length; $i++) {
-                               if (this._listeners[identifier][action][$i].uuid == uuid) {
-                                       this._listeners[identifier][action].splice($i, 1);
-                                       
-                                       return true;
-                               }
-                       }
-               }
-               
-               return false;
+               return window.__wcf_bc_eventHandler.remove(identifier, action, uuid);
        },
        
        /**
@@ -5875,13 +5854,7 @@ WCF.System.Event = {
         * @return      boolean
         */
        removeAllListeners: function(identifier, action) {
-               if (this._listeners[identifier] && this._listeners[identifier][action]) {
-                       delete this._listeners[identifier][action];
-                       
-                       return true;
-               }
-               
-               return false;
+               return window.__wcf_bc_eventHandler.removeAll(identifier, action);
        },
        
        /**
@@ -5892,13 +5865,7 @@ WCF.System.Event = {
         * @param       object          data
         */
        fireEvent: function(identifier, action, data) {
-               data = data || { };
-               
-               if (this._listeners[identifier] && this._listeners[identifier][action]) {
-                       for (var $i = 0; $i < this._listeners[identifier][action].length; $i++) {
-                               this._listeners[identifier][action][$i].callback(data);
-                       }
-               }
+               window.__wcf_bc_eventHandler.fire(identifier, action, data);
        }
 };
 
index f85a8c6781d3a100b2176c3331086f2144a55d9d..1300d76445de6b00ba18373dcf5b7ded776c9e87 100644 (file)
  */
 define(
        [
-               'favico',                 'enquire',                'perfect-scrollbar',      'WoltLab/WCF/Date/Time/Relative',
-               'Ui/SimpleDropdown',      'WoltLab/WCF/Ui/Mobile',  'WoltLab/WCF/Ui/TabMenu', 'WoltLab/WCF/Ui/FlexibleMenu',
-               'Ui/Dialog',              'WoltLab/WCF/Ui/Tooltip', 'WoltLab/WCF/Language',   'WoltLab/WCF/Environment',
-               'WoltLab/WCF/Date/Picker'
+               'favico',                  'enquire',                'perfect-scrollbar',      'WoltLab/WCF/Date/Time/Relative',
+               'Ui/SimpleDropdown',       'WoltLab/WCF/Ui/Mobile',  'WoltLab/WCF/Ui/TabMenu', 'WoltLab/WCF/Ui/FlexibleMenu',
+               'Ui/Dialog',               'WoltLab/WCF/Ui/Tooltip', 'WoltLab/WCF/Language',   'WoltLab/WCF/Environment',
+               'WoltLab/WCF/Date/Picker', 'EventHandler'
        ], 
        function(
                 favico,                   enquire,                  perfectScrollbar,         DateTimeRelative,
                 UiSimpleDropdown,         UiMobile,                 UiTabMenu,                UiFlexibleMenu,
                 UiDialog,                 UiTooltip,                Language,                 Environment,
-                DatePicker
+                DatePicker,               EventHandler
        )
 {
        "use strict";
@@ -34,6 +34,9 @@ define(
        window.WCF.Language.add = Language.add;
        window.WCF.Language.addObject = Language.addObject;
        
+       // WCF.System.Event compatibility
+       window.__wcf_bc_eventHandler = EventHandler;
+       
        /**
         * @exports     WoltLab/WCF/Bootstrap
         */
index 24befc5e0d324d43c2bbfa2ac3027039be036a1d..ec1421fad3f22d0064d5652255d1e858d2c91110 100644 (file)
@@ -213,6 +213,32 @@ define([], function() {
                        }
                        
                        return parseInt(value);
+               },
+               
+               /**
+                * Sets the inner HTML of given element and reinjects <script> elements to be properly executed.
+                * 
+                * @see         http://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0
+                * @param       {Element}       element         target element
+                * @param       {string}        innerHtml       HTML string
+                */
+               setInnerHtml: function(element, innerHtml) {
+                       element.innerHTML = innerHtml;
+                       
+                       var newScript, script, scripts = elBySelAll('script', element);
+                       for (var i = 0, length = scripts.length; i < length; i++) {
+                               script = scripts[i];
+                               newScript = elCreate('script');
+                               if (script.src) {
+                                       newScript.src = script.src;
+                               }
+                               else {
+                                       newScript.textContent = script.textContent;
+                               }
+                               
+                               element.appendChild(newScript);
+                               script.parentNode.removeChild(script);
+                       }
                }
        };
        
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Message/InlineEditor.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Message/InlineEditor.js
new file mode 100644 (file)
index 0000000..614d6d4
--- /dev/null
@@ -0,0 +1,230 @@
+define(['Ajax', 'Core', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util'], function(Ajax, Core, EventHandler, ObjectMap, DomTraverse, DomUtil) {
+       "use strict";
+       
+       var _activeElement = null;
+       var _elements = new ObjectMap();
+       var _options = {};
+       
+       var UiMessageInlineEditor = {
+               init: function(options) {
+                       _options = Core.extend({
+                               extendedForm: false,
+                               
+                               className: '',
+                               containerId: 0,
+                               editorPrefix: 'messageEditor',
+                               
+                               messageSelector: '.jsMessage'
+                       }, options);
+                       
+                       this._initElements();
+               },
+               
+               _initElements: function() {
+                       var button, element, elements = elBySelAll(_options.messageSelector);
+                       for (var i = 0, length = elements.length; i < length; i++) {
+                               element = elements[i];
+                               if (_elements.has(element)) {
+                                       continue;
+                               }
+                               
+                               if (elAttrBool(element, 'data-can-edit')) {
+                                       button = elBySel('.jsMessageEditButton', element);
+                                       if (button !== null) {
+                                               button.addEventListener('click', this._click.bind(this, element));
+                                       }
+                               }
+                               
+                               _elements.set(element, {
+                                       messageBody: null,
+                                       messageBodyEditor: null,
+                                       messageFooter: null,
+                                       messageText: null
+                               });
+                       }
+               },
+               
+               _click: function(element, event) {
+                       event.preventDefault();
+                       
+                       if (_activeElement !== null) {
+                               // TODO: show notification
+                       }
+                       
+                       _activeElement = element;
+                       
+                       this._prepare();
+                       
+                       Ajax.api(this, {
+                               actionName: 'beginEdit',
+                               parameters: {
+                                       containerID: _options.containerId,
+                                       objectID: elAttr(element, 'data-object-id')
+                               }
+                       });
+               },
+               
+               _prepare: function() {
+                       var data = _elements.get(_activeElement);
+                       if (data.messageBody === null) data.messageBody = elBySel('.messageBody', _activeElement);
+                       if (data.messageFooter === null) data.messageFooter = elBySel('.messageFooter', _activeElement);
+                       if (data.messageText === null) data.messageText = elBySel('.messageText', data.messageBody);
+                       
+                       var messageBodyEditor = elCreate('div');
+                       messageBodyEditor.className = 'messageBody editor';
+                       data.messageBodyEditor = messageBodyEditor;
+                       
+                       var icon = elCreate('span');
+                       icon.className = 'icon icon48 fa-spinner';
+                       messageBodyEditor.appendChild(icon);
+                       
+                       DomUtil.insertAfter(messageBodyEditor, data.messageBody);
+                       
+                       elHide(data.messageBody);
+               },
+               
+               _showEditor: function(data) {
+                       var id = this._getEditorId();
+                       var elementData = _elements.get(_activeElement);
+                       
+                       _activeElement.classList.add('jsInvalidQuoteTarget');
+                       var icon = DomTraverse.childByClass(elementData.messageBodyEditor, 'icon');
+                       icon.parentNode.removeChild(icon);
+                       
+                       var messageBody = elementData.messageBodyEditor;
+                       var editor = elCreate('div');
+                       editor.className = 'editorContainer';
+                       DomUtil.setInnerHtml(editor, data.returnValues.template);
+                       messageBody.appendChild(editor);
+                       
+                       // bind buttons
+                       var formSubmit = elBySel('.formSubmit', editor);
+                       
+                       var buttonSave = elBySel('button[data-type="save"]', formSubmit);
+                       buttonSave.addEventListener('click', this._save.bind(this));
+                       
+                       if (_options.extendedForm) {
+                               var buttonExtended = elBySel('button[data-type="extended"]', formSubmit);
+                               buttonExtended.addEventListener('click', this._prepareExtended.bind(this));
+                       }
+                       
+                       var buttonCancel = elBySel('button[data-type="cancel"]', formSubmit);
+                       buttonCancel.addEventListener('click', this._restoreMessage.bind(this));
+                       
+                       EventHandler.add('com.woltlab.wcf.redactor', 'submitEditor_' + id, (function(data) {
+                               data.cancel = true;
+                               
+                               this._save();
+                       }).bind(this));
+                       
+                       // hide message options
+                       elHide(elementData.messageFooter);
+                       
+                       var editorElement = elById(id);
+                       if ($.browser.redactor) {
+                               window.setTimeout((function() {
+                                       // TODO: quote manager
+                                       if (this._quoteManager) {
+                                               this._quoteManager.setAlternativeEditor($element);
+                                       }
+                                       
+                                       // TODO
+                                       new WCF.Effect.Scroll().scrollTo(_activeElement, true);
+                               }).bind(this), 250);
+                       }
+                       else {
+                               editorElement.focus();
+                       }
+               },
+               
+               _restoreMessage: function() {
+                       var elementData = _elements.get(_activeElement);
+                       
+                       this._destroyEditor();
+                       
+                       elRemove(elementData.messageBodyEditor);
+                       elementData.messageBodyEditor = null;
+                       
+                       elShow(elementData.messageBody);
+                       elShow(elementData.messageFooter);
+                       _activeElement.classList.remove('jsInvalidQuoteTarget');
+                       
+                       _activeElement = null;
+                       
+                       // @TODO
+                       if (this._quoteManager) {
+                               this._quoteManager.clearAlternativeEditor();
+                       }
+               },
+               
+               _save: function() {
+                       var parameters = {
+                               containerID: _options.containerId,
+                               data: {
+                                       message: ''
+                               },
+                               objectID: elAttr(_activeElement, 'data-object-id'),
+                               removeQuoteIDs: [] // @TODO
+                       };
+                       
+                       var id = this._getEditorId();
+                       EventHandler.fire('com.woltlab.wcf.redactor', 'getText_' + id, parameters.data);
+                       EventHandler.fire('com.woltlab.wcf.messageOptionsInline', 'submit_' + id, parameters);
+                       
+                       Ajax.api(this, {
+                               actionName: 'save',
+                               parameters: parameters
+                       });
+                       
+                       this._hideEditor();
+               },
+               
+               _showMessage: function() {
+                       
+               },
+               
+               _prepareExtended: function() {
+                       
+               },
+               
+               _hideEditor: function() {
+                       var elementData = _elements.get(_activeElement);
+                       elHide(DomTraverse.childByClass(elementData.messageBodyEditor, 'editorContainer'));
+                       
+                       var icon = elCreate('span');
+                       icon.className = 'icon icon48 fa-spinner';
+                       elementData.messageBodyEditor.appendChild(icon);
+               },
+               
+               _destroyEditor: function() {
+                       EventHandler.fire('com.woltlab.wcf.redactor', 'destroy_' + this._getEditorId());
+               },
+               
+               _getEditorId: function() {
+                       return _options.editorPrefix + elAttr(_activeElement, 'data-object-id');
+               },
+               
+               _ajaxSuccess: function(data) {
+                       switch (data.actionName) {
+                               case 'beginEdit':
+                                       this._showEditor(data);
+                                       break;
+                               
+                               case 'save':
+                                       this._showMessage(data);
+                                       break;
+                       }
+               },
+               
+               _ajaxSetup: function() {
+                       return {
+                               data: {
+                                       className: _options.className,
+                                       interfaceName: 'wcf\\data\\IMessageInlineEditorAction'
+                               }
+                       };
+               }
+       };
+       
+       return UiMessageInlineEditor;
+});
index 2574ca3b478acdee7b81bc77c66da0e2bd6eca63..19e66044f625c7a916a3f36a8e282a21d2f5339c 100644 (file)
                element.setAttribute(attribute, value);
        };
        
+       /**
+        * Shorthand function to retrieve a boolean attribute.
+        * 
+        * @param       {Element}       element         target element
+        * @param       {string}        attribute       attribute name
+        * @return      {boolean}       true if value is either `1` or `true`
+        */
+       window.elAttrBool = function(element, attribute) {
+               var value = elAttr(element, attribute);
+               
+               return (value === "1" || value === "true");
+       };
+       
        /**
         * Shorthand function to find elements by class name.
         * 
        };
        
        /**
-        * Shorthand function to check if an object has a property while ignoring the chain.
+        * Shorthand function to hide an element by setting its 'display' value to 'none'.
         * 
-        * @param       {object}        obj             target object
-        * @param       {string}        property        property name
-        * @return      {boolean}       false if property does not exist or belongs to the chain
+        * @param       {Element}       element         DOM element
         */
-       window.objOwns = function(obj, property) {
-               return obj.hasOwnProperty(property);
+       window.elHide = function(element) {
+               element.style.setProperty('display', 'none');
        };
        
        /**
-        * Shorthand function to hide an element by setting its 'display' value to 'none'.
+        * Shorthand function to remove an element.
         * 
         * @param       {Element}       element         DOM element
         */
-       window.elHide = function(element) {
-               element.style.setProperty('display', 'none');
+       window.elRemove = function(element) {
+               element.parentNode.removeChild(element);
        };
        
        /**
        window.elShow = function(element) {
                element.style.removeProperty('display');
        };
+       
+       /**
+        * Shorthand function to check if an object has a property while ignoring the chain.
+        * 
+        * @param       {object}        obj             target object
+        * @param       {string}        property        property name
+        * @return      {boolean}       false if property does not exist or belongs to the chain
+        */
+       window.objOwns = function(obj, property) {
+               return obj.hasOwnProperty(property);
+       };
 })(window, document);
index 1c1d983e18e51254338c45a98c541b361df851f1..07dd508859aa0d9f46dae0d4fa7650f1a0888594 100644 (file)
@@ -107,7 +107,7 @@ dl {
        
        &:not(.plain) > dd {
                &:not(.floated) > label ~ small {
-                       margin-left: 20px;
+                       margin-left: 24px;
                }
                
                > small:not(.innerError) {
@@ -124,7 +124,16 @@ dl {
                > dd {
                        display: block;
                        margin: 0;
+                       text-align: left;
                        width: 100%;
                }
+               
+               > dd + dt {
+                       margin-top: 15px;
+               }
+               
+               > dt:not(:empty) {
+                       margin-bottom: 5px;
+               }
        }
 }
\ No newline at end of file
index 45c9b41bd751843748ca4b88f9a01641a7bdbace..0458fb0ac5a30c2772378ea380954dcb263510ef 100644 (file)
 /* content - body */
 .messageBody {
        flex: 1 auto;
+       
+       &.editor {
+               align-items: center;
+               display: flex;
+               justify-content: center;
+               
+               > .icon {
+                       flex: 0 0 auto;
+               }
+               
+               > .editorContainer {
+                       flex: 1 auto;
+               }
+       }
 }
 
 /* content - footer */