nodebb-plugin-admin-chats 3.4.1 → 3.4.2

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.
Files changed (3) hide show
  1. package/client.js +45 -28
  2. package/library.js +20 -11
  3. package/package.json +1 -1
package/client.js CHANGED
@@ -230,38 +230,54 @@ $(document).ready(function() {
230
230
  }
231
231
 
232
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
+ // Pass the container like core does — listeners (e.g. the emoji
248
+ // plugin's chat button) resolve elements relative to it
249
+ $(window).trigger('action:chat.loaded', $('.chats-full'));
250
+ if (roomId) {
251
+ ChatsMessages.scrollToBottomAfterImageLoad(mainWrapper.find('[component="chat/message/content"]'));
252
+ }
253
+ if (history.pushState) {
254
+ const fullUrl = `${window.location.protocol}//${window.location.host}${config.relative_path || ''}/${url}`;
255
+ // When the address bar already shows the target (e.g. closing a chat
256
+ // after a back-gesture), replace instead of stacking a duplicate entry
257
+ const sameUrl = window.location.href.split('?')[0] === fullUrl.split('?')[0];
258
+ history[sameUrl ? 'replaceState' : 'pushState']({ url: url }, null, fullUrl);
259
+ }
260
+ bindAdminRecentChatsInfiniteScroll();
261
+ }
262
+
263
+ if (!roomId) {
264
+ // Closing a chat needs no server round-trip — render the empty
265
+ // window from the data we already have (instant back-gesture)
266
+ renderAdminChatWindow({ ...ajaxify.data, roomId: 0, title: '[[pages:chats]]' });
267
+ return;
268
+ }
269
+
233
270
  fetch(getAdminChatsDataUrl(roomId), { credentials: 'include' })
234
271
  .then(async function(response) {
235
272
  if (!response.ok) {
236
273
  throw new Error(`Received ${response.status}`);
237
274
  }
238
-
239
- const payload = await response.json();
240
- const html = await app.parseAndTranslate('partials/chats/message-window', payload);
241
- const mainWrapper = $('[component="chat/main-wrapper"]');
242
- mainWrapper.html(html);
243
- mainWrapper.attr('data-roomid', roomId);
244
- html.find('.timeago').timeago();
245
- ajaxify.data = { ...ajaxify.data, ...payload, roomId: roomId };
246
- ajaxify.updateTitle(ajaxify.data.title);
247
- $('body').toggleClass('chat-loaded', !!roomId);
248
- mainWrapper.find('[data-bs-toggle="tooltip"]').tooltip({ trigger: 'hover', container: '#content' });
249
- Chats.setActive(roomId);
250
- Chats.addEventListeners();
251
- // Pass the container like core does — listeners (e.g. the emoji
252
- // plugin's chat button) resolve elements relative to it
253
- $(window).trigger('action:chat.loaded', $('.chats-full'));
254
- if (roomId) {
255
- ChatsMessages.scrollToBottomAfterImageLoad(mainWrapper.find('[component="chat/message/content"]'));
256
- }
257
- if (history.pushState) {
258
- const fullUrl = `${window.location.protocol}//${window.location.host}${config.relative_path || ''}/${url}`;
259
- // When the address bar already shows the target (e.g. closing a chat
260
- // after a back-gesture), replace instead of stacking a duplicate entry
261
- const sameUrl = window.location.href.split('?')[0] === fullUrl.split('?')[0];
262
- history[sameUrl ? 'replaceState' : 'pushState']({ url: url }, null, fullUrl);
263
- }
264
- bindAdminRecentChatsInfiniteScroll();
275
+ await renderAdminChatWindow(await response.json());
276
+ })
277
+ .catch(function() {
278
+ // Never leave the user stuck on the list — fall back to a
279
+ // regular ajaxify navigation to the room
280
+ ajaxify.go(url);
265
281
  });
266
282
  };
267
283
 
