Overhauled JavaScript for message inline editing
authorAlexander Ebert <ebert@woltlab.com>
Thu, 27 Aug 2015 11:17:29 +0000 (13:17 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 27 Aug 2015 11:17:29 +0000 (13:17 +0200)
12 files changed:
wcfsetup/install/files/js/WCF.Message.js
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/js/WoltLab/WCF/Dom/Util.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Message/InlineEditor.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Notification.js [new file with mode: 0644]
wcfsetup/install/files/js/require.config.js
wcfsetup/install/files/js/require.linearExecution.js
wcfsetup/install/files/js/wcf.globalHelper.js
wcfsetup/install/files/style/ui/alert.less
wcfsetup/install/files/style/ui/dialog.less
wcfsetup/install/files/style/ui/message.less
wcfsetup/install/files/style/ui/tabMenuMessage.less

index 2eadecfbebbec839f5b7d3d0241d3dcbff76238a..10d065ece0e81a6e673f583ca325e7e51f9c2511 100644 (file)
@@ -1211,15 +1211,11 @@ WCF.Message.QuickReply = Class.extend({
 /**
  * Provides an inline message editor.
  * 
+ * @deprecated 2.2 - please use `WoltLab/WCF/Ui/Message/InlineEditor` instead
+ * 
  * @param      integer         containerID
  */
 WCF.Message.InlineEditor = Class.extend({
-       /**
-        * currently active message
-        * @var string
-        */
-       _activeElementID: '',
-       
        /**
         * list of messages
         * @var object
@@ -1250,30 +1246,6 @@ WCF.Message.InlineEditor = Class.extend({
         */
        _messageEditorIDPrefix: 'messageEditor',
        
-       /**
-        * notification object
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * proxy object
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * quote manager object
-        * @var WCF.Message.Quote.Manager
-        */
-       _quoteManager: null,
-       
-       /**
-        * support for extended editing form
-        * @var boolean
-        */
-       _supportExtendedForm: false,
-       
        /**
         * Initializes a new WCF.Message.InlineEditor object.
         * 
@@ -1282,44 +1254,18 @@ WCF.Message.InlineEditor = Class.extend({
         * @param       WCF.Message.Quote.Manager       quoteManager
         */
        init: function(containerID, supportExtendedForm, quoteManager) {
-               this._activeElementID = '';
-               this._container = { };
-               this._containerID = parseInt(containerID);
-               this._dropdowns = { };
-               this._quoteManager = quoteManager || null;
-               this._supportExtendedForm = (supportExtendedForm) ? true : false;
-               this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
-               
-               this.initContainers();
-               
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.InlineEditor', $.proxy(this.initContainers, this));
-       },
-       
-       /**
-        * Initializes editing capability for all messages.
-        */
-       initContainers: function() {
-               $(this._messageContainerSelector).each($.proxy(function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
+               require(['WoltLab/WCF/Ui/Message/InlineEditor'], (function(UiMessageInlineEditor) {
+                       extendedForm: (supportExtendedForm === true),
                        
-                       if (!this._container[$containerID]) {
-                               this._container[$containerID] = $container;
-                               
-                               if ($container.data('canEditInline')) {
-                                       var $button = $container.find('.jsMessageEditButton:eq(0)').data('containerID', $containerID).click($.proxy(this._clickInline, this));
-                                       if ($container.data('canEdit')) $button.dblclick($.proxy(this._click, this));
-                               }
-                               else if ($container.data('canEdit')) {
-                                       $container.find('.jsMessageEditButton:eq(0)').data('containerID', $containerID).click($.proxy(this._click, this));
-                               }
-                       }
-               }, this));
+                       className: this._getClassName(),
+                       containerId: containerID,
+                       editorPrefix: this._messageEditorIDPrefix,
+                       
+                       messageSelector: this._messageContainerSelector,
+                       
+                       callbackDropdownInit: this._callbackDropdownInit.bind(this),
+                       callbackLegacyInitElements: this._callbackInitElements.bind(this)
+               }).bind(this));
        },
        
        /**
@@ -1330,102 +1276,15 @@ WCF.Message.InlineEditor = Class.extend({
         * @return      boolean
         */
        _click: function(event, containerID) {
-               var $containerID = (event === null) ? containerID : $(event.currentTarget).data('containerID');
-               if (this._activeElementID === '') {
-                       this._activeElementID = $containerID;
-                       this._prepare();
-                       
-                       this._proxy.setOption('data', {
-                               actionName: 'beginEdit',
-                               className: this._getClassName(),
-                               interfaceName: 'wcf\\data\\IMessageInlineEditorAction',
-                               parameters: {
-                                       containerID: this._containerID,
-                                       objectID: this._container[$containerID].data('objectID')
-                               }
-                       });
-                       this._proxy.setOption('failure', $.proxy(function() { this._cancel(); }, this));
-                       this._proxy.sendRequest();
-               }
-               else {
-                       var $notification = new WCF.System.Notification(WCF.Language.get('wcf.message.error.editorAlreadyInUse'), 'warning');
-                       $notification.show();
-               }
-               
-               // force closing dropdown to avoid displaying the dropdown after
-               // triple clicks
-               if (this._dropdowns[this._container[$containerID].data('objectID')]) {
-                       this._dropdowns[this._container[$containerID].data('objectID')].removeClass('dropdownOpen');
-               }
+               containerID = (event === null) ? ~~containerID : ~~elData(event.currentTarget, 'container-id');
                
-               if (event !== null) {
-                       event.stopPropagation();
-                       return false;
-               }
-       },
-       
-       /**
-        * Provides an inline dropdown menu instead of directly loading the WYSIWYG editor.
-        * 
-        * @param       object          event
-        * @return      boolean
-        */
-       _clickInline: function(event) {
-               var $button = $(event.currentTarget);
-               
-               if (!$button.hasClass('dropdownToggle')) {
-                       var $containerID = $button.data('containerID');
-                       
-                       $button.addClass('dropdownToggle').parent().addClass('dropdown');
-                       
-                       var $dropdownMenu = $('<ul class="dropdownMenu" />').insertAfter($button);
-                       this._initDropdownMenu($containerID, $dropdownMenu);
-                       
-                       WCF.DOMNodeInsertedHandler.execute();
-                       
-                       this._dropdowns[this._container[$containerID].data('objectID')] = $dropdownMenu;
-                       
-                       WCF.Dropdown.registerCallback($button.parent().wcfIdentify(), $.proxy(this._toggleDropdown, this));
-                       
-                       // trigger click event
-                       $button.trigger('click');
-               }
-               
-               event.stopPropagation();
-               return false;
-       },
-       
-       /**
-        * Handles errorneus editing requests.
-        * 
-        * @param       object          data
-        */
-       _failure: function(data) {
-               this._revertEditor();
-               
-               if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
-                       return true;
-               }
+               require(['WoltLab/WCF/Ui/Message/InlineEditor'], (function(UiMessageInlineEditor) {
+                       UiMessageInlineEditor.legacyEdit(containerID);
+               }).bind(this));
                
-               var $messageBody = this._container[this._activeElementID].find('.messageBody .messageInlineEditor');
-               var $innerError = $messageBody.children('small.innerError').empty();
-               if (!$innerError.length) {
-                       $innerError = $('<small class="innerError" />').insertBefore($messageBody.children('.formSubmit'));
+               if (event) {
+                       event.preventDefault();
                }
-               
-               $innerError.html(data.returnValues.errorType);
-               
-               return false;
-       },
-       
-       /**
-        * Forces message options to stay visible if toggling dropdown menu.
-        * 
-        * @param       string          containerID
-        * @param       string          action
-        */
-       _toggleDropdown: function(containerID, action) {
-               WCF.Dropdown.getDropdown(containerID).parents('.messageOptions').toggleClass('forceOpen');
        },
        
        /**
@@ -1436,288 +1295,10 @@ WCF.Message.InlineEditor = Class.extend({
         */
        _initDropdownMenu: function(containerID, dropdownMenu) { },
        
-       /**
-        * Prepares message for WYSIWYG display.
-        */
-       _prepare: function() {
-               var $messageBody = this._container[this._activeElementID].find('.messageBody');
-               $('<span class="icon icon48 icon-spinner" />').appendTo($messageBody);
-               
-               var $content = $messageBody.find('.messageText').hide();
-               
-               // hide unrelated content
-               $content.parent().children('.jsInlineEditorHideContent').hide();
-               $messageBody.children('.attachmentThumbnailList, .attachmentFileList').hide();
-       },
-       
-       /**
-        * Cancels editing and reverts to original message.
-        */
-       _cancel: function() {
-               var $container = this._container[this._activeElementID].removeClass('jsInvalidQuoteTarget');
-               
-               // remove editor
-               this._destroyEditor();
-               
-               // restore message
-               var $messageBody = $container.find('.messageBody');
-               $messageBody.children('.icon-spinner').remove();
-               $messageBody.find('.messageText').show();
-               $messageBody.children('.attachmentThumbnailList, .attachmentFileList').show();
-               
-               // show unrelated content
-               $messageBody.find('.jsInlineEditorHideContent').show();
-               
-               // revert message options
-               this._container[this._activeElementID].find('.messageOptions').removeClass('forceHidden');
-               
-               this._activeElementID = '';
-               
-               if (this._quoteManager) {
-                       this._quoteManager.clearAlternativeEditor();
-               }
-       },
-       
-       /**
-        * Handles successful AJAX calls.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (data.returnValues.actionName) {
-                       case 'beginEdit':
-                               this._showEditor(data);
-                       break;
-                       
-                       case 'save':
-                               this._showMessage(data);
-                       break;
-               }
-       },
-       
-       /**
-        * Shows WYSIWYG editor for active message.
-        * 
-        * @param       object          data
-        */
-       _showEditor: function(data) {
-               // revert failure function
-               this._proxy.setOption('failure', $.proxy(this._failure, this));
-               var $containerID = this._messageEditorIDPrefix + this._container[this._activeElementID].data('objectID');
-               
-               var $messageBody = this._container[this._activeElementID].addClass('jsInvalidQuoteTarget').find('.messageBody');
-               $messageBody.children('.icon-spinner').remove();
+       _callbackDropdownInit: function(element, dropdownMenu) {
+               this._initDropdownMenu($(element).wcfIdentify(), $(dropdownMenu));
                
-               // insert wysiwyg
-               $('' + data.returnValues.template).appendTo($messageBody);
-               
-               // bind buttons
-               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));
-               
-               // TODO: is this still used?
-               WCF.Message.Submit.registerButton(
-                       this._messageEditorIDPrefix + this._container[this._activeElementID].data('objectID'),
-                       $saveButton
-               );
-               
-               WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'submitEditor_' + $containerID, function(data) {
-                       data.cancel = true;
-                       
-                       $saveButton.trigger('click');
-               });
-               
-               // hide message options
-               this._container[this._activeElementID].find('.messageOptions').addClass('forceHidden');
-               
-               var $element = $('#' + $containerID);
-               if ($.browser.redactor) {
-                       new WCF.PeriodicalExecuter($.proxy(function(pe) {
-                               pe.stop();
-                               
-                               if (this._quoteManager) {
-                                       this._quoteManager.setAlternativeEditor($element);
-                               }
-                               
-                               new WCF.Effect.Scroll().scrollTo(this._container[this._activeElementID], true);
-                       }, this), 250);
-               }
-               else {
-                       $element.focus();
-               }
-       },
-       
-       /**
-        * Reverts editor.
-        */
-       _revertEditor: function() {
-               var $messageBody = this._container[this._activeElementID].removeClass('jsInvalidQuoteTarget').find('.messageBody');
-               $messageBody.children('span.icon-spinner').remove();
-               $messageBody.children('div:eq(0)').children(':not(.messageText)').show();
-               $messageBody.children('.attachmentThumbnailList, .attachmentFileList').show();
-               
-               // show unrelated content
-               $messageBody.find('.jsInlineEditorHideContent').show();
-               
-               if (this._quoteManager) {
-                       this._quoteManager.clearAlternativeEditor();
-               }
-       },
-       
-       /**
-        * Saves editor contents.
-        */
-       _save: function() {
-               var $container = this._container[this._activeElementID];
-               var $objectID = $container.data('objectID');
-               var $message = '';
-               
-               if ($.browser.redactor) {
-                       $message = $('#' + this._messageEditorIDPrefix + $objectID).redactor('wutil.getText');
-               }
-               else {
-                       $message = $('#' + this._messageEditorIDPrefix + $objectID).val();
-               }
-               
-               var $parameters = {
-                       containerID: this._containerID,
-                       data: {
-                               message: $message
-                       },
-                       objectID: $objectID,
-                       removeQuoteIDs: (this._quoteManager === null ? [ ] : this._quoteManager.getQuotesMarkedForRemoval())
-               };
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.messageOptionsInline', 'submit_' + this._messageEditorIDPrefix + $objectID, $parameters);
-               
-               this._proxy.setOption('data', {
-                       actionName: 'save',
-                       className: this._getClassName(),
-                       interfaceName: 'wcf\\data\\IMessageInlineEditorAction',
-                       parameters: $parameters
-               });
-               this._proxy.sendRequest();
-               
-               this._hideEditor();
-       },
-       
-       /**
-        * Prepares jumping to extended editing mode.
-        */
-       _prepareExtended: function() {
-               var $container = this._container[this._activeElementID];
-               var $objectID = $container.data('objectID');
-               var $message = '';
-               
-               if ($.browser.redactor) {
-                       $message = $('#' + this._messageEditorIDPrefix + $objectID).redactor('wutil.getText');
-               }
-               else {
-                       $message = $('#' + this._messageEditorIDPrefix + $objectID).val();
-               }
-               
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: 'jumpToExtended',
-                               className: this._getClassName(),
-                               parameters: {
-                                       containerID: this._containerID,
-                                       message: $message,
-                                       messageID: $objectID
-                               }
-                       },
-                       success: function(data, textStatus, jqXHR) {
-                               window.location = data.returnValues.url;
-                       }
-               });
-       },
-       
-       /**
-        * Hides WYSIWYG editor.
-        */
-       _hideEditor: function() {
-               var $messageBody = this._container[this._activeElementID].removeClass('jsInvalidQuoteTarget').find('.messageBody');
-               $('<span class="icon icon48 icon-spinner" />').appendTo($messageBody);
-               $messageBody.children('div:eq(0)').children().hide();
-               $messageBody.children('.attachmentThumbnailList, .attachmentFileList').show();
-               
-               // show unrelated content
-               $messageBody.find('.jsInlineEditorHideContent').show();
-               
-               if (this._quoteManager) {
-                       this._quoteManager.clearAlternativeEditor();
-               }
-       },
-       
-       /**
-        * Shows rendered message.
-        * 
-        * @param       object          data
-        */
-       _showMessage: function(data) {
-               var $container = this._container[this._activeElementID].removeClass('jsInvalidQuoteTarget');
-               var $messageBody = $container.find('.messageBody');
-               $messageBody.children('.icon-spinner').remove();
-               var $content = $messageBody.children('div:eq(0)');
-               
-               // show unrelated content
-               $content.parent().children('.jsInlineEditorHideContent').show();
-               
-               // revert message options
-               this._container[this._activeElementID].find('.messageOptions').removeClass('forceHidden');
-               
-               // remove editor
-               this._destroyEditor();
-               
-               $content.children('.messageText').html(data.returnValues.message).show();
-               
-               if (data.returnValues.attachmentList == undefined) {
-                       $messageBody.children('.attachmentThumbnailList, .attachmentFileList').show();
-               }
-               else {
-                       $messageBody.children('.attachmentThumbnailList, .attachmentFileList').remove();
-                       
-                       if (data.returnValues.attachmentList) {
-                               $(data.returnValues.attachmentList).insertAfter($messageBody.children('div:eq(0)'));
-                       }
-               }
-               
-               this._activeElementID = '';
-               
-               this._updateHistory(this._getHash($container.data('objectID')));
-               
-               this._notification.show();
-               
-               if (this._quoteManager) {
-                       this._quoteManager.clearAlternativeEditor();
-                       this._quoteManager.countQuotes();
-               }
-       },
-       
-       /**
-        * Destroys editor instance and removes its DOM elements.
-        */
-       _destroyEditor: function() {
-               var $container = this._container[this._activeElementID];
-               
-               // destroy editor
-               if ($.browser.redactor) {
-                       var $target = $('#' + this._messageEditorIDPrefix + $container.data('objectID'));
-                       $target.redactor('wutil.autosavePause');
-                       $target.redactor('wutil.autosavePurge');
-                       $target.redactor('core.destroy');
-               }
-               
-               // purge DOM elements
-               $container.find('.messageBody > div > .messageInlineEditor').remove();
-               
-               // remove event listeners
-               WCF.System.Event.removeAllListeners('com.woltlab.wcf.messageOptionsInline', 'submit_' + this._messageEditorIDPrefix + $container.data('objectID'));
+               return null;
        },
        
        /**
@@ -1727,25 +1308,6 @@ WCF.Message.InlineEditor = Class.extend({
         */
        _getClassName: function() {
                return '';
-       },
-       
-       /**
-        * Returns the hash added to the url after successfully editing a message.
-        * 
-        * @return      string
-        */
-       _getHash: function(objectID) {
-               return '#message' + objectID;
-       },
-       
-       /**
-        * Updates the history to avoid old content when going back in the browser
-        * history.
-        * 
-        * @param       hash
-        */
-       _updateHistory: function(hash) {
-               window.location.hash = hash;
        }
 });
 
