@convokitapp/vue-ui 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -165,6 +165,8 @@ function useConversation(options) {
165
165
  let sentTyping = false;
166
166
  let typingTimer = null;
167
167
  let subscriptions = [];
168
+ const pendingIds = /* @__PURE__ */ new Set();
169
+ let pendingSequence = 0;
168
170
  const unsubscribe = async () => {
169
171
  const active = subscriptions;
170
172
  subscriptions = [];
@@ -188,7 +190,12 @@ function useConversation(options) {
188
190
  subscriptions = [
189
191
  client.onMessage(conversationId, (message) => {
190
192
  if (disposed || activeGeneration !== generation) return;
191
- messages.value = mergeMessages(messages.value, [message]);
193
+ const pending = messages.value.find((candidate) => pendingIds.has(candidate.id) && message.senderId === client.currentUserId && candidate.text === message.text && candidate.media.length === message.media.length);
194
+ if (pending) pendingIds.delete(pending.id);
195
+ messages.value = mergeMessages(
196
+ pending ? messages.value.filter((candidate) => candidate.id !== pending.id) : messages.value,
197
+ [message]
198
+ );
192
199
  if ((options.markReadOnReceive ?? true) && message.senderId !== client.currentUserId) void markRead();
193
200
  }, report),
194
201
  client.onReadReceipt(conversationId, ({ userId, readAt }) => {
@@ -212,6 +219,7 @@ function useConversation(options) {
212
219
  await unsubscribe();
213
220
  if (disposed || activeGeneration !== generation) return;
214
221
  messages.value = [];
222
+ pendingIds.clear();
215
223
  conversation.value = null;
216
224
  typingUserIds.value = /* @__PURE__ */ new Set();
217
225
  readAtByUserId.value = /* @__PURE__ */ new Map();
@@ -251,7 +259,7 @@ function useConversation(options) {
251
259
  const page = await client.getMessages({
252
260
  conversationId,
253
261
  limit: messagePageSize,
254
- offset: messages.value.length
262
+ offset: messages.value.filter((message) => !pendingIds.has(message.id)).length
255
263
  });
256
264
  if (disposed || activeGeneration !== generation) return;
257
265
  messages.value = mergeMessages(messages.value, page);
@@ -282,6 +290,18 @@ function useConversation(options) {
282
290
  const normalizedText = text?.trim();
283
291
  if (!normalizedText && (!media || media.length === 0)) return null;
284
292
  if (isSending.value) return null;
293
+ const pendingId = `convokit-pending-${Date.now()}-${++pendingSequence}`;
294
+ const pendingMessage = {
295
+ id: pendingId,
296
+ conversationId: toValue(options.conversationId),
297
+ senderId: toValue(options.client).currentUserId,
298
+ text: normalizedText ?? null,
299
+ media: media ?? [],
300
+ createdAt: /* @__PURE__ */ new Date(),
301
+ updatedAt: null
302
+ };
303
+ pendingIds.add(pendingId);
304
+ messages.value = mergeMessages(messages.value, [pendingMessage]);
285
305
  isSending.value = true;
286
306
  error.value = null;
287
307
  const activeGeneration = generation;
@@ -291,11 +311,21 @@ function useConversation(options) {
291
311
  ...normalizedText ? { text: normalizedText } : {},
292
312
  ...media?.length ? { media } : {}
293
313
  });
294
- if (!disposed && activeGeneration === generation) messages.value = mergeMessages(messages.value, [message]);
314
+ pendingIds.delete(pendingId);
315
+ if (!disposed && activeGeneration === generation) {
316
+ messages.value = mergeMessages(
317
+ messages.value.filter((candidate) => candidate.id !== pendingId),
318
+ [message]
319
+ );
320
+ }
295
321
  await updateTyping(false);
296
322
  return message;
297
323
  } catch (cause) {
298
- if (!disposed) error.value = cause;
324
+ pendingIds.delete(pendingId);
325
+ if (!disposed) {
326
+ messages.value = messages.value.filter((candidate) => candidate.id !== pendingId);
327
+ error.value = cause;
328
+ }
299
329
  return null;
300
330
  } finally {
301
331
  if (!disposed && activeGeneration === generation) isSending.value = false;
@@ -651,7 +681,9 @@ var ConversationView = defineComponent3({
651
681
  ...props.styles ? { styles: props.styles } : {}
652
682
  });
653
683
  const draft = () => props.modelValue ?? internalDraft.value;
684
+ let latestDraft = draft();
654
685
  const setDraft = (value) => {
686
+ latestDraft = value;
655
687
  if (props.modelValue === void 0) internalDraft.value = value;
656
688
  props.onDraftChange?.(value);
657
689
  emit("update:modelValue", value);
@@ -659,12 +691,14 @@ var ConversationView = defineComponent3({
659
691
  void props.onTypingChange?.(isTyping);
660
692
  };
661
693
  const submit = async () => {
662
- const text = draft().trim();
694
+ const originalDraft = draft();
695
+ const text = originalDraft.trim();
663
696
  if (!text || props.isSending || submitting.value) return;
664
697
  submitting.value = true;
698
+ setDraft("");
665
699
  try {
666
700
  const shouldClear = await props.onSendMessage(text);
667
- if (shouldClear !== false) setDraft("");
701
+ if (shouldClear === false && latestDraft.length === 0) setDraft(originalDraft);
668
702
  } finally {
669
703
  submitting.value = false;
670
704
  }
@@ -757,7 +791,6 @@ var ConversationView = defineComponent3({
757
791
  class: cx(!props.unstyled && "ckui-composer__input", props.classNames?.input, props.composerProps?.class),
758
792
  style: [props.styles?.input, props.composerProps?.style],
759
793
  value: draft(),
760
- disabled: props.isSending || submitting.value,
761
794
  onInput: (event) => {
762
795
  const handler = props.composerProps?.onInput;
763
796
  if (typeof handler === "function") handler(event);