From 40a18c981ebd6905221a5e3c18fe9e1a88ad5b2d Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Thu, 5 Nov 2020 17:20:27 +0100 Subject: [PATCH] Convert `Ui/Message/Manager` to TypeScript --- .../WoltLabSuite/Core/Ui/Message/Manager.js | 290 +++++++--------- .../WoltLabSuite/Core/Ui/Message/Manager.js | 323 ------------------ .../WoltLabSuite/Core/Ui/Message/Manager.ts | 287 ++++++++++++++++ 3 files changed, 401 insertions(+), 499 deletions(-) delete mode 100644 wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Message/Manager.js create mode 100644 wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Message/Manager.ts diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Manager.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Manager.js index 924cfb3c5e..ef6e6f0ac8 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Manager.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Manager.js @@ -1,240 +1,186 @@ /** * Provides access and editing of message properties. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Message/Manager + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Message/Manager */ -define(['Ajax', 'Core', 'Dictionary', 'Language', 'Dom/ChangeListener', 'Dom/Util'], function (Ajax, Core, Dictionary, Language, DomChangeListener, DomUtil) { +define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Change/Listener", "../../Language"], function (require, exports, tslib_1, Ajax, Core, Listener_1, Language) { "use strict"; - if (!COMPILER_TARGET_DEFAULT) { - var Fake = function () { }; - Fake.prototype = { - init: function () { }, - rebuild: function () { }, - getPermission: function () { }, - getPropertyValue: function () { }, - update: function () { }, - updateItems: function () { }, - updateAllItems: function () { }, - setNote: function () { }, - _update: function () { }, - _updateState: function () { }, - _toggleMessageStatus: function () { }, - _getAttributeName: function () { }, - _ajaxSuccess: function () { }, - _ajaxSetup: function () { } - }; - return Fake; - } - /** - * @param {Object} options initialization options - * @constructor - */ - function UiMessageManager(options) { this.init(options); } - UiMessageManager.prototype = { + Ajax = tslib_1.__importStar(Ajax); + Core = tslib_1.__importStar(Core); + Listener_1 = tslib_1.__importDefault(Listener_1); + Language = tslib_1.__importStar(Language); + class UiMessageManager { /** * Initializes a new manager instance. - * - * @param {Object} options initialization options */ - init: function (options) { - this._elements = null; + constructor(options) { + this._elements = new Map(); this._options = Core.extend({ - className: '', - selector: '' + className: "", + selector: "", }, options); this.rebuild(); - DomChangeListener.add('Ui/Message/Manager' + this._options.className, this.rebuild.bind(this)); - }, + Listener_1.default.add(`Ui/Message/Manager${this._options.className}`, this.rebuild.bind(this)); + } /** * Rebuilds the list of observed messages. You should call this method whenever a * message has been either added or removed from the document. */ - rebuild: function () { - this._elements = new Dictionary(); - var element, elements = elBySelAll(this._options.selector); - for (var i = 0, length = elements.length; i < length; i++) { - element = elements[i]; - this._elements.set(elData(element, 'object-id'), element); - } - }, + rebuild() { + this._elements.clear(); + document.querySelectorAll(this._options.selector).forEach((element) => { + const objectId = ~~(element.dataset.objectId || "0"); + this._elements.set(objectId, element); + }); + } /** * Returns a boolean value for the given permission. The permission should not start * with "can" or "can-" as this is automatically assumed by this method. - * - * @param {int} objectId message object id - * @param {string} permission permission name without a leading "can" or "can-" - * @return {boolean} true if permission was set and is either 'true' or '1' */ - getPermission: function (objectId, permission) { - permission = 'can-' + this._getAttributeName(permission); - var element = this._elements.get(objectId); + getPermission(objectId, permission) { + permission = "can-" + this._getAttributeName(permission); + const element = this._elements.get(objectId); if (element === undefined) { - throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'"); + throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`); } - return elDataBool(element, permission); - }, + return Core.stringToBool(element.dataset[permission] || ""); + } /** * Returns the given property value from a message, optionally supporting a boolean return value. - * - * @param {int} objectId message object id - * @param {string} propertyName attribute name - * @param {boolean} asBool attempt to interpret property value as boolean - * @return {(boolean|string)} raw property value or boolean if requested */ - getPropertyValue: function (objectId, propertyName, asBool) { - var element = this._elements.get(objectId); + getPropertyValue(objectId, propertyName, asBool) { + const element = this._elements.get(objectId); if (element === undefined) { - throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'"); + throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`); } - return window[(asBool ? 'elDataBool' : 'elData')](element, this._getAttributeName(propertyName)); - }, + const attributeName = this._getAttributeName(propertyName); + const value = element.dataset[attributeName] || ""; + if (asBool) { + return Core.stringToBool(value); + } + return value; + } /** * Invokes a method for given message object id in order to alter its state or properties. - * - * @param {int} objectId message object id - * @param {string} actionName action name used for the ajax api - * @param {Object=} parameters optional list of parameters included with the ajax request */ - update: function (objectId, actionName, parameters) { + update(objectId, actionName, parameters) { Ajax.api(this, { actionName: actionName, parameters: parameters || {}, - objectIDs: [objectId] + objectIDs: [objectId], }); - }, + } /** * Updates properties and states for given object ids. Keep in mind that this method does * not support setting individual properties per message, instead all property changes * are applied to all matching message objects. - * - * @param {Array} objectIds list of message object ids - * @param {Object} data list of updated properties */ - updateItems: function (objectIds, data) { + updateItems(objectIds, data) { if (!Array.isArray(objectIds)) { objectIds = [objectIds]; } - var element; - for (var i = 0, length = objectIds.length; i < length; i++) { - element = this._elements.get(objectIds[i]); + objectIds.forEach((objectId) => { + const element = this._elements.get(objectId); if (element === undefined) { - continue; - } - for (var key in data) { - if (data.hasOwnProperty(key)) { - this._update(element, key, data[key]); - } + return; } - } - }, + Object.entries(data).forEach(([key, value]) => { + this._update(element, key, value); + }); + }); + } /** * Bulk updates the properties and states for all observed messages at once. - * - * @param {Object} data list of updated properties */ - updateAllItems: function (data) { - var objectIds = []; - this._elements.forEach((function (element, objectId) { - objectIds.push(objectId); - }).bind(this)); + updateAllItems(data) { + const objectIds = Array.from(this._elements.keys()); this.updateItems(objectIds, data); - }, + } /** * Sets or removes a message note identified by its unique CSS class. - * - * @param {int} objectId message object id - * @param {string} className unique CSS class - * @param {string} htmlContent HTML content */ - setNote: function (objectId, className, htmlContent) { - var element = this._elements.get(objectId); + setNote(objectId, className, htmlContent) { + const element = this._elements.get(objectId); if (element === undefined) { - throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'"); + throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`); } - var messageFooterNotes = elBySel('.messageFooterNotes', element); - var note = elBySel('.' + className, messageFooterNotes); + const messageFooterNotes = element.querySelector(".messageFooterNotes"); + let note = messageFooterNotes.querySelector(`.${className}`); if (htmlContent) { if (note === null) { - note = elCreate('p'); - note.className = 'messageFooterNote ' + className; + note = document.createElement("p"); + note.className = "messageFooterNote " + className; messageFooterNotes.appendChild(note); } note.innerHTML = htmlContent; } else if (note !== null) { - elRemove(note); + note.remove(); } - }, + } /** * Updates a single property of a message element. - * - * @param {Element} element message element - * @param {string} propertyName property name - * @param {?} propertyValue property value, will be implicitly converted to string - * @protected */ - _update: function (element, propertyName, propertyValue) { - elData(element, this._getAttributeName(propertyName), propertyValue); + _update(element, propertyName, propertyValue) { + const attributeName = this._getAttributeName(propertyName); + element.dataset[attributeName] = propertyValue.toString(); // handle special properties - var propertyValueBoolean = (propertyValue == 1 || propertyValue === true || propertyValue === 'true'); + const propertyValueBoolean = propertyValue == 1 || propertyValue === true || propertyValue === "true"; this._updateState(element, propertyName, propertyValue, propertyValueBoolean); - }, + } /** * Updates the message element's state based upon a property change. - * - * @param {Element} element message element - * @param {string} propertyName property name - * @param {?} propertyValue property value - * @param {boolean} propertyValueBoolean true if `propertyValue` equals either 'true' or '1' - * @protected */ - _updateState: function (element, propertyName, propertyValue, propertyValueBoolean) { + _updateState(element, propertyName, propertyValue, propertyValueBoolean) { switch (propertyName) { - case 'isDeleted': - element.classList[(propertyValueBoolean ? 'add' : 'remove')]('messageDeleted'); - this._toggleMessageStatus(element, 'jsIconDeleted', 'wcf.message.status.deleted', 'red', propertyValueBoolean); + case "isDeleted": + if (propertyValueBoolean) { + element.classList.add("messageDeleted"); + } + else { + element.classList.remove("messageDeleted"); + } + this._toggleMessageStatus(element, "jsIconDeleted", "wcf.message.status.deleted", "red", propertyValueBoolean); break; - case 'isDisabled': - element.classList[(propertyValueBoolean ? 'add' : 'remove')]('messageDisabled'); - this._toggleMessageStatus(element, 'jsIconDisabled', 'wcf.message.status.disabled', 'green', propertyValueBoolean); + case "isDisabled": + if (propertyValueBoolean) { + element.classList.add("messageDisabled"); + } + else { + element.classList.remove("messageDisabled"); + } + this._toggleMessageStatus(element, "jsIconDisabled", "wcf.message.status.disabled", "green", propertyValueBoolean); break; } - }, + } /** * Toggles the message status bade for provided element. - * - * @param {Element} element message element - * @param {string} className badge class name - * @param {string} phrase language phrase - * @param {string} badgeColor color css class - * @param {boolean} addBadge add or remove badge - * @protected */ - _toggleMessageStatus: function (element, className, phrase, badgeColor, addBadge) { - var messageStatus = elBySel('.messageStatus', element); + _toggleMessageStatus(element, className, phrase, badgeColor, addBadge) { + let messageStatus = element.querySelector(".messageStatus"); if (messageStatus === null) { - var messageHeaderMetaData = elBySel('.messageHeaderMetaData', element); + const messageHeaderMetaData = element.querySelector(".messageHeaderMetaData"); if (messageHeaderMetaData === null) { // can't find appropriate location to insert badge return; } - messageStatus = elCreate('ul'); - messageStatus.className = 'messageStatus'; - DomUtil.insertAfter(messageStatus, messageHeaderMetaData); + messageStatus = document.createElement("ul"); + messageStatus.className = "messageStatus"; + messageHeaderMetaData.insertAdjacentElement("afterend", messageStatus); } - var badge = elBySel('.' + className, messageStatus); + let badge = messageStatus.querySelector(`.${className}`); if (addBadge) { if (badge !== null) { // badge already exists return; } - badge = elCreate('span'); - badge.className = 'badge label ' + badgeColor + ' ' + className; + badge = document.createElement("span"); + badge.className = `badge label ${badgeColor} ${className}`; badge.textContent = Language.get(phrase); - var listItem = elCreate('li'); + const listItem = document.createElement("li"); listItem.appendChild(badge); messageStatus.appendChild(listItem); } @@ -243,42 +189,34 @@ define(['Ajax', 'Core', 'Dictionary', 'Language', 'Dom/ChangeListener', 'Dom/Uti // badge does not exist return; } - elRemove(badge.parentNode); + badge.parentElement.remove(); } - }, + } /** * Transforms camel-cased property names into their attribute equivalent. - * - * @param {string} propertyName camel-cased property name - * @return {string} equivalent attribute name - * @protected */ - _getAttributeName: function (propertyName) { - if (propertyName.indexOf('-') !== -1) { + _getAttributeName(propertyName) { + if (propertyName.indexOf("-") !== -1) { return propertyName; } - var attributeName = ''; - var str, tmp = propertyName.split(/([A-Z][a-z]+)/); - for (var i = 0, length = tmp.length; i < length; i++) { - str = tmp[i]; - if (str.length) { - if (attributeName.length) - attributeName += '-'; - attributeName += str.toLowerCase(); - } - } - return attributeName; - }, - _ajaxSuccess: function () { + return propertyName + .split(/([A-Z][a-z]+)/) + .map((s) => s.trim().toLowerCase()) + .filter((s) => s.length > 0) + .join("-"); + } + _ajaxSuccess(_data) { + // This should be an abstract method, but cannot be marked as such for backwards compatibility. throw new Error("Method _ajaxSuccess() must be implemented by deriving functions."); - }, - _ajaxSetup: function () { + } + _ajaxSetup() { return { data: { - className: this._options.className - } + className: this._options.className, + }, }; } - }; + } + Core.enableLegacyInheritance(UiMessageManager); return UiMessageManager; }); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Message/Manager.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Message/Manager.js deleted file mode 100644 index 170dcc99ef..0000000000 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Message/Manager.js +++ /dev/null @@ -1,323 +0,0 @@ -/** - * Provides access and editing of message properties. - * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Message/Manager - */ -define(['Ajax', 'Core', 'Dictionary', 'Language', 'Dom/ChangeListener', 'Dom/Util'], function(Ajax, Core, Dictionary, Language, DomChangeListener, DomUtil) { - "use strict"; - - if (!COMPILER_TARGET_DEFAULT) { - var Fake = function() {}; - Fake.prototype = { - init: function() {}, - rebuild: function() {}, - getPermission: function() {}, - getPropertyValue: function() {}, - update: function() {}, - updateItems: function() {}, - updateAllItems: function() {}, - setNote: function() {}, - _update: function() {}, - _updateState: function() {}, - _toggleMessageStatus: function() {}, - _getAttributeName: function() {}, - _ajaxSuccess: function() {}, - _ajaxSetup: function() {} - }; - return Fake; - } - - /** - * @param {Object} options initialization options - * @constructor - */ - function UiMessageManager(options) { this.init(options); } - UiMessageManager.prototype = { - /** - * Initializes a new manager instance. - * - * @param {Object} options initialization options - */ - init: function(options) { - this._elements = null; - this._options = Core.extend({ - className: '', - selector: '' - }, options); - - this.rebuild(); - - DomChangeListener.add('Ui/Message/Manager' + this._options.className, this.rebuild.bind(this)); - }, - - /** - * Rebuilds the list of observed messages. You should call this method whenever a - * message has been either added or removed from the document. - */ - rebuild: function() { - this._elements = new Dictionary(); - - var element, elements = elBySelAll(this._options.selector); - for (var i = 0, length = elements.length; i < length; i++) { - element = elements[i]; - - this._elements.set(elData(element, 'object-id'), element); - } - }, - - /** - * Returns a boolean value for the given permission. The permission should not start - * with "can" or "can-" as this is automatically assumed by this method. - * - * @param {int} objectId message object id - * @param {string} permission permission name without a leading "can" or "can-" - * @return {boolean} true if permission was set and is either 'true' or '1' - */ - getPermission: function(objectId, permission) { - permission = 'can-' + this._getAttributeName(permission); - var element = this._elements.get(objectId); - if (element === undefined) { - throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'"); - } - - return elDataBool(element, permission); - }, - - /** - * Returns the given property value from a message, optionally supporting a boolean return value. - * - * @param {int} objectId message object id - * @param {string} propertyName attribute name - * @param {boolean} asBool attempt to interpret property value as boolean - * @return {(boolean|string)} raw property value or boolean if requested - */ - getPropertyValue: function(objectId, propertyName, asBool) { - var element = this._elements.get(objectId); - if (element === undefined) { - throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'"); - } - - return window[(asBool ? 'elDataBool' : 'elData')](element, this._getAttributeName(propertyName)); - }, - - /** - * Invokes a method for given message object id in order to alter its state or properties. - * - * @param {int} objectId message object id - * @param {string} actionName action name used for the ajax api - * @param {Object=} parameters optional list of parameters included with the ajax request - */ - update: function(objectId, actionName, parameters) { - Ajax.api(this, { - actionName: actionName, - parameters: parameters || {}, - objectIDs: [objectId] - }); - }, - - /** - * Updates properties and states for given object ids. Keep in mind that this method does - * not support setting individual properties per message, instead all property changes - * are applied to all matching message objects. - * - * @param {Array} objectIds list of message object ids - * @param {Object} data list of updated properties - */ - updateItems: function(objectIds, data) { - if (!Array.isArray(objectIds)) { - objectIds = [objectIds]; - } - - var element; - for (var i = 0, length = objectIds.length; i < length; i++) { - element = this._elements.get(objectIds[i]); - if (element === undefined) { - continue; - } - - for (var key in data) { - if (data.hasOwnProperty(key)) { - this._update(element, key, data[key]); - } - } - } - }, - - /** - * Bulk updates the properties and states for all observed messages at once. - * - * @param {Object} data list of updated properties - */ - updateAllItems: function(data) { - var objectIds = []; - this._elements.forEach((function(element, objectId) { - objectIds.push(objectId); - }).bind(this)); - - this.updateItems(objectIds, data); - }, - - /** - * Sets or removes a message note identified by its unique CSS class. - * - * @param {int} objectId message object id - * @param {string} className unique CSS class - * @param {string} htmlContent HTML content - */ - setNote: function (objectId, className, htmlContent) { - var element = this._elements.get(objectId); - if (element === undefined) { - throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'"); - } - - var messageFooterNotes = elBySel('.messageFooterNotes', element); - var note = elBySel('.' + className, messageFooterNotes); - if (htmlContent) { - if (note === null) { - note = elCreate('p'); - note.className = 'messageFooterNote ' + className; - - messageFooterNotes.appendChild(note); - } - - note.innerHTML = htmlContent; - } - else if (note !== null) { - elRemove(note); - } - }, - - /** - * Updates a single property of a message element. - * - * @param {Element} element message element - * @param {string} propertyName property name - * @param {?} propertyValue property value, will be implicitly converted to string - * @protected - */ - _update: function(element, propertyName, propertyValue) { - elData(element, this._getAttributeName(propertyName), propertyValue); - - // handle special properties - var propertyValueBoolean = (propertyValue == 1 || propertyValue === true || propertyValue === 'true'); - this._updateState(element, propertyName, propertyValue, propertyValueBoolean); - }, - - /** - * Updates the message element's state based upon a property change. - * - * @param {Element} element message element - * @param {string} propertyName property name - * @param {?} propertyValue property value - * @param {boolean} propertyValueBoolean true if `propertyValue` equals either 'true' or '1' - * @protected - */ - _updateState: function(element, propertyName, propertyValue, propertyValueBoolean) { - switch (propertyName) { - case 'isDeleted': - element.classList[(propertyValueBoolean ? 'add' : 'remove')]('messageDeleted'); - this._toggleMessageStatus(element, 'jsIconDeleted', 'wcf.message.status.deleted', 'red', propertyValueBoolean); - - break; - - case 'isDisabled': - element.classList[(propertyValueBoolean ? 'add' : 'remove')]('messageDisabled'); - this._toggleMessageStatus(element, 'jsIconDisabled', 'wcf.message.status.disabled', 'green', propertyValueBoolean); - - break; - } - }, - - /** - * Toggles the message status bade for provided element. - * - * @param {Element} element message element - * @param {string} className badge class name - * @param {string} phrase language phrase - * @param {string} badgeColor color css class - * @param {boolean} addBadge add or remove badge - * @protected - */ - _toggleMessageStatus: function(element, className, phrase, badgeColor, addBadge) { - var messageStatus = elBySel('.messageStatus', element); - if (messageStatus === null) { - var messageHeaderMetaData = elBySel('.messageHeaderMetaData', element); - if (messageHeaderMetaData === null) { - // can't find appropriate location to insert badge - return; - } - - messageStatus = elCreate('ul'); - messageStatus.className = 'messageStatus'; - DomUtil.insertAfter(messageStatus, messageHeaderMetaData); - } - - var badge = elBySel('.' + className, messageStatus); - - if (addBadge) { - if (badge !== null) { - // badge already exists - return; - } - - badge = elCreate('span'); - badge.className = 'badge label ' + badgeColor + ' ' + className; - badge.textContent = Language.get(phrase); - - var listItem = elCreate('li'); - listItem.appendChild(badge); - messageStatus.appendChild(listItem); - } - else { - if (badge === null) { - // badge does not exist - return; - } - - elRemove(badge.parentNode); - } - }, - - /** - * Transforms camel-cased property names into their attribute equivalent. - * - * @param {string} propertyName camel-cased property name - * @return {string} equivalent attribute name - * @protected - */ - _getAttributeName: function(propertyName) { - if (propertyName.indexOf('-') !== -1) { - return propertyName; - } - - var attributeName = ''; - var str, tmp = propertyName.split(/([A-Z][a-z]+)/); - for (var i = 0, length = tmp.length; i < length; i++) { - str = tmp[i]; - if (str.length) { - if (attributeName.length) attributeName += '-'; - attributeName += str.toLowerCase(); - } - } - - return attributeName; - }, - - _ajaxSuccess: function() { - throw new Error("Method _ajaxSuccess() must be implemented by deriving functions."); - }, - - _ajaxSetup: function() { - return { - data: { - className: this._options.className - } - }; - } - }; - - return UiMessageManager; -}); \ No newline at end of file diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Message/Manager.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Message/Manager.ts new file mode 100644 index 0000000000..5d38424c19 --- /dev/null +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Message/Manager.ts @@ -0,0 +1,287 @@ +/** + * Provides access and editing of message properties. + * + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Message/Manager + */ + +import * as Ajax from "../../Ajax"; +import { AjaxCallbackObject, AjaxCallbackSetup, ResponseData } from "../../Ajax/Data"; +import * as Core from "../../Core"; +import DomChangeListener from "../../Dom/Change/Listener"; +import * as Language from "../../Language"; + +interface MessageManagerOptions { + className: string; + selector: string; +} + +type StringableValue = boolean | number | string; + +class UiMessageManager implements AjaxCallbackObject { + protected readonly _elements = new Map(); + protected readonly _options: MessageManagerOptions; + + /** + * Initializes a new manager instance. + */ + constructor(options: MessageManagerOptions) { + this._options = Core.extend( + { + className: "", + selector: "", + }, + options, + ) as MessageManagerOptions; + + this.rebuild(); + + DomChangeListener.add(`Ui/Message/Manager${this._options.className}`, this.rebuild.bind(this)); + } + + /** + * Rebuilds the list of observed messages. You should call this method whenever a + * message has been either added or removed from the document. + */ + rebuild(): void { + this._elements.clear(); + + document.querySelectorAll(this._options.selector).forEach((element: HTMLElement) => { + const objectId = ~~(element.dataset.objectId || "0"); + this._elements.set(objectId, element); + }); + } + + /** + * Returns a boolean value for the given permission. The permission should not start + * with "can" or "can-" as this is automatically assumed by this method. + */ + getPermission(objectId: number, permission: string): boolean { + permission = "can-" + this._getAttributeName(permission); + const element = this._elements.get(objectId); + if (element === undefined) { + throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`); + } + + return Core.stringToBool(element.dataset[permission] || ""); + } + + /** + * Returns the given property value from a message, optionally supporting a boolean return value. + */ + getPropertyValue(objectId: number, propertyName: string, asBool: boolean): boolean | string { + const element = this._elements.get(objectId); + if (element === undefined) { + throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`); + } + + const attributeName = this._getAttributeName(propertyName); + const value = element.dataset[attributeName] || ""; + + if (asBool) { + return Core.stringToBool(value); + } + + return value; + } + + /** + * Invokes a method for given message object id in order to alter its state or properties. + */ + update(objectId: number, actionName: string, parameters?: ArbitraryObject): void { + Ajax.api(this, { + actionName: actionName, + parameters: parameters || {}, + objectIDs: [objectId], + }); + } + + /** + * Updates properties and states for given object ids. Keep in mind that this method does + * not support setting individual properties per message, instead all property changes + * are applied to all matching message objects. + */ + updateItems(objectIds: number | number[], data: ArbitraryObject): void { + if (!Array.isArray(objectIds)) { + objectIds = [objectIds]; + } + + objectIds.forEach((objectId) => { + const element = this._elements.get(objectId); + if (element === undefined) { + return; + } + + Object.entries(data).forEach(([key, value]) => { + this._update(element, key, value as StringableValue); + }); + }); + } + + /** + * Bulk updates the properties and states for all observed messages at once. + */ + updateAllItems(data: ArbitraryObject): void { + const objectIds = Array.from(this._elements.keys()); + + this.updateItems(objectIds, data); + } + + /** + * Sets or removes a message note identified by its unique CSS class. + */ + setNote(objectId: number, className: string, htmlContent: string): void { + const element = this._elements.get(objectId); + if (element === undefined) { + throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`); + } + + const messageFooterNotes = element.querySelector(".messageFooterNotes") as HTMLElement; + let note = messageFooterNotes.querySelector(`.${className}`); + if (htmlContent) { + if (note === null) { + note = document.createElement("p"); + note.className = "messageFooterNote " + className; + + messageFooterNotes.appendChild(note); + } + + note.innerHTML = htmlContent; + } else if (note !== null) { + note.remove(); + } + } + + /** + * Updates a single property of a message element. + */ + protected _update(element: HTMLElement, propertyName: string, propertyValue: StringableValue): void { + const attributeName = this._getAttributeName(propertyName); + element.dataset[attributeName] = propertyValue.toString(); + + // handle special properties + const propertyValueBoolean = propertyValue == 1 || propertyValue === true || propertyValue === "true"; + this._updateState(element, propertyName, propertyValue, propertyValueBoolean); + } + + /** + * Updates the message element's state based upon a property change. + */ + protected _updateState( + element: HTMLElement, + propertyName: string, + propertyValue: StringableValue, + propertyValueBoolean: boolean, + ): void { + switch (propertyName) { + case "isDeleted": + if (propertyValueBoolean) { + element.classList.add("messageDeleted"); + } else { + element.classList.remove("messageDeleted"); + } + + this._toggleMessageStatus(element, "jsIconDeleted", "wcf.message.status.deleted", "red", propertyValueBoolean); + + break; + + case "isDisabled": + if (propertyValueBoolean) { + element.classList.add("messageDisabled"); + } else { + element.classList.remove("messageDisabled"); + } + + this._toggleMessageStatus( + element, + "jsIconDisabled", + "wcf.message.status.disabled", + "green", + propertyValueBoolean, + ); + + break; + } + } + + /** + * Toggles the message status bade for provided element. + */ + protected _toggleMessageStatus( + element: HTMLElement, + className: string, + phrase: string, + badgeColor: string, + addBadge: boolean, + ): void { + let messageStatus = element.querySelector(".messageStatus"); + if (messageStatus === null) { + const messageHeaderMetaData = element.querySelector(".messageHeaderMetaData"); + if (messageHeaderMetaData === null) { + // can't find appropriate location to insert badge + return; + } + + messageStatus = document.createElement("ul"); + messageStatus.className = "messageStatus"; + messageHeaderMetaData.insertAdjacentElement("afterend", messageStatus); + } + + let badge = messageStatus.querySelector(`.${className}`); + if (addBadge) { + if (badge !== null) { + // badge already exists + return; + } + + badge = document.createElement("span"); + badge.className = `badge label ${badgeColor} ${className}`; + badge.textContent = Language.get(phrase); + + const listItem = document.createElement("li"); + listItem.appendChild(badge); + messageStatus.appendChild(listItem); + } else { + if (badge === null) { + // badge does not exist + return; + } + + badge.parentElement!.remove(); + } + } + + /** + * Transforms camel-cased property names into their attribute equivalent. + */ + protected _getAttributeName(propertyName: string): string { + if (propertyName.indexOf("-") !== -1) { + return propertyName; + } + + return propertyName + .split(/([A-Z][a-z]+)/) + .map((s) => s.trim().toLowerCase()) + .filter((s) => s.length > 0) + .join("-"); + } + + _ajaxSuccess(_data: ResponseData): void { + // This should be an abstract method, but cannot be marked as such for backwards compatibility. + throw new Error("Method _ajaxSuccess() must be implemented by deriving functions."); + } + + _ajaxSetup(): ReturnType { + return { + data: { + className: this._options.className, + }, + }; + } +} + +Core.enableLegacyInheritance(UiMessageManager); + +export = UiMessageManager; -- 2.20.1