index a093cada756b7da55e4fc48f8de09d96d9a3e562..16620cec6c4c5688e77255c76570e65107e115ae 100755 (executable)
@@ -5346,114 +5346,18 @@ WCF.System.Captcha = {
 
 WCF.System.Page = { };
 
-WCF.System.Page.Multiple = Class.extend({
-       _cache: { },
-       _options: { },
-       _pageNo: 1,
-       _pages: 0,
-       _previousPageNo: 0,
-       
-       init: function(options) {
-               this._options = $.extend({
-                       // elements
-                       container: null,
-                       pagination: null,
-                       
-                       // callbacks
-                       loadItems: null
-               }, options);
-               
-               this._cache = { };
-               this._pageNo = 1;
-               this._pages = 0;
-               this._previousPageNo = 0;
-               
-               if (this._pagination.data('pages')) {
-                       this._pagination.wcfPages({
-                               maxPage: this._pagination.data('pages')
-                       }).on('wcfpagesswitched', $.proxy(this._showPage, this));
-               }
-       },
-       
-       /**
-        * Callback after page has changed.
-        * 
-        * @param       object          event
-        * @param       object          data
-        */
-       _showPage: function(event, data) {
-               if (data && data.activePage) {
-                       if (!data.template) {
-                               this._previousPageNo = this._pageNo;
-                       }
-                       
-                       this._pageNo = data.activePage;
-               }
-               
-               if (this._cache[this._pageNo] || (data && data.template)) {
-                       this._cache[this._previousPageNo] = this._list.children().detach();
-                       
-                       if (data && data.template) {
-                               this._list.html(data.template);
-                       }
-                       else {
-                               this._list.append(this._cache[this._pageNo]);
-                       }
-               }
-               else {
-                       this._options.loadItems();
-               }
-       },
-       
-       showPage: function(pageNo, template) {
-               this._showPage(null, {
-                       activePage: pageNo,
-                       template: template
-               });
-       },
-       
-       getPageNo: function() {
-               return this._pageNo;
-       }
-});
-
 /**
  * System notification overlays.
  * 
+ * @deprecated 2.2 - please use `Ui/Notification` instead
+ * 
  * @param      string          message
  * @param      string          cssClassNames
  */
 WCF.System.Notification = Class.extend({
-       /**
-        * callback on notification close
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * CSS class names
-        * @var string
-        */
        _cssClassNames: '',
-       
-       /**
-        * notification message
-        * @var string
-        */
        _message: '',
        
-       /**
-        * notification overlay
-        * @var jQuery
-        */
-       _overlay: null,
-       
-       /**
-        * periodical timer
-        * @var WCF.PeriodicalExecuter
-        */
-       _timer: null,
-       
        /**
         * Creates a new system notification overlay.
         * 
@@ -5461,17 +5365,8 @@ WCF.System.Notification = Class.extend({
         * @param       string          cssClassNames
         */
        init: function(message, cssClassNames) {
-               this._cssClassNames = cssClassNames || 'success';
-               this._message = message || WCF.Language.get('wcf.global.success');
-               this._overlay = $('#systemNotification');
-               this._timer = null;
-               
-               if (!this._overlay.length) {
-                       this._overlay = $('<div id="systemNotification"><p></p></div>').hide().appendTo(document.body);
-                       this._overlay.children('p').click((function() {
-                               this._hide();
-                       }).bind(this));
-               }
+               this._cssClassNames = cssClassNames || '';
+               this._message = message || '';
        },
        
        /**
@@ -5483,38 +5378,13 @@ WCF.System.Notification = Class.extend({
         * @param       string          cssClassName
         */
        show: function(callback, duration, message, cssClassNames) {
-               duration = parseInt(duration);
-               if (!duration) duration = 2000;
-               
-               if (callback && $.isFunction(callback)) {
-                       this._callback = callback;
-               }
-               
-               this._overlay.children('p').html((message || this._message));
-               this._overlay.children('p').removeClass().addClass((cssClassNames || this._cssClassNames));
-               
-               // hide overlay after specified duration
-               this._timer = new WCF.PeriodicalExecuter($.proxy(this._hide, this), duration);
-               
-               this._overlay.wcfFadeIn(undefined, 300);
-       },
-       
-       /**
-        * Hides the notification overlay after executing the callback.
-        * 
-        * @param       WCF.PeriodicalExecuter          pe
-        */
-       _hide: function(pe) {
-               pe = (pe) ? pe : this._timer;
-               
-               if (this._callback !== null) {
-                       this._callback();
-               }
-               
-               this._overlay.wcfFadeOut(undefined, 300);
-               
-               pe.stop();
-               pe = null;
+               require(['Ui/Notification'], (function(UiNotification) {
+                       UiNotification.show(
+                               message || this._message,
+                               callback,
+                               cssClassNames || this._cssClassNames
+                       );
+               }).bind(this));
        }
 });
 
index ec1421fad3f22d0064d5652255d1e858d2c91110..5086b7d64a5251906315dc38f794f3dce678ec18 100644 (file)
@@ -239,6 +239,25 @@ define([], function() {
                                element.appendChild(newScript);
                                script.parentNode.removeChild(script);
                        }
+               },
+               
+               /**
+                * Returns true if `element` contains the `child` element.
+                * 
+                * @param       {Element}       element         container element
+                * @param       {Element}       child           child element
+                * @returns     {boolean}       true if `child` is a (in-)direct child of `element`
+                */
+               contains: function(element, child) {
+                       while (child !== null) {
+                               child = child.parentNode;
+                               
+                               if (element === child) {
+                                       return true;
+                               }
+                       }
+                       
+                       return false;
                }
        };
        
