nodebb-plugin-admin-chats 3.1.2 → 3.3.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.
Files changed (3) hide show
  1. package/client.js +67 -37
  2. package/library.js +61 -25
  3. package/package.json +1 -1
package/client.js CHANGED
@@ -336,68 +336,69 @@ $(document).ready(function() {
336
336
  return null;
337
337
  }
338
338
 
339
+ // First, check if we have the room data in ajaxify.data (from the current page)
339
340
  const currentRoom = ajaxify && ajaxify.data && (ajaxify.data.room || ajaxify.data);
340
- if (currentRoom && parseInt(currentRoom.roomId, 10) === roomId && currentRoom.adminChatLock) {
341
- setCachedRoomData(roomId, currentRoom);
342
- return currentRoom;
341
+ if (currentRoom && parseInt(currentRoom.roomId, 10) === roomId) {
342
+ if (currentRoom.adminChatLock) {
343
+ setCachedRoomData(roomId, currentRoom);
344
+ return currentRoom;
345
+ }
343
346
  }
344
347
 
348
+ // Check cache
345
349
  const cached = getCachedRoomData(roomId);
346
350
  if (cached) {
347
351
  return cached;
348
352
  }
349
353
 
350
- const endpoint = isAdminAllChatsPage() ?
351
- `${config.relative_path || ''}/api/admin-chats/page/${roomId}` :
352
- `${config.relative_path || ''}/api/chats/${roomId}`;
353
- const response = await fetch(endpoint, {
354
- headers: {
355
- 'x-csrf-token': config.csrf_token,
356
- },
357
- credentials: 'same-origin',
358
- });
359
- if (!response.ok) {
360
- if (hasAdminChatsAccess() && !isAdminAllChatsPage()) {
361
- const adminResponse = await fetch(`${config.relative_path || ''}/api/admin-chats/page/${roomId}`, {
354
+ // Try admin endpoint first if user has access
355
+ if (hasAdminChatsAccess()) {
356
+ const adminEndpoint = `${config.relative_path || ''}/api/admin-chats/page/${roomId}`;
357
+ try {
358
+ const adminResponse = await fetch(adminEndpoint, {
362
359
  headers: {
363
360
  'x-csrf-token': config.csrf_token,
364
361
  },
365
362
  credentials: 'same-origin',
366
363
  });
367
- if (!adminResponse.ok) {
368
- return null;
369
- }
370
- const adminPayload = await adminResponse.json();
371
- const adminRoom = adminPayload && adminPayload.roomId ? adminPayload : (adminPayload && adminPayload.room ? adminPayload.room : null);
372
- if (adminRoom) {
373
- setCachedRoomData(roomId, adminRoom);
364
+ if (adminResponse.ok) {
365
+ const adminPayload = await adminResponse.json();
366
+ const adminRoom = adminPayload && adminPayload.roomId ? adminPayload : (adminPayload && adminPayload.room ? adminPayload.room : null);
367
+ if (adminRoom) {
368
+ setCachedRoomData(roomId, adminRoom);
369
+ return adminRoom;
370
+ }
374
371
  }
375
- return adminRoom;
372
+ } catch (err) {
373
+ // Continue to fallback
376
374
  }
377
- return null;
378
375
  }
379
376
 
380
- const payload = await response.json();
381
- const roomData = payload && payload.roomId ? payload : (payload && payload.room ? payload.room : null);
382
- if (roomData && !roomData.adminChatLock && hasAdminChatsAccess() && !isAdminAllChatsPage()) {
383
- const adminResponse = await fetch(`${config.relative_path || ''}/api/admin-chats/page/${roomId}`, {
377
+ // For all users, try to get lock data from the new endpoint
378
+ const lockEndpoint = `${config.relative_path || ''}/api/admin-chats/${roomId}/lock`;
379
+ try {
380
+ const lockResponse = await fetch(lockEndpoint, {
384
381
  headers: {
385
382
  'x-csrf-token': config.csrf_token,
386
383
  },
387
384
  credentials: 'same-origin',
388
385
  });
389
- if (adminResponse.ok) {
390
- const adminPayload = await adminResponse.json();
391
- const adminRoom = adminPayload && adminPayload.roomId ? adminPayload : (adminPayload && adminPayload.room ? adminPayload.room : null);
392
- if (adminRoom && adminRoom.adminChatLock) {
393
- roomData.adminChatLock = adminRoom.adminChatLock;
386
+ if (lockResponse.ok) {
387
+ const lockPayload = await lockResponse.json();
388
+ if (lockPayload && lockPayload.lockData) {
389
+ const roomData = {
390
+ roomId: roomId,
391
+ adminChatLock: lockPayload.lockData,
392
+ };
393
+ setCachedRoomData(roomId, roomData);
394
+ return roomData;
394
395
  }
395
396
  }
397
+ } catch (err) {
398
+ // Ignore error
396
399
  }
397
- if (roomData) {
398
- setCachedRoomData(roomId, roomData);
399
- }
400
- return roomData;
400
+
401
+ return null;
401
402
  }
402
403
  function isLockedForUser(roomData) {
403
404
  return !!(roomData && roomData.adminChatLock && roomData.adminChatLock.isLocked && !canManageChats());
@@ -739,6 +740,35 @@ $(document).ready(function() {
739
740
  socket.on('event:chats.restore', function() { setTimeout(refreshChatUi, 0); });
740
741
  }
741
742
 
743
+ // Monitor for new chat modals/windows being added to the DOM
744
+ // This ensures styling is applied to floating chat windows opened from non-chat pages
745
+ const observer = new MutationObserver(function(mutations) {
746
+ let hasNewChatWindow = false;
747
+ mutations.forEach(function(mutation) {
748
+ if (mutation.type === 'childList') {
749
+ mutation.addedNodes.forEach(function(node) {
750
+ if (node.nodeType === 1) { // Element node
751
+ const $node = $(node);
752
+ if ($node.is('.chat-modal') || $node.find('.chat-modal').length) {
753
+ hasNewChatWindow = true;
754
+ }
755
+ if ($node.is('[component="chat/message/window"]') || $node.find('[component="chat/message/window"]').length) {
756
+ hasNewChatWindow = true;
757
+ }
758
+ }
759
+ });
760
+ }
761
+ });
762
+ if (hasNewChatWindow) {
763
+ setTimeout(refreshChatUi, 50);
764
+ }
765
+ });
766
+
767
+ observer.observe(document.body, {
768
+ childList: true,
769
+ subtree: true
770
+ });
771
+
742
772
  // Handle browser back/forward navigation
743
773
  $(window).on('popstate', function() {
744
774
  if (isAdminAllChatsPage()) {
package/library.js CHANGED
@@ -113,7 +113,6 @@ async function getLockedActionMessage(uid) {
113
113
  }
114
114
  }
115
115
  const ADMIN_CHAT_PAGE_SIZE = 30;
116
- const ADMIN_CHAT_SCAN_SIZE = 100;
117
116
 
118
117
  async function canAccessAdminChats(uid) {
119
118
  if (!uid) {
@@ -830,6 +829,29 @@ function registerRoutes(app, router, middleware) {
830
829
  return res.status(500).json({ status: { code: 'error', message: err.message } });
831
830
  }
832
831
  });
832
+
833
+ router.get('/api/admin-chats/:roomId/lock', ...routeMiddleware, async (req, res) => {
834
+ try {
835
+ const roomId = parseInt(req.params.roomId, 10);
836
+
837
+ if (Number.isNaN(roomId)) {
838
+ return res.status(400).json({ status: { code: 'bad-request', message: 'Invalid room id' } });
839
+ }
840
+
841
+ if (!await roomExists(roomId)) {
842
+ return res.status(404).json({ status: { code: 'not-found', message: 'Room not found' } });
843
+ }
844
+
845
+ const lockData = await getRoomLockData(roomId);
846
+ return res.json({
847
+ status: { code: 'ok', message: 'Lock data retrieved' },
848
+ roomId,
849
+ lockData,
850
+ });
851
+ } catch (err) {
852
+ return res.status(500).json({ status: { code: 'error', message: err.message } });
853
+ }
854
+ });
833
855
  }
834
856
 
835
857
  async function renderAdminChatsPage(req, res, next) {
@@ -947,42 +969,57 @@ async function getPrivateRoomCount() {
947
969
  }
948
970
 
949
971
  async function getAdminRecentChats(uid, start, limit) {
950
- let cursor = Math.max(0, parseInt(start, 10) || 0);
951
- const roomPairs = [];
972
+ const offset = Math.max(0, parseInt(start, 10) || 0);
973
+ const pageSize = Math.max(0, parseInt(limit, 10) || 0);
974
+ const allRoomIds = await db.getSortedSetRevRange('chat:rooms', 0, -1);
952
975
 
953
- while (roomPairs.length < limit) {
954
- const roomIds = await db.getSortedSetRevRange('chat:rooms', cursor, cursor + ADMIN_CHAT_SCAN_SIZE - 1);
955
- if (!roomIds.length) {
956
- break;
976
+ if (!allRoomIds.length || !pageSize) {
977
+ return {
978
+ rooms: [],
979
+ nextStart: offset,
980
+ };
981
+ }
982
+
983
+ const allRooms = await Messaging.getRoomsData(allRoomIds);
984
+ const roomPairs = (await Promise.all(allRooms.map(async (room, index) => {
985
+ const roomId = parseInt(allRoomIds[index], 10) || 0;
986
+ if (!room || room.public || !roomId) {
987
+ return null;
957
988
  }
958
989
 
959
- const rooms = await Messaging.getRoomsData(roomIds);
960
- rooms.forEach((room, index) => {
961
- if (room && !room.public && roomPairs.length < limit) {
962
- roomPairs.push({
963
- roomId: roomIds[index],
964
- room,
965
- });
966
- }
967
- });
990
+ return {
991
+ roomId,
992
+ room,
993
+ lastActivity: await getAdminRoomLastActivity(roomId, room),
994
+ };
995
+ }))).filter(Boolean);
968
996
 
969
- cursor += roomIds.length;
970
- if (roomIds.length < ADMIN_CHAT_SCAN_SIZE) {
971
- break;
972
- }
973
- }
997
+ roomPairs.sort((a, b) => (
998
+ b.lastActivity - a.lastActivity ||
999
+ b.roomId - a.roomId
1000
+ ));
974
1001
 
975
- const rooms = roomPairs.map(item => item.room);
976
- const roomIds = roomPairs.map(item => item.roomId);
1002
+ const pagedRooms = roomPairs.slice(offset, offset + pageSize);
1003
+ const rooms = pagedRooms.map(item => item.room);
1004
+ const roomIds = pagedRooms.map(item => item.roomId);
977
1005
 
978
1006
  await enrichAdminRecentRooms(uid, roomIds, rooms);
979
1007
 
980
1008
  return {
981
1009
  rooms,
982
- nextStart: cursor,
1010
+ nextStart: offset + rooms.length,
983
1011
  };
984
1012
  }
985
1013
 
1014
+ async function getAdminRoomLastActivity(roomId, room) {
1015
+ const lastMessage = await db.getSortedSetRevRangeWithScores(`chat:room:${roomId}:mids`, 0, 0);
1016
+ if (lastMessage.length) {
1017
+ return parseInt(lastMessage[0].score, 10) || 0;
1018
+ }
1019
+
1020
+ return parseInt(room && room.timestamp, 10) || 0;
1021
+ }
1022
+
986
1023
  async function enrichAdminRecentRooms(uid, roomIds, rooms) {
987
1024
  if (!roomIds.length) {
988
1025
  return;
@@ -1225,4 +1262,3 @@ module.exports = plugin;
1225
1262
 
1226
1263
 
1227
1264
 
1228
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-admin-chats",
3
- "version": "3.1.2",
3
+ "version": "3.3.0",
4
4
  "description": "A plugin that allows the administrator to view and manage user chats.",
5
5
  "main": "library.js",
6
6
  "nbbpm": {