Commit | Line | Data |
---|---|---|
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 | ||
10 | import { dboAction } from "WoltLabSuite/Core/Ajax"; | |
11 | import UserMenuView from "WoltLabSuite/Core/Ui/User/Menu/View"; | |
12 | import { | |
3a2e468d | 13 | EventUpdateCounter, |
b297c618 AE |
14 | UserMenuButton, |
15 | UserMenuData, | |
16 | UserMenuFooter, | |
17 | UserMenuProvider, | |
18 | } from "WoltLabSuite/Core/Ui/User/Menu/Data/Provider"; | |
19 | import { registerProvider } from "WoltLabSuite/Core/Ui/User/Menu/Manager"; | |
20 | ||
21 | type 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 |
31 | type ResponseGetData = { |
32 | items: UserMenuData[]; | |
33 | totalCount: number; | |
34 | }; | |
35 | ||
b297c618 AE |
36 | type ResponseMarkAsRead = { |
37 | markAsRead: number; | |
38 | totalCount: number; | |
39 | }; | |
40 | ||
41 | class 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 | ||
181 | let isInitialized = false; | |
182 | export 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 | } |