/**
* Provides interface elements to use reactions.
*
- * @author Joshua Ruesweg
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Ui/Reaction/Handler
+ * @author Joshua Ruesweg
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/Reaction/Handler
* @since 5.2
*/
-define([
- 'Ajax',
- 'Core',
- 'Dictionary',
- 'Dom/ChangeListener',
- 'Dom/Util',
- 'Ui/Alignment',
- 'Ui/CloseOverlay',
- 'Ui/Screen',
- 'WoltLabSuite/Core/Ui/Reaction/CountButtons',
-], function (Ajax, Core, Dictionary, DomChangeListener, DomUtil, UiAlignment, UiCloseOverlay, UiScreen, CountButtons) {
+define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Change/Listener", "../../Dom/Util", "../Alignment", "../CloseOverlay", "../Screen", "./CountButtons"], function (require, exports, tslib_1, Ajax, Core, Listener_1, Util_1, UiAlignment, CloseOverlay_1, UiScreen, CountButtons_1) {
"use strict";
- /**
- * @constructor
- */
- function UiReactionHandler(objectType, options) { this.init(objectType, options); }
- UiReactionHandler.prototype = {
+ Ajax = tslib_1.__importStar(Ajax);
+ Core = tslib_1.__importStar(Core);
+ Listener_1 = tslib_1.__importDefault(Listener_1);
+ Util_1 = tslib_1.__importDefault(Util_1);
+ UiAlignment = tslib_1.__importStar(UiAlignment);
+ CloseOverlay_1 = tslib_1.__importDefault(CloseOverlay_1);
+ UiScreen = tslib_1.__importStar(UiScreen);
+ CountButtons_1 = tslib_1.__importDefault(CountButtons_1);
+ class UiReactionHandler {
/**
* Initializes the reaction handler.
- *
- * @param {string} objectType object type
- * @param {object} options initialization options
*/
- init: function (objectType, options) {
- if (options.containerSelector === '') {
+ constructor(objectType, opts) {
+ this._cache = new Map();
+ this._containers = new Map();
+ this._objects = new Map();
+ this._popoverCurrentObjectId = 0;
+ if (!opts.containerSelector) {
throw new Error("[WoltLabSuite/Core/Ui/Reaction/Handler] Expected a non-empty string for option 'containerSelector'.");
}
- this._containers = new Dictionary();
this._objectType = objectType;
- this._cache = new Dictionary();
- this._objects = new Dictionary();
- this._popoverCurrentObjectId = 0;
this._popover = null;
this._popoverContent = null;
this._options = Core.extend({
// selectors
- buttonSelector: '.reactButton',
- containerSelector: '',
+ buttonSelector: ".reactButton",
+ containerSelector: "",
isButtonGroupNavigation: false,
isSingleItem: false,
// other stuff
parameters: {
- data: {}
- }
- }, options);
- this.initReactButtons(options, objectType);
- this.countButtons = new CountButtons(this._objectType, this._options);
- DomChangeListener.add('WoltLabSuite/Core/Ui/Reaction/Handler-' + objectType, this.initReactButtons.bind(this));
- UiCloseOverlay.add('WoltLabSuite/Core/Ui/Reaction/Handler', this._closePopover.bind(this));
- },
+ data: {},
+ },
+ }, opts);
+ this.initReactButtons();
+ this.countButtons = new CountButtons_1.default(this._objectType, this._options);
+ Listener_1.default.add(`WoltLabSuite/Core/Ui/Reaction/Handler-${objectType}`, () => this.initReactButtons());
+ CloseOverlay_1.default.add("WoltLabSuite/Core/Ui/Reaction/Handler", () => this._closePopover());
+ }
/**
* Initializes all applicable react buttons with the given selector.
*/
- initReactButtons: function () {
- var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false, objectId;
- for (var i = 0, length = elements.length; i < length; i++) {
- element = elements[i];
- if (this._containers.has(DomUtil.identify(element))) {
- continue;
+ initReactButtons() {
+ let triggerChange = false;
+ document.querySelectorAll(this._options.containerSelector).forEach((element) => {
+ const elementId = Util_1.default.identify(element);
+ if (this._containers.has(elementId)) {
+ return;
}
- objectId = ~~elData(element, 'object-id');
- elementData = {
+ const objectId = ~~element.dataset.objectId;
+ const elementData = {
reactButton: null,
objectId: objectId,
- element: element
+ element: element,
};
- this._containers.set(DomUtil.identify(element), elementData);
+ this._containers.set(elementId, elementData);
this._initReactButton(element, elementData);
- var objects = [];
- if (this._objects.has(objectId)) {
- objects = this._objects.get(objectId);
- }
+ const objects = this._objects.get(objectId) || [];
objects.push(elementData);
this._objects.set(objectId, objects);
triggerChange = true;
- }
+ });
if (triggerChange) {
- DomChangeListener.trigger();
+ Listener_1.default.trigger();
}
- },
+ }
/**
* Initializes a specific react button.
*/
- _initReactButton: function (element, elementData) {
+ _initReactButton(element, elementData) {
if (this._options.isSingleItem) {
- elementData.reactButton = elBySel(this._options.buttonSelector);
+ elementData.reactButton = document.querySelector(this._options.buttonSelector);
}
else {
- elementData.reactButton = elBySel(this._options.buttonSelector, element);
+ elementData.reactButton = element.querySelector(this._options.buttonSelector);
}
- if (elementData.reactButton === null || elementData.reactButton.length === 0) {
- // The element may have no react button.
+ if (elementData.reactButton === null) {
+ // The element may have no react button.
return;
}
- //noinspection JSUnresolvedVariable
- if (Object.keys(REACTION_TYPES).length === 1) {
- //noinspection JSUnresolvedVariable
- var reaction = REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];
+ const availableReactions = Object.values(window.REACTION_TYPES);
+ if (availableReactions.length === 1) {
+ const reaction = availableReactions[0];
elementData.reactButton.title = reaction.title;
- var textSpan = elBySel('.invisible', elementData.reactButton);
- textSpan.innerText = reaction.title;
+ const textSpan = elementData.reactButton.querySelector(".invisible");
+ textSpan.textContent = reaction.title;
}
- elementData.reactButton.addEventListener('click', this._toggleReactPopover.bind(this, elementData.objectId, elementData.reactButton));
- },
- _updateReactButton: function (objectID, reactionTypeID) {
- this._objects.get(objectID).forEach(function (elementData) {
+ elementData.reactButton.addEventListener("click", (ev) => {
+ this._toggleReactPopover(elementData.objectId, elementData.reactButton, ev);
+ });
+ }
+ _updateReactButton(objectID, reactionTypeID) {
+ this._objects.get(objectID).forEach((elementData) => {
if (elementData.reactButton !== null) {
if (reactionTypeID) {
- elementData.reactButton.classList.add('active');
- elData(elementData.reactButton, 'reaction-type-id', reactionTypeID);
+ elementData.reactButton.classList.add("active");
+ elementData.reactButton.dataset.reactionTypeId = reactionTypeID.toString();
}
else {
- elData(elementData.reactButton, 'reaction-type-id', 0);
- elementData.reactButton.classList.remove('active');
+ elementData.reactButton.dataset.reactionTypeId = "0";
+ elementData.reactButton.classList.remove("active");
}
}
});
- },
- _markReactionAsActive: function () {
- var reactionTypeID = null;
- this._objects.get(this._popoverCurrentObjectId).forEach(function (element) {
+ }
+ _markReactionAsActive() {
+ let reactionTypeID = 0;
+ this._objects.get(this._popoverCurrentObjectId).forEach((element) => {
if (element.reactButton !== null) {
- reactionTypeID = ~~elData(element.reactButton, 'reaction-type-id');
+ reactionTypeID = ~~element.reactButton.dataset.reactionTypeId;
}
});
- if (reactionTypeID === null) {
+ if (!reactionTypeID) {
throw new Error("Unable to find react button for current popover.");
}
// Clear the old active state.
- elBySelAll('.reactionTypeButton.active', this._getPopover(), function (element) {
- element.classList.remove('active');
- });
- var scrollableContainer = elBySel('.reactionPopoverContent', this._getPopover());
+ const popover = this._getPopover();
+ popover.querySelectorAll(".reactionTypeButton.active").forEach((el) => el.classList.remove("active"));
+ const scrollableContainer = popover.querySelector(".reactionPopoverContent");
if (reactionTypeID) {
- var reactionTypeButton = elBySel('.reactionTypeButton[data-reaction-type-id="' + reactionTypeID + '"]', this._getPopover());
- reactionTypeButton.classList.add('active');
- if (~~elData(reactionTypeButton, 'is-assignable') === 0) {
- elShow(reactionTypeButton);
+ const reactionTypeButton = popover.querySelector(`.reactionTypeButton[data-reaction-type-id="${reactionTypeID}"]`);
+ reactionTypeButton.classList.add("active");
+ if (~~reactionTypeButton.dataset.isAssignable === 0) {
+ Util_1.default.show(reactionTypeButton);
}
this._scrollReactionIntoView(scrollableContainer, reactionTypeButton);
}
// The "first" reaction is positioned as close as possible to the toggle button,
// which means that we need to scroll the list to the bottom if the popover is
// displayed above the toggle button.
- if (UiScreen.is('screen-xs')) {
- if (this._getPopover().classList.contains('inverseOrder')) {
+ if (UiScreen.is("screen-xs")) {
+ if (popover.classList.contains("inverseOrder")) {
scrollableContainer.scrollTop = 0;
}
else {
}
}
}
- },
- _scrollReactionIntoView: function (scrollableContainer, reactionTypeButton) {
+ }
+ _scrollReactionIntoView(scrollableContainer, reactionTypeButton) {
// Do not scroll if the button is located in the upper 75%.
if (reactionTypeButton.offsetTop < scrollableContainer.clientHeight * 0.75) {
scrollableContainer.scrollTop = 0;
// the maximum possible offset value. We can abuse this behavior by calculating
// the values to place the selected reaction in the center of the popover,
// regardless of the offset being out of range.
- scrollableContainer.scrollTop = reactionTypeButton.offsetTop + reactionTypeButton.clientHeight / 2 - scrollableContainer.clientHeight / 2;
+ scrollableContainer.scrollTop =
+ reactionTypeButton.offsetTop + reactionTypeButton.clientHeight / 2 - scrollableContainer.clientHeight / 2;
}
- },
+ }
/**
* Toggle the visibility of the react popover.
- *
- * @param {int} objectId
- * @param {Element} element
- * @param {?Event} event
*/
- _toggleReactPopover: function (objectId, element, event) {
+ _toggleReactPopover(objectId, element, event) {
if (event !== null) {
event.preventDefault();
event.stopPropagation();
}
- //noinspection JSUnresolvedVariable
- if (Object.keys(REACTION_TYPES).length === 1) {
- //noinspection JSUnresolvedVariable
- var reaction = REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];
+ const availableReactions = Object.values(window.REACTION_TYPES);
+ if (availableReactions.length === 1) {
+ const reaction = availableReactions[0];
this._popoverCurrentObjectId = objectId;
this._react(reaction.reactionTypeID);
}
this._openReactPopover(objectId, element);
}
else {
- this._closePopover(objectId, element);
+ this._closePopover();
}
}
- },
+ }
/**
* Opens the react popover for a specific react button.
- *
- * @param {int} objectId objectId of the element
- * @param {Element} element container element
*/
- _openReactPopover: function (objectId, element) {
+ _openReactPopover(objectId, element) {
if (this._popoverCurrentObjectId !== 0) {
this._closePopover();
}
this._popoverCurrentObjectId = objectId;
UiAlignment.set(this._getPopover(), element, {
pointer: true,
- horizontal: (this._options.isButtonGroupNavigation) ? 'left' : 'center',
- vertical: UiScreen.is('screen-xs') ? 'bottom' : 'top'
+ horizontal: this._options.isButtonGroupNavigation ? "left" : "center",
+ vertical: UiScreen.is("screen-xs") ? "bottom" : "top",
});
if (this._options.isButtonGroupNavigation) {
- element.closest('nav').style.setProperty('opacity', '1', '');
+ element.closest("nav").style.setProperty("opacity", "1", "");
}
- var popover = this._getPopover();
+ const popover = this._getPopover();
// The popover could be rendered below the input field on mobile, in which case
// the "first" button is displayed at the bottom and thus farthest away. Reversing
// the display order will restore the logic by placing the "first" button as close
// to the react button as possible.
- var inverseOrder = popover.style.getPropertyValue('bottom') === 'auto';
- popover.classList[inverseOrder ? 'add' : 'remove']('inverseOrder');
+ const inverseOrder = popover.style.getPropertyValue("bottom") === "auto";
+ if (inverseOrder) {
+ popover.classList.add("inverseOrder");
+ }
+ else {
+ popover.classList.remove("inverseOrder");
+ }
this._markReactionAsActive();
this._rebuildOverflowIndicator();
- popover.classList.remove('forceHide');
- popover.classList.add('active');
- },
+ popover.classList.remove("forceHide");
+ popover.classList.add("active");
+ }
/**
* Returns the react popover element.
- *
- * @returns {Element}
*/
- _getPopover: function () {
+ _getPopover() {
if (this._popover == null) {
- this._popover = elCreate('div');
- this._popover.className = 'reactionPopover forceHide';
- this._popoverContent = elCreate('div');
- this._popoverContent.className = 'reactionPopoverContent';
- var popoverContentHTML = elCreate('ul');
- popoverContentHTML.className = 'reactionTypeButtonList';
- var sortedReactionTypes = this._getSortedReactionTypes();
- for (var key in sortedReactionTypes) {
- if (!sortedReactionTypes.hasOwnProperty(key))
- continue;
- var reactionType = sortedReactionTypes[key];
- var reactionTypeItem = elCreate('li');
- reactionTypeItem.className = 'reactionTypeButton jsTooltip';
- elData(reactionTypeItem, 'reaction-type-id', reactionType.reactionTypeID);
- elData(reactionTypeItem, 'title', reactionType.title);
- elData(reactionTypeItem, 'is-assignable', ~~reactionType.isAssignable);
+ this._popover = document.createElement("div");
+ this._popover.className = "reactionPopover forceHide";
+ this._popoverContent = document.createElement("div");
+ this._popoverContent.className = "reactionPopoverContent";
+ const popoverContentHTML = document.createElement("ul");
+ popoverContentHTML.className = "reactionTypeButtonList";
+ this._getSortedReactionTypes().forEach((reactionType) => {
+ const reactionTypeItem = document.createElement("li");
+ reactionTypeItem.className = "reactionTypeButton jsTooltip";
+ reactionTypeItem.dataset.reactionTypeId = reactionType.reactionTypeID.toString();
+ reactionTypeItem.dataset.title = reactionType.title;
+ reactionTypeItem.dataset.isAssignable = reactionType.isAssignable.toString();
reactionTypeItem.title = reactionType.title;
- var reactionTypeItemSpan = elCreate('span');
- reactionTypeItemSpan.className = 'reactionTypeButtonTitle';
+ const reactionTypeItemSpan = document.createElement("span");
+ reactionTypeItemSpan.className = "reactionTypeButtonTitle";
reactionTypeItemSpan.innerHTML = reactionType.title;
- //noinspection JSUnresolvedVariable
reactionTypeItem.innerHTML = reactionType.renderedIcon;
reactionTypeItem.appendChild(reactionTypeItemSpan);
- reactionTypeItem.addEventListener('click', this._react.bind(this, reactionType.reactionTypeID));
+ reactionTypeItem.addEventListener("click", () => this._react(reactionType.reactionTypeID));
if (!reactionType.isAssignable) {
- elHide(reactionTypeItem);
+ Util_1.default.hide(reactionTypeItem);
}
popoverContentHTML.appendChild(reactionTypeItem);
- }
+ });
this._popoverContent.appendChild(popoverContentHTML);
- this._popoverContent.addEventListener('scroll', this._rebuildOverflowIndicator.bind(this), { passive: true });
+ this._popoverContent.addEventListener("scroll", () => this._rebuildOverflowIndicator(), { passive: true });
this._popover.appendChild(this._popoverContent);
- var pointer = elCreate('span');
- pointer.className = 'elementPointer';
- pointer.appendChild(elCreate('span'));
+ const pointer = document.createElement("span");
+ pointer.className = "elementPointer";
+ pointer.appendChild(document.createElement("span"));
this._popover.appendChild(pointer);
document.body.appendChild(this._popover);
- DomChangeListener.trigger();
+ Listener_1.default.trigger();
}
return this._popover;
- },
- _rebuildOverflowIndicator: function () {
- var hasTopOverflow = this._popoverContent.scrollTop > 0;
- this._popoverContent.classList[hasTopOverflow ? 'add' : 'remove']('overflowTop');
- var hasBottomOverflow = this._popoverContent.scrollTop + this._popoverContent.clientHeight < this._popoverContent.scrollHeight;
- this._popoverContent.classList[hasBottomOverflow ? 'add' : 'remove']('overflowBottom');
- },
+ }
+ _rebuildOverflowIndicator() {
+ const popoverContent = this._popoverContent;
+ const hasTopOverflow = popoverContent.scrollTop > 0;
+ if (hasTopOverflow) {
+ popoverContent.classList.add("overflowTop");
+ }
+ else {
+ popoverContent.classList.remove("overflowTop");
+ }
+ const hasBottomOverflow = popoverContent.scrollTop + popoverContent.clientHeight < popoverContent.scrollHeight;
+ if (hasBottomOverflow) {
+ popoverContent.classList.add("overflowBottom");
+ }
+ else {
+ popoverContent.classList.remove("overflowBottom");
+ }
+ }
/**
* Sort the reaction types by the showOrder field.
- *
- * @returns {Array} the reaction types sorted by showOrder
*/
- _getSortedReactionTypes: function () {
- var sortedReactionTypes = [];
- // convert our reaction type object to an array
- //noinspection JSUnresolvedVariable
- for (var key in REACTION_TYPES) {
- //noinspection JSUnresolvedVariable
- if (REACTION_TYPES.hasOwnProperty(key)) {
- //noinspection JSUnresolvedVariable
- sortedReactionTypes.push(REACTION_TYPES[key]);
- }
- }
- // sort the array
- sortedReactionTypes.sort(function (a, b) {
- //noinspection JSUnresolvedVariable
- return a.showOrder - b.showOrder;
- });
- return sortedReactionTypes;
- },
+ _getSortedReactionTypes() {
+ return Object.values(window.REACTION_TYPES).sort((a, b) => a.showOrder - b.showOrder);
+ }
/**
* Closes the react popover.
*/
- _closePopover: function () {
+ _closePopover() {
if (this._popoverCurrentObjectId !== 0) {
- this._getPopover().classList.remove('active');
- elBySelAll('.reactionTypeButton[data-is-assignable="0"]', this._getPopover(), elHide);
+ const popover = this._getPopover();
+ popover.classList.remove("active");
+ popover
+ .querySelectorAll('.reactionTypeButton[data-is-assignable="0"]')
+ .forEach((el) => Util_1.default.hide(el));
if (this._options.isButtonGroupNavigation) {
- this._objects.get(this._popoverCurrentObjectId).forEach(function (elementData) {
- elementData.reactButton.closest('nav').style.cssText = "";
+ this._objects.get(this._popoverCurrentObjectId).forEach((elementData) => {
+ elementData.reactButton.closest("nav").style.cssText = "";
});
}
this._popoverCurrentObjectId = 0;
}
- },
+ }
/**
* React with the given reactionTypeId on an object.
- *
- * @param {init} reactionTypeId
*/
- _react: function (reactionTypeId) {
+ _react(reactionTypeId) {
if (~~this._popoverCurrentObjectId === 0) {
// Double clicking the reaction will cause the first click to go through, but
// causes the second to fail because the overlay is already closing.
this._options.parameters.data.objectID = this._popoverCurrentObjectId;
this._options.parameters.data.objectType = this._objectType;
Ajax.api(this, {
- parameters: this._options.parameters
+ parameters: this._options.parameters,
});
this._closePopover();
- },
- _ajaxSuccess: function (data) {
- //noinspection JSUnresolvedVariable
+ }
+ _ajaxSuccess(data) {
this.countButtons.updateCountButtons(data.returnValues.objectID, data.returnValues.reactions);
this._updateReactButton(data.returnValues.objectID, data.returnValues.reactionTypeID);
- },
- _ajaxSetup: function () {
+ }
+ _ajaxSetup() {
return {
data: {
- actionName: 'react',
- className: '\\wcf\\data\\reaction\\ReactionAction'
- }
+ actionName: "react",
+ className: "\\wcf\\data\\reaction\\ReactionAction",
+ },
};
}
- };
+ }
+ Core.enableLegacyInheritance(UiReactionHandler);
return UiReactionHandler;
});
+++ /dev/null
-/**
- * Provides interface elements to use reactions.
- *
- * @author Joshua Ruesweg
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Ui/Reaction/Handler
- * @since 5.2
- */
-define(
- [
- 'Ajax',
- 'Core',
- 'Dictionary',
- 'Dom/ChangeListener',
- 'Dom/Util',
- 'Ui/Alignment',
- 'Ui/CloseOverlay',
- 'Ui/Screen',
- 'WoltLabSuite/Core/Ui/Reaction/CountButtons',
- ],
- function(
- Ajax,
- Core,
- Dictionary,
- DomChangeListener,
- DomUtil,
- UiAlignment,
- UiCloseOverlay,
- UiScreen,
- CountButtons
- ) {
- "use strict";
-
- /**
- * @constructor
- */
- function UiReactionHandler(objectType, options) { this.init(objectType, options); }
- UiReactionHandler.prototype = {
- /**
- * Initializes the reaction handler.
- *
- * @param {string} objectType object type
- * @param {object} options initialization options
- */
- init: function(objectType, options) {
- if (options.containerSelector === '') {
- throw new Error("[WoltLabSuite/Core/Ui/Reaction/Handler] Expected a non-empty string for option 'containerSelector'.");
- }
-
- this._containers = new Dictionary();
- this._objectType = objectType;
- this._cache = new Dictionary();
- this._objects = new Dictionary();
-
- this._popoverCurrentObjectId = 0;
-
- this._popover = null;
- this._popoverContent = null;
-
- this._options = Core.extend({
- // selectors
- buttonSelector: '.reactButton',
- containerSelector: '',
- isButtonGroupNavigation: false,
- isSingleItem: false,
-
- // other stuff
- parameters: {
- data: {}
- }
- }, options);
-
- this.initReactButtons(options, objectType);
-
- this.countButtons = new CountButtons(this._objectType, this._options);
-
- DomChangeListener.add('WoltLabSuite/Core/Ui/Reaction/Handler-' + objectType, this.initReactButtons.bind(this));
- UiCloseOverlay.add('WoltLabSuite/Core/Ui/Reaction/Handler', this._closePopover.bind(this));
- },
-
- /**
- * Initializes all applicable react buttons with the given selector.
- */
- initReactButtons: function() {
- var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false, objectId;
- for (var i = 0, length = elements.length; i < length; i++) {
- element = elements[i];
- if (this._containers.has(DomUtil.identify(element))) {
- continue;
- }
-
- objectId = ~~elData(element, 'object-id');
- elementData = {
- reactButton: null,
- objectId: objectId,
- element: element
- };
-
- this._containers.set(DomUtil.identify(element), elementData);
- this._initReactButton(element, elementData);
-
- var objects = [];
- if (this._objects.has(objectId)) {
- objects = this._objects.get(objectId);
- }
-
- objects.push(elementData);
-
- this._objects.set(objectId, objects);
-
- triggerChange = true;
- }
-
- if (triggerChange) {
- DomChangeListener.trigger();
- }
- },
-
-
- /**
- * Initializes a specific react button.
- */
- _initReactButton: function(element, elementData) {
- if (this._options.isSingleItem) {
- elementData.reactButton = elBySel(this._options.buttonSelector);
- }
- else {
- elementData.reactButton = elBySel(this._options.buttonSelector, element);
- }
-
- if (elementData.reactButton === null || elementData.reactButton.length === 0) {
- // The element may have no react button.
- return;
- }
-
- //noinspection JSUnresolvedVariable
- if (Object.keys(REACTION_TYPES).length === 1) {
- //noinspection JSUnresolvedVariable
- var reaction = REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];
- elementData.reactButton.title = reaction.title;
- var textSpan = elBySel('.invisible', elementData.reactButton);
- textSpan.innerText = reaction.title;
- }
-
- elementData.reactButton.addEventListener('click', this._toggleReactPopover.bind(this, elementData.objectId, elementData.reactButton));
- },
-
- _updateReactButton: function(objectID, reactionTypeID) {
- this._objects.get(objectID).forEach(function (elementData) {
- if (elementData.reactButton !== null) {
- if (reactionTypeID) {
- elementData.reactButton.classList.add('active');
- elData(elementData.reactButton, 'reaction-type-id', reactionTypeID);
- }
- else {
- elData(elementData.reactButton, 'reaction-type-id', 0);
- elementData.reactButton.classList.remove('active');
- }
- }
- });
- },
-
- _markReactionAsActive: function() {
- var reactionTypeID = null;
- this._objects.get(this._popoverCurrentObjectId).forEach(function (element) {
- if (element.reactButton !== null) {
- reactionTypeID = ~~elData(element.reactButton, 'reaction-type-id');
- }
- });
-
- if (reactionTypeID === null) {
- throw new Error("Unable to find react button for current popover.");
- }
-
- // Clear the old active state.
- elBySelAll('.reactionTypeButton.active', this._getPopover(), function(element) {
- element.classList.remove('active');
- });
-
- var scrollableContainer = elBySel('.reactionPopoverContent', this._getPopover());
- if (reactionTypeID) {
- var reactionTypeButton = elBySel('.reactionTypeButton[data-reaction-type-id="' + reactionTypeID + '"]', this._getPopover());
- reactionTypeButton.classList.add('active');
-
- if (~~elData(reactionTypeButton, 'is-assignable') === 0) {
- elShow(reactionTypeButton);
- }
-
- this._scrollReactionIntoView(scrollableContainer, reactionTypeButton);
- }
- else {
- // The "first" reaction is positioned as close as possible to the toggle button,
- // which means that we need to scroll the list to the bottom if the popover is
- // displayed above the toggle button.
- if (UiScreen.is('screen-xs')) {
- if (this._getPopover().classList.contains('inverseOrder')) {
- scrollableContainer.scrollTop = 0;
- }
- else {
- scrollableContainer.scrollTop = scrollableContainer.scrollHeight - scrollableContainer.clientHeight;
- }
- }
- }
- },
-
- _scrollReactionIntoView: function (scrollableContainer, reactionTypeButton) {
- // Do not scroll if the button is located in the upper 75%.
- if (reactionTypeButton.offsetTop < scrollableContainer.clientHeight * 0.75) {
- scrollableContainer.scrollTop = 0;
- }
- else {
- // `Element.scrollTop` permits arbitrary values and will always clamp them to
- // the maximum possible offset value. We can abuse this behavior by calculating
- // the values to place the selected reaction in the center of the popover,
- // regardless of the offset being out of range.
- scrollableContainer.scrollTop = reactionTypeButton.offsetTop + reactionTypeButton.clientHeight / 2 - scrollableContainer.clientHeight / 2;
- }
- },
-
- /**
- * Toggle the visibility of the react popover.
- *
- * @param {int} objectId
- * @param {Element} element
- * @param {?Event} event
- */
- _toggleReactPopover: function(objectId, element, event) {
- if (event !== null) {
- event.preventDefault();
- event.stopPropagation();
- }
-
- //noinspection JSUnresolvedVariable
- if (Object.keys(REACTION_TYPES).length === 1) {
- //noinspection JSUnresolvedVariable
- var reaction = REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];
- this._popoverCurrentObjectId = objectId;
-
- this._react(reaction.reactionTypeID);
- }
- else {
- if (this._popoverCurrentObjectId === 0 || this._popoverCurrentObjectId !== objectId) {
- this._openReactPopover(objectId, element);
- }
- else {
- this._closePopover(objectId, element);
- }
- }
- },
-
- /**
- * Opens the react popover for a specific react button.
- *
- * @param {int} objectId objectId of the element
- * @param {Element} element container element
- */
- _openReactPopover: function(objectId, element) {
- if (this._popoverCurrentObjectId !== 0) {
- this._closePopover();
- }
-
- this._popoverCurrentObjectId = objectId;
-
- UiAlignment.set(this._getPopover(), element, {
- pointer: true,
- horizontal: (this._options.isButtonGroupNavigation) ? 'left' : 'center',
- vertical: UiScreen.is('screen-xs') ? 'bottom' : 'top'
- });
-
- if (this._options.isButtonGroupNavigation) {
- element.closest('nav').style.setProperty('opacity', '1', '');
- }
-
- var popover = this._getPopover();
-
- // The popover could be rendered below the input field on mobile, in which case
- // the "first" button is displayed at the bottom and thus farthest away. Reversing
- // the display order will restore the logic by placing the "first" button as close
- // to the react button as possible.
- var inverseOrder = popover.style.getPropertyValue('bottom') === 'auto';
- popover.classList[inverseOrder ? 'add' : 'remove']('inverseOrder');
-
- this._markReactionAsActive();
-
- this._rebuildOverflowIndicator();
-
- popover.classList.remove('forceHide');
- popover.classList.add('active');
- },
-
- /**
- * Returns the react popover element.
- *
- * @returns {Element}
- */
- _getPopover: function() {
- if (this._popover == null) {
- this._popover = elCreate('div');
- this._popover.className = 'reactionPopover forceHide';
-
- this._popoverContent = elCreate('div');
- this._popoverContent.className = 'reactionPopoverContent';
-
- var popoverContentHTML = elCreate('ul');
- popoverContentHTML.className = 'reactionTypeButtonList';
-
- var sortedReactionTypes = this._getSortedReactionTypes();
-
- for (var key in sortedReactionTypes) {
- if (!sortedReactionTypes.hasOwnProperty(key)) continue;
-
- var reactionType = sortedReactionTypes[key];
-
- var reactionTypeItem = elCreate('li');
- reactionTypeItem.className = 'reactionTypeButton jsTooltip';
- elData(reactionTypeItem, 'reaction-type-id', reactionType.reactionTypeID);
- elData(reactionTypeItem, 'title', reactionType.title);
- elData(reactionTypeItem, 'is-assignable', ~~reactionType.isAssignable);
-
- reactionTypeItem.title = reactionType.title;
-
- var reactionTypeItemSpan = elCreate('span');
- reactionTypeItemSpan.className = 'reactionTypeButtonTitle';
- reactionTypeItemSpan.innerHTML = reactionType.title;
-
- //noinspection JSUnresolvedVariable
- reactionTypeItem.innerHTML = reactionType.renderedIcon;
-
- reactionTypeItem.appendChild(reactionTypeItemSpan);
-
- reactionTypeItem.addEventListener('click', this._react.bind(this, reactionType.reactionTypeID));
-
- if (!reactionType.isAssignable) {
- elHide(reactionTypeItem);
- }
-
- popoverContentHTML.appendChild(reactionTypeItem);
- }
-
- this._popoverContent.appendChild(popoverContentHTML);
- this._popoverContent.addEventListener('scroll', this._rebuildOverflowIndicator.bind(this), {passive: true});
-
- this._popover.appendChild(this._popoverContent);
-
- var pointer = elCreate('span');
- pointer.className = 'elementPointer';
- pointer.appendChild(elCreate('span'));
- this._popover.appendChild(pointer);
-
- document.body.appendChild(this._popover);
-
- DomChangeListener.trigger();
- }
-
- return this._popover;
- },
-
- _rebuildOverflowIndicator: function () {
- var hasTopOverflow = this._popoverContent.scrollTop > 0;
- this._popoverContent.classList[hasTopOverflow ? 'add' : 'remove']('overflowTop');
-
- var hasBottomOverflow = this._popoverContent.scrollTop + this._popoverContent.clientHeight < this._popoverContent.scrollHeight;
- this._popoverContent.classList[hasBottomOverflow ? 'add' : 'remove']('overflowBottom');
- },
-
- /**
- * Sort the reaction types by the showOrder field.
- *
- * @returns {Array} the reaction types sorted by showOrder
- */
- _getSortedReactionTypes: function() {
- var sortedReactionTypes = [];
-
- // convert our reaction type object to an array
- //noinspection JSUnresolvedVariable
- for (var key in REACTION_TYPES) {
- //noinspection JSUnresolvedVariable
- if (REACTION_TYPES.hasOwnProperty(key)) {
- //noinspection JSUnresolvedVariable
- sortedReactionTypes.push(REACTION_TYPES[key]);
- }
- }
-
- // sort the array
- sortedReactionTypes.sort(function (a, b) {
- //noinspection JSUnresolvedVariable
- return a.showOrder - b.showOrder;
- });
-
- return sortedReactionTypes;
- },
-
- /**
- * Closes the react popover.
- */
- _closePopover: function() {
- if (this._popoverCurrentObjectId !== 0) {
- this._getPopover().classList.remove('active');
-
- elBySelAll('.reactionTypeButton[data-is-assignable="0"]', this._getPopover(), elHide);
-
- if (this._options.isButtonGroupNavigation) {
- this._objects.get(this._popoverCurrentObjectId).forEach(function (elementData) {
- elementData.reactButton.closest('nav').style.cssText = "";
- });
- }
-
- this._popoverCurrentObjectId = 0;
- }
- },
-
- /**
- * React with the given reactionTypeId on an object.
- *
- * @param {init} reactionTypeId
- */
- _react: function(reactionTypeId) {
- if (~~this._popoverCurrentObjectId === 0) {
- // Double clicking the reaction will cause the first click to go through, but
- // causes the second to fail because the overlay is already closing.
- return;
- }
-
- this._options.parameters.reactionTypeID = reactionTypeId;
- this._options.parameters.data.objectID = this._popoverCurrentObjectId;
- this._options.parameters.data.objectType = this._objectType;
-
- Ajax.api(this, {
- parameters: this._options.parameters
- });
-
- this._closePopover();
- },
-
- _ajaxSuccess: function(data) {
- //noinspection JSUnresolvedVariable
- this.countButtons.updateCountButtons(data.returnValues.objectID, data.returnValues.reactions);
-
- this._updateReactButton(data.returnValues.objectID, data.returnValues.reactionTypeID);
- },
-
- _ajaxSetup: function() {
- return {
- data: {
- actionName: 'react',
- className: '\\wcf\\data\\reaction\\ReactionAction'
- }
- };
- }
- };
-
- return UiReactionHandler;
- });
--- /dev/null
+/**
+ * Provides interface elements to use reactions.
+ *
+ * @author Joshua Ruesweg
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/Reaction/Handler
+ * @since 5.2
+ */
+
+import * as Ajax from "../../Ajax";
+import { AjaxCallbackSetup } from "../../Ajax/Data";
+import * as Core from "../../Core";
+import DomChangeListener from "../../Dom/Change/Listener";
+import DomUtil from "../../Dom/Util";
+import * as UiAlignment from "../Alignment";
+import UiCloseOverlay from "../CloseOverlay";
+import * as UiScreen from "../Screen";
+import CountButtons from "./CountButtons";
+import { Reaction, ReactionStats } from "./Data";
+
+interface ReactionHandlerOptions {
+ // selectors
+ buttonSelector: string;
+ containerSelector: string;
+ isButtonGroupNavigation: boolean;
+ isSingleItem: boolean;
+
+ // other stuff
+ parameters: {
+ data: {
+ [key: string]: unknown;
+ };
+ reactionTypeID?: number;
+ };
+}
+
+interface ElementData {
+ reactButton: HTMLElement | null;
+ objectId: number;
+ element: HTMLElement;
+}
+
+interface AjaxResponse {
+ returnValues: {
+ objectID: number;
+ objectType: string;
+ reactions: ReactionStats;
+ reactionTypeID: number;
+ reputationCount: number;
+ };
+}
+
+class UiReactionHandler {
+ readonly countButtons: CountButtons;
+ protected readonly _cache = new Map<string, unknown>();
+ protected readonly _containers = new Map<string, ElementData>();
+ protected readonly _options: ReactionHandlerOptions;
+ protected readonly _objects = new Map<number, ElementData[]>();
+ protected readonly _objectType: string;
+ protected _popoverCurrentObjectId = 0;
+ protected _popover: HTMLElement | null;
+ protected _popoverContent: HTMLElement | null;
+
+ /**
+ * Initializes the reaction handler.
+ */
+ constructor(objectType: string, opts: ReactionHandlerOptions) {
+ if (!opts.containerSelector) {
+ throw new Error(
+ "[WoltLabSuite/Core/Ui/Reaction/Handler] Expected a non-empty string for option 'containerSelector'.",
+ );
+ }
+
+ this._objectType = objectType;
+
+ this._popover = null;
+ this._popoverContent = null;
+
+ this._options = Core.extend(
+ {
+ // selectors
+ buttonSelector: ".reactButton",
+ containerSelector: "",
+ isButtonGroupNavigation: false,
+ isSingleItem: false,
+
+ // other stuff
+ parameters: {
+ data: {},
+ },
+ },
+ opts,
+ ) as ReactionHandlerOptions;
+
+ this.initReactButtons();
+
+ this.countButtons = new CountButtons(this._objectType, this._options);
+
+ DomChangeListener.add(`WoltLabSuite/Core/Ui/Reaction/Handler-${objectType}`, () => this.initReactButtons());
+ UiCloseOverlay.add("WoltLabSuite/Core/Ui/Reaction/Handler", () => this._closePopover());
+ }
+
+ /**
+ * Initializes all applicable react buttons with the given selector.
+ */
+ initReactButtons(): void {
+ let triggerChange = false;
+
+ document.querySelectorAll(this._options.containerSelector).forEach((element: HTMLElement) => {
+ const elementId = DomUtil.identify(element);
+ if (this._containers.has(elementId)) {
+ return;
+ }
+
+ const objectId = ~~element.dataset.objectId!;
+ const elementData: ElementData = {
+ reactButton: null,
+ objectId: objectId,
+ element: element,
+ };
+
+ this._containers.set(elementId, elementData);
+ this._initReactButton(element, elementData);
+
+ const objects = this._objects.get(objectId) || [];
+
+ objects.push(elementData);
+
+ this._objects.set(objectId, objects);
+
+ triggerChange = true;
+ });
+
+ if (triggerChange) {
+ DomChangeListener.trigger();
+ }
+ }
+
+ /**
+ * Initializes a specific react button.
+ */
+ _initReactButton(element: HTMLElement, elementData: ElementData): void {
+ if (this._options.isSingleItem) {
+ elementData.reactButton = document.querySelector(this._options.buttonSelector) as HTMLElement;
+ } else {
+ elementData.reactButton = element.querySelector(this._options.buttonSelector) as HTMLElement;
+ }
+
+ if (elementData.reactButton === null) {
+ // The element may have no react button.
+ return;
+ }
+
+ const availableReactions = Object.values(window.REACTION_TYPES);
+ if (availableReactions.length === 1) {
+ const reaction = availableReactions[0];
+ elementData.reactButton.title = reaction.title;
+ const textSpan = elementData.reactButton.querySelector(".invisible")!;
+ textSpan.textContent = reaction.title;
+ }
+
+ elementData.reactButton.addEventListener("click", (ev) => {
+ this._toggleReactPopover(elementData.objectId, elementData.reactButton!, ev);
+ });
+ }
+
+ protected _updateReactButton(objectID: number, reactionTypeID: number): void {
+ this._objects.get(objectID)!.forEach((elementData) => {
+ if (elementData.reactButton !== null) {
+ if (reactionTypeID) {
+ elementData.reactButton.classList.add("active");
+ elementData.reactButton.dataset.reactionTypeId = reactionTypeID.toString();
+ } else {
+ elementData.reactButton.dataset.reactionTypeId = "0";
+ elementData.reactButton.classList.remove("active");
+ }
+ }
+ });
+ }
+
+ protected _markReactionAsActive(): void {
+ let reactionTypeID = 0;
+ this._objects.get(this._popoverCurrentObjectId)!.forEach((element) => {
+ if (element.reactButton !== null) {
+ reactionTypeID = ~~element.reactButton.dataset.reactionTypeId!;
+ }
+ });
+
+ if (!reactionTypeID) {
+ throw new Error("Unable to find react button for current popover.");
+ }
+
+ // Clear the old active state.
+ const popover = this._getPopover();
+ popover.querySelectorAll(".reactionTypeButton.active").forEach((el) => el.classList.remove("active"));
+
+ const scrollableContainer = popover.querySelector(".reactionPopoverContent") as HTMLElement;
+ if (reactionTypeID) {
+ const reactionTypeButton = popover.querySelector(
+ `.reactionTypeButton[data-reaction-type-id="${reactionTypeID}"]`,
+ ) as HTMLElement;
+ reactionTypeButton.classList.add("active");
+
+ if (~~reactionTypeButton.dataset.isAssignable! === 0) {
+ DomUtil.show(reactionTypeButton);
+ }
+
+ this._scrollReactionIntoView(scrollableContainer, reactionTypeButton);
+ } else {
+ // The "first" reaction is positioned as close as possible to the toggle button,
+ // which means that we need to scroll the list to the bottom if the popover is
+ // displayed above the toggle button.
+ if (UiScreen.is("screen-xs")) {
+ if (popover.classList.contains("inverseOrder")) {
+ scrollableContainer.scrollTop = 0;
+ } else {
+ scrollableContainer.scrollTop = scrollableContainer.scrollHeight - scrollableContainer.clientHeight;
+ }
+ }
+ }
+ }
+
+ protected _scrollReactionIntoView(scrollableContainer: HTMLElement, reactionTypeButton: HTMLElement): void {
+ // Do not scroll if the button is located in the upper 75%.
+ if (reactionTypeButton.offsetTop < scrollableContainer.clientHeight * 0.75) {
+ scrollableContainer.scrollTop = 0;
+ } else {
+ // `Element.scrollTop` permits arbitrary values and will always clamp them to
+ // the maximum possible offset value. We can abuse this behavior by calculating
+ // the values to place the selected reaction in the center of the popover,
+ // regardless of the offset being out of range.
+ scrollableContainer.scrollTop =
+ reactionTypeButton.offsetTop + reactionTypeButton.clientHeight / 2 - scrollableContainer.clientHeight / 2;
+ }
+ }
+
+ /**
+ * Toggle the visibility of the react popover.
+ */
+ protected _toggleReactPopover(objectId: number, element: HTMLElement, event: MouseEvent): void {
+ if (event !== null) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ const availableReactions = Object.values(window.REACTION_TYPES);
+ if (availableReactions.length === 1) {
+ const reaction = availableReactions[0];
+ this._popoverCurrentObjectId = objectId;
+
+ this._react(reaction.reactionTypeID);
+ } else {
+ if (this._popoverCurrentObjectId === 0 || this._popoverCurrentObjectId !== objectId) {
+ this._openReactPopover(objectId, element);
+ } else {
+ this._closePopover();
+ }
+ }
+ }
+
+ /**
+ * Opens the react popover for a specific react button.
+ */
+ protected _openReactPopover(objectId: number, element: HTMLElement): void {
+ if (this._popoverCurrentObjectId !== 0) {
+ this._closePopover();
+ }
+
+ this._popoverCurrentObjectId = objectId;
+
+ UiAlignment.set(this._getPopover(), element, {
+ pointer: true,
+ horizontal: this._options.isButtonGroupNavigation ? "left" : "center",
+ vertical: UiScreen.is("screen-xs") ? "bottom" : "top",
+ });
+
+ if (this._options.isButtonGroupNavigation) {
+ element.closest("nav")!.style.setProperty("opacity", "1", "");
+ }
+
+ const popover = this._getPopover();
+
+ // The popover could be rendered below the input field on mobile, in which case
+ // the "first" button is displayed at the bottom and thus farthest away. Reversing
+ // the display order will restore the logic by placing the "first" button as close
+ // to the react button as possible.
+ const inverseOrder = popover.style.getPropertyValue("bottom") === "auto";
+ if (inverseOrder) {
+ popover.classList.add("inverseOrder");
+ } else {
+ popover.classList.remove("inverseOrder");
+ }
+
+ this._markReactionAsActive();
+
+ this._rebuildOverflowIndicator();
+
+ popover.classList.remove("forceHide");
+ popover.classList.add("active");
+ }
+
+ /**
+ * Returns the react popover element.
+ */
+ protected _getPopover(): HTMLElement {
+ if (this._popover == null) {
+ this._popover = document.createElement("div");
+ this._popover.className = "reactionPopover forceHide";
+
+ this._popoverContent = document.createElement("div");
+ this._popoverContent.className = "reactionPopoverContent";
+
+ const popoverContentHTML = document.createElement("ul");
+ popoverContentHTML.className = "reactionTypeButtonList";
+
+ this._getSortedReactionTypes().forEach((reactionType) => {
+ const reactionTypeItem = document.createElement("li");
+ reactionTypeItem.className = "reactionTypeButton jsTooltip";
+ reactionTypeItem.dataset.reactionTypeId = reactionType.reactionTypeID.toString();
+ reactionTypeItem.dataset.title = reactionType.title;
+ reactionTypeItem.dataset.isAssignable = reactionType.isAssignable.toString();
+
+ reactionTypeItem.title = reactionType.title;
+
+ const reactionTypeItemSpan = document.createElement("span");
+ reactionTypeItemSpan.className = "reactionTypeButtonTitle";
+ reactionTypeItemSpan.innerHTML = reactionType.title;
+
+ reactionTypeItem.innerHTML = reactionType.renderedIcon;
+
+ reactionTypeItem.appendChild(reactionTypeItemSpan);
+
+ reactionTypeItem.addEventListener("click", () => this._react(reactionType.reactionTypeID));
+
+ if (!reactionType.isAssignable) {
+ DomUtil.hide(reactionTypeItem);
+ }
+
+ popoverContentHTML.appendChild(reactionTypeItem);
+ });
+
+ this._popoverContent.appendChild(popoverContentHTML);
+ this._popoverContent.addEventListener("scroll", () => this._rebuildOverflowIndicator(), { passive: true });
+
+ this._popover.appendChild(this._popoverContent);
+
+ const pointer = document.createElement("span");
+ pointer.className = "elementPointer";
+ pointer.appendChild(document.createElement("span"));
+ this._popover.appendChild(pointer);
+
+ document.body.appendChild(this._popover);
+
+ DomChangeListener.trigger();
+ }
+
+ return this._popover;
+ }
+
+ protected _rebuildOverflowIndicator(): void {
+ const popoverContent = this._popoverContent!;
+ const hasTopOverflow = popoverContent.scrollTop > 0;
+ if (hasTopOverflow) {
+ popoverContent.classList.add("overflowTop");
+ } else {
+ popoverContent.classList.remove("overflowTop");
+ }
+
+ const hasBottomOverflow = popoverContent.scrollTop + popoverContent.clientHeight < popoverContent.scrollHeight;
+ if (hasBottomOverflow) {
+ popoverContent.classList.add("overflowBottom");
+ } else {
+ popoverContent.classList.remove("overflowBottom");
+ }
+ }
+
+ /**
+ * Sort the reaction types by the showOrder field.
+ */
+ protected _getSortedReactionTypes(): Reaction[] {
+ return Object.values(window.REACTION_TYPES).sort((a, b) => a.showOrder - b.showOrder);
+ }
+
+ /**
+ * Closes the react popover.
+ */
+ protected _closePopover(): void {
+ if (this._popoverCurrentObjectId !== 0) {
+ const popover = this._getPopover();
+ popover.classList.remove("active");
+
+ popover
+ .querySelectorAll('.reactionTypeButton[data-is-assignable="0"]')
+ .forEach((el: HTMLElement) => DomUtil.hide(el));
+
+ if (this._options.isButtonGroupNavigation) {
+ this._objects.get(this._popoverCurrentObjectId)!.forEach((elementData) => {
+ elementData.reactButton!.closest("nav")!.style.cssText = "";
+ });
+ }
+
+ this._popoverCurrentObjectId = 0;
+ }
+ }
+
+ /**
+ * React with the given reactionTypeId on an object.
+ */
+ protected _react(reactionTypeId: number): void {
+ if (~~this._popoverCurrentObjectId === 0) {
+ // Double clicking the reaction will cause the first click to go through, but
+ // causes the second to fail because the overlay is already closing.
+ return;
+ }
+
+ this._options.parameters.reactionTypeID = reactionTypeId;
+ this._options.parameters.data.objectID = this._popoverCurrentObjectId;
+ this._options.parameters.data.objectType = this._objectType;
+
+ Ajax.api(this, {
+ parameters: this._options.parameters,
+ });
+
+ this._closePopover();
+ }
+
+ _ajaxSuccess(data: AjaxResponse): void {
+ this.countButtons.updateCountButtons(data.returnValues.objectID, data.returnValues.reactions);
+
+ this._updateReactButton(data.returnValues.objectID, data.returnValues.reactionTypeID);
+ }
+
+ _ajaxSetup(): ReturnType<AjaxCallbackSetup> {
+ return {
+ data: {
+ actionName: "react",
+ className: "\\wcf\\data\\reaction\\ReactionAction",
+ },
+ };
+ }
+}
+
+Core.enableLegacyInheritance(UiReactionHandler);
+
+export = UiReactionHandler;