From b7a92df597fae5da9d0f1a8e7a8d21c9e4cb6e3a Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Tue, 3 Nov 2020 12:02:05 +0100 Subject: [PATCH] Convert `Ui/Like/Handler` to TypeScript --- .../js/WoltLabSuite/Core/Ui/Like/Handler.js | 217 +++++++------ .../ts/WoltLabSuite/Core/Ui/Like/Handler.js | 248 -------------- .../ts/WoltLabSuite/Core/Ui/Like/Handler.ts | 306 ++++++++++++++++++ .../WoltLabSuite/Core/Ui/Reaction/Handler.ts | 2 +- 4 files changed, 421 insertions(+), 352 deletions(-) delete mode 100644 wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Like/Handler.js create mode 100644 wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Like/Handler.ts diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Like/Handler.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Like/Handler.js index daf360935f..551da42664 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Like/Handler.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Like/Handler.js @@ -1,39 +1,33 @@ /** * Provides interface elements to display and review likes. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Like/Handler + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Like/Handler * @deprecated 5.2 use ReactionHandler instead */ -define([ - 'Ajax', 'Core', 'Dictionary', 'Language', - 'ObjectMap', 'StringUtil', 'Dom/ChangeListener', 'Dom/Util', - 'Ui/Dialog', 'WoltLabSuite/Core/Ui/User/List', 'User', 'WoltLabSuite/Core/Ui/Reaction/Handler' -], function (Ajax, Core, Dictionary, Language, ObjectMap, StringUtil, DomChangeListener, DomUtil, UiDialog, UiUserList, User, UiReactionHandler) { +define(["require", "exports", "tslib", "../../Core", "../../Dom/Change/Listener", "../../Language", "../../StringUtil", "../Reaction/Handler", "../../User"], function (require, exports, tslib_1, Core, Listener_1, Language, StringUtil, Handler_1, User_1) { "use strict"; - /** - * @constructor - */ - function UiLikeHandler(objectType, options) { this.init(objectType, options); } - UiLikeHandler.prototype = { + Core = tslib_1.__importStar(Core); + Listener_1 = tslib_1.__importDefault(Listener_1); + Language = tslib_1.__importStar(Language); + StringUtil = tslib_1.__importStar(StringUtil); + Handler_1 = tslib_1.__importDefault(Handler_1); + User_1 = tslib_1.__importDefault(User_1); + class UiLikeHandler { /** * Initializes the like handler. - * - * @param {string} objectType object type - * @param {object} options initialization options */ - init: function (objectType, options) { - if (options.containerSelector === '') { + constructor(objectType, opts) { + this._containers = new Map(); + if (!opts.containerSelector) { throw new Error("[WoltLabSuite/Core/Ui/Like/Handler] Expected a non-empty string for option 'containerSelector'."); } - this._containers = new ObjectMap(); - this._details = new ObjectMap(); this._objectType = objectType; this._options = Core.extend({ // settings - badgeClassNames: '', + badgeClassNames: "", isSingleItem: false, markListItemAsActive: false, renderAsButton: true, @@ -45,98 +39,104 @@ define([ canLikeOwnContent: false, canViewSummary: false, // selectors - badgeContainerSelector: '.messageHeader .messageStatus', - buttonAppendToSelector: '.messageFooter .messageFooterButtons', - buttonBeforeSelector: '', - containerSelector: '', - summarySelector: '.messageFooterGroup' - }, options); - this.initContainers(options, objectType); - DomChangeListener.add('WoltLabSuite/Core/Ui/Like/Handler-' + objectType, this.initContainers.bind(this)); - new UiReactionHandler(this._objectType, { + badgeContainerSelector: ".messageHeader .messageStatus", + buttonAppendToSelector: ".messageFooter .messageFooterButtons", + buttonBeforeSelector: "", + containerSelector: "", + summarySelector: ".messageFooterGroup", + }, opts); + this.initContainers(); + Listener_1.default.add(`WoltLabSuite/Core/Ui/Like/Handler-${objectType}`, () => this.initContainers()); + new Handler_1.default(this._objectType, { containerSelector: this._options.containerSelector, - summaryListSelector: '.reactionSummaryList' }); - }, + } /** * Initializes all applicable containers. */ - initContainers: function () { - var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false; - for (var i = 0, length = elements.length; i < length; i++) { - element = elements[i]; + initContainers() { + let triggerChange = false; + document.querySelectorAll(this._options.containerSelector).forEach((element) => { if (this._containers.has(element)) { - continue; + return; } - elementData = { + const elementData = { badge: null, dislikeButton: null, likeButton: null, summary: null, - dislikes: ~~elData(element, 'like-dislikes'), - liked: ~~elData(element, 'like-liked'), - likes: ~~elData(element, 'like-likes'), - objectId: ~~elData(element, 'object-id'), - users: JSON.parse(elData(element, 'like-users')) + dislikes: ~~element.dataset.likeDislikes, + liked: ~~element.dataset.likeLiked, + likes: ~~element.dataset.likeLikes, + objectId: ~~element.dataset.objectId, + users: JSON.parse(element.dataset.likeUsers), }; this._containers.set(element, elementData); this._buildWidget(element, elementData); triggerChange = true; - } + }); if (triggerChange) { - DomChangeListener.trigger(); + Listener_1.default.trigger(); } - }, + } /** * Creates the interface elements. - * - * @param {Element} element container element - * @param {object} elementData like data */ - _buildWidget: function (element, elementData) { - // build reaction summary list - var summaryList, listItem, badgeContainer, isSummaryPosition = true; - badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.summarySelector) : elBySel(this._options.summarySelector, element); + _buildWidget(element, elementData) { + let badgeContainer; + let isSummaryPosition = true; + if (this._options.isSingleItem) { + badgeContainer = document.querySelector(this._options.summarySelector); + } + else { + badgeContainer = element.querySelector(this._options.summarySelector); + } if (badgeContainer === null) { - badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.badgeContainerSelector) : elBySel(this._options.badgeContainerSelector, element); + if (this._options.isSingleItem) { + badgeContainer = document.querySelector(this._options.badgeContainerSelector); + } + else { + badgeContainer = element.querySelector(this._options.badgeContainerSelector); + } isSummaryPosition = false; } if (badgeContainer !== null) { - summaryList = elCreate('ul'); - summaryList.classList.add('reactionSummaryList'); + const summaryList = document.createElement("ul"); + summaryList.classList.add("reactionSummaryList"); if (isSummaryPosition) { - summaryList.classList.add('likesSummary'); + summaryList.classList.add("likesSummary"); } else { - summaryList.classList.add('reactionSummaryListTiny'); + summaryList.classList.add("reactionSummaryListTiny"); } - for (var key in elementData.users) { - if (key === "reactionTypeID") - continue; - if (!REACTION_TYPES.hasOwnProperty(key)) - continue; - // create element - var createdElement = elCreate('li'); - createdElement.className = 'reactCountButton'; - elData(createdElement, 'reaction-type-id', key); - var countSpan = elCreate('span'); - countSpan.className = 'reactionCount'; - countSpan.innerHTML = StringUtil.shortUnit(elementData.users[key]); + const availableReactions = new Map(Object.entries(window.REACTION_TYPES)); + Object.entries(elementData.users).forEach(([reactionTypeId, count]) => { + const reaction = availableReactions.get(reactionTypeId); + if (reactionTypeId === "reactionTypeID" || !reaction) { + return; + } + // create element + const createdElement = document.createElement("li"); + createdElement.className = "reactCountButton"; + createdElement.setAttribute("reaction-type-id", reactionTypeId); + const countSpan = document.createElement("span"); + countSpan.className = "reactionCount"; + countSpan.innerHTML = StringUtil.shortUnit(~~count); createdElement.appendChild(countSpan); - createdElement.innerHTML = REACTION_TYPES[key].renderedIcon + createdElement.innerHTML; + createdElement.innerHTML = reaction.renderedIcon + createdElement.innerHTML; summaryList.appendChild(createdElement); - } + }); if (isSummaryPosition) { if (this._options.summaryPrepend) { - DomUtil.prepend(summaryList, badgeContainer); + badgeContainer.insertAdjacentElement("afterbegin", summaryList); } else { - badgeContainer.appendChild(summaryList); + badgeContainer.insertAdjacentElement("beforeend", summaryList); } } else { - if (badgeContainer.nodeName === 'OL' || badgeContainer.nodeName === 'UL') { - listItem = elCreate('li'); + if (badgeContainer.nodeName === "OL" || badgeContainer.nodeName === "UL") { + const listItem = document.createElement("li"); listItem.appendChild(summaryList); badgeContainer.appendChild(listItem); } @@ -147,9 +147,25 @@ define([ elementData.badge = summaryList; } // build reaction button - if (this._options.canLike && (User.userId != elData(element, 'user-id') || this._options.canLikeOwnContent)) { - var appendTo = (this._options.buttonAppendToSelector) ? ((this._options.isSingleItem) ? elBySel(this._options.buttonAppendToSelector) : elBySel(this._options.buttonAppendToSelector, element)) : null; - var insertPosition = (this._options.buttonBeforeSelector) ? ((this._options.isSingleItem) ? elBySel(this._options.buttonBeforeSelector) : elBySel(this._options.buttonBeforeSelector, element)) : null; + if (this._options.canLike && (User_1.default.userId != ~~element.dataset.userId || this._options.canLikeOwnContent)) { + let appendTo = null; + if (this._options.buttonAppendToSelector) { + if (this._options.isSingleItem) { + appendTo = document.querySelector(this._options.buttonAppendToSelector); + } + else { + appendTo = element.querySelector(this._options.buttonAppendToSelector); + } + } + let insertPosition = null; + if (this._options.buttonBeforeSelector) { + if (this._options.isSingleItem) { + insertPosition = document.querySelector(this._options.buttonBeforeSelector); + } + else { + insertPosition = element.querySelector(this._options.buttonBeforeSelector); + } + } if (insertPosition === null && appendTo === null) { throw new Error("Unable to find insert location for like/dislike buttons."); } @@ -157,51 +173,46 @@ define([ elementData.likeButton = this._createButton(element, elementData.users.reactionTypeID, insertPosition, appendTo); } } - }, + } /** * Creates a reaction button. - * - * @param {Element} element container element - * @param {int} reactionTypeID the reactionTypeID of the current state - * @param {Element?} insertBefore insert button before given element - * @param {Element?} appendTo append button to given element - * @return {Element} button element */ - _createButton: function (element, reactionTypeID, insertBefore, appendTo) { - var title = Language.get('wcf.reactions.react'); - var listItem = elCreate('li'); - listItem.className = 'wcfReactButton'; - var button = elCreate('a'); - button.className = 'jsTooltip reactButton'; + _createButton(element, reactionTypeID, insertBefore, appendTo) { + const title = Language.get("wcf.reactions.react"); + const listItem = document.createElement("li"); + listItem.className = "wcfReactButton"; + const button = document.createElement("a"); + button.className = "jsTooltip reactButton"; if (this._options.renderAsButton) { - button.classList.add('button'); + button.classList.add("button"); } - button.href = '#'; + button.href = "#"; button.title = title; - var icon = elCreate('span'); - icon.className = 'icon icon16 fa-smile-o'; + const icon = document.createElement("span"); + icon.className = "icon icon16 fa-smile-o"; if (reactionTypeID === undefined || reactionTypeID == 0) { - elData(icon, 'reaction-type-id', 0); + icon.dataset.reactionTypeId = "0"; } else { - elData(button, 'reaction-type-id', reactionTypeID); + button.dataset.reactionTypeId = reactionTypeID.toString(); button.classList.add("active"); } button.appendChild(icon); - var invisibleText = elCreate("span"); + const invisibleText = document.createElement("span"); invisibleText.className = "invisible"; invisibleText.innerHTML = title; button.appendChild(document.createTextNode(" ")); button.appendChild(invisibleText); listItem.appendChild(button); if (insertBefore) { - insertBefore.parentNode.insertBefore(listItem, insertBefore); + insertBefore.insertAdjacentElement("beforebegin", listItem); } else { - appendTo.appendChild(listItem); + appendTo.insertAdjacentElement("beforeend", listItem); } return button; } - }; + } + Core.enableLegacyInheritance(UiLikeHandler); return UiLikeHandler; }); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Like/Handler.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Like/Handler.js deleted file mode 100644 index 4c21ef4cf7..0000000000 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Like/Handler.js +++ /dev/null @@ -1,248 +0,0 @@ -/** - * Provides interface elements to display and review likes. - * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Like/Handler - * @deprecated 5.2 use ReactionHandler instead - */ -define( - [ - 'Ajax', 'Core', 'Dictionary', 'Language', - 'ObjectMap', 'StringUtil', 'Dom/ChangeListener', 'Dom/Util', - 'Ui/Dialog', 'WoltLabSuite/Core/Ui/User/List', 'User', 'WoltLabSuite/Core/Ui/Reaction/Handler' - ], - function( - Ajax, Core, Dictionary, Language, - ObjectMap, StringUtil, DomChangeListener, DomUtil, - UiDialog, UiUserList, User, UiReactionHandler - ) -{ - "use strict"; - - /** - * @constructor - */ - function UiLikeHandler(objectType, options) { this.init(objectType, options); } - UiLikeHandler.prototype = { - /** - * Initializes the like handler. - * - * @param {string} objectType object type - * @param {object} options initialization options - */ - init: function(objectType, options) { - if (options.containerSelector === '') { - throw new Error("[WoltLabSuite/Core/Ui/Like/Handler] Expected a non-empty string for option 'containerSelector'."); - } - - this._containers = new ObjectMap(); - this._details = new ObjectMap(); - this._objectType = objectType; - this._options = Core.extend({ - // settings - badgeClassNames: '', - isSingleItem: false, - markListItemAsActive: false, - renderAsButton: true, - summaryPrepend: true, - summaryUseIcon: true, - - // permissions - canDislike: false, - canLike: false, - canLikeOwnContent: false, - canViewSummary: false, - - // selectors - badgeContainerSelector: '.messageHeader .messageStatus', - buttonAppendToSelector: '.messageFooter .messageFooterButtons', - buttonBeforeSelector: '', - containerSelector: '', - summarySelector: '.messageFooterGroup' - }, options); - - this.initContainers(options, objectType); - - DomChangeListener.add('WoltLabSuite/Core/Ui/Like/Handler-' + objectType, this.initContainers.bind(this)); - - new UiReactionHandler(this._objectType, { - containerSelector: this._options.containerSelector, - summaryListSelector: '.reactionSummaryList' - }); - }, - - /** - * Initializes all applicable containers. - */ - initContainers: function() { - var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false; - for (var i = 0, length = elements.length; i < length; i++) { - element = elements[i]; - if (this._containers.has(element)) { - continue; - } - - elementData = { - badge: null, - dislikeButton: null, - likeButton: null, - summary: null, - - dislikes: ~~elData(element, 'like-dislikes'), - liked: ~~elData(element, 'like-liked'), - likes: ~~elData(element, 'like-likes'), - objectId: ~~elData(element, 'object-id'), - users: JSON.parse(elData(element, 'like-users')) - }; - - this._containers.set(element, elementData); - this._buildWidget(element, elementData); - - triggerChange = true; - } - - if (triggerChange) { - DomChangeListener.trigger(); - } - }, - - /** - * Creates the interface elements. - * - * @param {Element} element container element - * @param {object} elementData like data - */ - _buildWidget: function(element, elementData) { - // build reaction summary list - var summaryList, listItem, badgeContainer, isSummaryPosition = true; - badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.summarySelector) : elBySel(this._options.summarySelector, element); - if (badgeContainer === null) { - badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.badgeContainerSelector) : elBySel(this._options.badgeContainerSelector, element); - isSummaryPosition = false; - } - - if (badgeContainer !== null) { - summaryList = elCreate('ul'); - summaryList.classList.add('reactionSummaryList'); - if (isSummaryPosition) { - summaryList.classList.add('likesSummary'); - } - else { - summaryList.classList.add('reactionSummaryListTiny'); - } - - for (var key in elementData.users) { - if (key === "reactionTypeID") continue; - if (!REACTION_TYPES.hasOwnProperty(key)) continue; - - // create element - var createdElement = elCreate('li'); - createdElement.className = 'reactCountButton'; - elData(createdElement, 'reaction-type-id', key); - - var countSpan = elCreate('span'); - countSpan.className = 'reactionCount'; - countSpan.innerHTML = StringUtil.shortUnit(elementData.users[key]); - createdElement.appendChild(countSpan); - - createdElement.innerHTML = REACTION_TYPES[key].renderedIcon + createdElement.innerHTML; - - summaryList.appendChild(createdElement); - } - - if (isSummaryPosition) { - if (this._options.summaryPrepend) { - DomUtil.prepend(summaryList, badgeContainer); - } - else { - badgeContainer.appendChild(summaryList); - } - } - else { - if (badgeContainer.nodeName === 'OL' || badgeContainer.nodeName === 'UL') { - listItem = elCreate('li'); - listItem.appendChild(summaryList); - badgeContainer.appendChild(listItem); - } - else { - badgeContainer.appendChild(summaryList); - } - } - - elementData.badge = summaryList; - } - - // build reaction button - if (this._options.canLike && (User.userId != elData(element, 'user-id') || this._options.canLikeOwnContent)) { - var appendTo = (this._options.buttonAppendToSelector) ? ((this._options.isSingleItem) ? elBySel(this._options.buttonAppendToSelector) : elBySel(this._options.buttonAppendToSelector, element)) : null; - var insertPosition = (this._options.buttonBeforeSelector) ? ((this._options.isSingleItem) ? elBySel(this._options.buttonBeforeSelector) : elBySel(this._options.buttonBeforeSelector, element)) : null; - if (insertPosition === null && appendTo === null) { - throw new Error("Unable to find insert location for like/dislike buttons."); - } - else { - elementData.likeButton = this._createButton(element, elementData.users.reactionTypeID, insertPosition, appendTo); - } - } - }, - - /** - * Creates a reaction button. - * - * @param {Element} element container element - * @param {int} reactionTypeID the reactionTypeID of the current state - * @param {Element?} insertBefore insert button before given element - * @param {Element?} appendTo append button to given element - * @return {Element} button element - */ - _createButton: function(element, reactionTypeID, insertBefore, appendTo) { - var title = Language.get('wcf.reactions.react'); - - var listItem = elCreate('li'); - listItem.className = 'wcfReactButton'; - - var button = elCreate('a'); - button.className = 'jsTooltip reactButton'; - if (this._options.renderAsButton) { - button.classList.add('button'); - } - - button.href = '#'; - button.title = title; - - var icon = elCreate('span'); - icon.className = 'icon icon16 fa-smile-o'; - - if (reactionTypeID === undefined || reactionTypeID == 0) { - elData(icon, 'reaction-type-id', 0); - } - else { - elData(button, 'reaction-type-id', reactionTypeID); - button.classList.add("active"); - } - - button.appendChild(icon); - - var invisibleText = elCreate("span"); - invisibleText.className = "invisible"; - invisibleText.innerHTML = title; - - button.appendChild(document.createTextNode(" ")); - button.appendChild(invisibleText); - - listItem.appendChild(button); - - if (insertBefore) { - insertBefore.parentNode.insertBefore(listItem, insertBefore); - } - else { - appendTo.appendChild(listItem); - } - - return button; - } - }; - - return UiLikeHandler; -}); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Like/Handler.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Like/Handler.ts new file mode 100644 index 0000000000..f53ea405e6 --- /dev/null +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Like/Handler.ts @@ -0,0 +1,306 @@ +/** + * Provides interface elements to display and review likes. + * + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Like/Handler + * @deprecated 5.2 use ReactionHandler instead + */ + +import * as Core from "../../Core"; +import DomChangeListener from "../../Dom/Change/Listener"; +import * as Language from "../../Language"; +import * as StringUtil from "../../StringUtil"; +import UiReactionHandler from "../Reaction/Handler"; +import User from "../../User"; + +interface LikeHandlerOptions { + // settings + badgeClassNames: string; + isSingleItem: boolean; + markListItemAsActive: boolean; + renderAsButton: boolean; + summaryPrepend: boolean; + summaryUseIcon: boolean; + + // permissions + canDislike: boolean; + canLike: boolean; + canLikeOwnContent: boolean; + canViewSummary: boolean; + + // selectors + badgeContainerSelector: string; + buttonAppendToSelector: string; + buttonBeforeSelector: string; + containerSelector: string; + summarySelector: string; +} + +interface LikeUsers { + [key: string]: number; +} + +interface ElementData { + badge: HTMLUListElement | null; + dislikeButton: null; + likeButton: HTMLAnchorElement | null; + summary: null; + + dislikes: number; + liked: number; + likes: number; + objectId: number; + users: LikeUsers; +} + +class UiLikeHandler { + protected readonly _containers = new Map(); + protected readonly _objectType: string; + protected readonly _options: LikeHandlerOptions; + + /** + * Initializes the like handler. + */ + constructor(objectType: string, opts: Partial) { + if (!opts.containerSelector) { + throw new Error( + "[WoltLabSuite/Core/Ui/Like/Handler] Expected a non-empty string for option 'containerSelector'.", + ); + } + + this._objectType = objectType; + this._options = Core.extend( + { + // settings + badgeClassNames: "", + isSingleItem: false, + markListItemAsActive: false, + renderAsButton: true, + summaryPrepend: true, + summaryUseIcon: true, + + // permissions + canDislike: false, + canLike: false, + canLikeOwnContent: false, + canViewSummary: false, + + // selectors + badgeContainerSelector: ".messageHeader .messageStatus", + buttonAppendToSelector: ".messageFooter .messageFooterButtons", + buttonBeforeSelector: "", + containerSelector: "", + summarySelector: ".messageFooterGroup", + }, + opts, + ) as LikeHandlerOptions; + + this.initContainers(); + + DomChangeListener.add(`WoltLabSuite/Core/Ui/Like/Handler-${objectType}`, () => this.initContainers()); + + new UiReactionHandler(this._objectType, { + containerSelector: this._options.containerSelector, + }); + } + + /** + * Initializes all applicable containers. + */ + initContainers(): void { + let triggerChange = false; + + document.querySelectorAll(this._options.containerSelector).forEach((element: HTMLElement) => { + if (this._containers.has(element)) { + return; + } + + const elementData = { + badge: null, + dislikeButton: null, + likeButton: null, + summary: null, + + dislikes: ~~element.dataset.likeDislikes!, + liked: ~~element.dataset.likeLiked!, + likes: ~~element.dataset.likeLikes!, + objectId: ~~element.dataset.objectId!, + users: JSON.parse(element.dataset.likeUsers!), + }; + + this._containers.set(element, elementData); + this._buildWidget(element, elementData); + + triggerChange = true; + }); + + if (triggerChange) { + DomChangeListener.trigger(); + } + } + + /** + * Creates the interface elements. + */ + protected _buildWidget(element: HTMLElement, elementData: ElementData): void { + let badgeContainer: HTMLElement | null; + let isSummaryPosition = true; + + if (this._options.isSingleItem) { + badgeContainer = document.querySelector(this._options.summarySelector); + } else { + badgeContainer = element.querySelector(this._options.summarySelector); + } + + if (badgeContainer === null) { + if (this._options.isSingleItem) { + badgeContainer = document.querySelector(this._options.badgeContainerSelector); + } else { + badgeContainer = element.querySelector(this._options.badgeContainerSelector); + } + + isSummaryPosition = false; + } + + if (badgeContainer !== null) { + const summaryList = document.createElement("ul"); + summaryList.classList.add("reactionSummaryList"); + if (isSummaryPosition) { + summaryList.classList.add("likesSummary"); + } else { + summaryList.classList.add("reactionSummaryListTiny"); + } + + const availableReactions = new Map(Object.entries(window.REACTION_TYPES)); + Object.entries(elementData.users).forEach(([reactionTypeId, count]) => { + const reaction = availableReactions.get(reactionTypeId); + if (reactionTypeId === "reactionTypeID" || !reaction) { + return; + } + + // create element + const createdElement = document.createElement("li"); + createdElement.className = "reactCountButton"; + createdElement.setAttribute("reaction-type-id", reactionTypeId); + + const countSpan = document.createElement("span"); + countSpan.className = "reactionCount"; + countSpan.innerHTML = StringUtil.shortUnit(~~count); + createdElement.appendChild(countSpan); + + createdElement.innerHTML = reaction.renderedIcon + createdElement.innerHTML; + + summaryList.appendChild(createdElement); + }); + + if (isSummaryPosition) { + if (this._options.summaryPrepend) { + badgeContainer.insertAdjacentElement("afterbegin", summaryList); + } else { + badgeContainer.insertAdjacentElement("beforeend", summaryList); + } + } else { + if (badgeContainer.nodeName === "OL" || badgeContainer.nodeName === "UL") { + const listItem = document.createElement("li"); + listItem.appendChild(summaryList); + badgeContainer.appendChild(listItem); + } else { + badgeContainer.appendChild(summaryList); + } + } + + elementData.badge = summaryList; + } + + // build reaction button + if (this._options.canLike && (User.userId != ~~element.dataset.userId! || this._options.canLikeOwnContent)) { + let appendTo: HTMLElement | null = null; + if (this._options.buttonAppendToSelector) { + if (this._options.isSingleItem) { + appendTo = document.querySelector(this._options.buttonAppendToSelector); + } else { + appendTo = element.querySelector(this._options.buttonAppendToSelector); + } + } + + let insertPosition: HTMLElement | null = null; + if (this._options.buttonBeforeSelector) { + if (this._options.isSingleItem) { + insertPosition = document.querySelector(this._options.buttonBeforeSelector); + } else { + insertPosition = element.querySelector(this._options.buttonBeforeSelector); + } + } + + if (insertPosition === null && appendTo === null) { + throw new Error("Unable to find insert location for like/dislike buttons."); + } else { + elementData.likeButton = this._createButton( + element, + elementData.users.reactionTypeID, + insertPosition, + appendTo, + ); + } + } + } + + /** + * Creates a reaction button. + */ + protected _createButton( + element: HTMLElement, + reactionTypeID: number, + insertBefore: HTMLElement | null, + appendTo: HTMLElement | null, + ): HTMLAnchorElement { + const title = Language.get("wcf.reactions.react"); + + const listItem = document.createElement("li"); + listItem.className = "wcfReactButton"; + + const button = document.createElement("a"); + button.className = "jsTooltip reactButton"; + if (this._options.renderAsButton) { + button.classList.add("button"); + } + + button.href = "#"; + button.title = title; + + const icon = document.createElement("span"); + icon.className = "icon icon16 fa-smile-o"; + + if (reactionTypeID === undefined || reactionTypeID == 0) { + icon.dataset.reactionTypeId = "0"; + } else { + button.dataset.reactionTypeId = reactionTypeID.toString(); + button.classList.add("active"); + } + + button.appendChild(icon); + + const invisibleText = document.createElement("span"); + invisibleText.className = "invisible"; + invisibleText.innerHTML = title; + + button.appendChild(document.createTextNode(" ")); + button.appendChild(invisibleText); + + listItem.appendChild(button); + + if (insertBefore) { + insertBefore.insertAdjacentElement("beforebegin", listItem); + } else { + appendTo!.insertAdjacentElement("beforeend", listItem); + } + + return button; + } +} + +Core.enableLegacyInheritance(UiLikeHandler); + +export = UiLikeHandler; diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Reaction/Handler.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Reaction/Handler.ts index a623878737..c5d0108b64 100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Reaction/Handler.ts +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Reaction/Handler.ts @@ -65,7 +65,7 @@ class UiReactionHandler { /** * Initializes the reaction handler. */ - constructor(objectType: string, opts: ReactionHandlerOptions) { + constructor(objectType: string, opts: Partial) { if (!opts.containerSelector) { throw new Error( "[WoltLabSuite/Core/Ui/Reaction/Handler] Expected a non-empty string for option 'containerSelector'.", -- 2.20.1