@convokitapp/vue-ui 0.2.1 → 0.3.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 +25 -0
- package/PARITY.md +9 -1
- package/README.md +59 -1
- package/dist/index.cjs +840 -400
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -29
- package/dist/index.d.ts +35 -29
- package/dist/index.js +726 -303
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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,
|
|
@@ -45,12 +46,21 @@ module.exports = __toCommonJS(index_exports);
|
|
|
45
46
|
// src/client.ts
|
|
46
47
|
function createConvoKitUiClient(client) {
|
|
47
48
|
return {
|
|
49
|
+
get sessionIdentity() {
|
|
50
|
+
return client.connected ? client.realtime : null;
|
|
51
|
+
},
|
|
52
|
+
onConnectionEvent: (handlers) => client.realtime.onConnectionEvent(handlers),
|
|
53
|
+
onMessageDeleted: (conversationId, handler, onError) => client.realtime.onMessageDeleted(conversationId, {
|
|
54
|
+
onEvent: handler,
|
|
55
|
+
...onError ? { onError } : {}
|
|
56
|
+
}),
|
|
48
57
|
get currentUserId() {
|
|
49
|
-
return client.currentUserId;
|
|
58
|
+
return client.connected ? client.currentUserId : "";
|
|
50
59
|
},
|
|
51
60
|
getConversations: (options) => client.getConversations(options),
|
|
52
61
|
getConversation: (conversationId) => client.getConversation(conversationId),
|
|
53
62
|
getMessages: (options) => client.getMessages(options),
|
|
63
|
+
getMessage: (id) => client.getMessage(id),
|
|
54
64
|
sendMessage: (input) => client.sendMessage(input),
|
|
55
65
|
markConversationRead: (conversationId) => client.markConversationRead(conversationId),
|
|
56
66
|
sendTyping: (input) => client.sendTyping(input),
|
|
@@ -71,12 +81,17 @@ function createConvoKitUiClient(client) {
|
|
|
71
81
|
|
|
72
82
|
// src/components/avatar.ts
|
|
73
83
|
var import_reka_ui = require("reka-ui");
|
|
74
|
-
var
|
|
84
|
+
var import_vue2 = require("vue");
|
|
75
85
|
|
|
76
86
|
// src/utils.ts
|
|
77
87
|
var import_clsx = require("clsx");
|
|
88
|
+
var import_vue = require("vue");
|
|
89
|
+
var pendingMessageIdPrefix = "convokit-pending-";
|
|
90
|
+
function isConvoKitPendingMessage(message) {
|
|
91
|
+
return message.id.startsWith(pendingMessageIdPrefix);
|
|
92
|
+
}
|
|
78
93
|
function cx(...values) {
|
|
79
|
-
return (0, import_clsx.clsx)(values);
|
|
94
|
+
return (0, import_clsx.clsx)(values.map(import_vue.normalizeClass));
|
|
80
95
|
}
|
|
81
96
|
function requestedParticipantIds(filter) {
|
|
82
97
|
const values = filter.participantIds ?? [];
|
|
@@ -117,11 +132,15 @@ function mergeMessages(current, incoming) {
|
|
|
117
132
|
const byId = new Map(current.map((message) => [message.id, message]));
|
|
118
133
|
for (const message of incoming) byId.set(message.id, message);
|
|
119
134
|
return [...byId.values()].sort((left, right) => {
|
|
135
|
+
const leftPending = isConvoKitPendingMessage(left);
|
|
136
|
+
const rightPending = isConvoKitPendingMessage(right);
|
|
137
|
+
if (leftPending !== rightPending) return leftPending ? 1 : -1;
|
|
120
138
|
const byTime = left.createdAt.getTime() - right.createdAt.getTime();
|
|
121
139
|
return byTime === 0 ? left.id.localeCompare(right.id) : byTime;
|
|
122
140
|
});
|
|
123
141
|
}
|
|
124
142
|
function readerIdsFor(message, readAtByUserId) {
|
|
143
|
+
if (isConvoKitPendingMessage(message)) return /* @__PURE__ */ new Set();
|
|
125
144
|
return new Set([...readAtByUserId.entries()].filter(([userId, readAt]) => userId !== message.senderId && readAt.getTime() >= message.createdAt.getTime()).map(([userId]) => userId));
|
|
126
145
|
}
|
|
127
146
|
function partClass(part, appearance, defaultClass) {
|
|
@@ -145,7 +164,7 @@ function initials(value) {
|
|
|
145
164
|
}
|
|
146
165
|
|
|
147
166
|
// src/components/avatar.ts
|
|
148
|
-
var ConvoKitAvatar = (0,
|
|
167
|
+
var ConvoKitAvatar = (0, import_vue2.defineComponent)({
|
|
149
168
|
name: "ConvoKitAvatar",
|
|
150
169
|
inheritAttrs: false,
|
|
151
170
|
props: {
|
|
@@ -153,13 +172,13 @@ var ConvoKitAvatar = (0, import_vue.defineComponent)({
|
|
|
153
172
|
src: { type: String, default: null }
|
|
154
173
|
},
|
|
155
174
|
setup(props, { attrs }) {
|
|
156
|
-
return () => (0,
|
|
175
|
+
return () => (0, import_vue2.h)(import_reka_ui.AvatarRoot, {
|
|
157
176
|
...attrs,
|
|
158
177
|
class: cx("ckui-avatar", attrs.class)
|
|
159
178
|
}, {
|
|
160
179
|
default: () => [
|
|
161
|
-
props.src ? (0,
|
|
162
|
-
(0,
|
|
180
|
+
props.src ? (0, import_vue2.h)(import_reka_ui.AvatarImage, { class: "ckui-avatar__image", src: props.src, alt: "" }) : null,
|
|
181
|
+
(0, import_vue2.h)(import_reka_ui.AvatarFallback, {
|
|
163
182
|
class: "ckui-avatar__fallback",
|
|
164
183
|
...props.src ? { delayMs: 300 } : {}
|
|
165
184
|
}, () => initials(props.name))
|
|
@@ -169,251 +188,588 @@ var ConvoKitAvatar = (0, import_vue.defineComponent)({
|
|
|
169
188
|
});
|
|
170
189
|
|
|
171
190
|
// src/components/conversation.ts
|
|
172
|
-
var
|
|
173
|
-
var
|
|
191
|
+
var import_vue6 = require("@lucide/vue");
|
|
192
|
+
var import_vue7 = require("vue");
|
|
174
193
|
|
|
175
194
|
// src/composables/use-conversation.ts
|
|
176
|
-
var
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
195
|
+
var import_vue3 = require("vue");
|
|
196
|
+
|
|
197
|
+
// src/conversation-store.ts
|
|
198
|
+
function version(message) {
|
|
199
|
+
return message.updatedAt?.getTime() ?? message.createdAt.getTime();
|
|
200
|
+
}
|
|
201
|
+
function hasContent(message) {
|
|
202
|
+
return !!message.text?.trim() || message.media.length > 0;
|
|
203
|
+
}
|
|
204
|
+
function newest(current, incoming, incomingComplete = true) {
|
|
205
|
+
return version(current) > version(incoming) || !incomingComplete && version(current) === version(incoming) ? current : incoming;
|
|
206
|
+
}
|
|
207
|
+
function compare(left, right) {
|
|
208
|
+
return left.createdAt.getTime() - right.createdAt.getTime() || (left.id < right.id ? -1 : left.id > right.id ? 1 : 0);
|
|
209
|
+
}
|
|
210
|
+
function blank(currentUserId = "") {
|
|
211
|
+
return {
|
|
212
|
+
conversation: null,
|
|
213
|
+
messages: [],
|
|
214
|
+
typingUserIds: /* @__PURE__ */ new Set(),
|
|
215
|
+
readAtByUserId: /* @__PURE__ */ new Map(),
|
|
216
|
+
isInitialLoading: false,
|
|
217
|
+
isLoadingOlder: false,
|
|
218
|
+
isReconciling: false,
|
|
219
|
+
isSending: false,
|
|
220
|
+
hasOlderMessages: true,
|
|
221
|
+
hasLoaded: false,
|
|
222
|
+
error: null,
|
|
223
|
+
currentUserId
|
|
205
224
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
225
|
+
}
|
|
226
|
+
var ConversationStore = class {
|
|
227
|
+
constructor(options) {
|
|
228
|
+
this.options = options;
|
|
229
|
+
this.client = options.client;
|
|
230
|
+
this.room = options.conversationId.trim();
|
|
231
|
+
this.pageSize = options.messagePageSize ?? 30;
|
|
232
|
+
this.typingTimeout = options.typingTimeoutMs ?? 3e3;
|
|
233
|
+
if (!this.room) throw new TypeError("conversationId is required");
|
|
234
|
+
if (!Number.isInteger(this.pageSize) || this.pageSize < 1 || this.pageSize > 100) {
|
|
235
|
+
throw new RangeError("messagePageSize must be an integer between 1 and 100");
|
|
236
|
+
}
|
|
237
|
+
if (!Number.isFinite(this.typingTimeout) || this.typingTimeout < 0) {
|
|
238
|
+
throw new RangeError("typingTimeoutMs must be non-negative");
|
|
215
239
|
}
|
|
240
|
+
this.owner = this.client.sessionIdentity;
|
|
241
|
+
this.user = this.owner ? this.client.currentUserId : "";
|
|
242
|
+
this.state = blank(this.user);
|
|
243
|
+
}
|
|
244
|
+
options;
|
|
245
|
+
client;
|
|
246
|
+
room;
|
|
247
|
+
owner;
|
|
248
|
+
user;
|
|
249
|
+
pageSize;
|
|
250
|
+
typingTimeout;
|
|
251
|
+
state;
|
|
252
|
+
listeners = /* @__PURE__ */ new Set();
|
|
253
|
+
subscriptions = [];
|
|
254
|
+
disposed = true;
|
|
255
|
+
generation = 0;
|
|
256
|
+
cursor;
|
|
257
|
+
revision = 0;
|
|
258
|
+
changes = /* @__PURE__ */ new Map();
|
|
259
|
+
hydrations = /* @__PURE__ */ new Map();
|
|
260
|
+
hydrationPool = { running: /* @__PURE__ */ new Set(), queued: /* @__PURE__ */ new Map() };
|
|
261
|
+
// Keep tombstones until an explicit reload/session change, including across refreshes.
|
|
262
|
+
deleted = /* @__PURE__ */ new Set();
|
|
263
|
+
sendRevision;
|
|
264
|
+
pendingSequence = 0;
|
|
265
|
+
refreshQueued = false;
|
|
266
|
+
typingTimers = /* @__PURE__ */ new Map();
|
|
267
|
+
ownTypingTimer;
|
|
268
|
+
sentTyping = false;
|
|
269
|
+
typingRevision = 0;
|
|
270
|
+
lastTypingSentAt = -Infinity;
|
|
271
|
+
getSnapshot = () => this.state;
|
|
272
|
+
subscribe = (listener) => {
|
|
273
|
+
this.listeners.add(listener);
|
|
274
|
+
return () => {
|
|
275
|
+
this.listeners.delete(listener);
|
|
276
|
+
};
|
|
216
277
|
};
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
278
|
+
patch(patch) {
|
|
279
|
+
this.state = { ...this.state, ...patch };
|
|
280
|
+
for (const listener of this.listeners) listener();
|
|
281
|
+
}
|
|
282
|
+
alive(generation = this.generation) {
|
|
283
|
+
return !this.disposed && generation === this.generation && this.owner !== null && this.client.sessionIdentity === this.owner;
|
|
284
|
+
}
|
|
285
|
+
start = (autoLoad = true) => {
|
|
286
|
+
if (this.owner === null || this.client.sessionIdentity !== this.owner) return;
|
|
287
|
+
this.disposed = false;
|
|
288
|
+
this.patch({ currentUserId: this.user });
|
|
289
|
+
if (autoLoad) void this.loadInitial();
|
|
290
|
+
else {
|
|
291
|
+
try {
|
|
292
|
+
this.attach(this.generation, false);
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
detach() {
|
|
298
|
+
const active = this.subscriptions;
|
|
299
|
+
this.subscriptions = [];
|
|
300
|
+
for (const subscription of active) void subscription.unsubscribe().catch(() => void 0);
|
|
301
|
+
}
|
|
302
|
+
clearTyping() {
|
|
303
|
+
for (const timer of this.typingTimers.values()) clearTimeout(timer);
|
|
304
|
+
this.typingTimers.clear();
|
|
305
|
+
clearTimeout(this.ownTypingTimer);
|
|
306
|
+
this.ownTypingTimer = void 0;
|
|
307
|
+
this.sentTyping = false;
|
|
308
|
+
this.typingRevision++;
|
|
309
|
+
this.lastTypingSentAt = -Infinity;
|
|
310
|
+
this.patch({ typingUserIds: /* @__PURE__ */ new Set() });
|
|
311
|
+
}
|
|
312
|
+
clear() {
|
|
313
|
+
this.generation++;
|
|
314
|
+
this.detach();
|
|
315
|
+
this.clearTyping();
|
|
316
|
+
this.cursor = void 0;
|
|
317
|
+
this.changes.clear();
|
|
318
|
+
this.hydrations.clear();
|
|
319
|
+
this.hydrationPool.queued.clear();
|
|
320
|
+
this.deleted.clear();
|
|
321
|
+
this.sendRevision = void 0;
|
|
322
|
+
this.refreshQueued = false;
|
|
323
|
+
}
|
|
324
|
+
dispose = () => {
|
|
325
|
+
if (this.alive() && this.sentTyping) {
|
|
326
|
+
void this.client.sendTyping({ conversationId: this.room, isTyping: false }).catch(() => void 0);
|
|
327
|
+
}
|
|
328
|
+
this.disposed = true;
|
|
329
|
+
this.clear();
|
|
330
|
+
this.patch(blank());
|
|
331
|
+
};
|
|
332
|
+
fail(cause, generation, history = false) {
|
|
333
|
+
if (!this.alive(generation)) return;
|
|
334
|
+
const status = typeof cause === "object" && cause !== null && "status" in cause ? cause.status : void 0;
|
|
335
|
+
if (history && (status === 401 || status === 403 || status === 404)) {
|
|
336
|
+
this.clear();
|
|
337
|
+
this.patch({ ...blank(this.user), error: cause, hasLoaded: true, hasOlderMessages: false });
|
|
338
|
+
} else this.patch({ error: cause });
|
|
339
|
+
}
|
|
340
|
+
attach(generation, data = true) {
|
|
341
|
+
const report = (cause) => this.fail(cause, generation);
|
|
342
|
+
const add = (create) => {
|
|
343
|
+
if (!this.alive(generation)) return;
|
|
344
|
+
const subscription = create();
|
|
345
|
+
if (this.alive(generation)) this.subscriptions.push(subscription);
|
|
346
|
+
else void subscription.unsubscribe().catch(() => void 0);
|
|
220
347
|
};
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
client.
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}, report)
|
|
243
|
-
|
|
348
|
+
try {
|
|
349
|
+
add(() => this.client.onConnectionEvent({
|
|
350
|
+
onEvent: ({ topic, status }) => {
|
|
351
|
+
if (!data || !this.alive(generation) || topic !== `messages:${this.room}` && topic !== `conversation:${this.room}`) return;
|
|
352
|
+
if (status === "SUBSCRIBED") this.queueRefresh();
|
|
353
|
+
else this.clearTyping();
|
|
354
|
+
},
|
|
355
|
+
onSessionEnded: () => {
|
|
356
|
+
if (this.disposed || generation !== this.generation) return;
|
|
357
|
+
this.dispose();
|
|
358
|
+
},
|
|
359
|
+
onError: report
|
|
360
|
+
}));
|
|
361
|
+
if (!data) return;
|
|
362
|
+
add(() => this.client.onMessage(this.room, (event) => this.onMessage(event, generation), report));
|
|
363
|
+
add(() => this.client.onMessageDeleted(this.room, ({ id, conversationId }) => {
|
|
364
|
+
if (!this.alive(generation) || conversationId !== this.room || !id.trim()) return;
|
|
365
|
+
this.removeMessage(id);
|
|
366
|
+
}, report));
|
|
367
|
+
add(() => this.client.onReadReceipt(this.room, ({ userId, readAt }) => {
|
|
368
|
+
if (this.alive(generation)) this.mergeReads([[userId, readAt]]);
|
|
369
|
+
}, report));
|
|
370
|
+
add(() => this.client.onTyping(this.room, ({ userId, isTyping }) => {
|
|
371
|
+
if (!this.alive(generation) || !userId.trim() || userId === this.user) return;
|
|
372
|
+
clearTimeout(this.typingTimers.get(userId));
|
|
373
|
+
this.typingTimers.delete(userId);
|
|
374
|
+
const next = new Set(this.state.typingUserIds);
|
|
375
|
+
if (isTyping) {
|
|
376
|
+
next.add(userId);
|
|
377
|
+
this.typingTimers.set(userId, setTimeout(() => {
|
|
378
|
+
this.typingTimers.delete(userId);
|
|
379
|
+
if (!this.alive(generation)) return;
|
|
380
|
+
const remaining = new Set(this.state.typingUserIds);
|
|
381
|
+
remaining.delete(userId);
|
|
382
|
+
this.patch({ typingUserIds: remaining });
|
|
383
|
+
}, this.typingTimeout));
|
|
384
|
+
} else next.delete(userId);
|
|
385
|
+
this.patch({ typingUserIds: next });
|
|
386
|
+
}, report));
|
|
387
|
+
} catch (cause) {
|
|
388
|
+
this.detach();
|
|
389
|
+
this.fail(cause, generation);
|
|
390
|
+
throw cause;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
validMessage(message) {
|
|
394
|
+
return message.conversationId === this.room && !!message.id.trim() && !!message.senderId.trim() && !isConvoKitPendingMessage(message) && Number.isFinite(message.createdAt.getTime()) && Number.isFinite(version(message));
|
|
395
|
+
}
|
|
396
|
+
onMessage(event, generation) {
|
|
397
|
+
const { message, type } = event;
|
|
398
|
+
if (!this.alive(generation) || type !== "insert" && type !== "update" || !this.validMessage(message) || this.deleted.has(message.id)) return;
|
|
399
|
+
const existing = this.state.messages.find((item) => item.id === message.id);
|
|
400
|
+
const known = existing ?? this.changes.get(message.id)?.message;
|
|
401
|
+
if (known && version(message) < version(known)) return;
|
|
402
|
+
const insert = type === "insert" || this.changes.get(message.id)?.insert === true;
|
|
403
|
+
if (!existing && !insert && type === "update" && !this.state.isInitialLoading && !this.state.isLoadingOlder && !this.state.isReconciling) return;
|
|
404
|
+
const revision = ++this.revision;
|
|
405
|
+
const provisional = existing && !message.media.length ? { ...message, media: existing.media } : message;
|
|
406
|
+
this.record(provisional, insert, revision, false);
|
|
407
|
+
const job = { revision, generation, message, insert };
|
|
408
|
+
this.hydrations.set(message.id, job);
|
|
409
|
+
this.hydrationPool.queued.set(message.id, job);
|
|
410
|
+
this.drainHydration();
|
|
411
|
+
if (type === "insert" && !existing && message.senderId !== this.user && (this.options.markReadOnReceive ?? true)) {
|
|
412
|
+
void this.markRead();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
record(message, insert, revision, complete) {
|
|
416
|
+
const existing = this.state.messages.find((item) => item.id === message.id);
|
|
417
|
+
if (existing && version(existing) > version(message)) return;
|
|
418
|
+
this.changes.set(message.id, { revision, message, insert, complete });
|
|
419
|
+
if (existing || insert && hasContent(message)) this.patch({ messages: mergeMessages(this.state.messages, [message]) });
|
|
420
|
+
}
|
|
421
|
+
currentHydration(job) {
|
|
422
|
+
return this.alive(job.generation) && !this.deleted.has(job.message.id) && this.hydrations.get(job.message.id) === job;
|
|
423
|
+
}
|
|
424
|
+
removeMessage(id) {
|
|
425
|
+
this.deleted.add(id);
|
|
426
|
+
this.changes.delete(id);
|
|
427
|
+
this.hydrations.delete(id);
|
|
428
|
+
this.hydrationPool.queued.delete(id);
|
|
429
|
+
this.patch({ messages: this.state.messages.filter((message) => message.id !== id) });
|
|
430
|
+
}
|
|
431
|
+
drainHydration() {
|
|
432
|
+
const pool = this.hydrationPool;
|
|
433
|
+
for (const [id, job] of pool.queued) {
|
|
434
|
+
if (pool.running.size >= 8) break;
|
|
435
|
+
if (pool.running.has(id)) continue;
|
|
436
|
+
pool.queued.delete(id);
|
|
437
|
+
if (!this.currentHydration(job)) continue;
|
|
438
|
+
pool.running.add(id);
|
|
439
|
+
void this.hydrate(job, pool);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async hydrate(job, pool) {
|
|
443
|
+
const id = job.message.id;
|
|
444
|
+
try {
|
|
445
|
+
if (!this.currentHydration(job)) return;
|
|
446
|
+
const full = await this.client.getMessage(id);
|
|
447
|
+
if (!this.currentHydration(job)) return;
|
|
448
|
+
if (!this.validMessage(full) || full.id !== id || full.senderId !== job.message.senderId || version(full) < version(job.message)) {
|
|
449
|
+
throw new Error("Complete message response does not match the observed resource/revision");
|
|
450
|
+
}
|
|
451
|
+
this.record(full, job.insert, job.revision, true);
|
|
452
|
+
} catch (cause) {
|
|
453
|
+
if (!this.currentHydration(job)) return;
|
|
454
|
+
const status = typeof cause === "object" && cause !== null && "status" in cause ? cause.status : void 0;
|
|
455
|
+
if (status === 404) this.removeMessage(id);
|
|
456
|
+
this.fail(cause, job.generation);
|
|
457
|
+
this.queueRefresh();
|
|
458
|
+
} finally {
|
|
459
|
+
if (this.hydrations.get(id) === job) this.hydrations.delete(id);
|
|
460
|
+
pool.running.delete(id);
|
|
461
|
+
queueMicrotask(() => {
|
|
462
|
+
if (this.hydrationPool === pool) this.drainHydration();
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
mergeReads(entries) {
|
|
467
|
+
const next = new Map(this.state.readAtByUserId);
|
|
468
|
+
for (const [userId, readAt] of entries) {
|
|
469
|
+
if (!userId.trim() || !Number.isFinite(readAt.getTime())) continue;
|
|
470
|
+
if (readAt.getTime() > (next.get(userId)?.getTime() ?? -Infinity)) next.set(userId, readAt);
|
|
471
|
+
}
|
|
472
|
+
this.patch({ readAtByUserId: next });
|
|
473
|
+
}
|
|
474
|
+
validatePage(page, before) {
|
|
475
|
+
if (page.length > this.pageSize) throw new Error("Message page exceeds the requested limit");
|
|
476
|
+
let previous = before;
|
|
477
|
+
for (const message of page) {
|
|
478
|
+
if (!this.validMessage(message) || previous && compare(message, previous) >= 0) {
|
|
479
|
+
throw new Error("Message history must contain distinct, room-scoped rows in newest-first cursor order");
|
|
480
|
+
}
|
|
481
|
+
previous = message;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
fetchPage(before) {
|
|
485
|
+
return this.client.getMessages({
|
|
486
|
+
conversationId: this.room,
|
|
487
|
+
limit: this.pageSize,
|
|
488
|
+
...before ? { beforeCreatedAt: before.createdAt, beforeId: before.id } : {}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
overlay(rows, revision) {
|
|
492
|
+
const byId = new Map(rows.filter((message) => !this.deleted.has(message.id)).map((message) => [message.id, message]));
|
|
493
|
+
for (const [id, change] of this.changes) {
|
|
494
|
+
if (change.revision > revision && !this.deleted.has(id) && (change.insert || byId.has(id))) {
|
|
495
|
+
const current = byId.get(id);
|
|
496
|
+
if (current || change.complete || hasContent(change.message)) {
|
|
497
|
+
byId.set(id, current ? newest(current, change.message, change.complete) : change.message);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
for (const message of this.state.messages) {
|
|
502
|
+
if (isConvoKitPendingMessage(message)) byId.set(message.id, message);
|
|
503
|
+
}
|
|
504
|
+
return mergeMessages([], [...byId.values()]);
|
|
505
|
+
}
|
|
506
|
+
prune(revision) {
|
|
507
|
+
const safeRevision = Math.min(revision, this.sendRevision ?? Infinity);
|
|
508
|
+
for (const [id, change] of this.changes) if (change.revision <= safeRevision) this.changes.delete(id);
|
|
509
|
+
for (const [id, job] of this.hydrations) if (job.revision <= revision) {
|
|
510
|
+
this.hydrations.delete(id);
|
|
511
|
+
this.hydrationPool.queued.delete(id);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
loadInitial = async () => {
|
|
515
|
+
if (!this.alive()) return;
|
|
516
|
+
this.clear();
|
|
517
|
+
const generation = this.generation;
|
|
518
|
+
this.patch({ ...blank(this.user), isInitialLoading: true });
|
|
519
|
+
const revision = this.revision;
|
|
520
|
+
try {
|
|
521
|
+
this.attach(generation);
|
|
522
|
+
if (!this.alive(generation)) return;
|
|
523
|
+
const [conversation, page] = await Promise.all([this.client.getConversation(this.room), this.fetchPage()]);
|
|
524
|
+
if (!this.alive(generation)) return;
|
|
525
|
+
if (conversation.id !== this.room) throw new Error("Conversation response belongs to a different room");
|
|
526
|
+
this.validatePage(page);
|
|
527
|
+
this.cursor = page.at(-1);
|
|
528
|
+
this.patch({ conversation, messages: this.overlay(page, revision), hasOlderMessages: page.length === this.pageSize });
|
|
529
|
+
this.mergeReads(conversation.participants.flatMap((participant) => participant.lastReadAt ? [[participant.appUserId, participant.lastReadAt]] : []));
|
|
530
|
+
this.prune(revision);
|
|
531
|
+
if (this.options.markReadOnLoad ?? true) await this.markRead();
|
|
532
|
+
} catch (cause) {
|
|
533
|
+
this.fail(cause, generation, true);
|
|
534
|
+
} finally {
|
|
535
|
+
if (this.alive(generation)) {
|
|
536
|
+
this.patch({ isInitialLoading: false, hasLoaded: true });
|
|
537
|
+
this.flushRefresh();
|
|
538
|
+
}
|
|
539
|
+
}
|
|
244
540
|
};
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
isLoadingOlder.
|
|
261
|
-
|
|
541
|
+
queueRefresh() {
|
|
542
|
+
this.refreshQueued = true;
|
|
543
|
+
this.flushRefresh();
|
|
544
|
+
}
|
|
545
|
+
flushRefresh() {
|
|
546
|
+
const generation = this.generation;
|
|
547
|
+
void Promise.resolve().then(() => {
|
|
548
|
+
if (!this.alive(generation) || !this.refreshQueued || this.state.isInitialLoading || this.state.isLoadingOlder || this.state.isReconciling) return;
|
|
549
|
+
this.refreshQueued = false;
|
|
550
|
+
void this.refresh();
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
/** Re-fetch the entire viewed range atomically; a first-page-only refresh loses history. */
|
|
554
|
+
refresh = async () => {
|
|
555
|
+
if (!this.alive()) return;
|
|
556
|
+
if (this.state.isInitialLoading || this.state.isLoadingOlder || this.state.isReconciling) {
|
|
557
|
+
this.refreshQueued = true;
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (!this.state.hasLoaded || !this.subscriptions.length) return this.loadInitial();
|
|
561
|
+
const generation = this.generation;
|
|
562
|
+
const revision = this.revision;
|
|
563
|
+
const observed = this.state.messages.filter((message) => !isConvoKitPendingMessage(message)).concat([...this.changes.values()].filter((change) => change.insert).map((change) => change.message));
|
|
564
|
+
const boundary = observed.reduce((oldest, message) => !oldest || compare(message, oldest) < 0 ? message : oldest, this.cursor);
|
|
565
|
+
const previouslyExhausted = !this.state.hasOlderMessages;
|
|
566
|
+
this.patch({ isReconciling: true, error: null });
|
|
262
567
|
try {
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
]
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
568
|
+
const conversation = await this.client.getConversation(this.room);
|
|
569
|
+
if (!this.alive(generation)) return;
|
|
570
|
+
if (conversation.id !== this.room) throw new Error("Conversation response belongs to a different room");
|
|
571
|
+
const rows = [];
|
|
572
|
+
let before;
|
|
573
|
+
let hasOlder = true;
|
|
574
|
+
while (this.alive(generation)) {
|
|
575
|
+
const page = await this.fetchPage(before);
|
|
576
|
+
if (!this.alive(generation)) return;
|
|
577
|
+
this.validatePage(page, before);
|
|
578
|
+
rows.push(...page);
|
|
579
|
+
before = page.at(-1) ?? before;
|
|
580
|
+
hasOlder = page.length === this.pageSize;
|
|
581
|
+
if (!hasOlder || !boundary || !previouslyExhausted && before && compare(before, boundary) < 0) break;
|
|
582
|
+
}
|
|
583
|
+
this.cursor = before;
|
|
584
|
+
const reconciled = this.overlay(rows, revision);
|
|
585
|
+
const survivingIds = new Set(reconciled.map((message) => message.id));
|
|
586
|
+
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));
|
|
587
|
+
for (const id of known) if (!survivingIds.has(id)) {
|
|
588
|
+
this.deleted.add(id);
|
|
589
|
+
this.changes.delete(id);
|
|
590
|
+
this.hydrations.delete(id);
|
|
591
|
+
this.hydrationPool.queued.delete(id);
|
|
592
|
+
}
|
|
593
|
+
this.patch({ conversation, messages: reconciled, hasOlderMessages: hasOlder });
|
|
594
|
+
this.mergeReads(conversation.participants.flatMap((participant) => participant.lastReadAt ? [[participant.appUserId, participant.lastReadAt]] : []));
|
|
595
|
+
this.prune(revision);
|
|
273
596
|
} catch (cause) {
|
|
274
|
-
|
|
597
|
+
this.fail(cause, generation, true);
|
|
275
598
|
} finally {
|
|
276
|
-
if (
|
|
277
|
-
|
|
278
|
-
|
|
599
|
+
if (this.alive(generation)) {
|
|
600
|
+
this.patch({ isReconciling: false });
|
|
601
|
+
this.flushRefresh();
|
|
279
602
|
}
|
|
280
603
|
}
|
|
281
604
|
};
|
|
282
|
-
|
|
283
|
-
if (isInitialLoading.
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
error.value = null;
|
|
605
|
+
loadOlderMessages = async () => {
|
|
606
|
+
if (!this.alive() || !this.state.hasLoaded || this.state.isInitialLoading || this.state.isLoadingOlder || this.state.isReconciling || !this.state.hasOlderMessages) return;
|
|
607
|
+
const generation = this.generation;
|
|
608
|
+
const revision = this.revision;
|
|
609
|
+
const cursor = this.cursor;
|
|
610
|
+
this.patch({ isLoadingOlder: true, error: null });
|
|
289
611
|
try {
|
|
290
|
-
const page = await
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
612
|
+
const page = await this.fetchPage(cursor);
|
|
613
|
+
if (!this.alive(generation)) return;
|
|
614
|
+
this.validatePage(page, cursor);
|
|
615
|
+
this.cursor = page.at(-1) ?? cursor;
|
|
616
|
+
this.patch({
|
|
617
|
+
messages: this.overlay(mergeMessages(page, this.state.messages), revision),
|
|
618
|
+
hasOlderMessages: page.length === this.pageSize
|
|
294
619
|
});
|
|
295
|
-
if (disposed || activeGeneration !== generation) return;
|
|
296
|
-
messages.value = mergeMessages(messages.value, page);
|
|
297
|
-
hasOlderMessages.value = page.length === messagePageSize;
|
|
298
620
|
} catch (cause) {
|
|
299
|
-
|
|
621
|
+
this.fail(cause, generation, true);
|
|
300
622
|
} finally {
|
|
301
|
-
if (
|
|
623
|
+
if (this.alive(generation)) {
|
|
624
|
+
this.patch({ isLoadingOlder: false });
|
|
625
|
+
this.flushRefresh();
|
|
626
|
+
}
|
|
302
627
|
}
|
|
303
628
|
};
|
|
304
|
-
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
void updateTyping(false);
|
|
308
|
-
}, typingTimeoutMs);
|
|
309
|
-
if (sentTyping === nextTyping) return;
|
|
310
|
-
sentTyping = nextTyping;
|
|
629
|
+
markRead = async () => {
|
|
630
|
+
if (!this.alive()) return;
|
|
631
|
+
const generation = this.generation;
|
|
311
632
|
try {
|
|
312
|
-
await
|
|
313
|
-
conversationId: (0, import_vue2.toValue)(options.conversationId),
|
|
314
|
-
isTyping: nextTyping
|
|
315
|
-
});
|
|
633
|
+
await this.client.markConversationRead(this.room);
|
|
316
634
|
} catch (cause) {
|
|
317
|
-
|
|
635
|
+
this.fail(cause, generation);
|
|
318
636
|
}
|
|
319
637
|
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
638
|
+
updateTyping = async (isTyping) => {
|
|
639
|
+
if (!this.alive()) return;
|
|
640
|
+
const generation = this.generation;
|
|
641
|
+
clearTimeout(this.ownTypingTimer);
|
|
642
|
+
if (isTyping) this.ownTypingTimer = setTimeout(() => {
|
|
643
|
+
if (this.alive(generation)) void this.updateTyping(false);
|
|
644
|
+
}, this.typingTimeout);
|
|
645
|
+
const now = performance.now();
|
|
646
|
+
const renew = isTyping && now - this.lastTypingSentAt >= Math.max(1, this.typingTimeout / 2);
|
|
647
|
+
if (this.sentTyping === isTyping && !renew) return;
|
|
648
|
+
const revision = ++this.typingRevision;
|
|
649
|
+
this.sentTyping = isTyping;
|
|
650
|
+
this.lastTypingSentAt = isTyping ? now : -Infinity;
|
|
651
|
+
try {
|
|
652
|
+
await this.client.sendTyping({ conversationId: this.room, isTyping });
|
|
653
|
+
} catch (cause) {
|
|
654
|
+
if (this.alive(generation) && revision === this.typingRevision) {
|
|
655
|
+
this.sentTyping = false;
|
|
656
|
+
this.fail(cause, generation);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
sendMessage = async ({ text, media }) => {
|
|
661
|
+
const normalized = text?.trim();
|
|
662
|
+
if (!this.alive() || this.state.isSending || !normalized && !media?.length) return null;
|
|
663
|
+
const generation = this.generation;
|
|
664
|
+
const revision = this.revision;
|
|
665
|
+
this.sendRevision = revision;
|
|
666
|
+
const pendingId = `convokit-pending-${Date.now()}-${++this.pendingSequence}`;
|
|
667
|
+
const pending = {
|
|
326
668
|
id: pendingId,
|
|
327
|
-
conversationId:
|
|
328
|
-
senderId:
|
|
329
|
-
text:
|
|
669
|
+
conversationId: this.room,
|
|
670
|
+
senderId: this.user,
|
|
671
|
+
text: normalized || null,
|
|
330
672
|
media: media ?? [],
|
|
331
673
|
createdAt: /* @__PURE__ */ new Date(),
|
|
332
674
|
updatedAt: null
|
|
333
675
|
};
|
|
334
|
-
|
|
335
|
-
messages.value = mergeMessages(messages.value, [pendingMessage]);
|
|
336
|
-
isSending.value = true;
|
|
337
|
-
error.value = null;
|
|
338
|
-
const activeGeneration = generation;
|
|
676
|
+
this.patch({ messages: mergeMessages(this.state.messages, [pending]), isSending: true, error: null });
|
|
339
677
|
try {
|
|
340
|
-
const message = await
|
|
341
|
-
conversationId:
|
|
342
|
-
...
|
|
678
|
+
const message = await this.client.sendMessage({
|
|
679
|
+
conversationId: this.room,
|
|
680
|
+
...normalized ? { text: normalized } : {},
|
|
343
681
|
...media?.length ? { media } : {}
|
|
344
682
|
});
|
|
345
|
-
|
|
346
|
-
if (!
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
683
|
+
if (!this.alive(generation)) return null;
|
|
684
|
+
if (!this.validMessage(message) || message.senderId !== this.user) throw new Error("Send response belongs to a different room or sender");
|
|
685
|
+
const live = this.changes.get(message.id);
|
|
686
|
+
const existing = this.state.messages.find((item) => item.id === message.id);
|
|
687
|
+
let latest = existing ? newest(message, existing, live?.complete !== false) : message;
|
|
688
|
+
if (live && live.revision > revision) latest = newest(latest, live.message, live.complete);
|
|
689
|
+
if (!this.deleted.has(message.id)) this.changes.set(message.id, { revision: ++this.revision, message: latest, insert: true, complete: true });
|
|
690
|
+
this.patch({ messages: mergeMessages(
|
|
691
|
+
this.state.messages.filter((item) => item.id !== pendingId),
|
|
692
|
+
this.deleted.has(message.id) ? [] : [latest]
|
|
693
|
+
) });
|
|
694
|
+
void this.updateTyping(false);
|
|
695
|
+
return this.alive(generation) ? latest : null;
|
|
354
696
|
} catch (cause) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
error.value = cause;
|
|
697
|
+
if (this.alive(generation)) {
|
|
698
|
+
this.patch({ messages: this.state.messages.filter((item) => item.id !== pendingId) });
|
|
699
|
+
this.fail(cause, generation);
|
|
359
700
|
}
|
|
360
701
|
return null;
|
|
361
702
|
} finally {
|
|
362
|
-
if (
|
|
703
|
+
if (this.alive(generation)) {
|
|
704
|
+
this.sendRevision = void 0;
|
|
705
|
+
this.patch({ isSending: false });
|
|
706
|
+
}
|
|
363
707
|
}
|
|
364
708
|
};
|
|
709
|
+
readerIdsFor = (message) => readerIdsFor(message, this.state.readAtByUserId);
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// src/composables/use-conversation.ts
|
|
713
|
+
function useConversation(options) {
|
|
714
|
+
const createStore = () => new ConversationStore({
|
|
715
|
+
...options,
|
|
716
|
+
client: (0, import_vue3.toValue)(options.client),
|
|
717
|
+
conversationId: (0, import_vue3.toValue)(options.conversationId)
|
|
718
|
+
});
|
|
719
|
+
let store = createStore();
|
|
720
|
+
const snapshot = (0, import_vue3.shallowRef)(store.getSnapshot());
|
|
721
|
+
let unsubscribe;
|
|
722
|
+
const stop = (0, import_vue3.watch)(
|
|
723
|
+
() => [(0, import_vue3.toValue)(options.client), (0, import_vue3.toValue)(options.client).sessionIdentity, (0, import_vue3.toValue)(options.conversationId)],
|
|
724
|
+
() => {
|
|
725
|
+
store.dispose();
|
|
726
|
+
unsubscribe?.();
|
|
727
|
+
store = createStore();
|
|
728
|
+
snapshot.value = store.getSnapshot();
|
|
729
|
+
unsubscribe = store.subscribe(() => {
|
|
730
|
+
snapshot.value = store.getSnapshot();
|
|
731
|
+
});
|
|
732
|
+
store.start(options.autoLoad ?? true);
|
|
733
|
+
},
|
|
734
|
+
{ immediate: true, flush: "sync" }
|
|
735
|
+
);
|
|
736
|
+
const field = (key) => (0, import_vue3.computed)(() => snapshot.value[key]);
|
|
365
737
|
const dispose = async () => {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
await (0, import_vue2.toValue)(options.client).sendTyping({
|
|
371
|
-
conversationId: (0, import_vue2.toValue)(options.conversationId),
|
|
372
|
-
isTyping: false
|
|
373
|
-
}).catch(() => void 0);
|
|
374
|
-
}
|
|
375
|
-
await unsubscribe();
|
|
738
|
+
stop();
|
|
739
|
+
store.dispose();
|
|
740
|
+
unsubscribe?.();
|
|
741
|
+
unsubscribe = void 0;
|
|
376
742
|
};
|
|
377
|
-
if (
|
|
378
|
-
(0, import_vue2.watch)(
|
|
379
|
-
() => [(0, import_vue2.toValue)(options.client), (0, import_vue2.toValue)(options.conversationId)],
|
|
380
|
-
() => {
|
|
381
|
-
disposed = false;
|
|
382
|
-
sentTyping = false;
|
|
383
|
-
void loadInitial();
|
|
384
|
-
},
|
|
385
|
-
{ immediate: true }
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
if ((0, import_vue2.getCurrentScope)()) (0, import_vue2.onScopeDispose)(() => {
|
|
743
|
+
if ((0, import_vue3.getCurrentScope)()) (0, import_vue3.onScopeDispose)(() => {
|
|
389
744
|
void dispose();
|
|
390
745
|
});
|
|
391
746
|
return {
|
|
392
|
-
conversation,
|
|
393
|
-
messages,
|
|
394
|
-
typingUserIds,
|
|
395
|
-
readAtByUserId,
|
|
396
|
-
isInitialLoading,
|
|
397
|
-
isLoadingOlder,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
747
|
+
conversation: field("conversation"),
|
|
748
|
+
messages: field("messages"),
|
|
749
|
+
typingUserIds: field("typingUserIds"),
|
|
750
|
+
readAtByUserId: field("readAtByUserId"),
|
|
751
|
+
isInitialLoading: field("isInitialLoading"),
|
|
752
|
+
isLoadingOlder: field("isLoadingOlder"),
|
|
753
|
+
isReconciling: field("isReconciling"),
|
|
754
|
+
isSending: field("isSending"),
|
|
755
|
+
hasOlderMessages: field("hasOlderMessages"),
|
|
756
|
+
hasLoaded: field("hasLoaded"),
|
|
757
|
+
error: field("error"),
|
|
758
|
+
currentUserId: field("currentUserId"),
|
|
759
|
+
readerIdsFor: (message) => store.readerIdsFor(message),
|
|
760
|
+
loadInitial: () => store.loadInitial(),
|
|
761
|
+
refresh: () => store.refresh(),
|
|
762
|
+
loadOlderMessages: () => store.loadOlderMessages(),
|
|
763
|
+
sendMessage: (input) => store.sendMessage(input),
|
|
764
|
+
markRead: () => store.markRead(),
|
|
765
|
+
updateTyping: (isTyping) => store.updateTyping(isTyping),
|
|
410
766
|
dispose
|
|
411
767
|
};
|
|
412
768
|
}
|
|
413
769
|
|
|
414
770
|
// src/components/message-list.ts
|
|
415
|
-
var
|
|
416
|
-
var
|
|
771
|
+
var import_vue4 = require("@lucide/vue");
|
|
772
|
+
var import_vue5 = require("vue");
|
|
417
773
|
var appearanceProps = {
|
|
418
774
|
classNames: { type: Object, default: void 0 },
|
|
419
775
|
styles: { type: Object, default: void 0 },
|
|
@@ -427,33 +783,33 @@ function defaultMedia(media, open, imageLoading) {
|
|
|
427
783
|
const tag = open ? "button" : "div";
|
|
428
784
|
const interactive = open ? { type: "button", onClick: open } : {};
|
|
429
785
|
if (media.type === "image") {
|
|
430
|
-
return (0,
|
|
431
|
-
media.url ? (0,
|
|
432
|
-
media.name ? (0,
|
|
786
|
+
return (0, import_vue5.h)(tag, { ...interactive, class: "ckui-media-card ckui-media-card--image" }, [
|
|
787
|
+
media.url ? (0, import_vue5.h)("img", { src: media.url, alt: media.name ?? "Shared image", loading: imageLoading }) : (0, import_vue5.h)("span", { class: "ckui-media-placeholder" }, [(0, import_vue5.h)(import_vue4.ImageOff, { "aria-hidden": "true" }), " Image unavailable"]),
|
|
788
|
+
media.name ? (0, import_vue5.h)("span", { class: "ckui-media-name" }, media.name) : null
|
|
433
789
|
]);
|
|
434
790
|
}
|
|
435
791
|
if (media.type === "file") {
|
|
436
792
|
const size = formatFileSize(media.size);
|
|
437
|
-
return (0,
|
|
438
|
-
(0,
|
|
439
|
-
(0,
|
|
440
|
-
open ? (0,
|
|
793
|
+
return (0, import_vue5.h)(tag, { ...interactive, class: "ckui-media-card ckui-media-card--file" }, [
|
|
794
|
+
(0, import_vue5.h)(import_vue4.FileText, { "aria-hidden": "true" }),
|
|
795
|
+
(0, import_vue5.h)("span", [(0, import_vue5.h)("strong", media.name || "Attachment"), size ? (0, import_vue5.h)("small", size) : null]),
|
|
796
|
+
open ? (0, import_vue5.h)(import_vue4.Download, { size: 18, "aria-hidden": "true" }) : null
|
|
441
797
|
]);
|
|
442
798
|
}
|
|
443
799
|
if (media.type === "location") {
|
|
444
800
|
const label = media.name || `${media.metadata.lat}, ${media.metadata.lng}`;
|
|
445
|
-
return (0,
|
|
446
|
-
(0,
|
|
447
|
-
(0,
|
|
801
|
+
return (0, import_vue5.h)(tag, { ...interactive, class: "ckui-media-card ckui-media-card--location" }, [
|
|
802
|
+
(0, import_vue5.h)(import_vue4.MapPin, { "aria-hidden": "true" }),
|
|
803
|
+
(0, import_vue5.h)("span", [(0, import_vue5.h)("strong", label), (0, import_vue5.h)("small", `${media.metadata.lat}, ${media.metadata.lng}`)])
|
|
448
804
|
]);
|
|
449
805
|
}
|
|
450
806
|
const contact = media.metadata.email || media.metadata.phone || "Contact details";
|
|
451
|
-
return (0,
|
|
452
|
-
(0,
|
|
453
|
-
(0,
|
|
807
|
+
return (0, import_vue5.h)(tag, { ...interactive, class: "ckui-media-card ckui-media-card--contact" }, [
|
|
808
|
+
(0, import_vue5.h)(import_vue4.ContactRound, { "aria-hidden": "true" }),
|
|
809
|
+
(0, import_vue5.h)("span", [(0, import_vue5.h)("strong", media.name || "Shared contact"), (0, import_vue5.h)("small", String(contact))])
|
|
454
810
|
]);
|
|
455
811
|
}
|
|
456
|
-
var MessageListView = (0,
|
|
812
|
+
var MessageListView = (0, import_vue5.defineComponent)({
|
|
457
813
|
name: "MessageListView",
|
|
458
814
|
inheritAttrs: false,
|
|
459
815
|
props: {
|
|
@@ -477,11 +833,11 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
477
833
|
},
|
|
478
834
|
emits: ["load-older", "attachment-click"],
|
|
479
835
|
setup(props, { attrs, emit, slots }) {
|
|
480
|
-
const internalElement = (0,
|
|
836
|
+
const internalElement = (0, import_vue5.ref)(null);
|
|
481
837
|
let requestInFlight = false;
|
|
482
838
|
let lastRequestedLength = null;
|
|
483
839
|
let previousMessageCount = 0;
|
|
484
|
-
const participants = (0,
|
|
840
|
+
const participants = (0, import_vue5.computed)(() => new Map(props.conversation.participants.flatMap((participant) => [
|
|
485
841
|
[participant.id, participant],
|
|
486
842
|
[participant.appUserId, participant]
|
|
487
843
|
])));
|
|
@@ -503,7 +859,7 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
503
859
|
requestInFlight = false;
|
|
504
860
|
}
|
|
505
861
|
};
|
|
506
|
-
(0,
|
|
862
|
+
(0, import_vue5.watch)(() => [props.messages.length, props.hasOlderMessages], async ([count, hasOlder]) => {
|
|
507
863
|
const previous = previousMessageCount;
|
|
508
864
|
if (count !== previousMessageCount || !hasOlder) lastRequestedLength = null;
|
|
509
865
|
const appended = count > previous;
|
|
@@ -512,7 +868,7 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
512
868
|
if (element && props.reverse && props.stickToBottom && appended) {
|
|
513
869
|
const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
|
|
514
870
|
if (previous === 0 || distanceFromBottom < 320) {
|
|
515
|
-
await (0,
|
|
871
|
+
await (0, import_vue5.nextTick)();
|
|
516
872
|
element.scrollTop = element.scrollHeight;
|
|
517
873
|
}
|
|
518
874
|
}
|
|
@@ -520,10 +876,11 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
520
876
|
const renderMessage = (message, index) => {
|
|
521
877
|
const isCurrentUser = message.senderId === props.currentUserId;
|
|
522
878
|
const sender = participants.value.get(message.senderId);
|
|
523
|
-
const
|
|
879
|
+
const isPending = isConvoKitPendingMessage(message);
|
|
880
|
+
const readerIds = isPending ? /* @__PURE__ */ new Set() : props.readersResolver ? props.readersResolver(message) : readerIdsFor(message, props.readAtByUserId);
|
|
524
881
|
const slotProps = { message, chronologicalIndex: index, isCurrentUser, sender, readerIds };
|
|
525
882
|
const custom = slots.message?.(slotProps);
|
|
526
|
-
if (custom) return (0,
|
|
883
|
+
if (custom) return (0, import_vue5.h)("div", { key: message.id, role: "listitem" }, custom);
|
|
527
884
|
const currentAppearance = appearance();
|
|
528
885
|
const messagePart = isCurrentUser ? "outgoingMessage" : "incomingMessage";
|
|
529
886
|
const mediaNodes = message.media.map((media, mediaIndex) => {
|
|
@@ -531,15 +888,15 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
531
888
|
props.onAttachmentClick?.(media, message);
|
|
532
889
|
} : void 0;
|
|
533
890
|
const mediaSlotProps = { media, message, isCurrentUser, ...open ? { open } : {} };
|
|
534
|
-
return (0,
|
|
891
|
+
return (0, import_vue5.h)("div", {
|
|
535
892
|
key: media.id ?? `${media.type}-${mediaIndex}`,
|
|
536
893
|
class: partClass("media", currentAppearance, "ckui-media"),
|
|
537
894
|
style: partStyle("media", currentAppearance)
|
|
538
895
|
}, slots.media?.(mediaSlotProps) ?? [defaultMedia(media, open, props.imageLoading)]);
|
|
539
896
|
});
|
|
540
897
|
const receiptSlotProps = { message, readerIds };
|
|
541
|
-
return (0,
|
|
542
|
-
(0,
|
|
898
|
+
return (0, import_vue5.h)("div", { key: message.id, role: "listitem" }, [
|
|
899
|
+
(0, import_vue5.h)("article", {
|
|
543
900
|
class: cx(
|
|
544
901
|
!props.unstyled && "ckui-message-row",
|
|
545
902
|
isCurrentUser && !props.unstyled && "ckui-message-row--outgoing",
|
|
@@ -549,16 +906,16 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
549
906
|
style: [props.styles?.message, props.styles?.[messagePart]],
|
|
550
907
|
"data-message-id": message.id
|
|
551
908
|
}, [
|
|
552
|
-
(0,
|
|
553
|
-
!isCurrentUser ? (0,
|
|
554
|
-
message.text ? (0,
|
|
909
|
+
(0, import_vue5.h)("div", { class: "ckui-message-bubble" }, [
|
|
910
|
+
!isCurrentUser ? (0, import_vue5.h)("strong", { class: "ckui-message-sender" }, sender?.name || message.senderId) : null,
|
|
911
|
+
message.text ? (0, import_vue5.h)("div", { class: "ckui-message-text" }, message.text) : null,
|
|
555
912
|
...mediaNodes,
|
|
556
|
-
(0,
|
|
557
|
-
props.formatTime(message.createdAt),
|
|
558
|
-
isCurrentUser ? readerIds.size > 0 ? (0,
|
|
913
|
+
(0, import_vue5.h)("span", { class: "ckui-message-time" }, [
|
|
914
|
+
isPending ? "Sending\u2026" : props.formatTime(message.createdAt),
|
|
915
|
+
isCurrentUser && !isPending ? readerIds.size > 0 ? (0, import_vue5.h)(import_vue4.CheckCheck, { size: 14, "aria-label": "Read" }) : (0, import_vue5.h)(import_vue4.Check, { size: 14, "aria-label": "Delivered" }) : null
|
|
559
916
|
])
|
|
560
917
|
]),
|
|
561
|
-
isCurrentUser ? slots["read-receipt"]?.(receiptSlotProps) ?? (0,
|
|
918
|
+
isCurrentUser && !isPending ? slots["read-receipt"]?.(receiptSlotProps) ?? (0, import_vue5.h)("div", {
|
|
562
919
|
class: partClass("receipt", currentAppearance, "ckui-read-receipt"),
|
|
563
920
|
style: partStyle("receipt", currentAppearance)
|
|
564
921
|
}, readerIds.size > 0 ? `Read by ${readerIds.size}` : "Delivered") : null
|
|
@@ -569,34 +926,34 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
569
926
|
const currentAppearance = appearance();
|
|
570
927
|
const children = [];
|
|
571
928
|
if (props.isLoadingOlder) {
|
|
572
|
-
children.push(slots["loading-older"]?.() ?? (0,
|
|
929
|
+
children.push(slots["loading-older"]?.() ?? (0, import_vue5.h)("div", {
|
|
573
930
|
class: partClass("loading", currentAppearance, "ckui-inline-state"),
|
|
574
931
|
style: partStyle("loading", currentAppearance),
|
|
575
932
|
role: "status"
|
|
576
|
-
}, [(0,
|
|
933
|
+
}, [(0, import_vue5.h)(import_vue4.LoaderCircle, { class: "ckui-spin", "aria-hidden": "true" }), " Loading older messages\u2026"]));
|
|
577
934
|
}
|
|
578
935
|
if (props.error) {
|
|
579
936
|
const retry = props.onLoadOlder ? () => {
|
|
580
937
|
void requestOlder();
|
|
581
938
|
} : void 0;
|
|
582
|
-
children.push(slots.error?.({ error: props.error, ...retry ? { retry } : {} }) ?? (0,
|
|
939
|
+
children.push(slots.error?.({ error: props.error, ...retry ? { retry } : {} }) ?? (0, import_vue5.h)("div", {
|
|
583
940
|
class: partClass("error", currentAppearance, "ckui-inline-state ckui-state--error"),
|
|
584
941
|
style: partStyle("error", currentAppearance),
|
|
585
942
|
role: "alert"
|
|
586
943
|
}, [
|
|
587
|
-
(0,
|
|
588
|
-
retry ? (0,
|
|
944
|
+
(0, import_vue5.h)("span", errorMessage(props.error)),
|
|
945
|
+
retry ? (0, import_vue5.h)("button", { type: "button", class: "ckui-link-button", onClick: retry }, "Retry") : null
|
|
589
946
|
]));
|
|
590
947
|
}
|
|
591
948
|
if (props.messages.length === 0 && !props.isLoadingOlder) {
|
|
592
|
-
children.push(slots.empty?.() ?? (0,
|
|
949
|
+
children.push(slots.empty?.() ?? (0, import_vue5.h)("div", {
|
|
593
950
|
class: partClass("empty", currentAppearance, "ckui-state"),
|
|
594
951
|
style: partStyle("empty", currentAppearance)
|
|
595
|
-
}, [(0,
|
|
952
|
+
}, [(0, import_vue5.h)(import_vue4.MessageCircle, { "aria-hidden": "true" }), " No messages yet"]));
|
|
596
953
|
} else {
|
|
597
954
|
children.push(...props.messages.map(renderMessage));
|
|
598
955
|
}
|
|
599
|
-
return (0,
|
|
956
|
+
return (0, import_vue5.h)("div", {
|
|
600
957
|
...attrs,
|
|
601
958
|
ref: (element) => {
|
|
602
959
|
internalElement.value = element;
|
|
@@ -671,7 +1028,7 @@ function typingLabel(userIds, displayNameForUser) {
|
|
|
671
1028
|
if (names.length === 2) return `${names[0]} and ${names[1]} are typing\u2026`;
|
|
672
1029
|
return `${names[0]} and ${names.length - 1} others are typing\u2026`;
|
|
673
1030
|
}
|
|
674
|
-
var ConversationView = (0,
|
|
1031
|
+
var ConversationView = (0, import_vue7.defineComponent)({
|
|
675
1032
|
name: "ConversationView",
|
|
676
1033
|
inheritAttrs: false,
|
|
677
1034
|
props: viewProps,
|
|
@@ -686,8 +1043,8 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
686
1043
|
"update:modelValue"
|
|
687
1044
|
],
|
|
688
1045
|
setup(props, { attrs, emit, slots }) {
|
|
689
|
-
const internalDraft = (0,
|
|
690
|
-
const submitting = (0,
|
|
1046
|
+
const internalDraft = (0, import_vue7.ref)(props.defaultDraft);
|
|
1047
|
+
const submitting = (0, import_vue7.ref)(false);
|
|
691
1048
|
const appearance = () => ({
|
|
692
1049
|
density: props.density,
|
|
693
1050
|
unstyled: props.unstyled,
|
|
@@ -737,23 +1094,23 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
737
1094
|
...props.onBack ? { onBack: goBack } : {},
|
|
738
1095
|
...props.onRefresh ? { onRefresh: refresh } : {}
|
|
739
1096
|
};
|
|
740
|
-
return slots.header?.(slotProps) ?? (0,
|
|
1097
|
+
return slots.header?.(slotProps) ?? (0, import_vue7.h)("header", {
|
|
741
1098
|
class: partClass("header", appearance(), "ckui-conversation-header"),
|
|
742
1099
|
style: partStyle("header", appearance())
|
|
743
1100
|
}, [
|
|
744
|
-
props.onBack ? (0,
|
|
1101
|
+
props.onBack ? (0, import_vue7.h)("button", {
|
|
745
1102
|
type: "button",
|
|
746
1103
|
"aria-label": "Back",
|
|
747
1104
|
onClick: goBack,
|
|
748
1105
|
class: partClass("button", appearance(), "ckui-icon-button"),
|
|
749
1106
|
style: partStyle("button", appearance())
|
|
750
|
-
}, [(0,
|
|
751
|
-
(0,
|
|
752
|
-
(0,
|
|
753
|
-
(0,
|
|
754
|
-
(0,
|
|
1107
|
+
}, [(0, import_vue7.h)(import_vue6.ArrowLeft, { size: 20, "aria-hidden": "true" })]) : null,
|
|
1108
|
+
(0, import_vue7.h)(ConvoKitAvatar, { name: props.conversation.displayTitle, src: props.conversation.imageUrl }),
|
|
1109
|
+
(0, import_vue7.h)("div", { class: "ckui-conversation-header__body" }, [
|
|
1110
|
+
(0, import_vue7.h)("strong", props.conversation.displayTitle),
|
|
1111
|
+
(0, import_vue7.h)("span", `${props.conversation.participants.length} participant${props.conversation.participants.length === 1 ? "" : "s"}`)
|
|
755
1112
|
]),
|
|
756
|
-
props.onRefresh ? (0,
|
|
1113
|
+
props.onRefresh ? (0, import_vue7.h)("button", {
|
|
757
1114
|
type: "button",
|
|
758
1115
|
"aria-label": "Refresh conversation",
|
|
759
1116
|
onClick: () => {
|
|
@@ -761,12 +1118,12 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
761
1118
|
},
|
|
762
1119
|
class: partClass("button", appearance(), "ckui-icon-button"),
|
|
763
1120
|
style: partStyle("button", appearance())
|
|
764
|
-
}, [(0,
|
|
1121
|
+
}, [(0, import_vue7.h)(import_vue6.RefreshCw, { size: 18, "aria-hidden": "true" })]) : null
|
|
765
1122
|
]);
|
|
766
1123
|
};
|
|
767
1124
|
const renderTyping = () => {
|
|
768
1125
|
const slotProps = { userIds: props.typingUserIds, displayNameForUser: nameForUser };
|
|
769
|
-
return slots["typing-indicator"]?.(slotProps) ?? (0,
|
|
1126
|
+
return slots["typing-indicator"]?.(slotProps) ?? (0, import_vue7.h)("div", {
|
|
770
1127
|
class: partClass("typing", appearance(), "ckui-typing"),
|
|
771
1128
|
style: partStyle("typing", appearance()),
|
|
772
1129
|
"aria-live": "polite"
|
|
@@ -782,7 +1139,7 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
782
1139
|
},
|
|
783
1140
|
...props.onAddAttachment ? { addAttachment } : {}
|
|
784
1141
|
};
|
|
785
|
-
return slots.composer?.(slotProps) ?? (0,
|
|
1142
|
+
return slots.composer?.(slotProps) ?? (0, import_vue7.h)("form", {
|
|
786
1143
|
class: partClass("composer", appearance(), "ckui-composer"),
|
|
787
1144
|
style: partStyle("composer", appearance()),
|
|
788
1145
|
onSubmit: (event) => {
|
|
@@ -790,14 +1147,14 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
790
1147
|
void submit();
|
|
791
1148
|
}
|
|
792
1149
|
}, [
|
|
793
|
-
props.onAddAttachment ? (0,
|
|
1150
|
+
props.onAddAttachment ? (0, import_vue7.h)("button", {
|
|
794
1151
|
type: "button",
|
|
795
1152
|
"aria-label": "Add attachment",
|
|
796
1153
|
onClick: addAttachment,
|
|
797
1154
|
class: partClass("button", appearance(), "ckui-icon-button"),
|
|
798
1155
|
style: partStyle("button", appearance())
|
|
799
|
-
}, [(0,
|
|
800
|
-
(0,
|
|
1156
|
+
}, [(0, import_vue7.h)(import_vue6.Paperclip, { size: 20, "aria-hidden": "true" })]) : null,
|
|
1157
|
+
(0, import_vue7.h)("textarea", {
|
|
801
1158
|
...props.composerProps,
|
|
802
1159
|
rows: props.composerProps?.rows ?? 1,
|
|
803
1160
|
placeholder: props.composerPlaceholder,
|
|
@@ -820,38 +1177,38 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
820
1177
|
}
|
|
821
1178
|
}
|
|
822
1179
|
}),
|
|
823
|
-
(0,
|
|
1180
|
+
(0, import_vue7.h)("button", {
|
|
824
1181
|
type: "submit",
|
|
825
1182
|
"aria-label": "Send message",
|
|
826
1183
|
disabled: !draft().trim() || props.isSending || submitting.value,
|
|
827
1184
|
class: partClass("button", appearance(), "ckui-send-button"),
|
|
828
1185
|
style: partStyle("button", appearance())
|
|
829
|
-
}, [props.isSending || submitting.value ? (0,
|
|
1186
|
+
}, [props.isSending || submitting.value ? (0, import_vue7.h)(import_vue6.LoaderCircle, { class: "ckui-spin", size: 18, "aria-hidden": "true" }) : (0, import_vue7.h)(import_vue6.Send, { size: 18, "aria-hidden": "true" })])
|
|
830
1187
|
]);
|
|
831
1188
|
};
|
|
832
1189
|
return () => {
|
|
833
1190
|
if (props.isInitialLoading && props.messages.length === 0) {
|
|
834
|
-
return (0,
|
|
1191
|
+
return (0, import_vue7.h)("div", {
|
|
835
1192
|
...attrs,
|
|
836
1193
|
class: cx(!props.unstyled && "ckui ckui-conversation", props.classNames?.root, attrs.class),
|
|
837
1194
|
style: [props.styles?.root, attrs.style],
|
|
838
1195
|
"data-density": props.density
|
|
839
|
-
}, slots.loading?.() ?? (0,
|
|
1196
|
+
}, slots.loading?.() ?? (0, import_vue7.h)("div", {
|
|
840
1197
|
class: partClass("loading", appearance(), "ckui-state"),
|
|
841
1198
|
style: partStyle("loading", appearance()),
|
|
842
1199
|
role: "status"
|
|
843
|
-
}, [(0,
|
|
1200
|
+
}, [(0, import_vue7.h)(import_vue6.LoaderCircle, { class: "ckui-spin", "aria-hidden": "true" }), " Loading conversation\u2026"]));
|
|
844
1201
|
}
|
|
845
1202
|
const children = [renderHeader()];
|
|
846
1203
|
if (props.error) {
|
|
847
1204
|
const retry = props.onRefresh ? () => {
|
|
848
1205
|
void refresh();
|
|
849
1206
|
} : void 0;
|
|
850
|
-
children.push(slots.error?.({ error: props.error, ...retry ? { retry } : {} }) ?? (0,
|
|
1207
|
+
children.push(slots.error?.({ error: props.error, ...retry ? { retry } : {} }) ?? (0, import_vue7.h)("div", {
|
|
851
1208
|
class: partClass("error", appearance(), "ckui-conversation-error"),
|
|
852
1209
|
style: partStyle("error", appearance()),
|
|
853
1210
|
role: "alert"
|
|
854
|
-
}, [(0,
|
|
1211
|
+
}, [(0, import_vue7.h)("span", errorMessage(props.error)), retry ? (0, import_vue7.h)("button", { type: "button", class: "ckui-link-button", onClick: retry }, "Retry") : null]));
|
|
855
1212
|
}
|
|
856
1213
|
const messageSlots = {
|
|
857
1214
|
...slots.message ? { message: slots.message } : {},
|
|
@@ -861,7 +1218,7 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
861
1218
|
...slots["loading-older"] ? { "loading-older": slots["loading-older"] } : {},
|
|
862
1219
|
...slots["message-error"] ? { error: slots["message-error"] } : {}
|
|
863
1220
|
};
|
|
864
|
-
children.push((0,
|
|
1221
|
+
children.push((0, import_vue7.h)(MessageListView, {
|
|
865
1222
|
conversation: props.conversation,
|
|
866
1223
|
messages: props.messages,
|
|
867
1224
|
currentUserId: props.currentUserId,
|
|
@@ -885,7 +1242,7 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
885
1242
|
unstyled: props.unstyled
|
|
886
1243
|
}, messageSlots));
|
|
887
1244
|
children.push(renderTyping(), renderComposer());
|
|
888
|
-
return (0,
|
|
1245
|
+
return (0, import_vue7.h)("section", {
|
|
889
1246
|
...attrs,
|
|
890
1247
|
class: cx(!props.unstyled && "ckui ckui-conversation", props.classNames?.root, attrs.class),
|
|
891
1248
|
style: [props.styles?.root, attrs.style],
|
|
@@ -895,7 +1252,7 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
895
1252
|
};
|
|
896
1253
|
}
|
|
897
1254
|
});
|
|
898
|
-
var Conversation = (0,
|
|
1255
|
+
var Conversation = (0, import_vue7.defineComponent)({
|
|
899
1256
|
name: "Conversation",
|
|
900
1257
|
inheritAttrs: false,
|
|
901
1258
|
props: {
|
|
@@ -925,7 +1282,7 @@ var Conversation = (0, import_vue6.defineComponent)({
|
|
|
925
1282
|
autoLoad: props.autoLoad
|
|
926
1283
|
});
|
|
927
1284
|
expose({ controller });
|
|
928
|
-
(0,
|
|
1285
|
+
(0, import_vue7.watchEffect)(() => {
|
|
929
1286
|
emit("controller-change", controller);
|
|
930
1287
|
});
|
|
931
1288
|
return () => {
|
|
@@ -934,16 +1291,16 @@ var Conversation = (0, import_vue6.defineComponent)({
|
|
|
934
1291
|
const retry = () => {
|
|
935
1292
|
void controller.refresh();
|
|
936
1293
|
};
|
|
937
|
-
return (0,
|
|
1294
|
+
return (0, import_vue7.h)("div", {
|
|
938
1295
|
...attrs,
|
|
939
1296
|
class: cx(!props.unstyled && "ckui ckui-conversation", props.classNames?.root, attrs.class),
|
|
940
1297
|
style: [props.styles?.root, attrs.style],
|
|
941
1298
|
"data-density": props.density
|
|
942
|
-
}, controller.error.value ? slots.error?.({ error: controller.error.value, retry }) ?? (0,
|
|
943
|
-
(0,
|
|
944
|
-
(0,
|
|
945
|
-
]) : slots.loading?.() ?? (0,
|
|
946
|
-
(0,
|
|
1299
|
+
}, controller.error.value ? slots.error?.({ error: controller.error.value, retry }) ?? (0, import_vue7.h)("div", { class: "ckui-state ckui-state--error", role: "alert" }, [
|
|
1300
|
+
(0, import_vue7.h)("span", errorMessage(controller.error.value)),
|
|
1301
|
+
(0, import_vue7.h)("button", { type: "button", class: "ckui-link-button", onClick: retry }, "Try again")
|
|
1302
|
+
]) : slots.loading?.() ?? (0, import_vue7.h)("div", { class: "ckui-state", role: "status" }, [
|
|
1303
|
+
(0, import_vue7.h)(import_vue6.LoaderCircle, { class: "ckui-spin", "aria-hidden": "true" }),
|
|
947
1304
|
" Loading conversation\u2026"
|
|
948
1305
|
]));
|
|
949
1306
|
}
|
|
@@ -972,7 +1329,7 @@ var Conversation = (0, import_vue6.defineComponent)({
|
|
|
972
1329
|
error: _error,
|
|
973
1330
|
...forwarded
|
|
974
1331
|
} = props;
|
|
975
|
-
return (0,
|
|
1332
|
+
return (0, import_vue7.h)(ConversationView, {
|
|
976
1333
|
...attrs,
|
|
977
1334
|
...forwarded,
|
|
978
1335
|
conversation: loadedConversation,
|
|
@@ -1011,110 +1368,192 @@ var Conversation = (0, import_vue6.defineComponent)({
|
|
|
1011
1368
|
});
|
|
1012
1369
|
|
|
1013
1370
|
// src/components/conversation-list.ts
|
|
1014
|
-
var
|
|
1015
|
-
var
|
|
1371
|
+
var import_vue9 = require("@lucide/vue");
|
|
1372
|
+
var import_vue10 = require("vue");
|
|
1016
1373
|
|
|
1017
1374
|
// src/composables/use-conversation-list.ts
|
|
1018
|
-
var
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1375
|
+
var import_vue8 = require("vue");
|
|
1376
|
+
|
|
1377
|
+
// src/conversation-list-store.ts
|
|
1378
|
+
function blank2(filter) {
|
|
1379
|
+
return { conversations: [], filter, isInitialLoading: false, isLoadingMore: false, hasMore: true, hasLoaded: false, error: null };
|
|
1380
|
+
}
|
|
1381
|
+
var ConversationListStore = class {
|
|
1382
|
+
constructor(options) {
|
|
1383
|
+
this.options = options;
|
|
1384
|
+
this.owner = options.client.sessionIdentity;
|
|
1385
|
+
this.pageSize = options.pageSize ?? 30;
|
|
1386
|
+
if (!Number.isInteger(this.pageSize) || this.pageSize < 1 || this.pageSize > 100) {
|
|
1387
|
+
throw new RangeError("pageSize must be an integer between 1 and 100");
|
|
1388
|
+
}
|
|
1389
|
+
this.state = blank2(options.initialFilter ?? {});
|
|
1390
|
+
}
|
|
1391
|
+
options;
|
|
1392
|
+
owner;
|
|
1393
|
+
pageSize;
|
|
1394
|
+
state;
|
|
1395
|
+
source = [];
|
|
1396
|
+
offset = 0;
|
|
1397
|
+
generation = 0;
|
|
1398
|
+
lifecycleGeneration = 0;
|
|
1399
|
+
disposed = true;
|
|
1400
|
+
lifecycle;
|
|
1401
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1402
|
+
getSnapshot = () => this.state;
|
|
1403
|
+
subscribe = (listener) => {
|
|
1404
|
+
this.listeners.add(listener);
|
|
1405
|
+
return () => {
|
|
1406
|
+
this.listeners.delete(listener);
|
|
1407
|
+
};
|
|
1408
|
+
};
|
|
1409
|
+
patch(patch) {
|
|
1410
|
+
this.state = { ...this.state, ...patch };
|
|
1411
|
+
for (const listener of this.listeners) listener();
|
|
1412
|
+
}
|
|
1413
|
+
alive(generation = this.generation) {
|
|
1414
|
+
return !this.disposed && generation === this.generation && this.owner !== null && this.options.client.sessionIdentity === this.owner;
|
|
1415
|
+
}
|
|
1416
|
+
start = (autoLoad = true) => {
|
|
1417
|
+
if (!this.owner || this.options.client.sessionIdentity !== this.owner) return;
|
|
1418
|
+
this.disposed = false;
|
|
1419
|
+
const lifecycleGeneration = ++this.lifecycleGeneration;
|
|
1420
|
+
try {
|
|
1421
|
+
const subscription = this.options.client.onConnectionEvent({
|
|
1422
|
+
onEvent: () => {
|
|
1423
|
+
},
|
|
1424
|
+
onSessionEnded: () => {
|
|
1425
|
+
if (!this.disposed && lifecycleGeneration === this.lifecycleGeneration) this.dispose();
|
|
1426
|
+
}
|
|
1041
1427
|
});
|
|
1042
|
-
if (
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
if (
|
|
1428
|
+
if (this.alive()) this.lifecycle = subscription;
|
|
1429
|
+
else void subscription.unsubscribe().catch(() => void 0);
|
|
1430
|
+
if (autoLoad) void this.loadInitial();
|
|
1431
|
+
} catch (cause) {
|
|
1432
|
+
if (this.alive()) this.patch({ error: cause });
|
|
1047
1433
|
}
|
|
1048
1434
|
};
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1435
|
+
dispose = () => {
|
|
1436
|
+
this.disposed = true;
|
|
1437
|
+
this.generation++;
|
|
1438
|
+
this.lifecycleGeneration++;
|
|
1439
|
+
const subscription = this.lifecycle;
|
|
1440
|
+
this.lifecycle = void 0;
|
|
1441
|
+
if (subscription) void subscription.unsubscribe().catch(() => void 0);
|
|
1442
|
+
this.source = [];
|
|
1443
|
+
this.offset = 0;
|
|
1444
|
+
this.patch(blank2(this.state.filter));
|
|
1445
|
+
};
|
|
1446
|
+
fail(cause, generation) {
|
|
1447
|
+
if (!this.alive(generation)) return;
|
|
1448
|
+
const status = typeof cause === "object" && cause !== null && "status" in cause ? cause.status : void 0;
|
|
1449
|
+
if (status === 401 || status === 403 || status === 404) {
|
|
1450
|
+
this.source = [];
|
|
1451
|
+
this.offset = 0;
|
|
1452
|
+
this.patch({ conversations: [], hasMore: false });
|
|
1453
|
+
}
|
|
1454
|
+
this.patch({ error: cause });
|
|
1455
|
+
}
|
|
1456
|
+
async loadUntilVisible(generation, filter) {
|
|
1457
|
+
const visibleBefore = applyConversationFilter(this.source, filter).length;
|
|
1458
|
+
while (this.alive(generation)) {
|
|
1459
|
+
const request = { limit: this.pageSize, offset: this.offset, filter };
|
|
1460
|
+
const page = this.options.pageLoader ? await this.options.pageLoader(request) : await this.options.client.getConversations({ limit: this.pageSize, offset: this.offset, archived: filter.archived ?? false });
|
|
1461
|
+
if (!this.alive(generation)) return;
|
|
1462
|
+
if (page.length > this.pageSize || page.some((conversation) => !conversation.id.trim())) {
|
|
1463
|
+
throw new Error("Invalid conversation page");
|
|
1464
|
+
}
|
|
1465
|
+
const merged = mergeConversations(this.source, page);
|
|
1466
|
+
if (page.length === this.pageSize && merged.length === this.source.length) {
|
|
1467
|
+
throw new Error("Conversation pagination did not advance");
|
|
1468
|
+
}
|
|
1469
|
+
this.offset += page.length;
|
|
1470
|
+
this.source = merged;
|
|
1471
|
+
const visible = applyConversationFilter(merged, filter);
|
|
1472
|
+
const hasMore = page.length === this.pageSize;
|
|
1473
|
+
this.patch({ conversations: visible, hasMore });
|
|
1474
|
+
if (!hasMore || visible.length > visibleBefore) return;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
loadInitial = async () => {
|
|
1478
|
+
if (!this.alive()) return;
|
|
1479
|
+
const generation = ++this.generation;
|
|
1480
|
+
this.source = [];
|
|
1481
|
+
this.offset = 0;
|
|
1482
|
+
this.patch({ ...blank2(this.state.filter), isInitialLoading: true });
|
|
1057
1483
|
try {
|
|
1058
|
-
await loadUntilVisible(
|
|
1484
|
+
await this.loadUntilVisible(generation, this.state.filter);
|
|
1059
1485
|
} catch (cause) {
|
|
1060
|
-
|
|
1486
|
+
this.fail(cause, generation);
|
|
1061
1487
|
} finally {
|
|
1062
|
-
if (
|
|
1063
|
-
isInitialLoading.value = false;
|
|
1064
|
-
hasLoaded.value = true;
|
|
1065
|
-
}
|
|
1488
|
+
if (this.alive(generation)) this.patch({ isInitialLoading: false, hasLoaded: true });
|
|
1066
1489
|
}
|
|
1067
1490
|
};
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1491
|
+
refresh = () => this.loadInitial();
|
|
1492
|
+
loadMore = async () => {
|
|
1493
|
+
if (!this.alive() || this.state.isInitialLoading || this.state.isLoadingMore || !this.state.hasMore) return;
|
|
1494
|
+
const generation = this.generation;
|
|
1495
|
+
this.patch({ isLoadingMore: true, error: null });
|
|
1073
1496
|
try {
|
|
1074
|
-
await loadUntilVisible(
|
|
1497
|
+
await this.loadUntilVisible(generation, this.state.filter);
|
|
1075
1498
|
} catch (cause) {
|
|
1076
|
-
|
|
1499
|
+
this.fail(cause, generation);
|
|
1077
1500
|
} finally {
|
|
1078
|
-
if (
|
|
1501
|
+
if (this.alive(generation)) this.patch({ isLoadingMore: false });
|
|
1079
1502
|
}
|
|
1080
1503
|
};
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
filter.
|
|
1084
|
-
|
|
1085
|
-
if (
|
|
1086
|
-
|
|
1087
|
-
} else if (applyConversationFilter(source.value, nextFilter).length === 0 && hasMore.value) {
|
|
1088
|
-
await loadMore();
|
|
1089
|
-
}
|
|
1504
|
+
setFilter = async (filter) => {
|
|
1505
|
+
if (!this.alive()) return;
|
|
1506
|
+
const reload = this.options.pageLoader || !this.state.hasLoaded || this.state.isInitialLoading || this.state.isLoadingMore || (filter.archived ?? false) !== (this.state.filter.archived ?? false);
|
|
1507
|
+
this.patch({ filter, conversations: applyConversationFilter(this.source, filter), error: null });
|
|
1508
|
+
if (reload) await this.loadInitial();
|
|
1509
|
+
else if (!this.state.conversations.length && this.state.hasMore) await this.loadMore();
|
|
1090
1510
|
};
|
|
1091
|
-
|
|
1511
|
+
setQuery = (query) => this.setFilter({ ...this.state.filter, query });
|
|
1512
|
+
};
|
|
1513
|
+
|
|
1514
|
+
// src/composables/use-conversation-list.ts
|
|
1515
|
+
function useConversationList(options) {
|
|
1516
|
+
const createStore = () => new ConversationListStore({ ...options, client: (0, import_vue8.toValue)(options.client) });
|
|
1517
|
+
let store = createStore();
|
|
1518
|
+
const snapshot = (0, import_vue8.shallowRef)(store.getSnapshot());
|
|
1519
|
+
let unsubscribe;
|
|
1520
|
+
const stop = (0, import_vue8.watch)(
|
|
1521
|
+
() => [(0, import_vue8.toValue)(options.client), (0, import_vue8.toValue)(options.client).sessionIdentity],
|
|
1522
|
+
() => {
|
|
1523
|
+
store.dispose();
|
|
1524
|
+
unsubscribe?.();
|
|
1525
|
+
store = createStore();
|
|
1526
|
+
snapshot.value = store.getSnapshot();
|
|
1527
|
+
unsubscribe = store.subscribe(() => {
|
|
1528
|
+
snapshot.value = store.getSnapshot();
|
|
1529
|
+
});
|
|
1530
|
+
store.start(options.autoLoad ?? true);
|
|
1531
|
+
},
|
|
1532
|
+
{ immediate: true, flush: "sync" }
|
|
1533
|
+
);
|
|
1534
|
+
const field = (key) => (0, import_vue8.computed)(() => snapshot.value[key]);
|
|
1092
1535
|
const dispose = async () => {
|
|
1093
|
-
|
|
1094
|
-
|
|
1536
|
+
stop();
|
|
1537
|
+
store.dispose();
|
|
1538
|
+
unsubscribe?.();
|
|
1539
|
+
unsubscribe = void 0;
|
|
1095
1540
|
};
|
|
1096
|
-
if (
|
|
1097
|
-
(0, import_vue7.watch)(() => (0, import_vue7.toValue)(options.client), () => {
|
|
1098
|
-
disposed = false;
|
|
1099
|
-
void loadInitialFor(filter.value);
|
|
1100
|
-
}, { immediate: true });
|
|
1101
|
-
}
|
|
1102
|
-
if ((0, import_vue7.getCurrentScope)()) (0, import_vue7.onScopeDispose)(() => {
|
|
1541
|
+
if ((0, import_vue8.getCurrentScope)()) (0, import_vue8.onScopeDispose)(() => {
|
|
1103
1542
|
void dispose();
|
|
1104
1543
|
});
|
|
1105
1544
|
return {
|
|
1106
|
-
conversations,
|
|
1107
|
-
filter,
|
|
1108
|
-
isInitialLoading,
|
|
1109
|
-
isLoadingMore,
|
|
1110
|
-
hasMore,
|
|
1111
|
-
hasLoaded,
|
|
1112
|
-
error,
|
|
1113
|
-
loadInitial:
|
|
1114
|
-
refresh:
|
|
1115
|
-
loadMore,
|
|
1116
|
-
setFilter,
|
|
1117
|
-
setQuery,
|
|
1545
|
+
conversations: field("conversations"),
|
|
1546
|
+
filter: field("filter"),
|
|
1547
|
+
isInitialLoading: field("isInitialLoading"),
|
|
1548
|
+
isLoadingMore: field("isLoadingMore"),
|
|
1549
|
+
hasMore: field("hasMore"),
|
|
1550
|
+
hasLoaded: field("hasLoaded"),
|
|
1551
|
+
error: field("error"),
|
|
1552
|
+
loadInitial: () => store.loadInitial(),
|
|
1553
|
+
refresh: () => store.refresh(),
|
|
1554
|
+
loadMore: () => store.loadMore(),
|
|
1555
|
+
setFilter: (filter) => store.setFilter(filter),
|
|
1556
|
+
setQuery: (query) => store.setQuery(query),
|
|
1118
1557
|
dispose
|
|
1119
1558
|
};
|
|
1120
1559
|
}
|
|
@@ -1141,7 +1580,7 @@ var listViewProps = {
|
|
|
1141
1580
|
paginationThreshold: { type: Number, default: 240 },
|
|
1142
1581
|
ariaLabel: { type: String, default: "Conversations" }
|
|
1143
1582
|
};
|
|
1144
|
-
var ConversationListView = (0,
|
|
1583
|
+
var ConversationListView = (0, import_vue10.defineComponent)({
|
|
1145
1584
|
name: "ConversationListView",
|
|
1146
1585
|
inheritAttrs: false,
|
|
1147
1586
|
props: listViewProps,
|
|
@@ -1151,7 +1590,7 @@ var ConversationListView = (0, import_vue9.defineComponent)({
|
|
|
1151
1590
|
"load-more": () => true
|
|
1152
1591
|
},
|
|
1153
1592
|
setup(props, { attrs, emit, slots }) {
|
|
1154
|
-
const internalElement = (0,
|
|
1593
|
+
const internalElement = (0, import_vue10.ref)(null);
|
|
1155
1594
|
let requestInFlight = false;
|
|
1156
1595
|
let lastRequestedLength = null;
|
|
1157
1596
|
const appearance = () => ({
|
|
@@ -1181,36 +1620,36 @@ var ConversationListView = (0, import_vue9.defineComponent)({
|
|
|
1181
1620
|
const renderContent = () => {
|
|
1182
1621
|
const currentAppearance = appearance();
|
|
1183
1622
|
if (props.isInitialLoading && props.conversations.length === 0) {
|
|
1184
|
-
return slots["initial-loading"]?.() ?? (0,
|
|
1623
|
+
return slots["initial-loading"]?.() ?? (0, import_vue10.h)("div", {
|
|
1185
1624
|
class: partClass("loading", currentAppearance, "ckui-state"),
|
|
1186
1625
|
style: partStyle("loading", currentAppearance),
|
|
1187
1626
|
role: "status"
|
|
1188
|
-
}, [(0,
|
|
1627
|
+
}, [(0, import_vue10.h)(import_vue9.LoaderCircle, { class: "ckui-spin", "aria-hidden": "true" }), " Loading conversations\u2026"]);
|
|
1189
1628
|
}
|
|
1190
1629
|
if (props.error && props.conversations.length === 0) {
|
|
1191
1630
|
const retry = props.onRefresh ? () => {
|
|
1192
1631
|
void refresh();
|
|
1193
1632
|
} : void 0;
|
|
1194
|
-
return slots.error?.({ error: props.error, ...retry ? { retry } : {} }) ?? (0,
|
|
1633
|
+
return slots.error?.({ error: props.error, ...retry ? { retry } : {} }) ?? (0, import_vue10.h)("div", {
|
|
1195
1634
|
class: partClass("error", currentAppearance, "ckui-state ckui-state--error"),
|
|
1196
1635
|
style: partStyle("error", currentAppearance),
|
|
1197
1636
|
role: "alert"
|
|
1198
1637
|
}, [
|
|
1199
|
-
(0,
|
|
1200
|
-
retry ? (0,
|
|
1638
|
+
(0, import_vue10.h)("span", errorMessage(props.error)),
|
|
1639
|
+
retry ? (0, import_vue10.h)("button", { type: "button", class: "ckui-link-button", onClick: retry }, "Try again") : null
|
|
1201
1640
|
]);
|
|
1202
1641
|
}
|
|
1203
1642
|
if (props.conversations.length === 0) {
|
|
1204
|
-
return slots.empty?.() ?? (0,
|
|
1643
|
+
return slots.empty?.() ?? (0, import_vue10.h)("div", {
|
|
1205
1644
|
class: partClass("empty", currentAppearance, "ckui-state"),
|
|
1206
1645
|
style: partStyle("empty", currentAppearance)
|
|
1207
|
-
}, [(0,
|
|
1646
|
+
}, [(0, import_vue10.h)(import_vue9.Inbox, { "aria-hidden": "true" }), " No conversations yet"]);
|
|
1208
1647
|
}
|
|
1209
1648
|
const children = props.conversations.flatMap((conversation, index) => {
|
|
1210
1649
|
const selected = props.selectedConversationId === conversation.id;
|
|
1211
1650
|
const select = () => selectConversation(conversation);
|
|
1212
1651
|
const slotProps = { conversation, index, selected, select };
|
|
1213
|
-
const item = slots["conversation-item"]?.(slotProps) ?? (0,
|
|
1652
|
+
const item = slots["conversation-item"]?.(slotProps) ?? (0, import_vue10.h)("button", {
|
|
1214
1653
|
type: "button",
|
|
1215
1654
|
"data-selected": selected || void 0,
|
|
1216
1655
|
"aria-current": selected ? "true" : void 0,
|
|
@@ -1218,54 +1657,54 @@ var ConversationListView = (0, import_vue9.defineComponent)({
|
|
|
1218
1657
|
class: partClass("listItem", currentAppearance, "ckui-conversation-item"),
|
|
1219
1658
|
style: partStyle("listItem", currentAppearance)
|
|
1220
1659
|
}, [
|
|
1221
|
-
(0,
|
|
1660
|
+
(0, import_vue10.h)(ConvoKitAvatar, {
|
|
1222
1661
|
name: conversation.displayTitle,
|
|
1223
1662
|
src: conversation.imageUrl,
|
|
1224
1663
|
class: partClass("avatar", currentAppearance, ""),
|
|
1225
1664
|
style: partStyle("avatar", currentAppearance)
|
|
1226
1665
|
}),
|
|
1227
|
-
(0,
|
|
1228
|
-
(0,
|
|
1229
|
-
(0,
|
|
1666
|
+
(0, import_vue10.h)("span", { class: "ckui-conversation-item__body" }, [
|
|
1667
|
+
(0, import_vue10.h)("strong", conversation.displayTitle),
|
|
1668
|
+
(0, import_vue10.h)("span", conversation.participants.map((participant) => participant.name).join(", ") || conversation.description || "No participants")
|
|
1230
1669
|
]),
|
|
1231
|
-
(0,
|
|
1670
|
+
(0, import_vue10.h)(import_vue9.ChevronRight, { size: 18, "aria-hidden": "true" })
|
|
1232
1671
|
]);
|
|
1233
|
-
const nodes = [(0,
|
|
1672
|
+
const nodes = [(0, import_vue10.h)("div", { key: conversation.id, role: "listitem" }, [item])];
|
|
1234
1673
|
if (index < props.conversations.length - 1) {
|
|
1235
|
-
nodes.push((0,
|
|
1674
|
+
nodes.push((0, import_vue10.h)("div", { key: `${conversation.id}-separator` }, slots.separator?.({ index }) ?? (0, import_vue10.h)("div", { class: "ckui-separator" })));
|
|
1236
1675
|
}
|
|
1237
1676
|
return nodes;
|
|
1238
1677
|
});
|
|
1239
1678
|
if (props.error) {
|
|
1240
|
-
children.push(slots.error?.({ error: props.error, retry: requestMore }) ?? (0,
|
|
1679
|
+
children.push(slots.error?.({ error: props.error, retry: requestMore }) ?? (0, import_vue10.h)("div", {
|
|
1241
1680
|
class: partClass("error", currentAppearance, "ckui-inline-state ckui-state--error"),
|
|
1242
1681
|
style: partStyle("error", currentAppearance),
|
|
1243
1682
|
role: "alert"
|
|
1244
|
-
}, [(0,
|
|
1683
|
+
}, [(0, import_vue10.h)("span", errorMessage(props.error)), (0, import_vue10.h)("button", { type: "button", class: "ckui-link-button", onClick: () => {
|
|
1245
1684
|
void requestMore();
|
|
1246
1685
|
} }, "Retry")]));
|
|
1247
1686
|
} else if (props.isLoadingMore) {
|
|
1248
|
-
children.push(slots["load-more"]?.() ?? (0,
|
|
1687
|
+
children.push(slots["load-more"]?.() ?? (0, import_vue10.h)("div", {
|
|
1249
1688
|
class: partClass("loading", currentAppearance, "ckui-inline-state"),
|
|
1250
1689
|
style: partStyle("loading", currentAppearance),
|
|
1251
1690
|
role: "status"
|
|
1252
|
-
}, [(0,
|
|
1691
|
+
}, [(0, import_vue10.h)(import_vue9.LoaderCircle, { class: "ckui-spin", "aria-hidden": "true" }), " Loading more\u2026"]));
|
|
1253
1692
|
}
|
|
1254
|
-
return (0,
|
|
1693
|
+
return (0, import_vue10.h)("div", {
|
|
1255
1694
|
role: "list",
|
|
1256
1695
|
class: partClass("list", currentAppearance, "ckui-conversation-list__items"),
|
|
1257
1696
|
style: partStyle("list", currentAppearance)
|
|
1258
1697
|
}, children);
|
|
1259
1698
|
};
|
|
1260
|
-
return () => (0,
|
|
1699
|
+
return () => (0, import_vue10.h)("div", {
|
|
1261
1700
|
...attrs,
|
|
1262
1701
|
class: cx(!props.unstyled && "ckui ckui-conversation-list", props.classNames?.root, attrs.class),
|
|
1263
1702
|
style: [props.styles?.root, attrs.style],
|
|
1264
1703
|
"data-density": props.density
|
|
1265
1704
|
}, [
|
|
1266
|
-
props.onRefresh ? (0,
|
|
1267
|
-
(0,
|
|
1268
|
-
(0,
|
|
1705
|
+
props.onRefresh ? (0, import_vue10.h)("div", { class: "ckui-conversation-list__toolbar" }, [
|
|
1706
|
+
(0, import_vue10.h)("span", props.ariaLabel),
|
|
1707
|
+
(0, import_vue10.h)("button", {
|
|
1269
1708
|
type: "button",
|
|
1270
1709
|
"aria-label": "Refresh conversations",
|
|
1271
1710
|
class: partClass("button", appearance(), "ckui-icon-button"),
|
|
@@ -1273,9 +1712,9 @@ var ConversationListView = (0, import_vue9.defineComponent)({
|
|
|
1273
1712
|
onClick: () => {
|
|
1274
1713
|
void refresh();
|
|
1275
1714
|
}
|
|
1276
|
-
}, [(0,
|
|
1715
|
+
}, [(0, import_vue10.h)(import_vue9.RefreshCw, { size: 17, "aria-hidden": "true" })])
|
|
1277
1716
|
]) : null,
|
|
1278
|
-
(0,
|
|
1717
|
+
(0, import_vue10.h)("div", {
|
|
1279
1718
|
ref: (element) => {
|
|
1280
1719
|
internalElement.value = element;
|
|
1281
1720
|
if (props.scrollElement) props.scrollElement.value = element;
|
|
@@ -1293,7 +1732,7 @@ var ConversationListView = (0, import_vue9.defineComponent)({
|
|
|
1293
1732
|
]);
|
|
1294
1733
|
}
|
|
1295
1734
|
});
|
|
1296
|
-
var ConversationList = (0,
|
|
1735
|
+
var ConversationList = (0, import_vue10.defineComponent)({
|
|
1297
1736
|
name: "ConversationList",
|
|
1298
1737
|
inheritAttrs: false,
|
|
1299
1738
|
props: {
|
|
@@ -1316,7 +1755,7 @@ var ConversationList = (0, import_vue9.defineComponent)({
|
|
|
1316
1755
|
autoLoad: props.autoLoad
|
|
1317
1756
|
});
|
|
1318
1757
|
expose({ controller });
|
|
1319
|
-
(0,
|
|
1758
|
+
(0, import_vue10.watchEffect)(() => {
|
|
1320
1759
|
emit("controller-change", controller);
|
|
1321
1760
|
});
|
|
1322
1761
|
return () => {
|
|
@@ -1336,7 +1775,7 @@ var ConversationList = (0, import_vue9.defineComponent)({
|
|
|
1336
1775
|
error: _error,
|
|
1337
1776
|
...forwarded
|
|
1338
1777
|
} = props;
|
|
1339
|
-
return (0,
|
|
1778
|
+
return (0, import_vue10.h)(ConversationListView, {
|
|
1340
1779
|
...attrs,
|
|
1341
1780
|
...forwarded,
|
|
1342
1781
|
conversations: controller.conversations.value,
|
|
@@ -1355,7 +1794,7 @@ var ConversationList = (0, import_vue9.defineComponent)({
|
|
|
1355
1794
|
});
|
|
1356
1795
|
|
|
1357
1796
|
// src/theme.ts
|
|
1358
|
-
var
|
|
1797
|
+
var import_vue11 = require("vue");
|
|
1359
1798
|
var defaultConvoKitTheme = {
|
|
1360
1799
|
background: "#fafafa",
|
|
1361
1800
|
surface: "#ffffff",
|
|
@@ -1372,8 +1811,8 @@ var defaultConvoKitTheme = {
|
|
|
1372
1811
|
fontFamily: 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
1373
1812
|
};
|
|
1374
1813
|
var themeKey = /* @__PURE__ */ Symbol("ConvoKitTheme");
|
|
1375
|
-
var defaultThemeRef = (0,
|
|
1376
|
-
var ConvoKitThemeProvider = (0,
|
|
1814
|
+
var defaultThemeRef = (0, import_vue11.computed)(() => defaultConvoKitTheme);
|
|
1815
|
+
var ConvoKitThemeProvider = (0, import_vue11.defineComponent)({
|
|
1377
1816
|
name: "ConvoKitThemeProvider",
|
|
1378
1817
|
inheritAttrs: false,
|
|
1379
1818
|
props: {
|
|
@@ -1382,9 +1821,9 @@ var ConvoKitThemeProvider = (0, import_vue10.defineComponent)({
|
|
|
1382
1821
|
style: { type: [String, Array, Object], default: void 0 }
|
|
1383
1822
|
},
|
|
1384
1823
|
setup(props, { attrs, slots }) {
|
|
1385
|
-
const parent = (0,
|
|
1386
|
-
const value = (0,
|
|
1387
|
-
(0,
|
|
1824
|
+
const parent = (0, import_vue11.inject)(themeKey, defaultThemeRef);
|
|
1825
|
+
const value = (0, import_vue11.computed)(() => ({ ...parent.value, ...props.theme }));
|
|
1826
|
+
(0, import_vue11.provide)(themeKey, value);
|
|
1388
1827
|
return () => {
|
|
1389
1828
|
const theme = value.value;
|
|
1390
1829
|
const variables = {
|
|
@@ -1402,7 +1841,7 @@ var ConvoKitThemeProvider = (0, import_vue10.defineComponent)({
|
|
|
1402
1841
|
"--ckui-avatar-size": theme.avatarSize,
|
|
1403
1842
|
"--ckui-font": theme.fontFamily
|
|
1404
1843
|
};
|
|
1405
|
-
return (0,
|
|
1844
|
+
return (0, import_vue11.h)("div", {
|
|
1406
1845
|
...attrs,
|
|
1407
1846
|
class: cx("ckui-theme", props.class, attrs.class),
|
|
1408
1847
|
style: [variables, props.style, attrs.style]
|
|
@@ -1411,7 +1850,7 @@ var ConvoKitThemeProvider = (0, import_vue10.defineComponent)({
|
|
|
1411
1850
|
}
|
|
1412
1851
|
});
|
|
1413
1852
|
function useConvoKitTheme() {
|
|
1414
|
-
return (0,
|
|
1853
|
+
return (0, import_vue11.inject)(themeKey, defaultThemeRef);
|
|
1415
1854
|
}
|
|
1416
1855
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1417
1856
|
0 && (module.exports = {
|
|
@@ -1427,6 +1866,7 @@ function useConvoKitTheme() {
|
|
|
1427
1866
|
defaultConvoKitTheme,
|
|
1428
1867
|
defaultReadersResolver,
|
|
1429
1868
|
formatFileSize,
|
|
1869
|
+
isConvoKitPendingMessage,
|
|
1430
1870
|
matchesConversation,
|
|
1431
1871
|
mergeConversations,
|
|
1432
1872
|
mergeMessages,
|