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