@convokitapp/vue-ui 0.2.2 → 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 +19 -0
- package/PARITY.md +8 -1
- package/README.md +55 -1
- package/dist/index.cjs +827 -398
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -28
- package/dist/index.d.ts +32 -28
- package/dist/index.js +711 -298
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -46,12 +46,21 @@ module.exports = __toCommonJS(index_exports);
|
|
|
46
46
|
// src/client.ts
|
|
47
47
|
function createConvoKitUiClient(client) {
|
|
48
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
|
+
}),
|
|
49
57
|
get currentUserId() {
|
|
50
|
-
return client.currentUserId;
|
|
58
|
+
return client.connected ? client.currentUserId : "";
|
|
51
59
|
},
|
|
52
60
|
getConversations: (options) => client.getConversations(options),
|
|
53
61
|
getConversation: (conversationId) => client.getConversation(conversationId),
|
|
54
62
|
getMessages: (options) => client.getMessages(options),
|
|
63
|
+
getMessage: (id) => client.getMessage(id),
|
|
55
64
|
sendMessage: (input) => client.sendMessage(input),
|
|
56
65
|
markConversationRead: (conversationId) => client.markConversationRead(conversationId),
|
|
57
66
|
sendTyping: (input) => client.sendTyping(input),
|
|
@@ -72,16 +81,17 @@ function createConvoKitUiClient(client) {
|
|
|
72
81
|
|
|
73
82
|
// src/components/avatar.ts
|
|
74
83
|
var import_reka_ui = require("reka-ui");
|
|
75
|
-
var
|
|
84
|
+
var import_vue2 = require("vue");
|
|
76
85
|
|
|
77
86
|
// src/utils.ts
|
|
78
87
|
var import_clsx = require("clsx");
|
|
88
|
+
var import_vue = require("vue");
|
|
79
89
|
var pendingMessageIdPrefix = "convokit-pending-";
|
|
80
90
|
function isConvoKitPendingMessage(message) {
|
|
81
91
|
return message.id.startsWith(pendingMessageIdPrefix);
|
|
82
92
|
}
|
|
83
93
|
function cx(...values) {
|
|
84
|
-
return (0, import_clsx.clsx)(values);
|
|
94
|
+
return (0, import_clsx.clsx)(values.map(import_vue.normalizeClass));
|
|
85
95
|
}
|
|
86
96
|
function requestedParticipantIds(filter) {
|
|
87
97
|
const values = filter.participantIds ?? [];
|
|
@@ -154,7 +164,7 @@ function initials(value) {
|
|
|
154
164
|
}
|
|
155
165
|
|
|
156
166
|
// src/components/avatar.ts
|
|
157
|
-
var ConvoKitAvatar = (0,
|
|
167
|
+
var ConvoKitAvatar = (0, import_vue2.defineComponent)({
|
|
158
168
|
name: "ConvoKitAvatar",
|
|
159
169
|
inheritAttrs: false,
|
|
160
170
|
props: {
|
|
@@ -162,13 +172,13 @@ var ConvoKitAvatar = (0, import_vue.defineComponent)({
|
|
|
162
172
|
src: { type: String, default: null }
|
|
163
173
|
},
|
|
164
174
|
setup(props, { attrs }) {
|
|
165
|
-
return () => (0,
|
|
175
|
+
return () => (0, import_vue2.h)(import_reka_ui.AvatarRoot, {
|
|
166
176
|
...attrs,
|
|
167
177
|
class: cx("ckui-avatar", attrs.class)
|
|
168
178
|
}, {
|
|
169
179
|
default: () => [
|
|
170
|
-
props.src ? (0,
|
|
171
|
-
(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, {
|
|
172
182
|
class: "ckui-avatar__fallback",
|
|
173
183
|
...props.src ? { delayMs: 300 } : {}
|
|
174
184
|
}, () => initials(props.name))
|
|
@@ -178,251 +188,588 @@ var ConvoKitAvatar = (0, import_vue.defineComponent)({
|
|
|
178
188
|
});
|
|
179
189
|
|
|
180
190
|
// src/components/conversation.ts
|
|
181
|
-
var
|
|
182
|
-
var
|
|
191
|
+
var import_vue6 = require("@lucide/vue");
|
|
192
|
+
var import_vue7 = require("vue");
|
|
183
193
|
|
|
184
194
|
// src/composables/use-conversation.ts
|
|
185
|
-
var
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
214
224
|
};
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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");
|
|
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
|
+
};
|
|
277
|
+
};
|
|
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);
|
|
224
327
|
}
|
|
328
|
+
this.disposed = true;
|
|
329
|
+
this.clear();
|
|
330
|
+
this.patch(blank());
|
|
225
331
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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);
|
|
229
347
|
};
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
client.
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}, report)
|
|
252
|
-
|
|
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
|
+
}
|
|
253
540
|
};
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
isLoadingOlder.
|
|
270
|
-
|
|
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 });
|
|
271
567
|
try {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
]
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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);
|
|
282
596
|
} catch (cause) {
|
|
283
|
-
|
|
597
|
+
this.fail(cause, generation, true);
|
|
284
598
|
} finally {
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
599
|
+
if (this.alive(generation)) {
|
|
600
|
+
this.patch({ isReconciling: false });
|
|
601
|
+
this.flushRefresh();
|
|
288
602
|
}
|
|
289
603
|
}
|
|
290
604
|
};
|
|
291
|
-
|
|
292
|
-
if (isInitialLoading.
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
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 });
|
|
298
611
|
try {
|
|
299
|
-
const page = await
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
|
303
619
|
});
|
|
304
|
-
if (disposed || activeGeneration !== generation) return;
|
|
305
|
-
messages.value = mergeMessages(messages.value, page);
|
|
306
|
-
hasOlderMessages.value = page.length === messagePageSize;
|
|
307
620
|
} catch (cause) {
|
|
308
|
-
|
|
621
|
+
this.fail(cause, generation, true);
|
|
309
622
|
} finally {
|
|
310
|
-
if (
|
|
623
|
+
if (this.alive(generation)) {
|
|
624
|
+
this.patch({ isLoadingOlder: false });
|
|
625
|
+
this.flushRefresh();
|
|
626
|
+
}
|
|
311
627
|
}
|
|
312
628
|
};
|
|
313
|
-
|
|
314
|
-
if (
|
|
315
|
-
|
|
316
|
-
void updateTyping(false);
|
|
317
|
-
}, typingTimeoutMs);
|
|
318
|
-
if (sentTyping === nextTyping) return;
|
|
319
|
-
sentTyping = nextTyping;
|
|
629
|
+
markRead = async () => {
|
|
630
|
+
if (!this.alive()) return;
|
|
631
|
+
const generation = this.generation;
|
|
320
632
|
try {
|
|
321
|
-
await
|
|
322
|
-
conversationId: (0, import_vue2.toValue)(options.conversationId),
|
|
323
|
-
isTyping: nextTyping
|
|
324
|
-
});
|
|
633
|
+
await this.client.markConversationRead(this.room);
|
|
325
634
|
} catch (cause) {
|
|
326
|
-
|
|
635
|
+
this.fail(cause, generation);
|
|
327
636
|
}
|
|
328
637
|
};
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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 = {
|
|
335
668
|
id: pendingId,
|
|
336
|
-
conversationId:
|
|
337
|
-
senderId:
|
|
338
|
-
text:
|
|
669
|
+
conversationId: this.room,
|
|
670
|
+
senderId: this.user,
|
|
671
|
+
text: normalized || null,
|
|
339
672
|
media: media ?? [],
|
|
340
673
|
createdAt: /* @__PURE__ */ new Date(),
|
|
341
674
|
updatedAt: null
|
|
342
675
|
};
|
|
343
|
-
|
|
344
|
-
messages.value = mergeMessages(messages.value, [pendingMessage]);
|
|
345
|
-
isSending.value = true;
|
|
346
|
-
error.value = null;
|
|
347
|
-
const activeGeneration = generation;
|
|
676
|
+
this.patch({ messages: mergeMessages(this.state.messages, [pending]), isSending: true, error: null });
|
|
348
677
|
try {
|
|
349
|
-
const message = await
|
|
350
|
-
conversationId:
|
|
351
|
-
...
|
|
678
|
+
const message = await this.client.sendMessage({
|
|
679
|
+
conversationId: this.room,
|
|
680
|
+
...normalized ? { text: normalized } : {},
|
|
352
681
|
...media?.length ? { media } : {}
|
|
353
682
|
});
|
|
354
|
-
|
|
355
|
-
if (!
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
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;
|
|
363
696
|
} catch (cause) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
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);
|
|
368
700
|
}
|
|
369
701
|
return null;
|
|
370
702
|
} finally {
|
|
371
|
-
if (
|
|
703
|
+
if (this.alive(generation)) {
|
|
704
|
+
this.sendRevision = void 0;
|
|
705
|
+
this.patch({ isSending: false });
|
|
706
|
+
}
|
|
372
707
|
}
|
|
373
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]);
|
|
374
737
|
const dispose = async () => {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
await (0, import_vue2.toValue)(options.client).sendTyping({
|
|
380
|
-
conversationId: (0, import_vue2.toValue)(options.conversationId),
|
|
381
|
-
isTyping: false
|
|
382
|
-
}).catch(() => void 0);
|
|
383
|
-
}
|
|
384
|
-
await unsubscribe();
|
|
738
|
+
stop();
|
|
739
|
+
store.dispose();
|
|
740
|
+
unsubscribe?.();
|
|
741
|
+
unsubscribe = void 0;
|
|
385
742
|
};
|
|
386
|
-
if (
|
|
387
|
-
(0, import_vue2.watch)(
|
|
388
|
-
() => [(0, import_vue2.toValue)(options.client), (0, import_vue2.toValue)(options.conversationId)],
|
|
389
|
-
() => {
|
|
390
|
-
disposed = false;
|
|
391
|
-
sentTyping = false;
|
|
392
|
-
void loadInitial();
|
|
393
|
-
},
|
|
394
|
-
{ immediate: true }
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
if ((0, import_vue2.getCurrentScope)()) (0, import_vue2.onScopeDispose)(() => {
|
|
743
|
+
if ((0, import_vue3.getCurrentScope)()) (0, import_vue3.onScopeDispose)(() => {
|
|
398
744
|
void dispose();
|
|
399
745
|
});
|
|
400
746
|
return {
|
|
401
|
-
conversation,
|
|
402
|
-
messages,
|
|
403
|
-
typingUserIds,
|
|
404
|
-
readAtByUserId,
|
|
405
|
-
isInitialLoading,
|
|
406
|
-
isLoadingOlder,
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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),
|
|
419
766
|
dispose
|
|
420
767
|
};
|
|
421
768
|
}
|
|
422
769
|
|
|
423
770
|
// src/components/message-list.ts
|
|
424
|
-
var
|
|
425
|
-
var
|
|
771
|
+
var import_vue4 = require("@lucide/vue");
|
|
772
|
+
var import_vue5 = require("vue");
|
|
426
773
|
var appearanceProps = {
|
|
427
774
|
classNames: { type: Object, default: void 0 },
|
|
428
775
|
styles: { type: Object, default: void 0 },
|
|
@@ -436,33 +783,33 @@ function defaultMedia(media, open, imageLoading) {
|
|
|
436
783
|
const tag = open ? "button" : "div";
|
|
437
784
|
const interactive = open ? { type: "button", onClick: open } : {};
|
|
438
785
|
if (media.type === "image") {
|
|
439
|
-
return (0,
|
|
440
|
-
media.url ? (0,
|
|
441
|
-
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
|
|
442
789
|
]);
|
|
443
790
|
}
|
|
444
791
|
if (media.type === "file") {
|
|
445
792
|
const size = formatFileSize(media.size);
|
|
446
|
-
return (0,
|
|
447
|
-
(0,
|
|
448
|
-
(0,
|
|
449
|
-
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
|
|
450
797
|
]);
|
|
451
798
|
}
|
|
452
799
|
if (media.type === "location") {
|
|
453
800
|
const label = media.name || `${media.metadata.lat}, ${media.metadata.lng}`;
|
|
454
|
-
return (0,
|
|
455
|
-
(0,
|
|
456
|
-
(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}`)])
|
|
457
804
|
]);
|
|
458
805
|
}
|
|
459
806
|
const contact = media.metadata.email || media.metadata.phone || "Contact details";
|
|
460
|
-
return (0,
|
|
461
|
-
(0,
|
|
462
|
-
(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))])
|
|
463
810
|
]);
|
|
464
811
|
}
|
|
465
|
-
var MessageListView = (0,
|
|
812
|
+
var MessageListView = (0, import_vue5.defineComponent)({
|
|
466
813
|
name: "MessageListView",
|
|
467
814
|
inheritAttrs: false,
|
|
468
815
|
props: {
|
|
@@ -486,11 +833,11 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
486
833
|
},
|
|
487
834
|
emits: ["load-older", "attachment-click"],
|
|
488
835
|
setup(props, { attrs, emit, slots }) {
|
|
489
|
-
const internalElement = (0,
|
|
836
|
+
const internalElement = (0, import_vue5.ref)(null);
|
|
490
837
|
let requestInFlight = false;
|
|
491
838
|
let lastRequestedLength = null;
|
|
492
839
|
let previousMessageCount = 0;
|
|
493
|
-
const participants = (0,
|
|
840
|
+
const participants = (0, import_vue5.computed)(() => new Map(props.conversation.participants.flatMap((participant) => [
|
|
494
841
|
[participant.id, participant],
|
|
495
842
|
[participant.appUserId, participant]
|
|
496
843
|
])));
|
|
@@ -512,7 +859,7 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
512
859
|
requestInFlight = false;
|
|
513
860
|
}
|
|
514
861
|
};
|
|
515
|
-
(0,
|
|
862
|
+
(0, import_vue5.watch)(() => [props.messages.length, props.hasOlderMessages], async ([count, hasOlder]) => {
|
|
516
863
|
const previous = previousMessageCount;
|
|
517
864
|
if (count !== previousMessageCount || !hasOlder) lastRequestedLength = null;
|
|
518
865
|
const appended = count > previous;
|
|
@@ -521,7 +868,7 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
521
868
|
if (element && props.reverse && props.stickToBottom && appended) {
|
|
522
869
|
const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
|
|
523
870
|
if (previous === 0 || distanceFromBottom < 320) {
|
|
524
|
-
await (0,
|
|
871
|
+
await (0, import_vue5.nextTick)();
|
|
525
872
|
element.scrollTop = element.scrollHeight;
|
|
526
873
|
}
|
|
527
874
|
}
|
|
@@ -533,7 +880,7 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
533
880
|
const readerIds = isPending ? /* @__PURE__ */ new Set() : props.readersResolver ? props.readersResolver(message) : readerIdsFor(message, props.readAtByUserId);
|
|
534
881
|
const slotProps = { message, chronologicalIndex: index, isCurrentUser, sender, readerIds };
|
|
535
882
|
const custom = slots.message?.(slotProps);
|
|
536
|
-
if (custom) return (0,
|
|
883
|
+
if (custom) return (0, import_vue5.h)("div", { key: message.id, role: "listitem" }, custom);
|
|
537
884
|
const currentAppearance = appearance();
|
|
538
885
|
const messagePart = isCurrentUser ? "outgoingMessage" : "incomingMessage";
|
|
539
886
|
const mediaNodes = message.media.map((media, mediaIndex) => {
|
|
@@ -541,15 +888,15 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
541
888
|
props.onAttachmentClick?.(media, message);
|
|
542
889
|
} : void 0;
|
|
543
890
|
const mediaSlotProps = { media, message, isCurrentUser, ...open ? { open } : {} };
|
|
544
|
-
return (0,
|
|
891
|
+
return (0, import_vue5.h)("div", {
|
|
545
892
|
key: media.id ?? `${media.type}-${mediaIndex}`,
|
|
546
893
|
class: partClass("media", currentAppearance, "ckui-media"),
|
|
547
894
|
style: partStyle("media", currentAppearance)
|
|
548
895
|
}, slots.media?.(mediaSlotProps) ?? [defaultMedia(media, open, props.imageLoading)]);
|
|
549
896
|
});
|
|
550
897
|
const receiptSlotProps = { message, readerIds };
|
|
551
|
-
return (0,
|
|
552
|
-
(0,
|
|
898
|
+
return (0, import_vue5.h)("div", { key: message.id, role: "listitem" }, [
|
|
899
|
+
(0, import_vue5.h)("article", {
|
|
553
900
|
class: cx(
|
|
554
901
|
!props.unstyled && "ckui-message-row",
|
|
555
902
|
isCurrentUser && !props.unstyled && "ckui-message-row--outgoing",
|
|
@@ -559,16 +906,16 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
559
906
|
style: [props.styles?.message, props.styles?.[messagePart]],
|
|
560
907
|
"data-message-id": message.id
|
|
561
908
|
}, [
|
|
562
|
-
(0,
|
|
563
|
-
!isCurrentUser ? (0,
|
|
564
|
-
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,
|
|
565
912
|
...mediaNodes,
|
|
566
|
-
(0,
|
|
913
|
+
(0, import_vue5.h)("span", { class: "ckui-message-time" }, [
|
|
567
914
|
isPending ? "Sending\u2026" : props.formatTime(message.createdAt),
|
|
568
|
-
isCurrentUser && !isPending ? readerIds.size > 0 ? (0,
|
|
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
|
|
569
916
|
])
|
|
570
917
|
]),
|
|
571
|
-
isCurrentUser && !isPending ? slots["read-receipt"]?.(receiptSlotProps) ?? (0,
|
|
918
|
+
isCurrentUser && !isPending ? slots["read-receipt"]?.(receiptSlotProps) ?? (0, import_vue5.h)("div", {
|
|
572
919
|
class: partClass("receipt", currentAppearance, "ckui-read-receipt"),
|
|
573
920
|
style: partStyle("receipt", currentAppearance)
|
|
574
921
|
}, readerIds.size > 0 ? `Read by ${readerIds.size}` : "Delivered") : null
|
|
@@ -579,34 +926,34 @@ var MessageListView = (0, import_vue4.defineComponent)({
|
|
|
579
926
|
const currentAppearance = appearance();
|
|
580
927
|
const children = [];
|
|
581
928
|
if (props.isLoadingOlder) {
|
|
582
|
-
children.push(slots["loading-older"]?.() ?? (0,
|
|
929
|
+
children.push(slots["loading-older"]?.() ?? (0, import_vue5.h)("div", {
|
|
583
930
|
class: partClass("loading", currentAppearance, "ckui-inline-state"),
|
|
584
931
|
style: partStyle("loading", currentAppearance),
|
|
585
932
|
role: "status"
|
|
586
|
-
}, [(0,
|
|
933
|
+
}, [(0, import_vue5.h)(import_vue4.LoaderCircle, { class: "ckui-spin", "aria-hidden": "true" }), " Loading older messages\u2026"]));
|
|
587
934
|
}
|
|
588
935
|
if (props.error) {
|
|
589
936
|
const retry = props.onLoadOlder ? () => {
|
|
590
937
|
void requestOlder();
|
|
591
938
|
} : void 0;
|
|
592
|
-
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", {
|
|
593
940
|
class: partClass("error", currentAppearance, "ckui-inline-state ckui-state--error"),
|
|
594
941
|
style: partStyle("error", currentAppearance),
|
|
595
942
|
role: "alert"
|
|
596
943
|
}, [
|
|
597
|
-
(0,
|
|
598
|
-
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
|
|
599
946
|
]));
|
|
600
947
|
}
|
|
601
948
|
if (props.messages.length === 0 && !props.isLoadingOlder) {
|
|
602
|
-
children.push(slots.empty?.() ?? (0,
|
|
949
|
+
children.push(slots.empty?.() ?? (0, import_vue5.h)("div", {
|
|
603
950
|
class: partClass("empty", currentAppearance, "ckui-state"),
|
|
604
951
|
style: partStyle("empty", currentAppearance)
|
|
605
|
-
}, [(0,
|
|
952
|
+
}, [(0, import_vue5.h)(import_vue4.MessageCircle, { "aria-hidden": "true" }), " No messages yet"]));
|
|
606
953
|
} else {
|
|
607
954
|
children.push(...props.messages.map(renderMessage));
|
|
608
955
|
}
|
|
609
|
-
return (0,
|
|
956
|
+
return (0, import_vue5.h)("div", {
|
|
610
957
|
...attrs,
|
|
611
958
|
ref: (element) => {
|
|
612
959
|
internalElement.value = element;
|
|
@@ -681,7 +1028,7 @@ function typingLabel(userIds, displayNameForUser) {
|
|
|
681
1028
|
if (names.length === 2) return `${names[0]} and ${names[1]} are typing\u2026`;
|
|
682
1029
|
return `${names[0]} and ${names.length - 1} others are typing\u2026`;
|
|
683
1030
|
}
|
|
684
|
-
var ConversationView = (0,
|
|
1031
|
+
var ConversationView = (0, import_vue7.defineComponent)({
|
|
685
1032
|
name: "ConversationView",
|
|
686
1033
|
inheritAttrs: false,
|
|
687
1034
|
props: viewProps,
|
|
@@ -696,8 +1043,8 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
696
1043
|
"update:modelValue"
|
|
697
1044
|
],
|
|
698
1045
|
setup(props, { attrs, emit, slots }) {
|
|
699
|
-
const internalDraft = (0,
|
|
700
|
-
const submitting = (0,
|
|
1046
|
+
const internalDraft = (0, import_vue7.ref)(props.defaultDraft);
|
|
1047
|
+
const submitting = (0, import_vue7.ref)(false);
|
|
701
1048
|
const appearance = () => ({
|
|
702
1049
|
density: props.density,
|
|
703
1050
|
unstyled: props.unstyled,
|
|
@@ -747,23 +1094,23 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
747
1094
|
...props.onBack ? { onBack: goBack } : {},
|
|
748
1095
|
...props.onRefresh ? { onRefresh: refresh } : {}
|
|
749
1096
|
};
|
|
750
|
-
return slots.header?.(slotProps) ?? (0,
|
|
1097
|
+
return slots.header?.(slotProps) ?? (0, import_vue7.h)("header", {
|
|
751
1098
|
class: partClass("header", appearance(), "ckui-conversation-header"),
|
|
752
1099
|
style: partStyle("header", appearance())
|
|
753
1100
|
}, [
|
|
754
|
-
props.onBack ? (0,
|
|
1101
|
+
props.onBack ? (0, import_vue7.h)("button", {
|
|
755
1102
|
type: "button",
|
|
756
1103
|
"aria-label": "Back",
|
|
757
1104
|
onClick: goBack,
|
|
758
1105
|
class: partClass("button", appearance(), "ckui-icon-button"),
|
|
759
1106
|
style: partStyle("button", appearance())
|
|
760
|
-
}, [(0,
|
|
761
|
-
(0,
|
|
762
|
-
(0,
|
|
763
|
-
(0,
|
|
764
|
-
(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"}`)
|
|
765
1112
|
]),
|
|
766
|
-
props.onRefresh ? (0,
|
|
1113
|
+
props.onRefresh ? (0, import_vue7.h)("button", {
|
|
767
1114
|
type: "button",
|
|
768
1115
|
"aria-label": "Refresh conversation",
|
|
769
1116
|
onClick: () => {
|
|
@@ -771,12 +1118,12 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
771
1118
|
},
|
|
772
1119
|
class: partClass("button", appearance(), "ckui-icon-button"),
|
|
773
1120
|
style: partStyle("button", appearance())
|
|
774
|
-
}, [(0,
|
|
1121
|
+
}, [(0, import_vue7.h)(import_vue6.RefreshCw, { size: 18, "aria-hidden": "true" })]) : null
|
|
775
1122
|
]);
|
|
776
1123
|
};
|
|
777
1124
|
const renderTyping = () => {
|
|
778
1125
|
const slotProps = { userIds: props.typingUserIds, displayNameForUser: nameForUser };
|
|
779
|
-
return slots["typing-indicator"]?.(slotProps) ?? (0,
|
|
1126
|
+
return slots["typing-indicator"]?.(slotProps) ?? (0, import_vue7.h)("div", {
|
|
780
1127
|
class: partClass("typing", appearance(), "ckui-typing"),
|
|
781
1128
|
style: partStyle("typing", appearance()),
|
|
782
1129
|
"aria-live": "polite"
|
|
@@ -792,7 +1139,7 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
792
1139
|
},
|
|
793
1140
|
...props.onAddAttachment ? { addAttachment } : {}
|
|
794
1141
|
};
|
|
795
|
-
return slots.composer?.(slotProps) ?? (0,
|
|
1142
|
+
return slots.composer?.(slotProps) ?? (0, import_vue7.h)("form", {
|
|
796
1143
|
class: partClass("composer", appearance(), "ckui-composer"),
|
|
797
1144
|
style: partStyle("composer", appearance()),
|
|
798
1145
|
onSubmit: (event) => {
|
|
@@ -800,14 +1147,14 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
800
1147
|
void submit();
|
|
801
1148
|
}
|
|
802
1149
|
}, [
|
|
803
|
-
props.onAddAttachment ? (0,
|
|
1150
|
+
props.onAddAttachment ? (0, import_vue7.h)("button", {
|
|
804
1151
|
type: "button",
|
|
805
1152
|
"aria-label": "Add attachment",
|
|
806
1153
|
onClick: addAttachment,
|
|
807
1154
|
class: partClass("button", appearance(), "ckui-icon-button"),
|
|
808
1155
|
style: partStyle("button", appearance())
|
|
809
|
-
}, [(0,
|
|
810
|
-
(0,
|
|
1156
|
+
}, [(0, import_vue7.h)(import_vue6.Paperclip, { size: 20, "aria-hidden": "true" })]) : null,
|
|
1157
|
+
(0, import_vue7.h)("textarea", {
|
|
811
1158
|
...props.composerProps,
|
|
812
1159
|
rows: props.composerProps?.rows ?? 1,
|
|
813
1160
|
placeholder: props.composerPlaceholder,
|
|
@@ -830,38 +1177,38 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
830
1177
|
}
|
|
831
1178
|
}
|
|
832
1179
|
}),
|
|
833
|
-
(0,
|
|
1180
|
+
(0, import_vue7.h)("button", {
|
|
834
1181
|
type: "submit",
|
|
835
1182
|
"aria-label": "Send message",
|
|
836
1183
|
disabled: !draft().trim() || props.isSending || submitting.value,
|
|
837
1184
|
class: partClass("button", appearance(), "ckui-send-button"),
|
|
838
1185
|
style: partStyle("button", appearance())
|
|
839
|
-
}, [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" })])
|
|
840
1187
|
]);
|
|
841
1188
|
};
|
|
842
1189
|
return () => {
|
|
843
1190
|
if (props.isInitialLoading && props.messages.length === 0) {
|
|
844
|
-
return (0,
|
|
1191
|
+
return (0, import_vue7.h)("div", {
|
|
845
1192
|
...attrs,
|
|
846
1193
|
class: cx(!props.unstyled && "ckui ckui-conversation", props.classNames?.root, attrs.class),
|
|
847
1194
|
style: [props.styles?.root, attrs.style],
|
|
848
1195
|
"data-density": props.density
|
|
849
|
-
}, slots.loading?.() ?? (0,
|
|
1196
|
+
}, slots.loading?.() ?? (0, import_vue7.h)("div", {
|
|
850
1197
|
class: partClass("loading", appearance(), "ckui-state"),
|
|
851
1198
|
style: partStyle("loading", appearance()),
|
|
852
1199
|
role: "status"
|
|
853
|
-
}, [(0,
|
|
1200
|
+
}, [(0, import_vue7.h)(import_vue6.LoaderCircle, { class: "ckui-spin", "aria-hidden": "true" }), " Loading conversation\u2026"]));
|
|
854
1201
|
}
|
|
855
1202
|
const children = [renderHeader()];
|
|
856
1203
|
if (props.error) {
|
|
857
1204
|
const retry = props.onRefresh ? () => {
|
|
858
1205
|
void refresh();
|
|
859
1206
|
} : void 0;
|
|
860
|
-
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", {
|
|
861
1208
|
class: partClass("error", appearance(), "ckui-conversation-error"),
|
|
862
1209
|
style: partStyle("error", appearance()),
|
|
863
1210
|
role: "alert"
|
|
864
|
-
}, [(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]));
|
|
865
1212
|
}
|
|
866
1213
|
const messageSlots = {
|
|
867
1214
|
...slots.message ? { message: slots.message } : {},
|
|
@@ -871,7 +1218,7 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
871
1218
|
...slots["loading-older"] ? { "loading-older": slots["loading-older"] } : {},
|
|
872
1219
|
...slots["message-error"] ? { error: slots["message-error"] } : {}
|
|
873
1220
|
};
|
|
874
|
-
children.push((0,
|
|
1221
|
+
children.push((0, import_vue7.h)(MessageListView, {
|
|
875
1222
|
conversation: props.conversation,
|
|
876
1223
|
messages: props.messages,
|
|
877
1224
|
currentUserId: props.currentUserId,
|
|
@@ -895,7 +1242,7 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
895
1242
|
unstyled: props.unstyled
|
|
896
1243
|
}, messageSlots));
|
|
897
1244
|
children.push(renderTyping(), renderComposer());
|
|
898
|
-
return (0,
|
|
1245
|
+
return (0, import_vue7.h)("section", {
|
|
899
1246
|
...attrs,
|
|
900
1247
|
class: cx(!props.unstyled && "ckui ckui-conversation", props.classNames?.root, attrs.class),
|
|
901
1248
|
style: [props.styles?.root, attrs.style],
|
|
@@ -905,7 +1252,7 @@ var ConversationView = (0, import_vue6.defineComponent)({
|
|
|
905
1252
|
};
|
|
906
1253
|
}
|
|
907
1254
|
});
|
|
908
|
-
var Conversation = (0,
|
|
1255
|
+
var Conversation = (0, import_vue7.defineComponent)({
|
|
909
1256
|
name: "Conversation",
|
|
910
1257
|
inheritAttrs: false,
|
|
911
1258
|
props: {
|
|
@@ -935,7 +1282,7 @@ var Conversation = (0, import_vue6.defineComponent)({
|
|
|
935
1282
|
autoLoad: props.autoLoad
|
|
936
1283
|
});
|
|
937
1284
|
expose({ controller });
|
|
938
|
-
(0,
|
|
1285
|
+
(0, import_vue7.watchEffect)(() => {
|
|
939
1286
|
emit("controller-change", controller);
|
|
940
1287
|
});
|
|
941
1288
|
return () => {
|
|
@@ -944,16 +1291,16 @@ var Conversation = (0, import_vue6.defineComponent)({
|
|
|
944
1291
|
const retry = () => {
|
|
945
1292
|
void controller.refresh();
|
|
946
1293
|
};
|
|
947
|
-
return (0,
|
|
1294
|
+
return (0, import_vue7.h)("div", {
|
|
948
1295
|
...attrs,
|
|
949
1296
|
class: cx(!props.unstyled && "ckui ckui-conversation", props.classNames?.root, attrs.class),
|
|
950
1297
|
style: [props.styles?.root, attrs.style],
|
|
951
1298
|
"data-density": props.density
|
|
952
|
-
}, controller.error.value ? slots.error?.({ error: controller.error.value, retry }) ?? (0,
|
|
953
|
-
(0,
|
|
954
|
-
(0,
|
|
955
|
-
]) : slots.loading?.() ?? (0,
|
|
956
|
-
(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" }),
|
|
957
1304
|
" Loading conversation\u2026"
|
|
958
1305
|
]));
|
|
959
1306
|
}
|
|
@@ -982,7 +1329,7 @@ var Conversation = (0, import_vue6.defineComponent)({
|
|
|
982
1329
|
error: _error,
|
|
983
1330
|
...forwarded
|
|
984
1331
|
} = props;
|
|
985
|
-
return (0,
|
|
1332
|
+
return (0, import_vue7.h)(ConversationView, {
|
|
986
1333
|
...attrs,
|
|
987
1334
|
...forwarded,
|
|
988
1335
|
conversation: loadedConversation,
|
|
@@ -1021,110 +1368,192 @@ var Conversation = (0, import_vue6.defineComponent)({
|
|
|
1021
1368
|
});
|
|
1022
1369
|
|
|
1023
1370
|
// src/components/conversation-list.ts
|
|
1024
|
-
var
|
|
1025
|
-
var
|
|
1371
|
+
var import_vue9 = require("@lucide/vue");
|
|
1372
|
+
var import_vue10 = require("vue");
|
|
1026
1373
|
|
|
1027
1374
|
// src/composables/use-conversation-list.ts
|
|
1028
|
-
var
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
+
}
|
|
1051
1427
|
});
|
|
1052
|
-
if (
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
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 });
|
|
1057
1433
|
}
|
|
1058
1434
|
};
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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 });
|
|
1067
1483
|
try {
|
|
1068
|
-
await loadUntilVisible(
|
|
1484
|
+
await this.loadUntilVisible(generation, this.state.filter);
|
|
1069
1485
|
} catch (cause) {
|
|
1070
|
-
|
|
1486
|
+
this.fail(cause, generation);
|
|
1071
1487
|
} finally {
|
|
1072
|
-
if (
|
|
1073
|
-
isInitialLoading.value = false;
|
|
1074
|
-
hasLoaded.value = true;
|
|
1075
|
-
}
|
|
1488
|
+
if (this.alive(generation)) this.patch({ isInitialLoading: false, hasLoaded: true });
|
|
1076
1489
|
}
|
|
1077
1490
|
};
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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 });
|
|
1083
1496
|
try {
|
|
1084
|
-
await loadUntilVisible(
|
|
1497
|
+
await this.loadUntilVisible(generation, this.state.filter);
|
|
1085
1498
|
} catch (cause) {
|
|
1086
|
-
|
|
1499
|
+
this.fail(cause, generation);
|
|
1087
1500
|
} finally {
|
|
1088
|
-
if (
|
|
1501
|
+
if (this.alive(generation)) this.patch({ isLoadingMore: false });
|
|
1089
1502
|
}
|
|
1090
1503
|
};
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
filter.
|
|
1094
|
-
|
|
1095
|
-
if (
|
|
1096
|
-
|
|
1097
|
-
} else if (applyConversationFilter(source.value, nextFilter).length === 0 && hasMore.value) {
|
|
1098
|
-
await loadMore();
|
|
1099
|
-
}
|
|
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();
|
|
1100
1510
|
};
|
|
1101
|
-
|
|
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]);
|
|
1102
1535
|
const dispose = async () => {
|
|
1103
|
-
|
|
1104
|
-
|
|
1536
|
+
stop();
|
|
1537
|
+
store.dispose();
|
|
1538
|
+
unsubscribe?.();
|
|
1539
|
+
unsubscribe = void 0;
|
|
1105
1540
|
};
|
|
1106
|
-
if (
|
|
1107
|
-
(0, import_vue7.watch)(() => (0, import_vue7.toValue)(options.client), () => {
|
|
1108
|
-
disposed = false;
|
|
1109
|
-
void loadInitialFor(filter.value);
|
|
1110
|
-
}, { immediate: true });
|
|
1111
|
-
}
|
|
1112
|
-
if ((0, import_vue7.getCurrentScope)()) (0, import_vue7.onScopeDispose)(() => {
|
|
1541
|
+
if ((0, import_vue8.getCurrentScope)()) (0, import_vue8.onScopeDispose)(() => {
|
|
1113
1542
|
void dispose();
|
|
1114
1543
|
});
|
|
1115
1544
|
return {
|
|
1116
|
-
conversations,
|
|
1117
|
-
filter,
|
|
1118
|
-
isInitialLoading,
|
|
1119
|
-
isLoadingMore,
|
|
1120
|
-
hasMore,
|
|
1121
|
-
hasLoaded,
|
|
1122
|
-
error,
|
|
1123
|
-
loadInitial:
|
|
1124
|
-
refresh:
|
|
1125
|
-
loadMore,
|
|
1126
|
-
setFilter,
|
|
1127
|
-
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),
|
|
1128
1557
|
dispose
|
|
1129
1558
|
};
|
|
1130
1559
|
}
|
|
@@ -1151,7 +1580,7 @@ var listViewProps = {
|
|
|
1151
1580
|
paginationThreshold: { type: Number, default: 240 },
|
|
1152
1581
|
ariaLabel: { type: String, default: "Conversations" }
|
|
1153
1582
|
};
|
|
1154
|
-
var ConversationListView = (0,
|
|
1583
|
+
var ConversationListView = (0, import_vue10.defineComponent)({
|
|
1155
1584
|
name: "ConversationListView",
|
|
1156
1585
|
inheritAttrs: false,
|
|
1157
1586
|
props: listViewProps,
|
|
@@ -1161,7 +1590,7 @@ var ConversationListView = (0, import_vue9.defineComponent)({
|
|
|
1161
1590
|
"load-more": () => true
|
|
1162
1591
|
},
|
|
1163
1592
|
setup(props, { attrs, emit, slots }) {
|
|
1164
|
-
const internalElement = (0,
|
|
1593
|
+
const internalElement = (0, import_vue10.ref)(null);
|
|
1165
1594
|
let requestInFlight = false;
|
|
1166
1595
|
let lastRequestedLength = null;
|
|
1167
1596
|
const appearance = () => ({
|
|
@@ -1191,36 +1620,36 @@ var ConversationListView = (0, import_vue9.defineComponent)({
|
|
|
1191
1620
|
const renderContent = () => {
|
|
1192
1621
|
const currentAppearance = appearance();
|
|
1193
1622
|
if (props.isInitialLoading && props.conversations.length === 0) {
|
|
1194
|
-
return slots["initial-loading"]?.() ?? (0,
|
|
1623
|
+
return slots["initial-loading"]?.() ?? (0, import_vue10.h)("div", {
|
|
1195
1624
|
class: partClass("loading", currentAppearance, "ckui-state"),
|
|
1196
1625
|
style: partStyle("loading", currentAppearance),
|
|
1197
1626
|
role: "status"
|
|
1198
|
-
}, [(0,
|
|
1627
|
+
}, [(0, import_vue10.h)(import_vue9.LoaderCircle, { class: "ckui-spin", "aria-hidden": "true" }), " Loading conversations\u2026"]);
|
|
1199
1628
|
}
|
|
1200
1629
|
if (props.error && props.conversations.length === 0) {
|
|
1201
1630
|
const retry = props.onRefresh ? () => {
|
|
1202
1631
|
void refresh();
|
|
1203
1632
|
} : void 0;
|
|
1204
|
-
return slots.error?.({ error: props.error, ...retry ? { retry } : {} }) ?? (0,
|
|
1633
|
+
return slots.error?.({ error: props.error, ...retry ? { retry } : {} }) ?? (0, import_vue10.h)("div", {
|
|
1205
1634
|
class: partClass("error", currentAppearance, "ckui-state ckui-state--error"),
|
|
1206
1635
|
style: partStyle("error", currentAppearance),
|
|
1207
1636
|
role: "alert"
|
|
1208
1637
|
}, [
|
|
1209
|
-
(0,
|
|
1210
|
-
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
|
|
1211
1640
|
]);
|
|
1212
1641
|
}
|
|
1213
1642
|
if (props.conversations.length === 0) {
|
|
1214
|
-
return slots.empty?.() ?? (0,
|
|
1643
|
+
return slots.empty?.() ?? (0, import_vue10.h)("div", {
|
|
1215
1644
|
class: partClass("empty", currentAppearance, "ckui-state"),
|
|
1216
1645
|
style: partStyle("empty", currentAppearance)
|
|
1217
|
-
}, [(0,
|
|
1646
|
+
}, [(0, import_vue10.h)(import_vue9.Inbox, { "aria-hidden": "true" }), " No conversations yet"]);
|
|
1218
1647
|
}
|
|
1219
1648
|
const children = props.conversations.flatMap((conversation, index) => {
|
|
1220
1649
|
const selected = props.selectedConversationId === conversation.id;
|
|
1221
1650
|
const select = () => selectConversation(conversation);
|
|
1222
1651
|
const slotProps = { conversation, index, selected, select };
|
|
1223
|
-
const item = slots["conversation-item"]?.(slotProps) ?? (0,
|
|
1652
|
+
const item = slots["conversation-item"]?.(slotProps) ?? (0, import_vue10.h)("button", {
|
|
1224
1653
|
type: "button",
|
|
1225
1654
|
"data-selected": selected || void 0,
|
|
1226
1655
|
"aria-current": selected ? "true" : void 0,
|
|
@@ -1228,54 +1657,54 @@ var ConversationListView = (0, import_vue9.defineComponent)({
|
|
|
1228
1657
|
class: partClass("listItem", currentAppearance, "ckui-conversation-item"),
|
|
1229
1658
|
style: partStyle("listItem", currentAppearance)
|
|
1230
1659
|
}, [
|
|
1231
|
-
(0,
|
|
1660
|
+
(0, import_vue10.h)(ConvoKitAvatar, {
|
|
1232
1661
|
name: conversation.displayTitle,
|
|
1233
1662
|
src: conversation.imageUrl,
|
|
1234
1663
|
class: partClass("avatar", currentAppearance, ""),
|
|
1235
1664
|
style: partStyle("avatar", currentAppearance)
|
|
1236
1665
|
}),
|
|
1237
|
-
(0,
|
|
1238
|
-
(0,
|
|
1239
|
-
(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")
|
|
1240
1669
|
]),
|
|
1241
|
-
(0,
|
|
1670
|
+
(0, import_vue10.h)(import_vue9.ChevronRight, { size: 18, "aria-hidden": "true" })
|
|
1242
1671
|
]);
|
|
1243
|
-
const nodes = [(0,
|
|
1672
|
+
const nodes = [(0, import_vue10.h)("div", { key: conversation.id, role: "listitem" }, [item])];
|
|
1244
1673
|
if (index < props.conversations.length - 1) {
|
|
1245
|
-
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" })));
|
|
1246
1675
|
}
|
|
1247
1676
|
return nodes;
|
|
1248
1677
|
});
|
|
1249
1678
|
if (props.error) {
|
|
1250
|
-
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", {
|
|
1251
1680
|
class: partClass("error", currentAppearance, "ckui-inline-state ckui-state--error"),
|
|
1252
1681
|
style: partStyle("error", currentAppearance),
|
|
1253
1682
|
role: "alert"
|
|
1254
|
-
}, [(0,
|
|
1683
|
+
}, [(0, import_vue10.h)("span", errorMessage(props.error)), (0, import_vue10.h)("button", { type: "button", class: "ckui-link-button", onClick: () => {
|
|
1255
1684
|
void requestMore();
|
|
1256
1685
|
} }, "Retry")]));
|
|
1257
1686
|
} else if (props.isLoadingMore) {
|
|
1258
|
-
children.push(slots["load-more"]?.() ?? (0,
|
|
1687
|
+
children.push(slots["load-more"]?.() ?? (0, import_vue10.h)("div", {
|
|
1259
1688
|
class: partClass("loading", currentAppearance, "ckui-inline-state"),
|
|
1260
1689
|
style: partStyle("loading", currentAppearance),
|
|
1261
1690
|
role: "status"
|
|
1262
|
-
}, [(0,
|
|
1691
|
+
}, [(0, import_vue10.h)(import_vue9.LoaderCircle, { class: "ckui-spin", "aria-hidden": "true" }), " Loading more\u2026"]));
|
|
1263
1692
|
}
|
|
1264
|
-
return (0,
|
|
1693
|
+
return (0, import_vue10.h)("div", {
|
|
1265
1694
|
role: "list",
|
|
1266
1695
|
class: partClass("list", currentAppearance, "ckui-conversation-list__items"),
|
|
1267
1696
|
style: partStyle("list", currentAppearance)
|
|
1268
1697
|
}, children);
|
|
1269
1698
|
};
|
|
1270
|
-
return () => (0,
|
|
1699
|
+
return () => (0, import_vue10.h)("div", {
|
|
1271
1700
|
...attrs,
|
|
1272
1701
|
class: cx(!props.unstyled && "ckui ckui-conversation-list", props.classNames?.root, attrs.class),
|
|
1273
1702
|
style: [props.styles?.root, attrs.style],
|
|
1274
1703
|
"data-density": props.density
|
|
1275
1704
|
}, [
|
|
1276
|
-
props.onRefresh ? (0,
|
|
1277
|
-
(0,
|
|
1278
|
-
(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", {
|
|
1279
1708
|
type: "button",
|
|
1280
1709
|
"aria-label": "Refresh conversations",
|
|
1281
1710
|
class: partClass("button", appearance(), "ckui-icon-button"),
|
|
@@ -1283,9 +1712,9 @@ var ConversationListView = (0, import_vue9.defineComponent)({
|
|
|
1283
1712
|
onClick: () => {
|
|
1284
1713
|
void refresh();
|
|
1285
1714
|
}
|
|
1286
|
-
}, [(0,
|
|
1715
|
+
}, [(0, import_vue10.h)(import_vue9.RefreshCw, { size: 17, "aria-hidden": "true" })])
|
|
1287
1716
|
]) : null,
|
|
1288
|
-
(0,
|
|
1717
|
+
(0, import_vue10.h)("div", {
|
|
1289
1718
|
ref: (element) => {
|
|
1290
1719
|
internalElement.value = element;
|
|
1291
1720
|
if (props.scrollElement) props.scrollElement.value = element;
|
|
@@ -1303,7 +1732,7 @@ var ConversationListView = (0, import_vue9.defineComponent)({
|
|
|
1303
1732
|
]);
|
|
1304
1733
|
}
|
|
1305
1734
|
});
|
|
1306
|
-
var ConversationList = (0,
|
|
1735
|
+
var ConversationList = (0, import_vue10.defineComponent)({
|
|
1307
1736
|
name: "ConversationList",
|
|
1308
1737
|
inheritAttrs: false,
|
|
1309
1738
|
props: {
|
|
@@ -1326,7 +1755,7 @@ var ConversationList = (0, import_vue9.defineComponent)({
|
|
|
1326
1755
|
autoLoad: props.autoLoad
|
|
1327
1756
|
});
|
|
1328
1757
|
expose({ controller });
|
|
1329
|
-
(0,
|
|
1758
|
+
(0, import_vue10.watchEffect)(() => {
|
|
1330
1759
|
emit("controller-change", controller);
|
|
1331
1760
|
});
|
|
1332
1761
|
return () => {
|
|
@@ -1346,7 +1775,7 @@ var ConversationList = (0, import_vue9.defineComponent)({
|
|
|
1346
1775
|
error: _error,
|
|
1347
1776
|
...forwarded
|
|
1348
1777
|
} = props;
|
|
1349
|
-
return (0,
|
|
1778
|
+
return (0, import_vue10.h)(ConversationListView, {
|
|
1350
1779
|
...attrs,
|
|
1351
1780
|
...forwarded,
|
|
1352
1781
|
conversations: controller.conversations.value,
|
|
@@ -1365,7 +1794,7 @@ var ConversationList = (0, import_vue9.defineComponent)({
|
|
|
1365
1794
|
});
|
|
1366
1795
|
|
|
1367
1796
|
// src/theme.ts
|
|
1368
|
-
var
|
|
1797
|
+
var import_vue11 = require("vue");
|
|
1369
1798
|
var defaultConvoKitTheme = {
|
|
1370
1799
|
background: "#fafafa",
|
|
1371
1800
|
surface: "#ffffff",
|
|
@@ -1382,8 +1811,8 @@ var defaultConvoKitTheme = {
|
|
|
1382
1811
|
fontFamily: 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
1383
1812
|
};
|
|
1384
1813
|
var themeKey = /* @__PURE__ */ Symbol("ConvoKitTheme");
|
|
1385
|
-
var defaultThemeRef = (0,
|
|
1386
|
-
var ConvoKitThemeProvider = (0,
|
|
1814
|
+
var defaultThemeRef = (0, import_vue11.computed)(() => defaultConvoKitTheme);
|
|
1815
|
+
var ConvoKitThemeProvider = (0, import_vue11.defineComponent)({
|
|
1387
1816
|
name: "ConvoKitThemeProvider",
|
|
1388
1817
|
inheritAttrs: false,
|
|
1389
1818
|
props: {
|
|
@@ -1392,9 +1821,9 @@ var ConvoKitThemeProvider = (0, import_vue10.defineComponent)({
|
|
|
1392
1821
|
style: { type: [String, Array, Object], default: void 0 }
|
|
1393
1822
|
},
|
|
1394
1823
|
setup(props, { attrs, slots }) {
|
|
1395
|
-
const parent = (0,
|
|
1396
|
-
const value = (0,
|
|
1397
|
-
(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);
|
|
1398
1827
|
return () => {
|
|
1399
1828
|
const theme = value.value;
|
|
1400
1829
|
const variables = {
|
|
@@ -1412,7 +1841,7 @@ var ConvoKitThemeProvider = (0, import_vue10.defineComponent)({
|
|
|
1412
1841
|
"--ckui-avatar-size": theme.avatarSize,
|
|
1413
1842
|
"--ckui-font": theme.fontFamily
|
|
1414
1843
|
};
|
|
1415
|
-
return (0,
|
|
1844
|
+
return (0, import_vue11.h)("div", {
|
|
1416
1845
|
...attrs,
|
|
1417
1846
|
class: cx("ckui-theme", props.class, attrs.class),
|
|
1418
1847
|
style: [variables, props.style, attrs.style]
|
|
@@ -1421,7 +1850,7 @@ var ConvoKitThemeProvider = (0, import_vue10.defineComponent)({
|
|
|
1421
1850
|
}
|
|
1422
1851
|
});
|
|
1423
1852
|
function useConvoKitTheme() {
|
|
1424
|
-
return (0,
|
|
1853
|
+
return (0, import_vue11.inject)(themeKey, defaultThemeRef);
|
|
1425
1854
|
}
|
|
1426
1855
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1427
1856
|
0 && (module.exports = {
|