nodebb-plugin-chat-search 0.0.2 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/static/lib/main.js +173 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-chat-search",
3
- "version": "0.0.2",
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,52 +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 (Clean UI).');
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
22
  $(window).on('action:ajaxify.end', function (ev, data) {
7
- $('#global-chat-search-container').remove();
8
-
23
+ // אם יש observer ישן, ננתק אותו כדי לא להעמיס
24
+ if (observer) observer.disconnect();
25
+
9
26
  const isChatUrl = data.url.match(/^(user\/[^\/]+\/)?chats/);
10
27
  const isChatTemplate = data.template && data.template.name === 'chats';
11
28
 
12
29
  if (isChatUrl || isChatTemplate) {
13
- waitForElementAndAdd();
30
+ initFastInjection();
31
+ } else {
32
+ // יציאה מאזור הצ'אטים - איפוס הזיכרון
33
+ window.chatSearchState = { query: '', resultsHtml: '', isOpen: false, lastScroll: 0 };
14
34
  }
15
35
  });
16
36
 
17
37
  $(window).on('action:chat.loaded', function (ev, data) {
38
+ highlightActiveChat();
18
39
  handleScrollToMessage();
19
40
  });
20
41
 
42
+ // בדיקה ראשונית
21
43
  if (ajaxify.data && ajaxify.data.template && ajaxify.data.template.name === 'chats') {
22
- waitForElementAndAdd();
44
+ initFastInjection();
23
45
  }
24
46
 
25
- 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 פספס
26
70
  let attempts = 0;
27
- const interval = setInterval(function() {
71
+ const interval = setInterval(() => {
28
72
  attempts++;
29
- let container = $('[component="chat/nav-wrapper"]');
30
- if (container.length === 0) container = $('.chats-page').find('.col-md-4').first();
31
-
32
- if (container.length > 0) {
33
- addGlobalSearchBar(container);
34
- clearInterval(interval);
35
- } else if (attempts >= 20) {
73
+ if (tryInject()) {
74
+ // לא עוצרים את האינטרוול מיד, כי לפעמים NodeBB מרענן פעמיים
75
+ if (attempts > 20) clearInterval(interval);
76
+ } else if (attempts > 40) { // 2 שניות
36
77
  clearInterval(interval);
37
78
  }
38
- }, 200);
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;
39
96
  }
40
97
 
41
98
  function addGlobalSearchBar(container) {
42
99
  if ($('#global-chat-search-container').length > 0) return;
43
100
 
101
+ // ה-HTML של התיבה
44
102
  const searchHtml = `
45
103
  <div id="global-chat-search-container" style="padding: 10px; background: #fff; border-bottom: 1px solid #ddd; margin-bottom: 10px;">
46
104
  <div class="input-group">
47
- <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;">
48
106
  <span class="input-group-btn">
49
- <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>
50
108
  </span>
51
109
  </div>
52
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>
@@ -55,10 +113,39 @@ $(document).ready(function () {
55
113
 
56
114
  container.prepend(searchHtml);
57
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)
58
135
  $('#btn-chat-search').off('click').on('click', executeSearch);
59
- $('#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) {
60
143
  if (e.which === 13) executeSearch();
61
144
  });
145
+
146
+ input.on('input', function() {
147
+ window.chatSearchState.query = $(this).val();
148
+ });
62
149
  }
63
150
 
64
151
  function executeSearch() {
@@ -67,12 +154,15 @@ $(document).ready(function () {
67
154
 
68
155
  if (!query) {
69
156
  resultsContainer.hide();
157
+ window.chatSearchState.isOpen = false;
158
+ window.chatSearchState.resultsHtml = '';
70
159
  return;
71
160
  }
72
161
 
73
162
  let targetUid = ajaxify.data.uid || app.user.uid;
74
163
 
75
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;
76
166
 
77
167
  socket.emit('plugins.chatSearch.searchGlobal', {
78
168
  query: query,
@@ -80,12 +170,14 @@ $(document).ready(function () {
80
170
  }, function (err, messages) {
81
171
  if (err) {
82
172
  console.error(err);
83
- resultsContainer.html('<div class="alert alert-danger" style="margin:5px;">שגיאה בחיפוש</div>');
173
+ resultsContainer.html('<div class="alert alert-danger" style="margin:5px;">שגיאה</div>');
84
174
  return;
85
175
  }
86
176
 
87
177
  if (!messages || messages.length === 0) {
88
- 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;
89
181
  return;
90
182
  }
91
183
 
@@ -93,17 +185,71 @@ $(document).ready(function () {
93
185
 
94
186
  messages.forEach(msg => {
95
187
  const date = new Date(msg.timestamp).toLocaleDateString();
96
-
97
- let baseUrl = window.location.pathname;
188
+ let baseUrl = window.location.pathname.replace(/\/chats\/.*$/, '/chats');
98
189
  if (baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1);
99
- if (baseUrl.match(/\/[0-9]+$/)) baseUrl = baseUrl.replace(/\/[0-9]+$/, '');
100
190
 
101
191
  const chatLink = baseUrl + '/' + msg.roomId + '?mid=' + msg.mid;
102
192
  const senderName = (msg.user && msg.user.username) ? msg.user.username : 'Unknown';
103
-
104
- // הסרנו את השורה של "עם: ..."
105
193
 
106
194
  html += `
107
- <li class="list-group-item search-result" style="cursor:pointer; border-bottom: 1px solid #f0f0f0; padding: 10px 8px;" onclick="ajaxify.go('${chatLink}')">
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}')">
108
196
  <div style="font-size:13px; color:#333; margin-bottom:5px;">
109
- <span class="pull-left text-muted"
197
+ <span class="pull-left text-muted" style="font-size:10px; margin-right: 8px;">${date}</span>
198
+ <strong>${msg.roomName}</strong>
199
+ </div>
200
+ <div style="font-size:12px; color:#444; background: #f9f9f9; padding: 6px; border-radius: 4px; border-right: 3px solid #007bff;">
201
+ <strong>${senderName}:</strong> ${msg.content}
202
+ </div>
203
+ </li>
204
+ `;
205
+ });
206
+ html += '</ul>';
207
+
208
+ resultsContainer.html(html);
209
+
210
+ // עדכון הזיכרון
211
+ window.chatSearchState.resultsHtml = html;
212
+ window.chatSearchState.lastScroll = 0;
213
+
214
+ highlightActiveChat();
215
+ });
216
+ }
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
+
232
+ function handleScrollToMessage() {
233
+ const params = new URLSearchParams(window.location.search);
234
+ const mid = params.get('mid');
235
+ if (!mid) return;
236
+
237
+ scrollToId(mid);
238
+ let attempts = 0;
239
+ const scrollInt = setInterval(() => {
240
+ attempts++;
241
+ if (scrollToId(mid) || attempts > 15) clearInterval(scrollInt);
242
+ }, 300);
243
+ }
244
+
245
+ function scrollToId(mid) {
246
+ const el = $('[data-mid="' + mid + '"]');
247
+ if (el.length > 0) {
248
+ el[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
249
+ el.css('background', '#fffeca').css('transition', 'background 1s');
250
+ setTimeout(() => el.css('background', ''), 2000);
251
+ return true;
252
+ }
253
+ return false;
254
+ }
255
+ });