@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.js
CHANGED
|
@@ -9,6 +9,10 @@ function createConvoKitUiClient(client) {
|
|
|
9
9
|
onEvent: handler,
|
|
10
10
|
...onError ? { onError } : {}
|
|
11
11
|
}),
|
|
12
|
+
onInboxActivity: (handler, onError) => client.realtime.onInboxActivity(client.clientId, {
|
|
13
|
+
onEvent: handler,
|
|
14
|
+
...onError ? { onError } : {}
|
|
15
|
+
}),
|
|
12
16
|
onMessageDeleted: (conversationId, handler, onError) => client.realtime.onMessageDeleted(conversationId, {
|
|
13
17
|
onEvent: handler,
|
|
14
18
|
...onError ? { onError } : {}
|
|
@@ -17,11 +21,14 @@ function createConvoKitUiClient(client) {
|
|
|
17
21
|
return client.connected ? client.currentUserId : "";
|
|
18
22
|
},
|
|
19
23
|
getConversations: (options) => client.getConversations(options),
|
|
24
|
+
listInbox: (options) => client.listInbox(options),
|
|
20
25
|
getConversation: (conversationId) => client.getConversation(conversationId),
|
|
21
26
|
getMessages: (options) => client.getMessages(options),
|
|
22
27
|
getMessage: (id) => client.getMessage(id),
|
|
23
28
|
sendMessage: (input) => client.sendMessage(input),
|
|
24
29
|
markConversationRead: (conversationId, options) => client.markConversationRead(conversationId, options),
|
|
30
|
+
markConversationUnread: (conversationId) => client.markConversationUnread(conversationId),
|
|
31
|
+
clearConversationUnread: (conversationId, options) => client.clearConversationUnread(conversationId, options),
|
|
25
32
|
sendTyping: (input) => client.sendTyping(input),
|
|
26
33
|
onMessage: (conversationId, handler, onError) => client.realtime.onMessage(conversationId, {
|
|
27
34
|
onEvent: handler,
|
|
@@ -91,6 +98,31 @@ function mergeConversations(current, incoming) {
|
|
|
91
98
|
for (const conversation of incoming) byId.set(conversation.id, conversation);
|
|
92
99
|
return [...byId.values()];
|
|
93
100
|
}
|
|
101
|
+
function compareInboxOrder(left, right) {
|
|
102
|
+
return right.activityAt.getTime() - left.activityAt.getTime() || (left.conversation.id < right.conversation.id ? 1 : left.conversation.id > right.conversation.id ? -1 : 0);
|
|
103
|
+
}
|
|
104
|
+
function mergeInboxEntries(current, incoming) {
|
|
105
|
+
const byId = new Map(current.map((entry) => [entry.conversation.id, entry]));
|
|
106
|
+
for (const entry of incoming) byId.set(entry.conversation.id, entry);
|
|
107
|
+
return [...byId.values()].sort(compareInboxOrder);
|
|
108
|
+
}
|
|
109
|
+
function inboxPreview(conversation, summary, currentUserId) {
|
|
110
|
+
const message = summary?.latestMessage;
|
|
111
|
+
if (!message) return "";
|
|
112
|
+
const first = message.media[0];
|
|
113
|
+
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" : "");
|
|
114
|
+
if (!body) return "";
|
|
115
|
+
if (currentUserId !== void 0 && message.senderId === currentUserId) return `You: ${body}`;
|
|
116
|
+
if (conversation.participants.length > 2) {
|
|
117
|
+
const sender = conversation.participants.find((participant) => participant.appUserId === message.senderId || participant.id === message.senderId);
|
|
118
|
+
const name = sender?.name.trim();
|
|
119
|
+
if (name) return `${name}: ${body}`;
|
|
120
|
+
}
|
|
121
|
+
return body;
|
|
122
|
+
}
|
|
123
|
+
function formatMessageTime(date) {
|
|
124
|
+
return new Intl.DateTimeFormat(void 0, { hour: "numeric", minute: "2-digit" }).format(date);
|
|
125
|
+
}
|
|
94
126
|
function mergeMessages(current, incoming) {
|
|
95
127
|
const byId = new Map(current.map((message) => [message.id, message]));
|
|
96
128
|
for (const message of incoming) byId.set(message.id, message);
|
|
@@ -187,6 +219,10 @@ function readEntry(participant) {
|
|
|
187
219
|
function acknowledgement() {
|
|
188
220
|
return { inFlight: void 0, followUp: false, suppressed: false, target: void 0, acknowledged: void 0, unacknowledgeable: /* @__PURE__ */ new Set() };
|
|
189
221
|
}
|
|
222
|
+
function capture(conversation) {
|
|
223
|
+
const membership = conversation?.membership;
|
|
224
|
+
return { version: membership?.privateStateVersion, clearPending: membership?.unreadMarkedAt != null };
|
|
225
|
+
}
|
|
190
226
|
function isTargetMiss(cause) {
|
|
191
227
|
if (typeof cause !== "object" || cause === null) return false;
|
|
192
228
|
const { code, status } = cause;
|
|
@@ -247,6 +283,7 @@ var ConversationStore = class {
|
|
|
247
283
|
// Keep tombstones until an explicit reload/session change, including across refreshes.
|
|
248
284
|
deleted = /* @__PURE__ */ new Set();
|
|
249
285
|
ack = acknowledgement();
|
|
286
|
+
captured = capture();
|
|
250
287
|
// Visible until the platform reports otherwise; unknown/prerender/no document count as visible.
|
|
251
288
|
visible = true;
|
|
252
289
|
sendRevision;
|
|
@@ -312,6 +349,7 @@ var ConversationStore = class {
|
|
|
312
349
|
this.hydrationPool.queued.clear();
|
|
313
350
|
this.deleted.clear();
|
|
314
351
|
this.ack = acknowledgement();
|
|
352
|
+
this.captured = capture();
|
|
315
353
|
this.sendRevision = void 0;
|
|
316
354
|
this.activeSend = void 0;
|
|
317
355
|
this.refreshQueued = false;
|
|
@@ -556,9 +594,13 @@ var ConversationStore = class {
|
|
|
556
594
|
this.validatePage(page);
|
|
557
595
|
this.cursor = page.at(-1);
|
|
558
596
|
this.patch({ conversation, messages: this.overlay(page, revision), hasOlderMessages: page.length === this.pageSize });
|
|
597
|
+
this.captured = capture(conversation);
|
|
559
598
|
this.mergeReads(conversation.participants.map(readEntry));
|
|
560
599
|
this.prune(revision);
|
|
561
|
-
if (this.options.markReadOnLoad ?? true)
|
|
600
|
+
if ((this.options.markReadOnLoad ?? true) || this.ack.suppressed) {
|
|
601
|
+
this.ack.suppressed = false;
|
|
602
|
+
await this.acknowledge(true);
|
|
603
|
+
}
|
|
562
604
|
} catch (cause) {
|
|
563
605
|
this.fail(cause, generation, true);
|
|
564
606
|
} finally {
|
|
@@ -615,9 +657,12 @@ var ConversationStore = class {
|
|
|
615
657
|
const survivingIds = new Set(reconciled.map((message) => message.id));
|
|
616
658
|
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));
|
|
617
659
|
for (const id of known) if (!survivingIds.has(id)) this.forget(id);
|
|
660
|
+
const opening = this.state.conversation === null;
|
|
618
661
|
this.patch({ conversation, messages: reconciled, hasOlderMessages: hasOlder });
|
|
662
|
+
if (opening) this.captured = capture(conversation);
|
|
619
663
|
this.mergeReads(conversation.participants.map(readEntry));
|
|
620
664
|
this.prune(revision);
|
|
665
|
+
if (opening) void this.resumeAcknowledgement();
|
|
621
666
|
} catch (cause) {
|
|
622
667
|
this.fail(cause, generation, true);
|
|
623
668
|
} finally {
|
|
@@ -651,15 +696,22 @@ var ConversationStore = class {
|
|
|
651
696
|
}
|
|
652
697
|
}
|
|
653
698
|
};
|
|
654
|
-
/** Acknowledge through the newest rendered row now, regardless of visibility; no
|
|
699
|
+
/** Acknowledge through the newest rendered row now, regardless of visibility; no acknowledgement without a
|
|
700
|
+
* target (a room opened with a marker that renders nothing clears the marker instead, once).
|
|
701
|
+
*/
|
|
655
702
|
markRead = () => this.alive() ? this.acknowledge(false) : Promise.resolve();
|
|
656
703
|
/** Automatic acknowledgements wait while hidden and are re-issued (once) on becoming visible. */
|
|
657
704
|
setVisible = (visible) => {
|
|
658
705
|
this.visible = visible;
|
|
659
|
-
if (!visible || !this.
|
|
660
|
-
this.
|
|
661
|
-
void this.acknowledge(true);
|
|
706
|
+
if (!visible || !this.alive()) return;
|
|
707
|
+
void this.resumeAcknowledgement();
|
|
662
708
|
};
|
|
709
|
+
/** Re-issue (once) the automatic acknowledgement that waited while hidden or before this open's DTO, if any. */
|
|
710
|
+
resumeAcknowledgement() {
|
|
711
|
+
if (!this.ack.suppressed) return Promise.resolve();
|
|
712
|
+
this.ack.suppressed = false;
|
|
713
|
+
return this.acknowledge(true);
|
|
714
|
+
}
|
|
663
715
|
/** The newest non-pending rendered row by (createdAt, id), never by list index and never a raw realtime
|
|
664
716
|
* row; rows the server does not know are skipped, and nothing at or before the accepted target is re-sent.
|
|
665
717
|
*/
|
|
@@ -674,7 +726,7 @@ var ConversationStore = class {
|
|
|
674
726
|
/** Resolves when the request this call issued or joined settles; a follow-up is issued, not awaited. */
|
|
675
727
|
acknowledge(automatic) {
|
|
676
728
|
const ack = this.ack;
|
|
677
|
-
if (automatic && !this.visible) {
|
|
729
|
+
if (automatic && (!this.visible || this.state.conversation === null)) {
|
|
678
730
|
ack.suppressed = true;
|
|
679
731
|
return Promise.resolve();
|
|
680
732
|
}
|
|
@@ -687,9 +739,10 @@ var ConversationStore = class {
|
|
|
687
739
|
issue(ack, generation) {
|
|
688
740
|
ack.followUp = false;
|
|
689
741
|
const target = this.ackTarget(ack);
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
ack.
|
|
742
|
+
const request = target ? this.send(ack, generation, target) : this.clearMarker(ack, generation);
|
|
743
|
+
if (!request) return void 0;
|
|
744
|
+
ack.target = target?.id;
|
|
745
|
+
ack.inFlight = request.finally(() => {
|
|
693
746
|
ack.inFlight = void 0;
|
|
694
747
|
ack.target = void 0;
|
|
695
748
|
if (!this.alive(generation) || !ack.followUp) return;
|
|
@@ -700,9 +753,31 @@ var ConversationStore = class {
|
|
|
700
753
|
});
|
|
701
754
|
return ack.inFlight;
|
|
702
755
|
}
|
|
756
|
+
/** A room opened with the caller's unread marker that renders no non-pending, acknowledgeable row cannot clear
|
|
757
|
+
* it through a targeted acknowledgement, so it asks the adapter to clear the marker conditionally on the captured
|
|
758
|
+
* version, once per open, under the acknowledgement triggers and visibility gating. Rows rendered later clear it
|
|
759
|
+
* through their acknowledgements; adapters without the member leave it; `cleared: false` is not an error.
|
|
760
|
+
*/
|
|
761
|
+
clearMarker(ack, generation) {
|
|
762
|
+
const captured = this.captured;
|
|
763
|
+
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;
|
|
764
|
+
captured.clearPending = false;
|
|
765
|
+
return this.clearUnread(captured.version, generation);
|
|
766
|
+
}
|
|
767
|
+
async clearUnread(version2, generation) {
|
|
768
|
+
try {
|
|
769
|
+
await this.client.clearConversationUnread(this.room, { ifVersion: version2 });
|
|
770
|
+
} catch (cause) {
|
|
771
|
+
if (this.alive(generation)) this.fail(cause, generation);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
703
774
|
async send(ack, generation, target) {
|
|
704
775
|
try {
|
|
705
|
-
|
|
776
|
+
const version2 = this.captured.version;
|
|
777
|
+
await this.client.markConversationRead(this.room, {
|
|
778
|
+
throughMessageId: target.id,
|
|
779
|
+
...version2 === void 0 ? {} : { privateStateVersion: version2 }
|
|
780
|
+
});
|
|
706
781
|
if (!this.alive(generation)) return;
|
|
707
782
|
if (!ack.acknowledged || compare(target, ack.acknowledged) > 0) ack.acknowledged = target;
|
|
708
783
|
} catch (cause) {
|
|
@@ -889,9 +964,6 @@ var appearanceProps = {
|
|
|
889
964
|
density: { type: String, default: "comfortable" },
|
|
890
965
|
unstyled: { type: Boolean, default: false }
|
|
891
966
|
};
|
|
892
|
-
function defaultFormatTime(date) {
|
|
893
|
-
return new Intl.DateTimeFormat(void 0, { hour: "numeric", minute: "2-digit" }).format(date);
|
|
894
|
-
}
|
|
895
967
|
function defaultMedia(media, open, imageLoading) {
|
|
896
968
|
const tag = open ? "button" : "div";
|
|
897
969
|
const interactive = open ? { type: "button", onClick: open } : {};
|
|
@@ -942,7 +1014,7 @@ var MessageListView = defineComponent2({
|
|
|
942
1014
|
paginationThreshold: { type: Number, default: 240 },
|
|
943
1015
|
reverse: { type: Boolean, default: true },
|
|
944
1016
|
stickToBottom: { type: Boolean, default: true },
|
|
945
|
-
formatTime: { type: Function, default:
|
|
1017
|
+
formatTime: { type: Function, default: formatMessageTime },
|
|
946
1018
|
imageLoading: { type: String, default: "lazy" }
|
|
947
1019
|
},
|
|
948
1020
|
emits: ["load-older", "attachment-click"],
|
|
@@ -1504,30 +1576,64 @@ import {
|
|
|
1504
1576
|
import { computed as computed3, getCurrentScope as getCurrentScope2, onScopeDispose as onScopeDispose2, shallowRef as shallowRef2, toValue as toValue2, watch as watch3 } from "vue";
|
|
1505
1577
|
|
|
1506
1578
|
// src/conversation-list-store.ts
|
|
1579
|
+
var defaultActivityRefreshWindowMs = 500;
|
|
1507
1580
|
function blank2(filter) {
|
|
1508
|
-
return {
|
|
1581
|
+
return {
|
|
1582
|
+
conversations: [],
|
|
1583
|
+
summaries: /* @__PURE__ */ new Map(),
|
|
1584
|
+
currentUserId: "",
|
|
1585
|
+
filter,
|
|
1586
|
+
isInitialLoading: false,
|
|
1587
|
+
isLoadingMore: false,
|
|
1588
|
+
hasMore: true,
|
|
1589
|
+
hasLoaded: false,
|
|
1590
|
+
error: null
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
function statusOf(cause) {
|
|
1594
|
+
return typeof cause === "object" && cause !== null && "status" in cause ? cause.status : void 0;
|
|
1595
|
+
}
|
|
1596
|
+
function summaryOf(entry) {
|
|
1597
|
+
const { conversation: _conversation, ...summary } = entry;
|
|
1598
|
+
return summary;
|
|
1599
|
+
}
|
|
1600
|
+
function ids(entries) {
|
|
1601
|
+
return entries.map((entry) => entry.conversation);
|
|
1509
1602
|
}
|
|
1510
1603
|
var ConversationListStore = class {
|
|
1511
1604
|
constructor(options) {
|
|
1512
1605
|
this.options = options;
|
|
1513
1606
|
this.owner = options.client.sessionIdentity;
|
|
1607
|
+
this.user = this.owner ? options.client.currentUserId : "";
|
|
1514
1608
|
this.pageSize = options.pageSize ?? 30;
|
|
1515
1609
|
if (!Number.isInteger(this.pageSize) || this.pageSize < 1 || this.pageSize > 100) {
|
|
1516
1610
|
throw new RangeError("pageSize must be an integer between 1 and 100");
|
|
1517
1611
|
}
|
|
1612
|
+
this.activityRefreshWindowMs = options.activityRefreshWindowMs ?? defaultActivityRefreshWindowMs;
|
|
1613
|
+
if (!Number.isFinite(this.activityRefreshWindowMs) || this.activityRefreshWindowMs < 0) {
|
|
1614
|
+
throw new RangeError("activityRefreshWindowMs must be a non-negative number");
|
|
1615
|
+
}
|
|
1518
1616
|
this.state = blank2(options.initialFilter ?? {});
|
|
1519
1617
|
}
|
|
1520
1618
|
options;
|
|
1521
1619
|
owner;
|
|
1620
|
+
user;
|
|
1522
1621
|
pageSize;
|
|
1622
|
+
activityRefreshWindowMs;
|
|
1523
1623
|
state;
|
|
1524
1624
|
source = [];
|
|
1625
|
+
entries = [];
|
|
1525
1626
|
offset = 0;
|
|
1627
|
+
cursor = null;
|
|
1628
|
+
inboxUnavailable = false;
|
|
1629
|
+
inboxWarned = false;
|
|
1526
1630
|
generation = 0;
|
|
1527
1631
|
lifecycleGeneration = 0;
|
|
1528
1632
|
disposed = true;
|
|
1529
1633
|
lifecycle;
|
|
1530
1634
|
inbox;
|
|
1635
|
+
activity;
|
|
1636
|
+
activityTimer;
|
|
1531
1637
|
refreshQueued = false;
|
|
1532
1638
|
refreshing = false;
|
|
1533
1639
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -1545,12 +1651,27 @@ var ConversationListStore = class {
|
|
|
1545
1651
|
alive(generation = this.generation) {
|
|
1546
1652
|
return !this.disposed && generation === this.generation && this.owner !== null && this.options.client.sessionIdentity === this.owner;
|
|
1547
1653
|
}
|
|
1654
|
+
get inboxMode() {
|
|
1655
|
+
return !this.options.pageLoader && typeof this.options.client.listInbox === "function" && !this.inboxUnavailable;
|
|
1656
|
+
}
|
|
1657
|
+
currentUserId() {
|
|
1658
|
+
return this.inboxMode ? this.user : "";
|
|
1659
|
+
}
|
|
1548
1660
|
start = (autoLoad = true) => {
|
|
1549
1661
|
if (!this.owner || this.options.client.sessionIdentity !== this.owner) return;
|
|
1550
1662
|
if (!this.disposed) return;
|
|
1551
1663
|
this.disposed = false;
|
|
1664
|
+
this.inboxUnavailable = false;
|
|
1552
1665
|
const lifecycleGeneration = ++this.lifecycleGeneration;
|
|
1666
|
+
const current = () => this.alive() && lifecycleGeneration === this.lifecycleGeneration;
|
|
1667
|
+
const reconcile = (cause) => {
|
|
1668
|
+
if (current()) {
|
|
1669
|
+
this.patch({ error: cause });
|
|
1670
|
+
this.queueRefresh();
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1553
1673
|
try {
|
|
1674
|
+
this.patch({ currentUserId: this.currentUserId() });
|
|
1554
1675
|
const subscription = this.options.client.onConnectionEvent({
|
|
1555
1676
|
onEvent: () => {
|
|
1556
1677
|
},
|
|
@@ -1562,15 +1683,20 @@ var ConversationListStore = class {
|
|
|
1562
1683
|
else void subscription.unsubscribe().catch(() => void 0);
|
|
1563
1684
|
if (!this.alive()) return;
|
|
1564
1685
|
const inbox = this.options.client.onInboxChanged(() => {
|
|
1565
|
-
if (
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
this.queueRefresh();
|
|
1570
|
-
}
|
|
1571
|
-
});
|
|
1686
|
+
if (!current()) return;
|
|
1687
|
+
this.clearActivityTimer();
|
|
1688
|
+
this.queueRefresh();
|
|
1689
|
+
}, reconcile);
|
|
1572
1690
|
if (this.alive()) this.inbox = inbox;
|
|
1573
1691
|
else void inbox.unsubscribe().catch(() => void 0);
|
|
1692
|
+
if (!this.alive()) return;
|
|
1693
|
+
if (this.inboxMode && typeof this.options.client.onInboxActivity === "function") {
|
|
1694
|
+
const activity = this.options.client.onInboxActivity(() => {
|
|
1695
|
+
if (current()) this.scheduleActivityRefresh(lifecycleGeneration);
|
|
1696
|
+
}, reconcile);
|
|
1697
|
+
if (this.alive()) this.activity = activity;
|
|
1698
|
+
else void activity.unsubscribe().catch(() => void 0);
|
|
1699
|
+
}
|
|
1574
1700
|
if (autoLoad) void this.loadInitial();
|
|
1575
1701
|
} catch (cause) {
|
|
1576
1702
|
if (this.alive()) this.patch({ error: cause });
|
|
@@ -1580,27 +1706,109 @@ var ConversationListStore = class {
|
|
|
1580
1706
|
this.disposed = true;
|
|
1581
1707
|
this.generation++;
|
|
1582
1708
|
this.lifecycleGeneration++;
|
|
1709
|
+
this.clearActivityTimer();
|
|
1583
1710
|
const subscription = this.lifecycle;
|
|
1584
1711
|
this.lifecycle = void 0;
|
|
1585
1712
|
if (subscription) void subscription.unsubscribe().catch(() => void 0);
|
|
1586
1713
|
if (this.inbox) void this.inbox.unsubscribe().catch(() => void 0);
|
|
1587
1714
|
this.inbox = void 0;
|
|
1715
|
+
this.stopActivity();
|
|
1588
1716
|
this.refreshQueued = false;
|
|
1589
1717
|
this.refreshing = false;
|
|
1590
1718
|
this.source = [];
|
|
1719
|
+
this.entries = [];
|
|
1591
1720
|
this.offset = 0;
|
|
1721
|
+
this.cursor = null;
|
|
1592
1722
|
this.patch(blank2(this.state.filter));
|
|
1593
1723
|
};
|
|
1724
|
+
stopActivity() {
|
|
1725
|
+
if (this.activity) void this.activity.unsubscribe().catch(() => void 0);
|
|
1726
|
+
this.activity = void 0;
|
|
1727
|
+
}
|
|
1728
|
+
clearActivityTimer() {
|
|
1729
|
+
if (this.activityTimer !== void 0) clearTimeout(this.activityTimer);
|
|
1730
|
+
this.activityTimer = void 0;
|
|
1731
|
+
}
|
|
1732
|
+
/** Max-wait throttle: the first signal opens a window; later signals wait for it; one refresh runs when it closes. */
|
|
1733
|
+
scheduleActivityRefresh(lifecycleGeneration) {
|
|
1734
|
+
if (this.activityRefreshWindowMs === 0) return this.queueRefresh();
|
|
1735
|
+
if (this.activityTimer !== void 0) return;
|
|
1736
|
+
const timer = setTimeout(() => {
|
|
1737
|
+
this.activityTimer = void 0;
|
|
1738
|
+
if (this.alive() && lifecycleGeneration === this.lifecycleGeneration) this.queueRefresh();
|
|
1739
|
+
}, this.activityRefreshWindowMs);
|
|
1740
|
+
timer.unref?.();
|
|
1741
|
+
this.activityTimer = timer;
|
|
1742
|
+
}
|
|
1594
1743
|
fail(cause, generation) {
|
|
1595
1744
|
if (!this.alive(generation)) return;
|
|
1596
|
-
const status =
|
|
1745
|
+
const status = statusOf(cause);
|
|
1597
1746
|
if (status === 401 || status === 403 || status === 404) {
|
|
1598
1747
|
this.source = [];
|
|
1748
|
+
this.entries = [];
|
|
1599
1749
|
this.offset = 0;
|
|
1600
|
-
this.
|
|
1750
|
+
this.cursor = null;
|
|
1751
|
+
this.patch({ conversations: [], summaries: /* @__PURE__ */ new Map(), hasMore: false });
|
|
1601
1752
|
}
|
|
1602
1753
|
this.patch({ error: cause });
|
|
1603
1754
|
}
|
|
1755
|
+
/** Run an operation in inbox mode, falling back to the legacy path for the rest of this store's life when the
|
|
1756
|
+
* inbox route is absent (404: rollback, staging). Loaded rows are kept and the same operation continues.
|
|
1757
|
+
*/
|
|
1758
|
+
async withFallback(generation, inbox, legacy) {
|
|
1759
|
+
if (!this.inboxMode) return legacy();
|
|
1760
|
+
try {
|
|
1761
|
+
await inbox();
|
|
1762
|
+
} catch (cause) {
|
|
1763
|
+
if (!this.alive(generation) || statusOf(cause) !== 404) throw cause;
|
|
1764
|
+
this.inboxUnavailable = true;
|
|
1765
|
+
this.clearActivityTimer();
|
|
1766
|
+
this.stopActivity();
|
|
1767
|
+
this.entries = [];
|
|
1768
|
+
this.cursor = null;
|
|
1769
|
+
this.offset = this.source.length;
|
|
1770
|
+
if (!this.inboxWarned) {
|
|
1771
|
+
this.inboxWarned = true;
|
|
1772
|
+
console.warn("ConvoKit inbox endpoint unavailable (404); using getConversations without previews or unread counts.");
|
|
1773
|
+
}
|
|
1774
|
+
this.patch({ summaries: /* @__PURE__ */ new Map(), currentUserId: "" });
|
|
1775
|
+
await legacy();
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
validateInboxPage(page, limit, requestedCursor) {
|
|
1779
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1780
|
+
for (const entry of page.entries) {
|
|
1781
|
+
const id = entry.conversation.id;
|
|
1782
|
+
if (!id.trim() || seen.has(id)) throw new Error("Invalid conversation page");
|
|
1783
|
+
seen.add(id);
|
|
1784
|
+
}
|
|
1785
|
+
if (page.entries.length > limit) throw new Error("Invalid conversation page");
|
|
1786
|
+
if (page.nextCursor !== null && (page.nextCursor === requestedCursor || page.entries.length === 0)) {
|
|
1787
|
+
throw new Error("Inbox pagination did not advance");
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
/** Swap rows, summaries, cursor and hasMore together, filtered by the filter current at commit time. */
|
|
1791
|
+
commitInbox(entries, cursor) {
|
|
1792
|
+
this.entries = entries;
|
|
1793
|
+
this.source = ids(entries);
|
|
1794
|
+
this.cursor = cursor;
|
|
1795
|
+
this.patch({
|
|
1796
|
+
conversations: applyConversationFilter(this.source, this.state.filter),
|
|
1797
|
+
summaries: new Map(entries.map((entry) => [entry.conversation.id, summaryOf(entry)])),
|
|
1798
|
+
hasMore: cursor !== null
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
async loadInboxUntilVisible(generation, filter) {
|
|
1802
|
+
const visibleBefore = applyConversationFilter(this.source, filter).length;
|
|
1803
|
+
while (this.alive(generation)) {
|
|
1804
|
+
const requested = this.cursor;
|
|
1805
|
+
const page = await this.options.client.listInbox({ limit: this.pageSize, cursor: requested, archived: filter.archived ?? false });
|
|
1806
|
+
if (!this.alive(generation)) return;
|
|
1807
|
+
this.validateInboxPage(page, this.pageSize, requested);
|
|
1808
|
+
this.commitInbox(mergeInboxEntries(this.entries, page.entries), page.nextCursor);
|
|
1809
|
+
if (page.nextCursor === null || this.state.conversations.length > visibleBefore) return;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1604
1812
|
async loadUntilVisible(generation, filter) {
|
|
1605
1813
|
const visibleBefore = applyConversationFilter(this.source, filter).length;
|
|
1606
1814
|
while (this.alive(generation)) {
|
|
@@ -1627,10 +1835,17 @@ var ConversationListStore = class {
|
|
|
1627
1835
|
const generation = ++this.generation;
|
|
1628
1836
|
this.refreshing = false;
|
|
1629
1837
|
this.source = [];
|
|
1838
|
+
this.entries = [];
|
|
1630
1839
|
this.offset = 0;
|
|
1631
|
-
this.
|
|
1840
|
+
this.cursor = null;
|
|
1841
|
+
this.patch({ ...blank2(this.state.filter), currentUserId: this.currentUserId(), isInitialLoading: true });
|
|
1842
|
+
const filter = this.state.filter;
|
|
1632
1843
|
try {
|
|
1633
|
-
await this.
|
|
1844
|
+
await this.withFallback(
|
|
1845
|
+
generation,
|
|
1846
|
+
() => this.loadInboxUntilVisible(generation, filter),
|
|
1847
|
+
() => this.loadUntilVisible(generation, filter)
|
|
1848
|
+
);
|
|
1634
1849
|
} catch (cause) {
|
|
1635
1850
|
this.fail(cause, generation);
|
|
1636
1851
|
} finally {
|
|
@@ -1652,6 +1867,47 @@ var ConversationListStore = class {
|
|
|
1652
1867
|
void this.refresh();
|
|
1653
1868
|
});
|
|
1654
1869
|
}
|
|
1870
|
+
/** Re-walk the inbox from the head until the loaded window is covered and something is visible, or the inbox
|
|
1871
|
+
* ends. Rooms that moved are re-positioned by the merge; an exhausted inbox publishes what it found.
|
|
1872
|
+
*/
|
|
1873
|
+
async refreshInbox(generation, filter) {
|
|
1874
|
+
const target = Math.max(this.pageSize, this.entries.length);
|
|
1875
|
+
let rows = [], consumed = 0, cursor = null;
|
|
1876
|
+
while (this.alive(generation)) {
|
|
1877
|
+
const remaining = target - consumed;
|
|
1878
|
+
const limit = remaining >= 1 ? Math.min(100, remaining) : this.pageSize;
|
|
1879
|
+
const page = await this.options.client.listInbox({ limit, cursor, archived: filter.archived ?? false });
|
|
1880
|
+
if (!this.alive(generation)) return;
|
|
1881
|
+
this.validateInboxPage(page, limit, cursor);
|
|
1882
|
+
rows = mergeInboxEntries(rows, page.entries);
|
|
1883
|
+
consumed += page.entries.length;
|
|
1884
|
+
cursor = page.nextCursor;
|
|
1885
|
+
if (cursor === null || consumed >= target && applyConversationFilter(ids(rows), this.state.filter).length > 0) break;
|
|
1886
|
+
}
|
|
1887
|
+
if (!this.alive(generation)) return;
|
|
1888
|
+
this.commitInbox(rows, cursor);
|
|
1889
|
+
}
|
|
1890
|
+
async refreshLegacy(generation, filter) {
|
|
1891
|
+
const target = Math.max(this.pageSize, this.offset);
|
|
1892
|
+
const compare2 = (a, b) => a.createdAt.getTime() - b.createdAt.getTime() || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
1893
|
+
const boundary = this.options.pageLoader ? void 0 : this.source.reduce((oldest, row) => !oldest || compare2(row, oldest) < 0 ? row : oldest, void 0);
|
|
1894
|
+
let rows = [], offset = 0, hasMore = true;
|
|
1895
|
+
while (this.alive(generation)) {
|
|
1896
|
+
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 });
|
|
1897
|
+
if (!this.alive(generation)) return;
|
|
1898
|
+
if (page.length > this.pageSize || page.some((row) => !row.id.trim())) throw new Error("Invalid conversation page");
|
|
1899
|
+
const merged = mergeConversations(rows, page);
|
|
1900
|
+
if (page.length === this.pageSize && merged.length === rows.length) throw new Error("Conversation pagination did not advance");
|
|
1901
|
+
rows = merged;
|
|
1902
|
+
offset += page.length;
|
|
1903
|
+
hasMore = page.length === this.pageSize;
|
|
1904
|
+
if (!hasMore || offset >= target && applyConversationFilter(rows, this.state.filter).length > 0 && (!boundary || page.some((row) => compare2(row, boundary) <= 0))) break;
|
|
1905
|
+
}
|
|
1906
|
+
if (!this.alive(generation)) return;
|
|
1907
|
+
this.source = rows;
|
|
1908
|
+
this.offset = offset;
|
|
1909
|
+
this.patch({ conversations: applyConversationFilter(rows, this.state.filter), hasMore });
|
|
1910
|
+
}
|
|
1655
1911
|
/** Replace the loaded window atomically, retaining filters and rows during transient failures. */
|
|
1656
1912
|
refresh = async () => {
|
|
1657
1913
|
if (!this.alive()) return;
|
|
@@ -1662,28 +1918,14 @@ var ConversationListStore = class {
|
|
|
1662
1918
|
if (!this.state.hasLoaded) return this.loadInitial();
|
|
1663
1919
|
const generation = this.generation;
|
|
1664
1920
|
const filter = this.state.filter;
|
|
1665
|
-
const target = Math.max(this.pageSize, this.offset);
|
|
1666
|
-
const compare2 = (a, b) => a.createdAt.getTime() - b.createdAt.getTime() || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
1667
|
-
const boundary = this.options.pageLoader ? void 0 : this.source.reduce((oldest, row) => !oldest || compare2(row, oldest) < 0 ? row : oldest, void 0);
|
|
1668
1921
|
this.refreshing = true;
|
|
1669
1922
|
this.patch({ error: null });
|
|
1670
1923
|
try {
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
const merged = mergeConversations(rows, page);
|
|
1677
|
-
if (page.length === this.pageSize && merged.length === rows.length) throw new Error("Conversation pagination did not advance");
|
|
1678
|
-
rows = merged;
|
|
1679
|
-
offset += page.length;
|
|
1680
|
-
hasMore = page.length === this.pageSize;
|
|
1681
|
-
if (!hasMore || offset >= target && applyConversationFilter(rows, this.state.filter).length > 0 && (!boundary || page.some((row) => compare2(row, boundary) <= 0))) break;
|
|
1682
|
-
}
|
|
1683
|
-
if (!this.alive(generation)) return;
|
|
1684
|
-
this.source = rows;
|
|
1685
|
-
this.offset = offset;
|
|
1686
|
-
this.patch({ conversations: applyConversationFilter(rows, this.state.filter), hasMore });
|
|
1924
|
+
await this.withFallback(
|
|
1925
|
+
generation,
|
|
1926
|
+
() => this.refreshInbox(generation, filter),
|
|
1927
|
+
() => this.refreshLegacy(generation, filter)
|
|
1928
|
+
);
|
|
1687
1929
|
} catch (cause) {
|
|
1688
1930
|
this.fail(cause, generation);
|
|
1689
1931
|
} finally {
|
|
@@ -1696,9 +1938,14 @@ var ConversationListStore = class {
|
|
|
1696
1938
|
loadMore = async () => {
|
|
1697
1939
|
if (!this.alive() || this.refreshing || this.state.isInitialLoading || this.state.isLoadingMore || !this.state.hasMore) return;
|
|
1698
1940
|
const generation = this.generation;
|
|
1941
|
+
const filter = this.state.filter;
|
|
1699
1942
|
this.patch({ isLoadingMore: true, error: null });
|
|
1700
1943
|
try {
|
|
1701
|
-
await this.
|
|
1944
|
+
await this.withFallback(
|
|
1945
|
+
generation,
|
|
1946
|
+
() => this.loadInboxUntilVisible(generation, filter),
|
|
1947
|
+
() => this.loadUntilVisible(generation, filter)
|
|
1948
|
+
);
|
|
1702
1949
|
} catch (cause) {
|
|
1703
1950
|
this.fail(cause, generation);
|
|
1704
1951
|
} finally {
|
|
@@ -1716,6 +1963,66 @@ var ConversationListStore = class {
|
|
|
1716
1963
|
else if (!this.state.conversations.length && this.state.hasMore) await this.loadMore();
|
|
1717
1964
|
};
|
|
1718
1965
|
setQuery = (query) => this.setFilter({ ...this.state.filter, query });
|
|
1966
|
+
/** Mark a room unread for the viewer only; the row's summary takes the response (D10). Rejects when the adapter
|
|
1967
|
+
* lacks `markConversationUnread` or the store is not active; a request failure is reported through `error`
|
|
1968
|
+
* without evicting rows and rejects.
|
|
1969
|
+
*/
|
|
1970
|
+
markUnread = async (conversationId) => {
|
|
1971
|
+
const client = this.options.client;
|
|
1972
|
+
if (typeof client.markConversationUnread !== "function") {
|
|
1973
|
+
throw new TypeError("markUnread requires a ConvoKitUiClient adapter with markConversationUnread (core SDK 0.7)");
|
|
1974
|
+
}
|
|
1975
|
+
this.assertActive();
|
|
1976
|
+
this.applyPrivateState(conversationId, await this.mutate(client.markConversationUnread(conversationId)));
|
|
1977
|
+
};
|
|
1978
|
+
/** Remove the viewer's marker (conditionally on `options.ifVersion`); resolves to the response's `cleared` ("this
|
|
1979
|
+
* request removed the marker", not "the room is read") and patches the summary on true and false alike (D10).
|
|
1980
|
+
* Rejects when the adapter lacks `clearConversationUnread` or the store is not active; failures are reported like
|
|
1981
|
+
* `markUnread`.
|
|
1982
|
+
*/
|
|
1983
|
+
clearUnread = async (conversationId, options) => {
|
|
1984
|
+
const client = this.options.client;
|
|
1985
|
+
if (typeof client.clearConversationUnread !== "function") {
|
|
1986
|
+
throw new TypeError("clearUnread requires a ConvoKitUiClient adapter with clearConversationUnread (core SDK 0.7)");
|
|
1987
|
+
}
|
|
1988
|
+
this.assertActive();
|
|
1989
|
+
const result = await this.mutate(client.clearConversationUnread(conversationId, options));
|
|
1990
|
+
this.applyPrivateState(conversationId, result);
|
|
1991
|
+
return result.cleared;
|
|
1992
|
+
};
|
|
1993
|
+
/** A disposed or session-evicted store never sends a private-state mutation: on a shared client it could go out
|
|
1994
|
+
* under a replacement login. Rejected without touching `error` (there is no live snapshot to report into).
|
|
1995
|
+
*/
|
|
1996
|
+
assertActive() {
|
|
1997
|
+
if (!this.alive()) throw new Error("ConversationListStore is not active");
|
|
1998
|
+
}
|
|
1999
|
+
async mutate(request) {
|
|
2000
|
+
try {
|
|
2001
|
+
return await request;
|
|
2002
|
+
} catch (cause) {
|
|
2003
|
+
if (this.alive()) this.patch({ error: cause });
|
|
2004
|
+
throw cause;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
/** Apply a mark/clear response to the row's CURRENT summary (a refresh may have swapped it) as one unit, only while
|
|
2008
|
+
* the store is alive and the response is not older than the stored version: a delayed response never resurrects a
|
|
2009
|
+
* marker a newer action removed (equal versions are an idempotent no-op). `isUnread` is recomputed from the stored
|
|
2010
|
+
* counts and the response marker. Other devices learn of the change through `inbox_activity`.
|
|
2011
|
+
*/
|
|
2012
|
+
applyPrivateState(conversationId, state) {
|
|
2013
|
+
if (!this.alive()) return;
|
|
2014
|
+
const current = this.entries.find((entry) => entry.conversation.id === conversationId);
|
|
2015
|
+
if (!current || state.privateStateVersion < current.privateStateVersion) return;
|
|
2016
|
+
const { unreadMarkedAt, privateStateVersion } = state;
|
|
2017
|
+
const patched = {
|
|
2018
|
+
...current,
|
|
2019
|
+
unreadMarkedAt,
|
|
2020
|
+
privateStateVersion,
|
|
2021
|
+
isUnread: current.unreadCount > 0 || current.unreadCountCapped || unreadMarkedAt !== null
|
|
2022
|
+
};
|
|
2023
|
+
this.entries = this.entries.map((entry) => entry === current ? patched : entry);
|
|
2024
|
+
this.patch({ summaries: new Map(this.entries.map((entry) => [entry.conversation.id, summaryOf(entry)])) });
|
|
2025
|
+
}
|
|
1719
2026
|
};
|
|
1720
2027
|
|
|
1721
2028
|
// src/composables/use-conversation-list.ts
|
|
@@ -1750,6 +2057,8 @@ function useConversationList(options) {
|
|
|
1750
2057
|
});
|
|
1751
2058
|
return {
|
|
1752
2059
|
conversations: field("conversations"),
|
|
2060
|
+
summaries: field("summaries"),
|
|
2061
|
+
currentUserId: field("currentUserId"),
|
|
1753
2062
|
filter: field("filter"),
|
|
1754
2063
|
isInitialLoading: field("isInitialLoading"),
|
|
1755
2064
|
isLoadingMore: field("isLoadingMore"),
|
|
@@ -1761,6 +2070,8 @@ function useConversationList(options) {
|
|
|
1761
2070
|
loadMore: () => store.loadMore(),
|
|
1762
2071
|
setFilter: (filter) => store.setFilter(filter),
|
|
1763
2072
|
setQuery: (query) => store.setQuery(query),
|
|
2073
|
+
markUnread: (conversationId) => store.markUnread(conversationId),
|
|
2074
|
+
clearUnread: (conversationId, options2) => store.clearUnread(conversationId, options2),
|
|
1764
2075
|
dispose
|
|
1765
2076
|
};
|
|
1766
2077
|
}
|
|
@@ -1775,6 +2086,8 @@ var appearanceProps3 = {
|
|
|
1775
2086
|
var listViewProps = {
|
|
1776
2087
|
...appearanceProps3,
|
|
1777
2088
|
conversations: { type: Array, required: true },
|
|
2089
|
+
summaries: { type: Object, default: void 0 },
|
|
2090
|
+
currentUserId: { type: String, default: void 0 },
|
|
1778
2091
|
selectedConversationId: { type: String, default: void 0 },
|
|
1779
2092
|
onConversationSelect: { type: Function, default: void 0 },
|
|
1780
2093
|
onRefresh: { type: Function, default: void 0 },
|
|
@@ -1824,6 +2137,28 @@ var ConversationListView = defineComponent4({
|
|
|
1824
2137
|
const refresh = () => {
|
|
1825
2138
|
return props.onRefresh?.();
|
|
1826
2139
|
};
|
|
2140
|
+
const inlineRetry = () => {
|
|
2141
|
+
if (props.hasMore && props.onLoadMore) return () => {
|
|
2142
|
+
lastRequestedLength = null;
|
|
2143
|
+
void requestMore();
|
|
2144
|
+
};
|
|
2145
|
+
if (props.onRefresh) return () => {
|
|
2146
|
+
void refresh();
|
|
2147
|
+
};
|
|
2148
|
+
return void 0;
|
|
2149
|
+
};
|
|
2150
|
+
const unreadBadge = (summary) => {
|
|
2151
|
+
if (summary.unreadCount > 0 || summary.unreadCountCapped) {
|
|
2152
|
+
const capped = summary.unreadCountCapped || summary.unreadCount > 99;
|
|
2153
|
+
return [h4("span", {
|
|
2154
|
+
class: "ckui-unread-badge",
|
|
2155
|
+
role: "img",
|
|
2156
|
+
"aria-label": `${summary.unreadCountCapped ? "99+" : summary.unreadCount} unread`
|
|
2157
|
+
}, [h4("span", { "aria-hidden": "true" }, capped ? "99+" : String(summary.unreadCount))])];
|
|
2158
|
+
}
|
|
2159
|
+
if (summary.isUnread) return [h4("span", { class: "ckui-unread-badge ckui-unread-badge--dot", role: "img", "aria-label": "Unread" })];
|
|
2160
|
+
return [];
|
|
2161
|
+
};
|
|
1827
2162
|
const renderContent = () => {
|
|
1828
2163
|
const currentAppearance = appearance();
|
|
1829
2164
|
if (props.isInitialLoading && props.conversations.length === 0) {
|
|
@@ -1855,10 +2190,21 @@ var ConversationListView = defineComponent4({
|
|
|
1855
2190
|
const children = props.conversations.flatMap((conversation, index) => {
|
|
1856
2191
|
const selected = props.selectedConversationId === conversation.id;
|
|
1857
2192
|
const select = () => selectConversation(conversation);
|
|
1858
|
-
const
|
|
2193
|
+
const summary = props.summaries?.get(conversation.id);
|
|
2194
|
+
const slotProps = {
|
|
2195
|
+
conversation,
|
|
2196
|
+
index,
|
|
2197
|
+
selected,
|
|
2198
|
+
select,
|
|
2199
|
+
...summary ? { summary } : {},
|
|
2200
|
+
...props.currentUserId === void 0 ? {} : { currentUserId: props.currentUserId }
|
|
2201
|
+
};
|
|
2202
|
+
const preview = inboxPreview(conversation, summary, props.currentUserId);
|
|
2203
|
+
const unread = summary !== void 0 && (summary.isUnread || summary.unreadCount > 0 || summary.unreadCountCapped);
|
|
1859
2204
|
const item = slots["conversation-item"]?.(slotProps) ?? h4("button", {
|
|
1860
2205
|
type: "button",
|
|
1861
2206
|
"data-selected": selected || void 0,
|
|
2207
|
+
"data-unread": unread || void 0,
|
|
1862
2208
|
"aria-current": selected ? "true" : void 0,
|
|
1863
2209
|
onClick: select,
|
|
1864
2210
|
class: partClass("listItem", currentAppearance, "ckui-conversation-item"),
|
|
@@ -1872,8 +2218,14 @@ var ConversationListView = defineComponent4({
|
|
|
1872
2218
|
}),
|
|
1873
2219
|
h4("span", { class: "ckui-conversation-item__body" }, [
|
|
1874
2220
|
h4("strong", conversation.displayTitle),
|
|
1875
|
-
h4("span", conversation.participants.map((participant) => participant.name).join(", ") || conversation.description || "No participants")
|
|
2221
|
+
h4("span", preview || conversation.participants.map((participant) => participant.name).join(", ") || conversation.description || "No participants")
|
|
1876
2222
|
]),
|
|
2223
|
+
// Spread rather than emit `null`: a null child renders a `<!---->` comment, and rows without a
|
|
2224
|
+
// summary must keep 0.5's exact markup.
|
|
2225
|
+
...summary ? [h4("span", { class: "ckui-conversation-item__meta" }, [
|
|
2226
|
+
h4("time", { class: "ckui-conversation-item__time", datetime: summary.activityAt.toISOString() }, formatMessageTime(summary.activityAt)),
|
|
2227
|
+
...unreadBadge(summary)
|
|
2228
|
+
])] : [],
|
|
1877
2229
|
h4(ChevronRight, { size: 18, "aria-hidden": "true" })
|
|
1878
2230
|
]);
|
|
1879
2231
|
const nodes = [h4("div", { key: conversation.id, role: "listitem" }, [item])];
|
|
@@ -1883,13 +2235,15 @@ var ConversationListView = defineComponent4({
|
|
|
1883
2235
|
return nodes;
|
|
1884
2236
|
});
|
|
1885
2237
|
if (props.error) {
|
|
1886
|
-
|
|
2238
|
+
const retry = inlineRetry();
|
|
2239
|
+
children.push(slots.error?.({ error: props.error, ...retry ? { retry } : {} }) ?? h4("div", {
|
|
1887
2240
|
class: partClass("error", currentAppearance, "ckui-inline-state ckui-state--error"),
|
|
1888
2241
|
style: partStyle("error", currentAppearance),
|
|
1889
2242
|
role: "alert"
|
|
1890
|
-
}, [
|
|
1891
|
-
|
|
1892
|
-
|
|
2243
|
+
}, [
|
|
2244
|
+
h4("span", errorMessage(props.error)),
|
|
2245
|
+
...retry ? [h4("button", { type: "button", class: "ckui-link-button", onClick: retry }, "Retry")] : []
|
|
2246
|
+
]));
|
|
1893
2247
|
} else if (props.isLoadingMore) {
|
|
1894
2248
|
children.push(slots["load-more"]?.() ?? h4("div", {
|
|
1895
2249
|
class: partClass("loading", currentAppearance, "ckui-inline-state"),
|
|
@@ -1950,6 +2304,7 @@ var ConversationList = defineComponent4({
|
|
|
1950
2304
|
initialFilter: { type: Object, default: void 0 },
|
|
1951
2305
|
pageSize: { type: Number, default: 30 },
|
|
1952
2306
|
autoLoad: { type: Boolean, default: true },
|
|
2307
|
+
activityRefreshWindowMs: { type: Number, default: void 0 },
|
|
1953
2308
|
onControllerChange: { type: Function, default: void 0 }
|
|
1954
2309
|
},
|
|
1955
2310
|
emits: ["conversation-select", "controller-change"],
|
|
@@ -1959,7 +2314,8 @@ var ConversationList = defineComponent4({
|
|
|
1959
2314
|
...props.pageLoader ? { pageLoader: props.pageLoader } : {},
|
|
1960
2315
|
...props.initialFilter ? { initialFilter: props.initialFilter } : {},
|
|
1961
2316
|
pageSize: props.pageSize,
|
|
1962
|
-
autoLoad: props.autoLoad
|
|
2317
|
+
autoLoad: props.autoLoad,
|
|
2318
|
+
...props.activityRefreshWindowMs === void 0 ? {} : { activityRefreshWindowMs: props.activityRefreshWindowMs }
|
|
1963
2319
|
});
|
|
1964
2320
|
expose({ controller });
|
|
1965
2321
|
watchEffect2(() => {
|
|
@@ -1972,8 +2328,11 @@ var ConversationList = defineComponent4({
|
|
|
1972
2328
|
initialFilter: _initialFilter,
|
|
1973
2329
|
pageSize: _pageSize,
|
|
1974
2330
|
autoLoad: _autoLoad,
|
|
2331
|
+
activityRefreshWindowMs: _activityRefreshWindowMs,
|
|
1975
2332
|
onControllerChange: _onControllerChange,
|
|
1976
2333
|
conversations: _conversations,
|
|
2334
|
+
summaries: _summaries,
|
|
2335
|
+
currentUserId: _currentUserId,
|
|
1977
2336
|
onRefresh: _onRefresh,
|
|
1978
2337
|
onLoadMore: _onLoadMore,
|
|
1979
2338
|
isInitialLoading: _isInitialLoading,
|
|
@@ -1986,6 +2345,8 @@ var ConversationList = defineComponent4({
|
|
|
1986
2345
|
...attrs,
|
|
1987
2346
|
...forwarded,
|
|
1988
2347
|
conversations: controller.conversations.value,
|
|
2348
|
+
summaries: controller.summaries.value,
|
|
2349
|
+
currentUserId: controller.currentUserId.value,
|
|
1989
2350
|
onRefresh: controller.refresh,
|
|
1990
2351
|
onLoadMore: controller.loadMore,
|
|
1991
2352
|
isInitialLoading: controller.isInitialLoading.value,
|
|
@@ -2019,6 +2380,7 @@ var defaultConvoKitTheme = {
|
|
|
2019
2380
|
incomingBubble: "#f4f4f5",
|
|
2020
2381
|
outgoingBubble: "#18181b",
|
|
2021
2382
|
outgoingText: "#fafafa",
|
|
2383
|
+
badge: "#18181b",
|
|
2022
2384
|
radius: "10px",
|
|
2023
2385
|
avatarSize: "40px",
|
|
2024
2386
|
fontFamily: 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
@@ -2050,6 +2412,7 @@ var ConvoKitThemeProvider = defineComponent5({
|
|
|
2050
2412
|
"--ckui-incoming": theme.incomingBubble,
|
|
2051
2413
|
"--ckui-outgoing": theme.outgoingBubble,
|
|
2052
2414
|
"--ckui-outgoing-text": theme.outgoingText,
|
|
2415
|
+
"--ckui-badge": theme.badge,
|
|
2053
2416
|
"--ckui-radius": theme.radius,
|
|
2054
2417
|
"--ckui-avatar-size": theme.avatarSize,
|
|
2055
2418
|
"--ckui-font": theme.fontFamily
|
|
@@ -2081,6 +2444,7 @@ export {
|
|
|
2081
2444
|
isConvoKitPendingMessage,
|
|
2082
2445
|
matchesConversation,
|
|
2083
2446
|
mergeConversations,
|
|
2447
|
+
mergeInboxEntries,
|
|
2084
2448
|
mergeMessages,
|
|
2085
2449
|
readerIdsFor,
|
|
2086
2450
|
useConversation,
|