@natoe/colab 0.1.1 → 0.1.2
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/dist/index.js +302 -215
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +118 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React4 = require('react');
|
|
4
4
|
var phoenix = require('phoenix');
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
6
|
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var React4__default = /*#__PURE__*/_interopDefault(React4);
|
|
10
|
+
|
|
7
11
|
// src/components/CollabPanel.tsx
|
|
8
12
|
|
|
9
13
|
// src/core/constants.ts
|
|
@@ -316,25 +320,25 @@ var CollabSocket = class {
|
|
|
316
320
|
this.config = null;
|
|
317
321
|
}
|
|
318
322
|
};
|
|
319
|
-
var CollabContext =
|
|
323
|
+
var CollabContext = React4.createContext(null);
|
|
320
324
|
function useCollab() {
|
|
321
|
-
const context =
|
|
325
|
+
const context = React4.useContext(CollabContext);
|
|
322
326
|
if (!context) {
|
|
323
327
|
throw new Error("useCollab must be used within a <CollabProvider>");
|
|
324
328
|
}
|
|
325
329
|
return context;
|
|
326
330
|
}
|
|
327
331
|
function CollabProvider({ config, apiBaseUrl, children }) {
|
|
328
|
-
const [socket, setSocket] =
|
|
332
|
+
const [socket, setSocket] = React4.useState(() => {
|
|
329
333
|
if (typeof window === "undefined") return null;
|
|
330
334
|
return new CollabSocket();
|
|
331
335
|
});
|
|
332
|
-
const [unreadCounts, setUnreadCounts] =
|
|
333
|
-
const pendingOrderIds =
|
|
334
|
-
const pendingResolvers =
|
|
335
|
-
const previewCache =
|
|
336
|
-
const batchScheduled =
|
|
337
|
-
|
|
336
|
+
const [unreadCounts, setUnreadCounts] = React4.useState({});
|
|
337
|
+
const pendingOrderIds = React4.useRef(/* @__PURE__ */ new Set());
|
|
338
|
+
const pendingResolvers = React4.useRef(/* @__PURE__ */ new Map());
|
|
339
|
+
const previewCache = React4.useRef(/* @__PURE__ */ new Map());
|
|
340
|
+
const batchScheduled = React4.useRef(false);
|
|
341
|
+
React4.useEffect(() => {
|
|
338
342
|
if (typeof window === "undefined") return;
|
|
339
343
|
let s = socket;
|
|
340
344
|
if (!s) {
|
|
@@ -349,14 +353,14 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
349
353
|
s.connect(config);
|
|
350
354
|
}
|
|
351
355
|
}, [config.socketUrl, config.userId]);
|
|
352
|
-
const authHeaders =
|
|
356
|
+
const authHeaders = React4.useCallback(
|
|
353
357
|
() => ({
|
|
354
358
|
Authorization: `Bearer ${config.getToken()}`,
|
|
355
359
|
"Content-Type": "application/json"
|
|
356
360
|
}),
|
|
357
361
|
[config]
|
|
358
362
|
);
|
|
359
|
-
const flushPreviewBatch =
|
|
363
|
+
const flushPreviewBatch = React4.useCallback(async () => {
|
|
360
364
|
const orderIds = Array.from(pendingOrderIds.current);
|
|
361
365
|
const resolvers = new Map(pendingResolvers.current);
|
|
362
366
|
pendingOrderIds.current.clear();
|
|
@@ -386,7 +390,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
386
390
|
});
|
|
387
391
|
}
|
|
388
392
|
}, [apiBaseUrl, authHeaders, config]);
|
|
389
|
-
const requestPreview =
|
|
393
|
+
const requestPreview = React4.useCallback(
|
|
390
394
|
(orderId) => {
|
|
391
395
|
if (previewCache.current.has(orderId)) {
|
|
392
396
|
return Promise.resolve(previewCache.current.get(orderId) ?? null);
|
|
@@ -404,10 +408,10 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
404
408
|
},
|
|
405
409
|
[flushPreviewBatch]
|
|
406
410
|
);
|
|
407
|
-
const invalidatePreview =
|
|
411
|
+
const invalidatePreview = React4.useCallback((orderId) => {
|
|
408
412
|
previewCache.current.delete(orderId);
|
|
409
413
|
}, []);
|
|
410
|
-
const fetchMessages =
|
|
414
|
+
const fetchMessages = React4.useCallback(
|
|
411
415
|
async (conversationId, options = {}) => {
|
|
412
416
|
const limit = options.limit ?? MESSAGES_PAGE_SIZE;
|
|
413
417
|
const params = new URLSearchParams({ limit: String(limit) });
|
|
@@ -423,7 +427,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
423
427
|
},
|
|
424
428
|
[apiBaseUrl, authHeaders]
|
|
425
429
|
);
|
|
426
|
-
const createConversationWithMessage =
|
|
430
|
+
const createConversationWithMessage = React4.useCallback(
|
|
427
431
|
async (orderId, message, options) => {
|
|
428
432
|
if (!orderId || !orderId.trim()) {
|
|
429
433
|
throw {
|
|
@@ -452,7 +456,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
452
456
|
},
|
|
453
457
|
[apiBaseUrl, authHeaders]
|
|
454
458
|
);
|
|
455
|
-
const addParticipant =
|
|
459
|
+
const addParticipant = React4.useCallback(
|
|
456
460
|
async (conversationId, userId) => {
|
|
457
461
|
const response = await fetch(
|
|
458
462
|
`${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/participants`,
|
|
@@ -468,7 +472,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
468
472
|
},
|
|
469
473
|
[apiBaseUrl, authHeaders]
|
|
470
474
|
);
|
|
471
|
-
const removeParticipant =
|
|
475
|
+
const removeParticipant = React4.useCallback(
|
|
472
476
|
async (conversationId, userId) => {
|
|
473
477
|
const response = await fetch(
|
|
474
478
|
`${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/participants/${userId}`,
|
|
@@ -483,7 +487,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
483
487
|
},
|
|
484
488
|
[apiBaseUrl, authHeaders]
|
|
485
489
|
);
|
|
486
|
-
const fetchConversationList =
|
|
490
|
+
const fetchConversationList = React4.useCallback(async () => {
|
|
487
491
|
const response = await fetch(`${apiBaseUrl}/api/natoe-colab/conversations`, {
|
|
488
492
|
headers: authHeaders()
|
|
489
493
|
});
|
|
@@ -492,7 +496,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
492
496
|
}
|
|
493
497
|
return snakeToCamel(await response.json());
|
|
494
498
|
}, [apiBaseUrl, authHeaders]);
|
|
495
|
-
const pinMessage =
|
|
499
|
+
const pinMessage = React4.useCallback(
|
|
496
500
|
async (conversationId, messageId) => {
|
|
497
501
|
const response = await fetch(
|
|
498
502
|
`${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/messages/${messageId}/pin`,
|
|
@@ -508,7 +512,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
508
512
|
},
|
|
509
513
|
[apiBaseUrl, authHeaders]
|
|
510
514
|
);
|
|
511
|
-
const unpinMessage =
|
|
515
|
+
const unpinMessage = React4.useCallback(
|
|
512
516
|
async (conversationId, messageId) => {
|
|
513
517
|
const response = await fetch(
|
|
514
518
|
`${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/messages/${messageId}/pin`,
|
|
@@ -523,7 +527,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
523
527
|
},
|
|
524
528
|
[apiBaseUrl, authHeaders]
|
|
525
529
|
);
|
|
526
|
-
const fetchPinnedMessages =
|
|
530
|
+
const fetchPinnedMessages = React4.useCallback(
|
|
527
531
|
async (conversationId) => {
|
|
528
532
|
const response = await fetch(
|
|
529
533
|
`${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/pinned`,
|
|
@@ -587,25 +591,25 @@ function useConversation({
|
|
|
587
591
|
pinMessage: pinMessageApi,
|
|
588
592
|
unpinMessage: unpinMessageApi
|
|
589
593
|
} = useCollab();
|
|
590
|
-
const [conversation, setConversation] =
|
|
591
|
-
const [messages, setMessages] =
|
|
592
|
-
const [pinnedMessages, setPinnedMessages] =
|
|
593
|
-
const [participants, setParticipants] =
|
|
594
|
-
const [typingUsers, setTypingUsers] =
|
|
595
|
-
const [isLoading, setIsLoading] =
|
|
596
|
-
const [hasMore, setHasMore] =
|
|
597
|
-
const [error, setError] =
|
|
598
|
-
const [isConnected, setIsConnected] =
|
|
599
|
-
const [replyTo, setReplyTo] =
|
|
600
|
-
const joinedConversationId =
|
|
601
|
-
const typingTimers =
|
|
602
|
-
const ensureConversationInFlight =
|
|
603
|
-
const markedReadIdsRef =
|
|
604
|
-
const buildName =
|
|
594
|
+
const [conversation, setConversation] = React4.useState(null);
|
|
595
|
+
const [messages, setMessages] = React4.useState([]);
|
|
596
|
+
const [pinnedMessages, setPinnedMessages] = React4.useState([]);
|
|
597
|
+
const [participants, setParticipants] = React4.useState([]);
|
|
598
|
+
const [typingUsers, setTypingUsers] = React4.useState([]);
|
|
599
|
+
const [isLoading, setIsLoading] = React4.useState(true);
|
|
600
|
+
const [hasMore, setHasMore] = React4.useState(true);
|
|
601
|
+
const [error, setError] = React4.useState(null);
|
|
602
|
+
const [isConnected, setIsConnected] = React4.useState(false);
|
|
603
|
+
const [replyTo, setReplyTo] = React4.useState(null);
|
|
604
|
+
const joinedConversationId = React4.useRef(null);
|
|
605
|
+
const typingTimers = React4.useRef(/* @__PURE__ */ new Map());
|
|
606
|
+
const ensureConversationInFlight = React4.useRef(null);
|
|
607
|
+
const markedReadIdsRef = React4.useRef(/* @__PURE__ */ new Set());
|
|
608
|
+
const buildName = React4.useCallback(
|
|
605
609
|
() => buildChannelName(patientData),
|
|
606
610
|
[patientData]
|
|
607
611
|
);
|
|
608
|
-
const joinChannel =
|
|
612
|
+
const joinChannel = React4.useCallback(
|
|
609
613
|
(conv) => {
|
|
610
614
|
if (joinedConversationId.current === conv.id) return;
|
|
611
615
|
socket.joinConversation(conv.id, {
|
|
@@ -688,7 +692,7 @@ function useConversation({
|
|
|
688
692
|
},
|
|
689
693
|
[socket, config.userId]
|
|
690
694
|
);
|
|
691
|
-
|
|
695
|
+
React4.useEffect(() => {
|
|
692
696
|
let cancelled = false;
|
|
693
697
|
const init = async () => {
|
|
694
698
|
setIsLoading(true);
|
|
@@ -757,7 +761,7 @@ function useConversation({
|
|
|
757
761
|
setIsConnected(false);
|
|
758
762
|
};
|
|
759
763
|
}, [orderId]);
|
|
760
|
-
const ensureConversation =
|
|
764
|
+
const ensureConversation = React4.useCallback(
|
|
761
765
|
async (payload) => {
|
|
762
766
|
if (conversation) return { conv: conversation, persistedByCreate: false };
|
|
763
767
|
if (ensureConversationInFlight.current) {
|
|
@@ -811,7 +815,7 @@ function useConversation({
|
|
|
811
815
|
fetchMessages
|
|
812
816
|
]
|
|
813
817
|
);
|
|
814
|
-
const sendMessage =
|
|
818
|
+
const sendMessage = React4.useCallback(
|
|
815
819
|
async (body) => {
|
|
816
820
|
const payload = {
|
|
817
821
|
body,
|
|
@@ -855,7 +859,7 @@ function useConversation({
|
|
|
855
859
|
},
|
|
856
860
|
[ensureConversation, socket, replyTo, config]
|
|
857
861
|
);
|
|
858
|
-
const sendAudioMessage =
|
|
862
|
+
const sendAudioMessage = React4.useCallback(
|
|
859
863
|
async (audioBlob, duration) => {
|
|
860
864
|
if (!config.onUploadFile) return;
|
|
861
865
|
const url = await config.onUploadFile(audioBlob, "voice-message.webm");
|
|
@@ -871,7 +875,7 @@ function useConversation({
|
|
|
871
875
|
},
|
|
872
876
|
[config, ensureConversation, socket]
|
|
873
877
|
);
|
|
874
|
-
const sendFileMessage =
|
|
878
|
+
const sendFileMessage = React4.useCallback(
|
|
875
879
|
async (file) => {
|
|
876
880
|
if (!config.onUploadFile) return;
|
|
877
881
|
const url = await config.onUploadFile(file, file.name);
|
|
@@ -888,7 +892,7 @@ function useConversation({
|
|
|
888
892
|
},
|
|
889
893
|
[config, ensureConversation, socket]
|
|
890
894
|
);
|
|
891
|
-
const sendDeepLink =
|
|
895
|
+
const sendDeepLink = React4.useCallback(
|
|
892
896
|
async (path, label) => {
|
|
893
897
|
const payload = {
|
|
894
898
|
body: label,
|
|
@@ -901,14 +905,14 @@ function useConversation({
|
|
|
901
905
|
},
|
|
902
906
|
[ensureConversation, socket]
|
|
903
907
|
);
|
|
904
|
-
const sendTypingIndicator =
|
|
908
|
+
const sendTypingIndicator = React4.useCallback(
|
|
905
909
|
(isTyping) => {
|
|
906
910
|
if (!conversation || !isConnected) return;
|
|
907
911
|
socket.sendTyping(conversation.id, isTyping);
|
|
908
912
|
},
|
|
909
913
|
[conversation, isConnected, socket]
|
|
910
914
|
);
|
|
911
|
-
const markAsRead =
|
|
915
|
+
const markAsRead = React4.useCallback(
|
|
912
916
|
(messageId) => {
|
|
913
917
|
if (!conversation || !isConnected) return;
|
|
914
918
|
if (markedReadIdsRef.current.has(messageId)) return;
|
|
@@ -917,7 +921,7 @@ function useConversation({
|
|
|
917
921
|
},
|
|
918
922
|
[conversation, isConnected, socket]
|
|
919
923
|
);
|
|
920
|
-
const loadMoreMessages =
|
|
924
|
+
const loadMoreMessages = React4.useCallback(async () => {
|
|
921
925
|
if (!conversation || !hasMore || isLoading) return;
|
|
922
926
|
const oldestMessage = messages[0];
|
|
923
927
|
const before = oldestMessage?.id;
|
|
@@ -936,7 +940,7 @@ function useConversation({
|
|
|
936
940
|
setIsLoading(false);
|
|
937
941
|
}
|
|
938
942
|
}, [conversation, hasMore, isLoading, messages, fetchMessages, config]);
|
|
939
|
-
const pinMessage =
|
|
943
|
+
const pinMessage = React4.useCallback(
|
|
940
944
|
async (messageId) => {
|
|
941
945
|
if (!conversation) return;
|
|
942
946
|
try {
|
|
@@ -951,7 +955,7 @@ function useConversation({
|
|
|
951
955
|
},
|
|
952
956
|
[conversation, pinMessageApi, config]
|
|
953
957
|
);
|
|
954
|
-
const unpinMessage =
|
|
958
|
+
const unpinMessage = React4.useCallback(
|
|
955
959
|
async (messageId) => {
|
|
956
960
|
if (!conversation) return;
|
|
957
961
|
try {
|
|
@@ -991,10 +995,10 @@ function useConversation({
|
|
|
991
995
|
}
|
|
992
996
|
function useChannelSettings({ conversationId }) {
|
|
993
997
|
const { socket, config } = useCollab();
|
|
994
|
-
const [isUpdating, setIsUpdating] =
|
|
995
|
-
const [error, setError] =
|
|
998
|
+
const [isUpdating, setIsUpdating] = React4.useState(false);
|
|
999
|
+
const [error, setError] = React4.useState(null);
|
|
996
1000
|
const isAdmin = config.userRole === "admin";
|
|
997
|
-
const withLoading =
|
|
1001
|
+
const withLoading = React4.useCallback(
|
|
998
1002
|
async (operation) => {
|
|
999
1003
|
if (!conversationId) return;
|
|
1000
1004
|
setIsUpdating(true);
|
|
@@ -1013,25 +1017,25 @@ function useChannelSettings({ conversationId }) {
|
|
|
1013
1017
|
},
|
|
1014
1018
|
[conversationId, config]
|
|
1015
1019
|
);
|
|
1016
|
-
const inviteUser =
|
|
1020
|
+
const inviteUser = React4.useCallback(
|
|
1017
1021
|
async (payload) => {
|
|
1018
1022
|
await withLoading(() => socket.inviteUser(conversationId, payload));
|
|
1019
1023
|
},
|
|
1020
1024
|
[conversationId, socket, withLoading]
|
|
1021
1025
|
);
|
|
1022
|
-
const removeUser =
|
|
1026
|
+
const removeUser = React4.useCallback(
|
|
1023
1027
|
async (userId) => {
|
|
1024
1028
|
await withLoading(() => socket.removeUser(conversationId, userId));
|
|
1025
1029
|
},
|
|
1026
1030
|
[conversationId, socket, withLoading]
|
|
1027
1031
|
);
|
|
1028
|
-
const leaveChannel =
|
|
1032
|
+
const leaveChannel = React4.useCallback(async () => {
|
|
1029
1033
|
await withLoading(async () => {
|
|
1030
1034
|
await socket.removeUser(conversationId, config.userId);
|
|
1031
1035
|
socket.leaveConversation(conversationId);
|
|
1032
1036
|
});
|
|
1033
1037
|
}, [conversationId, socket, config.userId, withLoading]);
|
|
1034
|
-
const deleteChannel =
|
|
1038
|
+
const deleteChannel = React4.useCallback(async () => {
|
|
1035
1039
|
if (!isAdmin) {
|
|
1036
1040
|
const err = {
|
|
1037
1041
|
code: "FORBIDDEN",
|
|
@@ -1043,7 +1047,7 @@ function useChannelSettings({ conversationId }) {
|
|
|
1043
1047
|
}
|
|
1044
1048
|
await withLoading(() => socket.deleteChannel(conversationId));
|
|
1045
1049
|
}, [conversationId, socket, isAdmin, config, withLoading]);
|
|
1046
|
-
const updateChannel =
|
|
1050
|
+
const updateChannel = React4.useCallback(
|
|
1047
1051
|
async (payload) => {
|
|
1048
1052
|
await withLoading(() => socket.updateChannel(conversationId, payload));
|
|
1049
1053
|
},
|
|
@@ -1062,7 +1066,7 @@ function useChannelSettings({ conversationId }) {
|
|
|
1062
1066
|
}
|
|
1063
1067
|
function useDeepLinks() {
|
|
1064
1068
|
const { config } = useCollab();
|
|
1065
|
-
const parseDeepLink =
|
|
1069
|
+
const parseDeepLink = React4.useCallback((link) => {
|
|
1066
1070
|
if (!link.startsWith(DEEP_LINK_PREFIX)) return null;
|
|
1067
1071
|
const path = link.slice(DEEP_LINK_PREFIX.length);
|
|
1068
1072
|
const parts = path.split("/").filter(Boolean);
|
|
@@ -1074,7 +1078,7 @@ function useDeepLinks() {
|
|
|
1074
1078
|
id: parts[1]
|
|
1075
1079
|
};
|
|
1076
1080
|
}, []);
|
|
1077
|
-
const handleDeepLink =
|
|
1081
|
+
const handleDeepLink = React4.useCallback(
|
|
1078
1082
|
(link) => {
|
|
1079
1083
|
const parsed = parseDeepLink(link);
|
|
1080
1084
|
if (!parsed) return;
|
|
@@ -1088,10 +1092,10 @@ function useDeepLinks() {
|
|
|
1088
1092
|
},
|
|
1089
1093
|
[parseDeepLink, config]
|
|
1090
1094
|
);
|
|
1091
|
-
const containsDeepLink =
|
|
1095
|
+
const containsDeepLink = React4.useCallback((text) => {
|
|
1092
1096
|
return text.includes(DEEP_LINK_PREFIX);
|
|
1093
1097
|
}, []);
|
|
1094
|
-
const extractDeepLinks =
|
|
1098
|
+
const extractDeepLinks = React4.useCallback(
|
|
1095
1099
|
(text) => {
|
|
1096
1100
|
const regex = new RegExp(`${escapeRegex(DEEP_LINK_PREFIX)}[\\w/.-]+`, "g");
|
|
1097
1101
|
const matches = text.match(regex) || [];
|
|
@@ -1281,7 +1285,7 @@ function SeenByIndicator({
|
|
|
1281
1285
|
senderId,
|
|
1282
1286
|
className
|
|
1283
1287
|
}) {
|
|
1284
|
-
const [expanded, setExpanded] =
|
|
1288
|
+
const [expanded, setExpanded] = React4.useState(false);
|
|
1285
1289
|
if (senderId !== currentUserId) return null;
|
|
1286
1290
|
const seenByOthers = readBy.filter((id) => id !== currentUserId && id !== senderId).map((id) => participants.find((p) => p.userId === id)).filter((p) => !!p);
|
|
1287
1291
|
if (seenByOthers.length === 0) return null;
|
|
@@ -1441,7 +1445,7 @@ function MessageBubble({
|
|
|
1441
1445
|
pinDisabled = false,
|
|
1442
1446
|
className
|
|
1443
1447
|
}) {
|
|
1444
|
-
const [isHovered, setIsHovered] =
|
|
1448
|
+
const [isHovered, setIsHovered] = React4.useState(false);
|
|
1445
1449
|
if (message.type === "system") {
|
|
1446
1450
|
return /* @__PURE__ */ jsxRuntime.jsx(SystemBubble, { message });
|
|
1447
1451
|
}
|
|
@@ -1757,7 +1761,83 @@ var styles5 = {
|
|
|
1757
1761
|
color: "#d1d5db"
|
|
1758
1762
|
}
|
|
1759
1763
|
};
|
|
1760
|
-
|
|
1764
|
+
|
|
1765
|
+
// src/core/dateLabels.ts
|
|
1766
|
+
var SHORT_MONTHS = [
|
|
1767
|
+
"Jan",
|
|
1768
|
+
"Feb",
|
|
1769
|
+
"Mar",
|
|
1770
|
+
"Apr",
|
|
1771
|
+
"May",
|
|
1772
|
+
"Jun",
|
|
1773
|
+
"Jul",
|
|
1774
|
+
"Aug",
|
|
1775
|
+
"Sep",
|
|
1776
|
+
"Oct",
|
|
1777
|
+
"Nov",
|
|
1778
|
+
"Dec"
|
|
1779
|
+
];
|
|
1780
|
+
var LONG_WEEKDAYS = [
|
|
1781
|
+
"Sunday",
|
|
1782
|
+
"Monday",
|
|
1783
|
+
"Tuesday",
|
|
1784
|
+
"Wednesday",
|
|
1785
|
+
"Thursday",
|
|
1786
|
+
"Friday",
|
|
1787
|
+
"Saturday"
|
|
1788
|
+
];
|
|
1789
|
+
var ONE_DAY_MS = 864e5;
|
|
1790
|
+
function parseSafe(iso) {
|
|
1791
|
+
const d = new Date(iso);
|
|
1792
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
1793
|
+
}
|
|
1794
|
+
function startOfDay(d) {
|
|
1795
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
1796
|
+
}
|
|
1797
|
+
function daysBetween(a, b) {
|
|
1798
|
+
return Math.floor((startOfDay(a).getTime() - startOfDay(b).getTime()) / ONE_DAY_MS);
|
|
1799
|
+
}
|
|
1800
|
+
function formatFullDate(d) {
|
|
1801
|
+
return `${d.getDate()} ${SHORT_MONTHS[d.getMonth()]} ${d.getFullYear()}`;
|
|
1802
|
+
}
|
|
1803
|
+
function formatLocaleTime(d) {
|
|
1804
|
+
try {
|
|
1805
|
+
return d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
|
1806
|
+
} catch {
|
|
1807
|
+
const h = d.getHours();
|
|
1808
|
+
const m = d.getMinutes().toString().padStart(2, "0");
|
|
1809
|
+
const suffix = h >= 12 ? "PM" : "AM";
|
|
1810
|
+
const h12 = (h + 11) % 12 + 1;
|
|
1811
|
+
return `${h12}:${m} ${suffix}`;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
function formatInboxTimestamp(iso) {
|
|
1815
|
+
if (!iso) return "";
|
|
1816
|
+
const d = parseSafe(iso);
|
|
1817
|
+
if (!d) return "";
|
|
1818
|
+
const diffDays = daysBetween(/* @__PURE__ */ new Date(), d);
|
|
1819
|
+
if (diffDays === 0) return formatLocaleTime(d);
|
|
1820
|
+
if (diffDays === 1) return "Yesterday";
|
|
1821
|
+
if (diffDays > 1 && diffDays < 7) return LONG_WEEKDAYS[d.getDay()];
|
|
1822
|
+
return formatFullDate(d);
|
|
1823
|
+
}
|
|
1824
|
+
function formatDayDivider(iso) {
|
|
1825
|
+
if (!iso) return "";
|
|
1826
|
+
const d = parseSafe(iso);
|
|
1827
|
+
if (!d) return "";
|
|
1828
|
+
const diffDays = daysBetween(/* @__PURE__ */ new Date(), d);
|
|
1829
|
+
if (diffDays === 0) return "Today";
|
|
1830
|
+
if (diffDays === 1) return "Yesterday";
|
|
1831
|
+
if (diffDays > 1 && diffDays < 7) return LONG_WEEKDAYS[d.getDay()];
|
|
1832
|
+
return formatFullDate(d);
|
|
1833
|
+
}
|
|
1834
|
+
function dayBucketKey(iso) {
|
|
1835
|
+
if (!iso) return "";
|
|
1836
|
+
const d = parseSafe(iso);
|
|
1837
|
+
if (!d) return "";
|
|
1838
|
+
return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
|
|
1839
|
+
}
|
|
1840
|
+
var MessageList = React4.forwardRef(function MessageList2({
|
|
1761
1841
|
messages,
|
|
1762
1842
|
currentUserId,
|
|
1763
1843
|
participants = [],
|
|
@@ -1775,10 +1855,10 @@ var MessageList = react.forwardRef(function MessageList2({
|
|
|
1775
1855
|
pinDisabled,
|
|
1776
1856
|
className
|
|
1777
1857
|
}, ref) {
|
|
1778
|
-
const containerRef =
|
|
1779
|
-
const bottomRef =
|
|
1780
|
-
const prevMessageCount =
|
|
1781
|
-
|
|
1858
|
+
const containerRef = React4.useRef(null);
|
|
1859
|
+
const bottomRef = React4.useRef(null);
|
|
1860
|
+
const prevMessageCount = React4.useRef(messages.length);
|
|
1861
|
+
React4.useImperativeHandle(
|
|
1782
1862
|
ref,
|
|
1783
1863
|
() => ({
|
|
1784
1864
|
scrollToMessage: (messageId) => {
|
|
@@ -1795,7 +1875,7 @@ var MessageList = react.forwardRef(function MessageList2({
|
|
|
1795
1875
|
}),
|
|
1796
1876
|
[]
|
|
1797
1877
|
);
|
|
1798
|
-
|
|
1878
|
+
React4.useEffect(() => {
|
|
1799
1879
|
if (messages.length > prevMessageCount.current) {
|
|
1800
1880
|
const lastMessage = messages[messages.length - 1];
|
|
1801
1881
|
const isOwnMessage = lastMessage?.senderId === currentUserId;
|
|
@@ -1805,7 +1885,7 @@ var MessageList = react.forwardRef(function MessageList2({
|
|
|
1805
1885
|
}
|
|
1806
1886
|
prevMessageCount.current = messages.length;
|
|
1807
1887
|
}, [messages, currentUserId]);
|
|
1808
|
-
|
|
1888
|
+
React4.useEffect(() => {
|
|
1809
1889
|
if (!onMessageVisible || !containerRef.current) return;
|
|
1810
1890
|
const observer = new IntersectionObserver(
|
|
1811
1891
|
(entries) => {
|
|
@@ -1822,7 +1902,7 @@ var MessageList = react.forwardRef(function MessageList2({
|
|
|
1822
1902
|
messageElements.forEach((el) => observer.observe(el));
|
|
1823
1903
|
return () => observer.disconnect();
|
|
1824
1904
|
}, [messages, onMessageVisible]);
|
|
1825
|
-
const handleScroll =
|
|
1905
|
+
const handleScroll = React4.useCallback(() => {
|
|
1826
1906
|
if (!containerRef.current || !hasMore || isLoading) return;
|
|
1827
1907
|
if (containerRef.current.scrollTop < 50) {
|
|
1828
1908
|
onLoadMore();
|
|
@@ -1842,22 +1922,30 @@ var MessageList = react.forwardRef(function MessageList2({
|
|
|
1842
1922
|
/* @__PURE__ */ jsxRuntime.jsx("p", { style: styles6.emptyTitle, children: "No messages yet" }),
|
|
1843
1923
|
/* @__PURE__ */ jsxRuntime.jsx("p", { style: styles6.emptySubtitle, children: "Start the conversation about this study" })
|
|
1844
1924
|
] }),
|
|
1845
|
-
messages.map((message) =>
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1925
|
+
messages.map((message, index) => {
|
|
1926
|
+
const currentBucket = dayBucketKey(message.insertedAt);
|
|
1927
|
+
const prevBucket = index === 0 ? null : dayBucketKey(messages[index - 1].insertedAt);
|
|
1928
|
+
const showDivider = currentBucket && currentBucket !== prevBucket;
|
|
1929
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
|
|
1930
|
+
showDivider && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayDividerPill, children: formatDayDivider(message.insertedAt) }) }),
|
|
1931
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "data-message-id": message.id, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1932
|
+
MessageBubble,
|
|
1933
|
+
{
|
|
1934
|
+
message,
|
|
1935
|
+
isOwn: message.senderId === currentUserId,
|
|
1936
|
+
participants,
|
|
1937
|
+
currentUserId,
|
|
1938
|
+
showSeenBy,
|
|
1939
|
+
onDeepLinkClick,
|
|
1940
|
+
onReply,
|
|
1941
|
+
onPin,
|
|
1942
|
+
onUnpin,
|
|
1943
|
+
onCopy,
|
|
1944
|
+
pinDisabled
|
|
1945
|
+
}
|
|
1946
|
+
) })
|
|
1947
|
+
] }, message.id);
|
|
1948
|
+
}),
|
|
1861
1949
|
typingUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles6.typingIndicator, children: [
|
|
1862
1950
|
typingUsers.map((t) => t.userName ?? "Someone").join(", "),
|
|
1863
1951
|
typingUsers.length === 1 ? " is " : " are ",
|
|
@@ -1919,6 +2007,22 @@ var styles6 = {
|
|
|
1919
2007
|
fontSize: "12px",
|
|
1920
2008
|
color: "#9ca3af",
|
|
1921
2009
|
fontStyle: "italic"
|
|
2010
|
+
},
|
|
2011
|
+
dayDivider: {
|
|
2012
|
+
display: "flex",
|
|
2013
|
+
justifyContent: "center",
|
|
2014
|
+
margin: "12px 0 8px"
|
|
2015
|
+
},
|
|
2016
|
+
dayDividerPill: {
|
|
2017
|
+
display: "inline-block",
|
|
2018
|
+
padding: "4px 12px",
|
|
2019
|
+
fontSize: "12px",
|
|
2020
|
+
fontWeight: 500,
|
|
2021
|
+
color: "#475569",
|
|
2022
|
+
backgroundColor: "#f1f5f9",
|
|
2023
|
+
border: "1px solid #e2e8f0",
|
|
2024
|
+
borderRadius: "12px",
|
|
2025
|
+
letterSpacing: "0.2px"
|
|
1922
2026
|
}
|
|
1923
2027
|
};
|
|
1924
2028
|
function ReplyPreview({ message, onCancel, className }) {
|
|
@@ -2025,14 +2129,14 @@ function MessageInput({
|
|
|
2025
2129
|
placeholder = "Type a message...",
|
|
2026
2130
|
className
|
|
2027
2131
|
}) {
|
|
2028
|
-
const [text, setText] =
|
|
2029
|
-
const [isSending, setIsSending] =
|
|
2030
|
-
const [fileError, setFileError] =
|
|
2031
|
-
const fileInputRef =
|
|
2032
|
-
const typingTimeoutRef =
|
|
2033
|
-
const isTypingRef =
|
|
2034
|
-
const sendingRef =
|
|
2035
|
-
const handleTyping =
|
|
2132
|
+
const [text, setText] = React4.useState("");
|
|
2133
|
+
const [isSending, setIsSending] = React4.useState(false);
|
|
2134
|
+
const [fileError, setFileError] = React4.useState(null);
|
|
2135
|
+
const fileInputRef = React4.useRef(null);
|
|
2136
|
+
const typingTimeoutRef = React4.useRef(null);
|
|
2137
|
+
const isTypingRef = React4.useRef(false);
|
|
2138
|
+
const sendingRef = React4.useRef(false);
|
|
2139
|
+
const handleTyping = React4.useCallback(() => {
|
|
2036
2140
|
if (!isTypingRef.current) {
|
|
2037
2141
|
isTypingRef.current = true;
|
|
2038
2142
|
onTyping(true);
|
|
@@ -2045,12 +2149,12 @@ function MessageInput({
|
|
|
2045
2149
|
onTyping(false);
|
|
2046
2150
|
}, 2e3);
|
|
2047
2151
|
}, [onTyping]);
|
|
2048
|
-
|
|
2152
|
+
React4.useEffect(() => {
|
|
2049
2153
|
return () => {
|
|
2050
2154
|
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
|
|
2051
2155
|
};
|
|
2052
2156
|
}, []);
|
|
2053
|
-
const handleSendText =
|
|
2157
|
+
const handleSendText = React4.useCallback(async () => {
|
|
2054
2158
|
const trimmed = text.trim();
|
|
2055
2159
|
if (!trimmed || sendingRef.current) return;
|
|
2056
2160
|
sendingRef.current = true;
|
|
@@ -2071,7 +2175,7 @@ function MessageInput({
|
|
|
2071
2175
|
setIsSending(false);
|
|
2072
2176
|
}
|
|
2073
2177
|
}, [text, onSendText, onTyping]);
|
|
2074
|
-
const handleKeyDown =
|
|
2178
|
+
const handleKeyDown = React4.useCallback(
|
|
2075
2179
|
(e) => {
|
|
2076
2180
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
2077
2181
|
e.preventDefault();
|
|
@@ -2080,7 +2184,7 @@ function MessageInput({
|
|
|
2080
2184
|
},
|
|
2081
2185
|
[handleSendText]
|
|
2082
2186
|
);
|
|
2083
|
-
const handleFileSelect =
|
|
2187
|
+
const handleFileSelect = React4.useCallback(
|
|
2084
2188
|
async (e) => {
|
|
2085
2189
|
const file = e.target.files?.[0];
|
|
2086
2190
|
if (!file) return;
|
|
@@ -2376,13 +2480,13 @@ function ChannelSettings({
|
|
|
2376
2480
|
onClose,
|
|
2377
2481
|
className
|
|
2378
2482
|
}) {
|
|
2379
|
-
const [editingName, setEditingName] =
|
|
2380
|
-
const [nameValue, setNameValue] =
|
|
2381
|
-
const [showInvite, setShowInvite] =
|
|
2382
|
-
const [inviteId, setInviteId] =
|
|
2383
|
-
const [inviteName, setInviteName] =
|
|
2384
|
-
const [inviteRole, setInviteRole] =
|
|
2385
|
-
const [confirmDelete, setConfirmDelete] =
|
|
2483
|
+
const [editingName, setEditingName] = React4.useState(false);
|
|
2484
|
+
const [nameValue, setNameValue] = React4.useState(channelName);
|
|
2485
|
+
const [showInvite, setShowInvite] = React4.useState(false);
|
|
2486
|
+
const [inviteId, setInviteId] = React4.useState("");
|
|
2487
|
+
const [inviteName, setInviteName] = React4.useState("");
|
|
2488
|
+
const [inviteRole, setInviteRole] = React4.useState("radiologist");
|
|
2489
|
+
const [confirmDelete, setConfirmDelete] = React4.useState(false);
|
|
2386
2490
|
const handleSaveName = async () => {
|
|
2387
2491
|
if (nameValue.trim() && nameValue !== channelName) {
|
|
2388
2492
|
await onUpdateChannel({ name: nameValue.trim() });
|
|
@@ -2673,7 +2777,7 @@ function PinnedMessagesBar({
|
|
|
2673
2777
|
onUnpin,
|
|
2674
2778
|
className
|
|
2675
2779
|
}) {
|
|
2676
|
-
const [index, setIndex] =
|
|
2780
|
+
const [index, setIndex] = React4.useState(0);
|
|
2677
2781
|
if (pinnedMessages.length === 0) return null;
|
|
2678
2782
|
const safeIndex = Math.min(index, pinnedMessages.length - 1);
|
|
2679
2783
|
const current = pinnedMessages[safeIndex];
|
|
@@ -2832,8 +2936,8 @@ function CollabPanel({
|
|
|
2832
2936
|
style
|
|
2833
2937
|
}) {
|
|
2834
2938
|
const { config } = useCollab();
|
|
2835
|
-
const [showSettings, setShowSettings] =
|
|
2836
|
-
const messageListRef =
|
|
2939
|
+
const [showSettings, setShowSettings] = React4.useState(false);
|
|
2940
|
+
const messageListRef = React4.useRef(null);
|
|
2837
2941
|
const {
|
|
2838
2942
|
conversation,
|
|
2839
2943
|
messages,
|
|
@@ -3005,17 +3109,17 @@ function CollabPopup({
|
|
|
3005
3109
|
height = 520,
|
|
3006
3110
|
className
|
|
3007
3111
|
}) {
|
|
3008
|
-
const [position, setPosition] =
|
|
3112
|
+
const [position, setPosition] = React4.useState(
|
|
3009
3113
|
initialPosition || {
|
|
3010
3114
|
x: window.innerWidth - width - 24,
|
|
3011
3115
|
y: Math.max(24, window.innerHeight - height - 104)
|
|
3012
3116
|
}
|
|
3013
3117
|
);
|
|
3014
|
-
const [isDragging, setIsDragging] =
|
|
3015
|
-
const [isMinimized, setIsMinimized] =
|
|
3016
|
-
const dragOffset =
|
|
3017
|
-
const containerRef =
|
|
3018
|
-
const handleMouseDown =
|
|
3118
|
+
const [isDragging, setIsDragging] = React4.useState(false);
|
|
3119
|
+
const [isMinimized, setIsMinimized] = React4.useState(false);
|
|
3120
|
+
const dragOffset = React4.useRef({ x: 0, y: 0 });
|
|
3121
|
+
const containerRef = React4.useRef(null);
|
|
3122
|
+
const handleMouseDown = React4.useCallback(
|
|
3019
3123
|
(e) => {
|
|
3020
3124
|
if (!e.target.closest("[data-drag-handle]")) return;
|
|
3021
3125
|
setIsDragging(true);
|
|
@@ -3027,7 +3131,7 @@ function CollabPopup({
|
|
|
3027
3131
|
},
|
|
3028
3132
|
[position]
|
|
3029
3133
|
);
|
|
3030
|
-
|
|
3134
|
+
React4.useEffect(() => {
|
|
3031
3135
|
if (!isOpen) return;
|
|
3032
3136
|
const handleKey = (e) => {
|
|
3033
3137
|
if (e.key === "Escape") {
|
|
@@ -3037,7 +3141,7 @@ function CollabPopup({
|
|
|
3037
3141
|
window.addEventListener("keydown", handleKey);
|
|
3038
3142
|
return () => window.removeEventListener("keydown", handleKey);
|
|
3039
3143
|
}, [isOpen, onClose]);
|
|
3040
|
-
|
|
3144
|
+
React4.useEffect(() => {
|
|
3041
3145
|
if (!isDragging) return;
|
|
3042
3146
|
const handleMouseMove = (e) => {
|
|
3043
3147
|
setPosition({
|
|
@@ -3160,25 +3264,25 @@ function useInlineCollab({
|
|
|
3160
3264
|
invalidatePreview,
|
|
3161
3265
|
createConversationWithMessage
|
|
3162
3266
|
} = useCollab();
|
|
3163
|
-
const [preview, setPreview] =
|
|
3164
|
-
const [messages, setMessages] =
|
|
3165
|
-
const [participants, setParticipants] =
|
|
3166
|
-
const [unreadCount, setUnreadCount] =
|
|
3167
|
-
const [isLoading, setIsLoading] =
|
|
3168
|
-
const [isSubscribed, setIsSubscribed] =
|
|
3169
|
-
const [error, setError] =
|
|
3170
|
-
const elementRef =
|
|
3171
|
-
const observerRef =
|
|
3172
|
-
const subscribedConversationIdRef =
|
|
3173
|
-
const trimToLimit =
|
|
3267
|
+
const [preview, setPreview] = React4.useState(null);
|
|
3268
|
+
const [messages, setMessages] = React4.useState([]);
|
|
3269
|
+
const [participants, setParticipants] = React4.useState([]);
|
|
3270
|
+
const [unreadCount, setUnreadCount] = React4.useState(0);
|
|
3271
|
+
const [isLoading, setIsLoading] = React4.useState(true);
|
|
3272
|
+
const [isSubscribed, setIsSubscribed] = React4.useState(false);
|
|
3273
|
+
const [error, setError] = React4.useState(null);
|
|
3274
|
+
const elementRef = React4.useRef(null);
|
|
3275
|
+
const observerRef = React4.useRef(null);
|
|
3276
|
+
const subscribedConversationIdRef = React4.useRef(null);
|
|
3277
|
+
const trimToLimit = React4.useCallback(
|
|
3174
3278
|
(msgs) => msgs.length > messageLimit ? msgs.slice(msgs.length - messageLimit) : msgs,
|
|
3175
3279
|
[messageLimit]
|
|
3176
3280
|
);
|
|
3177
|
-
const buildName =
|
|
3281
|
+
const buildName = React4.useCallback(
|
|
3178
3282
|
() => buildChannelName(patientData),
|
|
3179
3283
|
[patientData]
|
|
3180
3284
|
);
|
|
3181
|
-
|
|
3285
|
+
React4.useEffect(() => {
|
|
3182
3286
|
let cancelled = false;
|
|
3183
3287
|
const load = async () => {
|
|
3184
3288
|
setIsLoading(true);
|
|
@@ -3204,7 +3308,7 @@ function useInlineCollab({
|
|
|
3204
3308
|
cancelled = true;
|
|
3205
3309
|
};
|
|
3206
3310
|
}, [orderId, requestPreview, trimToLimit]);
|
|
3207
|
-
const subscribe =
|
|
3311
|
+
const subscribe = React4.useCallback(
|
|
3208
3312
|
(conversationId) => {
|
|
3209
3313
|
if (subscribedConversationIdRef.current === conversationId) return;
|
|
3210
3314
|
socket.joinConversation(conversationId, {
|
|
@@ -3228,7 +3332,7 @@ function useInlineCollab({
|
|
|
3228
3332
|
},
|
|
3229
3333
|
[socket, trimToLimit, config.userId]
|
|
3230
3334
|
);
|
|
3231
|
-
const unsubscribe =
|
|
3335
|
+
const unsubscribe = React4.useCallback(() => {
|
|
3232
3336
|
const convId = subscribedConversationIdRef.current;
|
|
3233
3337
|
if (convId) {
|
|
3234
3338
|
socket.leaveConversation(convId);
|
|
@@ -3236,7 +3340,7 @@ function useInlineCollab({
|
|
|
3236
3340
|
setIsSubscribed(false);
|
|
3237
3341
|
}
|
|
3238
3342
|
}, [socket]);
|
|
3239
|
-
const containerRef =
|
|
3343
|
+
const containerRef = React4.useCallback(
|
|
3240
3344
|
(element) => {
|
|
3241
3345
|
if (observerRef.current) {
|
|
3242
3346
|
observerRef.current.disconnect();
|
|
@@ -3262,18 +3366,18 @@ function useInlineCollab({
|
|
|
3262
3366
|
},
|
|
3263
3367
|
[preview, subscribe, unsubscribe]
|
|
3264
3368
|
);
|
|
3265
|
-
|
|
3369
|
+
React4.useEffect(() => {
|
|
3266
3370
|
return () => {
|
|
3267
3371
|
if (observerRef.current) observerRef.current.disconnect();
|
|
3268
3372
|
unsubscribe();
|
|
3269
3373
|
};
|
|
3270
3374
|
}, [unsubscribe]);
|
|
3271
|
-
|
|
3375
|
+
React4.useEffect(() => {
|
|
3272
3376
|
if (preview?.conversationId && elementRef.current && !observerRef.current) {
|
|
3273
3377
|
containerRef(elementRef.current);
|
|
3274
3378
|
}
|
|
3275
3379
|
}, [preview, containerRef]);
|
|
3276
|
-
const dispatchSend =
|
|
3380
|
+
const dispatchSend = React4.useCallback(
|
|
3277
3381
|
async (payload) => {
|
|
3278
3382
|
if (!preview) {
|
|
3279
3383
|
try {
|
|
@@ -3330,7 +3434,7 @@ function useInlineCollab({
|
|
|
3330
3434
|
config
|
|
3331
3435
|
]
|
|
3332
3436
|
);
|
|
3333
|
-
const sendMessage =
|
|
3437
|
+
const sendMessage = React4.useCallback(
|
|
3334
3438
|
async (body) => {
|
|
3335
3439
|
const trimmed = body.trim();
|
|
3336
3440
|
if (!trimmed) return;
|
|
@@ -3338,7 +3442,7 @@ function useInlineCollab({
|
|
|
3338
3442
|
},
|
|
3339
3443
|
[dispatchSend]
|
|
3340
3444
|
);
|
|
3341
|
-
const sendAudioMessage =
|
|
3445
|
+
const sendAudioMessage = React4.useCallback(
|
|
3342
3446
|
async (audioBlob, duration) => {
|
|
3343
3447
|
if (!config.onUploadFile) {
|
|
3344
3448
|
setError("File upload is not configured");
|
|
@@ -3451,10 +3555,10 @@ function RoleDot({ role }) {
|
|
|
3451
3555
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles13.roleDot, backgroundColor: colors[role] } });
|
|
3452
3556
|
}
|
|
3453
3557
|
function InlineAudioPlayer({ url, duration }) {
|
|
3454
|
-
const audioRef =
|
|
3455
|
-
const [isPlaying, setIsPlaying] =
|
|
3456
|
-
const [currentTime, setCurrentTime] =
|
|
3457
|
-
const toggle =
|
|
3558
|
+
const audioRef = React4.useRef(null);
|
|
3559
|
+
const [isPlaying, setIsPlaying] = React4.useState(false);
|
|
3560
|
+
const [currentTime, setCurrentTime] = React4.useState(0);
|
|
3561
|
+
const toggle = React4.useCallback(
|
|
3458
3562
|
(e) => {
|
|
3459
3563
|
e.stopPropagation();
|
|
3460
3564
|
const audio = audioRef.current;
|
|
@@ -3468,7 +3572,7 @@ function InlineAudioPlayer({ url, duration }) {
|
|
|
3468
3572
|
},
|
|
3469
3573
|
[isPlaying]
|
|
3470
3574
|
);
|
|
3471
|
-
|
|
3575
|
+
React4.useEffect(() => {
|
|
3472
3576
|
const audio = audioRef.current;
|
|
3473
3577
|
if (!audio) return;
|
|
3474
3578
|
const onPlay = () => setIsPlaying(true);
|
|
@@ -3544,10 +3648,10 @@ function InlineInputBar({
|
|
|
3544
3648
|
showExpand,
|
|
3545
3649
|
onExpand
|
|
3546
3650
|
}) {
|
|
3547
|
-
const [text, setText] =
|
|
3548
|
-
const [isSending, setIsSending] =
|
|
3549
|
-
const inputRef =
|
|
3550
|
-
const handleSend =
|
|
3651
|
+
const [text, setText] = React4.useState("");
|
|
3652
|
+
const [isSending, setIsSending] = React4.useState(false);
|
|
3653
|
+
const inputRef = React4.useRef(null);
|
|
3654
|
+
const handleSend = React4.useCallback(async () => {
|
|
3551
3655
|
const trimmed = text.trim();
|
|
3552
3656
|
if (!trimmed || isSending) return;
|
|
3553
3657
|
setIsSending(true);
|
|
@@ -3559,7 +3663,7 @@ function InlineInputBar({
|
|
|
3559
3663
|
setIsSending(false);
|
|
3560
3664
|
}
|
|
3561
3665
|
}, [text, isSending, onSend]);
|
|
3562
|
-
const handleKeyDown =
|
|
3666
|
+
const handleKeyDown = React4.useCallback(
|
|
3563
3667
|
(e) => {
|
|
3564
3668
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
3565
3669
|
e.preventDefault();
|
|
@@ -3792,10 +3896,10 @@ var styles13 = {
|
|
|
3792
3896
|
function useConversationList(options) {
|
|
3793
3897
|
const enabled = options?.enabled ?? true;
|
|
3794
3898
|
const { fetchConversationList, config, totalUnread: serverTotalUnread } = useCollab();
|
|
3795
|
-
const [conversations, setConversations] =
|
|
3796
|
-
const [isLoading, setIsLoading] =
|
|
3797
|
-
const [error, setError] =
|
|
3798
|
-
const silentRefresh =
|
|
3899
|
+
const [conversations, setConversations] = React4.useState([]);
|
|
3900
|
+
const [isLoading, setIsLoading] = React4.useState(enabled);
|
|
3901
|
+
const [error, setError] = React4.useState(null);
|
|
3902
|
+
const silentRefresh = React4.useCallback(async () => {
|
|
3799
3903
|
try {
|
|
3800
3904
|
const list = await fetchConversationList();
|
|
3801
3905
|
setConversations(list);
|
|
@@ -3807,7 +3911,7 @@ function useConversationList(options) {
|
|
|
3807
3911
|
});
|
|
3808
3912
|
}
|
|
3809
3913
|
}, [fetchConversationList, config]);
|
|
3810
|
-
const refresh =
|
|
3914
|
+
const refresh = React4.useCallback(async () => {
|
|
3811
3915
|
setIsLoading(true);
|
|
3812
3916
|
setError(null);
|
|
3813
3917
|
try {
|
|
@@ -3825,12 +3929,12 @@ function useConversationList(options) {
|
|
|
3825
3929
|
setIsLoading(false);
|
|
3826
3930
|
}
|
|
3827
3931
|
}, [fetchConversationList, config]);
|
|
3828
|
-
|
|
3932
|
+
React4.useEffect(() => {
|
|
3829
3933
|
if (!enabled) return;
|
|
3830
3934
|
refresh();
|
|
3831
3935
|
}, [enabled, refresh]);
|
|
3832
|
-
const hasHandledInitialUnreadRef =
|
|
3833
|
-
|
|
3936
|
+
const hasHandledInitialUnreadRef = React4.useRef(false);
|
|
3937
|
+
React4.useEffect(() => {
|
|
3834
3938
|
if (!enabled) return;
|
|
3835
3939
|
if (!hasHandledInitialUnreadRef.current) {
|
|
3836
3940
|
hasHandledInitialUnreadRef.current = true;
|
|
@@ -3838,7 +3942,7 @@ function useConversationList(options) {
|
|
|
3838
3942
|
}
|
|
3839
3943
|
silentRefresh();
|
|
3840
3944
|
}, [serverTotalUnread, enabled]);
|
|
3841
|
-
const filter =
|
|
3945
|
+
const filter = React4.useCallback(
|
|
3842
3946
|
(query) => {
|
|
3843
3947
|
const q = query.trim().toLowerCase();
|
|
3844
3948
|
if (!q) return conversations;
|
|
@@ -3903,7 +4007,7 @@ function ConversationListItem({
|
|
|
3903
4007
|
...styles14.time,
|
|
3904
4008
|
...hasUnread ? styles14.timeUnread : {}
|
|
3905
4009
|
},
|
|
3906
|
-
children:
|
|
4010
|
+
children: formatInboxTimestamp(item.lastActivityAt)
|
|
3907
4011
|
}
|
|
3908
4012
|
)
|
|
3909
4013
|
] }),
|
|
@@ -3946,23 +4050,6 @@ function renderLastMessagePreview(message) {
|
|
|
3946
4050
|
}
|
|
3947
4051
|
}
|
|
3948
4052
|
}
|
|
3949
|
-
function formatRelativeTime(iso) {
|
|
3950
|
-
try {
|
|
3951
|
-
const date = new Date(iso);
|
|
3952
|
-
const now = /* @__PURE__ */ new Date();
|
|
3953
|
-
const diffMs = now.getTime() - date.getTime();
|
|
3954
|
-
const diffMin = Math.floor(diffMs / 6e4);
|
|
3955
|
-
const diffHr = Math.floor(diffMs / 36e5);
|
|
3956
|
-
const diffDay = Math.floor(diffMs / 864e5);
|
|
3957
|
-
if (diffMin < 1) return "now";
|
|
3958
|
-
if (diffMin < 60) return `${diffMin}m`;
|
|
3959
|
-
if (diffHr < 24) return `${diffHr}h`;
|
|
3960
|
-
if (diffDay < 7) return `${diffDay}d`;
|
|
3961
|
-
return date.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
3962
|
-
} catch {
|
|
3963
|
-
return "";
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
4053
|
var styles14 = {
|
|
3967
4054
|
container: {
|
|
3968
4055
|
display: "flex",
|
|
@@ -4092,9 +4179,9 @@ function ConversationList({
|
|
|
4092
4179
|
title = "Conversations",
|
|
4093
4180
|
className
|
|
4094
4181
|
}) {
|
|
4095
|
-
const [query, setQuery] =
|
|
4096
|
-
const [showUnreadOnly, setShowUnreadOnly] =
|
|
4097
|
-
const filtered =
|
|
4182
|
+
const [query, setQuery] = React4.useState("");
|
|
4183
|
+
const [showUnreadOnly, setShowUnreadOnly] = React4.useState(false);
|
|
4184
|
+
const filtered = React4.useMemo(() => {
|
|
4098
4185
|
let result = conversations;
|
|
4099
4186
|
if (showUnreadOnly) {
|
|
4100
4187
|
result = result.filter((c) => c.unreadCount > 0);
|
|
@@ -4271,7 +4358,7 @@ function CollabInbox({
|
|
|
4271
4358
|
style
|
|
4272
4359
|
}) {
|
|
4273
4360
|
const { conversations, isLoading, error } = useConversationList();
|
|
4274
|
-
const [selectedId, setSelectedId] =
|
|
4361
|
+
const [selectedId, setSelectedId] = React4.useState(initialConversationId ?? null);
|
|
4275
4362
|
const selected = conversations.find((c) => c.id === selectedId) || null;
|
|
4276
4363
|
const handleSelect = (item) => {
|
|
4277
4364
|
setSelectedId(item.id);
|
|
@@ -4373,12 +4460,12 @@ function useMessages({
|
|
|
4373
4460
|
initialLimit = MESSAGES_PAGE_SIZE
|
|
4374
4461
|
}) {
|
|
4375
4462
|
const { fetchMessages, config } = useCollab();
|
|
4376
|
-
const [messages, setMessages] =
|
|
4377
|
-
const [isLoading, setIsLoading] =
|
|
4378
|
-
const [hasMore, setHasMore] =
|
|
4379
|
-
const [error, setError] =
|
|
4380
|
-
const loadedRef =
|
|
4381
|
-
|
|
4463
|
+
const [messages, setMessages] = React4.useState([]);
|
|
4464
|
+
const [isLoading, setIsLoading] = React4.useState(false);
|
|
4465
|
+
const [hasMore, setHasMore] = React4.useState(true);
|
|
4466
|
+
const [error, setError] = React4.useState(null);
|
|
4467
|
+
const loadedRef = React4.useRef(null);
|
|
4468
|
+
React4.useEffect(() => {
|
|
4382
4469
|
if (!conversationId) {
|
|
4383
4470
|
setMessages([]);
|
|
4384
4471
|
setHasMore(false);
|
|
@@ -4414,7 +4501,7 @@ function useMessages({
|
|
|
4414
4501
|
cancelled = true;
|
|
4415
4502
|
};
|
|
4416
4503
|
}, [conversationId, initialLimit, fetchMessages, config]);
|
|
4417
|
-
const loadMore =
|
|
4504
|
+
const loadMore = React4.useCallback(async () => {
|
|
4418
4505
|
if (!conversationId || !hasMore || isLoading) return;
|
|
4419
4506
|
const oldestId = messages[0]?.id;
|
|
4420
4507
|
setIsLoading(true);
|
|
@@ -4435,17 +4522,17 @@ function useMessages({
|
|
|
4435
4522
|
setIsLoading(false);
|
|
4436
4523
|
}
|
|
4437
4524
|
}, [conversationId, hasMore, isLoading, messages, initialLimit, fetchMessages, config]);
|
|
4438
|
-
const addMessage =
|
|
4525
|
+
const addMessage = React4.useCallback((message) => {
|
|
4439
4526
|
setMessages((prev) => [...prev, message]);
|
|
4440
4527
|
}, []);
|
|
4441
|
-
const updateMessageReadBy =
|
|
4528
|
+
const updateMessageReadBy = React4.useCallback((messageId, userId) => {
|
|
4442
4529
|
setMessages(
|
|
4443
4530
|
(prev) => prev.map(
|
|
4444
4531
|
(msg) => msg.id === messageId && !(msg.readBy ?? []).includes(userId) ? { ...msg, readBy: [...msg.readBy ?? [], userId] } : msg
|
|
4445
4532
|
)
|
|
4446
4533
|
);
|
|
4447
4534
|
}, []);
|
|
4448
|
-
const reset =
|
|
4535
|
+
const reset = React4.useCallback(() => {
|
|
4449
4536
|
setMessages([]);
|
|
4450
4537
|
setHasMore(true);
|
|
4451
4538
|
setError(null);
|
|
@@ -4463,17 +4550,17 @@ function useMessages({
|
|
|
4463
4550
|
};
|
|
4464
4551
|
}
|
|
4465
4552
|
function useAudioRecorder() {
|
|
4466
|
-
const [isRecording, setIsRecording] =
|
|
4467
|
-
const [duration, setDuration] =
|
|
4468
|
-
const [error, setError] =
|
|
4469
|
-
const mediaRecorderRef =
|
|
4470
|
-
const chunksRef =
|
|
4471
|
-
const streamRef =
|
|
4472
|
-
const timerRef =
|
|
4473
|
-
const startTimeRef =
|
|
4474
|
-
const resolveRef =
|
|
4553
|
+
const [isRecording, setIsRecording] = React4.useState(false);
|
|
4554
|
+
const [duration, setDuration] = React4.useState(0);
|
|
4555
|
+
const [error, setError] = React4.useState(null);
|
|
4556
|
+
const mediaRecorderRef = React4.useRef(null);
|
|
4557
|
+
const chunksRef = React4.useRef([]);
|
|
4558
|
+
const streamRef = React4.useRef(null);
|
|
4559
|
+
const timerRef = React4.useRef(null);
|
|
4560
|
+
const startTimeRef = React4.useRef(0);
|
|
4561
|
+
const resolveRef = React4.useRef(null);
|
|
4475
4562
|
const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
|
|
4476
|
-
const cleanup =
|
|
4563
|
+
const cleanup = React4.useCallback(() => {
|
|
4477
4564
|
if (timerRef.current) {
|
|
4478
4565
|
clearInterval(timerRef.current);
|
|
4479
4566
|
timerRef.current = null;
|
|
@@ -4487,7 +4574,7 @@ function useAudioRecorder() {
|
|
|
4487
4574
|
setIsRecording(false);
|
|
4488
4575
|
setDuration(0);
|
|
4489
4576
|
}, []);
|
|
4490
|
-
const start =
|
|
4577
|
+
const start = React4.useCallback(async () => {
|
|
4491
4578
|
if (!isSupported) {
|
|
4492
4579
|
setError("Audio recording is not supported in this browser");
|
|
4493
4580
|
return;
|
|
@@ -4534,7 +4621,7 @@ function useAudioRecorder() {
|
|
|
4534
4621
|
cleanup();
|
|
4535
4622
|
}
|
|
4536
4623
|
}, [isSupported, cleanup]);
|
|
4537
|
-
const stop =
|
|
4624
|
+
const stop = React4.useCallback(async () => {
|
|
4538
4625
|
return new Promise((resolve) => {
|
|
4539
4626
|
if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
|
|
4540
4627
|
resolve(null);
|
|
@@ -4544,7 +4631,7 @@ function useAudioRecorder() {
|
|
|
4544
4631
|
mediaRecorderRef.current.stop();
|
|
4545
4632
|
});
|
|
4546
4633
|
}, []);
|
|
4547
|
-
const cancel =
|
|
4634
|
+
const cancel = React4.useCallback(() => {
|
|
4548
4635
|
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
|
4549
4636
|
mediaRecorderRef.current.onstop = null;
|
|
4550
4637
|
mediaRecorderRef.current.stop();
|
|
@@ -4565,19 +4652,19 @@ function useAudioRecorder() {
|
|
|
4565
4652
|
}
|
|
4566
4653
|
function useUnreadCount() {
|
|
4567
4654
|
const { socket, totalUnread } = useCollab();
|
|
4568
|
-
const [counts, setCounts] =
|
|
4569
|
-
|
|
4655
|
+
const [counts, setCounts] = React4.useState({});
|
|
4656
|
+
React4.useEffect(() => {
|
|
4570
4657
|
socket.onUnreadCountUpdate((serverCounts) => {
|
|
4571
4658
|
setCounts(serverCounts);
|
|
4572
4659
|
});
|
|
4573
4660
|
}, [socket]);
|
|
4574
|
-
const getCountForConversation =
|
|
4661
|
+
const getCountForConversation = React4.useCallback(
|
|
4575
4662
|
(conversationId) => {
|
|
4576
4663
|
return counts[conversationId] ?? 0;
|
|
4577
4664
|
},
|
|
4578
4665
|
[counts]
|
|
4579
4666
|
);
|
|
4580
|
-
const clearForConversation =
|
|
4667
|
+
const clearForConversation = React4.useCallback((conversationId) => {
|
|
4581
4668
|
setCounts((prev) => {
|
|
4582
4669
|
const next = { ...prev };
|
|
4583
4670
|
delete next[conversationId];
|
|
@@ -4595,10 +4682,10 @@ function usePinnedMessages({
|
|
|
4595
4682
|
initial = []
|
|
4596
4683
|
}) {
|
|
4597
4684
|
const { pinMessage, unpinMessage, fetchPinnedMessages, config } = useCollab();
|
|
4598
|
-
const [pinnedMessages, setPinnedMessages] =
|
|
4599
|
-
const [isLoading, setIsLoading] =
|
|
4600
|
-
const [error, setError] =
|
|
4601
|
-
const refresh =
|
|
4685
|
+
const [pinnedMessages, setPinnedMessages] = React4.useState(initial);
|
|
4686
|
+
const [isLoading, setIsLoading] = React4.useState(false);
|
|
4687
|
+
const [error, setError] = React4.useState(null);
|
|
4688
|
+
const refresh = React4.useCallback(async () => {
|
|
4602
4689
|
if (!conversationId) return;
|
|
4603
4690
|
setIsLoading(true);
|
|
4604
4691
|
setError(null);
|
|
@@ -4612,12 +4699,12 @@ function usePinnedMessages({
|
|
|
4612
4699
|
setIsLoading(false);
|
|
4613
4700
|
}
|
|
4614
4701
|
}, [conversationId, fetchPinnedMessages]);
|
|
4615
|
-
|
|
4702
|
+
React4.useEffect(() => {
|
|
4616
4703
|
if (conversationId && initial.length === 0) {
|
|
4617
4704
|
refresh();
|
|
4618
4705
|
}
|
|
4619
4706
|
}, [conversationId]);
|
|
4620
|
-
const pin =
|
|
4707
|
+
const pin = React4.useCallback(
|
|
4621
4708
|
async (messageId) => {
|
|
4622
4709
|
if (!conversationId) return;
|
|
4623
4710
|
setError(null);
|
|
@@ -4635,7 +4722,7 @@ function usePinnedMessages({
|
|
|
4635
4722
|
},
|
|
4636
4723
|
[conversationId, pinMessage, config]
|
|
4637
4724
|
);
|
|
4638
|
-
const unpin =
|
|
4725
|
+
const unpin = React4.useCallback(
|
|
4639
4726
|
async (messageId) => {
|
|
4640
4727
|
if (!conversationId) return;
|
|
4641
4728
|
setError(null);
|
|
@@ -4653,13 +4740,13 @@ function usePinnedMessages({
|
|
|
4653
4740
|
},
|
|
4654
4741
|
[conversationId, unpinMessage, config]
|
|
4655
4742
|
);
|
|
4656
|
-
const handlePinned =
|
|
4743
|
+
const handlePinned = React4.useCallback((message) => {
|
|
4657
4744
|
setPinnedMessages((prev) => {
|
|
4658
4745
|
if (prev.some((m) => m.id === message.id)) return prev;
|
|
4659
4746
|
return [...prev, message];
|
|
4660
4747
|
});
|
|
4661
4748
|
}, []);
|
|
4662
|
-
const handleUnpinned =
|
|
4749
|
+
const handleUnpinned = React4.useCallback((messageId) => {
|
|
4663
4750
|
setPinnedMessages((prev) => prev.filter((m) => m.id !== messageId));
|
|
4664
4751
|
}, []);
|
|
4665
4752
|
return {
|