@realtimexsco/live-chat 1.0.0 → 1.2.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.
- package/README.md +935 -148
- package/dist/index.cjs +493 -220
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +72 -3
- package/dist/index.d.ts +72 -3
- package/dist/index.mjs +487 -222
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -5
package/dist/index.mjs
CHANGED
|
@@ -3,13 +3,14 @@ import axios from 'axios';
|
|
|
3
3
|
import { create } from 'zustand';
|
|
4
4
|
import { persist } from 'zustand/middleware';
|
|
5
5
|
import * as React7 from 'react';
|
|
6
|
-
import React7__default, { createContext, useContext, useState, useEffect, useRef, useMemo, useLayoutEffect, useCallback } from 'react';
|
|
6
|
+
import React7__default, { createContext, useContext, useState, useEffect, useRef, useMemo, useLayoutEffect, useCallback, useSyncExternalStore } from 'react';
|
|
7
7
|
import { Tooltip as Tooltip$1, Avatar as Avatar$1, Dialog as Dialog$1, Slot, ScrollArea as ScrollArea$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, AlertDialog as AlertDialog$1, Switch as Switch$1 } from 'radix-ui';
|
|
8
8
|
import { clsx } from 'clsx';
|
|
9
9
|
import { twMerge } from 'tailwind-merge';
|
|
10
10
|
import { Search, X, UserPlus, MessageCircle, Users, Camera, Check, ArrowRight, Loader2, MessageCircleMore, History, Paperclip, Smile, Send, XIcon, ArrowLeft, Pin, Star, Phone, Video, MoreVertical, Info, UserKey, ShieldUser, MessageSquareX, Trash2, LogOut, Ban, AlertCircle, Save, Calendar, Mail, User, ShieldCheck, UserMinus, Plus, Download, Forward, Music, Film, FileText, FileSpreadsheet, FileIcon, PinOff, StarOff, CornerUpLeft, Pencil, Clock, CheckCheck } from 'lucide-react';
|
|
11
11
|
import { cva } from 'class-variance-authority';
|
|
12
12
|
import EmojiPicker, { Theme } from 'emoji-picker-react';
|
|
13
|
+
import { toast } from 'react-hot-toast';
|
|
13
14
|
|
|
14
15
|
var __defProp = Object.defineProperty;
|
|
15
16
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -142,11 +143,28 @@ var init_socket_events_constants = __esm({
|
|
|
142
143
|
};
|
|
143
144
|
}
|
|
144
145
|
});
|
|
145
|
-
|
|
146
|
+
|
|
147
|
+
// src/store/useSocketStore.ts
|
|
148
|
+
var useSocketStore_exports = {};
|
|
149
|
+
__export(useSocketStore_exports, {
|
|
150
|
+
default: () => useSocketStore_default,
|
|
151
|
+
useSocketStore: () => useSocketStore
|
|
152
|
+
});
|
|
153
|
+
var useSocketStore, useSocketStore_default;
|
|
154
|
+
var init_useSocketStore = __esm({
|
|
155
|
+
"src/store/useSocketStore.ts"() {
|
|
156
|
+
init_useStore();
|
|
157
|
+
useSocketStore = useStore;
|
|
158
|
+
useSocketStore_default = useSocketStore;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
var getStore, SocketService, socketService, socket_service_default;
|
|
146
162
|
var init_socket_service = __esm({
|
|
147
163
|
"src/services/socket.service.ts"() {
|
|
148
164
|
init_socket_events_constants();
|
|
149
|
-
|
|
165
|
+
getStore = () => {
|
|
166
|
+
return (init_useSocketStore(), __toCommonJS(useSocketStore_exports)).useSocketStore;
|
|
167
|
+
};
|
|
150
168
|
SocketService = class {
|
|
151
169
|
constructor() {
|
|
152
170
|
this.socket = null;
|
|
@@ -165,7 +183,7 @@ var init_socket_service = __esm({
|
|
|
165
183
|
}
|
|
166
184
|
this.currentConfig = config;
|
|
167
185
|
this.isConnecting = true;
|
|
168
|
-
const { setConnecting, setConnected, setDisconnected, setError } =
|
|
186
|
+
const { setConnecting, setConnected, setDisconnected, setError } = getStore().getState();
|
|
169
187
|
setConnecting();
|
|
170
188
|
this.socket = io(config.url, config.options);
|
|
171
189
|
this.socket.on("connect", () => {
|
|
@@ -183,72 +201,72 @@ var init_socket_service = __esm({
|
|
|
183
201
|
});
|
|
184
202
|
this.socket.on(SOCKET_EVENTS.ERROR, (payload) => {
|
|
185
203
|
const message = typeof payload === "string" ? payload : payload?.message ?? "app_error";
|
|
186
|
-
|
|
204
|
+
getStore().getState().handleAppError(message);
|
|
187
205
|
});
|
|
188
206
|
this.socket.on(SOCKET_EVENTS.DM_RECEIVE, (payload) => {
|
|
189
|
-
|
|
207
|
+
getStore().getState().receivedDM(payload);
|
|
190
208
|
});
|
|
191
209
|
this.socket.on(SOCKET_EVENTS.GROUP_MESSAGE_RECEIVE, (payload) => {
|
|
192
|
-
|
|
210
|
+
getStore().getState().receivedGroupMessage(payload);
|
|
193
211
|
});
|
|
194
212
|
this.socket.on(SOCKET_EVENTS.CONVERSATION_UPDATE, (payload) => {
|
|
195
213
|
if (!payload) return;
|
|
196
|
-
|
|
214
|
+
getStore().getState().updateConversation(payload);
|
|
197
215
|
});
|
|
198
216
|
this.socket.on(SOCKET_EVENTS.DM_EDIT_RECEIVE, (payload) => {
|
|
199
|
-
|
|
217
|
+
getStore().getState().receivedEditDM(payload);
|
|
200
218
|
});
|
|
201
219
|
this.socket.on(SOCKET_EVENTS.DM_DELETE_ME, (payload) => {
|
|
202
|
-
|
|
220
|
+
getStore().getState().receivedDeleteForMe(payload);
|
|
203
221
|
});
|
|
204
222
|
this.socket.on(SOCKET_EVENTS.DM_DELETE_EVERYONE, (payload) => {
|
|
205
|
-
|
|
223
|
+
getStore().getState().receivedDeleteForEveryone(payload);
|
|
206
224
|
});
|
|
207
225
|
this.socket.on(SOCKET_EVENTS.DM_CLEAR_CHAT_SUCCESS, (payload) => {
|
|
208
|
-
|
|
226
|
+
getStore().getState().receivedDmClearChat(payload);
|
|
209
227
|
});
|
|
210
228
|
this.socket.on(SOCKET_EVENTS.GROUP_CLEAR_CHAT_SUCCESS, (payload) => {
|
|
211
|
-
|
|
229
|
+
getStore().getState().receivedGroupClearChat(payload);
|
|
212
230
|
});
|
|
213
231
|
this.socket.on(SOCKET_EVENTS.DM_DELETE_CONVERSATION_SUCCESS, (payload) => {
|
|
214
|
-
|
|
232
|
+
getStore().getState().receivedDmDeleteConversation(payload);
|
|
215
233
|
});
|
|
216
234
|
this.socket.on(SOCKET_EVENTS.GROUP_DELETE_CONVERSATION_SUCCESS, (payload) => {
|
|
217
|
-
|
|
235
|
+
getStore().getState().receivedGroupDeleteConversation(payload);
|
|
218
236
|
});
|
|
219
237
|
this.socket.on(SOCKET_EVENTS.DM_REACTION_ADD_RECEIVE, (payload) => {
|
|
220
|
-
|
|
238
|
+
getStore().getState().receivedReactionAdd(payload);
|
|
221
239
|
});
|
|
222
240
|
this.socket.on(SOCKET_EVENTS.DM_REACTION_REMOVE_RECEIVE, (payload) => {
|
|
223
|
-
|
|
241
|
+
getStore().getState().receivedReactionRemove(payload);
|
|
224
242
|
});
|
|
225
243
|
this.socket.on(SOCKET_EVENTS.DM_PIN_UPDATE, (payload) => {
|
|
226
|
-
|
|
244
|
+
getStore().getState().receivedPinUpdate(payload);
|
|
227
245
|
});
|
|
228
246
|
this.socket.on(SOCKET_EVENTS.DM_STAR_UPDATE, (payload) => {
|
|
229
|
-
|
|
247
|
+
getStore().getState().receivedStarUpdate(payload);
|
|
230
248
|
});
|
|
231
249
|
this.socket.on(SOCKET_EVENTS.DM_STATUS_UPDATE, (payload) => {
|
|
232
|
-
|
|
250
|
+
getStore().getState().receivedStatusUpdate(payload);
|
|
233
251
|
});
|
|
234
252
|
this.socket.on(SOCKET_EVENTS.DM_MARK_READ, (payload) => {
|
|
235
|
-
|
|
253
|
+
getStore().getState().receivedDMMarkRead(payload);
|
|
236
254
|
});
|
|
237
255
|
this.socket.on(SOCKET_EVENTS.GROUP_MESSAGE_READ, (payload) => {
|
|
238
|
-
|
|
256
|
+
getStore().getState().receivedGroupMarkRead(payload);
|
|
239
257
|
});
|
|
240
258
|
this.socket.on(SOCKET_EVENTS.DM_FORWARD_RECEIVE, (payload) => {
|
|
241
|
-
|
|
259
|
+
getStore().getState().receivedForward(payload);
|
|
242
260
|
});
|
|
243
261
|
this.socket.on(SOCKET_EVENTS.DM_TYPING_START, (payload) => {
|
|
244
|
-
|
|
262
|
+
getStore().getState().setTypingStart(payload);
|
|
245
263
|
});
|
|
246
264
|
this.socket.on(SOCKET_EVENTS.DM_TYPING_STOP, (payload) => {
|
|
247
|
-
|
|
265
|
+
getStore().getState().setTypingStop(payload);
|
|
248
266
|
});
|
|
249
267
|
this.socket.on(SOCKET_EVENTS.ONLINE_USERS_LIST, (payload) => {
|
|
250
268
|
const users = payload?.onlineUsers || [];
|
|
251
|
-
|
|
269
|
+
getStore().getState().setOnlineUsers(users);
|
|
252
270
|
});
|
|
253
271
|
this.socket.on(SOCKET_EVENTS.USER_ONLINE, () => {
|
|
254
272
|
this.emitGetOnlineUsers();
|
|
@@ -257,49 +275,49 @@ var init_socket_service = __esm({
|
|
|
257
275
|
this.emitGetOnlineUsers();
|
|
258
276
|
});
|
|
259
277
|
this.socket.on(SOCKET_EVENTS.CONVERSATION_STARRED_PINNED_COUNT_UPDATE, (payload) => {
|
|
260
|
-
|
|
278
|
+
getStore().getState().receivedCountUpdate(payload);
|
|
261
279
|
});
|
|
262
280
|
this.socket.on(SOCKET_EVENTS.GROUP_EDIT_RECEIVE, (payload) => {
|
|
263
|
-
|
|
281
|
+
getStore().getState().receivedEditDM(payload);
|
|
264
282
|
});
|
|
265
283
|
this.socket.on(SOCKET_EVENTS.GROUP_STATUS_UPDATE, (payload) => {
|
|
266
|
-
|
|
284
|
+
getStore().getState().receivedGroupStatusUpdate(payload);
|
|
267
285
|
});
|
|
268
286
|
this.socket.on(SOCKET_EVENTS.GROUP_MESSAGE_DELIVERED, (payload) => {
|
|
269
|
-
|
|
287
|
+
getStore().getState().receivedGroupStatusUpdate({
|
|
270
288
|
...payload,
|
|
271
289
|
status: payload?.status || "delivered"
|
|
272
290
|
});
|
|
273
291
|
});
|
|
274
292
|
this.socket.on(SOCKET_EVENTS.GROUP_REACTION_ADD_RECEIVE, (payload) => {
|
|
275
|
-
|
|
293
|
+
getStore().getState().receivedGroupReactionAdd(payload);
|
|
276
294
|
});
|
|
277
295
|
this.socket.on(SOCKET_EVENTS.GROUP_TYPING_START, (payload) => {
|
|
278
|
-
|
|
296
|
+
getStore().getState().setTypingStart(payload);
|
|
279
297
|
});
|
|
280
298
|
this.socket.on(SOCKET_EVENTS.GROUP_TYPING_STOP, (payload) => {
|
|
281
|
-
|
|
299
|
+
getStore().getState().setTypingStop(payload);
|
|
282
300
|
});
|
|
283
301
|
this.socket.on(SOCKET_EVENTS.GROUP_REACTION_REMOVE_RECEIVE, (payload) => {
|
|
284
|
-
|
|
302
|
+
getStore().getState().receivedGroupReactionRemove(payload);
|
|
285
303
|
});
|
|
286
304
|
this.socket.on(SOCKET_EVENTS.GROUP_DELETE_ME, (payload) => {
|
|
287
|
-
|
|
305
|
+
getStore().getState().receivedDeleteForMe(payload);
|
|
288
306
|
});
|
|
289
307
|
this.socket.on(SOCKET_EVENTS.GROUP_DELETE_EVERYONE, (payload) => {
|
|
290
|
-
|
|
308
|
+
getStore().getState().receivedDeleteForEveryone(payload);
|
|
291
309
|
});
|
|
292
310
|
this.socket.on(SOCKET_EVENTS.GROUP_PIN_UPDATE, (payload) => {
|
|
293
|
-
|
|
311
|
+
getStore().getState().receivedPinUpdate(payload);
|
|
294
312
|
});
|
|
295
313
|
this.socket.on(SOCKET_EVENTS.GROUP_STAR_UPDATE, (payload) => {
|
|
296
|
-
|
|
314
|
+
getStore().getState().receivedStarUpdate(payload);
|
|
297
315
|
});
|
|
298
316
|
this.socket.on(SOCKET_EVENTS.GROUP_FORWARD_RECEIVE, (payload) => {
|
|
299
|
-
|
|
317
|
+
getStore().getState().receivedForward(payload);
|
|
300
318
|
});
|
|
301
319
|
this.socket.on(SOCKET_EVENTS.GROUP_LEAVE_RECEIVE, (payload) => {
|
|
302
|
-
|
|
320
|
+
getStore().getState().receivedGroupLeave(payload);
|
|
303
321
|
});
|
|
304
322
|
}
|
|
305
323
|
disconnect() {
|
|
@@ -429,6 +447,176 @@ var init_socket_service = __esm({
|
|
|
429
447
|
}
|
|
430
448
|
});
|
|
431
449
|
|
|
450
|
+
// src/store/helpers/store.helpers.ts
|
|
451
|
+
var findConversationIdByMessageId, normalizeId, normalizeUnreadCount, incrementUnreadCount, emptyMessagesMetadata, extractMessageData, mergeLastMessage, getLatestMessageFromList, resolveConversationLastMessage, syncConversationLastMessage, isGroupConversation, findConversationById, resolveIsGroupFromPayload, getOtherParticipantId, findConversationForChannel, resolveConversationIdForChannel, getMessageContent, getMessageTimestamp, isOwnMessage, STATUS_RANK, pickBetterMessageStatus, mergeChatMessages;
|
|
452
|
+
var init_store_helpers = __esm({
|
|
453
|
+
"src/store/helpers/store.helpers.ts"() {
|
|
454
|
+
findConversationIdByMessageId = (messagesByConversation, messageId) => {
|
|
455
|
+
return Object.keys(messagesByConversation).find(
|
|
456
|
+
(cid) => messagesByConversation[cid].some(
|
|
457
|
+
(m) => String(m._id || m.id) === String(messageId)
|
|
458
|
+
)
|
|
459
|
+
);
|
|
460
|
+
};
|
|
461
|
+
normalizeId = (id) => String(id?._id || id?.id || id || "");
|
|
462
|
+
normalizeUnreadCount = (unreadCount) => {
|
|
463
|
+
if (typeof unreadCount === "number") return unreadCount;
|
|
464
|
+
return 0;
|
|
465
|
+
};
|
|
466
|
+
incrementUnreadCount = (unreadCount) => normalizeUnreadCount(unreadCount) + 1;
|
|
467
|
+
emptyMessagesMetadata = () => ({
|
|
468
|
+
nextCursor: null,
|
|
469
|
+
hasMore: false,
|
|
470
|
+
isFirstPage: true
|
|
471
|
+
});
|
|
472
|
+
extractMessageData = (payload) => {
|
|
473
|
+
const messageId = payload.messageId || payload._id || payload.message?._id || payload.message?.id;
|
|
474
|
+
const content = payload.content || payload.message?.content;
|
|
475
|
+
const conversationId = payload.conversationId || payload.message?.conversationId || payload.message?.conversation_id;
|
|
476
|
+
const status = payload.status || payload.message?.status;
|
|
477
|
+
const isPinned = payload.isPinned ?? payload.message?.isPinned;
|
|
478
|
+
const isStarred = payload.isStarred ?? payload.message?.isStarred;
|
|
479
|
+
return { messageId, content, conversationId, status, isPinned, isStarred };
|
|
480
|
+
};
|
|
481
|
+
mergeLastMessage = (existing, incoming) => {
|
|
482
|
+
if (!incoming) return existing ?? null;
|
|
483
|
+
if (!existing) return incoming;
|
|
484
|
+
const content = incoming.content?.trim() ? incoming.content : existing.content;
|
|
485
|
+
const createdAt = incoming.createdAt ?? incoming.timestamp ?? existing.createdAt ?? existing.timestamp;
|
|
486
|
+
return {
|
|
487
|
+
...existing,
|
|
488
|
+
...incoming,
|
|
489
|
+
...content !== void 0 ? { content } : {},
|
|
490
|
+
...createdAt !== void 0 ? { createdAt } : {}
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
|
+
getLatestMessageFromList = (messages) => {
|
|
494
|
+
if (!messages?.length) return null;
|
|
495
|
+
return messages.reduce((latest, message) => {
|
|
496
|
+
const latestTime = new Date(latest.createdAt || latest.timestamp || 0).getTime();
|
|
497
|
+
const messageTime = new Date(message.createdAt || message.timestamp || 0).getTime();
|
|
498
|
+
return messageTime >= latestTime ? message : latest;
|
|
499
|
+
});
|
|
500
|
+
};
|
|
501
|
+
resolveConversationLastMessage = (conversation, messagesByConversation) => {
|
|
502
|
+
const conversationId = String(conversation._id || conversation.id || "");
|
|
503
|
+
const storedLast = conversation.lastMessage;
|
|
504
|
+
if (storedLast?.content?.trim()) return storedLast;
|
|
505
|
+
const latestFromMessages = getLatestMessageFromList(messagesByConversation[conversationId] || []);
|
|
506
|
+
return mergeLastMessage(storedLast, latestFromMessages);
|
|
507
|
+
};
|
|
508
|
+
syncConversationLastMessage = (conversations, conversationId, messages) => {
|
|
509
|
+
const latest = getLatestMessageFromList(messages);
|
|
510
|
+
if (!latest) return conversations;
|
|
511
|
+
return conversations.map((conversation) => {
|
|
512
|
+
if (String(conversation._id) !== String(conversationId)) return conversation;
|
|
513
|
+
return {
|
|
514
|
+
...conversation,
|
|
515
|
+
lastMessage: mergeLastMessage(conversation.lastMessage, latest)
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
isGroupConversation = (conversation) => {
|
|
520
|
+
if (!conversation) return false;
|
|
521
|
+
return conversation.conversationType === "group" || !!conversation.isGroup;
|
|
522
|
+
};
|
|
523
|
+
findConversationById = (conversations, conversationId) => {
|
|
524
|
+
if (!conversationId) return void 0;
|
|
525
|
+
return conversations.find((c) => String(c._id) === String(conversationId));
|
|
526
|
+
};
|
|
527
|
+
resolveIsGroupFromPayload = (payload, conversations) => {
|
|
528
|
+
const conversationId = payload?.conversationId || payload?.message?.conversationId || payload?.message?.conversation_id;
|
|
529
|
+
const fromStore = findConversationById(conversations, conversationId);
|
|
530
|
+
if (fromStore) return isGroupConversation(fromStore);
|
|
531
|
+
return !!payload?.isGroup || payload?.conversationType === "group" || payload?.message?.conversationType === "group";
|
|
532
|
+
};
|
|
533
|
+
getOtherParticipantId = (conversation, loggedUserId) => {
|
|
534
|
+
if (!conversation?.participants?.length) return "";
|
|
535
|
+
const other = conversation.participants.find(
|
|
536
|
+
(p) => String(p._id || p) !== String(loggedUserId)
|
|
537
|
+
) || conversation.participants[0];
|
|
538
|
+
return String(other?._id || other || "");
|
|
539
|
+
};
|
|
540
|
+
findConversationForChannel = (channel, loggedUserId, conversations) => {
|
|
541
|
+
if (!channel || !loggedUserId) return void 0;
|
|
542
|
+
if (channel.conversationId) {
|
|
543
|
+
const byId = findConversationById(conversations, channel.conversationId);
|
|
544
|
+
if (byId) return byId;
|
|
545
|
+
}
|
|
546
|
+
if (channel.isGroup) {
|
|
547
|
+
return conversations.find(
|
|
548
|
+
(c) => isGroupConversation(c) && String(c._id) === String(channel.id)
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
return conversations.find((c) => {
|
|
552
|
+
if (isGroupConversation(c)) return false;
|
|
553
|
+
const ids = (c.participants || []).map((p) => String(p._id || p));
|
|
554
|
+
return ids.includes(String(channel.id)) && ids.includes(String(loggedUserId));
|
|
555
|
+
});
|
|
556
|
+
};
|
|
557
|
+
resolveConversationIdForChannel = (channel, loggedUserId, conversations) => {
|
|
558
|
+
const found = findConversationForChannel(channel, loggedUserId, conversations);
|
|
559
|
+
if (found?._id) return String(found._id);
|
|
560
|
+
if (channel?.conversationId) return String(channel.conversationId);
|
|
561
|
+
return null;
|
|
562
|
+
};
|
|
563
|
+
getMessageContent = (message) => String(message?.content || "").trim();
|
|
564
|
+
getMessageTimestamp = (message) => {
|
|
565
|
+
const value = message?.createdAt || message?.timestamp;
|
|
566
|
+
const time = value ? new Date(value).getTime() : NaN;
|
|
567
|
+
return Number.isFinite(time) ? time : 0;
|
|
568
|
+
};
|
|
569
|
+
isOwnMessage = (message, loggedUserId) => {
|
|
570
|
+
if (message?.isOwnMessage) return true;
|
|
571
|
+
if (!loggedUserId) return false;
|
|
572
|
+
const senderId = message?.sender?._id || message?.sender?.id || message?.sender;
|
|
573
|
+
return String(senderId) === String(loggedUserId);
|
|
574
|
+
};
|
|
575
|
+
STATUS_RANK = { sent: 1, delivered: 2, read: 3 };
|
|
576
|
+
pickBetterMessageStatus = (current, incoming) => {
|
|
577
|
+
if (!incoming) return current;
|
|
578
|
+
if (!current) return incoming;
|
|
579
|
+
return (STATUS_RANK[incoming] ?? 0) >= (STATUS_RANK[current] ?? 0) ? incoming : current;
|
|
580
|
+
};
|
|
581
|
+
mergeChatMessages = (apiMessages, localMessages, loggedUserId) => {
|
|
582
|
+
const merged = /* @__PURE__ */ new Map();
|
|
583
|
+
apiMessages.forEach((message) => {
|
|
584
|
+
const id = String(message?._id || message?.id || "");
|
|
585
|
+
if (id) merged.set(id, message);
|
|
586
|
+
});
|
|
587
|
+
localMessages.forEach((localMessage) => {
|
|
588
|
+
if (!localMessage) return;
|
|
589
|
+
const localId = String(localMessage._id || localMessage.id || "");
|
|
590
|
+
if (localId && merged.has(localId)) {
|
|
591
|
+
const existing = merged.get(localId);
|
|
592
|
+
merged.set(localId, {
|
|
593
|
+
...existing,
|
|
594
|
+
...localMessage,
|
|
595
|
+
status: pickBetterMessageStatus(existing?.status, localMessage?.status)
|
|
596
|
+
});
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const duplicateInApi = apiMessages.some((apiMessage) => {
|
|
600
|
+
const apiId = String(apiMessage?._id || apiMessage?.id || "");
|
|
601
|
+
if (localId && apiId && localId === apiId) return true;
|
|
602
|
+
if (!isOwnMessage(localMessage, loggedUserId)) return false;
|
|
603
|
+
if (!isOwnMessage(apiMessage, loggedUserId)) return false;
|
|
604
|
+
if (getMessageContent(localMessage) !== getMessageContent(apiMessage)) return false;
|
|
605
|
+
return Math.abs(getMessageTimestamp(localMessage) - getMessageTimestamp(apiMessage)) < 12e4;
|
|
606
|
+
});
|
|
607
|
+
if (duplicateInApi) return;
|
|
608
|
+
const key = localId || `pending-${getMessageTimestamp(localMessage)}-${getMessageContent(localMessage)}`;
|
|
609
|
+
if (!merged.has(key)) merged.set(key, localMessage);
|
|
610
|
+
});
|
|
611
|
+
return Array.from(merged.values()).filter((message, index, list) => {
|
|
612
|
+
const id = String(message?._id || message?.id || "");
|
|
613
|
+
if (!id) return true;
|
|
614
|
+
return list.findIndex((m) => String(m?._id || m?.id || "") === id) === index;
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
|
|
432
620
|
// src/constants/api.constants.ts
|
|
433
621
|
var API_ENDPOINTS;
|
|
434
622
|
var init_api_constants = __esm({
|
|
@@ -456,21 +644,6 @@ var init_api_constants = __esm({
|
|
|
456
644
|
};
|
|
457
645
|
}
|
|
458
646
|
});
|
|
459
|
-
|
|
460
|
-
// src/store/useSocketStore.ts
|
|
461
|
-
var useSocketStore_exports = {};
|
|
462
|
-
__export(useSocketStore_exports, {
|
|
463
|
-
default: () => useSocketStore_default,
|
|
464
|
-
useSocketStore: () => useSocketStore
|
|
465
|
-
});
|
|
466
|
-
var useSocketStore, useSocketStore_default;
|
|
467
|
-
var init_useSocketStore = __esm({
|
|
468
|
-
"src/store/useSocketStore.ts"() {
|
|
469
|
-
init_useStore();
|
|
470
|
-
useSocketStore = useStore;
|
|
471
|
-
useSocketStore_default = useSocketStore;
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
647
|
var chatClientId, chatAccessToken, rateLimitResetAt, setChatClientId, setChatAccessToken, isRateLimited, apiClient, setChatBaseURL, clientUserLogin, fetchUsers, fetchConversations, fetchMessages, fetchPinnedMessages, fetchStarredMessages, createGroup, fetchConversationInfo, updateConversation, updateGroupInformation, addRemoveGroupMembers, addRemoveGroupAdmins, searchMessages, searchMessagesContext, blockUnblockUser;
|
|
475
648
|
var init_api_service = __esm({
|
|
476
649
|
"src/services/api.service.ts"() {
|
|
@@ -691,138 +864,6 @@ var init_api_service = __esm({
|
|
|
691
864
|
}
|
|
692
865
|
});
|
|
693
866
|
|
|
694
|
-
// src/store/helpers/store.helpers.ts
|
|
695
|
-
var findConversationIdByMessageId, normalizeId, normalizeUnreadCount, incrementUnreadCount, emptyMessagesMetadata, extractMessageData, isGroupConversation, findConversationById, resolveIsGroupFromPayload, getOtherParticipantId, findConversationForChannel, resolveConversationIdForChannel, getMessageContent, getMessageTimestamp, isOwnMessage, STATUS_RANK, pickBetterMessageStatus, mergeChatMessages;
|
|
696
|
-
var init_store_helpers = __esm({
|
|
697
|
-
"src/store/helpers/store.helpers.ts"() {
|
|
698
|
-
findConversationIdByMessageId = (messagesByConversation, messageId) => {
|
|
699
|
-
return Object.keys(messagesByConversation).find(
|
|
700
|
-
(cid) => messagesByConversation[cid].some(
|
|
701
|
-
(m) => String(m._id || m.id) === String(messageId)
|
|
702
|
-
)
|
|
703
|
-
);
|
|
704
|
-
};
|
|
705
|
-
normalizeId = (id) => String(id?._id || id?.id || id || "");
|
|
706
|
-
normalizeUnreadCount = (unreadCount) => {
|
|
707
|
-
if (typeof unreadCount === "number") return unreadCount;
|
|
708
|
-
return 0;
|
|
709
|
-
};
|
|
710
|
-
incrementUnreadCount = (unreadCount) => normalizeUnreadCount(unreadCount) + 1;
|
|
711
|
-
emptyMessagesMetadata = () => ({
|
|
712
|
-
nextCursor: null,
|
|
713
|
-
hasMore: false,
|
|
714
|
-
isFirstPage: true
|
|
715
|
-
});
|
|
716
|
-
extractMessageData = (payload) => {
|
|
717
|
-
const messageId = payload.messageId || payload._id || payload.message?._id || payload.message?.id;
|
|
718
|
-
const content = payload.content || payload.message?.content;
|
|
719
|
-
const conversationId = payload.conversationId || payload.message?.conversationId || payload.message?.conversation_id;
|
|
720
|
-
const status = payload.status || payload.message?.status;
|
|
721
|
-
const isPinned = payload.isPinned ?? payload.message?.isPinned;
|
|
722
|
-
const isStarred = payload.isStarred ?? payload.message?.isStarred;
|
|
723
|
-
return { messageId, content, conversationId, status, isPinned, isStarred };
|
|
724
|
-
};
|
|
725
|
-
isGroupConversation = (conversation) => {
|
|
726
|
-
if (!conversation) return false;
|
|
727
|
-
return conversation.conversationType === "group" || !!conversation.isGroup;
|
|
728
|
-
};
|
|
729
|
-
findConversationById = (conversations, conversationId) => {
|
|
730
|
-
if (!conversationId) return void 0;
|
|
731
|
-
return conversations.find((c) => String(c._id) === String(conversationId));
|
|
732
|
-
};
|
|
733
|
-
resolveIsGroupFromPayload = (payload, conversations) => {
|
|
734
|
-
const conversationId = payload?.conversationId || payload?.message?.conversationId || payload?.message?.conversation_id;
|
|
735
|
-
const fromStore = findConversationById(conversations, conversationId);
|
|
736
|
-
if (fromStore) return isGroupConversation(fromStore);
|
|
737
|
-
return !!payload?.isGroup || payload?.conversationType === "group" || payload?.message?.conversationType === "group";
|
|
738
|
-
};
|
|
739
|
-
getOtherParticipantId = (conversation, loggedUserId) => {
|
|
740
|
-
if (!conversation?.participants?.length) return "";
|
|
741
|
-
const other = conversation.participants.find(
|
|
742
|
-
(p) => String(p._id || p) !== String(loggedUserId)
|
|
743
|
-
) || conversation.participants[0];
|
|
744
|
-
return String(other?._id || other || "");
|
|
745
|
-
};
|
|
746
|
-
findConversationForChannel = (channel, loggedUserId, conversations) => {
|
|
747
|
-
if (!channel || !loggedUserId) return void 0;
|
|
748
|
-
if (channel.conversationId) {
|
|
749
|
-
const byId = findConversationById(conversations, channel.conversationId);
|
|
750
|
-
if (byId) return byId;
|
|
751
|
-
}
|
|
752
|
-
if (channel.isGroup) {
|
|
753
|
-
return conversations.find(
|
|
754
|
-
(c) => isGroupConversation(c) && String(c._id) === String(channel.id)
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
return conversations.find((c) => {
|
|
758
|
-
if (isGroupConversation(c)) return false;
|
|
759
|
-
const ids = (c.participants || []).map((p) => String(p._id || p));
|
|
760
|
-
return ids.includes(String(channel.id)) && ids.includes(String(loggedUserId));
|
|
761
|
-
});
|
|
762
|
-
};
|
|
763
|
-
resolveConversationIdForChannel = (channel, loggedUserId, conversations) => {
|
|
764
|
-
const found = findConversationForChannel(channel, loggedUserId, conversations);
|
|
765
|
-
if (found?._id) return String(found._id);
|
|
766
|
-
if (channel?.conversationId) return String(channel.conversationId);
|
|
767
|
-
return null;
|
|
768
|
-
};
|
|
769
|
-
getMessageContent = (message) => String(message?.content || "").trim();
|
|
770
|
-
getMessageTimestamp = (message) => {
|
|
771
|
-
const value = message?.createdAt || message?.timestamp;
|
|
772
|
-
const time = value ? new Date(value).getTime() : NaN;
|
|
773
|
-
return Number.isFinite(time) ? time : 0;
|
|
774
|
-
};
|
|
775
|
-
isOwnMessage = (message, loggedUserId) => {
|
|
776
|
-
if (message?.isOwnMessage) return true;
|
|
777
|
-
if (!loggedUserId) return false;
|
|
778
|
-
const senderId = message?.sender?._id || message?.sender?.id || message?.sender;
|
|
779
|
-
return String(senderId) === String(loggedUserId);
|
|
780
|
-
};
|
|
781
|
-
STATUS_RANK = { sent: 1, delivered: 2, read: 3 };
|
|
782
|
-
pickBetterMessageStatus = (current, incoming) => {
|
|
783
|
-
if (!incoming) return current;
|
|
784
|
-
if (!current) return incoming;
|
|
785
|
-
return (STATUS_RANK[incoming] ?? 0) >= (STATUS_RANK[current] ?? 0) ? incoming : current;
|
|
786
|
-
};
|
|
787
|
-
mergeChatMessages = (apiMessages, localMessages, loggedUserId) => {
|
|
788
|
-
const merged = /* @__PURE__ */ new Map();
|
|
789
|
-
apiMessages.forEach((message) => {
|
|
790
|
-
const id = String(message?._id || message?.id || "");
|
|
791
|
-
if (id) merged.set(id, message);
|
|
792
|
-
});
|
|
793
|
-
localMessages.forEach((localMessage) => {
|
|
794
|
-
if (!localMessage) return;
|
|
795
|
-
const localId = String(localMessage._id || localMessage.id || "");
|
|
796
|
-
if (localId && merged.has(localId)) {
|
|
797
|
-
const existing = merged.get(localId);
|
|
798
|
-
merged.set(localId, {
|
|
799
|
-
...existing,
|
|
800
|
-
...localMessage,
|
|
801
|
-
status: pickBetterMessageStatus(existing?.status, localMessage?.status)
|
|
802
|
-
});
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
const duplicateInApi = apiMessages.some((apiMessage) => {
|
|
806
|
-
const apiId = String(apiMessage?._id || apiMessage?.id || "");
|
|
807
|
-
if (localId && apiId && localId === apiId) return true;
|
|
808
|
-
if (!isOwnMessage(localMessage, loggedUserId)) return false;
|
|
809
|
-
if (!isOwnMessage(apiMessage, loggedUserId)) return false;
|
|
810
|
-
if (getMessageContent(localMessage) !== getMessageContent(apiMessage)) return false;
|
|
811
|
-
return Math.abs(getMessageTimestamp(localMessage) - getMessageTimestamp(apiMessage)) < 12e4;
|
|
812
|
-
});
|
|
813
|
-
if (duplicateInApi) return;
|
|
814
|
-
const key = localId || `pending-${getMessageTimestamp(localMessage)}-${getMessageContent(localMessage)}`;
|
|
815
|
-
if (!merged.has(key)) merged.set(key, localMessage);
|
|
816
|
-
});
|
|
817
|
-
return Array.from(merged.values()).filter((message, index, list) => {
|
|
818
|
-
const id = String(message?._id || message?.id || "");
|
|
819
|
-
if (!id) return true;
|
|
820
|
-
return list.findIndex((m) => String(m?._id || m?.id || "") === id) === index;
|
|
821
|
-
});
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
|
|
826
867
|
// src/store/forwardPending.store.ts
|
|
827
868
|
var pendingForwards, lastPendingMessageId, setPendingForward, peekPendingForward, peekMostRecentPendingForward, getLastPendingForwardMessageId, clearPendingForward;
|
|
828
869
|
var init_forwardPending_store = __esm({
|
|
@@ -1470,8 +1511,8 @@ var init_socket_slice = __esm({
|
|
|
1470
1511
|
"src/store/slices/socket.slice.ts"() {
|
|
1471
1512
|
init_socket_service();
|
|
1472
1513
|
init_socket_events_constants();
|
|
1473
|
-
init_api_service();
|
|
1474
1514
|
init_store_helpers();
|
|
1515
|
+
init_api_service();
|
|
1475
1516
|
init_forwardPending_store();
|
|
1476
1517
|
init_message_handlers();
|
|
1477
1518
|
init_group_handlers();
|
|
@@ -1935,6 +1976,7 @@ var init_socket_slice = __esm({
|
|
|
1935
1976
|
}));
|
|
1936
1977
|
},
|
|
1937
1978
|
// Fetching
|
|
1979
|
+
// Fetching
|
|
1938
1980
|
fetchConversations: async (limit = 10, page = 1, search = "", force = false) => {
|
|
1939
1981
|
const fetchKey = `${limit}:${page}:${search}`;
|
|
1940
1982
|
const state = get();
|
|
@@ -1947,7 +1989,15 @@ var init_socket_slice = __esm({
|
|
|
1947
1989
|
const response = await fetchConversations(limit, page, search);
|
|
1948
1990
|
const data = safelyExtractArray(response);
|
|
1949
1991
|
set((state2) => ({
|
|
1950
|
-
conversations: page === 1 ? data
|
|
1992
|
+
conversations: page === 1 ? data.map((incoming) => {
|
|
1993
|
+
const existing = state2.conversations.find(
|
|
1994
|
+
(conversation) => String(conversation._id) === String(incoming._id)
|
|
1995
|
+
);
|
|
1996
|
+
return {
|
|
1997
|
+
...incoming,
|
|
1998
|
+
lastMessage: mergeLastMessage(existing?.lastMessage, incoming.lastMessage)
|
|
1999
|
+
};
|
|
2000
|
+
}) : [...state2.conversations, ...data],
|
|
1951
2001
|
conversationsHasMore: data.length === limit,
|
|
1952
2002
|
conversationsPage: page,
|
|
1953
2003
|
isFetchingConversations: false
|
|
@@ -1999,6 +2049,7 @@ var init_socket_slice = __esm({
|
|
|
1999
2049
|
isFirstPage: isInitialLoad
|
|
2000
2050
|
}
|
|
2001
2051
|
},
|
|
2052
|
+
conversations: isInitialLoad ? syncConversationLastMessage(state2.conversations, conversationId, unique) : state2.conversations,
|
|
2002
2053
|
isFetchingMessages: false
|
|
2003
2054
|
};
|
|
2004
2055
|
});
|
|
@@ -2028,6 +2079,7 @@ var init_socket_slice = __esm({
|
|
|
2028
2079
|
const tempId = payload.tempId || `temp-${Date.now()}`;
|
|
2029
2080
|
set((state) => ({ messagePendingActions: { ...state.messagePendingActions, [tempId]: ["send"] } }));
|
|
2030
2081
|
socket_service_default.emit("dm_send", { ...payload, tempId });
|
|
2082
|
+
socket_service_default.emit("conversation_update", { conversationId: payload.conversationId });
|
|
2031
2083
|
},
|
|
2032
2084
|
emitDmEdit: (payload) => {
|
|
2033
2085
|
set((state) => ({ messagePendingActions: { ...state.messagePendingActions, [payload.messageId]: ["edit"] } }));
|
|
@@ -2166,27 +2218,22 @@ var init_socket_slice = __esm({
|
|
|
2166
2218
|
const { conversationId } = payload;
|
|
2167
2219
|
const readerId = payload.userId || payload.readerId || payload.readBy || payload.sender || payload.senderId;
|
|
2168
2220
|
if (!conversationId) return;
|
|
2169
|
-
const readStatus = payload.status || "read";
|
|
2170
2221
|
set({ lastDMMarkRead: payload });
|
|
2171
2222
|
set((state) => {
|
|
2172
2223
|
const messages = state.messagesByConversation[conversationId] || [];
|
|
2173
2224
|
const loggedUserDetails = get().loggedUserDetails || state.loggedUserDetails;
|
|
2174
2225
|
const myId = loggedUserDetails?._id || loggedUserDetails?.id;
|
|
2175
2226
|
const isReaderMe = String(readerId) === String(myId);
|
|
2176
|
-
const nextMessages = messages.map((m) => {
|
|
2177
|
-
if (isReaderMe) return m;
|
|
2178
|
-
const senderId = String(m.sender?._id || m.sender);
|
|
2179
|
-
if (senderId === String(myId)) {
|
|
2180
|
-
return { ...m, status: readStatus };
|
|
2181
|
-
}
|
|
2182
|
-
return m;
|
|
2183
|
-
});
|
|
2227
|
+
const nextMessages = messages.map((m) => ({ ...m, status: "read" }));
|
|
2184
2228
|
const nextConversations = state.conversations.map((c) => {
|
|
2185
2229
|
if (String(c._id) !== String(conversationId)) return c;
|
|
2186
2230
|
const nextUnreadCount = isReaderMe ? 0 : normalizeUnreadCount(c.unreadCount);
|
|
2187
|
-
let nextLastMessage = c.lastMessage;
|
|
2188
|
-
if (
|
|
2189
|
-
nextLastMessage =
|
|
2231
|
+
let nextLastMessage = mergeLastMessage(c.lastMessage, { status: "read" });
|
|
2232
|
+
if (!nextLastMessage?.content && messages.length) {
|
|
2233
|
+
nextLastMessage = mergeLastMessage(
|
|
2234
|
+
getLatestMessageFromList(messages),
|
|
2235
|
+
{ status: "read" }
|
|
2236
|
+
);
|
|
2190
2237
|
}
|
|
2191
2238
|
return { ...c, unreadCount: nextUnreadCount, lastMessage: nextLastMessage };
|
|
2192
2239
|
});
|
|
@@ -2348,7 +2395,7 @@ var init_socket_slice = __esm({
|
|
|
2348
2395
|
return {
|
|
2349
2396
|
...c,
|
|
2350
2397
|
...updated,
|
|
2351
|
-
lastMessage:
|
|
2398
|
+
lastMessage: mergeLastMessage(c.lastMessage, updated.lastMessage),
|
|
2352
2399
|
// Group conversation_update no longer carries per-user unread; keep local count
|
|
2353
2400
|
unreadCount: updated.unreadCount !== void 0 ? normalizeUnreadCount(updated.unreadCount) : normalizeUnreadCount(c.unreadCount)
|
|
2354
2401
|
};
|
|
@@ -3755,6 +3802,84 @@ function ScrollBar({
|
|
|
3755
3802
|
);
|
|
3756
3803
|
}
|
|
3757
3804
|
init_useStore();
|
|
3805
|
+
init_store_helpers();
|
|
3806
|
+
var conversationDrafts = /* @__PURE__ */ new Map();
|
|
3807
|
+
var DRAFT_PREVIEW_MAX_LENGTH = 50;
|
|
3808
|
+
var emptyDraft = () => ({
|
|
3809
|
+
messageInput: "",
|
|
3810
|
+
attachments: []
|
|
3811
|
+
});
|
|
3812
|
+
var draftRevision = 0;
|
|
3813
|
+
var draftListeners = /* @__PURE__ */ new Set();
|
|
3814
|
+
function notifyDraftChange() {
|
|
3815
|
+
draftRevision += 1;
|
|
3816
|
+
draftListeners.forEach((listener) => listener());
|
|
3817
|
+
}
|
|
3818
|
+
function subscribeToDrafts(listener) {
|
|
3819
|
+
draftListeners.add(listener);
|
|
3820
|
+
return () => {
|
|
3821
|
+
draftListeners.delete(listener);
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
function getDraftRevision() {
|
|
3825
|
+
return draftRevision;
|
|
3826
|
+
}
|
|
3827
|
+
function useDraftRevision() {
|
|
3828
|
+
return useSyncExternalStore(subscribeToDrafts, getDraftRevision, getDraftRevision);
|
|
3829
|
+
}
|
|
3830
|
+
function getConversationDraft(conversationId) {
|
|
3831
|
+
if (!conversationId) return emptyDraft();
|
|
3832
|
+
return conversationDrafts.get(conversationId) ?? emptyDraft();
|
|
3833
|
+
}
|
|
3834
|
+
function updateConversationDraft(conversationId, draft) {
|
|
3835
|
+
if (!conversationId) return;
|
|
3836
|
+
if (draft.messageInput.trim() || draft.attachments.length > 0) {
|
|
3837
|
+
conversationDrafts.set(conversationId, {
|
|
3838
|
+
messageInput: draft.messageInput,
|
|
3839
|
+
attachments: draft.attachments
|
|
3840
|
+
});
|
|
3841
|
+
} else {
|
|
3842
|
+
conversationDrafts.delete(conversationId);
|
|
3843
|
+
}
|
|
3844
|
+
notifyDraftChange();
|
|
3845
|
+
}
|
|
3846
|
+
function clearConversationDraft(conversationId) {
|
|
3847
|
+
if (!conversationId) return;
|
|
3848
|
+
if (!conversationDrafts.has(conversationId)) return;
|
|
3849
|
+
conversationDrafts.delete(conversationId);
|
|
3850
|
+
notifyDraftChange();
|
|
3851
|
+
}
|
|
3852
|
+
function getConversationDraftPreview(conversationId) {
|
|
3853
|
+
if (!conversationId) return null;
|
|
3854
|
+
const draft = conversationDrafts.get(conversationId);
|
|
3855
|
+
if (!draft) return null;
|
|
3856
|
+
const text = draft.messageInput.trim();
|
|
3857
|
+
if (text) {
|
|
3858
|
+
return text.length > DRAFT_PREVIEW_MAX_LENGTH ? `${text.slice(0, DRAFT_PREVIEW_MAX_LENGTH)}\u2026` : text;
|
|
3859
|
+
}
|
|
3860
|
+
if (draft.attachments.length > 0) {
|
|
3861
|
+
return draft.attachments.length === 1 ? "Attachment" : `${draft.attachments.length} attachments`;
|
|
3862
|
+
}
|
|
3863
|
+
return null;
|
|
3864
|
+
}
|
|
3865
|
+
function useConversationDraft(conversationId) {
|
|
3866
|
+
const composerRef = useRef(emptyDraft());
|
|
3867
|
+
const syncComposerRef = useCallback((draft) => {
|
|
3868
|
+
composerRef.current = draft;
|
|
3869
|
+
}, []);
|
|
3870
|
+
useEffect(() => {
|
|
3871
|
+
const id = conversationId;
|
|
3872
|
+
return () => {
|
|
3873
|
+
if (!id) return;
|
|
3874
|
+
updateConversationDraft(id, composerRef.current);
|
|
3875
|
+
};
|
|
3876
|
+
}, [conversationId]);
|
|
3877
|
+
return {
|
|
3878
|
+
syncComposerRef
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
// src/chat/ChannelList.tsx
|
|
3758
3883
|
var ChannelList = ({ activeChannel, onChannelSelect, onNewChatClick, debouncedSearch = "" }) => {
|
|
3759
3884
|
const onlineUserIds = useStore((s) => s.onlineUsers);
|
|
3760
3885
|
const allUsers = useStore((s) => s.allUsers);
|
|
@@ -3762,12 +3887,14 @@ var ChannelList = ({ activeChannel, onChannelSelect, onNewChatClick, debouncedSe
|
|
|
3762
3887
|
const deletedForMeIds = useStore((s) => s.deletedForMeIds);
|
|
3763
3888
|
const isFetchingUsers = useStore((s) => s.isFetchingAllUsers);
|
|
3764
3889
|
const conversations = useStore((s) => s.conversations);
|
|
3890
|
+
const messagesByConversation = useStore((s) => s.messagesByConversation);
|
|
3765
3891
|
const isFetchingConversations = useStore((s) => s.isFetchingConversations);
|
|
3766
3892
|
const conversationsHasMore = useStore((s) => s.conversationsHasMore);
|
|
3767
3893
|
const conversationsPage = useStore((s) => s.conversationsPage);
|
|
3768
3894
|
const accessToken = useStore((s) => s.accessToken);
|
|
3769
3895
|
const loggedUserDetails = useStore((s) => s.loggedUserDetails);
|
|
3770
3896
|
const lastFetchKeyRef = useRef(null);
|
|
3897
|
+
useDraftRevision();
|
|
3771
3898
|
useEffect(() => {
|
|
3772
3899
|
if (accessToken) {
|
|
3773
3900
|
useStore.getState().fetchAllUsers();
|
|
@@ -3796,17 +3923,20 @@ var ChannelList = ({ activeChannel, onChannelSelect, onNewChatClick, debouncedSe
|
|
|
3796
3923
|
const isOnline = c.conversationType === "group" || c.isGroup ? false : onlineUserIds.includes(otherParticipantId);
|
|
3797
3924
|
const unreadCount = typeof c.unreadCount === "number" ? c.unreadCount : 0;
|
|
3798
3925
|
const isTyping = typingUsersByConv[String(c._id)]?.length > 0;
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3926
|
+
const resolvedLastMessage = resolveConversationLastMessage(c, messagesByConversation);
|
|
3927
|
+
let lastMessageContent = resolvedLastMessage?.content || "No messages yet";
|
|
3928
|
+
if (resolvedLastMessage) {
|
|
3929
|
+
if (resolvedLastMessage.isDeletedForEveryone) {
|
|
3802
3930
|
lastMessageContent = "This message has been deleted";
|
|
3803
|
-
} else if (deletedForMeIds.includes(String(
|
|
3931
|
+
} else if (deletedForMeIds.includes(String(resolvedLastMessage._id || resolvedLastMessage.id))) {
|
|
3804
3932
|
lastMessageContent = "";
|
|
3805
3933
|
}
|
|
3806
3934
|
}
|
|
3935
|
+
const conversationId = String(c._id);
|
|
3936
|
+
const draftPreview = getConversationDraftPreview(conversationId);
|
|
3807
3937
|
return {
|
|
3808
|
-
id: c.conversationType === "group" || c.isGroup ?
|
|
3809
|
-
conversationId
|
|
3938
|
+
id: c.conversationType === "group" || c.isGroup ? conversationId : otherParticipantId,
|
|
3939
|
+
conversationId,
|
|
3810
3940
|
name: name.charAt(0).toUpperCase() + name.slice(1),
|
|
3811
3941
|
image,
|
|
3812
3942
|
isOnline,
|
|
@@ -3815,16 +3945,17 @@ var ChannelList = ({ activeChannel, onChannelSelect, onNewChatClick, debouncedSe
|
|
|
3815
3945
|
isBlocked: c.isBlocked || false,
|
|
3816
3946
|
unreadCount,
|
|
3817
3947
|
isTyping,
|
|
3948
|
+
draftPreview,
|
|
3818
3949
|
pinnedCount: c.pinnedCount,
|
|
3819
3950
|
starredCount: c.starredCount,
|
|
3820
3951
|
lastMessage: {
|
|
3821
3952
|
content: lastMessageContent,
|
|
3822
|
-
timestamp:
|
|
3953
|
+
timestamp: resolvedLastMessage?.createdAt ? new Date(resolvedLastMessage.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "",
|
|
3823
3954
|
sender: "User"
|
|
3824
3955
|
}
|
|
3825
3956
|
};
|
|
3826
3957
|
});
|
|
3827
|
-
}, [conversations, onlineUserIds, loggedUserDetails, allUsers, typingUsersByConv, deletedForMeIds]);
|
|
3958
|
+
}, [conversations, messagesByConversation, onlineUserIds, loggedUserDetails, allUsers, typingUsersByConv, deletedForMeIds]);
|
|
3828
3959
|
const handleLoadMore = () => {
|
|
3829
3960
|
if (!isFetchingConversations && conversationsHasMore) {
|
|
3830
3961
|
useStore.getState().fetchConversations(10, conversationsPage + 1, debouncedSearch);
|
|
@@ -3855,8 +3986,10 @@ var ChannelList = ({ activeChannel, onChannelSelect, onNewChatClick, debouncedSe
|
|
|
3855
3986
|
isActive ? "text-(--chat-theme)" : "text-[#0f172a]"
|
|
3856
3987
|
) }, channel.name), /* @__PURE__ */ React7__default.createElement("div", { className: "flex shrink-0 flex-col items-end gap-1" }, /* @__PURE__ */ React7__default.createElement("span", { className: "text-[11px] font-medium text-muted-foreground" }, channel.lastMessage?.timestamp), channel.isLeft && /* @__PURE__ */ React7__default.createElement("span", { className: "rounded-full bg-red-50 px-1.5 py-0.5 text-[9px] font-semibold text-red-600" }, "Left"))), /* @__PURE__ */ React7__default.createElement("div", { className: "flex w-full min-w-0 items-center justify-between gap-2 pr-1" }, /* @__PURE__ */ React7__default.createElement("p", { className: cn(
|
|
3857
3988
|
"min-w-0 line-clamp-1 text-[13px]",
|
|
3858
|
-
channel.isTyping ? "font-medium
|
|
3859
|
-
|
|
3989
|
+
channel.isTyping || channel.draftPreview ? "font-medium" : "text-muted-foreground",
|
|
3990
|
+
channel.isTyping && "text-(--chat-theme)",
|
|
3991
|
+
!channel.isTyping && channel.draftPreview && "text-muted-foreground"
|
|
3992
|
+
) }, channel.isTyping ? "typing..." : channel.draftPreview ? /* @__PURE__ */ React7__default.createElement(React7__default.Fragment, null, /* @__PURE__ */ React7__default.createElement("span", { className: "font-semibold text-rose-500" }, "Draft: "), /* @__PURE__ */ React7__default.createElement("span", null, channel.draftPreview)) : channel.lastMessage?.content), channel.unreadCount !== void 0 && channel.unreadCount > 0 && /* @__PURE__ */ React7__default.createElement(Badge, { className: cn(
|
|
3860
3993
|
"flex h-5 min-w-5 items-center justify-center rounded-full border-none px-1.5 text-[10px] font-semibold",
|
|
3861
3994
|
isActive ? "bg-(--chat-theme) text-white" : "bg-(--chat-theme) text-white"
|
|
3862
3995
|
) }, channel.unreadCount)))
|
|
@@ -5771,10 +5904,9 @@ var MessageReactions = ({
|
|
|
5771
5904
|
return /* @__PURE__ */ React7__default.createElement(Tooltip, { key: emoji }, /* @__PURE__ */ React7__default.createElement(TooltipTrigger, { asChild: true }, /* @__PURE__ */ React7__default.createElement(
|
|
5772
5905
|
"button",
|
|
5773
5906
|
{
|
|
5774
|
-
key: emoji,
|
|
5775
5907
|
onClick: () => onReactionClick(emoji),
|
|
5776
5908
|
className: cn(
|
|
5777
|
-
"flex items-center gap-1 px-2 py-0.5 rounded-full text-xs border shadow-sm transition-all shadow-black/5",
|
|
5909
|
+
"flex items-center gap-1 px-2 py-0.5 rounded-full text-xs border shadow-sm transition-all shadow-black/5 outline-none",
|
|
5778
5910
|
isReactedByMe ? "bg-(--chat-theme-10) border-(--chat-theme-20) text-(--chat-theme)" : "bg-white border-border/40 hover:bg-muted/50 text-[#111827]"
|
|
5779
5911
|
)
|
|
5780
5912
|
},
|
|
@@ -5837,13 +5969,9 @@ var MessageToolbar = ({
|
|
|
5837
5969
|
canEdit = true
|
|
5838
5970
|
}) => {
|
|
5839
5971
|
return /* @__PURE__ */ React7__default.createElement("div", { className: cn(
|
|
5840
|
-
"absolute -top-10 z-
|
|
5972
|
+
"absolute -top-10 z-20 flex items-center gap-1 rounded-full border bg-background/95 p-1 shadow-md backdrop-blur-sm",
|
|
5841
5973
|
isSender ? "right-0" : "left-0"
|
|
5842
|
-
) }, /* @__PURE__ */ React7__default.createElement(Button, { variant: "ghost", size: "icon", className: "size-8 rounded-full", onClick: ()
|
|
5843
|
-
onReply?.();
|
|
5844
|
-
} }, /* @__PURE__ */ React7__default.createElement(CornerUpLeft, { className: "size-4" })), /* @__PURE__ */ React7__default.createElement(Button, { variant: "ghost", size: "icon", className: "size-8 rounded-full", onClick: () => {
|
|
5845
|
-
onForward?.();
|
|
5846
|
-
} }, /* @__PURE__ */ React7__default.createElement(Forward, { className: "size-4" })), /* @__PURE__ */ React7__default.createElement(Popover, { open: isReactionOpen, onOpenChange: setIsReactionOpen }, /* @__PURE__ */ React7__default.createElement(PopoverTrigger, { asChild: true }, /* @__PURE__ */ React7__default.createElement(Button, { variant: "ghost", size: "icon", className: "size-8 rounded-full hover:bg-muted transition-all" }, /* @__PURE__ */ React7__default.createElement(Smile, { className: "size-4 text-muted-foreground" }))), /* @__PURE__ */ React7__default.createElement(
|
|
5974
|
+
) }, /* @__PURE__ */ React7__default.createElement(Button, { variant: "ghost", size: "icon", className: "size-8 rounded-full", onClick: onReply }, /* @__PURE__ */ React7__default.createElement(CornerUpLeft, { className: "size-4" })), /* @__PURE__ */ React7__default.createElement(Button, { variant: "ghost", size: "icon", className: "size-8 rounded-full", onClick: onForward }, /* @__PURE__ */ React7__default.createElement(Forward, { className: "size-4" })), /* @__PURE__ */ React7__default.createElement(Popover, { open: isReactionOpen, onOpenChange: setIsReactionOpen }, /* @__PURE__ */ React7__default.createElement(PopoverTrigger, { asChild: true }, /* @__PURE__ */ React7__default.createElement(Button, { variant: "ghost", size: "icon", className: "size-8 rounded-full hover:bg-muted transition-all" }, /* @__PURE__ */ React7__default.createElement(Smile, { className: "size-4 text-muted-foreground" }))), /* @__PURE__ */ React7__default.createElement(
|
|
5847
5975
|
PopoverContent,
|
|
5848
5976
|
{
|
|
5849
5977
|
className: "w-auto p-0 border-none shadow-2xl rounded-2xl",
|
|
@@ -6387,6 +6515,9 @@ var ChannelMessageBox = ({ activeChannel, conversationId, onSendMessage, replyTo
|
|
|
6387
6515
|
setState((prev) => ({ ...prev, ...updates }));
|
|
6388
6516
|
};
|
|
6389
6517
|
const typingTimeoutRef = useRef(null);
|
|
6518
|
+
const conversationIdRef = useRef(conversationId);
|
|
6519
|
+
conversationIdRef.current = conversationId;
|
|
6520
|
+
const { syncComposerRef } = useConversationDraft(conversationId);
|
|
6390
6521
|
const emitTypingStart = useStore((s) => s.emitTypingStart);
|
|
6391
6522
|
const emitTypingStop = useStore((s) => s.emitTypingStop);
|
|
6392
6523
|
const emitGroupTypingStart = useStore((s) => s.emitGroupTypingStart);
|
|
@@ -6398,6 +6529,30 @@ var ChannelMessageBox = ({ activeChannel, conversationId, onSendMessage, replyTo
|
|
|
6398
6529
|
React7__default.useEffect(() => {
|
|
6399
6530
|
attachmentsCountRef.current = state.attachments.length;
|
|
6400
6531
|
}, [state.attachments.length]);
|
|
6532
|
+
React7__default.useEffect(() => {
|
|
6533
|
+
const draft = {
|
|
6534
|
+
messageInput: state.messageInput,
|
|
6535
|
+
attachments: state.attachments
|
|
6536
|
+
};
|
|
6537
|
+
syncComposerRef(draft);
|
|
6538
|
+
updateConversationDraft(conversationIdRef.current, draft);
|
|
6539
|
+
}, [state.messageInput, state.attachments, syncComposerRef]);
|
|
6540
|
+
React7__default.useEffect(() => {
|
|
6541
|
+
if (typingTimeoutRef.current) {
|
|
6542
|
+
clearTimeout(typingTimeoutRef.current);
|
|
6543
|
+
typingTimeoutRef.current = null;
|
|
6544
|
+
}
|
|
6545
|
+
const draft = getConversationDraft(conversationId);
|
|
6546
|
+
attachmentsCountRef.current = draft.attachments.length;
|
|
6547
|
+
setState((prev) => ({
|
|
6548
|
+
...prev,
|
|
6549
|
+
messageInput: draft.messageInput,
|
|
6550
|
+
attachments: draft.attachments,
|
|
6551
|
+
attachmentError: "",
|
|
6552
|
+
isTyping: false
|
|
6553
|
+
}));
|
|
6554
|
+
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
6555
|
+
}, [conversationId]);
|
|
6401
6556
|
const handleInputChange = (e) => {
|
|
6402
6557
|
updateState({ messageInput: e.target.value });
|
|
6403
6558
|
if (!conversationId || !loggedUserDetails || !activeChannel) return;
|
|
@@ -6482,6 +6637,8 @@ var ChannelMessageBox = ({ activeChannel, conversationId, onSendMessage, replyTo
|
|
|
6482
6637
|
});
|
|
6483
6638
|
};
|
|
6484
6639
|
const handleSendMessage = () => {
|
|
6640
|
+
const trimmed = state.messageInput.trim();
|
|
6641
|
+
if (!trimmed && state.attachments.length === 0) return;
|
|
6485
6642
|
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
|
|
6486
6643
|
if (state.isTyping && conversationId && loggedUserDetails && activeChannel) {
|
|
6487
6644
|
updateState({ isTyping: false });
|
|
@@ -6497,7 +6654,8 @@ var ChannelMessageBox = ({ activeChannel, conversationId, onSendMessage, replyTo
|
|
|
6497
6654
|
emitTypingStop(typingPayload);
|
|
6498
6655
|
}
|
|
6499
6656
|
}
|
|
6500
|
-
onSendMessage?.(
|
|
6657
|
+
onSendMessage?.(trimmed, state.attachments, replyTo);
|
|
6658
|
+
clearConversationDraft(conversationId);
|
|
6501
6659
|
updateState({
|
|
6502
6660
|
messageInput: "",
|
|
6503
6661
|
attachments: [],
|
|
@@ -7027,8 +7185,115 @@ var ChatMain_default = ChatMain;
|
|
|
7027
7185
|
init_useStore();
|
|
7028
7186
|
init_socket_service();
|
|
7029
7187
|
init_api_service();
|
|
7188
|
+
|
|
7189
|
+
// src/config/socket.config.ts
|
|
7190
|
+
var getSocketConfig = (accessToken, tenantId, apiBaseUrl) => {
|
|
7191
|
+
const baseURL = (apiBaseUrl || "").replace("/api/v1", "");
|
|
7192
|
+
return {
|
|
7193
|
+
url: baseURL,
|
|
7194
|
+
options: {
|
|
7195
|
+
path: "/connection",
|
|
7196
|
+
auth: {
|
|
7197
|
+
accessToken
|
|
7198
|
+
},
|
|
7199
|
+
query: {
|
|
7200
|
+
platform: "web",
|
|
7201
|
+
tenantId: tenantId || "",
|
|
7202
|
+
isTenant: "true"
|
|
7203
|
+
},
|
|
7204
|
+
extraHeaders: {
|
|
7205
|
+
Authorization: `Bearer ${accessToken}`,
|
|
7206
|
+
platform: "web",
|
|
7207
|
+
tenantId: tenantId || "",
|
|
7208
|
+
isTenant: "true"
|
|
7209
|
+
},
|
|
7210
|
+
transports: ["websocket", "polling"],
|
|
7211
|
+
reconnection: true,
|
|
7212
|
+
reconnectionAttempts: Infinity,
|
|
7213
|
+
reconnectionDelay: 2e3,
|
|
7214
|
+
reconnectionDelayMax: 5e3,
|
|
7215
|
+
timeout: 2e4
|
|
7216
|
+
}
|
|
7217
|
+
};
|
|
7218
|
+
};
|
|
7219
|
+
var showNotification = (message, type = "info", icon, duration) => {
|
|
7220
|
+
const options = {
|
|
7221
|
+
icon,
|
|
7222
|
+
duration
|
|
7223
|
+
};
|
|
7224
|
+
switch (type) {
|
|
7225
|
+
case "success":
|
|
7226
|
+
toast.success(message, options);
|
|
7227
|
+
break;
|
|
7228
|
+
case "error":
|
|
7229
|
+
toast.error(message, options);
|
|
7230
|
+
break;
|
|
7231
|
+
case "info":
|
|
7232
|
+
toast(message, options);
|
|
7233
|
+
break;
|
|
7234
|
+
case "warning":
|
|
7235
|
+
toast(message, { ...options, icon: "\u26A0\uFE0F" });
|
|
7236
|
+
break;
|
|
7237
|
+
default:
|
|
7238
|
+
toast(message, options);
|
|
7239
|
+
}
|
|
7240
|
+
};
|
|
7241
|
+
var downloadFile = async (src, name) => {
|
|
7242
|
+
try {
|
|
7243
|
+
const link = document.createElement("a");
|
|
7244
|
+
link.href = src;
|
|
7245
|
+
link.setAttribute("download", name);
|
|
7246
|
+
link.setAttribute("rel", "noopener noreferrer");
|
|
7247
|
+
link.click();
|
|
7248
|
+
showNotification("File Downloaded Successfully", "success");
|
|
7249
|
+
} catch (err) {
|
|
7250
|
+
console.error("File download failed:", err);
|
|
7251
|
+
showNotification("Failed to download file", "error");
|
|
7252
|
+
}
|
|
7253
|
+
};
|
|
7254
|
+
var getResponsiveWidth = (width) => {
|
|
7255
|
+
const screenWidth = window.innerWidth;
|
|
7256
|
+
if (width) {
|
|
7257
|
+
return width;
|
|
7258
|
+
} else if (screenWidth < 640) {
|
|
7259
|
+
return 350;
|
|
7260
|
+
} else if (screenWidth >= 640 && screenWidth < 1024) {
|
|
7261
|
+
return 600;
|
|
7262
|
+
} else if (screenWidth >= 1024 && screenWidth < 1280) {
|
|
7263
|
+
return 800;
|
|
7264
|
+
} else {
|
|
7265
|
+
return 1e3;
|
|
7266
|
+
}
|
|
7267
|
+
};
|
|
7268
|
+
var isEmpty = (value) => {
|
|
7269
|
+
if (value === null || value === void 0) return true;
|
|
7270
|
+
if (typeof value === "string") return value.trim().length === 0;
|
|
7271
|
+
if (Array.isArray(value)) {
|
|
7272
|
+
return value.length === 0 || value.every((item) => isEmpty(item));
|
|
7273
|
+
}
|
|
7274
|
+
if (typeof value === "object") {
|
|
7275
|
+
return Object.keys(value).length === 0;
|
|
7276
|
+
}
|
|
7277
|
+
return false;
|
|
7278
|
+
};
|
|
7279
|
+
function uniqueList(list, key) {
|
|
7280
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7281
|
+
return list.filter((item) => {
|
|
7282
|
+
const value = item[key];
|
|
7283
|
+
if (seen.has(value)) return false;
|
|
7284
|
+
seen.add(value);
|
|
7285
|
+
return true;
|
|
7286
|
+
});
|
|
7287
|
+
}
|
|
7288
|
+
var capitalizeFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
7289
|
+
var getFirstAndLastNameOneCharacter = (name) => {
|
|
7290
|
+
const names = name.split(" ");
|
|
7291
|
+
return names.map((name2) => name2.charAt(0).toUpperCase()).join("");
|
|
7292
|
+
};
|
|
7293
|
+
|
|
7294
|
+
// src/index.ts
|
|
7030
7295
|
var index_default = ChatMain_default;
|
|
7031
7296
|
|
|
7032
|
-
export { Channel, ChannelHeader_default as ChannelHeader, ChannelList_default as ChannelList, Chat, ChatLayout_default as ChatLayout, ChatMain_default as ChatMain, ForwardModal_default as ForwardModal, LoggedUserDetails_default as LoggedUserDetails, ChannelMessageBox_default as MessageInput, ActiveChannelMessages_default as MessageList, index_default as default, defaultChatLayoutConfig, mergeChatLayoutConfig, setChatAccessToken, setChatBaseURL, setChatClientId, socket_service_default as socketService, useChatContext, useChatController, useStore as useChatStore };
|
|
7297
|
+
export { Channel, ChannelHeader_default as ChannelHeader, ChannelList_default as ChannelList, Chat, ChatLayout_default as ChatLayout, ChatMain_default as ChatMain, ForwardModal_default as ForwardModal, LoggedUserDetails_default as LoggedUserDetails, ChannelMessageBox_default as MessageInput, ActiveChannelMessages_default as MessageList, capitalizeFirst, index_default as default, defaultChatLayoutConfig, downloadFile, getFirstAndLastNameOneCharacter, getResponsiveWidth, getSocketConfig, isEmpty, mergeChatLayoutConfig, setChatAccessToken, setChatBaseURL, setChatClientId, showNotification, socket_service_default as socketService, uniqueList, useChatContext, useChatController, useStore as useChatStore };
|
|
7033
7298
|
//# sourceMappingURL=index.mjs.map
|
|
7034
7299
|
//# sourceMappingURL=index.mjs.map
|