@scalemule/chat 0.0.4 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/{ChatClient-COmdEJ11.d.ts → ChatClient-DQPHdUHX.d.cts} +21 -2
  2. package/dist/{ChatClient-BoZaTtyM.d.cts → ChatClient-DtUKF-4c.d.ts} +21 -2
  3. package/dist/chat.embed.global.js +1 -1
  4. package/dist/chat.umd.global.js +288 -12
  5. package/dist/{chunk-ZLMMNFZL.js → chunk-5O5YLRJL.js} +386 -16
  6. package/dist/chunk-GTMAK3IA.js +285 -0
  7. package/dist/chunk-TRCELAZQ.cjs +287 -0
  8. package/dist/{chunk-YDLRISR7.cjs → chunk-W2PWFS3E.cjs} +386 -15
  9. package/dist/constants.d.ts +9 -0
  10. package/dist/constants.d.ts.map +1 -0
  11. package/dist/core/ChatClient.d.ts +96 -0
  12. package/dist/core/ChatClient.d.ts.map +1 -0
  13. package/dist/core/EventEmitter.d.ts +11 -0
  14. package/dist/core/EventEmitter.d.ts.map +1 -0
  15. package/dist/core/MessageCache.d.ts +18 -0
  16. package/dist/core/MessageCache.d.ts.map +1 -0
  17. package/dist/core/OfflineQueue.d.ts +19 -0
  18. package/dist/core/OfflineQueue.d.ts.map +1 -0
  19. package/dist/element.cjs +542 -51
  20. package/dist/element.d.ts +2 -2
  21. package/dist/element.d.ts.map +1 -0
  22. package/dist/element.js +541 -50
  23. package/dist/embed/index.d.ts +2 -0
  24. package/dist/embed/index.d.ts.map +1 -0
  25. package/dist/factory.d.ts +8 -0
  26. package/dist/factory.d.ts.map +1 -0
  27. package/dist/iframe.d.cts +1 -1
  28. package/dist/iframe.d.ts +3 -5
  29. package/dist/iframe.d.ts.map +1 -0
  30. package/dist/index.cjs +34 -5
  31. package/dist/index.d.cts +93 -4
  32. package/dist/index.d.ts +8 -77
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +29 -4
  35. package/dist/react-components/ChatInput.d.ts +16 -0
  36. package/dist/react-components/ChatInput.d.ts.map +1 -0
  37. package/dist/react-components/ChatMessageItem.d.ts +13 -0
  38. package/dist/react-components/ChatMessageItem.d.ts.map +1 -0
  39. package/dist/react-components/ChatMessageList.d.ts +15 -0
  40. package/dist/react-components/ChatMessageList.d.ts.map +1 -0
  41. package/dist/react-components/ChatThread.d.ts +12 -0
  42. package/dist/react-components/ChatThread.d.ts.map +1 -0
  43. package/dist/react-components/ConversationList.d.ts +13 -0
  44. package/dist/react-components/ConversationList.d.ts.map +1 -0
  45. package/dist/react-components/EmojiPicker.d.ts +8 -0
  46. package/dist/react-components/EmojiPicker.d.ts.map +1 -0
  47. package/dist/react-components/index.d.ts +8 -0
  48. package/dist/react-components/index.d.ts.map +1 -0
  49. package/dist/react-components/theme.d.ts +19 -0
  50. package/dist/react-components/theme.d.ts.map +1 -0
  51. package/dist/react-components/utils.d.ts +4 -0
  52. package/dist/react-components/utils.d.ts.map +1 -0
  53. package/dist/react.cjs +1213 -53
  54. package/dist/react.d.cts +100 -4
  55. package/dist/react.d.ts +38 -15
  56. package/dist/react.d.ts.map +1 -0
  57. package/dist/react.js +1167 -18
  58. package/dist/shared/ChatController.d.ts +65 -0
  59. package/dist/shared/ChatController.d.ts.map +1 -0
  60. package/dist/shared/upload.d.ts +3 -0
  61. package/dist/shared/upload.d.ts.map +1 -0
  62. package/dist/support-widget.global.js +485 -157
  63. package/dist/support.d.ts +99 -0
  64. package/dist/support.d.ts.map +1 -0
  65. package/dist/transport/HttpTransport.d.ts +20 -0
  66. package/dist/transport/HttpTransport.d.ts.map +1 -0
  67. package/dist/transport/WebSocketTransport.d.ts +78 -0
  68. package/dist/transport/WebSocketTransport.d.ts.map +1 -0
  69. package/dist/{types-BmD7f1gV.d.cts → types-COPVrm3K.d.cts} +25 -1
  70. package/dist/{types-BmD7f1gV.d.ts → types-COPVrm3K.d.ts} +25 -1
  71. package/dist/types.d.ts +271 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/umd.d.ts +6 -0
  74. package/dist/umd.d.ts.map +1 -0
  75. package/dist/version.d.ts +2 -0
  76. package/dist/version.d.ts.map +1 -0
  77. package/dist/widget/icons.d.ts +8 -0
  78. package/dist/widget/icons.d.ts.map +1 -0
  79. package/dist/widget/index.d.ts +2 -0
  80. package/dist/widget/index.d.ts.map +1 -0
  81. package/dist/widget/storage.d.ts +5 -0
  82. package/dist/widget/storage.d.ts.map +1 -0
  83. package/dist/widget/styles.d.ts +3 -0
  84. package/dist/widget/styles.d.ts.map +1 -0
  85. package/package.json +5 -2
package/dist/react.js CHANGED
@@ -1,8 +1,1019 @@
1
- import { ChatClient } from './chunk-ZLMMNFZL.js';
2
- export { ChatClient } from './chunk-ZLMMNFZL.js';
3
- import { createContext, useState, useEffect, useCallback, useRef, useContext } from 'react';
4
- import { jsx } from 'react/jsx-runtime';
1
+ import { ChatClient } from './chunk-5O5YLRJL.js';
2
+ export { ChatClient } from './chunk-5O5YLRJL.js';
3
+ import React3, { createContext, useState, useRef, useMemo, useEffect, useCallback, useContext } from 'react';
4
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
5
 
