nodebb-plugin-admin-chats 3.0.1 → 3.1.1

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/client.js CHANGED
@@ -1,35 +1,38 @@
1
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
+
2
18
  syncAdminAccessFromAjaxify();
3
19
  fetchAdminPrivileges();
4
20
  const LOCK_PREFIX = '[admin-chat-lock]';
5
21
  const roomStateCache = new Map();
6
- const TEXT = {
7
- he: {
8
- lockBanner: '🔒 חדר זה ננעל ע"י המנהלים.',
9
- menuLock: 'נעל חדר',
10
- menuRelease: 'שחרר נעילה',
11
- updateError: 'לא ניתן לעדכן את נעילת החדר.',
12
- viewChats: "צפיה בצ'אטים",
13
- emptyState: "אנא בחר צ'אט מסרגל הצד.",
14
- participants: 'משתתפים',
15
- },
16
- en: {
17
- lockBanner: '🔒 This room was locked by the administrators.',
18
- menuLock: 'Lock Room',
19
- menuRelease: 'Release Lock',
20
- updateError: 'Unable to update room lock.',
21
- viewChats: 'View Chats',
22
- emptyState: 'Please select a chat from the sidebar.',
23
- participants: 'Participants',
24
- },
25
- };
26
22
 
27
- function isEnglishSystem() {
28
- return $('html').attr('lang') && $('html').attr('lang').startsWith('en');
29
- }
30
23
  function t(key) {
31
- const dict = isEnglishSystem() ? TEXT.en : TEXT.he;
32
- return dict[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';
33
36
  }
34
37
 
35
38
  function hasAdminChatsAccess() {
@@ -129,8 +132,8 @@ $(document).ready(function() {
129
132
 
130
133
  // Get lockedAction message from server translations only (based on forum settings)
131
134
  function getLockedActionMessage() {
132
- if (window.adminChatsTranslations && window.adminChatsTranslations.lockedAction) {
133
- return window.adminChatsTranslations.lockedAction;
135
+ if (window.adminChatsTranslations && window.adminChatsTranslations['errors.lockedAction']) {
136
+ return window.adminChatsTranslations['errors.lockedAction'];
134
137
  }
135
138
  // If server translation not available, default to English
136
139
  return 'This action cannot be performed in a locked room.';
@@ -281,14 +284,25 @@ $(document).ready(function() {
281
284
  currentText.includes('You have no active chats') ||
282
285
  currentText === 'You have no active chats.'
283
286
  ) {
284
- $(this).text(t('emptyState'));
287
+ $(this).text(t('empty.selectChat'));
285
288
  $(this).removeClass('text-muted');
286
289
  }
287
290
  });
288
291
  }
289
292
 
290
293
  function getChatWindows() {
291
- return $('[component="chat/message/window"]');
294
+ const windows = new Set($('[component="chat/message/window"]').toArray());
295
+ $('.chat-modal').each(function() {
296
+ const $modal = $(this);
297
+ const nested = $modal.find('[component="chat/message/window"]').toArray();
298
+ if (nested.length) {
299
+ nested.forEach(el => windows.delete(el));
300
+ windows.add(this);
301
+ } else {
302
+ windows.add(this);
303
+ }
304
+ });
305
+ return $(Array.from(windows));
292
306
  }
293
307
 
294
308
  function getWindowRoomId($window) {
@@ -416,7 +430,7 @@ $(document).ready(function() {
416
430
  }
417
431
 
418
432
  const positionStyle = isEnglishSystem() ? 'float:right; clear:both;' : 'float:left; clear:both;';
419
- target.prepend(`<div class="admin-chat-lock-banner alert alert-warning mb-2 text-start" style="${positionStyle} max-width: fit-content;">${t('lockBanner')}</div>`);
433
+ 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>`);
420
434
  }
421
435
 
422
436
  function updateLockedActionVisibility($window, hidden) {
@@ -457,7 +471,7 @@ $(document).ready(function() {
457
471
  if (!text.includes(LOCK_PREFIX.slice(1, -1)) && !text.includes('admin-chat-lock')) {
458
472
  return;
459
473
  }
460
- $el.text(t('lockBanner'));
474
+ $el.text(t('lock.banner'));
461
475
  });
462
476
  }
463
477
 
@@ -483,7 +497,7 @@ $(document).ready(function() {
483
497
 
484
498
  const lockData = roomData.adminChatLock || {};
485
499
  const isLocked = !!lockData.isLocked;
486
- const itemText = isLocked ? t('menuRelease') : t('menuLock');
500
+ const itemText = isLocked ? t('menu.release') : t('menu.lock');
487
501
  const iconClass = isLocked ? 'fa-lock-open' : 'fa-lock';
488
502
  const menuItemHtml = `
489
503
  <li role="presentation" class="admin-chat-lock-item-wrap">
@@ -609,7 +623,7 @@ $(document).ready(function() {
609
623
  <li role="presentation" class="admin-chats-profile-link">
610
624
  <a class="dropdown-item rounded-1 d-flex align-items-center gap-2" href="${relativePath}/user/${userSlug}/chats" role="menuitem">
611
625
  <i class="far fa-fw fa-comments"></i>
612
- <span>${t("viewChats")}</span>
626
+ <span>${t("profile.viewChats")}</span>
613
627
  </a>
614
628
  </li>
615
629
  `;
@@ -804,7 +818,7 @@ $(document).ready(function() {
804
818
  setCachedRoomData(roomId, cached);
805
819
  await refreshChatUi();
806
820
  } catch (err) {
807
- app.alertError(t('updateError'));
821
+ app.alertError(t('errors.update'));
808
822
  $button.removeClass('disabled').removeAttr('aria-disabled');
809
823
  }
810
824
  });
@@ -823,3 +837,4 @@ $(document).ready(function() {
823
837
  app.alertError._adminChatsWrapped = true;
824
838
  }
825
839
  });
840
+
package/library.js CHANGED
@@ -7,13 +7,83 @@ const db = require.main.require('./src/database');
7
7
  const meta = require.main.require('./src/meta');
8
8
  const privileges = require.main.require('./src/privileges');
9
9
  const helpers = require.main.require('./src/controllers/helpers');
10
+ const fs = require('fs');
11
+ const path = require('path');
10
12
 
11
13
  const plugin = {};
12
14
  const LOCK_PREFIX = '[admin-chat-lock]';
13
- const LOCKED_MESSAGE_TEXT = '🔒 חדר זה ננעל ע"י המנהלים.';
15
+ const LOCKED_MESSAGE_TEXT_KEY = 'lock.banner';
14
16
  const ADMIN_CHATS_PRIVILEGE = 'admin-chats:view';
15
17
  const ADMIN_CHATS_MANAGE_PRIVILEGE = 'admin-chats:manage';
16
18
 
19
+ // Load translations from language files
20
+ const translations = {};
21
+ function loadTranslations() {
22
+ // Try multiple possible paths
23
+ const possiblePaths = [
24
+ path.join(__dirname, 'public', 'languages'),
25
+ path.join(__dirname, '..', '..', 'public', 'languages'),
26
+ path.resolve(__dirname, 'public', 'languages'),
27
+ ];
28
+
29
+ let languagesDir = null;
30
+ for (const dir of possiblePaths) {
31
+ if (fs.existsSync(dir)) {
32
+ languagesDir = dir;
33
+ break;
34
+ }
35
+ }
36
+
37
+ if (!languagesDir) {
38
+ return;
39
+ }
40
+
41
+ try {
42
+ const langs = fs.readdirSync(languagesDir);
43
+ langs.forEach(lang => {
44
+ const langPath = path.join(languagesDir, lang, 'admin-chats.json');
45
+ try {
46
+ if (fs.existsSync(langPath)) {
47
+ const content = fs.readFileSync(langPath, 'utf8');
48
+ translations[lang] = JSON.parse(content);
49
+ }
50
+ } catch (err) {
51
+ console.error(`[admin-chats] Error loading translations for ${lang}:`, err.message);
52
+ }
53
+ });
54
+ } catch (err) {
55
+ console.error(`[admin-chats] Error reading languages directory:`, err.message);
56
+ }
57
+ }
58
+ loadTranslations();
59
+ function getTranslationForLang(lang, key) {
60
+ if (!key) {
61
+ return '';
62
+ }
63
+ if (lang && translations[lang] && translations[lang][key]) {
64
+ return translations[lang][key];
65
+ }
66
+ const langCode = lang ? lang.split('-')[0] : '';
67
+ if (langCode && translations[langCode] && translations[langCode][key]) {
68
+ return translations[langCode][key];
69
+ }
70
+ if (translations.en && translations.en[key]) {
71
+ return translations.en[key];
72
+ }
73
+ if (translations['en-GB'] && translations['en-GB'][key]) {
74
+ return translations['en-GB'][key];
75
+ }
76
+ return '';
77
+ }
78
+
79
+ function getDefaultLockMessage() {
80
+ const defaultLang = meta.config.defaultLang || 'en-GB';
81
+ return getTranslationForLang(defaultLang, LOCKED_MESSAGE_TEXT_KEY)
82
+ || getTranslationForLang('en-GB', LOCKED_MESSAGE_TEXT_KEY)
83
+ || getTranslationForLang('en', LOCKED_MESSAGE_TEXT_KEY)
84
+ || 'This room was locked by the administrators.';
85
+ }
86
+
17
87
  async function getLockedActionMessage(uid) {
18
88
  try {
19
89
  const userData = await User.getUserData(uid);
@@ -21,15 +91,22 @@ async function getLockedActionMessage(uid) {
21
91
 
22
92
  // If user doesn't have specific language setting, use forum default
23
93
  if (!userLang) {
24
- const meta = require.main.require('./src/meta');
25
94
  userLang = meta.config.defaultLang || 'en-GB';
26
95
  }
27
96
 
28
- if (userLang === 'he' || userLang === 'he-IL') {
29
- return 'לא ניתן לבצע פעולה זו בחדר נעול';
30
- } else {
31
- return 'This action cannot be performed in a locked room.';
97
+ // Extract language code (e.g., 'he' from 'he-IL', 'fr' from 'fr-FR')
98
+ const langCode = userLang.split('-')[0];
99
+
100
+ // Try to get translation from loaded files
101
+ if (translations[langCode] && translations[langCode]['errors.lockedAction']) {
102
+ return translations[langCode]['errors.lockedAction'];
103
+ }
104
+ if (translations[userLang] && translations[userLang]['errors.lockedAction']) {
105
+ return translations[userLang]['errors.lockedAction'];
32
106
  }
107
+
108
+ // Fallback to English only
109
+ return 'This action cannot be performed in a locked room.';
33
110
  } catch (err) {
34
111
  // Default to English if we can't determine language
35
112
  return 'This action cannot be performed in a locked room.';
@@ -77,6 +154,13 @@ plugin.init = async function (params) {
77
154
  overrideMessagingFunctions();
78
155
  overrideChatsApi();
79
156
  overrideCoreChatRedirect(params.controllers);
157
+
158
+ // Add middleware to inject translations into every page
159
+ params.app.use((req, res, next) => {
160
+ // Store translations in res.locals so they can be accessed in templates
161
+ res.locals.adminChatsTranslations = translations;
162
+ next();
163
+ });
80
164
  };
81
165
  plugin.registerPrivileges = async function (data) {
82
166
  if (!data || !data.privileges || typeof data.privileges.set !== 'function') {
@@ -153,43 +237,106 @@ plugin.filterUserNotificationsGetNotifications = async function (data) {
153
237
 
154
238
  return data;
155
239
  };
156
- plugin.addScripts = async function (data) {
157
- let isHebrew = false;
158
-
159
- const uid = data.req && data.req.uid;
240
+ async function buildAdminChatsClientContext(req) {
241
+ const uid = req && req.uid;
242
+
243
+ let userLang = 'en-GB';
244
+
160
245
  if (uid) {
161
246
  try {
162
247
  const userData = await User.getUserData(uid);
163
- let userLang = userData && userData.settings && userData.userLang;
164
-
248
+
249
+ // Try different ways to get user language
250
+ userLang = userData && userData.userLang;
251
+ if (!userLang && userData && userData.settings) {
252
+ userLang = userData.settings.userLang;
253
+ }
254
+
165
255
  // If user doesn't have specific language setting, use forum default
166
256
  if (!userLang) {
167
- const meta = require.main.require('./src/meta');
168
257
  userLang = meta.config.defaultLang || 'en-GB';
169
258
  }
170
-
171
- isHebrew = userLang === 'he' || userLang === 'he-IL';
172
259
  } catch (err) {
173
- // Default to English if error
174
- isHebrew = false;
260
+ console.error(`[admin-chats] Error getting user language:`, err.message);
261
+ userLang = meta.config.defaultLang || 'en-GB';
175
262
  }
263
+ } else {
264
+ // No uid, use forum default language
265
+ userLang = meta.config.defaultLang || 'en-GB';
176
266
  }
177
-
178
- // Only send the lockedAction translation from server
179
- const translations = {
180
- lockedAction: isHebrew ? 'לא ניתן לבצע פעולה זו בחדר נעול' : 'This action cannot be performed in a locked room.',
181
- };
267
+
268
+ // Build translations based on user language from loaded files
269
+ let clientTranslations = {};
270
+ const langCode = userLang.split('-')[0];
271
+
272
+
273
+ // Try to get translations from loaded files
274
+ // First try exact match (e.g., 'he-IL')
275
+ if (translations[userLang]) {
276
+ clientTranslations = translations[userLang];
277
+ }
278
+ // Then try language code (e.g., 'he' from 'he-IL')
279
+ else if (translations[langCode]) {
280
+ clientTranslations = translations[langCode];
281
+ }
282
+ // Then try full locale (e.g., 'en-GB')
283
+ else if (translations[userLang.replace('-', '-')]) {
284
+ clientTranslations = translations[userLang];
285
+ }
286
+ // Fallback to English
287
+ else {
288
+ clientTranslations = translations['en'] || translations['en-GB'] || {};
289
+ }
290
+
182
291
 
183
292
  const isAdmin = uid ? await User.isAdministrator(uid) : false;
184
293
  const adminChatsManage = uid ? (isAdmin || await privileges.global.can(ADMIN_CHATS_MANAGE_PRIVILEGE, uid)) : false;
185
294
  const adminChatsAccess = uid ? (isAdmin || adminChatsManage || await privileges.global.can(ADMIN_CHATS_PRIVILEGE, uid)) : false;
186
295
 
296
+ return {
297
+ clientTranslations,
298
+ userLang,
299
+ adminChatsAccess,
300
+ adminChatsIsAdmin: isAdmin,
301
+ adminChatsCanManage: adminChatsManage,
302
+ };
303
+ }
304
+
305
+ plugin.addTemplateData = async function (data) {
306
+ const templateData = data && data.templateData ? data.templateData : null;
307
+ if (!templateData) {
308
+ return data;
309
+ }
310
+
311
+ const context = await buildAdminChatsClientContext(data.req);
312
+ templateData.adminChatsTranslations = context.clientTranslations;
313
+ templateData.adminChatsLanguage = context.userLang;
314
+ templateData.adminChatsAccess = context.adminChatsAccess;
315
+ templateData.adminChatsIsAdmin = context.adminChatsIsAdmin;
316
+ templateData.adminChatsCanManage = context.adminChatsCanManage;
317
+
318
+ return data;
319
+ };
320
+
321
+ plugin.addScripts = async function (data) {
322
+ const context = await buildAdminChatsClientContext(data.req);
323
+
324
+ // Add translations directly to data object
325
+ data.adminChatsTranslations = context.clientTranslations;
326
+ data.adminChatsLanguage = context.userLang;
327
+ data.adminChatsAccess = context.adminChatsAccess;
328
+ data.adminChatsIsAdmin = context.adminChatsIsAdmin;
329
+ data.adminChatsCanManage = context.adminChatsCanManage;
330
+
331
+ // Also add as inline script to ensure it's available before client.js runs
187
332
  data.scripts = data.scripts || [];
333
+ const scriptContent = `window.adminChatsTranslations = ${JSON.stringify(context.clientTranslations)}; window.adminChatsLanguage = '${context.userLang}'; window.adminChatsAccess = ${context.adminChatsAccess}; window.adminChatsIsAdmin = ${context.adminChatsIsAdmin}; window.adminChatsCanManage = ${context.adminChatsCanManage};`;
188
334
  data.scripts.push({
189
335
  src: false,
190
- script: `window.adminChatsTranslations = ${JSON.stringify(translations)}; window.adminChatsAccess = ${adminChatsAccess}; window.adminChatsIsAdmin = ${isAdmin}; window.adminChatsCanManage = ${adminChatsManage};`
336
+ script: scriptContent
191
337
  });
192
338
 
339
+
193
340
  return data;
194
341
  };
195
342
 
@@ -1003,7 +1150,7 @@ function buildRoomMessagesWithLockNotice(messages, lockData) {
1003
1150
  }
1004
1151
 
1005
1152
  cleanedMessages.push({
1006
- content: `${LOCK_PREFIX} ${LOCKED_MESSAGE_TEXT}`,
1153
+ content: `${LOCK_PREFIX} ${getDefaultLockMessage()}`,
1007
1154
  fromuid: lockData.lockedBy,
1008
1155
  uid: lockData.lockedBy,
1009
1156
  timestamp: lockData.lockedAt,
@@ -1029,7 +1176,7 @@ function stripAdminLockMessages(messages) {
1029
1176
  function isAdminLockMessage(msg) {
1030
1177
  const content = String(msg && msg.content || '');
1031
1178
  return content.includes('admin-chat-lock') ||
1032
- content.includes(LOCKED_MESSAGE_TEXT) ||
1179
+ content.includes(getDefaultLockMessage()) ||
1033
1180
  content.includes('נפתח מחדש ע"י המנהלים') ||
1034
1181
  content.includes('modules:chat.system.[admin-chat-lock]');
1035
1182
  }
@@ -1070,3 +1217,12 @@ function getRoomId(payload) {
1070
1217
  }
1071
1218
 
1072
1219
  module.exports = plugin;
1220
+
1221
+
1222
+
1223
+
1224
+
1225
+
1226
+
1227
+
1228
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-admin-chats",
3
- "version": "3.0.1",
3
+ "version": "3.1.1",
4
4
  "description": "A plugin that allows the administrator to view and manage user chats.",
5
5
  "main": "library.js",
6
6
  "nbbpm": {
package/plugin.json CHANGED
@@ -61,8 +61,8 @@
61
61
  "method": "filterRenameRoom"
62
62
  },
63
63
  {
64
- "hook": "filter:scripts.get",
65
- "method": "addScripts"
64
+ "hook": "filter:middleware.render",
65
+ "method": "addTemplateData"
66
66
  },
67
67
  {
68
68
  "hook": "filter:notifications.create",
@@ -79,3 +79,6 @@
79
79
  ],
80
80
  "languages": "public/languages"
81
81
  }
82
+
83
+
84
+
@@ -0,0 +1,12 @@
1
+ {
2
+ "lock.banner": "🔒 This room was locked by the administrators.",
3
+ "menu.lock": "Lock Room",
4
+ "menu.release": "Release Lock",
5
+ "errors.update": "Unable to update room lock.",
6
+ "profile.viewChats": "View Chats",
7
+ "empty.selectChat": "Please select a chat from the sidebar.",
8
+ "errors.lockedAction": "This action cannot be performed in a locked room.",
9
+ "admin-chats-view": "View all chats",
10
+ "admin-chats-manage": "Manage chat rooms",
11
+ "participants": "Participants"
12
+ }
@@ -7,5 +7,6 @@
7
7
  "empty.selectChat": "Please select a chat from the sidebar.",
8
8
  "errors.lockedAction": "This action cannot be performed in a locked room.",
9
9
  "admin-chats-view": "View all chats",
10
- "admin-chats-manage": "Manage chat rooms"
10
+ "admin-chats-manage": "Manage chat rooms",
11
+ "participants": "Participants"
11
12
  }
@@ -0,0 +1,12 @@
1
+ {
2
+ "lock.banner": "🔒 Ce salon a été verrouillé par les administrateurs.",
3
+ "menu.lock": "Verrouiller le salon",
4
+ "menu.release": "Déverrouiller le salon",
5
+ "errors.update": "Impossible de mettre à jour le verrouillage du salon.",
6
+ "profile.viewChats": "Voir les Chats",
7
+ "empty.selectChat": "Veuillez sélectionner un Chat dans la barre latérale.",
8
+ "errors.lockedAction": "Cette action ne peut pas être effectuée dans un salon verrouillé.",
9
+ "admin-chats-view": "Voir tous les Chats",
10
+ "admin-chats-manage": "Gérer les salons de discussion",
11
+ "participants": "Participants"
12
+ }
@@ -7,5 +7,6 @@
7
7
  "empty.selectChat": "אנא בחר צ'אט מסרגל הצד.",
8
8
  "errors.lockedAction": "לא ניתן לבצע פעולה זו בחדר נעול",
9
9
  "admin-chats-view": "צפיה בכל הצ'אטים",
10
- "admin-chats-manage": "ניהול חדרי צ'אט"
10
+ "admin-chats-manage": "ניהול חדרי צ'אט",
11
+ "participants": "משתתפים"
11
12
  }