@convokitapp/vue-ui 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/PARITY.md +1 -0
- package/README.md +8 -0
- package/dist/index.cjs +56 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +55 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,6 +31,10 @@ import { defineComponent, h } from "vue";
|
|
|
31
31
|
|
|
32
32
|
// src/utils.ts
|
|
33
33
|
import { clsx } from "clsx";
|
|
34
|
+
var pendingMessageIdPrefix = "convokit-pending-";
|
|
35
|
+
function isConvoKitPendingMessage(message) {
|
|
36
|
+
return message.id.startsWith(pendingMessageIdPrefix);
|
|
37
|
+
}
|
|
34
38
|
function cx(...values) {
|
|
35
39
|
return clsx(values);
|
|
36
40
|
}
|
|
@@ -73,11 +77,15 @@ function mergeMessages(current, incoming) {
|
|
|
73
77
|
const byId = new Map(current.map((message) => [message.id, message]));
|
|
74
78
|
for (const message of incoming) byId.set(message.id, message);
|
|
75
79
|
return [...byId.values()].sort((left, right) => {
|
|
80
|
+
const leftPending = isConvoKitPendingMessage(left);
|
|
81
|
+
const rightPending = isConvoKitPendingMessage(right);
|
|
82
|
+
if (leftPending !== rightPending) return leftPending ? 1 : -1;
|
|
76
83
|
const byTime = left.createdAt.getTime() - right.createdAt.getTime();
|
|
77
84
|
return byTime === 0 ? left.id.localeCompare(right.id) : byTime;
|
|
78
85
|
});
|
|
79
86
|
}
|
|
80
87
|
function readerIdsFor(message, readAtByUserId) {
|
|
88
|
+
if (isConvoKitPendingMessage(message)) return /* @__PURE__ */ new Set();
|
|
81
89
|
return new Set([...readAtByUserId.entries()].filter(([userId, readAt]) => userId !== message.senderId && readAt.getTime() >= message.createdAt.getTime()).map(([userId]) => userId));
|
|
82
90
|
}
|
|
83
91
|
function partClass(part, appearance, defaultClass) {
|
|
@@ -165,6 +173,8 @@ function useConversation(options) {
|
|
|
165
173
|
let sentTyping = false;
|
|
166
174
|
let typingTimer = null;
|
|
167
175
|
let subscriptions = [];
|
|
176
|
+
const pendingIds = /* @__PURE__ */ new Set();
|
|
177
|
+
let pendingSequence = 0;
|
|
168
178
|
const unsubscribe = async () => {
|
|
169
179
|
const active = subscriptions;
|
|
170
180
|
subscriptions = [];
|
|
@@ -188,7 +198,12 @@ function useConversation(options) {
|
|
|
188
198
|
subscriptions = [
|
|
189
199
|
client.onMessage(conversationId, (message) => {
|
|
190
200
|
if (disposed || activeGeneration !== generation) return;
|
|
191
|
-
messages.value
|
|
201
|
+
const pending = messages.value.find((candidate) => pendingIds.has(candidate.id) && message.senderId === client.currentUserId && candidate.text === message.text && candidate.media.length === message.media.length);
|
|
202
|
+
if (pending) pendingIds.delete(pending.id);
|
|
203
|
+
messages.value = mergeMessages(
|
|
204
|
+
pending ? messages.value.filter((candidate) => candidate.id !== pending.id) : messages.value,
|
|
205
|
+
[message]
|
|
206
|
+
);
|
|
192
207
|
if ((options.markReadOnReceive ?? true) && message.senderId !== client.currentUserId) void markRead();
|
|
193
208
|
}, report),
|
|
194
209
|
client.onReadReceipt(conversationId, ({ userId, readAt }) => {
|
|
@@ -212,6 +227,7 @@ function useConversation(options) {
|
|
|
212
227
|
await unsubscribe();
|
|
213
228
|
if (disposed || activeGeneration !== generation) return;
|
|
214
229
|
messages.value = [];
|
|
230
|
+
pendingIds.clear();
|
|
215
231
|
conversation.value = null;
|
|
216
232
|
typingUserIds.value = /* @__PURE__ */ new Set();
|
|
217
233
|
readAtByUserId.value = /* @__PURE__ */ new Map();
|
|
@@ -251,7 +267,7 @@ function useConversation(options) {
|
|
|
251
267
|
const page = await client.getMessages({
|
|
252
268
|
conversationId,
|
|
253
269
|
limit: messagePageSize,
|
|
254
|
-
offset: messages.value.length
|
|
270
|
+
offset: messages.value.filter((message) => !pendingIds.has(message.id)).length
|
|
255
271
|
});
|
|
256
272
|
if (disposed || activeGeneration !== generation) return;
|
|
257
273
|
messages.value = mergeMessages(messages.value, page);
|
|
@@ -282,6 +298,18 @@ function useConversation(options) {
|
|
|
282
298
|
const normalizedText = text?.trim();
|
|
283
299
|
if (!normalizedText && (!media || media.length === 0)) return null;
|
|
284
300
|
if (isSending.value) return null;
|
|
301
|
+
const pendingId = `convokit-pending-${Date.now()}-${++pendingSequence}`;
|
|
302
|
+
const pendingMessage = {
|
|
303
|
+
id: pendingId,
|
|
304
|
+
conversationId: toValue(options.conversationId),
|
|
305
|
+
senderId: toValue(options.client).currentUserId,
|
|
306
|
+
text: normalizedText ?? null,
|
|
307
|
+
media: media ?? [],
|
|
308
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
309
|
+
updatedAt: null
|
|
310
|
+
};
|
|
311
|
+
pendingIds.add(pendingId);
|
|
312
|
+
messages.value = mergeMessages(messages.value, [pendingMessage]);
|
|
285
313
|
isSending.value = true;
|
|
286
314
|
error.value = null;
|
|
287
315
|
const activeGeneration = generation;
|
|
@@ -291,11 +319,21 @@ function useConversation(options) {
|
|
|
291
319
|
...normalizedText ? { text: normalizedText } : {},
|
|
292
320
|
...media?.length ? { media } : {}
|
|
293
321
|
});
|
|
294
|
-
|
|
322
|
+
pendingIds.delete(pendingId);
|
|
323
|
+
if (!disposed && activeGeneration === generation) {
|
|
324
|
+
messages.value = mergeMessages(
|
|
325
|
+
messages.value.filter((candidate) => candidate.id !== pendingId),
|
|
326
|
+
[message]
|
|
327
|
+
);
|
|
328
|
+
}
|
|
295
329
|
await updateTyping(false);
|
|
296
330
|
return message;
|
|
297
331
|
} catch (cause) {
|
|
298
|
-
|
|
332
|
+
pendingIds.delete(pendingId);
|
|
333
|
+
if (!disposed) {
|
|
334
|
+
messages.value = messages.value.filter((candidate) => candidate.id !== pendingId);
|
|
335
|
+
error.value = cause;
|
|
336
|
+
}
|
|
299
337
|
return null;
|
|
300
338
|
} finally {
|
|
301
339
|
if (!disposed && activeGeneration === generation) isSending.value = false;
|
|
@@ -476,7 +514,8 @@ var MessageListView = defineComponent2({
|
|
|
476
514
|
const renderMessage = (message, index) => {
|
|
477
515
|
const isCurrentUser = message.senderId === props.currentUserId;
|
|
478
516
|
const sender = participants.value.get(message.senderId);
|
|
479
|
-
const
|
|
517
|
+
const isPending = isConvoKitPendingMessage(message);
|
|
518
|
+
const readerIds = isPending ? /* @__PURE__ */ new Set() : props.readersResolver ? props.readersResolver(message) : readerIdsFor(message, props.readAtByUserId);
|
|
480
519
|
const slotProps = { message, chronologicalIndex: index, isCurrentUser, sender, readerIds };
|
|
481
520
|
const custom = slots.message?.(slotProps);
|
|
482
521
|
if (custom) return h2("div", { key: message.id, role: "listitem" }, custom);
|
|
@@ -509,12 +548,12 @@ var MessageListView = defineComponent2({
|
|
|
509
548
|
!isCurrentUser ? h2("strong", { class: "ckui-message-sender" }, sender?.name || message.senderId) : null,
|
|
510
549
|
message.text ? h2("div", { class: "ckui-message-text" }, message.text) : null,
|
|
511
550
|
...mediaNodes,
|
|
512
|
-
h2("
|
|
513
|
-
props.formatTime(message.createdAt),
|
|
514
|
-
isCurrentUser ? readerIds.size > 0 ? h2(CheckCheck, { size: 14, "aria-label": "Read" }) : h2(Check, { size: 14, "aria-label": "Delivered" }) : null
|
|
551
|
+
h2("span", { class: "ckui-message-time" }, [
|
|
552
|
+
isPending ? "Sending\u2026" : props.formatTime(message.createdAt),
|
|
553
|
+
isCurrentUser && !isPending ? readerIds.size > 0 ? h2(CheckCheck, { size: 14, "aria-label": "Read" }) : h2(Check, { size: 14, "aria-label": "Delivered" }) : null
|
|
515
554
|
])
|
|
516
555
|
]),
|
|
517
|
-
isCurrentUser ? slots["read-receipt"]?.(receiptSlotProps) ?? h2("div", {
|
|
556
|
+
isCurrentUser && !isPending ? slots["read-receipt"]?.(receiptSlotProps) ?? h2("div", {
|
|
518
557
|
class: partClass("receipt", currentAppearance, "ckui-read-receipt"),
|
|
519
558
|
style: partStyle("receipt", currentAppearance)
|
|
520
559
|
}, readerIds.size > 0 ? `Read by ${readerIds.size}` : "Delivered") : null
|
|
@@ -651,7 +690,9 @@ var ConversationView = defineComponent3({
|
|
|
651
690
|
...props.styles ? { styles: props.styles } : {}
|
|
652
691
|
});
|
|
653
692
|
const draft = () => props.modelValue ?? internalDraft.value;
|
|
693
|
+
let latestDraft = draft();
|
|
654
694
|
const setDraft = (value) => {
|
|
695
|
+
latestDraft = value;
|
|
655
696
|
if (props.modelValue === void 0) internalDraft.value = value;
|
|
656
697
|
props.onDraftChange?.(value);
|
|
657
698
|
emit("update:modelValue", value);
|
|
@@ -659,12 +700,14 @@ var ConversationView = defineComponent3({
|
|
|
659
700
|
void props.onTypingChange?.(isTyping);
|
|
660
701
|
};
|
|
661
702
|
const submit = async () => {
|
|
662
|
-
const
|
|
703
|
+
const originalDraft = draft();
|
|
704
|
+
const text = originalDraft.trim();
|
|
663
705
|
if (!text || props.isSending || submitting.value) return;
|
|
664
706
|
submitting.value = true;
|
|
707
|
+
setDraft("");
|
|
665
708
|
try {
|
|
666
709
|
const shouldClear = await props.onSendMessage(text);
|
|
667
|
-
if (shouldClear
|
|
710
|
+
if (shouldClear === false && latestDraft.length === 0) setDraft(originalDraft);
|
|
668
711
|
} finally {
|
|
669
712
|
submitting.value = false;
|
|
670
713
|
}
|
|
@@ -757,7 +800,6 @@ var ConversationView = defineComponent3({
|
|
|
757
800
|
class: cx(!props.unstyled && "ckui-composer__input", props.classNames?.input, props.composerProps?.class),
|
|
758
801
|
style: [props.styles?.input, props.composerProps?.style],
|
|
759
802
|
value: draft(),
|
|
760
|
-
disabled: props.isSending || submitting.value,
|
|
761
803
|
onInput: (event) => {
|
|
762
804
|
const handler = props.composerProps?.onInput;
|
|
763
805
|
if (typeof handler === "function") handler(event);
|
|
@@ -1398,6 +1440,7 @@ export {
|
|
|
1398
1440
|
defaultConvoKitTheme,
|
|
1399
1441
|
defaultReadersResolver,
|
|
1400
1442
|
formatFileSize,
|
|
1443
|
+
isConvoKitPendingMessage,
|
|
1401
1444
|
matchesConversation,
|
|
1402
1445
|
mergeConversations,
|
|
1403
1446
|
mergeMessages,
|