nodebb-plugin-admin-chats 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -7
- package/lib/access.js +89 -0
- package/lib/history.js +110 -0
- package/lib/hooks.js +310 -0
- package/lib/lock.js +145 -0
- package/lib/overrides.js +186 -0
- package/lib/routes.js +469 -0
- package/library.js +5 -1501
- package/package.json +1 -1
- package/plugin.json +7 -6
- package/public/languages/ar/admin-chats.json +11 -1
- package/public/languages/de/admin-chats.json +11 -1
- package/public/languages/de-CH/admin-chats.json +11 -1
- package/public/languages/el/admin-chats.json +11 -1
- package/public/languages/en/admin-chats.json +11 -1
- package/public/languages/en-GB/admin-chats.json +11 -1
- package/public/languages/es/admin-chats.json +11 -1
- package/public/languages/fr/admin-chats.json +11 -1
- package/public/languages/fr-CH/admin-chats.json +11 -1
- package/public/languages/he/admin-chats.json +11 -1
- package/public/languages/hi/admin-chats.json +11 -1
- package/public/languages/it/admin-chats.json +11 -1
- package/public/languages/it-CH/admin-chats.json +11 -1
- package/public/languages/ja/admin-chats.json +11 -1
- package/public/languages/nl/admin-chats.json +11 -1
- package/public/languages/pl/admin-chats.json +11 -1
- package/public/languages/pt-BR/admin-chats.json +11 -1
- package/public/languages/rm-CH/admin-chats.json +11 -1
- package/public/languages/ru/admin-chats.json +11 -1
- package/public/languages/uk/admin-chats.json +11 -1
- package/public/languages/zh-CN/admin-chats.json +11 -1
- package/public/languages/zh-TW/admin-chats.json +11 -1
- package/static/lib/live-updates.js +42 -0
- package/static/lib/lock-toggle.js +57 -0
- package/static/lib/message-history.js +221 -0
- package/static/lib/profile-link.js +98 -0
- package/static/lib/switch-chat.js +234 -0
- package/templates/partials/chats/message-window.tpl +60 -0
- package/templates/partials/chats/message.tpl +83 -0
- package/templates/partials/chats/options.tpl +111 -0
- package/templates/partials/chats/recent_room.tpl +60 -0
- package/.github/workflows/npm-publish.yml +0 -62
- package/.vscode/settings.json +0 -3
- package/client.js +0 -917
package/client.js
DELETED
|
@@ -1,917 +0,0 @@
|
|
|
1
|
-
$(document).ready(function() {
|
|
2
|
-
|
|
3
|
-
// Initialize translations object if not already set
|
|
4
|
-
if (!window.adminChatsTranslations) {
|
|
5
|
-
window.adminChatsTranslations = {};
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// Get translations from ajaxify.data if available
|
|
10
|
-
if (ajaxify.data && ajaxify.data.adminChatsTranslations) {
|
|
11
|
-
window.adminChatsTranslations = ajaxify.data.adminChatsTranslations;
|
|
12
|
-
window.adminChatsLanguage = ajaxify.data.adminChatsLanguage;
|
|
13
|
-
window.adminChatsAccess = ajaxify.data.adminChatsAccess;
|
|
14
|
-
window.adminChatsIsAdmin = ajaxify.data.adminChatsIsAdmin;
|
|
15
|
-
window.adminChatsCanManage = ajaxify.data.adminChatsCanManage;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
syncAdminAccessFromAjaxify();
|
|
19
|
-
fetchAdminPrivileges();
|
|
20
|
-
const LOCK_PREFIX = '[admin-chat-lock]';
|
|
21
|
-
const roomStateCache = new Map();
|
|
22
|
-
|
|
23
|
-
function t(key) {
|
|
24
|
-
// Get translation from server (from language files)
|
|
25
|
-
if (window.adminChatsTranslations && window.adminChatsTranslations[key]) {
|
|
26
|
-
return window.adminChatsTranslations[key];
|
|
27
|
-
}
|
|
28
|
-
// If translation not found, log warning and return the key itself
|
|
29
|
-
return key;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function isEnglishSystem() {
|
|
33
|
-
const lang = window.adminChatsLanguage || $('html').attr('lang') || 'en';
|
|
34
|
-
const langCode = lang.split('-')[0];
|
|
35
|
-
return langCode === 'en';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function hasAdminChatsAccess() {
|
|
39
|
-
return !!((ajaxify && ajaxify.data && (ajaxify.data.adminChatsAccess || ajaxify.data.adminChatsCanManage)) || window.adminChatsAccess || window.adminChatsCanManage || (app.user && app.user.isAdmin));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function isAdminUser() {
|
|
43
|
-
return !!((ajaxify && ajaxify.data && ajaxify.data.adminChatsIsAdmin) || window.adminChatsIsAdmin || (app.user && app.user.isAdmin));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function fetchAdminPrivileges() {
|
|
47
|
-
if (!hasAdminChatsAccess() || window.adminChatsPrivilegesPending) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
window.adminChatsPrivilegesPending = true;
|
|
52
|
-
try {
|
|
53
|
-
const response = await fetch(`${config.relative_path || ''}/api/admin-chats/privileges`, {
|
|
54
|
-
headers: {
|
|
55
|
-
'x-csrf-token': config.csrf_token,
|
|
56
|
-
},
|
|
57
|
-
credentials: 'same-origin',
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
if (!response.ok) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const payload = await response.json();
|
|
65
|
-
if (payload && typeof payload.adminChatsAccess === 'boolean') {
|
|
66
|
-
window.adminChatsAccess = payload.adminChatsAccess;
|
|
67
|
-
}
|
|
68
|
-
if (payload && typeof payload.adminChatsCanManage === 'boolean') {
|
|
69
|
-
window.adminChatsCanManage = payload.adminChatsCanManage;
|
|
70
|
-
}
|
|
71
|
-
if (payload && typeof payload.adminChatsIsAdmin === 'boolean') {
|
|
72
|
-
window.adminChatsIsAdmin = payload.adminChatsIsAdmin;
|
|
73
|
-
}
|
|
74
|
-
} catch (err) {
|
|
75
|
-
// ignore
|
|
76
|
-
} finally {
|
|
77
|
-
window.adminChatsPrivilegesPending = false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function syncAdminAccessFromAjaxify() {
|
|
82
|
-
if (!ajaxify || !ajaxify.data) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (typeof ajaxify.data.adminChatsAccess === 'boolean') {
|
|
86
|
-
window.adminChatsAccess = ajaxify.data.adminChatsAccess;
|
|
87
|
-
}
|
|
88
|
-
if (typeof ajaxify.data.adminChatsCanManage === 'boolean') {
|
|
89
|
-
window.adminChatsCanManage = ajaxify.data.adminChatsCanManage;
|
|
90
|
-
}
|
|
91
|
-
if (typeof ajaxify.data.adminChatsIsAdmin === 'boolean') {
|
|
92
|
-
window.adminChatsIsAdmin = ajaxify.data.adminChatsIsAdmin;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function canManageChats() {
|
|
97
|
-
return !!((ajaxify && ajaxify.data && ajaxify.data.adminChatsCanManage) || window.adminChatsCanManage || isAdminUser());
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
let adminChatsAccessProbe = null;
|
|
101
|
-
|
|
102
|
-
function resolveAdminChatsAccess() {
|
|
103
|
-
if (hasAdminChatsAccess()) {
|
|
104
|
-
return Promise.resolve(true);
|
|
105
|
-
}
|
|
106
|
-
if (window.adminChatsAccessChecked) {
|
|
107
|
-
return Promise.resolve(false);
|
|
108
|
-
}
|
|
109
|
-
if (adminChatsAccessProbe) {
|
|
110
|
-
return adminChatsAccessProbe;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
adminChatsAccessProbe = fetch(`${config.relative_path || ''}/api/admin-chats?start=0`, {
|
|
114
|
-
headers: {
|
|
115
|
-
'x-csrf-token': config.csrf_token,
|
|
116
|
-
},
|
|
117
|
-
credentials: 'same-origin',
|
|
118
|
-
}).then((response) => {
|
|
119
|
-
const ok = response && response.ok;
|
|
120
|
-
window.adminChatsAccess = ok;
|
|
121
|
-
return ok;
|
|
122
|
-
}).catch(() => {
|
|
123
|
-
window.adminChatsAccess = false;
|
|
124
|
-
return false;
|
|
125
|
-
}).finally(() => {
|
|
126
|
-
window.adminChatsAccessChecked = true;
|
|
127
|
-
adminChatsAccessProbe = null;
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
return adminChatsAccessProbe;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Get lockedAction message from server translations only (based on forum settings)
|
|
134
|
-
function getLockedActionMessage() {
|
|
135
|
-
if (window.adminChatsTranslations && window.adminChatsTranslations['errors.lockedAction']) {
|
|
136
|
-
return window.adminChatsTranslations['errors.lockedAction'];
|
|
137
|
-
}
|
|
138
|
-
// If server translation not available, default to English
|
|
139
|
-
return 'This action cannot be performed in a locked room.';
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function isAdminAllChatsPage() {
|
|
143
|
-
return !!(ajaxify && ajaxify.data && ajaxify.data.adminAllChats);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function getAdminChatsPageUrl(roomId) {
|
|
147
|
-
return roomId ? `chats/${roomId}` : 'chats';
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function getAdminChatsDataUrl(roomId) {
|
|
151
|
-
const params = new URL(document.location).searchParams;
|
|
152
|
-
params.set('switch', 1);
|
|
153
|
-
const query = params.toString();
|
|
154
|
-
return `${config.relative_path || ''}/api/admin-chats/page${roomId ? `/${roomId}` : ''}${query ? `?${query}` : ''}`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async function loadMoreAdminChats() {
|
|
158
|
-
const recentChats = $('[component="chat/recent"]');
|
|
159
|
-
if (!isAdminAllChatsPage() || !recentChats.length || recentChats.attr('loading')) {
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
recentChats.attr('loading', 1);
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const start = parseInt(recentChats.attr('data-nextstart'), 10) || 0;
|
|
167
|
-
const response = await fetch(`${config.relative_path || ''}/api/admin-chats?start=${start}`, {
|
|
168
|
-
headers: {
|
|
169
|
-
'x-csrf-token': config.csrf_token,
|
|
170
|
-
},
|
|
171
|
-
credentials: 'same-origin',
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
if (!response.ok) {
|
|
175
|
-
throw new Error('Unable to load chats');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const payload = await response.json();
|
|
179
|
-
if (payload.rooms && payload.rooms.length) {
|
|
180
|
-
payload.loadingMore = true;
|
|
181
|
-
const html = await app.parseAndTranslate('chats', 'rooms', payload);
|
|
182
|
-
recentChats.append(html);
|
|
183
|
-
html.find('.timeago').timeago();
|
|
184
|
-
recentChats.attr('data-nextstart', payload.nextStart);
|
|
185
|
-
}
|
|
186
|
-
} catch (err) {
|
|
187
|
-
app.alertError(err);
|
|
188
|
-
} finally {
|
|
189
|
-
recentChats.removeAttr('loading');
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function bindAdminRecentChatsInfiniteScroll() {
|
|
194
|
-
if (!isAdminAllChatsPage()) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
$('[component="chat/recent"]').off('scroll').on('scroll', utils.debounce(function() {
|
|
199
|
-
const $this = $(this);
|
|
200
|
-
const bottom = ($this[0].scrollHeight - $this.height()) * 0.9;
|
|
201
|
-
if ($this.scrollTop() > bottom) {
|
|
202
|
-
loadMoreAdminChats();
|
|
203
|
-
}
|
|
204
|
-
}, 100));
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function patchForumChatsForAdminAll() {
|
|
208
|
-
if (!isAdminAllChatsPage()) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
require(['forum/chats', 'forum/chats/messages'], function(Chats, ChatsMessages) {
|
|
213
|
-
if (!Chats) {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (!Chats._adminAllChatsPatched) {
|
|
218
|
-
const originalSwitchChat = Chats.switchChat;
|
|
219
|
-
|
|
220
|
-
Chats.switchChat = function(roomId) {
|
|
221
|
-
if (!isAdminAllChatsPage()) {
|
|
222
|
-
return originalSwitchChat.call(this, roomId);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
roomId = roomId || '';
|
|
226
|
-
// NodeBB 4.14.9+ (56c6115a61): destroyAutoComplete takes the input element, not a roomId
|
|
227
|
-
Chats.destroyAutoComplete($('[component="chat/main-wrapper"]').find('[component="chat/input"]'));
|
|
228
|
-
if (ajaxify.data.roomId) {
|
|
229
|
-
socket.emit('modules.chats.leave', ajaxify.data.roomId);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const url = getAdminChatsPageUrl(roomId);
|
|
233
|
-
|
|
234
|
-
async function renderAdminChatWindow(payload) {
|
|
235
|
-
const html = await app.parseAndTranslate('partials/chats/message-window', payload);
|
|
236
|
-
const mainWrapper = $('[component="chat/main-wrapper"]');
|
|
237
|
-
mainWrapper.html(html);
|
|
238
|
-
mainWrapper.attr('data-roomid', roomId);
|
|
239
|
-
html.find('.timeago').timeago();
|
|
240
|
-
ajaxify.data = { ...ajaxify.data, ...payload, roomId: roomId };
|
|
241
|
-
ajaxify.updateTitle(ajaxify.data.title);
|
|
242
|
-
$('body').toggleClass('chat-loaded', !!roomId);
|
|
243
|
-
$('[component="chat/nav-wrapper"]').attr('data-loaded', roomId ? '1' : '0');
|
|
244
|
-
mainWrapper.find('[data-bs-toggle="tooltip"]').tooltip({ trigger: 'hover', container: '#content' });
|
|
245
|
-
Chats.setActive(roomId);
|
|
246
|
-
Chats.addEventListeners();
|
|
247
|
-
// Fire through the hooks module like core does — a raw jQuery
|
|
248
|
-
// trigger never reaches hooks.on() listeners (e.g. the reactions
|
|
249
|
-
// plugin), only legacy $(window).on() ones (e.g. the emoji plugin).
|
|
250
|
-
// hooks.fire invokes both kinds and passes the container.
|
|
251
|
-
require(['hooks'], function(hooks) {
|
|
252
|
-
hooks.fire('action:chat.loaded', $('.chats-full'));
|
|
253
|
-
});
|
|
254
|
-
if (roomId) {
|
|
255
|
-
ChatsMessages.scrollToBottomAfterImageLoad(mainWrapper.find('[component="chat/message/content"]'));
|
|
256
|
-
// Read-state is now cleared server-side when the room payload is
|
|
257
|
-
// built; just keep the list UI in sync
|
|
258
|
-
$('[component="chat/nav-wrapper"]').find(`[data-roomid="${roomId}"]`).removeClass('unread');
|
|
259
|
-
}
|
|
260
|
-
if (history.pushState) {
|
|
261
|
-
const fullUrl = `${window.location.protocol}//${window.location.host}${config.relative_path || ''}/${url}`;
|
|
262
|
-
// When the address bar already shows the target (e.g. closing a chat
|
|
263
|
-
// after a back-gesture), replace instead of stacking a duplicate entry
|
|
264
|
-
const sameUrl = window.location.href.split('?')[0] === fullUrl.split('?')[0];
|
|
265
|
-
history[sameUrl ? 'replaceState' : 'pushState']({ url: url }, null, fullUrl);
|
|
266
|
-
}
|
|
267
|
-
bindAdminRecentChatsInfiniteScroll();
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (!roomId) {
|
|
271
|
-
// Closing a chat needs no server round-trip — render the empty
|
|
272
|
-
// window from the data we already have (instant back-gesture)
|
|
273
|
-
renderAdminChatWindow({ ...ajaxify.data, roomId: 0, title: '[[pages:chats]]' });
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
fetch(getAdminChatsDataUrl(roomId), { credentials: 'include' })
|
|
278
|
-
.then(async function(response) {
|
|
279
|
-
if (!response.ok) {
|
|
280
|
-
throw new Error(`Received ${response.status}`);
|
|
281
|
-
}
|
|
282
|
-
await renderAdminChatWindow(await response.json());
|
|
283
|
-
})
|
|
284
|
-
.catch(function() {
|
|
285
|
-
// Never leave the user stuck on the list — fall back to a
|
|
286
|
-
// regular ajaxify navigation to the room
|
|
287
|
-
ajaxify.go(url);
|
|
288
|
-
});
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
Chats._adminAllChatsPatched = true;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Auto-open chat if roomId is specified in URL and the room is not
|
|
295
|
-
// already rendered (server-side render sets data-roomid on the wrapper —
|
|
296
|
-
// re-switching in that case caused a visible list->chat flash on mobile)
|
|
297
|
-
const currentRoomId = ajaxify && ajaxify.data && ajaxify.data.roomId;
|
|
298
|
-
const activeChatId = $('[component="chat/main-wrapper"]').attr('data-roomid') || '';
|
|
299
|
-
if (currentRoomId && String(currentRoomId) !== String(activeChatId)) {
|
|
300
|
-
setTimeout(function() {
|
|
301
|
-
Chats.switchChat(currentRoomId);
|
|
302
|
-
}, 100);
|
|
303
|
-
} else {
|
|
304
|
-
syncChatLoadedState();
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
bindAdminRecentChatsInfiniteScroll();
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Keep the mobile layout state (body.chat-loaded / nav data-loaded) in sync with
|
|
312
|
-
// the room that is actually rendered, without re-fetching it
|
|
313
|
-
function syncChatLoadedState() {
|
|
314
|
-
const activeChatId = parseInt($('[component="chat/main-wrapper"]').attr('data-roomid'), 10) || 0;
|
|
315
|
-
$('body').toggleClass('chat-loaded', !!activeChatId);
|
|
316
|
-
$('[component="chat/nav-wrapper"]').attr('data-loaded', activeChatId ? '1' : '0');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function replaceAdminEmptyStateText() {
|
|
320
|
-
if (!hasAdminChatsAccess()) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
$('span.text-muted.text-sm').each(function() {
|
|
325
|
-
const currentText = $(this).text().trim();
|
|
326
|
-
if (
|
|
327
|
-
currentText.includes("אין לכם צ'אטים פעילים") ||
|
|
328
|
-
currentText === "אין לכם צ'אטים פעילים." ||
|
|
329
|
-
currentText.includes('You have no active chats') ||
|
|
330
|
-
currentText === 'You have no active chats.'
|
|
331
|
-
) {
|
|
332
|
-
$(this).text(t('empty.selectChat'));
|
|
333
|
-
$(this).removeClass('text-muted');
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function getChatWindows() {
|
|
339
|
-
const windows = new Set($('[component="chat/message/window"]').toArray());
|
|
340
|
-
$('.chat-modal').each(function() {
|
|
341
|
-
const $modal = $(this);
|
|
342
|
-
const nested = $modal.find('[component="chat/message/window"]').toArray();
|
|
343
|
-
if (nested.length) {
|
|
344
|
-
nested.forEach(el => windows.delete(el));
|
|
345
|
-
windows.add(this);
|
|
346
|
-
} else {
|
|
347
|
-
windows.add(this);
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
return $(Array.from(windows));
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function getWindowRoomId($window) {
|
|
354
|
-
const roomId = $window.find('[component="chat/messages"]').first().attr('data-roomid') ||
|
|
355
|
-
$window.closest('.chat-modal').attr('data-roomid') ||
|
|
356
|
-
(ajaxify && ajaxify.data && ajaxify.data.roomId);
|
|
357
|
-
return parseInt(roomId, 10) || 0;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function getCachedRoomData(roomId) {
|
|
361
|
-
const cached = roomStateCache.get(String(roomId));
|
|
362
|
-
if (!cached) {
|
|
363
|
-
return null;
|
|
364
|
-
}
|
|
365
|
-
if (Date.now() - cached.timestamp > 10000) {
|
|
366
|
-
roomStateCache.delete(String(roomId));
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
return cached.data;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function setCachedRoomData(roomId, data) {
|
|
373
|
-
roomStateCache.set(String(roomId), {
|
|
374
|
-
data,
|
|
375
|
-
timestamp: Date.now(),
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async function fetchRoomData(roomId) {
|
|
380
|
-
if (!roomId) {
|
|
381
|
-
return null;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// First, check if we have the room data in ajaxify.data (from the current page)
|
|
385
|
-
const currentRoom = ajaxify && ajaxify.data && (ajaxify.data.room || ajaxify.data);
|
|
386
|
-
if (currentRoom && parseInt(currentRoom.roomId, 10) === roomId) {
|
|
387
|
-
if (currentRoom.adminChatLock) {
|
|
388
|
-
setCachedRoomData(roomId, currentRoom);
|
|
389
|
-
return currentRoom;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Check cache
|
|
394
|
-
const cached = getCachedRoomData(roomId);
|
|
395
|
-
if (cached) {
|
|
396
|
-
return cached;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Try admin endpoint first if user has access
|
|
400
|
-
if (hasAdminChatsAccess()) {
|
|
401
|
-
// switch=1: room data only — no need to rebuild the full room list here
|
|
402
|
-
const adminEndpoint = `${config.relative_path || ''}/api/admin-chats/page/${roomId}?switch=1`;
|
|
403
|
-
try {
|
|
404
|
-
const adminResponse = await fetch(adminEndpoint, {
|
|
405
|
-
headers: {
|
|
406
|
-
'x-csrf-token': config.csrf_token,
|
|
407
|
-
},
|
|
408
|
-
credentials: 'same-origin',
|
|
409
|
-
});
|
|
410
|
-
if (adminResponse.ok) {
|
|
411
|
-
const adminPayload = await adminResponse.json();
|
|
412
|
-
const adminRoom = adminPayload && adminPayload.roomId ? adminPayload : (adminPayload && adminPayload.room ? adminPayload.room : null);
|
|
413
|
-
if (adminRoom) {
|
|
414
|
-
setCachedRoomData(roomId, adminRoom);
|
|
415
|
-
return adminRoom;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
} catch (err) {
|
|
419
|
-
// Continue to fallback
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// For all users, try to get lock data from the new endpoint
|
|
424
|
-
const lockEndpoint = `${config.relative_path || ''}/api/admin-chats/${roomId}/lock`;
|
|
425
|
-
try {
|
|
426
|
-
const lockResponse = await fetch(lockEndpoint, {
|
|
427
|
-
headers: {
|
|
428
|
-
'x-csrf-token': config.csrf_token,
|
|
429
|
-
},
|
|
430
|
-
credentials: 'same-origin',
|
|
431
|
-
});
|
|
432
|
-
if (lockResponse.ok) {
|
|
433
|
-
const lockPayload = await lockResponse.json();
|
|
434
|
-
if (lockPayload && lockPayload.lockData) {
|
|
435
|
-
const roomData = {
|
|
436
|
-
roomId: roomId,
|
|
437
|
-
adminChatLock: lockPayload.lockData,
|
|
438
|
-
};
|
|
439
|
-
setCachedRoomData(roomId, roomData);
|
|
440
|
-
return roomData;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
} catch (err) {
|
|
444
|
-
// Ignore error
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return null;
|
|
448
|
-
}
|
|
449
|
-
function isLockedForUser(roomData) {
|
|
450
|
-
return !!(roomData && roomData.adminChatLock && roomData.adminChatLock.isLocked && !canManageChats());
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function setComposerHidden($window, hidden) {
|
|
454
|
-
$window.find('[component="chat/composer"]').each(function() {
|
|
455
|
-
const $composer = $(this);
|
|
456
|
-
if (hidden) {
|
|
457
|
-
$composer.addClass('hidden').hide();
|
|
458
|
-
} else {
|
|
459
|
-
$composer.removeClass('hidden').show();
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
$window.find('[component="chat/input"], [component="chat/send"], button[data-action="send"], textarea.chat-input')
|
|
464
|
-
.prop('disabled', hidden)
|
|
465
|
-
.attr('disabled', hidden ? 'disabled' : null);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function renderLockBanner($window, hidden) {
|
|
469
|
-
$window.find('.admin-chat-lock-banner').remove();
|
|
470
|
-
if (!hidden) {
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const target = $window.find('[component="chat/messages"]').first();
|
|
475
|
-
if (!target.length) {
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const positionStyle = isEnglishSystem() ? 'float:right; clear:both;' : 'float:left; clear:both;';
|
|
480
|
-
target.prepend(`<div class="admin-chat-lock-banner alert alert-warning mb-2 text-start" style="${positionStyle} max-width: fit-content;">${t('lock.banner')}</div>`);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function updateLockedActionVisibility($window, hidden) {
|
|
484
|
-
[
|
|
485
|
-
'[data-action="reply"]',
|
|
486
|
-
'[data-action="edit"]',
|
|
487
|
-
'[data-action="delete"]',
|
|
488
|
-
'[data-action="restore"]',
|
|
489
|
-
'[data-action="kick"]',
|
|
490
|
-
'[data-action="toggleOwner"]'
|
|
491
|
-
].forEach(function(selector) {
|
|
492
|
-
$window.find(selector).toggleClass('hidden', hidden).toggle(!hidden);
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
$window.find('[component="chat/controlsToggle"]').closest('.dropdown').toggleClass('hidden', hidden).toggle(!hidden);
|
|
496
|
-
$window.find('[component="chat/manage/user/add/search"], [component="chat/manage/user/list/search"], [component="chat/manage/save"]')
|
|
497
|
-
.toggleClass('hidden', hidden)
|
|
498
|
-
.toggle(!hidden)
|
|
499
|
-
.prop('disabled', hidden)
|
|
500
|
-
.attr('disabled', hidden ? 'disabled' : null);
|
|
501
|
-
$window.find('[component="chat/manage-modal"] .form-text, [component="chat/manage-modal"] .text-danger')
|
|
502
|
-
.toggleClass('hidden', hidden);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function updateDeleteRestoreVisibility($window) {
|
|
506
|
-
$window.find('[component="chat/message"]').each(function() {
|
|
507
|
-
const $message = $(this);
|
|
508
|
-
const isDeleted = $message.hasClass('deleted');
|
|
509
|
-
$message.find('[data-action="delete"]').toggleClass('hidden', isDeleted).toggle(!isDeleted);
|
|
510
|
-
$message.find('[data-action="restore"]').toggleClass('hidden', !isDeleted).toggle(isDeleted);
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function normalizeLockMessages($window) {
|
|
515
|
-
$window.find('[component="chat/system-message"] > div').each(function() {
|
|
516
|
-
const $el = $(this);
|
|
517
|
-
const text = $el.text().trim();
|
|
518
|
-
if (!text.includes(LOCK_PREFIX.slice(1, -1)) && !text.includes('admin-chat-lock')) {
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
$el.text(t('lock.banner'));
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function getRoomMenuTargets($window) {
|
|
526
|
-
const scoped = $window.find('[component="chat/controls"]');
|
|
527
|
-
if (scoped.length) {
|
|
528
|
-
return scoped;
|
|
529
|
-
}
|
|
530
|
-
return $window.find('[component="chat/header"], .chat-header, .modal-header, [component="chat/nav"]').find('.dropdown-menu');
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function renderAdminLockControl($window, roomData) {
|
|
534
|
-
$window.find('.admin-chat-lock-toggle-item, .admin-chat-lock-divider, .admin-chat-lock-item-wrap').remove();
|
|
535
|
-
|
|
536
|
-
if (!canManageChats() || !roomData || !roomData.roomId) {
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
const menus = getRoomMenuTargets($window);
|
|
541
|
-
if (!menus.length) {
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
const lockData = roomData.adminChatLock || {};
|
|
546
|
-
const isLocked = !!lockData.isLocked;
|
|
547
|
-
const itemText = isLocked ? t('menu.release') : t('menu.lock');
|
|
548
|
-
const iconClass = isLocked ? 'fa-lock-open' : 'fa-lock';
|
|
549
|
-
const menuItemHtml = `
|
|
550
|
-
<li role="presentation" class="admin-chat-lock-item-wrap">
|
|
551
|
-
<a href="#" role="menuitem" class="dropdown-item rounded-1 d-flex align-items-center gap-2 admin-chat-lock-toggle-item" data-room-id="${roomData.roomId}" data-locked="${isLocked}">
|
|
552
|
-
<i class="fa fa-fw ${iconClass}"></i>
|
|
553
|
-
<span>${itemText}</span>
|
|
554
|
-
</a>
|
|
555
|
-
</li>
|
|
556
|
-
<li role="presentation" class="dropdown-divider admin-chat-lock-divider"></li>
|
|
557
|
-
`;
|
|
558
|
-
|
|
559
|
-
menus.each(function() {
|
|
560
|
-
const $menu = $(this);
|
|
561
|
-
if ($menu.find('.admin-chat-lock-toggle-item').length) {
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
$menu.prepend(menuItemHtml);
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
async function applyUiToWindow($window) {
|
|
569
|
-
const roomId = getWindowRoomId($window);
|
|
570
|
-
if (!roomId) {
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const roomData = await fetchRoomData(roomId);
|
|
575
|
-
if (!roomData) {
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
const hidden = isLockedForUser(roomData);
|
|
580
|
-
setComposerHidden($window, hidden);
|
|
581
|
-
updateLockedActionVisibility($window, hidden);
|
|
582
|
-
updateDeleteRestoreVisibility($window);
|
|
583
|
-
renderLockBanner($window, hidden);
|
|
584
|
-
renderAdminLockControl($window, roomData);
|
|
585
|
-
normalizeLockMessages($window);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
function getAdminRoomsMap() {
|
|
590
|
-
const rooms = ajaxify && ajaxify.data && Array.isArray(ajaxify.data.rooms) ? ajaxify.data.rooms : [];
|
|
591
|
-
return new Map(rooms.map(room => [String(room.roomId), room]));
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function renderAdminParticipants() {
|
|
595
|
-
if (!isAdminAllChatsPage()) {
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
const roomsMap = getAdminRoomsMap();
|
|
600
|
-
$('[component="chat/recent/room"]').each(function() {
|
|
601
|
-
const $room = $(this);
|
|
602
|
-
const roomId = String($room.attr('data-roomid') || '');
|
|
603
|
-
const roomData = roomsMap.get(roomId);
|
|
604
|
-
const $container = $room.find('[component="chat/room/title"]').closest('.d-flex.flex-grow-1.flex-column.w-100');
|
|
605
|
-
|
|
606
|
-
$container.find('.admin-chat-participants').remove();
|
|
607
|
-
if (!$container.length || !roomData || !roomData.participantsLabel) {
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
const html = `<div class="admin-chat-participants text-muted text-xs text-break line-clamp-2"><span class="fw-semibold">${t('participants')}:</span> ${utils.escapeHTML(roomData.participantsLabel)}</div>`;
|
|
612
|
-
$container.find('[component="chat/room/title"]').after(html);
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
function applyAdminAllPageTweaks() {
|
|
617
|
-
if (!isAdminAllChatsPage()) {
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
$('[component="chat/recent"] .mark-read').addClass('hidden');
|
|
622
|
-
$('[data-action="leave"]').closest('li').addClass('hidden');
|
|
623
|
-
renderAdminParticipants();
|
|
624
|
-
}
|
|
625
|
-
async function refreshChatUi() {
|
|
626
|
-
replaceAdminEmptyStateText();
|
|
627
|
-
applyAdminAllPageTweaks();
|
|
628
|
-
const windows = getChatWindows();
|
|
629
|
-
await Promise.all(windows.map(function() {
|
|
630
|
-
return applyUiToWindow($(this));
|
|
631
|
-
}).get());
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
async function toggleRoomLock(roomId, nextState) {
|
|
635
|
-
const response = await fetch(`${config.relative_path || ''}/api/admin-chats/${roomId}/lock`, {
|
|
636
|
-
method: 'POST',
|
|
637
|
-
headers: {
|
|
638
|
-
'Content-Type': 'application/json',
|
|
639
|
-
'x-csrf-token': config.csrf_token,
|
|
640
|
-
},
|
|
641
|
-
body: JSON.stringify({ locked: nextState }),
|
|
642
|
-
credentials: 'same-origin',
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
if (!response.ok) {
|
|
646
|
-
throw new Error('Unable to update room lock');
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
return await response.json();
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
async function injectProfileChatsMenu() {
|
|
653
|
-
const allowed = await resolveAdminChatsAccess();
|
|
654
|
-
if (!allowed) {
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const isUserProfile = $(".account").length && window.location.pathname.includes("/user/");
|
|
659
|
-
if (!isUserProfile) {
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
const userSlug = (ajaxify && ajaxify.data && (ajaxify.data.userslug || (ajaxify.data.user && ajaxify.data.user.userslug))) || null;
|
|
664
|
-
if (!userSlug) {
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
const relativePath = config.relative_path || "";
|
|
669
|
-
const menuItemHtml = `
|
|
670
|
-
<li role="presentation" class="admin-chats-profile-link">
|
|
671
|
-
<a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="${relativePath}/user/${userSlug}/chats" role="menuitem">
|
|
672
|
-
<i class="far fa-fw fa-comments"></i>
|
|
673
|
-
<span>${t("profile.viewChats")}</span>
|
|
674
|
-
</a>
|
|
675
|
-
</li>
|
|
676
|
-
`;
|
|
677
|
-
|
|
678
|
-
let menu = $(".account-sub-links");
|
|
679
|
-
if (!menu.length) {
|
|
680
|
-
const container = $(".account .flex-shrink-0.d-flex.gap-1").first();
|
|
681
|
-
const fallbackContainer = container.length ? container : $(".account .flex-shrink-0").first();
|
|
682
|
-
|
|
683
|
-
if (fallbackContainer.length) {
|
|
684
|
-
const menuHtml = `
|
|
685
|
-
<div class="btn-group bottom-sheet admin-chats-privileges-menu">
|
|
686
|
-
<button type="button" class="btn btn-light dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
687
|
-
<i class="fa fa-gear fa-fw"></i>
|
|
688
|
-
</button>
|
|
689
|
-
<ul class="dropdown-menu dropdown-menu-end p-1 text-sm account-sub-links" role="menu"></ul>
|
|
690
|
-
</div>
|
|
691
|
-
`;
|
|
692
|
-
fallbackContainer.append(menuHtml);
|
|
693
|
-
menu = fallbackContainer.find(".account-sub-links").last();
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
if (menu.length) {
|
|
698
|
-
menu.find(".admin-chats-profile-link").remove();
|
|
699
|
-
menu.find(".admin-chats-profile-divider").remove();
|
|
700
|
-
menu.find(`a[href*="/user/${userSlug}/chats"]`).parent().remove();
|
|
701
|
-
menu.prepend(menuItemHtml);
|
|
702
|
-
|
|
703
|
-
const hasOtherItems = menu.find("li").not(".admin-chats-profile-link").length > 0;
|
|
704
|
-
if (hasOtherItems) {
|
|
705
|
-
menu.find(".admin-chats-profile-link").after('<li role="presentation" class="dropdown-divider admin-chats-profile-divider"></li>');
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
$(window).on('action:ajaxify.end', function(ev, data) {
|
|
711
|
-
syncAdminAccessFromAjaxify();
|
|
712
|
-
fetchAdminPrivileges();
|
|
713
|
-
const templateName = ajaxify && ajaxify.data && ajaxify.data.template ? ajaxify.data.template.name : '';
|
|
714
|
-
|
|
715
|
-
if (templateName.startsWith('account/')) {
|
|
716
|
-
injectProfileChatsMenu();
|
|
717
|
-
setTimeout(injectProfileChatsMenu, 200);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
if (isAdminAllChatsPage()) {
|
|
721
|
-
patchForumChatsForAdminAll();
|
|
722
|
-
bindAdminRecentChatsInfiniteScroll();
|
|
723
|
-
|
|
724
|
-
// Auto-open chat if roomId is specified in URL and that room is not already rendered
|
|
725
|
-
const currentRoomId = ajaxify && ajaxify.data && ajaxify.data.roomId;
|
|
726
|
-
const activeChatId = $('[component="chat/main-wrapper"]').attr('data-roomid') || '';
|
|
727
|
-
if (currentRoomId && String(currentRoomId) !== String(activeChatId)) {
|
|
728
|
-
setTimeout(function() {
|
|
729
|
-
require(['forum/chats'], function(Chats) {
|
|
730
|
-
if (Chats && Chats.switchChat) {
|
|
731
|
-
Chats.switchChat(currentRoomId);
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
}, 200);
|
|
735
|
-
} else {
|
|
736
|
-
syncChatLoadedState();
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
const url = data && data.url ? data.url : '';
|
|
741
|
-
if (url.match(/^user\/.+\/chats/) || url.match(/^chats(\/|$)/)) {
|
|
742
|
-
refreshChatUi();
|
|
743
|
-
setTimeout(refreshChatUi, 500);
|
|
744
|
-
setTimeout(refreshChatUi, 1200);
|
|
745
|
-
|
|
746
|
-
// Handle chat navigation for internal URL changes
|
|
747
|
-
if (url.match(/^chats\/\d+/)) {
|
|
748
|
-
setTimeout(handleChatNavigation, 300);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
$(window).on('action:chat.loaded', function() {
|
|
754
|
-
if (isAdminAllChatsPage()) {
|
|
755
|
-
patchForumChatsForAdminAll();
|
|
756
|
-
bindAdminRecentChatsInfiniteScroll();
|
|
757
|
-
|
|
758
|
-
// Auto-open chat if roomId is specified in URL and no chat is currently active
|
|
759
|
-
const currentRoomId = ajaxify && ajaxify.data && ajaxify.data.roomId;
|
|
760
|
-
const activeChatId = $('[component="chat/main-wrapper"]').attr('data-roomid');
|
|
761
|
-
|
|
762
|
-
if (currentRoomId && (!activeChatId || activeChatId !== String(currentRoomId))) {
|
|
763
|
-
setTimeout(function() {
|
|
764
|
-
require(['forum/chats'], function(Chats) {
|
|
765
|
-
if (Chats && Chats.switchChat) {
|
|
766
|
-
Chats.switchChat(currentRoomId);
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
}, 100);
|
|
770
|
-
} else {
|
|
771
|
-
syncChatLoadedState();
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
refreshChatUi();
|
|
775
|
-
setTimeout(refreshChatUi, 200);
|
|
776
|
-
setTimeout(refreshChatUi, 1000);
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
$(window).on('action:chat.closed', function() {
|
|
780
|
-
setTimeout(refreshChatUi, 200);
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
$(window).on('action:chat.onMessagesAddedToDom action:chat.edited action:chat.renamed', function() {
|
|
784
|
-
setTimeout(refreshChatUi, 0);
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
if (window.socket) {
|
|
788
|
-
socket.removeListener('event:chats.delete', refreshChatUi);
|
|
789
|
-
socket.removeListener('event:chats.restore', refreshChatUi);
|
|
790
|
-
socket.on('event:chats.delete', function() { setTimeout(refreshChatUi, 0); });
|
|
791
|
-
socket.on('event:chats.restore', function() { setTimeout(refreshChatUi, 0); });
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
// Monitor for new chat modals/windows being added to the DOM
|
|
795
|
-
// This ensures styling is applied to floating chat windows opened from non-chat pages
|
|
796
|
-
const observer = new MutationObserver(function(mutations) {
|
|
797
|
-
let hasNewChatWindow = false;
|
|
798
|
-
mutations.forEach(function(mutation) {
|
|
799
|
-
if (mutation.type === 'childList') {
|
|
800
|
-
mutation.addedNodes.forEach(function(node) {
|
|
801
|
-
if (node.nodeType === 1) { // Element node
|
|
802
|
-
const $node = $(node);
|
|
803
|
-
if ($node.is('.chat-modal') || $node.find('.chat-modal').length) {
|
|
804
|
-
hasNewChatWindow = true;
|
|
805
|
-
}
|
|
806
|
-
if ($node.is('[component="chat/message/window"]') || $node.find('[component="chat/message/window"]').length) {
|
|
807
|
-
hasNewChatWindow = true;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
});
|
|
813
|
-
if (hasNewChatWindow) {
|
|
814
|
-
setTimeout(refreshChatUi, 50);
|
|
815
|
-
}
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
observer.observe(document.body, {
|
|
819
|
-
childList: true,
|
|
820
|
-
subtree: true
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
// Handle browser back/forward navigation
|
|
824
|
-
$(window).on('popstate', function() {
|
|
825
|
-
if (isAdminAllChatsPage()) {
|
|
826
|
-
setTimeout(handleChatNavigation, 100);
|
|
827
|
-
}
|
|
828
|
-
});
|
|
829
|
-
|
|
830
|
-
// Handle URL changes for internal navigation (including back/forward gestures).
|
|
831
|
-
// The URL is the source of truth here — ajaxify.data.roomId is stale after popstate.
|
|
832
|
-
function handleChatNavigation() {
|
|
833
|
-
if (!isAdminAllChatsPage()) {
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
const urlMatch = window.location.pathname.match(/\/chats\/(\d+)/);
|
|
838
|
-
const urlRoomId = urlMatch ? parseInt(urlMatch[1], 10) : 0;
|
|
839
|
-
const activeChatId = parseInt($('[component="chat/main-wrapper"]').attr('data-roomid'), 10) || 0;
|
|
840
|
-
|
|
841
|
-
if (urlRoomId === activeChatId) {
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
require(['forum/chats'], function(Chats) {
|
|
846
|
-
if (Chats && Chats.switchChat) {
|
|
847
|
-
// urlRoomId 0 -> URL is the chat list: close the open chat and show the list
|
|
848
|
-
// (previously a back-gesture out of a chat left the chat stuck on screen)
|
|
849
|
-
Chats.switchChat(urlRoomId || '');
|
|
850
|
-
}
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
$(document).on('click', '[data-action="delete"], [data-action="restore"]', function() {
|
|
855
|
-
const $window = $(this).closest('[component="chat/message/window"]');
|
|
856
|
-
if (!$window.length) {
|
|
857
|
-
return;
|
|
858
|
-
}
|
|
859
|
-
setTimeout(function() {
|
|
860
|
-
updateDeleteRestoreVisibility($window);
|
|
861
|
-
}, 50);
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
$(document).on('click', '.admin-chat-lock-toggle-item', async function(ev) {
|
|
865
|
-
ev.preventDefault();
|
|
866
|
-
|
|
867
|
-
const $button = $(this);
|
|
868
|
-
const roomId = parseInt($button.attr('data-room-id'), 10);
|
|
869
|
-
const isLocked = $button.attr('data-locked') === 'true';
|
|
870
|
-
|
|
871
|
-
if (!roomId) {
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
$button.addClass('disabled').attr('aria-disabled', 'true');
|
|
876
|
-
|
|
877
|
-
try {
|
|
878
|
-
const result = await toggleRoomLock(roomId, !isLocked);
|
|
879
|
-
const currentRoom = ajaxify && ajaxify.data && (ajaxify.data.room || ajaxify.data);
|
|
880
|
-
if (currentRoom && parseInt(currentRoom.roomId, 10) === roomId) {
|
|
881
|
-
currentRoom.adminChatLock = result.lockData;
|
|
882
|
-
currentRoom.canReply = !result.lockData.isLocked || canManageChats();
|
|
883
|
-
currentRoom.showUserInput = currentRoom.canReply;
|
|
884
|
-
if (ajaxify.data.room) {
|
|
885
|
-
ajaxify.data.room.adminChatLock = result.lockData;
|
|
886
|
-
ajaxify.data.room.canReply = currentRoom.canReply;
|
|
887
|
-
ajaxify.data.room.showUserInput = currentRoom.showUserInput;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
const cached = getCachedRoomData(roomId) || { roomId };
|
|
892
|
-
cached.adminChatLock = result.lockData;
|
|
893
|
-
cached.canReply = !result.lockData.isLocked || canManageChats();
|
|
894
|
-
cached.showUserInput = cached.canReply;
|
|
895
|
-
setCachedRoomData(roomId, cached);
|
|
896
|
-
await refreshChatUi();
|
|
897
|
-
} catch (err) {
|
|
898
|
-
app.alertError(t('errors.update'));
|
|
899
|
-
$button.removeClass('disabled').removeAttr('aria-disabled');
|
|
900
|
-
}
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
// Intercept and translate error messages from API - only for lockedAction
|
|
904
|
-
if (app.alertError && !app.alertError._adminChatsWrapped) {
|
|
905
|
-
const originalAlertError = app.alertError;
|
|
906
|
-
app.alertError = function(message) {
|
|
907
|
-
if (typeof message === 'string') {
|
|
908
|
-
if (message.includes('[[admin-chats:errors.lockedAction]]') || message.includes('errors.lockedAction')) {
|
|
909
|
-
message = getLockedActionMessage();
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
return originalAlertError.call(this, message);
|
|
913
|
-
};
|
|
914
|
-
app.alertError._adminChatsWrapped = true;
|
|
915
|
-
}
|
|
916
|
-
});
|
|
917
|
-
|