@convokitapp/vue-ui 0.2.1 → 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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.2
4
+
5
+ - Show `Sending…` for optimistic messages instead of a device-clock timestamp that can jump after acknowledgement.
6
+ - Keep pending messages after canonical history and exclude them from read receipts.
7
+ - Continue displaying acknowledged server timestamps in the viewer's local timezone.
8
+
3
9
  ## 0.2.1
4
10
 
5
11
  - Show outgoing messages immediately while the request completes, then reconcile the server response and realtime echo without duplicates.
package/PARITY.md CHANGED
@@ -16,6 +16,7 @@ while using Vue-native composition patterns.
16
16
  | Offset pagination and de-duplication | Yes | Yes | Yes |
17
17
  | Search, archived, participant, predicate, sort filters | Yes | Yes | Yes |
18
18
  | Realtime messages, typing, reads | Yes | Yes | Yes |
19
+ | Pending state without provisional timestamps or receipts | Yes | Yes | Yes |
19
20
  | Read markers and reader resolution | Yes | Yes | Yes |
20
21
  | Mark read on load/receive | Yes | Yes | Yes |
21
22
  | Typing idle timeout | Yes | Yes | Yes |
package/README.md CHANGED
@@ -87,6 +87,10 @@ Use `useConversationList` and `useConversation` when you want ConvoKit's
87
87
  pagination, de-duplication, realtime, typing, and read state without the
88
88
  default UI. Both return readonly Vue refs plus actions and a `dispose()` method.
89
89
 
90
+ Optimistic rows display `Sending…` until acknowledgement. The final server
91
+ timestamp is formatted in the viewer's local timezone. Custom message slots can
92
+ use `isConvoKitPendingMessage(message)` to present the same state.
93
+
90
94
  ## Appearance
91
95
 
92
96
  Wrap any subtree with `ConvoKitThemeProvider`, set `density="compact"`, or use
package/dist/index.cjs CHANGED
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  defaultConvoKitTheme: () => defaultConvoKitTheme,
33
33
  defaultReadersResolver: () => defaultReadersResolver,
34
34
  formatFileSize: () => formatFileSize,
35
+ isConvoKitPendingMessage: () => isConvoKitPendingMessage,
35
36
  matchesConversation: () => matchesConversation,
36
37
  mergeConversations: () => mergeConversations,
37
38
  mergeMessages: () => mergeMessages,
@@ -75,6 +76,10 @@ var import_vue = require("vue");
75
76
 
76
77
  // src/utils.ts
77
78
  var import_clsx = require("clsx");
79
+ var pendingMessageIdPrefix = "convokit-pending-";
80
+ function isConvoKitPendingMessage(message) {
81
+ return message.id.startsWith(pendingMessageIdPrefix);
82
+ }
78
83
  function cx(...values) {
79
84
  return (0, import_clsx.clsx)(values);
80
85
  }
@@ -117,11 +122,15 @@ function mergeMessages(current, incoming) {
117
122
  const byId = new Map(current.map((message) => [message.id, message]));
118
123
  for (const message of incoming) byId.set(message.id, message);
119
124
  return [...byId.values()].sort((left, right) => {
125
+ const leftPending = isConvoKitPendingMessage(left);
126
+ const rightPending = isConvoKitPendingMessage(right);
127
+ if (leftPending !== rightPending) return leftPending ? 1 : -1;
120
128
  const byTime = left.createdAt.getTime() - right.createdAt.getTime();
121
129
  return byTime === 0 ? left.id.localeCompare(right.id) : byTime;
122
130
  });
123
131
  }
124
132
  function readerIdsFor(message, readAtByUserId) {
133
+ if (isConvoKitPendingMessage(message)) return /* @__PURE__ */ new Set();
125
134
  return new Set([...readAtByUserId.entries()].filter(([userId, readAt]) => userId !== message.senderId && readAt.getTime() >= message.createdAt.getTime()).map(([userId]) => userId));
126
135
  }
127
136
  function partClass(part, appearance, defaultClass) {
@@ -520,7 +529,8 @@ var MessageListView = (0, import_vue4.defineComponent)({
520
529
  const renderMessage = (message, index) => {
521
530
  const isCurrentUser = message.senderId === props.currentUserId;
522
531
  const sender = participants.value.get(message.senderId);
523
- const readerIds = props.readersResolver ? props.readersResolver(message) : readerIdsFor(message, props.readAtByUserId);
532
+ const isPending = isConvoKitPendingMessage(message);
533
+ const readerIds = isPending ? /* @__PURE__ */ new Set() : props.readersResolver ? props.readersResolver(message) : readerIdsFor(message, props.readAtByUserId);
524
534
  const slotProps = { message, chronologicalIndex: index, isCurrentUser, sender, readerIds };
525
535
  const custom = slots.message?.(slotProps);
526
536
  if (custom) return (0, import_vue4.h)("div", { key: message.id, role: "listitem" }, custom);
@@ -553,12 +563,12 @@ var MessageListView = (0, import_vue4.defineComponent)({
553
563
  !isCurrentUser ? (0, import_vue4.h)("strong", { class: "ckui-message-sender" }, sender?.name || message.senderId) : null,
554
564
  message.text ? (0, import_vue4.h)("div", { class: "ckui-message-text" }, message.text) : null,
555
565
  ...mediaNodes,
556
- (0, import_vue4.h)("time", { class: "ckui-message-time", datetime: message.createdAt.toISOString() }, [
557
- props.formatTime(message.createdAt),
558
- isCurrentUser ? readerIds.size > 0 ? (0, import_vue4.h)(import_vue3.CheckCheck, { size: 14, "aria-label": "Read" }) : (0, import_vue4.h)(import_vue3.Check, { size: 14, "aria-label": "Delivered" }) : null
566
+ (0, import_vue4.h)("span", { class: "ckui-message-time" }, [
567
+ isPending ? "Sending\u2026" : props.formatTime(message.createdAt),
568
+ isCurrentUser && !isPending ? readerIds.size > 0 ? (0, import_vue4.h)(import_vue3.CheckCheck, { size: 14, "aria-label": "Read" }) : (0, import_vue4.h)(import_vue3.Check, { size: 14, "aria-label": "Delivered" }) : null
559
569
  ])
560
570
  ]),
561
- isCurrentUser ? slots["read-receipt"]?.(receiptSlotProps) ?? (0, import_vue4.h)("div", {
571
+ isCurrentUser && !isPending ? slots["read-receipt"]?.(receiptSlotProps) ?? (0, import_vue4.h)("div", {
562
572
  class: partClass("receipt", currentAppearance, "ckui-read-receipt"),
563
573
  style: partStyle("receipt", currentAppearance)
564
574
  }, readerIds.size > 0 ? `Read by ${readerIds.size}` : "Delivered") : null
@@ -1427,6 +1437,7 @@ function useConvoKitTheme() {
1427
1437
  defaultConvoKitTheme,
1428
1438
  defaultReadersResolver,
1429
1439
  formatFileSize,
1440
+ isConvoKitPendingMessage,
1430
1441
  matchesConversation,
1431
1442
  mergeConversations,
1432
1443
  mergeMessages,