index 614d6d48041dbf4501d8c0b16885a693a751f21e..1a006476080da2b9acfd1ed69ce1e25c454fa668 100644 (file)
-define(['Ajax', 'Core', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util'], function(Ajax, Core, EventHandler, ObjectMap, DomTraverse, DomUtil) {
+/**
+ * Flexible message inline editor.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Message/InlineEditor
+ */
+define(
+       [
+               'Ajax',         'Core',            'Dictionary',        'Environment',
+               'EventHandler', 'Language',        'ObjectMap',         'Dom/Traverse',
+               'Dom/Util',     'Ui/Notification', 'Ui/SimpleDropdown'
+       ],
+       function(
+               Ajax,            Core,              Dictionary,          Environment,
+               EventHandler,    Language,          ObjectMap,           DomTraverse,
+               DomUtil,         UiNotification,    UiSimpleDropdown
+       )
+{
        "use strict";
        
        var _activeElement = null;
+       var _dropdownMenus = new Dictionary();
        var _elements = new ObjectMap();
        var _options = {};
        
+       /**
+        * @exports     WoltLab/WCF/Ui/Message/InlineEditor
+        */
        var UiMessageInlineEditor = {
+               /**
+                * Initializes the message inline editor.
+                * 
+                * @param       {object<mixed>}         options         list of configuration options
+                */
                init: function(options) {
                        _options = Core.extend({
-                               extendedForm: false,
+                               canEditInline: false,
+                               extendedForm: true,
                                
                                className: '',
                                containerId: 0,
                                editorPrefix: 'messageEditor',
                                
-                               messageSelector: '.jsMessage'
+                               messageSelector: '.jsMessage',
+                               
+                               callbackDropdownInit: null,
+                               callbackDropdownOpen: null
                        }, options);
                        
                        this._initElements();
                },
                
+               /**
+                * Initializes each applicable message.
+                */
                _initElements: function() {
-                       var button, element, elements = elBySelAll(_options.messageSelector);
+                       var button, canEdit, 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 = elBySel('.jsMessageEditButton', element);
+                               if (button !== null) {
+                                       canEdit = elAttrBool(element, 'data-can-edit');
+                                       
+                                       if (_options.canEditInline) {
+                                               button.addEventListener('click', this._clickDropdown.bind(this, element));
+                                               
+                                               if (canEdit) {
+                                                       button.addEventListener('dblclick', this._click.bind(this, element));
+                                               }
+                                       }
+                                       else if (canEdit) {
                                                button.addEventListener('click', this._click.bind(this, element));
                                        }
                                }
                                
+                               
+                               var messageBody = elBySel('.messageBody', element);
+                               var messageFooter = elBySel('.messageFooter', element);
+                               
                                _elements.set(element, {
-                                       messageBody: null,
+                                       messageBody: messageBody,
                                        messageBodyEditor: null,
-                                       messageFooter: null,
-                                       messageText: null
+                                       messageFooter: messageFooter,
+                                       messageFooterButtons: elBySel('.messageFooterButtons', messageFooter),
+                                       messageText: elBySel('.messageText', messageBody)
                                });
                        }
                },
                
+               /**
+                * Handles clicks on the edit button or the edit dropdown item.
+                * 
+                * @param       {Element}       element         message element
+                * @param       {?object}       event           event object
+                */
                _click: function(element, event) {
+                       if (event !== null) event.preventDefault();
+                       
+                       if (_activeElement === null) {
+                               _activeElement = element;
+                               
+                               this._prepare();
+                               
+                               Ajax.api(this, {
+                                       actionName: 'beginEdit',
+                                       parameters: {
+                                               containerID: _options.containerId,
+                                               objectID: this._getObjectId(element)
+                                       }
+                               });
+                       }
+                       else {
+                               UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
+                       }
+               },
+               
+               /**
+                * Creates and opens the dropdown on first usage.
+                * 
+                * @param       {Element}       element         message element
+                * @param       {object}        event           event object
+                */
+               _clickDropdown: function(element, event) {
                        event.preventDefault();
                        
-                       if (_activeElement !== null) {
-                               // TODO: show notification
+                       var button = event.currentTarget;
+                       if (button.classList.contains('dropdownToggle')) {
+                               return;
                        }
                        
-                       _activeElement = element;
+                       // build dropdown
+                       button.classList.add('dropdownToggle');
+                       button.parentNode.classList.add('dropdown');
                        
-                       this._prepare();
+                       var dropdownMenu = elCreate('ul');
+                       dropdownMenu.className = 'dropdownMenu';
                        
-                       Ajax.api(this, {
-                               actionName: 'beginEdit',
-                               parameters: {
-                                       containerID: _options.containerId,
-                                       objectID: elAttr(element, 'data-object-id')
+                       var items = _options.callbackDropdownInit(element, dropdownMenu);
+                       if (items !== null) this._dropdownBuild(element, dropdownMenu, items);
+                       
+                       DomUtil.insertAfter(dropdownMenu, button);
+                       
+                       _dropdownMenus.set(this._getObjectId(element), dropdownMenu);
+                       
+                       UiSimpleDropdown.init(button, true);
+                       
+                       var id = DomUtil.identify(button.parentNode);
+                       UiSimpleDropdown.registerCallback(id, this._dropdownToggle.bind(this, element));
+               },
+               
+               /**
+                * Creates the dropdown menu on first usage.
+                * 
+                * @param       {Element}               element         message element
+                * @param       {Element}               dropdownMenu    dropdown menu
+                * @param       {array<object>}         items           list of dropdown items
+                */
+               _dropdownBuild: function(element, dropdownMenu, items) {
+                       var item, label, listItem;
+                       var callbackClick = this._clickDropdownItem.bind(this, element);
+                       
+                       for (var i = 0, length = items.length; i < length; i++) {
+                               item = items[i];
+                               listItem = elCreate('li');
+                               
+                               if (item.special === 'divider') {
+                                       listItem.className = 'dropdownDivider';
                                }
-                       });
+                               else {
+                                       elData(listItem, 'action', item.action);
+                                       label = elCreate('span');
+                                       label.textContent = Language.get(item.label);
+                                       listItem.appendChild(label);
+                                       
+                                       if (item.special === 'edit') {
+                                               listItem.addEventListener('click', this._click.bind(this, element));
+                                       }
+                                       else {
+                                               listItem.addEventListener('click', callbackClick);
+                                       }
+                                       
+                                       if (item.visible === false) {
+                                               elHide(listItem);
+                                       }
+                               }
+                               
+                               dropdownMenu.appendChild(listItem);
+                       }
                },
                
+               /**
+                * Callback for dropdown toggle.
+                * 
+                * @param       {Element}       element         message element
+                * @param       {integer}       containerId     container id
+                * @param       {string}        action          toggle action, either 'open' or 'close'
+                */
+               _dropdownToggle: function(element, containerId, action) {
+                       _elements.get(element).messageFooterButtons.classList[(action === 'open' ? 'add' : 'remove')]('forceVisible');
+                       
+                       if (action === 'open' && typeof _options.callbackDropdownOpen === 'function') {
+                               _options.callbackDropdownOpen(element, this._getObjectId(element));
+                       }
+               },
+               
+               /**
+                * Handles clicks on a dropdown item.
+                * 
+                * @param       {Element}       element         message element
+                * @param       {object}        event           event object
+                */
+               _clickDropdownItem: function(element, event) {
+                       event.preventDefault();
+                       
+                       _options.callbackDropdownSelect(element, this._getObjectId(element), elAttr(event.currentTarget, 'data-class-name'));
+               },
+               
+               /**
+                * Prepares the message for editor display.
+                */
                _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';
@@ -83,6 +240,11 @@ define(['Ajax', 'Core', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util']
                        elHide(data.messageBody);
                },
                
+               /**
+                * Shows the message editor.
+                * 
+                * @param       {object}        data            ajax response data
+                */
                _showEditor: function(data) {
                        var id = this._getEditorId();
                        var elementData = _elements.get(_activeElement);
@@ -121,7 +283,7 @@ define(['Ajax', 'Core', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util']
                        elHide(elementData.messageFooter);
                        
                        var editorElement = elById(id);
-                       if ($.browser.redactor) {
+                       if (Environment.editor() === 'redactor') {
                                window.setTimeout((function() {
                                        // TODO: quote manager
                                        if (this._quoteManager) {
@@ -137,6 +299,9 @@ define(['Ajax', 'Core', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util']
                        }
                },
                
+               /**
+                * Restores the message view.
+                */
                _restoreMessage: function() {
                        var elementData = _elements.get(_activeElement);
                        
@@ -157,13 +322,16 @@ define(['Ajax', 'Core', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util']
                        }
                },
                
+               /**
+                * Saves the editor message.
+                */
                _save: function() {
                        var parameters = {
                                containerID: _options.containerId,
                                data: {
                                        message: ''
                                },
-                               objectID: elAttr(_activeElement, 'data-object-id'),
+                               objectID: this._getObjectId(),
                                removeQuoteIDs: [] // @TODO
                        };
                        
@@ -179,14 +347,69 @@ define(['Ajax', 'Core', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util']
                        this._hideEditor();
                },
                
-               _showMessage: function() {
+               /**
+                * Shows the update message.
+                * 
+                * @param       {object}        data            ajax response data
+                */
+               _showMessage: function(data) {
+                       var elementData = _elements.get(_activeElement);
+                       var attachmentLists = elBySelAll('.attachmentThumbnailList, .attachmentFileList', elementData.messageBody);
                        
+                       // set new content
+                       DomUtil.setInnerHtml(elementData.messageBody, data.returnValues.message);
+                       
+                       // handle attachment list
+                       if (typeof data.returnValues.attachmentList === 'string') {
+                               for (var i = 0, length = attachmentLists.length; i < length; i++) {
+                                       elRemove(attachmentLists[i]);
+                               }
+                               
+                               var element = elCreate('div');
+                               DomUtil.setInnerHtml(element, data.returnValues.attachmentList);
+                               
+                               while (element.childNodes.length) {
+                                       elementData.messageBody.appendChild(element.childNodes[0]);
+                               }
+                       }
+                       
+                       this._restoreMessage();
+                       
+                       this._updateHistory(this._getHash(this._getObjectId()));
+                       
+                       UiNotification.show();
+                       
+                       // @TODO
+                       return;
+                       
+                       if (this._quoteManager) {
+                               this._quoteManager.clearAlternativeEditor();
+                               this._quoteManager.countQuotes();
+                       }
                },
                
+               /**
+                * Initiates the jump to the extended edit form.
+                */
                _prepareExtended: function() {
+                       var data = {
+                               actionName: 'jumpToExtended',
+                               parameters: {
+                                       containerID: _options.containerId,
+                                       message: '',
+                                       messageID: this._getObjectId()
+                               }
+                       };
                        
+                       var id = this._getEditorId();
+                       EventHandler.fire('com.woltlab.wcf.redactor', 'getText_' + id, data.parameters);
+                       
+                       Ajax.api(this, data);
                },
                
+               /**
+                * Hides the editor from view.
+                */
                _hideEditor: function() {
                        var elementData = _elements.get(_activeElement);
                        elHide(DomTraverse.childByClass(elementData.messageBodyEditor, 'editorContainer'));
@@ -196,12 +419,84 @@ define(['Ajax', 'Core', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util']
                        elementData.messageBodyEditor.appendChild(icon);
                },
                
+               /**
+                * Restores the previously hidden editor.
+                */
+               _restoreEditor: function() {
+                       var elementData = _elements.get(_activeElement);
+                       var icon = elBySel('.fa-spinner', elementData.messageBodyEditor);
+                       elRemove(icon);
+                       console.debug(icon);
+                       elShow(DomTraverse.childByClass(elementData.messageBodyEditor, 'editorContainer'));
+               },
+               
+               /**
+                * Destroys the editor instance.
+                */
                _destroyEditor: function() {
                        EventHandler.fire('com.woltlab.wcf.redactor', 'destroy_' + this._getEditorId());
                },
                
+               /**
+                * Returns the hash added to the url after successfully editing a message.
+                * 
+                * @param       {integer}       objectId        message object id
+                * @return      string
+                */
+               _getHash: function(objectId) {
+                       return '#message' + objectId;
+               },
+               
+               /**
+                * Updates the history to avoid old content when going back in the browser
+                * history.
+                * 
+                * @param       hash
+                */
+               _updateHistory: function(hash) {
+                       window.location.hash = hash;
+               },
+               
+               /**
+                * Returns the unique editor id.
+                * 
+                * @return      {string}        editor id
+                */
                _getEditorId: function() {
-                       return _options.editorPrefix + elAttr(_activeElement, 'data-object-id');
+                       return _options.editorPrefix + this._getObjectId();
+               },
+               
+               /**
+                * Returns the element's `data-object-id` value.
+                * 
+                * @param       {Element=}      element         target element, `_activeElement` if empty
+                * @return      {integer}
+                */
+               _getObjectId: function(element) {
+                       return ~~elAttr(element || _activeElement, 'data-object-id');
+               },
+               
+               _ajaxFailure: function(data) {
+                       this._restoreEditor();
+                       
+                       if (!data || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+                               return true;
+                       }
+                       
+                       var elementData = _elements.get(_activeElement);
+                       var innerError = elBySel('.innerError', elementData.messageBodyEditor);
+                       if (innerError === null) {
+                               innerError = elCreate('small');
+                               innerError.className = 'innerError';
+                               
+                               var editor = elBySel('.redactor-editor', elementData.messageBodyEditor);
+                               DomUtil.insertAfter(innerError, editor);
+                       }
+                       
+                       
+                       innerError.textContent = data.returnValues.errorType;
+                       
+                       return false;
                },
                
                _ajaxSuccess: function(data) {
@@ -209,6 +504,10 @@ define(['Ajax', 'Core', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util']
                                case 'beginEdit':
                                        this._showEditor(data);
                                        break;
+                                       
+                               case 'jumpToExtended':
+                                       window.location = data.returnValues.url;
+                                       break;
                                
                                case 'save':
                                        this._showMessage(data);
@@ -223,6 +522,17 @@ define(['Ajax', 'Core', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util']
                                        interfaceName: 'wcf\\data\\IMessageInlineEditorAction'
                                }
                        };
+               },
+               
+               /** @deprecated 2.2 - used only for backward compatibility with `WCF.Message.InlineEditor` */
+               legacyGetDropdownMenus: function() { return _dropdownMenus; },
+               
+               /** @deprecated 2.2 - used only for backward compatibility with `WCF.Message.InlineEditor` */
+               legacyGetElements: function() { return _elements; },
+               
+               /** @deprecated 2.2 - used only for backward compatibility with `WCF.Message.InlineEditor` */
+               legacyEdit: function(containerId) {
+                       this._click(elById(containerId), null);
                }
        };
        
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Notification.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Notification.js
new file mode 100644 (file)
index 0000000..c2012db
--- /dev/null
@@ -0,0 +1,84 @@
+/**
+ * Simple notification overlay.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Notification
+ */
+define(['Language'], function(Language) {
+       "use strict";
+       
+       var _busy = false;
+       var _callback = null;
+       var _message = null;
+       var _notificationElement = null;
+       var _timeout = null;
+       
+       var _callbackHide = null;
+       
+       /**
+        * @exports     WoltLab/WCF/Ui/Notification
+        */
+       var UiNotification = {
+               /**
+                * Shows a notification.
+                * 
+                * @param       {string}        message         message
+                * @param       {function=}     callback        callback function to be executed once notification is being hidden
+                * @param       {string=}       cssClassName    alternate CSS class name, defaults to 'success'
+                */
+               show: function(message, callback, cssClassName) {
+                       if (_busy) {
+                               return;
+                       }
+                       
+                       this._init();
+                       
+                       _callback = (typeof callback === 'function') ? callback : null;
+                       _message.className = cssClassName || 'success';
+                       _message.textContent = Language.get(message || 'wcf.global.success');
+                       
+                       _busy = true;
+                       
+                       _notificationElement.classList.add('active');
+                       
+                       _timeout = setTimeout(_callbackHide, 2000);
+               },
+               
+               /**
+                * Initializes the UI elements.
+                */
+               _init: function() {
+                       if (_notificationElement === null) {
+                               _callbackHide = this._hide.bind(this);
+                               
+                               _notificationElement = elCreate('div');
+                               _notificationElement.id = 'systemNotification';
+                               
+                               _message = elCreate('p');
+                               _message.addEventListener('click', _callbackHide);
+                               _notificationElement.appendChild(_message);
+                               
+                               document.body.appendChild(_notificationElement);
+                       }
+               },
+               
+               /**
+                * Hides the notification and invokes the callback if provided.
+                */
+               _hide: function() {
+                       clearTimeout(_timeout);
+                       
+                       _notificationElement.classList.remove('active');
+                       
+                       if (_callback !== null) {
+                               _callback();
+                       }
+                       
+                       _busy = false;
+               }
+       };
+       
+       return UiNotification;
+});
index e63ed6431c1b952a1a6fc1252e0fb62e3391d85d..1be3aad9c0342271910fd491a1a02e27e620c672 100644 (file)
@@ -31,6 +31,7 @@ requirejs.config({
                        'Ui/CloseOverlay': 'WoltLab/WCF/Ui/CloseOverlay',
                        'Ui/Confirmation': 'WoltLab/WCF/Ui/Confirmation',
                        'Ui/Dialog': 'WoltLab/WCF/Ui/Dialog',
+                       'Ui/Notification': 'WoltLab/WCF/Ui/Notification',
                        'Ui/SimpleDropdown': 'WoltLab/WCF/Ui/Dropdown/Simple',
                        'Ui/TabMenu': 'WoltLab/WCF/Ui/TabMenu'
                }
index 375e8353ea68ab41d6506437a972bf576ebe84a5..e134fbbfce33276e6c8feafa700ae6317c59c19c 100644 (file)
@@ -19,6 +19,7 @@
                        executeCallbacks();
                });
        };
+       window.require.config = orgRequire.config;
        
        function executeCallbacks() {
                while (queue.length) {
index 19e66044f625c7a916a3f36a8e282a21d2f5339c..c3d79feb8c83bc27bb7a9f93aea8e4cf844bb9a7 100644 (file)
                return document.createElement(tagName);
        };
        
+       /**
+        * Shorthand function to retrieve or set a 'data-' attribute.
+        * 
+        * @param       {Element}       element         target element
+        * @param       {string}        attribute       attribute name
+        * @param       {mixed=}        value           attribute value, omit if attribute should be read
+        * @return      {(string|undefined)}            attribute value, empty string if attribute is not set or undefined if `value` was omitted
+        */
+       window.elData = function(element, attribute, value) {
+               attribute = 'data-' + attribute;
+               
+               if (value === undefined) {
+                       return element.getAttribute(attribute) || '';
+               }
+               
+               element.setAttribute(attribute, value);
+       };
+       
+       /**
+        * Shorthand function to retrieve a boolean 'data-' attribute.
+        * 
+        * @param       {Element}       element         target element
+        * @param       {string}        attribute       attribute name
+        * @return      {boolean}       true if value is either `1` or `true`
+        */
+       window.elDataBool = function(element, attribute) {
+               var value = elData(element, attribute);
+               
+               return (value === "1" || value === "true");
+       }; 
+       
        /**
         * Shorthand function to hide an element by setting its 'display' value to 'none'.
         * 
                element.style.removeProperty('display');
        };
        
+       /**
+        * Shorthand function to iterative over an array-like object, arguments passed are the value and the index second.
+        * 
+        * Do not use this function if a simple `for()` is enough or `list` is a plain object.
+        * 
+        * @param       {object}        list            array-like object
+        * @param       {function}      callback        callback function
+        */
+       window.forEach = function(list, callback) {
+               for (var i = 0, length = list.length; i < length; i++) {
+                       callback(list[i], i);
+               }
+       };
+       
        /**
         * Shorthand function to check if an object has a property while ignoring the chain.
         * 
index a5334e0ffbb66e80350e00206430a00032ac4f0e..f83dc8c00bc5f3f9f223c6272bbd9671e3881ba0 100644 (file)
@@ -1,5 +1,6 @@
 .error,
-.info {
+.info,
+.success {
        border-radius: 3px;
        padding: 10px 20px;
 }
 .info {
        background-color: rgb(217, 237, 247);
        color: rgb(49, 112, 143);
-}
\ No newline at end of file
+}
+
+.success {
+       background-color: rgb(223, 240, 216);
+       color: rgb(60, 118, 61);
+}
+
+/* inline errors */
+.innerError {
+       background-color: rgb(242, 222, 222);
+       color: rgb(169, 68, 66);
+       display: table;
+       line-height: 1.5;
+       margin-top: 8px;
+       padding: 5px 10px;
+       position: relative;
+       
+       /* pointer */
+       &::before {
+               border: 6px solid transparent;
+               border-bottom-color: rgb(242, 222, 222);
+               border-top-width: 0;
+               content: "";
+               display: inline-block;
+               left: 10px;
+               position: absolute;
+               top: -6px;
+               z-index: 101;
+       }
+}
index 8ebbd0827ae9104001c9bedd283262ffaf9282cb..a72f73a99fba11b78f7e295292cd9b0a2de19aa1 100644 (file)
 /* notification overlay */
 #systemNotification {
        left: 0;
+       opacity: 0;
        pointer-events: none;
        position: fixed;
        top: 0;
+       transform: translateY(-100%);
+       transition: visibility .2s linear .2s, transform .2s linear, opacity .2s linear;
+       visibility: hidden;
        width: 100%;
        z-index: 460;
        
+       &.active {
+               opacity: 1;
+               transform: translateY(0%);
+               transition-delay: 0s;
+               visibility: visible;
+       }
+       
        > p {
                border-top-left-radius: 0;
                border-top-right-radius: 0;
                max-width: 80%;
                pointer-events: auto;
                
-               -webkit-user-select: none;
-               -moz-user-select: none;
-               -ms-user-select: none;
-               user-select: none;
+               .userSelectNone;
        }
-}
\ No newline at end of file
+}
index 0458fb0ac5a30c2772378ea380954dcb263510ef..d011236aed8b95fbd4b718f31e9be25c54ba86c9 100644 (file)
        opacity: 1;
 }
 
-.message .messageFooter .messageFooterButtons {
+.messageFooterButtons {
        .inlineList;
        
        justify-content: flex-end;
        opacity: .3;
        transition: opacity .2s linear;
        
+       &.forceVisible {
+               opacity: 1 !important;
+       }
+       
        > li:not(:last-child) {
                margin-right: 5px;
        }
 }
 
-.message:hover .messageFooterButtons {
-       opacity: .6;
-}
-
-.message .messageFooter .messageFooterButtons:hover {
-       opacity: 1;
+.message:hover {
+       .messageFooterButtons {
+               opacity: .6;
+               
+               &:hover {
+                       opacity: 1;
+               }
+       }
 }
 
 /* ### message groups ### */
index 1bed1d74682574f777bbc2aed2361ec5b9ad076b..145c86d1c9f9a7378958e5cdf7cb4fcf08821d22 100644 (file)
        }
 }
 
+.messageTabMenu + .innerError {
+       box-sizing: border-box;
+       margin-top: -1px;
+       width: 100%;
+}
+
 .messageTabMenuNavigation > ul {
        background-color: @wcfContainerBackgroundColor;
        font-size: 0;