/**
* 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
*/
_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.
*
* @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));
},
/**
* @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');
},
/**
*/
_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;
},
/**
*/
_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;
}
});
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.
*
* @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 || '';
},
/**
* @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));
}
});
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;
}
};
-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';
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);
elHide(elementData.messageFooter);
var editorElement = elById(id);
- if ($.browser.redactor) {
+ if (Environment.editor() === 'redactor') {
window.setTimeout((function() {
// TODO: quote manager
if (this._quoteManager) {
}
},
+ /**
+ * Restores the message view.
+ */
_restoreMessage: function() {
var elementData = _elements.get(_activeElement);
}
},
+ /**
+ * Saves the editor message.
+ */
_save: function() {
var parameters = {
containerID: _options.containerId,
data: {
message: ''
},
- objectID: elAttr(_activeElement, 'data-object-id'),
+ objectID: this._getObjectId(),
removeQuoteIDs: [] // @TODO
};
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'));
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) {
case 'beginEdit':
this._showEditor(data);
break;
+
+ case 'jumpToExtended':
+ window.location = data.returnValues.url;
+ break;
case 'save':
this._showMessage(data);
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);
}
};
--- /dev/null
+/**
+ * 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;
+});
'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'
}
executeCallbacks();
});
};
+ window.require.config = orgRequire.config;
function executeCallbacks() {
while (queue.length) {
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.
*
.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;
+ }
+}
/* 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
+}
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 ### */
}
}
+.messageTabMenu + .innerError {
+ box-sizing: border-box;
+ margin-top: -1px;
+ width: 100%;
+}
+
.messageTabMenuNavigation > ul {
background-color: @wcfContainerBackgroundColor;
font-size: 0;