e5c0d20f058a73dd25ffeb38d4d2e0e98ff2cd35
[GitHub/WoltLab/WCF.git] / ts / WoltLabSuite / Core / Acp / Ui / User / Editor.ts
1 /**
2 * User editing capabilities for the user list.
3 *
4 * @author Alexander Ebert
5 * @copyright 2001-2019 WoltLab GmbH
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7 * @module WoltLabSuite/Core/Acp/Ui/User/Editor
8 * @since 3.1
9 */
10
11 import AcpUserContentRemoveHandler from "./Content/Remove/Handler";
12 import * as Ajax from "../../../Ajax";
13 import * as Core from "../../../Core";
14 import * as EventHandler from "../../../Event/Handler";
15 import * as Language from "../../../Language";
16 import * as UiNotification from "../../../Ui/Notification";
17 import UiDropdownSimple from "../../../Ui/Dropdown/Simple";
18 import { AjaxCallbackObject, DatabaseObjectActionResponse } from "../../../Ajax/Data";
19 import DomUtil from "../../../Dom/Util";
20
21 interface RefreshUsersData {
22 userIds: number[];
23 }
24
25 class AcpUiUserEditor {
26 /**
27 * Initializes the edit dropdown for each user.
28 */
29 constructor() {
30 document.querySelectorAll(".jsUserRow").forEach((userRow: HTMLTableRowElement) => this.initUser(userRow));
31
32 EventHandler.add("com.woltlab.wcf.acp.user", "refresh", (data: RefreshUsersData) => this.refreshUsers(data));
33 }
34
35 /**
36 * Initializes the edit dropdown for a user.
37 */
38 private initUser(userRow: HTMLTableRowElement): void {
39 const userId = ~~userRow.dataset.objectId!;
40 const dropdownId = `userListDropdown${userId}`;
41 const dropdownMenu = UiDropdownSimple.getDropdownMenu(dropdownId)!;
42 const legacyButtonContainer = userRow.querySelector(".jsLegacyButtons") as HTMLElement;
43
44 if (dropdownMenu.childElementCount === 0 && legacyButtonContainer.childElementCount === 0) {
45 const toggleButton = userRow.querySelector(".dropdownToggle") as HTMLAnchorElement;
46 toggleButton.classList.add("disabled");
47
48 return;
49 }
50
51 UiDropdownSimple.registerCallback(dropdownId, (identifier, action) => {
52 if (action === "open") {
53 this.rebuild(dropdownMenu, legacyButtonContainer);
54 }
55 });
56
57 const editLink = dropdownMenu.querySelector(".jsEditLink") as HTMLAnchorElement;
58 if (editLink !== null) {
59 const toggleButton = userRow.querySelector(".dropdownToggle") as HTMLAnchorElement;
60 toggleButton.addEventListener("dblclick", (event) => {
61 event.preventDefault();
62
63 editLink.click();
64 });
65 }
66
67 const sendNewPassword = dropdownMenu.querySelector(".jsSendNewPassword") as HTMLAnchorElement;
68 if (sendNewPassword !== null) {
69 sendNewPassword.addEventListener("click", (event) => {
70 event.preventDefault();
71
72 // emulate clipboard selection
73 EventHandler.fire("com.woltlab.wcf.clipboard", "com.woltlab.wcf.user", {
74 data: {
75 actionName: "com.woltlab.wcf.user.sendNewPassword",
76 parameters: {
77 confirmMessage: Language.get("wcf.acp.user.action.sendNewPassword.confirmMessage"),
78 objectIDs: [userId],
79 },
80 },
81 responseData: {
82 actionName: "com.woltlab.wcf.user.sendNewPassword",
83 objectIDs: [userId],
84 },
85 });
86 });
87 }
88
89 const deleteContent = dropdownMenu.querySelector(".jsDeleteContent") as HTMLAnchorElement;
90 if (deleteContent !== null) {
91 new AcpUserContentRemoveHandler(deleteContent, userId);
92 }
93
94 const toggleConfirmEmail = dropdownMenu.querySelector(".jsConfirmEmailToggle") as HTMLAnchorElement;
95 if (toggleConfirmEmail !== null) {
96 toggleConfirmEmail.addEventListener("click", (event) => {
97 event.preventDefault();
98
99 Ajax.api(
100 {
101 _ajaxSetup: () => {
102 const isEmailConfirmed = Core.stringToBool(userRow.dataset.emailConfirmed!);
103
104 return {
105 data: {
106 actionName: (isEmailConfirmed ? "un" : "") + "confirmEmail",
107 className: "wcf\\data\\user\\UserAction",
108 objectIDs: [userId],
109 },
110 };
111 },
112 } as AjaxCallbackObject,
113 undefined,
114 (data: DatabaseObjectActionResponse) => {
115 document.querySelectorAll(".jsUserRow").forEach((userRow: HTMLTableRowElement) => {
116 const userId = ~~userRow.dataset.objectId!;
117 if (data.objectIDs.includes(userId)) {
118 const confirmEmailButton = dropdownMenu.querySelector(".jsConfirmEmailToggle") as HTMLAnchorElement;
119
120 switch (data.actionName) {
121 case "confirmEmail":
122 userRow.dataset.emailConfirmed = "true";
123 confirmEmailButton.textContent = confirmEmailButton.dataset.unconfirmEmailMessage!;
124 break;
125
126 case "unconfirmEmail":
127 userRow.dataset.emailEonfirmed = "false";
128 confirmEmailButton.textContent = confirmEmailButton.dataset.confirmEmailMessage!;
129 break;
130
131 default:
132 throw new Error("Unreachable");
133 }
134 }
135 });
136
137 UiNotification.show();
138 },
139 );
140 });
141 }
142 }
143
144 /**
145 * Rebuilds the dropdown by adding wrapper links for legacy buttons,
146 * that will eventually receive the click event.
147 */
148 private rebuild(dropdownMenu: HTMLElement, legacyButtonContainer: HTMLElement): void {
149 dropdownMenu.querySelectorAll(".jsLegacyItem").forEach((element) => element.remove());
150
151 // inject buttons
152 const items: HTMLLIElement[] = [];
153 let deleteButton: HTMLAnchorElement | null = null;
154 Array.from(legacyButtonContainer.children).forEach((button: HTMLAnchorElement) => {
155 if (button.classList.contains("jsDeleteButton")) {
156 deleteButton = button;
157
158 return;
159 }
160
161 const item = document.createElement("li");
162 item.className = "jsLegacyItem";
163 item.innerHTML = '<a href="#"></a>';
164
165 const link = item.children[0] as HTMLAnchorElement;
166 link.textContent = button.dataset.tooltip || button.title;
167 link.addEventListener("click", (event) => {
168 event.preventDefault();
169
170 // forward click onto original button
171 if (button.nodeName === "A") {
172 button.click();
173 } else {
174 Core.triggerEvent(button, "click");
175 }
176 });
177
178 items.push(item);
179 });
180
181 items.forEach((item) => {
182 dropdownMenu.insertAdjacentElement("afterbegin", item);
183 });
184
185 if (deleteButton !== null) {
186 const dispatchDeleteButton = dropdownMenu.querySelector(".jsDispatchDelete") as HTMLAnchorElement;
187 dispatchDeleteButton.addEventListener("click", (event) => {
188 event.preventDefault();
189
190 deleteButton!.click();
191 });
192 }
193
194 // check if there are visible items before each divider
195 const listItems = Array.from(dropdownMenu.children) as HTMLElement[];
196 listItems.forEach((element) => DomUtil.show(element));
197
198 let hasItem = false;
199 listItems.forEach((item) => {
200 if (item.classList.contains("dropdownDivider")) {
201 if (!hasItem) {
202 DomUtil.hide(item);
203 }
204 } else {
205 hasItem = true;
206 }
207 });
208 }
209
210 private refreshUsers(data: RefreshUsersData): void {
211 document.querySelectorAll(".jsUserRow").forEach((userRow: HTMLTableRowElement) => {
212 const userId = ~~userRow.dataset.objectId!;
213 if (data.userIds.includes(userId)) {
214 const userStatusIcons = userRow.querySelector(".userStatusIcons") as HTMLElement;
215
216 const banned = Core.stringToBool(userRow.dataset.banned!);
217 let iconBanned = userRow.querySelector(".jsUserStatusBanned") as HTMLElement;
218 if (banned && iconBanned === null) {
219 iconBanned = document.createElement("span");
220 iconBanned.className = "icon icon16 fa-lock jsUserStatusBanned jsTooltip";
221 iconBanned.title = Language.get("wcf.user.status.banned");
222
223 userStatusIcons.appendChild(iconBanned);
224 } else if (!banned && iconBanned !== null) {
225 iconBanned.remove();
226 }
227
228 const isDisabled = !Core.stringToBool(userRow.dataset.enabled!);
229 let iconIsDisabled = userRow.querySelector(".jsUserStatusIsDisabled") as HTMLElement;
230 if (isDisabled && iconIsDisabled === null) {
231 iconIsDisabled = document.createElement("span");
232 iconIsDisabled.className = "icon icon16 fa-power-off jsUserStatusIsDisabled jsTooltip";
233 iconIsDisabled.title = Language.get("wcf.user.status.isDisabled");
234 userStatusIcons.appendChild(iconIsDisabled);
235 } else if (!isDisabled && iconIsDisabled !== null) {
236 iconIsDisabled.remove();
237 }
238 }
239 });
240 }
241 }
242
243 export = AcpUiUserEditor;