Merge pull request #196 from WoltLab/event-update-user-menu-item
[GitHub/WoltLab/com.woltlab.wcf.conversation.git] / ts / WoltLabSuite / Core / Conversation / Ui / User / Menu / Data / Conversation.ts
CommitLineData
b297c618
AE
1/**
2 * User menu for notifications.
3 *
4 * @author Alexander Ebert
5 * @copyright 2001-2021 WoltLab GmbH
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
b297c618
AE
7 * @woltlabExcludeBundle tiny
8 */
9
10import { dboAction } from "WoltLabSuite/Core/Ajax";
11import UserMenuView from "WoltLabSuite/Core/Ui/User/Menu/View";
12import {
3a2e468d 13 EventUpdateCounter,
b297c618
AE
14 UserMenuButton,
15 UserMenuData,
16 UserMenuFooter,
17 UserMenuProvider,
18} from "WoltLabSuite/Core/Ui/User/Menu/Data/Provider";
19import { registerProvider } from "WoltLabSuite/Core/Ui/User/Menu/Manager";
20
21type Options = {
22 canStartConversation: boolean;
23 newConversationLink: string;
24 newConversationTitle: string;
25 noItems: string;
26 showAllLink: string;
27 showAllTitle: string;
28 title: string;
29};
30
05e725cc
AE
31type ResponseGetData = {
32 items: UserMenuData[];
33 totalCount: number;
34};
35
b297c618
AE
36type ResponseMarkAsRead = {
37 markAsRead: number;
38 totalCount: number;
39};
40
41class UserMenuDataConversation implements UserMenuProvider {
42 private readonly button: HTMLElement;
43 private counter = 0;
44 private readonly options: Options;
45 private stale = true;
46 private view: UserMenuView | undefined = undefined;
47
48 constructor(button: HTMLElement, options: Options) {
49 this.button = button;
50 this.options = options;
51
52 const badge = button.querySelector<HTMLElement>(".badge");
53 if (badge) {
54 const counter = parseInt(badge.textContent!.trim());
55 if (counter) {
56 this.counter = counter;
57 }
58 }
3a2e468d
C
59 this.button.addEventListener("updateCounter", (event: CustomEvent<EventUpdateCounter>) => {
60 this.updateCounter(event.detail.counter);
61
62 this.stale = true;
63 });
b297c618
AE
64 }
65
66 getPanelButton(): HTMLElement {
67 return this.button;
68 }
69
70 getMenuButtons(): UserMenuButton[] {
71 const buttons: UserMenuButton[] = [];
72 if (this.options.canStartConversation) {
73 buttons.push({
3f3d496b 74 icon: '<fa-icon size="24" name="plus"></fa-icon>',
b297c618
AE
75 link: this.options.newConversationLink,
76 name: "newConversation",
77 title: this.options.newConversationTitle,
78 });
79 }
80
81 return buttons;
82 }
83
84 getIdentifier(): string {
85 return "com.woltlab.wcf.conversation.conversations";
86 }
87
88 async getData(): Promise<UserMenuData[]> {
ed9666e1
AE
89 const data = (await dboAction("getConversations", "wcf\\data\\conversation\\ConversationAction")
90 .disableLoadingIndicator()
05e725cc 91 .dispatch()) as ResponseGetData;
b297c618 92
05e725cc 93 this.updateCounter(data.totalCount);
b297c618
AE
94
95 this.stale = false;
96
05e725cc 97 return data.items;
b297c618
AE
98 }
99
100 getFooter(): UserMenuFooter | null {
101 return {
102 link: this.options.showAllLink,
103 title: this.options.showAllTitle,
104 };
105 }
106
107 getTitle(): string {
108 return this.options.title;
109 }
110
111 getView(): UserMenuView {
112 if (this.view === undefined) {
113 this.view = new UserMenuView(this);
114 }
115
116 return this.view;
117 }
118
119 getEmptyViewMessage(): string {
120 return this.options.noItems;
121 }
122
9e759b54
AE
123 hasPlainTitle(): boolean {
124 return true;
125 }
126
b297c618
AE
127 hasUnreadContent(): boolean {
128 return this.counter > 0;
129 }
130
131 isStale(): boolean {
132 if (this.stale) {
133 return true;
134 }
135
136 const unreadItems = this.getView()
137 .getItems()
138 .filter((item) => item.dataset.isUnread === "true");
139 if (this.counter !== unreadItems.length) {
140 return true;
141 }
142
143 return false;
144 }
145
146 async markAsRead(objectId: number): Promise<void> {
147 const response = (await dboAction("markAsRead", "wcf\\data\\conversation\\ConversationAction")
148 .objectIds([objectId])
149 .dispatch()) as ResponseMarkAsRead;
150
151 this.updateCounter(response.totalCount);
152 }
153
154 async markAllAsRead(): Promise<void> {
155 await dboAction("markAllAsRead", "wcf\\data\\conversation\\ConversationAction").dispatch();
969524a1
AE
156
157 this.updateCounter(0);
b297c618
AE
158 }
159
160 private updateCounter(counter: number): void {
161 let badge = this.button.querySelector<HTMLElement>(".badge");
162 if (badge === null && counter > 0) {
163 badge = document.createElement("span");
ca519063 164 badge.classList.add("badge", "badgeUpdate");
b297c618
AE
165
166 this.button.querySelector("a")!.append(badge);
167 }
168
169 if (badge) {
170 if (counter === 0) {
171 badge.remove();
172 } else {
173 badge.textContent = counter.toString();
174 }
175 }
176
177 this.counter = counter;
178 }
179}
180
181let isInitialized = false;
182export function setup(options: Options): void {
183 if (!isInitialized) {
184 const button = document.getElementById("unreadConversations");
185 if (button !== null) {
186 const provider = new UserMenuDataConversation(button, options);
187 registerProvider(provider);
188 }
189
190 isInitialized = true;
191 }
192}