@@ -375,7 +391,8 @@ $(document).ready(function() {
375
391
 
376
392
  // Try admin endpoint first if user has access
377
393
  if (hasAdminChatsAccess()) {
378
- const adminEndpoint = `${config.relative_path || ''}/api/admin-chats/page/${roomId}`;
394
+ // switch=1: room data only — no need to rebuild the full room list here
395
+ const adminEndpoint = `${config.relative_path || ''}/api/admin-chats/page/${roomId}?switch=1`;
379
396
  try {
380
397
  const adminResponse = await fetch(adminEndpoint, {
381
398
  headers: {
package/library.js CHANGED
@@ -847,7 +847,9 @@ function registerRoutes(app, router, middleware) {
847
847
  return;
848
848
  }
849
849
 
850
- const payload = await buildAdminChatsPayload(req);
850
+ const payload = await buildAdminChatsPayload(req, {
851
+ isSwitch: parseInt(req.query.switch, 10) === 1,
852
+ });
851
853
  res.json(payload);
852
854
  } catch (err) {
853
855
  res.status(500).json({ status: { code: 'error', message: err.message } });
@@ -1115,7 +1117,11 @@ async function assertAdminChatsAccess(req, res) {
1115
1117
  return true;
1116
1118
  }
1117
1119
 
1118
- async function buildAdminChatsPayload(req) {
1120
+ async function buildAdminChatsPayload(req, options = {}) {
1121
+ // Like core (controllers/accounts/chats.js): when the client only switches
1122
+ // rooms (?switch=1 on the JSON endpoint), skip rebuilding the room list —
1123
+ // scanning every chat room on each switch made room-opening slow on mobile
1124
+ const isSwitch = !!options.isSwitch;
1119
1125
  const userslug = await User.getUserField(req.uid, 'userslug');
1120
1126
  const [isAdmin, canView, canManage] = await Promise.all([
1121
1127
  User.isAdministrator(req.uid),
@@ -1124,11 +1130,6 @@ async function buildAdminChatsPayload(req) {
1124
1130
  ]);
1125
1131
  const adminChatsManage = isAdmin || canManage;
1126
1132
  const adminChatsAccess = isAdmin || canView || canManage;
1127
- const [recentChats, publicRooms, privateRoomCount] = await Promise.all([
1128
- getAdminRecentChats(req.uid, 0, ADMIN_CHAT_PAGE_SIZE),
1129
- Messaging.getPublicRooms(req.uid, req.uid),
1130
- getPrivateRoomCount(),
1131
- ]);
1132
1133
 
1133
1134
  const payload = {
1134
1135
  title: '[[pages:chats]]',
@@ -1138,13 +1139,21 @@ async function buildAdminChatsPayload(req) {
1138
1139
  adminChatsAccess,
1139
1140
  adminChatsIsAdmin: isAdmin,
1140
1141
  adminChatsCanManage: adminChatsManage,
1141
- rooms: recentChats.rooms,
1142
- nextStart: recentChats.nextStart,
1143
- publicRooms: publicRooms || [],
1144
- privateRoomCount,
1145
1142
  bodyClasses: ['page-user-chats'],
1146
1143
  };
1147
1144
 
1145
+ if (!isSwitch) {
1146
+ const [recentChats, publicRooms, privateRoomCount] = await Promise.all([
1147
+ getAdminRecentChats(req.uid, 0, ADMIN_CHAT_PAGE_SIZE),
1148
+ Messaging.getPublicRooms(req.uid, req.uid),
1149
+ getPrivateRoomCount(),
1150
+ ]);
1151
+ payload.rooms = recentChats.rooms;
1152
+ payload.nextStart = recentChats.nextStart;
1153
+ payload.publicRooms = publicRooms || [];
1154
+ payload.privateRoomCount = privateRoomCount;
1155
+ }
1156
+
1148
1157
  const roomId = parseInt(req.params.roomId, 10) || 0;
1149
1158
  if (!roomId) {
1150
1159
  return payload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-admin-chats",
3
- "version": "3.4.1",
3
+ "version": "3.4.2",
4
4
  "description": "A plugin that allows the administrator to view and manage user chats.",
5
5
  "main": "library.js",
6
6
  "nbbpm": {