@convokitapp/vue-ui 0.5.0 → 0.7.0
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 +98 -0
- package/PARITY.md +45 -0
- package/README.md +125 -1
- package/dist/index.cjs +418 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +56 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +129 -21
- package/dist/index.d.ts +129 -21
- package/dist/index.js +417 -53
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
isConvoKitPendingMessage: () => isConvoKitPendingMessage,
|
|
36
36
|
matchesConversation: () => matchesConversation,
|
|
37
37
|
mergeConversations: () => mergeConversations,
|
|
38
|
+
mergeInboxEntries: () => mergeInboxEntries,
|
|
38
39
|
mergeMessages: () => mergeMessages,
|
|
39
40
|
readerIdsFor: () => readerIdsFor,
|
|
40
41
|
useConversation: () => useConversation,
|
|
@@ -54,6 +55,10 @@ function createConvoKitUiClient(client) {
|
|
|
54
55
|
onEvent: handler,
|
|
55
56
|
...onError ? { onError } : {}
|
|
56
57
|
}),
|
|
58
|
+
onInboxActivity: (handler, onError) => client.realtime.onInboxActivity(client.clientId, {
|
|
59
|
+
onEvent: handler,
|
|
60
|
+
...onError ? { onError } : {}
|
|
61
|
+
}),
|
|
57
62
|
onMessageDeleted: (conversationId, handler, onError) => client.realtime.onMessageDeleted(conversationId, {
|
|
58
63
|
onEvent: handler,
|
|
59
64
|
...onError ? { onError } : {}
|
|
@@ -62,11 +67,14 @@ function createConvoKitUiClient(client) {
|
|
|
62
67
|
return client.connected ? client.currentUserId : "";
|
|
63
68
|
},
|
|
64
69
|
getConversations: (options) => client.getConversations(options),
|
|
70
|
+
listInbox: (options) => client.listInbox(options),
|
|
65
71
|
getConversation: (conversationId) => client.getConversation(conversationId),
|
|
66
72
|
getMessages: (options) => client.getMessages(options),
|
|
67
73
|
getMessage: (id) => client.getMessage(id),
|
|
68
74
|
sendMessage: (input) => client.sendMessage(input),
|
|
69
75
|
markConversationRead: (conversationId, options) => client.markConversationRead(conversationId, options),
|
|
76
|
+
markConversationUnread: (conversationId) => client.markConversationUnread(conversationId),
|
|
77
|
+
clearConversationUnread: (conversationId, options) => client.clearConversationUnread(conversationId, options),
|
|
70
78
|
sendTyping: (input) => client.sendTyping(input),
|
|
71
79
|
onMessage: (conversationId, handler, onError) => client.realtime.onMessage(conversationId, {
|
|
72
80
|
onEvent: handler,
|
|
@@ -136,6 +144,31 @@ function mergeConversations(current, incoming) {
|
|
|
136
144
|
for (const conversation of incoming) byId.set(conversation.id, conversation);
|
|
137
145
|
return [...byId.values()];
|
|
138
146
|
}
|
|
147
|
+
function compareInboxOrder(left, right) {
|
|
148
|
+
return right.activityAt.getTime() - left.activityAt.getTime() || (left.conversation.id < right.conversation.id ? 1 : left.conversation.id > right.conversation.id ? -1 : 0);
|
|
149
|
+
}
|
|
150
|
+
function mergeInboxEntries(current, incoming) {
|
|
151
|
+
const byId = new Map(current.map((entry) => [entry.conversation.id, entry]));
|
|
152
|
+
for (const entry of incoming) byId.set(entry.conversation.id, entry);
|
|
153
|
+
return [...byId.values()].sort(compareInboxOrder);
|
|
154
|
+
}
|
|
155
|
+
function inboxPreview(conversation, summary, currentUserId) {
|
|
156
|
+
const message = summary?.latestMessage;
|
|
157
|
+
if (!message) return "";
|
|
158
|
+
const first = message.media[0];
|
|
159
|
+
const body = message.text?.trim() || (!first ? "" : first.type === "image" ? "Photo" : first.type === "file" ? first.name?.trim() || "File" : first.type === "location" ? "Location" : first.type === "contact" ? "Contact" : "");
|
|
160
|
+
if (!body) return "";
|
|
161
|
+
if (currentUserId !== void 0 && message.senderId === currentUserId) return `You: ${body}`;
|
|
162
|
+
if (conversation.participants.length > 2) {
|
|
163
|
+
const sender = conversation.participants.find((participant) => participant.appUserId === message.senderId || participant.id === message.senderId);
|
|
164
|
+
const name = sender?.name.trim();
|
|
165
|
+
if (name) return `${name}: ${body}`;
|
|
166
|
+
}
|
|
167
|
+
return body;
|
|
168
|
+
}
|
|
169
|
+
function formatMessageTime(date) {
|
|
170
|
+
return new Intl.DateTimeFormat(void 0, { hour: "numeric", minute: "2-digit" }).format(date);
|
|
171
|
+
}
|
|
139
172
|
function mergeMessages(current, incoming) {
|
|
140
173
|
const byId = new Map(current.map((message) => [message.id, message]));
|
|
141
174
|
for (const message of incoming) byId.set(message.id, message);
|
|
@@ -226,6 +259,10 @@ function readEntry(participant) {
|
|
|
226
259
|
function acknowledgement() {
|
|
227
260
|
return { inFlight: void 0, followUp: false, suppressed: false, target: void 0, acknowledged: void 0, unacknowledgeable: /* @__PURE__ */ new Set() };
|
|
228
261
|
}
|
|
262
|
+
function capture(conversation) {
|
|
263
|
+
const membership = conversation?.membership;
|
|
264
|
+
return { version: membership?.privateStateVersion, clearPending: membership?.unreadMarkedAt != null };
|
|
265
|
+
}
|
|
229
266
|
function isTargetMiss(cause) {
|
|
230
267
|
if (typeof cause !== "object" || cause === null) return false;
|
|
231
268
|
const { code, status } = cause;
|
|
@@ -286,6 +323,7 @@ var ConversationStore = class {
|
|
|
286
323
|
// Keep tombstones until an explicit reload/session change, including across refreshes.
|
|
287
324
|
deleted = /* @__PURE__ */ new Set();
|
|
288
325
|
ack = acknowledgement();
|
|
326
|
+
captured = capture();
|
|
289
327
|
// Visible until the platform reports otherwise; unknown/prerender/no document count as visible.
|
|
290
328
|
visible = true;
|
|
291
329
|
sendRevision;
|
|
@@ -351,6 +389,7 @@ var ConversationStore = class {
|
|
|
351
389
|
this.hydrationPool.queued.clear();
|
|
352
390
|
this.deleted.clear();
|
|
353
391
|
this.ack = acknowledgement();
|
|
392
|
+
this.captured = capture();
|
|
354
393
|
this.sendRevision = void 0;
|
|
355
394
|
this.activeSend = void 0;
|
|
356
395
|
this.refreshQueued = false;
|
|
@@ -595,9 +634,13 @@ var ConversationStore = class {
|
|
|
595
634
|
this.validatePage(page);
|
|
596
635
|
this.cursor = page.at(-1);
|
|
597
636
|
this.patch({ conversation, messages: this.overlay(page, revision), hasOlderMessages: page.length === this.pageSize });
|
|
637
|
+
this.captured = capture(conversation);
|
|
598
638
|
this.mergeReads(conversation.participants.map(readEntry));
|
|
599
639
|
this.prune(revision);
|
|
600
|
-
if (this.options.markReadOnLoad ?? true)
|
|
640
|
+
if ((this.options.markReadOnLoad ?? true) || this.ack.suppressed) {
|
|
641
|
+
this.ack.suppressed = false;
|
|
642
|
+
await this.acknowledge(true);
|
|
643
|
+
}
|
|
601
644
|
} catch (cause) {
|
|
602
645
|
this.fail(cause, generation, true);
|
|
603
646
|
} finally {
|
|
@@ -654,9 +697,12 @@ var ConversationStore = class {
|
|
|
654
697
|
const survivingIds = new Set(reconciled.map((message) => message.id));
|
|
655
698
|
const known = this.state.messages.filter((message) => !isConvoKitPendingMessage(message)).map((message) => message.id).concat([...this.changes].filter(([, change]) => change.insert && change.revision <= revision).map(([id]) => id));
|
|
656
699
|
for (const id of known) if (!survivingIds.has(id)) this.forget(id);
|
|
700
|
+
const opening = this.state.conversation === null;
|
|
657
701
|
this.patch({ conversation, messages: reconciled, hasOlderMessages: hasOlder });
|
|
702
|
+
if (opening) this.captured = capture(conversation);
|
|
658
703
|
this.mergeReads(conversation.participants.map(readEntry));
|
|
659
704
|
this.prune(revision);
|
|
705
|
+
if (opening) void this.resumeAcknowledgement();
|
|
660
706
|
} catch (cause) {
|
|
661
707
|
this.fail(cause, generation, true);
|
|
662
708
|
} finally {
|
|
@@ -690,15 +736,22 @@ var ConversationStore = class {
|
|
|
690
736
|
}
|
|
691
737
|
}
|
|
692
738
|
};
|
|
693
|
-
/** Acknowledge through the newest rendered row now, regardless of visibility; no
|
|
739
|
+
/** Acknowledge through the newest rendered row now, regardless of visibility; no acknowledgement without a
|
|
740
|
+
* target (a room opened with a marker that renders nothing clears the marker instead, once).
|
|
741
|
+
*/
|
|
694
742
|
markRead = () => this.alive() ? this.acknowledge(false) : Promise.resolve();
|
|
695
743
|
/** Automatic acknowledgements wait while hidden and are re-issued (once) on becoming visible. */
|
|
696
744
|
setVisible = (visible) => {
|
|
697
745
|
this.visible = visible;
|
|
698
|
-
if (!visible || !this.
|
|
699
|
-
this.
|
|
700
|
-
void this.acknowledge(true);
|
|
746
|
+
if (!visible || !this.alive()) return;
|
|
747
|
+
void this.resumeAcknowledgement();
|
|
701
748
|
};
|
|
749
|
+
/** Re-issue (once) the automatic acknowledgement that waited while hidden or before this open's DTO, if any. */
|
|
750
|
+
resumeAcknowledgement() {
|
|
751
|
+
if (!this.ack.suppressed) return Promise.resolve();
|
|
752
|
+
this.ack.suppressed = false;
|
|
753
|
+
return this.acknowledge(true);
|
|
754
|
+
}
|
|
702
755
|
/** The newest non-pending rendered row by (createdAt, id), never by list index and never a raw realtime
|
|
703
756
|
* row; rows the server does not know are skipped, and nothing at or before the accepted target is re-sent.
|
|
704
757
|
*/
|
|
@@ -713,7 +766,7 @@ var ConversationStore = class {
|
|
|
713
766
|
/** Resolves when the request this call issued or joined settles; a follow-up is issued, not awaited. */
|
|
714
767
|
acknowledge(automatic) {
|
|
715
768
|
const ack = this.ack;
|
|
716
|
-
if (automatic && !this.visible) {
|
|
769
|
+
if (automatic && (!this.visible || this.state.conversation === null)) {
|
|
717
770
|
ack.suppressed = true;
|
|
718
771
|
return Promise.resolve();
|
|
719
772
|
}
|
|
@@ -726,9 +779,10 @@ var ConversationStore = class {
|
|
|
726
779
|
issue(ack, generation) {
|
|
727
780
|
ack.followUp = false;
|
|
728
781
|
const target = this.ackTarget(ack);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
ack.
|
|
782
|
+
const request = target ? this.send(ack, generation, target) : this.clearMarker(ack, generation);
|
|
783
|
+
if (!request) return void 0;
|
|
784
|
+
ack.target = target?.id;
|
|
785
|
+
ack.inFlight = request.finally(() => {
|
|
732
786
|
ack.inFlight = void 0;
|
|
733
787
|
ack.target = void 0;
|
|
734
788
|
if (!this.alive(generation) || !ack.followUp) return;
|
|
@@ -739,9 +793,31 @@ var ConversationStore = class {
|
|
|
739
793
|
});
|
|
740
794
|
return ack.inFlight;
|
|
741
795
|
}
|
|
796
|
+
/** A room opened with the caller's unread marker that renders no non-pending, acknowledgeable row cannot clear
|
|
797
|
+
* it through a targeted acknowledgement, so it asks the adapter to clear the marker conditionally on the captured
|
|
798
|
+
* version, once per open, under the acknowledgement triggers and visibility gating. Rows rendered later clear it
|
|
799
|
+
* through their acknowledgements; adapters without the member leave it; `cleared: false` is not an error.
|
|
800
|
+
*/
|
|
801
|
+
clearMarker(ack, generation) {
|
|
802
|
+
const captured = this.captured;
|
|
803
|
+
if (!captured.clearPending || captured.version === void 0 || typeof this.client.clearConversationUnread !== "function" || this.state.messages.some((message) => !isConvoKitPendingMessage(message) && !ack.unacknowledgeable.has(message.id))) return void 0;
|
|
804
|
+
captured.clearPending = false;
|
|
805
|
+
return this.clearUnread(captured.version, generation);
|
|
806
|
+
}
|
|
807
|
+
async clearUnread(version2, generation) {
|
|
808
|
+
try {
|
|
809
|
+
await this.client.clearConversationUnread(this.room, { ifVersion: version2 });
|
|
810
|
+
} catch (cause) {
|
|
811
|
+
if (this.alive(generation)) this.fail(cause, generation);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
742
814
|
async send(ack, generation, target) {
|
|
743
815
|
try {
|
|
744
|
-
|
|
816
|
+
const version2 = this.captured.version;
|
|
817
|
+
await this.client.markConversationRead(this.room, {
|
|
818
|
+
throughMessageId: target.id,
|
|
819
|
+
...version2 === void 0 ? {} : { privateStateVersion: version2 }
|
|
820
|
+
});
|
|
745
821
|
if (!this.alive(generation)) return;
|
|
746
822
|
if (!ack.acknowledged || compare(target, ack.acknowledged) > 0) ack.acknowledged = target;
|
|
747
823
|
} catch (cause) {
|
|
@@ -911,9 +987,6 @@ var appearanceProps = {
|
|
|
911
987
|
density: { type: String, default: "comfortable" },
|
|
912
988
|
unstyled: { type: Boolean, default: false }
|
|
913
989
|
};
|
|
914
|
-
function defaultFormatTime(date) {
|
|
915
|
-
return new Intl.DateTimeFormat(void 0, { hour: "numeric", minute: "2-digit" }).format(date);
|
|
916
|
-
}
|
|
917
990
|
function defaultMedia(media, open, imageLoading) {
|
|
918
991
|
const tag = open ? "button" : "div";
|
|
919
992
|
const interactive = open ? { type: "button", onClick: open } : {};
|
|
@@ -964,7 +1037,7 @@ var MessageListView = (0, import_vue5.defineComponent)({
|
|
|
964
1037
|
paginationThreshold: { type: Number, default: 240 },
|
|
965
1038
|
reverse: { type: Boolean, default: true },
|
|
966
1039
|
stickToBottom: { type: Boolean, default: true },
|
|
967
|
-
formatTime: { type: Function, default:
|
|
1040
|
+
formatTime: { type: Function, default: formatMessageTime },
|
|
968
1041
|
imageLoading: { type: String, default: "lazy" }
|
|
969
1042
|
},
|
|
970
1043
|
emits: ["load-older", "attachment-click"],
|
|
@@ -1521,30 +1594,64 @@ var import_vue10 = require("vue");
|
|
|
1521
1594
|
var import_vue8 = require("vue");
|
|
1522
1595
|
|
|
1523
1596
|
// src/conversation-list-store.ts
|
|
1597
|
+
var defaultActivityRefreshWindowMs = 500;
|
|
1524
1598
|
function blank2(filter) {
|
|
1525
|
-
return {
|
|
1599
|
+
return {
|
|
1600
|
+
conversations: [],
|
|
1601
|
+
summaries: /* @__PURE__ */ new Map(),
|
|
1602
|
+
currentUserId: "",
|
|
1603
|
+
filter,
|
|
1604
|
+
isInitialLoading: false,
|
|
1605
|
+
isLoadingMore: false,
|
|
1606
|
+
hasMore: true,
|
|
1607
|
+
hasLoaded: false,
|
|
1608
|
+
error: null
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
function statusOf(cause) {
|
|
1612
|
+
return typeof cause === "object" && cause !== null && "status" in cause ? cause.status : void 0;
|
|
1613
|
+
}
|
|
1614
|
+
function summaryOf(entry) {
|
|
1615
|
+
const { conversation: _conversation, ...summary } = entry;
|
|
1616
|
+
return summary;
|
|
1617
|
+
}
|
|
1618
|
+
function ids(entries) {
|
|
1619
|
+
return entries.map((entry) => entry.conversation);
|
|
1526
1620
|
}
|
|
1527
1621
|
var ConversationListStore = class {
|
|
1528
1622
|
constructor(options) {
|
|
1529
1623
|
this.options = options;
|
|
1530
1624
|
this.owner = options.client.sessionIdentity;
|
|
1625
|
+
this.user = this.owner ? options.client.currentUserId : "";
|
|
1531
1626
|
this.pageSize = options.pageSize ?? 30;
|
|
1532
1627
|
if (!Number.isInteger(this.pageSize) || this.pageSize < 1 || this.pageSize > 100) {
|
|
1533
1628
|
throw new RangeError("pageSize must be an integer between 1 and 100");
|
|
1534
1629
|
}
|
|
1630
|
+
this.activityRefreshWindowMs = options.activityRefreshWindowMs ?? defaultActivityRefreshWindowMs;
|
|
1631
|
+
if (!Number.isFinite(this.activityRefreshWindowMs) || this.activityRefreshWindowMs < 0) {
|
|
1632
|
+
throw new RangeError("activityRefreshWindowMs must be a non-negative number");
|
|
1633
|
+
}
|
|
1535
1634
|
this.state = blank2(options.initialFilter ?? {});
|
|
1536
1635
|
}
|
|
1537
1636
|
options;
|
|
1538
1637
|
owner;
|
|
1638
|
+
user;
|
|
1539
1639
|
pageSize;
|
|
1640
|
+
activityRefreshWindowMs;
|
|
1540
1641
|
state;
|
|
1541
1642
|
source = [];
|
|
1643
|
+
entries = [];
|
|
1542
1644
|
offset = 0;
|
|
1645
|
+
cursor = null;
|
|
1646
|
+
inboxUnavailable = false;
|
|
1647
|
+
inboxWarned = false;
|
|
1543
1648
|
generation = 0;
|
|
1544
1649
|
lifecycleGeneration = 0;
|
|
1545
1650
|
disposed = true;
|
|
1546
1651
|
lifecycle;
|
|
1547
1652
|
inbox;
|
|
1653
|
+
activity;
|
|
1654
|
+
activityTimer;
|
|
1548
1655
|
refreshQueued = false;
|
|
1549
1656
|
refreshing = false;
|
|
1550
1657
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -1562,12 +1669,27 @@ var ConversationListStore = class {
|
|
|
1562
1669
|
alive(generation = this.generation) {
|
|
1563
1670
|
return !this.disposed && generation === this.generation && this.owner !== null && this.options.client.sessionIdentity === this.owner;
|
|
1564
1671
|
}
|
|
1672
|
+
get inboxMode() {
|
|
1673
|
+
return !this.options.pageLoader && typeof this.options.client.listInbox === "function" && !this.inboxUnavailable;
|
|
1674
|
+
}
|
|
1675
|
+
currentUserId() {
|
|
1676
|
+
return this.inboxMode ? this.user : "";
|
|
1677
|
+
}
|
|
1565
1678
|
start = (autoLoad = true) => {
|
|
1566
1679
|
if (!this.owner || this.options.client.sessionIdentity !== this.owner) return;
|
|
1567
1680
|
if (!this.disposed) return;
|
|
1568
1681
|
this.disposed = false;
|
|
1682
|
+
this.inboxUnavailable = false;
|
|
1569
1683
|
const lifecycleGeneration = ++this.lifecycleGeneration;
|
|
1684
|
+
const current = () => this.alive() && lifecycleGeneration === this.lifecycleGeneration;
|
|
1685
|
+
const reconcile = (cause) => {
|
|
1686
|
+
if (current()) {
|
|
1687
|
+
this.patch({ error: cause });
|
|
1688
|
+
this.queueRefresh();
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1570
1691
|
try {
|
|
1692
|
+
this.patch({ currentUserId: this.currentUserId() });
|
|
1571
1693
|
const subscription = this.options.client.onConnectionEvent({
|
|
1572
1694
|
onEvent: () => {
|
|
1573
1695
|
},
|
|
@@ -1579,15 +1701,20 @@ var ConversationListStore = class {
|
|
|
1579
1701
|
else void subscription.unsubscribe().catch(() => void 0);
|
|
1580
1702
|
if (!this.alive()) return;
|
|
1581
1703
|
const inbox = this.options.client.onInboxChanged(() => {
|
|
1582
|
-
if (
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
this.queueRefresh();
|
|
1587
|
-
}
|
|
1588
|
-
});
|
|
1704
|
+
if (!current()) return;
|
|
1705
|
+
this.clearActivityTimer();
|
|
1706
|
+
this.queueRefresh();
|
|
1707
|
+
}, reconcile);
|
|
1589
1708
|
if (this.alive()) this.inbox = inbox;
|
|
1590
1709
|
else void inbox.unsubscribe().catch(() => void 0);
|
|
1710
|
+
if (!this.alive()) return;
|
|
1711
|
+
if (this.inboxMode && typeof this.options.client.onInboxActivity === "function") {
|
|
1712
|
+
const activity = this.options.client.onInboxActivity(() => {
|
|
1713
|
+
if (current()) this.scheduleActivityRefresh(lifecycleGeneration);
|
|
1714
|
+
}, reconcile);
|
|
1715
|
+
if (this.alive()) this.activity = activity;
|
|
1716
|
+
else void activity.unsubscribe().catch(() => void 0);
|
|
1717
|
+
}
|
|
1591
1718
|
if (autoLoad) void this.loadInitial();
|
|
1592
1719
|
} catch (cause) {
|
|
1593
1720
|
if (this.alive()) this.patch({ error: cause });
|
|
@@ -1597,27 +1724,109 @@ var ConversationListStore = class {
|
|
|
1597
1724
|
this.disposed = true;
|
|
1598
1725
|
this.generation++;
|
|
1599
1726
|
this.lifecycleGeneration++;
|
|
1727
|
+
this.clearActivityTimer();
|
|
1600
1728
|
const subscription = this.lifecycle;
|
|
1601
1729
|
this.lifecycle = void 0;
|
|
1602
1730
|
if (subscription) void subscription.unsubscribe().catch(() => void 0);
|
|
1603
1731
|
if (this.inbox) void this.inbox.unsubscribe().catch(() => void 0);
|
|
1604
1732
|
this.inbox = void 0;
|
|
1733
|
+
this.stopActivity();
|
|
1605
1734
|
this.refreshQueued = false;
|
|
1606
1735
|
this.refreshing = false;
|
|
1607
1736
|
this.source = [];
|
|
1737
|
+
this.entries = [];
|
|
1608
1738
|
this.offset = 0;
|
|
1739
|
+
this.cursor = null;
|
|
1609
1740
|
this.patch(blank2(this.state.filter));
|
|
1610
1741
|
};
|
|
1742
|
+
stopActivity() {
|
|
1743
|
+
if (this.activity) void this.activity.unsubscribe().catch(() => void 0);
|
|
1744
|
+
this.activity = void 0;
|
|
1745
|
+
}
|
|
1746
|
+
clearActivityTimer() {
|
|
1747
|
+
if (this.activityTimer !== void 0) clearTimeout(this.activityTimer);
|
|
1748
|
+
this.activityTimer = void 0;
|
|
1749
|
+
}
|
|
1750
|
+
/** Max-wait throttle: the first signal opens a window; later signals wait for it; one refresh runs when it closes. */
|
|
1751
|
+
scheduleActivityRefresh(lifecycleGeneration) {
|
|
1752
|
+
if (this.activityRefreshWindowMs === 0) return this.queueRefresh();
|
|
1753
|
+
if (this.activityTimer !== void 0) return;
|
|
1754
|
+
const timer = setTimeout(() => {
|
|
1755
|
+
this.activityTimer = void 0;
|
|
1756
|
+
if (this.alive() && lifecycleGeneration === this.lifecycleGeneration) this.queueRefresh();
|
|
1757
|
+
}, this.activityRefreshWindowMs);
|
|
1758
|
+
timer.unref?.();
|
|
1759
|
+
this.activityTimer = timer;
|
|
1760
|
+
}
|
|
1611
1761
|
fail(cause, generation) {
|
|
1612
1762
|
if (!this.alive(generation)) return;
|
|
1613
|
-
const status =
|
|
1763
|
+
const status = statusOf(cause);
|
|
1614
1764
|
if (status === 401 || status === 403 || status === 404) {
|
|
1615
1765
|
this.source = [];
|
|
1766
|
+
this.entries = [];
|
|
1616
1767
|
this.offset = 0;
|
|
1617
|
-
this.
|
|
1768
|
+
this.cursor = null;
|
|
1769
|
+
this.patch({ conversations: [], summaries: /* @__PURE__ */ new Map(), hasMore: false });
|
|
1618
1770
|
}
|
|
1619
1771
|
this.patch({ error: cause });
|
|
1620
1772
|
}
|
|
1773
|
+
/** Run an operation in inbox mode, falling back to the legacy path for the rest of this store's life when the
|
|
1774
|
+
* inbox route is absent (404: rollback, staging). Loaded rows are kept and the same operation continues.
|
|
1775
|
+
*/
|
|
1776
|
+
async withFallback(generation, inbox, legacy) {
|
|
1777
|
+
if (!this.inboxMode) return legacy();
|
|
1778
|
+
try {
|
|
1779
|
+
await inbox();
|
|
1780
|
+
} catch (cause) {
|
|
1781
|
+
if (!this.alive(generation) || statusOf(cause) !== 404) throw cause;
|
|
1782
|
+
this.inboxUnavailable = true;
|
|
1783
|
+
this.clearActivityTimer();
|
|
1784
|
+
this.stopActivity();
|
|
1785
|
+
this.entries = [];
|
|
1786
|
+
this.cursor = null;
|
|
1787
|
+
this.offset = this.source.length;
|
|
1788
|
+
if (!this.inboxWarned) {
|
|
1789
|
+
this.inboxWarned = true;
|
|
1790
|
+
console.warn("ConvoKit inbox endpoint unavailable (404); using getConversations without previews or unread counts.");
|
|
1791
|
+
}
|
|
1792
|
+
this.patch({ summaries: /* @__PURE__ */ new Map(), currentUserId: "" });
|
|
1793
|
+
await legacy();
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
validateInboxPage(page, limit, requestedCursor) {
|
|
1797
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1798
|
+
for (const entry of page.entries) {
|
|
1799
|
+
const id = entry.conversation.id;
|
|
1800
|
+
if (!id.trim() || seen.has(id)) throw new Error("Invalid conversation page");
|
|
1801
|
+
seen.add(id);
|
|
1802
|
+
}
|
|
1803
|
+
if (page.entries.length > limit) throw new Error("Invalid conversation page");
|
|
1804
|
+
if (page.nextCursor !== null && (page.nextCursor === requestedCursor || page.entries.length === 0)) {
|
|
1805
|
+
throw new Error("Inbox pagination did not advance");
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
/** Swap rows, summaries, cursor and hasMore together, filtered by the filter current at commit time. */
|
|
1809
|
+
commitInbox(entries, cursor) {
|
|
1810
|
+
this.entries = entries;
|
|
1811
|
+
this.source = ids(entries);
|
|
1812
|
+
this.cursor = cursor;
|
|
1813
|
+
this.patch({
|
|
1814
|
+
conversations: applyConversationFilter(this.source, this.state.filter),
|
|
1815
|
+
summaries: new Map(entries.map((entry) => [entry.conversation.id, summaryOf(entry)])),
|
|
1816
|
+
hasMore: cursor !== null
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
async loadInboxUntilVisible(generation, filter) {
|
|
1820
|
+
const visibleBefore = applyConversationFilter(this.source, filter).length;
|
|
1821
|
+
while (this.alive(generation)) {
|
|
1822
|
+
const requested = this.cursor;
|
|
1823
|
+
const page = await this.options.client.listInbox({ limit: this.pageSize, cursor: requested, archived: filter.archived ?? false });
|
|
1824
|
+
if (!this.alive(generation)) return;
|
|
1825
|
+
this.validateInboxPage(page, this.pageSize, requested);
|
|
1826
|
+
this.commitInbox(mergeInboxEntries(this.entries, page.entries), page.nextCursor);
|
|
1827
|
+
if (page.nextCursor === null || this.state.conversations.length > visibleBefore) return;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1621
1830
|
async loadUntilVisible(generation, filter) {
|
|
1622
1831
|
const visibleBefore = applyConversationFilter(this.source, filter).length;
|
|
1623
1832
|
while (this.alive(generation)) {
|
|
@@ -1644,10 +1853,17 @@ var ConversationListStore = class {
|
|
|
1644
1853
|
const generation = ++this.generation;
|
|
1645
1854
|
this.refreshing = false;
|
|
1646
1855
|
this.source = [];
|
|
1856
|
+
this.entries = [];
|
|
1647
1857
|
this.offset = 0;
|
|
1648
|
-
this.
|
|
1858
|
+
this.cursor = null;
|
|
1859
|
+
this.patch({ ...blank2(this.state.filter), currentUserId: this.currentUserId(), isInitialLoading: true });
|
|
1860
|
+
const filter = this.state.filter;
|
|
1649
1861
|
try {
|
|
1650
|
-
await this.
|
|
1862
|
+
await this.withFallback(
|
|
1863
|
+
generation,
|
|
1864
|
+
() => this.loadInboxUntilVisible(generation, filter),
|
|
1865
|
+
() => this.loadUntilVisible(generation, filter)
|
|
1866
|
+
);
|
|
1651
1867
|
} catch (cause) {
|
|
1652
1868
|
this.fail(cause, generation);
|
|
1653
1869
|
} finally {
|
|
@@ -1669,6 +1885,47 @@ var ConversationListStore = class {
|
|
|
1669
1885
|
void this.refresh();
|
|
1670
1886
|
});
|
|
1671
1887
|
}
|
|
1888
|
+
/** Re-walk the inbox from the head until the loaded window is covered and something is visible, or the inbox
|
|
1889
|
+
* ends. Rooms that moved are re-positioned by the merge; an exhausted inbox publishes what it found.
|
|
1890
|
+
*/
|
|
1891
|
+
async refreshInbox(generation, filter) {
|
|
1892
|
+
const target = Math.max(this.pageSize, this.entries.length);
|
|
1893
|
+
let rows = [], consumed = 0, cursor = null;
|
|
1894
|
+
while (this.alive(generation)) {
|
|
1895
|
+
const remaining = target - consumed;
|
|
1896
|
+
const limit = remaining >= 1 ? Math.min(100, remaining) : this.pageSize;
|
|
1897
|
+
const page = await this.options.client.listInbox({ limit, cursor, archived: filter.archived ?? false });
|
|
1898
|
+
if (!this.alive(generation)) return;
|
|
1899
|
+
this.validateInboxPage(page, limit, cursor);
|
|
1900
|
+
rows = mergeInboxEntries(rows, page.entries);
|
|
1901
|
+
consumed += page.entries.length;
|
|
1902
|
+
cursor = page.nextCursor;
|
|
1903
|
+
if (cursor === null || consumed >= target && applyConversationFilter(ids(rows), this.state.filter).length > 0) break;
|
|
1904
|
+
}
|
|
1905
|
+
if (!this.alive(generation)) return;
|
|
1906
|
+
this.commitInbox(rows, cursor);
|
|
1907
|
+
}
|
|
1908
|
+
async refreshLegacy(generation, filter) {
|
|
1909
|
+
const target = Math.max(this.pageSize, this.offset);
|
|
1910
|
+
const compare2 = (a, b) => a.createdAt.getTime() - b.createdAt.getTime() || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
1911
|
+
const boundary = this.options.pageLoader ? void 0 : this.source.reduce((oldest, row) => !oldest || compare2(row, oldest) < 0 ? row : oldest, void 0);
|
|
1912
|
+
let rows = [], offset = 0, hasMore = true;
|
|
1913
|
+
while (this.alive(generation)) {
|
|
1914
|
+
const page = this.options.pageLoader ? await this.options.pageLoader({ limit: this.pageSize, offset, filter }) : await this.options.client.getConversations({ limit: this.pageSize, offset, archived: filter.archived ?? false });
|
|
1915
|
+
if (!this.alive(generation)) return;
|
|
1916
|
+
if (page.length > this.pageSize || page.some((row) => !row.id.trim())) throw new Error("Invalid conversation page");
|
|
1917
|
+
const merged = mergeConversations(rows, page);
|
|
1918
|
+
if (page.length === this.pageSize && merged.length === rows.length) throw new Error("Conversation pagination did not advance");
|
|
1919
|
+
rows = merged;
|
|
1920
|
+
offset += page.length;
|
|
1921
|
+
hasMore = page.length === this.pageSize;
|
|
1922
|
+
if (!hasMore || offset >= target && applyConversationFilter(rows, this.state.filter).length > 0 && (!boundary || page.some((row) => compare2(row, boundary) <= 0))) break;
|
|
1923
|
+
}
|
|
1924
|
+
if (!this.alive(generation)) return;
|
|
1925
|
+
this.source = rows;
|
|
1926
|
+
this.offset = offset;
|
|
1927
|
+
this.patch({ conversations: applyConversationFilter(rows, this.state.filter), hasMore });
|
|
1928
|
+
}
|
|
1672
1929
|
/** Replace the loaded window atomically, retaining filters and rows during transient failures. */
|
|
1673
1930
|
refresh = async () => {
|
|
1674
1931
|
if (!this.alive()) return;
|
|
@@ -1679,28 +1936,14 @@ var ConversationListStore = class {
|
|
|
1679
1936
|
if (!this.state.hasLoaded) return this.loadInitial();
|
|
1680
1937
|
const generation = this.generation;
|
|
1681
1938
|
const filter = this.state.filter;
|
|
1682
|
-
const target = Math.max(this.pageSize, this.offset);
|
|
1683
|
-
const compare2 = (a, b) => a.createdAt.getTime() - b.createdAt.getTime() || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
1684
|
-
const boundary = this.options.pageLoader ? void 0 : this.source.reduce((oldest, row) => !oldest || compare2(row, oldest) < 0 ? row : oldest, void 0);
|
|
1685
1939
|
this.refreshing = true;
|
|
1686
1940
|
this.patch({ error: null });
|
|
1687
1941
|
try {
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
const merged = mergeConversations(rows, page);
|
|
1694
|
-
if (page.length === this.pageSize && merged.length === rows.length) throw new Error("Conversation pagination did not advance");
|
|
1695
|
-
rows = merged;
|
|
1696
|
-
offset += page.length;
|
|
1697
|
-
hasMore = page.length === this.pageSize;
|
|
1698
|
-
if (!hasMore || offset >= target && applyConversationFilter(rows, this.state.filter).length > 0 && (!boundary || page.some((row) => compare2(row, boundary) <= 0))) break;
|
|
1699
|
-
}
|
|
1700
|
-
if (!this.alive(generation)) return;
|
|
1701
|
-
this.source = rows;
|
|
1702
|
-
this.offset = offset;
|
|
1703
|
-
this.patch({ conversations: applyConversationFilter(rows, this.state.filter), hasMore });
|
|
1942
|
+
await this.withFallback(
|
|
1943
|
+
generation,
|
|
1944
|
+
() => this.refreshInbox(generation, filter),
|
|
1945
|
+
() => this.refreshLegacy(generation, filter)
|
|
1946
|
+
);
|
|
1704
1947
|
} catch (cause) {
|
|
1705
1948
|
this.fail(cause, generation);
|
|
1706
1949
|
} finally {
|
|
@@ -1713,9 +1956,14 @@ var ConversationListStore = class {
|
|
|
1713
1956
|
loadMore = async () => {
|
|
1714
1957
|
if (!this.alive() || this.refreshing || this.state.isInitialLoading || this.state.isLoadingMore || !this.state.hasMore) return;
|
|
1715
1958
|
const generation = this.generation;
|
|
1959
|
+
const filter = this.state.filter;
|
|
1716
1960
|
this.patch({ isLoadingMore: true, error: null });
|
|
1717
1961
|
try {
|
|
1718
|
-
await this.
|
|
1962
|
+
await this.withFallback(
|
|
1963
|
+
generation,
|
|
1964
|
+
() => this.loadInboxUntilVisible(generation, filter),
|
|
1965
|
+
() => this.loadUntilVisible(generation, filter)
|
|
1966
|
+
);
|
|
1719
1967
|
} catch (cause) {
|
|
1720
1968
|
this.fail(cause, generation);
|
|
1721
1969
|
} finally {
|
|
@@ -1733,6 +1981,66 @@ var ConversationListStore = class {
|
|
|
1733
1981
|
else if (!this.state.conversations.length && this.state.hasMore) await this.loadMore();
|
|
1734
1982
|
};
|
|
1735
1983
|
setQuery = (query) => this.setFilter({ ...this.state.filter, query });
|
|
1984
|
+
/** Mark a room unread for the viewer only; the row's summary takes the response (D10). Rejects when the adapter
|
|
1985
|
+
* lacks `markConversationUnread` or the store is not active; a request failure is reported through `error`
|
|
1986
|
+
* without evicting rows and rejects.
|
|
1987
|
+
*/
|
|
1988
|
+
markUnread = async (conversationId) => {
|
|
1989
|
+
const client = this.options.client;
|
|
1990
|
+
if (typeof client.markConversationUnread !== "function") {
|
|
1991
|
+
throw new TypeError("markUnread requires a ConvoKitUiClient adapter with markConversationUnread (core SDK 0.7)");
|
|
1992
|
+
}
|
|
1993
|
+
this.assertActive();
|
|
1994
|
+
this.applyPrivateState(conversationId, await this.mutate(client.markConversationUnread(conversationId)));
|
|
1995
|
+
};
|
|
1996
|
+
/** Remove the viewer's marker (conditionally on `options.ifVersion`); resolves to the response's `cleared` ("this
|
|
1997
|
+
* request removed the marker", not "the room is read") and patches the summary on true and false alike (D10).
|
|
1998
|
+
* Rejects when the adapter lacks `clearConversationUnread` or the store is not active; failures are reported like
|
|
1999
|
+
* `markUnread`.
|
|
2000
|
+
*/
|
|
2001
|
+
clearUnread = async (conversationId, options) => {
|
|
2002
|
+
const client = this.options.client;
|
|
2003
|
+
if (typeof client.clearConversationUnread !== "function") {
|
|
2004
|
+
throw new TypeError("clearUnread requires a ConvoKitUiClient adapter with clearConversationUnread (core SDK 0.7)");
|
|
2005
|
+
}
|
|
2006
|
+
this.assertActive();
|
|
2007
|
+
const result = await this.mutate(client.clearConversationUnread(conversationId, options));
|
|
2008
|
+
this.applyPrivateState(conversationId, result);
|
|
2009
|
+
return result.cleared;
|
|
2010
|
+
};
|
|
2011
|
+
/** A disposed or session-evicted store never sends a private-state mutation: on a shared client it could go out
|
|
2012
|
+
* under a replacement login. Rejected without touching `error` (there is no live snapshot to report into).
|
|
2013
|
+
*/
|
|
2014
|
+
assertActive() {
|
|
2015
|
+
if (!this.alive()) throw new Error("ConversationListStore is not active");
|
|
2016
|
+
}
|
|
2017
|
+
async mutate(request) {
|
|
2018
|
+
try {
|
|
2019
|
+
return await request;
|
|
2020
|
+
} catch (cause) {
|
|
2021
|
+
if (this.alive()) this.patch({ error: cause });
|
|
2022
|
+
throw cause;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
/** Apply a mark/clear response to the row's CURRENT summary (a refresh may have swapped it) as one unit, only while
|
|
2026
|
+
* the store is alive and the response is not older than the stored version: a delayed response never resurrects a
|
|
2027
|
+
* marker a newer action removed (equal versions are an idempotent no-op). `isUnread` is recomputed from the stored
|
|
2028
|
+
* counts and the response marker. Other devices learn of the change through `inbox_activity`.
|
|
2029
|
+
*/
|
|
2030
|
+
applyPrivateState(conversationId, state) {
|
|
2031
|
+
if (!this.alive()) return;
|
|
2032
|
+
const current = this.entries.find((entry) => entry.conversation.id === conversationId);
|
|
2033
|
+
if (!current || state.privateStateVersion < current.privateStateVersion) return;
|
|
2034
|
+
const { unreadMarkedAt, privateStateVersion } = state;
|
|
2035
|
+
const patched = {
|
|
2036
|
+
...current,
|
|
2037
|
+
unreadMarkedAt,
|
|
2038
|
+
privateStateVersion,
|
|
2039
|
+
isUnread: current.unreadCount > 0 || current.unreadCountCapped || unreadMarkedAt !== null
|
|
2040
|
+
};
|
|
2041
|
+
this.entries = this.entries.map((entry) => entry === current ? patched : entry);
|
|
2042
|
+
this.patch({ summaries: new Map(this.entries.map((entry) => [entry.conversation.id, summaryOf(entry)])) });
|
|
2043
|
+
}
|
|
1736
2044
|
};
|
|
1737
2045
|
|
|
1738
2046
|
// src/composables/use-conversation-list.ts
|
|
@@ -1767,6 +2075,8 @@ function useConversationList(options) {
|
|
|
1767
2075
|
});
|
|
1768
2076
|
return {
|
|
1769
2077
|
conversations: field("conversations"),
|
|
2078
|
+
summaries: field("summaries"),
|
|
2079
|
+
currentUserId: field("currentUserId"),
|
|
1770
2080
|
filter: field("filter"),
|
|
1771
2081
|
isInitialLoading: field("isInitialLoading"),
|
|
1772
2082
|
isLoadingMore: field("isLoadingMore"),
|
|
@@ -1778,6 +2088,8 @@ function useConversationList(options) {
|
|
|
1778
2088
|
loadMore: () => store.loadMore(),
|
|
1779
2089
|
setFilter: (filter) => store.setFilter(filter),
|
|
1780
2090
|
setQuery: (query) => store.setQuery(query),
|
|
2091
|
+
markUnread: (conversationId) => store.markUnread(conversationId),
|
|
2092
|
+
clearUnread: (conversationId, options2) => store.clearUnread(conversationId, options2),
|
|
1781
2093
|
dispose
|
|
1782
2094
|
};
|
|
1783
2095
|
}
|
|
@@ -1792,6 +2104,8 @@ var appearanceProps3 = {
|
|
|
1792
2104
|
var listViewProps = {
|
|
1793
2105
|
...appearanceProps3,
|
|
1794
2106
|
conversations: { type: Array, required: true },
|
|
2107
|
+
summaries: { type: Object, default: void 0 },
|
|
2108
|
+
currentUserId: { type: String, default: void 0 },
|
|
1795
2109
|
selectedConversationId: { type: String, default: void 0 },
|
|
1796
2110
|
onConversationSelect: { type: Function, default: void 0 },
|
|
1797
2111
|
onRefresh: { type: Function, default: void 0 },
|
|
@@ -1841,6 +2155,28 @@ var ConversationListView = (0, import_vue10.defineComponent)({
|
|
|
1841
2155
|
const refresh = () => {
|
|
1842
2156
|
return props.onRefresh?.();
|
|
1843
2157
|
};
|
|
2158
|
+
const inlineRetry = () => {
|
|
2159
|
+
if (props.hasMore && props.onLoadMore) return () => {
|
|
2160
|
+
lastRequestedLength = null;
|
|
2161
|
+
void requestMore();
|
|
2162
|
+
};
|
|
2163
|
+
if (props.onRefresh) return () => {
|
|
2164
|
+
void refresh();
|
|
2165
|
+
};
|
|
2166
|
+
return void 0;
|
|
2167
|
+
};
|
|
2168
|
+
const unreadBadge = (summary) => {
|
|
2169
|
+
if (summary.unreadCount > 0 || summary.unreadCountCapped) {
|
|
2170
|
+
const capped = summary.unreadCountCapped || summary.unreadCount > 99;
|
|
2171
|
+
return [(0, import_vue10.h)("span", {
|
|
2172
|
+
class: "ckui-unread-badge",
|
|
2173
|
+
role: "img",
|
|
2174
|
+
"aria-label": `${summary.unreadCountCapped ? "99+" : summary.unreadCount} unread`
|
|
2175
|
+
}, [(0, import_vue10.h)("span", { "aria-hidden": "true" }, capped ? "99+" : String(summary.unreadCount))])];
|
|
2176
|
+
}
|
|
2177
|
+
if (summary.isUnread) return [(0, import_vue10.h)("span", { class: "ckui-unread-badge ckui-unread-badge--dot", role: "img", "aria-label": "Unread" })];
|
|
2178
|
+
return [];
|
|
2179
|
+
};
|
|
1844
2180
|
const renderContent = () => {
|
|
1845
2181
|
const currentAppearance = appearance();
|
|
1846
2182
|
if (props.isInitialLoading && props.conversations.length === 0) {
|
|
@@ -1872,10 +2208,21 @@ var ConversationListView = (0, import_vue10.defineComponent)({
|
|
|
1872
2208
|
const children = props.conversations.flatMap((conversation, index) => {
|
|
1873
2209
|
const selected = props.selectedConversationId === conversation.id;
|
|
1874
2210
|
const select = () => selectConversation(conversation);
|
|
1875
|
-
const
|
|
2211
|
+
const summary = props.summaries?.get(conversation.id);
|
|
2212
|
+
const slotProps = {
|
|
2213
|
+
conversation,
|
|
2214
|
+
index,
|
|
2215
|
+
selected,
|
|
2216
|
+
select,
|
|
2217
|
+
...summary ? { summary } : {},
|
|
2218
|
+
...props.currentUserId === void 0 ? {} : { currentUserId: props.currentUserId }
|
|
2219
|
+
};
|
|
2220
|
+
const preview = inboxPreview(conversation, summary, props.currentUserId);
|
|
2221
|
+
const unread = summary !== void 0 && (summary.isUnread || summary.unreadCount > 0 || summary.unreadCountCapped);
|
|
1876
2222
|
const item = slots["conversation-item"]?.(slotProps) ?? (0, import_vue10.h)("button", {
|
|
1877
2223
|
type: "button",
|
|
1878
2224
|
"data-selected": selected || void 0,
|
|
2225
|
+
"data-unread": unread || void 0,
|
|
1879
2226
|
"aria-current": selected ? "true" : void 0,
|
|
1880
2227
|
onClick: select,
|
|
1881
2228
|
class: partClass("listItem", currentAppearance, "ckui-conversation-item"),
|
|
@@ -1889,8 +2236,14 @@ var ConversationListView = (0, import_vue10.defineComponent)({
|
|
|
1889
2236
|
}),
|
|
1890
2237
|
(0, import_vue10.h)("span", { class: "ckui-conversation-item__body" }, [
|
|
1891
2238
|
(0, import_vue10.h)("strong", conversation.displayTitle),
|
|
1892
|
-
(0, import_vue10.h)("span", conversation.participants.map((participant) => participant.name).join(", ") || conversation.description || "No participants")
|
|
2239
|
+
(0, import_vue10.h)("span", preview || conversation.participants.map((participant) => participant.name).join(", ") || conversation.description || "No participants")
|
|
1893
2240
|
]),
|
|
2241
|
+
// Spread rather than emit `null`: a null child renders a `<!---->` comment, and rows without a
|
|
2242
|
+
// summary must keep 0.5's exact markup.
|
|
2243
|
+
...summary ? [(0, import_vue10.h)("span", { class: "ckui-conversation-item__meta" }, [
|
|
2244
|
+
(0, import_vue10.h)("time", { class: "ckui-conversation-item__time", datetime: summary.activityAt.toISOString() }, formatMessageTime(summary.activityAt)),
|
|
2245
|
+
...unreadBadge(summary)
|
|
2246
|
+
])] : [],
|
|
1894
2247
|
(0, import_vue10.h)(import_vue9.ChevronRight, { size: 18, "aria-hidden": "true" })
|
|
1895
2248
|
]);
|
|
1896
2249
|
const nodes = [(0, import_vue10.h)("div", { key: conversation.id, role: "listitem" }, [item])];
|
|
@@ -1900,13 +2253,15 @@ var ConversationListView = (0, import_vue10.defineComponent)({
|
|
|
1900
2253
|
return nodes;
|
|
1901
2254
|
});
|
|
1902
2255
|
if (props.error) {
|
|
1903
|
-
|
|
2256
|
+
const retry = inlineRetry();
|
|
2257
|
+
children.push(slots.error?.({ error: props.error, ...retry ? { retry } : {} }) ?? (0, import_vue10.h)("div", {
|
|
1904
2258
|
class: partClass("error", currentAppearance, "ckui-inline-state ckui-state--error"),
|
|
1905
2259
|
style: partStyle("error", currentAppearance),
|
|
1906
2260
|
role: "alert"
|
|
1907
|
-
}, [
|
|
1908
|
-
|
|
1909
|
-
|
|
2261
|
+
}, [
|
|
2262
|
+
(0, import_vue10.h)("span", errorMessage(props.error)),
|
|
2263
|
+
...retry ? [(0, import_vue10.h)("button", { type: "button", class: "ckui-link-button", onClick: retry }, "Retry")] : []
|
|
2264
|
+
]));
|
|
1910
2265
|
} else if (props.isLoadingMore) {
|
|
1911
2266
|
children.push(slots["load-more"]?.() ?? (0, import_vue10.h)("div", {
|
|
1912
2267
|
class: partClass("loading", currentAppearance, "ckui-inline-state"),
|
|
@@ -1967,6 +2322,7 @@ var ConversationList = (0, import_vue10.defineComponent)({
|
|
|
1967
2322
|
initialFilter: { type: Object, default: void 0 },
|
|
1968
2323
|
pageSize: { type: Number, default: 30 },
|
|
1969
2324
|
autoLoad: { type: Boolean, default: true },
|
|
2325
|
+
activityRefreshWindowMs: { type: Number, default: void 0 },
|
|
1970
2326
|
onControllerChange: { type: Function, default: void 0 }
|
|
1971
2327
|
},
|
|
1972
2328
|
emits: ["conversation-select", "controller-change"],
|
|
@@ -1976,7 +2332,8 @@ var ConversationList = (0, import_vue10.defineComponent)({
|
|
|
1976
2332
|
...props.pageLoader ? { pageLoader: props.pageLoader } : {},
|
|
1977
2333
|
...props.initialFilter ? { initialFilter: props.initialFilter } : {},
|
|
1978
2334
|
pageSize: props.pageSize,
|
|
1979
|
-
autoLoad: props.autoLoad
|
|
2335
|
+
autoLoad: props.autoLoad,
|
|
2336
|
+
...props.activityRefreshWindowMs === void 0 ? {} : { activityRefreshWindowMs: props.activityRefreshWindowMs }
|
|
1980
2337
|
});
|
|
1981
2338
|
expose({ controller });
|
|
1982
2339
|
(0, import_vue10.watchEffect)(() => {
|
|
@@ -1989,8 +2346,11 @@ var ConversationList = (0, import_vue10.defineComponent)({
|
|
|
1989
2346
|
initialFilter: _initialFilter,
|
|
1990
2347
|
pageSize: _pageSize,
|
|
1991
2348
|
autoLoad: _autoLoad,
|
|
2349
|
+
activityRefreshWindowMs: _activityRefreshWindowMs,
|
|
1992
2350
|
onControllerChange: _onControllerChange,
|
|
1993
2351
|
conversations: _conversations,
|
|
2352
|
+
summaries: _summaries,
|
|
2353
|
+
currentUserId: _currentUserId,
|
|
1994
2354
|
onRefresh: _onRefresh,
|
|
1995
2355
|
onLoadMore: _onLoadMore,
|
|
1996
2356
|
isInitialLoading: _isInitialLoading,
|
|
@@ -2003,6 +2363,8 @@ var ConversationList = (0, import_vue10.defineComponent)({
|
|
|
2003
2363
|
...attrs,
|
|
2004
2364
|
...forwarded,
|
|
2005
2365
|
conversations: controller.conversations.value,
|
|
2366
|
+
summaries: controller.summaries.value,
|
|
2367
|
+
currentUserId: controller.currentUserId.value,
|
|
2006
2368
|
onRefresh: controller.refresh,
|
|
2007
2369
|
onLoadMore: controller.loadMore,
|
|
2008
2370
|
isInitialLoading: controller.isInitialLoading.value,
|
|
@@ -2030,6 +2392,7 @@ var defaultConvoKitTheme = {
|
|
|
2030
2392
|
incomingBubble: "#f4f4f5",
|
|
2031
2393
|
outgoingBubble: "#18181b",
|
|
2032
2394
|
outgoingText: "#fafafa",
|
|
2395
|
+
badge: "#18181b",
|
|
2033
2396
|
radius: "10px",
|
|
2034
2397
|
avatarSize: "40px",
|
|
2035
2398
|
fontFamily: 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
@@ -2061,6 +2424,7 @@ var ConvoKitThemeProvider = (0, import_vue11.defineComponent)({
|
|
|
2061
2424
|
"--ckui-incoming": theme.incomingBubble,
|
|
2062
2425
|
"--ckui-outgoing": theme.outgoingBubble,
|
|
2063
2426
|
"--ckui-outgoing-text": theme.outgoingText,
|
|
2427
|
+
"--ckui-badge": theme.badge,
|
|
2064
2428
|
"--ckui-radius": theme.radius,
|
|
2065
2429
|
"--ckui-avatar-size": theme.avatarSize,
|
|
2066
2430
|
"--ckui-font": theme.fontFamily
|
|
@@ -2093,6 +2457,7 @@ function useConvoKitTheme() {
|
|
|
2093
2457
|
isConvoKitPendingMessage,
|
|
2094
2458
|
matchesConversation,
|
|
2095
2459
|
mergeConversations,
|
|
2460
|
+
mergeInboxEntries,
|
|
2096
2461
|
mergeMessages,
|
|
2097
2462
|
readerIdsFor,
|
|
2098
2463
|
useConversation,
|