6
+ function ChatInput({
7
+ onSend,
8
+ onTypingChange,
9
+ onUploadAttachment,
10
+ placeholder = "Type a message..."
11
+ }) {
12
+ const [content, setContent] = useState("");
13
+ const [attachments, setAttachments] = useState([]);
14
+ const [isSending, setIsSending] = useState(false);
15
+ const [isDragging, setIsDragging] = useState(false);
16
+ const fileInputRef = useRef(null);
17
+ const typingTimeoutRef = useRef(null);
18
+ const readyAttachments = useMemo(
19
+ () => attachments.filter((attachment) => attachment.attachment).map((attachment) => attachment.attachment),
20
+ [attachments]
21
+ );
22
+ const uploadingCount = attachments.filter((attachment) => !attachment.attachment && !attachment.error).length;
23
+ const emitTyping = () => {
24
+ onTypingChange?.(true);
25
+ if (typingTimeoutRef.current) {
26
+ clearTimeout(typingTimeoutRef.current);
27
+ }
28
+ typingTimeoutRef.current = setTimeout(() => {
29
+ onTypingChange?.(false);
30
+ }, 2500);
31
+ };
32
+ const handleFiles = async (fileList) => {
33
+ if (!onUploadAttachment) return;
34
+ const files = Array.from(fileList);
35
+ for (const file of files) {
36
+ const id = `${file.name}:${file.size}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
37
+ setAttachments((current) => [
38
+ ...current,
39
+ {
40
+ id,
41
+ fileName: file.name,
42
+ progress: 0
43
+ }
44
+ ]);
45
+ const result = await onUploadAttachment(file, (progress) => {
46
+ setAttachments(
47
+ (current) => current.map(
48
+ (attachment) => attachment.id === id ? { ...attachment, progress } : attachment
49
+ )
50
+ );
51
+ });
52
+ setAttachments(
53
+ (current) => current.map((attachment) => {
54
+ if (attachment.id !== id) return attachment;
55
+ if (result?.data) {
56
+ return {
57
+ ...attachment,
58
+ progress: 100,
59
+ attachment: result.data
60
+ };
61
+ }
62
+ return {
63
+ ...attachment,
64
+ error: result?.error?.message ?? "Upload failed"
65
+ };
66
+ })
67
+ );
68
+ }
69
+ };
70
+ const handleSubmit = async () => {
71
+ const trimmed = content.trim();
72
+ if (!trimmed && !readyAttachments.length || isSending || uploadingCount > 0) return;
73
+ setIsSending(true);
74
+ try {
75
+ await onSend(trimmed, readyAttachments);
76
+ setContent("");
77
+ setAttachments([]);
78
+ onTypingChange?.(false);
79
+ } finally {
80
+ setIsSending(false);
81
+ }
82
+ };
83
+ return /* @__PURE__ */ jsxs(
84
+ "div",
85
+ {
86
+ onDragOver: (event) => {
87
+ event.preventDefault();
88
+ if (onUploadAttachment) {
89
+ setIsDragging(true);
90
+ }
91
+ },
92
+ onDragLeave: () => setIsDragging(false),
93
+ onDrop: (event) => {
94
+ event.preventDefault();
95
+ setIsDragging(false);
96
+ void handleFiles(event.dataTransfer.files);
97
+ },
98
+ style: {
99
+ borderTop: "1px solid var(--sm-border-color, #e5e7eb)",
100
+ background: isDragging ? "rgba(37, 99, 235, 0.06)" : "var(--sm-surface, #fff)",
101
+ padding: 12,
102
+ display: "flex",
103
+ flexDirection: "column",
104
+ gap: 10
105
+ },
106
+ children: [
107
+ attachments.length ? /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: attachments.map((attachment) => /* @__PURE__ */ jsxs(
108
+ "div",
109
+ {
110
+ style: {
111
+ display: "inline-flex",
112
+ alignItems: "center",
113
+ gap: 8,
114
+ padding: "6px 10px",
115
+ borderRadius: 999,
116
+ background: "var(--sm-surface-muted, #f8fafc)",
117
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
118
+ fontSize: 12
119
+ },
120
+ children: [
121
+ /* @__PURE__ */ jsx("span", { children: attachment.fileName }),
122
+ /* @__PURE__ */ jsx("span", { style: { color: attachment.error ? "#dc2626" : "var(--sm-muted-text, #6b7280)" }, children: attachment.error ?? `${attachment.progress}%` }),
123
+ /* @__PURE__ */ jsx(
124
+ "button",
125
+ {
126
+ type: "button",
127
+ onClick: () => {
128
+ setAttachments((current) => current.filter((item) => item.id !== attachment.id));
129
+ },
130
+ style: {
131
+ border: "none",
132
+ background: "transparent",
133
+ cursor: "pointer",
134
+ color: "var(--sm-muted-text, #6b7280)"
135
+ },
136
+ children: "x"
137
+ }
138
+ )
139
+ ]
140
+ },
141
+ attachment.id
142
+ )) }) : null,
143
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 10, alignItems: "flex-end" }, children: [
144
+ /* @__PURE__ */ jsx(
145
+ "textarea",
146
+ {
147
+ value: content,
148
+ onChange: (event) => {
149
+ setContent(event.target.value);
150
+ emitTyping();
151
+ },
152
+ onKeyDown: (event) => {
153
+ if (event.key === "Enter" && !event.shiftKey) {
154
+ event.preventDefault();
155
+ void handleSubmit();
156
+ }
157
+ },
158
+ rows: 1,
159
+ placeholder,
160
+ style: {
161
+ flex: 1,
162
+ minHeight: 44,
163
+ maxHeight: 120,
164
+ resize: "vertical",
165
+ borderRadius: 14,
166
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
167
+ padding: "12px 14px",
168
+ font: "inherit",
169
+ color: "var(--sm-text-color, #111827)"
170
+ }
171
+ }
172
+ ),
173
+ onUploadAttachment ? /* @__PURE__ */ jsxs(Fragment, { children: [
174
+ /* @__PURE__ */ jsx(
175
+ "input",
176
+ {
177
+ ref: fileInputRef,
178
+ type: "file",
179
+ hidden: true,
180
+ multiple: true,
181
+ accept: "image/*,video/*,audio/*",
182
+ onChange: (event) => {
183
+ if (event.target.files) {
184
+ void handleFiles(event.target.files);
185
+ event.target.value = "";
186
+ }
187
+ }
188
+ }
189
+ ),
190
+ /* @__PURE__ */ jsx(
191
+ "button",
192
+ {
193
+ type: "button",
194
+ onClick: () => fileInputRef.current?.click(),
195
+ "aria-label": "Attach files",
196
+ style: {
197
+ width: 44,
198
+ height: 44,
199
+ borderRadius: 14,
200
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
201
+ background: "var(--sm-surface, #fff)",
202
+ cursor: "pointer",
203
+ color: "var(--sm-text-color, #111827)"
204
+ },
205
+ children: "+"
206
+ }
207
+ )
208
+ ] }) : null,
209
+ /* @__PURE__ */ jsx(
210
+ "button",
211
+ {
212
+ type: "button",
213
+ onClick: () => void handleSubmit(),
214
+ disabled: isSending || uploadingCount > 0 || !content.trim() && !readyAttachments.length,
215
+ style: {
216
+ height: 44,
217
+ padding: "0 16px",
218
+ borderRadius: 14,
219
+ border: "none",
220
+ background: "var(--sm-primary, #2563eb)",
221
+ color: "#fff",
222
+ cursor: isSending ? "wait" : "pointer",
223
+ opacity: isSending || uploadingCount > 0 ? 0.75 : 1
224
+ },
225
+ children: "Send"
226
+ }
227
+ )
228
+ ] })
229
+ ]
230
+ }
231
+ );
232
+ }
233
+ var DEFAULT_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F62E}", "\u{1F440}"];
234
+ function EmojiPicker({
235
+ onSelect,
236
+ emojis = DEFAULT_EMOJIS
237
+ }) {
238
+ return /* @__PURE__ */ jsx(
239
+ "div",
240
+ {
241
+ style: {
242
+ display: "inline-flex",
243
+ gap: 6,
244
+ padding: 6,
245
+ borderRadius: 999,
246
+ background: "var(--sm-surface, #fff)",
247
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
248
+ boxShadow: "0 10px 25px rgba(15, 23, 42, 0.12)"
249
+ },
250
+ children: emojis.map((emoji) => /* @__PURE__ */ jsx(
251
+ "button",
252
+ {
253
+ type: "button",
254
+ onClick: () => onSelect(emoji),
255
+ "aria-label": `React with ${emoji}`,
256
+ style: {
257
+ width: 32,
258
+ height: 32,
259
+ border: "none",
260
+ background: "transparent",
261
+ borderRadius: 999,
262
+ cursor: "pointer",
263
+ fontSize: 18
264
+ },
265
+ children: emoji
266
+ },
267
+ emoji
268
+ ))
269
+ }
270
+ );
271
+ }
272
+
273
+ // src/react-components/utils.ts
274
+ var timeFormatter = new Intl.DateTimeFormat(void 0, {
275
+ hour: "numeric",
276
+ minute: "2-digit"
277
+ });
278
+ var dateFormatter = new Intl.DateTimeFormat(void 0, {
279
+ month: "short",
280
+ day: "numeric",
281
+ year: "numeric"
282
+ });
283
+ function formatMessageTime(value) {
284
+ if (!value) return "";
285
+ return timeFormatter.format(new Date(value));
286
+ }
287
+ function formatDayLabel(value) {
288
+ if (!value) return "";
289
+ return dateFormatter.format(new Date(value));
290
+ }
291
+ function isSameDay(left, right) {
292
+ if (!left || !right) return false;
293
+ const leftDate = new Date(left);
294
+ const rightDate = new Date(right);
295
+ return leftDate.getFullYear() === rightDate.getFullYear() && leftDate.getMonth() === rightDate.getMonth() && leftDate.getDate() === rightDate.getDate();
296
+ }
297
+ function renderAttachment(messageId, attachment) {
298
+ const key = `${messageId}:${attachment.file_id}`;
299
+ const url = attachment.presigned_url;
300
+ if (!url) {
301
+ return /* @__PURE__ */ jsx(
302
+ "div",
303
+ {
304
+ style: {
305
+ padding: "10px 12px",
306
+ borderRadius: 12,
307
+ background: "rgba(255,255,255,0.16)",
308
+ fontSize: 13
309
+ },
310
+ children: attachment.file_name
311
+ },
312
+ key
313
+ );
314
+ }
315
+ if (attachment.mime_type.startsWith("image/")) {
316
+ return /* @__PURE__ */ jsx(
317
+ "img",
318
+ {
319
+ src: url,
320
+ alt: attachment.file_name,
321
+ loading: "lazy",
322
+ style: {
323
+ display: "block",
324
+ maxWidth: "100%",
325
+ borderRadius: 12,
326
+ marginTop: 8
327
+ }
328
+ },
329
+ key
330
+ );
331
+ }
332
+ if (attachment.mime_type.startsWith("video/")) {
333
+ return /* @__PURE__ */ jsx(
334
+ "video",
335
+ {
336
+ controls: true,
337
+ src: url,
338
+ style: {
339
+ display: "block",
340
+ width: "100%",
341
+ maxWidth: 320,
342
+ borderRadius: 12,
343
+ marginTop: 8
344
+ }
345
+ },
346
+ key
347
+ );
348
+ }
349
+ if (attachment.mime_type.startsWith("audio/")) {
350
+ return /* @__PURE__ */ jsx(
351
+ "audio",
352
+ {
353
+ controls: true,
354
+ src: url,
355
+ style: {
356
+ display: "block",
357
+ width: "100%",
358
+ marginTop: 8
359
+ }
360
+ },
361
+ key
362
+ );
363
+ }
364
+ return /* @__PURE__ */ jsx(
365
+ "a",
366
+ {
367
+ href: url,
368
+ target: "_blank",
369
+ rel: "noreferrer",
370
+ style: {
371
+ display: "inline-block",
372
+ marginTop: 8,
373
+ color: "inherit",
374
+ fontSize: 13
375
+ },
376
+ children: attachment.file_name
377
+ },
378
+ key
379
+ );
380
+ }
381
+ function ChatMessageItem({
382
+ message,
383
+ currentUserId,
384
+ onAddReaction,
385
+ onRemoveReaction,
386
+ onReport,
387
+ highlight = false
388
+ }) {
389
+ const [showPicker, setShowPicker] = useState(false);
390
+ const isOwn = Boolean(currentUserId && message.sender_id === currentUserId);
391
+ const canReact = Boolean(onAddReaction || onRemoveReaction);
392
+ const reactionEntries = useMemo(() => message.reactions ?? [], [message.reactions]);
393
+ return /* @__PURE__ */ jsxs(
394
+ "div",
395
+ {
396
+ style: {
397
+ display: "flex",
398
+ flexDirection: "column",
399
+ alignItems: isOwn ? "flex-end" : "flex-start",
400
+ gap: 6
401
+ },
402
+ children: [
403
+ /* @__PURE__ */ jsxs(
404
+ "div",
405
+ {
406
+ style: {
407
+ maxWidth: "min(82%, 560px)",
408
+ padding: message.attachments?.length ? 10 : "10px 12px",
409
+ borderRadius: "var(--sm-border-radius, 16px)",
410
+ background: isOwn ? "var(--sm-own-bubble, #2563eb)" : "var(--sm-other-bubble, #f3f4f6)",
411
+ color: isOwn ? "var(--sm-own-text, #fff)" : "var(--sm-other-text, #111827)",
412
+ boxShadow: highlight ? "0 0 0 2px rgba(37, 99, 235, 0.22)" : "none",
413
+ transition: "box-shadow 0.2s ease"
414
+ },
415
+ children: [
416
+ message.content ? /* @__PURE__ */ jsx("div", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word", fontSize: 14 }, children: message.content }) : null,
417
+ message.attachments?.map((attachment) => renderAttachment(message.id, attachment))
418
+ ]
419
+ }
420
+ ),
421
+ /* @__PURE__ */ jsxs(
422
+ "div",
423
+ {
424
+ style: {
425
+ display: "flex",
426
+ alignItems: "center",
427
+ gap: 8,
428
+ color: "var(--sm-muted-text, #6b7280)",
429
+ fontSize: 12
430
+ },
431
+ children: [
432
+ /* @__PURE__ */ jsx("span", { children: formatMessageTime(message.created_at) }),
433
+ message.is_edited ? /* @__PURE__ */ jsx("span", { children: "edited" }) : null,
434
+ onReport && !isOwn ? /* @__PURE__ */ jsx(
435
+ "button",
436
+ {
437
+ type: "button",
438
+ onClick: () => void onReport(message.id),
439
+ style: {
440
+ border: "none",
441
+ background: "transparent",
442
+ color: "inherit",
443
+ cursor: "pointer",
444
+ fontSize: 12,
445
+ padding: 0
446
+ },
447
+ children: "Report"
448
+ }
449
+ ) : null,
450
+ canReact ? /* @__PURE__ */ jsx(
451
+ "button",
452
+ {
453
+ type: "button",
454
+ onClick: () => setShowPicker((value) => !value),
455
+ style: {
456
+ border: "none",
457
+ background: "transparent",
458
+ color: "inherit",
459
+ cursor: "pointer",
460
+ fontSize: 12,
461
+ padding: 0
462
+ },
463
+ children: "React"
464
+ }
465
+ ) : null
466
+ ]
467
+ }
468
+ ),
469
+ showPicker && canReact ? /* @__PURE__ */ jsx(
470
+ EmojiPicker,
471
+ {
472
+ onSelect: (emoji) => {
473
+ setShowPicker(false);
474
+ void onAddReaction?.(message.id, emoji);
475
+ }
476
+ }
477
+ ) : null,
478
+ reactionEntries.length ? /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: reactionEntries.map((reaction) => {
479
+ const reacted = Boolean(currentUserId && reaction.user_ids.includes(currentUserId));
480
+ return /* @__PURE__ */ jsxs(
481
+ "button",
482
+ {
483
+ type: "button",
484
+ onClick: () => {
485
+ if (reacted) {
486
+ void onRemoveReaction?.(message.id, reaction.emoji);
487
+ return;
488
+ }
489
+ void onAddReaction?.(message.id, reaction.emoji);
490
+ },
491
+ style: {
492
+ border: reacted ? "1px solid rgba(37, 99, 235, 0.4)" : "1px solid var(--sm-border-color, #e5e7eb)",
493
+ background: reacted ? "rgba(37, 99, 235, 0.08)" : "var(--sm-surface, #fff)",
494
+ color: "var(--sm-text-color, #111827)",
495
+ borderRadius: 999,
496
+ padding: "4px 8px",
497
+ cursor: "pointer",
498
+ fontSize: 12
499
+ },
500
+ children: [
501
+ reaction.emoji,
502
+ " ",
503
+ reaction.count
504
+ ]
505
+ },
506
+ `${message.id}:${reaction.emoji}`
507
+ );
508
+ }) }) : null
509
+ ]
510
+ }
511
+ );
512
+ }
513
+ function getUnreadIndex(messages, unreadSince) {
514
+ if (!unreadSince) return -1;
515
+ return messages.findIndex((message) => new Date(message.created_at).getTime() > new Date(unreadSince).getTime());
516
+ }
517
+ function ChatMessageList({
518
+ messages,
519
+ currentUserId,
520
+ unreadSince,
521
+ scrollToUnreadOnMount = true,
522
+ onAddReaction,
523
+ onRemoveReaction,
524
+ onReport,
525
+ emptyState
526
+ }) {
527
+ const containerRef = useRef(null);
528
+ const unreadMarkerRef = useRef(null);
529
+ const lastMessageCountRef = useRef(messages.length);
530
+ const didScrollToUnreadRef = useRef(false);
531
+ const [showJumpToLatest, setShowJumpToLatest] = useState(false);
532
+ const unreadIndex = useMemo(() => getUnreadIndex(messages, unreadSince), [messages, unreadSince]);
533
+ useEffect(() => {
534
+ const container = containerRef.current;
535
+ if (!container) return;
536
+ const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
537
+ const shouldStickToBottom = distanceFromBottom < 80;
538
+ if (messages.length > lastMessageCountRef.current && shouldStickToBottom) {
539
+ container.scrollTo({ top: container.scrollHeight, behavior: "smooth" });
540
+ setShowJumpToLatest(false);
541
+ } else if (messages.length > lastMessageCountRef.current) {
542
+ setShowJumpToLatest(true);
543
+ }
544
+ lastMessageCountRef.current = messages.length;
545
+ }, [messages]);
546
+ useEffect(() => {
547
+ if (!scrollToUnreadOnMount || unreadIndex < 0 || didScrollToUnreadRef.current) {
548
+ return;
549
+ }
550
+ unreadMarkerRef.current?.scrollIntoView({ block: "center" });
551
+ didScrollToUnreadRef.current = true;
552
+ }, [scrollToUnreadOnMount, unreadIndex, messages.length]);
553
+ if (!messages.length) {
554
+ return /* @__PURE__ */ jsx(
555
+ "div",
556
+ {
557
+ style: {
558
+ flex: 1,
559
+ display: "flex",
560
+ alignItems: "center",
561
+ justifyContent: "center",
562
+ color: "var(--sm-muted-text, #6b7280)",
563
+ fontSize: 14,
564
+ padding: 24
565
+ },
566
+ children: emptyState ?? "No messages yet"
567
+ }
568
+ );
569
+ }
570
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative", flex: 1, minHeight: 0 }, children: [
571
+ /* @__PURE__ */ jsx(
572
+ "div",
573
+ {
574
+ ref: containerRef,
575
+ onScroll: (event) => {
576
+ const element = event.currentTarget;
577
+ const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
578
+ setShowJumpToLatest(distanceFromBottom > 120);
579
+ },
580
+ style: {
581
+ height: "100%",
582
+ overflowY: "auto",
583
+ padding: 16,
584
+ display: "flex",
585
+ flexDirection: "column",
586
+ gap: 12,
587
+ background: "var(--sm-surface-muted, #f8fafc)"
588
+ },
589
+ children: messages.map((message, index) => {
590
+ const previousMessage = messages[index - 1];
591
+ const showDateDivider = !previousMessage || !isSameDay(previousMessage.created_at, message.created_at);
592
+ const showUnreadDivider = unreadIndex === index;
593
+ return /* @__PURE__ */ jsxs(React3.Fragment, { children: [
594
+ showDateDivider ? /* @__PURE__ */ jsx(
595
+ "div",
596
+ {
597
+ style: {
598
+ alignSelf: "center",
599
+ fontSize: 12,
600
+ color: "var(--sm-muted-text, #6b7280)",
601
+ padding: "4px 10px",
602
+ borderRadius: 999,
603
+ background: "rgba(148, 163, 184, 0.12)"
604
+ },
605
+ children: formatDayLabel(message.created_at)
606
+ }
607
+ ) : null,
608
+ showUnreadDivider ? /* @__PURE__ */ jsxs(
609
+ "div",
610
+ {
611
+ ref: unreadMarkerRef,
612
+ style: {
613
+ display: "flex",
614
+ alignItems: "center",
615
+ gap: 10,
616
+ color: "var(--sm-primary, #2563eb)",
617
+ fontSize: 12,
618
+ fontWeight: 600
619
+ },
620
+ children: [
621
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: "rgba(37, 99, 235, 0.28)" } }),
622
+ /* @__PURE__ */ jsx("span", { children: "New messages" }),
623
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: "rgba(37, 99, 235, 0.28)" } })
624
+ ]
625
+ }
626
+ ) : null,
627
+ /* @__PURE__ */ jsx(
628
+ ChatMessageItem,
629
+ {
630
+ message,
631
+ currentUserId,
632
+ onAddReaction,
633
+ onRemoveReaction,
634
+ onReport,
635
+ highlight: showUnreadDivider
636
+ }
637
+ )
638
+ ] }, message.id);
639
+ })
640
+ }
641
+ ),
642
+ showJumpToLatest ? /* @__PURE__ */ jsx(
643
+ "button",
644
+ {
645
+ type: "button",
646
+ onClick: () => {
647
+ containerRef.current?.scrollTo({
648
+ top: containerRef.current.scrollHeight,
649
+ behavior: "smooth"
650
+ });
651
+ setShowJumpToLatest(false);
652
+ },
653
+ style: {
654
+ position: "absolute",
655
+ right: 16,
656
+ bottom: 16,
657
+ border: "none",
658
+ borderRadius: 999,
659
+ background: "var(--sm-primary, #2563eb)",
660
+ color: "#fff",
661
+ padding: "10px 14px",
662
+ cursor: "pointer",
663
+ boxShadow: "0 12px 28px rgba(37, 99, 235, 0.28)"
664
+ },
665
+ children: "New messages"
666
+ }
667
+ ) : null
668
+ ] });
669
+ }
670
+
671
+ // src/react-components/theme.ts
672
+ function themeToStyle(theme) {
673
+ return {
674
+ "--sm-primary": theme?.primary ?? "#2563eb",
675
+ "--sm-own-bubble": theme?.ownBubble ?? theme?.primary ?? "#2563eb",
676
+ "--sm-own-text": theme?.ownText ?? "#ffffff",
677
+ "--sm-other-bubble": theme?.otherBubble ?? "#f3f4f6",
678
+ "--sm-other-text": theme?.otherText ?? "#111827",
679
+ "--sm-surface": theme?.surface ?? "#ffffff",
680
+ "--sm-surface-muted": theme?.surfaceMuted ?? "#f8fafc",
681
+ "--sm-border-color": theme?.borderColor ?? "#e5e7eb",
682
+ "--sm-text-color": theme?.textColor ?? "#111827",
683
+ "--sm-muted-text": theme?.mutedText ?? "#6b7280",
684
+ "--sm-border-radius": typeof theme?.borderRadius === "number" ? `${theme.borderRadius}px` : theme?.borderRadius ?? "16px",
685
+ "--sm-font-family": theme?.fontFamily ?? 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
686
+ };
687
+ }
688
+ function inferMessageType(content, attachments) {
689
+ if (!attachments.length) return "text";
690
+ if (!content && attachments.every((attachment) => attachment.mime_type.startsWith("image/"))) {
691
+ return "image";
692
+ }
693
+ return "file";
694
+ }
695
+ function ChatThread({
696
+ conversationId,
697
+ theme,
698
+ currentUserId,
699
+ title = "Chat",
700
+ subtitle
701
+ }) {
702
+ const client = useChatClient();
703
+ const resolvedUserId = currentUserId ?? client.userId;
704
+ const {
705
+ messages,
706
+ readStatuses,
707
+ isLoading,
708
+ error,
709
+ sendMessage,
710
+ markRead,
711
+ addReaction,
712
+ removeReaction,
713
+ reportMessage,
714
+ uploadAttachment
715
+ } = useChat(conversationId);
716
+ const { typingUsers, sendTyping } = useTyping(conversationId);
717
+ const { members } = usePresence(conversationId);
718
+ const ownReadStatus = useMemo(
719
+ () => resolvedUserId ? readStatuses.find((status) => status.user_id === resolvedUserId)?.last_read_at : void 0,
720
+ [readStatuses, resolvedUserId]
721
+ );
722
+ const otherTypingUsers = useMemo(
723
+ () => typingUsers.filter((userId) => userId !== resolvedUserId),
724
+ [typingUsers, resolvedUserId]
725
+ );
726
+ const activeMembers = useMemo(
727
+ () => members.filter((member) => member.userId !== resolvedUserId),
728
+ [members, resolvedUserId]
729
+ );
730
+ useEffect(() => {
731
+ if (!messages.length) return;
732
+ void markRead();
733
+ }, [markRead, messages.length, messages[messages.length - 1]?.id]);
734
+ return /* @__PURE__ */ jsxs(
735
+ "div",
736
+ {
737
+ "data-scalemule-chat": "",
738
+ style: {
739
+ ...themeToStyle(theme),
740
+ display: "flex",
741
+ flexDirection: "column",
742
+ height: "100%",
743
+ minHeight: 320,
744
+ borderRadius: "var(--sm-border-radius, 16px)",
745
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
746
+ background: "var(--sm-surface, #fff)",
747
+ color: "var(--sm-text-color, #111827)",
748
+ fontFamily: "var(--sm-font-family)",
749
+ overflow: "hidden"
750
+ },
751
+ children: [
752
+ /* @__PURE__ */ jsxs(
753
+ "div",
754
+ {
755
+ style: {
756
+ padding: "16px 18px",
757
+ borderBottom: "1px solid var(--sm-border-color, #e5e7eb)",
758
+ display: "flex",
759
+ justifyContent: "space-between",
760
+ alignItems: "center",
761
+ gap: 16
762
+ },
763
+ children: [
764
+ /* @__PURE__ */ jsxs("div", { children: [
765
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 700 }, children: title }),
766
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 13, color: "var(--sm-muted-text, #6b7280)" }, children: otherTypingUsers.length ? "Typing..." : subtitle ?? (activeMembers.length ? `${activeMembers.length} online` : "No one online") })
767
+ ] }),
768
+ /* @__PURE__ */ jsxs(
769
+ "div",
770
+ {
771
+ style: {
772
+ display: "inline-flex",
773
+ alignItems: "center",
774
+ gap: 8,
775
+ fontSize: 12,
776
+ color: "var(--sm-muted-text, #6b7280)"
777
+ },
778
+ children: [
779
+ /* @__PURE__ */ jsx(
780
+ "span",
781
+ {
782
+ style: {
783
+ width: 10,
784
+ height: 10,
785
+ borderRadius: 999,
786
+ background: activeMembers.length ? "#22c55e" : "#94a3b8"
787
+ }
788
+ }
789
+ ),
790
+ activeMembers.length ? "Online" : "Away"
791
+ ]
792
+ }
793
+ )
794
+ ]
795
+ }
796
+ ),
797
+ error ? /* @__PURE__ */ jsx(
798
+ "div",
799
+ {
800
+ style: {
801
+ padding: "10px 16px",
802
+ fontSize: 13,
803
+ color: "#b91c1c",
804
+ background: "#fef2f2",
805
+ borderBottom: "1px solid #fecaca"
806
+ },
807
+ children: error
808
+ }
809
+ ) : null,
810
+ /* @__PURE__ */ jsx(
811
+ ChatMessageList,
812
+ {
813
+ messages,
814
+ currentUserId: resolvedUserId,
815
+ unreadSince: ownReadStatus,
816
+ onAddReaction: (messageId, emoji) => void addReaction(messageId, emoji),
817
+ onRemoveReaction: (messageId, emoji) => void removeReaction(messageId, emoji),
818
+ onReport: (messageId) => void reportMessage(messageId, "other"),
819
+ emptyState: isLoading ? "Loading messages..." : "Start the conversation"
820
+ }
821
+ ),
822
+ /* @__PURE__ */ jsx(
823
+ "div",
824
+ {
825
+ style: {
826
+ minHeight: otherTypingUsers.length ? 28 : 0,
827
+ padding: otherTypingUsers.length ? "0 16px 8px" : 0,
828
+ fontSize: 12,
829
+ color: "var(--sm-muted-text, #6b7280)"
830
+ },
831
+ children: otherTypingUsers.length ? "Someone is typing..." : null
832
+ }
833
+ ),
834
+ /* @__PURE__ */ jsx(
835
+ ChatInput,
836
+ {
837
+ onSend: async (content, attachments) => {
838
+ await sendMessage(content, {
839
+ attachments,
840
+ message_type: inferMessageType(content, attachments)
841
+ });
842
+ },
843
+ onTypingChange: (isTyping) => {
844
+ sendTyping(isTyping);
845
+ },
846
+ onUploadAttachment: uploadAttachment
847
+ }
848
+ )
849
+ ]
850
+ }
851
+ );
852
+ }
853
+ function formatPreview(conversation) {
854
+ if (conversation.last_message_preview) {
855
+ return conversation.last_message_preview;
856
+ }
857
+ return conversation.name ?? conversation.id;
858
+ }
859
+ function ConversationList({
860
+ conversationType,
861
+ selectedConversationId,
862
+ onSelect,
863
+ theme,
864
+ title = "Conversations"
865
+ }) {
866
+ const { conversations, isLoading } = useConversations({
867
+ conversationType
868
+ });
869
+ const [search, setSearch] = useState("");
870
+ const filtered = useMemo(() => {
871
+ const query = search.trim().toLowerCase();
872
+ if (!query) return conversations;
873
+ return conversations.filter((conversation) => {
874
+ const haystack = [
875
+ conversation.name,
876
+ conversation.last_message_preview,
877
+ conversation.counterparty_user_id
878
+ ].filter(Boolean).join(" ").toLowerCase();
879
+ return haystack.includes(query);
880
+ });
881
+ }, [conversations, search]);
882
+ return /* @__PURE__ */ jsxs(
883
+ "div",
884
+ {
885
+ "data-scalemule-chat": "",
886
+ style: {
887
+ ...themeToStyle(theme),
888
+ display: "flex",
889
+ flexDirection: "column",
890
+ height: "100%",
891
+ minHeight: 280,
892
+ borderRadius: "var(--sm-border-radius, 16px)",
893
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
894
+ background: "var(--sm-surface, #fff)",
895
+ color: "var(--sm-text-color, #111827)",
896
+ fontFamily: "var(--sm-font-family)",
897
+ overflow: "hidden"
898
+ },
899
+ children: [
900
+ /* @__PURE__ */ jsxs(
901
+ "div",
902
+ {
903
+ style: {
904
+ padding: 16,
905
+ borderBottom: "1px solid var(--sm-border-color, #e5e7eb)",
906
+ display: "flex",
907
+ flexDirection: "column",
908
+ gap: 10
909
+ },
910
+ children: [
911
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 700 }, children: title }),
912
+ /* @__PURE__ */ jsx(
913
+ "input",
914
+ {
915
+ value: search,
916
+ onChange: (event) => setSearch(event.target.value),
917
+ placeholder: "Search conversations",
918
+ style: {
919
+ width: "100%",
920
+ borderRadius: 12,
921
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
922
+ padding: "10px 12px",
923
+ font: "inherit"
924
+ }
925
+ }
926
+ )
927
+ ]
928
+ }
929
+ ),
930
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, overflowY: "auto" }, children: isLoading ? /* @__PURE__ */ jsx(
931
+ "div",
932
+ {
933
+ style: {
934
+ padding: 24,
935
+ fontSize: 14,
936
+ color: "var(--sm-muted-text, #6b7280)"
937
+ },
938
+ children: "Loading conversations..."
939
+ }
940
+ ) : !filtered.length ? /* @__PURE__ */ jsx(
941
+ "div",
942
+ {
943
+ style: {
944
+ padding: 24,
945
+ fontSize: 14,
946
+ color: "var(--sm-muted-text, #6b7280)"
947
+ },
948
+ children: "No conversations found"
949
+ }
950
+ ) : filtered.map((conversation) => {
951
+ const selected = conversation.id === selectedConversationId;
952
+ return /* @__PURE__ */ jsxs(
953
+ "button",
954
+ {
955
+ type: "button",
956
+ onClick: () => onSelect?.(conversation),
957
+ style: {
958
+ width: "100%",
959
+ border: "none",
960
+ borderBottom: "1px solid var(--sm-border-color, #e5e7eb)",
961
+ padding: 16,
962
+ textAlign: "left",
963
+ background: selected ? "rgba(37, 99, 235, 0.08)" : "transparent",
964
+ cursor: "pointer",
965
+ display: "flex",
966
+ flexDirection: "column",
967
+ gap: 6
968
+ },
969
+ children: [
970
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }, children: [
971
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 14, fontWeight: 600 }, children: conversation.name ?? conversation.counterparty_user_id ?? "Conversation" }),
972
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
973
+ conversation.is_muted ? /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: "var(--sm-muted-text, #6b7280)" }, children: "Muted" }) : null,
974
+ conversation.unread_count ? /* @__PURE__ */ jsx(
975
+ "span",
976
+ {
977
+ style: {
978
+ minWidth: 22,
979
+ height: 22,
980
+ borderRadius: 999,
981
+ background: "var(--sm-primary, #2563eb)",
982
+ color: "#fff",
983
+ display: "inline-flex",
984
+ alignItems: "center",
985
+ justifyContent: "center",
986
+ fontSize: 12,
987
+ fontWeight: 700,
988
+ padding: "0 6px"
989
+ },
990
+ children: conversation.unread_count
991
+ }
992
+ ) : null
993
+ ] })
994
+ ] }),
995
+ /* @__PURE__ */ jsx(
996
+ "div",
997
+ {
998
+ style: {
999
+ fontSize: 13,
1000
+ color: "var(--sm-muted-text, #6b7280)",
1001
+ overflow: "hidden",
1002
+ textOverflow: "ellipsis",
1003
+ whiteSpace: "nowrap"
1004
+ },
1005
+ children: formatPreview(conversation)
1006
+ }
1007
+ )
1008
+ ]
1009
+ },
1010
+ conversation.id
1011
+ );
1012
+ }) })
1013
+ ]
1014
+ }
1015
+ );
1016
+ }
6
1017
  var ChatContext = createContext(null);
7
1018
  function useChatContext() {
8
1019
  const ctx = useContext(ChatContext);
@@ -16,11 +1027,14 @@ function ChatProvider({ config, children }) {
16
1027
  client.destroy();
17
1028
  };
18
1029
  }, [client]);
19
- return /* @__PURE__ */ jsx(ChatContext.Provider, { value: { client }, children });
1030
+ return /* @__PURE__ */ jsx(ChatContext.Provider, { value: { client, config }, children });
20
1031
  }
21
1032
  function useChatClient() {
22
1033
  return useChatContext().client;
23
1034
  }
1035
+ function useChatConfig() {
1036
+ return useChatContext().config;
1037
+ }
24
1038
  function useConnection() {
25
1039
  const { client } = useChatContext();
26
1040
  const [status, setStatus] = useState(client.status);
@@ -33,15 +1047,14 @@ function useConnection() {
33
1047
  useEffect(() => {
34
1048
  return client.on("reconnecting", () => setStatus("reconnecting"));
35
1049
  }, [client]);
36
- return {
37
- status,
38
- connect: () => client.connect(),
39
- disconnect: () => client.disconnect()
40
- };
1050
+ const connect = useCallback(() => client.connect(), [client]);
1051
+ const disconnect = useCallback(() => client.disconnect(), [client]);
1052
+ return { status, connect, disconnect };
41
1053
  }
42
1054
  function useChat(conversationId) {
43
1055
  const { client } = useChatContext();
44
1056
  const [messages, setMessages] = useState([]);
1057
+ const [readStatuses, setReadStatuses] = useState([]);
45
1058
  const [isLoading, setIsLoading] = useState(false);
46
1059
  const [error, setError] = useState(null);
47
1060
  const [hasMore, setHasMore] = useState(false);
@@ -62,6 +1075,11 @@ function useChat(conversationId) {
62
1075
  } else if (result.error) {
63
1076
  setError(result.error.message);
64
1077
  }
1078
+ const readStatusResult = await client.getReadStatus(conversationId);
1079
+ if (cancelled) return;
1080
+ if (readStatusResult.data?.statuses) {
1081
+ setReadStatuses(readStatusResult.data.statuses);
1082
+ }
65
1083
  setIsLoading(false);
66
1084
  unsub = client.subscribeToConversation(conversationId);
67
1085
  client.connect();
@@ -75,10 +1093,7 @@ function useChat(conversationId) {
75
1093
  if (!conversationId) return;
76
1094
  return client.on("message", ({ message, conversationId: convId }) => {
77
1095
  if (convId === conversationId) {
78
- setMessages((prev) => {
79
- if (prev.some((m) => m.id === message.id)) return prev;
80
- return [...prev, message];
81
- });
1096
+ setMessages([...client.getCachedMessages(conversationId)]);
82
1097
  }
83
1098
  });
84
1099
  }, [client, conversationId]);
@@ -86,7 +1101,7 @@ function useChat(conversationId) {
86
1101
  if (!conversationId) return;
87
1102
  return client.on("message:updated", ({ message, conversationId: convId }) => {
88
1103
  if (convId === conversationId) {
89
- setMessages((prev) => prev.map((m) => m.id === message.id ? message : m));
1104
+ setMessages([...client.getCachedMessages(conversationId)]);
90
1105
  }
91
1106
  });
92
1107
  }, [client, conversationId]);
@@ -94,10 +1109,33 @@ function useChat(conversationId) {
94
1109
  if (!conversationId) return;
95
1110
  return client.on("message:deleted", ({ messageId, conversationId: convId }) => {
96
1111
  if (convId === conversationId) {
97
- setMessages((prev) => prev.filter((m) => m.id !== messageId));
1112
+ setMessages([...client.getCachedMessages(conversationId)]);
1113
+ }
1114
+ });
1115
+ }, [client, conversationId]);
1116
+ useEffect(() => {
1117
+ if (!conversationId) return;
1118
+ return client.on("reaction", ({ conversationId: convId }) => {
1119
+ if (convId === conversationId) {
1120
+ setMessages([...client.getCachedMessages(conversationId)]);
98
1121
  }
99
1122
  });
100
1123
  }, [client, conversationId]);
1124
+ useEffect(() => {
1125
+ if (!conversationId) return;
1126
+ return client.on("read", ({ conversationId: convId, userId, lastReadAt }) => {
1127
+ if (convId !== conversationId) return;
1128
+ setReadStatuses((prev) => {
1129
+ const existingIndex = prev.findIndex((status) => status.user_id === userId);
1130
+ if (existingIndex < 0) {
1131
+ return [...prev, { user_id: userId, last_read_at: lastReadAt }];
1132
+ }
1133
+ return prev.map(
1134
+ (status) => status.user_id === userId ? { ...status, last_read_at: lastReadAt } : status
1135
+ );
1136
+ });
1137
+ });
1138
+ }, [client, conversationId]);
101
1139
  const sendMessage = useCallback(
102
1140
  async (content, options) => {
103
1141
  if (!conversationId) return;
@@ -114,17 +1152,128 @@ function useChat(conversationId) {
114
1152
  setHasMore(result.data.has_more ?? false);
115
1153
  }
116
1154
  }, [client, conversationId, messages]);
1155
+ const editMessage = useCallback(
1156
+ async (messageId, content) => {
1157
+ const result = await client.editMessage(messageId, content);
1158
+ if (result.error) {
1159
+ setError(result.error.message);
1160
+ }
1161
+ return result;
1162
+ },
1163
+ [client]
1164
+ );
1165
+ const deleteMessage = useCallback(
1166
+ async (messageId) => {
1167
+ const result = await client.deleteMessage(messageId);
1168
+ if (result.error) {
1169
+ setError(result.error.message);
1170
+ }
1171
+ return result;
1172
+ },
1173
+ [client]
1174
+ );
1175
+ const addReaction = useCallback(
1176
+ async (messageId, emoji) => {
1177
+ const result = await client.addReaction(messageId, emoji);
1178
+ if (result.error) {
1179
+ setError(result.error.message);
1180
+ }
1181
+ return result;
1182
+ },
1183
+ [client]
1184
+ );
1185
+ const removeReaction = useCallback(
1186
+ async (messageId, emoji) => {
1187
+ const result = await client.removeReaction(messageId, emoji);
1188
+ if (result.error) {
1189
+ setError(result.error.message);
1190
+ }
1191
+ return result;
1192
+ },
1193
+ [client]
1194
+ );
1195
+ const uploadAttachment = useCallback(
1196
+ async (file, onProgress, signal) => {
1197
+ const result = await client.uploadAttachment(file, onProgress, signal);
1198
+ if (result.error) {
1199
+ setError(result.error.message);
1200
+ }
1201
+ return result;
1202
+ },
1203
+ [client]
1204
+ );
1205
+ const refreshAttachmentUrl = useCallback(
1206
+ async (messageId, fileId) => {
1207
+ const result = await client.refreshAttachmentUrl(messageId, fileId);
1208
+ if (result.error) {
1209
+ setError(result.error.message);
1210
+ }
1211
+ return result;
1212
+ },
1213
+ [client]
1214
+ );
1215
+ const reportMessage = useCallback(
1216
+ async (messageId, reason, description) => {
1217
+ const result = await client.reportMessage(messageId, reason, description);
1218
+ if (result.error) {
1219
+ setError(result.error.message);
1220
+ }
1221
+ return result;
1222
+ },
1223
+ [client]
1224
+ );
1225
+ const muteConversation = useCallback(
1226
+ async (mutedUntil) => {
1227
+ if (!conversationId) return;
1228
+ const result = await client.muteConversation(conversationId, mutedUntil);
1229
+ if (result.error) {
1230
+ setError(result.error.message);
1231
+ }
1232
+ return result;
1233
+ },
1234
+ [client, conversationId]
1235
+ );
1236
+ const unmuteConversation = useCallback(async () => {
1237
+ if (!conversationId) return;
1238
+ const result = await client.unmuteConversation(conversationId);
1239
+ if (result.error) {
1240
+ setError(result.error.message);
1241
+ }
1242
+ return result;
1243
+ }, [client, conversationId]);
1244
+ const getReadStatus = useCallback(async () => {
1245
+ if (!conversationId) return;
1246
+ const result = await client.getReadStatus(conversationId);
1247
+ if (result.data?.statuses) {
1248
+ setReadStatuses(result.data.statuses);
1249
+ } else if (result.error) {
1250
+ setError(result.error.message);
1251
+ }
1252
+ return result;
1253
+ }, [client, conversationId]);
117
1254
  const markRead = useCallback(async () => {
118
1255
  if (!conversationId) return;
119
1256
  await client.markRead(conversationId);
120
- }, [client, conversationId]);
1257
+ await getReadStatus();
1258
+ }, [client, conversationId, getReadStatus]);
121
1259
  return {
122
1260
  messages,
1261
+ readStatuses,
123
1262
  isLoading,
124
1263
  error,
125
1264
  hasMore,
126
1265
  sendMessage,
127
1266
  loadMore,
1267
+ editMessage,
1268
+ deleteMessage,
1269
+ addReaction,
1270
+ removeReaction,
1271
+ uploadAttachment,
1272
+ refreshAttachmentUrl,
1273
+ reportMessage,
1274
+ muteConversation,
1275
+ unmuteConversation,
1276
+ getReadStatus,
128
1277
  markRead
129
1278
  };
130
1279
  }
@@ -300,4 +1449,4 @@ function useUnreadCount() {
300
1449
  return { totalUnread };
301
1450
  }
302
1451
 
303
- export { ChatProvider, useChat, useChatClient, useConnection, useConversations, usePresence, useTyping, useUnreadCount };
1452
+ export { ChatInput, ChatMessageItem, ChatMessageList, ChatProvider, ChatThread, ConversationList, EmojiPicker, useChat, useChatClient, useChatConfig, useConnection, useConversations, usePresence, useTyping, useUnreadCount };