nodebb-plugin-chat-search 0.0.1 → 0.0.3

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/library.js CHANGED
@@ -23,7 +23,6 @@ async function searchGlobal(socket, data) {
23
23
  }
24
24
 
25
25
  let targetUid = socket.uid;
26
- // בדיקת הרשאות (אדמין צופה במישהו אחר)
27
26
  if (data.targetUid && parseInt(data.targetUid, 10) !== parseInt(socket.uid, 10)) {
28
27
  const isAdmin = await user.isAdministrator(socket.uid);
29
28
  if (!isAdmin) {
@@ -38,12 +37,10 @@ async function searchGlobal(socket, data) {
38
37
  let allResults = [];
39
38
 
40
39
  for (const roomId of roomIds) {
41
- // בדיקת חברות
42
40
  const inRoom = await messaging.isUserInRoom(targetUid, roomId);
43
41
  if (!inRoom) continue;
44
42
 
45
43
  try {
46
- // 1. שליפת הודעות
47
44
  const messages = await messaging.getMessages({
48
45
  callerUid: socket.uid,
49
46
  uid: targetUid,
@@ -60,32 +57,41 @@ async function searchGlobal(socket, data) {
60
57
  );
61
58
 
62
59
  if (matches.length > 0) {
63
- // --- תיקון שליפת המשתתפים ---
64
- // במקום להסתמך על roomData, נשלוף UIDs ואז Users
60
+ // שליפת משתמשים
65
61
  const uids = await messaging.getUidsInRoom(roomId, 0, -1);
66
- const usersData = await user.getUsersFields(uids, ['uid', 'username', 'picture']);
62
+ const usersData = await user.getUsersFields(uids, ['uid', 'username']);
67
63
 
68
- // סינון המשתמש שבו אנו צופים
69
- const participants = usersData
70
- .filter(u => parseInt(u.uid, 10) !== parseInt(targetUid, 10))
71
- .map(u => u.username)
72
- .join(', ');
64
+ // סינון המשתמש הנוכחי (משאירים רק את הפרטנרים)
65
+ const otherUsers = usersData.filter(u => parseInt(u.uid, 10) !== parseInt(targetUid, 10));
66
+
67
+ // --- לוגיקת הקיצור (2 שמות + ועוד X) ---
68
+ let displayName = '';
69
+
70
+ if (otherUsers.length === 0) {
71
+ displayName = 'צ\'אט עצמי'; // או משתמש מחוק
72
+ } else if (otherUsers.length <= 2) {
73
+ // אם יש 1 או 2, מציגים את כולם
74
+ displayName = otherUsers.map(u => u.username).join(', ');
75
+ } else {
76
+ // אם יש יותר מ-2, לוקחים את ה-2 הראשונים ומוסיפים את היתרה
77
+ const firstTwo = otherUsers.slice(0, 2).map(u => u.username).join(', ');
78
+ const remaining = otherUsers.length - 2;
79
+ displayName = `${firstTwo} ועוד ${remaining} משתמשים`;
80
+ }
81
+ // ----------------------------------------
73
82
 
74
- // --- תיקון שליפת שם החדר ---
75
83
  const roomData = await messaging.getRoomData(roomId);
76
- let roomName = (roomData && roomData.roomName) || participants || `חדר ${roomId}`;
84
+ // שם החדר: אם יש שם מוגדר לקבוצה - קח אותו, אחרת קח את השמות שיצרנו
85
+ let roomName = (roomData && roomData.roomName) || displayName;
77
86
 
78
- // --- תיקון שם השולח (במקרה שחסר בהודעה) ---
79
- // אם חסר אובייקט user בהודעה, נשלים אותו מתוך usersData ששלפנו הרגע
80
87
  matches.forEach(m => {
81
88
  if (!m.user || !m.user.username) {
82
89
  const sender = usersData.find(u => parseInt(u.uid, 10) === parseInt(m.fromuid, 10));
83
90
  m.user = sender || { username: 'Unknown' };
84
91
  }
85
-
86
92
  m.roomName = roomName;
87
- m.chatWith = participants;
88
93
  m.targetUid = targetUid;
94
+ // מחקנו את chatWith כי כבר לא צריך אותו
89
95
  });
90
96
 
91
97
  allResults = allResults.concat(matches);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-chat-search",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "A plugin to search text within NodeBB chats",
5
5
  "main": "library.js",
6
6
  "nbbpm": {
@@ -1,59 +1,110 @@
1
1
  'use strict';
2
2
 
3
+ // מצב גלובלי לזיכרון בין מעברי דפים
4
+ window.chatSearchState = window.chatSearchState || {
5
+ query: '',
6
+ resultsHtml: '',
7
+ isOpen: false,
8
+ lastScroll: 0
9
+ };
10
+
3
11
  $(document).ready(function () {
4
- console.log('[Chat Search] Loaded (AJAX Fix).');
12
+ console.log('[Chat Search] Loaded (Instant No-Flicker).');
13
+
14
+ let observer = null;
15
+
16
+ // האזנה למעבר דפים
17
+ $(window).on('action:ajaxify.start', function () {
18
+ // לפני שהדף מתחלף, ננסה לשמור את האלמנט בזיכרון אם נרצה להחזירו (אופציונלי)
19
+ // כרגע אנו מסתמכים על בנייה מחדש מהירה מאוד
20
+ });
5
21
 
6
- // בכל מעבר דף, אנחנו מוחקים את הישן ומתחילים חיפוש חדש של הקונטיינר
7
22
  $(window).on('action:ajaxify.end', function (ev, data) {
8
- $('#global-chat-search-container').remove(); // איפוס
9
-
10
- // בדיקה: האם אנחנו בעמוד צ'אטים?
11
- // גם לפי URL וגם לפי Template
23
+ // אם יש observer ישן, ננתק אותו כדי לא להעמיס
24
+ if (observer) observer.disconnect();
25
+
12
26
  const isChatUrl = data.url.match(/^(user\/[^\/]+\/)?chats/);
13
27
  const isChatTemplate = data.template && data.template.name === 'chats';
14
28
 
15
29
  if (isChatUrl || isChatTemplate) {
16
- waitForElementAndAdd();
30
+ initFastInjection();
31
+ } else {
32
+ // יציאה מאזור הצ'אטים - איפוס הזיכרון
33
+ window.chatSearchState = { query: '', resultsHtml: '', isOpen: false, lastScroll: 0 };
17
34
  }
18
35
  });
19
36
 
20
37
  $(window).on('action:chat.loaded', function (ev, data) {
38
+ highlightActiveChat();
21
39
  handleScrollToMessage();
22
40
  });
23
41
 
24
- // הרצה ראשונית (למקרה של ריענון מלא)
25
- if (ajaxify.data.template && ajaxify.data.template.name === 'chats') {
26
- waitForElementAndAdd();
42
+ // בדיקה ראשונית
43
+ if (ajaxify.data && ajaxify.data.template && ajaxify.data.template.name === 'chats') {
44
+ initFastInjection();
27
45
  }
28
46
 
29
- function waitForElementAndAdd() {
47
+ function initFastInjection() {
48
+ // 1. ניסיון הזרקה מיידי
49
+ tryInject();
50
+
51
+ // 2. הפעלת MutationObserver לזיהוי שינויים ב-DOM בזמן אמת (מונע קפיצות)
52
+ const targetNode = document.body;
53
+ const config = { childList: true, subtree: true };
54
+
55
+ observer = new MutationObserver(function(mutationsList) {
56
+ for(let mutation of mutationsList) {
57
+ if (mutation.type === 'childList') {
58
+ // אם נוספו אלמנטים לדף, נבדוק אם הסרגל הגיע
59
+ const container = findContainer();
60
+ if (container.length > 0 && container.find('#global-chat-search-container').length === 0) {
61
+ addGlobalSearchBar(container);
62
+ }
63
+ }
64
+ }
65
+ });
66
+
67
+ observer.observe(targetNode, config);
68
+
69
+ // 3. גיבוי: Interval מהיר מאוד (50ms) למקרה שה-Observer פספס
30
70
  let attempts = 0;
31
- const interval = setInterval(function() {
71
+ const interval = setInterval(() => {
32
72
  attempts++;
33
-
34
- // חיפוש הקונטיינר החדש שנוצר
35
- let container = $('[component="chat/nav-wrapper"]');
36
- if (container.length === 0) container = $('.chats-page').find('.col-md-4').first();
37
-
38
- // ברגע שמצאנו - מזריקים ועוצרים
39
- if (container.length > 0) {
40
- addGlobalSearchBar(container);
41
- clearInterval(interval);
42
- } else if (attempts >= 20) { // מנסים במשך 4 שניות
73
+ if (tryInject()) {
74
+ // לא עוצרים את האינטרוול מיד, כי לפעמים NodeBB מרענן פעמיים
75
+ if (attempts > 20) clearInterval(interval);
76
+ } else if (attempts > 40) { // 2 שניות
43
77
  clearInterval(interval);
44
78
  }
45
- }, 200); // בדיקה מהירה כל 200ms
79
+ }, 50);
80
+ }
81
+
82
+ function findContainer() {
83
+ // סדר עדיפויות למציאת הקונטיינר
84
+ let container = $('[component="chat/nav-wrapper"]');
85
+ if (container.length === 0) container = $('.chats-page').find('.col-md-4').first();
86
+ return container;
87
+ }
88
+
89
+ function tryInject() {
90
+ const container = findContainer();
91
+ if (container.length > 0) {
92
+ addGlobalSearchBar(container);
93
+ return true;
94
+ }
95
+ return false;
46
96
  }
47
97
 
48
98
  function addGlobalSearchBar(container) {
49
99
  if ($('#global-chat-search-container').length > 0) return;
50
100
 
101
+ // ה-HTML של התיבה
51
102
  const searchHtml = `
52
103
  <div id="global-chat-search-container" style="padding: 10px; background: #fff; border-bottom: 1px solid #ddd; margin-bottom: 10px;">
53
104
  <div class="input-group">
54
- <input type="text" id="global-chat-search" class="form-control" placeholder="חפש הודעה..." style="font-size: 14px;">
105
+ <input type="text" id="global-chat-search" class="form-control" placeholder="חפש הודעה..." style="font-size: 14px; height: 34px;">
55
106
  <span class="input-group-btn">
56
- <button class="btn btn-primary" id="btn-chat-search" type="button"><i class="fa fa-search"></i></button>
107
+ <button class="btn btn-primary" id="btn-chat-search" type="button" style="height: 34px;"><i class="fa fa-search"></i></button>
57
108
  </span>
58
109
  </div>
59
110
  <div id="global-search-results" style="margin-top: 5px; max-height: 400px; overflow-y: auto; background: white; border: 1px solid #eee; display:none;"></div>
@@ -62,10 +113,39 @@ $(document).ready(function () {
62
113
 
63
114
  container.prepend(searchHtml);
64
115
 
116
+ // --- שחזור מיידי של המצב (מונע הבהוב של התוכן) ---
117
+ const input = $('#global-chat-search');
118
+ const results = $('#global-search-results');
119
+
120
+ if (window.chatSearchState.query) {
121
+ input.val(window.chatSearchState.query);
122
+ }
123
+
124
+ if (window.chatSearchState.isOpen && window.chatSearchState.resultsHtml) {
125
+ results.html(window.chatSearchState.resultsHtml).show();
126
+ // שחזור מיקום הגלילה
127
+ if (window.chatSearchState.lastScroll > 0) {
128
+ results.scrollTop(window.chatSearchState.lastScroll);
129
+ }
130
+ highlightActiveChat();
131
+ }
132
+ // ------------------------------------------------
133
+
134
+ // הגדרת אירועים (Events)
65
135
  $('#btn-chat-search').off('click').on('click', executeSearch);
66
- $('#global-chat-search').off('keypress').on('keypress', function (e) {
136
+
137
+ // שמירת גלילה
138
+ results.on('scroll', function() {
139
+ window.chatSearchState.lastScroll = $(this).scrollTop();
140
+ });
141
+
142
+ input.off('keypress').on('keypress', function (e) {
67
143
  if (e.which === 13) executeSearch();
68
144
  });
145
+
146
+ input.on('input', function() {
147
+ window.chatSearchState.query = $(this).val();
148
+ });
69
149
  }
70
150
 
71
151
  function executeSearch() {
@@ -74,12 +154,15 @@ $(document).ready(function () {
74
154
 
75
155
  if (!query) {
76
156
  resultsContainer.hide();
157
+ window.chatSearchState.isOpen = false;
158
+ window.chatSearchState.resultsHtml = '';
77
159
  return;
78
160
  }
79
161
 
80
162
  let targetUid = ajaxify.data.uid || app.user.uid;
81
163
 
82
164
  resultsContainer.show().html('<div class="text-center" style="padding:10px;"><i class="fa fa-spinner fa-spin"></i> מחפש...</div>');
165
+ window.chatSearchState.isOpen = true;
83
166
 
84
167
  socket.emit('plugins.chatSearch.searchGlobal', {
85
168
  query: query,
@@ -87,12 +170,14 @@ $(document).ready(function () {
87
170
  }, function (err, messages) {
88
171
  if (err) {
89
172
  console.error(err);
90
- resultsContainer.html('<div class="alert alert-danger" style="margin:5px;">שגיאה בחיפוש</div>');
173
+ resultsContainer.html('<div class="alert alert-danger" style="margin:5px;">שגיאה</div>');
91
174
  return;
92
175
  }
93
176
 
94
177
  if (!messages || messages.length === 0) {
95
- resultsContainer.html('<div class="text-center" style="padding:10px; color:#777;">לא נמצאו תוצאות.</div>');
178
+ const noRes = '<div class="text-center" style="padding:10px; color:#777;">לא נמצאו תוצאות.</div>';
179
+ resultsContainer.html(noRes);
180
+ window.chatSearchState.resultsHtml = noRes;
96
181
  return;
97
182
  }
98
183
 
@@ -100,37 +185,50 @@ $(document).ready(function () {
100
185
 
101
186
  messages.forEach(msg => {
102
187
  const date = new Date(msg.timestamp).toLocaleDateString();
103
-
104
- let baseUrl = window.location.pathname;
188
+ let baseUrl = window.location.pathname.replace(/\/chats\/.*$/, '/chats');
105
189
  if (baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1);
106
- if (baseUrl.match(/\/[0-9]+$/)) baseUrl = baseUrl.replace(/\/[0-9]+$/, '');
107
190
 
108
191
  const chatLink = baseUrl + '/' + msg.roomId + '?mid=' + msg.mid;
109
192
  const senderName = (msg.user && msg.user.username) ? msg.user.username : 'Unknown';
110
-
111
- let participantsHtml = '';
112
- if (msg.chatWith) {
113
- participantsHtml = `<div style="font-size:11px; color:#005999; margin-bottom:2px;"><i class="fa fa-users"></i> עם: <strong>${msg.chatWith}</strong></div>`;
114
- }
115
193
 
116
194
  html += `
117
- <li class="list-group-item search-result" style="cursor:pointer; border-bottom: 1px solid #f0f0f0; padding: 8px;" onclick="ajaxify.go('${chatLink}')">
118
- <div style="font-size:13px; color:#333; margin-bottom:2px;">
195
+ <li class="list-group-item search-result" data-roomid="${msg.roomId}" style="cursor:pointer; border-bottom: 1px solid #f0f0f0; padding: 10px 8px;" onclick="ajaxify.go('${chatLink}')">
196
+ <div style="font-size:13px; color:#333; margin-bottom:5px;">
197
+ <span class="pull-left text-muted" style="font-size:10px; margin-right: 8px;">${date}</span>
119
198
  <strong>${msg.roomName}</strong>
120
- <span class="pull-left text-muted" style="font-size:10px;">${date}</span>
121
199
  </div>
122
- ${participantsHtml}
123
- <div style="font-size:12px; color:#444; background: #f9f9f9; padding: 4px; border-radius: 3px; border-right: 2px solid #007bff;">
200
+ <div style="font-size:12px; color:#444; background: #f9f9f9; padding: 6px; border-radius: 4px; border-right: 3px solid #007bff;">
124
201
  <strong>${senderName}:</strong> ${msg.content}
125
202
  </div>
126
203
  </li>
127
204
  `;
128
205
  });
129
206
  html += '</ul>';
207
+
130
208
  resultsContainer.html(html);
209
+
210
+ // עדכון הזיכרון
211
+ window.chatSearchState.resultsHtml = html;
212
+ window.chatSearchState.lastScroll = 0;
213
+
214
+ highlightActiveChat();
131
215
  });
132
216
  }
133
217
 
218
+ function highlightActiveChat() {
219
+ // מנסים לקחת RoomID מה-URL או מהמידע של NodeBB
220
+ let currentRoomId = ajaxify.data.roomId;
221
+ if (!currentRoomId) {
222
+ const match = window.location.pathname.match(/chats\/(\d+)/);
223
+ if (match) currentRoomId = match[1];
224
+ }
225
+
226
+ if (!currentRoomId) return;
227
+
228
+ $('.search-result').css('background-color', '');
229
+ $('.search-result[data-roomid="' + currentRoomId + '"]').css('background-color', '#eef6ff');
230
+ }
231
+
134
232
  function handleScrollToMessage() {
135
233
  const params = new URLSearchParams(window.location.search);
136
234
  const mid = params.get('mid');
@@ -140,8 +238,8 @@ $(document).ready(function () {
140
238
  let attempts = 0;
141
239
  const scrollInt = setInterval(() => {
142
240
  attempts++;
143
- if (scrollToId(mid) || attempts > 10) clearInterval(scrollInt);
144
- }, 500);
241
+ if (scrollToId(mid) || attempts > 15) clearInterval(scrollInt);
242
+ }, 300);
145
243
  }
146
244
 
147
245
  function scrollToId(mid) {