@liveblocks/chat-sdk-adapter 0.0.0 → 3.16.0-feeds2
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/README.md +57 -0
- package/dist/index.cjs +972 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +67 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +972 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -2
package/dist/index.js
ADDED
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
// src/adapter.ts
|
|
2
|
+
import {
|
|
3
|
+
getMentionsFromCommentBody,
|
|
4
|
+
isCommentBodyLink,
|
|
5
|
+
isCommentBodyMention,
|
|
6
|
+
isCommentBodyText
|
|
7
|
+
} from "@liveblocks/core";
|
|
8
|
+
import {
|
|
9
|
+
Liveblocks,
|
|
10
|
+
LiveblocksError,
|
|
11
|
+
WebhookHandler
|
|
12
|
+
} from "@liveblocks/node";
|
|
13
|
+
import {
|
|
14
|
+
ConsoleLogger,
|
|
15
|
+
defaultEmojiResolver,
|
|
16
|
+
Message,
|
|
17
|
+
parseMarkdown,
|
|
18
|
+
tableToAscii,
|
|
19
|
+
toPlainText
|
|
20
|
+
} from "chat";
|
|
21
|
+
var ADAPTER_PREFIX = "liveblocks";
|
|
22
|
+
var LiveblocksAdapter = class {
|
|
23
|
+
name = "liveblocks";
|
|
24
|
+
userName;
|
|
25
|
+
#client;
|
|
26
|
+
#webhookHandler;
|
|
27
|
+
#resolveUsers;
|
|
28
|
+
#resolveGroupsInfo;
|
|
29
|
+
#logger;
|
|
30
|
+
#botUserId;
|
|
31
|
+
#chat = null;
|
|
32
|
+
constructor(config) {
|
|
33
|
+
this.#client = new Liveblocks({ secret: config.apiKey });
|
|
34
|
+
this.#webhookHandler = new WebhookHandler(config.webhookSecret);
|
|
35
|
+
this.#resolveUsers = config.resolveUsers;
|
|
36
|
+
this.#resolveGroupsInfo = config.resolveGroupsInfo;
|
|
37
|
+
this.#botUserId = config.botUserId;
|
|
38
|
+
this.userName = config.botUserName ?? "liveblocks-bot";
|
|
39
|
+
this.#logger = config.logger ?? new ConsoleLogger("info").child(ADAPTER_PREFIX);
|
|
40
|
+
}
|
|
41
|
+
async initialize(chat) {
|
|
42
|
+
this.#chat = chat;
|
|
43
|
+
}
|
|
44
|
+
async handleWebhook(request, options) {
|
|
45
|
+
let event;
|
|
46
|
+
try {
|
|
47
|
+
event = this.#webhookHandler.verifyRequest({
|
|
48
|
+
headers: request.headers,
|
|
49
|
+
rawBody: await request.text()
|
|
50
|
+
});
|
|
51
|
+
} catch (error) {
|
|
52
|
+
this.#logger.error("Failed to verify webhook request", { error });
|
|
53
|
+
return new Response("Invalid webhook request", { status: 401 });
|
|
54
|
+
}
|
|
55
|
+
if (event.type === "commentCreated") {
|
|
56
|
+
const threadId = this.encodeThreadId({
|
|
57
|
+
roomId: event.data.roomId,
|
|
58
|
+
threadId: event.data.threadId
|
|
59
|
+
});
|
|
60
|
+
const comment = await this.#client.getComment({
|
|
61
|
+
roomId: event.data.roomId,
|
|
62
|
+
threadId: event.data.threadId,
|
|
63
|
+
commentId: event.data.commentId
|
|
64
|
+
});
|
|
65
|
+
if (comment.deletedAt !== void 0) {
|
|
66
|
+
return new Response(null, { status: 200 });
|
|
67
|
+
}
|
|
68
|
+
this.#chat?.processMessage(
|
|
69
|
+
this,
|
|
70
|
+
threadId,
|
|
71
|
+
() => this.#convertLiveblocksCommentDataToChatMessage(comment),
|
|
72
|
+
options
|
|
73
|
+
);
|
|
74
|
+
} else if (event.type === "commentReactionAdded" || event.type === "commentReactionRemoved") {
|
|
75
|
+
const threadId = this.encodeThreadId({
|
|
76
|
+
roomId: event.data.roomId,
|
|
77
|
+
threadId: event.data.threadId
|
|
78
|
+
});
|
|
79
|
+
const userId = event.type === "commentReactionAdded" ? event.data.addedBy : event.data.removedBy;
|
|
80
|
+
const resolvedUsers = await this.#resolveUsers?.({ userIds: [userId] });
|
|
81
|
+
const user = resolvedUsers?.[0];
|
|
82
|
+
this.#chat?.processReaction(
|
|
83
|
+
{
|
|
84
|
+
added: event.type === "commentReactionAdded",
|
|
85
|
+
emoji: defaultEmojiResolver.fromGChat(event.data.emoji),
|
|
86
|
+
rawEmoji: event.data.emoji,
|
|
87
|
+
messageId: event.data.commentId,
|
|
88
|
+
threadId,
|
|
89
|
+
user: {
|
|
90
|
+
userId,
|
|
91
|
+
userName: user?.name ?? userId,
|
|
92
|
+
fullName: user?.name ?? userId,
|
|
93
|
+
// This assumes that the current bot is the only bot in the thread; if we want
|
|
94
|
+
// to support multiple bots, we need to add a way to determine the bot's user id.
|
|
95
|
+
isBot: userId === this.#botUserId,
|
|
96
|
+
isMe: userId === this.#botUserId
|
|
97
|
+
},
|
|
98
|
+
raw: event.data,
|
|
99
|
+
adapter: this
|
|
100
|
+
},
|
|
101
|
+
options
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return new Response(null, { status: 200 });
|
|
105
|
+
}
|
|
106
|
+
async postMessage(threadId, message) {
|
|
107
|
+
const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
|
|
108
|
+
const comment = await this.#client.createComment({
|
|
109
|
+
roomId,
|
|
110
|
+
threadId: threadId_liveblocks,
|
|
111
|
+
data: {
|
|
112
|
+
userId: this.#botUserId,
|
|
113
|
+
body: convertPostableMessageToCommentBody(message)
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return { id: comment.id, threadId, raw: comment };
|
|
117
|
+
}
|
|
118
|
+
async editMessage(threadId, messageId, message) {
|
|
119
|
+
const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
|
|
120
|
+
const comment = await this.#client.editComment({
|
|
121
|
+
roomId,
|
|
122
|
+
threadId: threadId_liveblocks,
|
|
123
|
+
commentId: messageId,
|
|
124
|
+
data: {
|
|
125
|
+
body: convertPostableMessageToCommentBody(message)
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return { id: comment.id, threadId, raw: comment };
|
|
129
|
+
}
|
|
130
|
+
async deleteMessage(threadId, messageId) {
|
|
131
|
+
const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
|
|
132
|
+
await this.#client.deleteComment({
|
|
133
|
+
roomId,
|
|
134
|
+
threadId: threadId_liveblocks,
|
|
135
|
+
commentId: messageId
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async addReaction(threadId, messageId, emoji) {
|
|
139
|
+
const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
|
|
140
|
+
await this.#client.addCommentReaction({
|
|
141
|
+
roomId,
|
|
142
|
+
threadId: threadId_liveblocks,
|
|
143
|
+
commentId: messageId,
|
|
144
|
+
data: {
|
|
145
|
+
// Liveblocks expects unicode emoji; 'toGChat' converts normalized names (e.g. 'thumbs_up') to unicode ('👍').
|
|
146
|
+
// Unknown normalized names (e.g. 'custom_emoji') will fail Liveblocks validation since they are not valid unicode emoji.
|
|
147
|
+
emoji: defaultEmojiResolver.toGChat(emoji),
|
|
148
|
+
userId: this.#botUserId
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async removeReaction(threadId, messageId, emoji) {
|
|
153
|
+
const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
|
|
154
|
+
await this.#client.removeCommentReaction({
|
|
155
|
+
roomId,
|
|
156
|
+
threadId: threadId_liveblocks,
|
|
157
|
+
commentId: messageId,
|
|
158
|
+
data: {
|
|
159
|
+
emoji: defaultEmojiResolver.toGChat(emoji),
|
|
160
|
+
userId: this.#botUserId
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async fetchMessages(threadId, options) {
|
|
165
|
+
const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
|
|
166
|
+
const thread = await this.#client.getThread({
|
|
167
|
+
roomId,
|
|
168
|
+
threadId: threadId_liveblocks
|
|
169
|
+
});
|
|
170
|
+
const comments = thread.comments.filter(
|
|
171
|
+
(comment) => comment.deletedAt === void 0
|
|
172
|
+
);
|
|
173
|
+
const direction = options?.direction ?? "backward";
|
|
174
|
+
const limit = options?.limit;
|
|
175
|
+
const startingAfter = options?.cursor;
|
|
176
|
+
const sliced = slicePageByCreatedAt(comments, {
|
|
177
|
+
direction: direction === "forward" ? "ascending" : "descending",
|
|
178
|
+
limit,
|
|
179
|
+
startingAfter
|
|
180
|
+
});
|
|
181
|
+
const messages = await Promise.all(
|
|
182
|
+
sliced.data.map(
|
|
183
|
+
(comment) => this.#convertLiveblocksCommentDataToChatMessage(comment)
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
return { messages, nextCursor: sliced.nextCursor };
|
|
187
|
+
}
|
|
188
|
+
async fetchThread(threadId) {
|
|
189
|
+
const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
|
|
190
|
+
const thread = await this.#client.getThread({
|
|
191
|
+
roomId,
|
|
192
|
+
threadId: threadId_liveblocks
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
id: threadId,
|
|
196
|
+
channelId: `${ADAPTER_PREFIX}:${roomId}`,
|
|
197
|
+
metadata: {
|
|
198
|
+
resolved: thread.resolved,
|
|
199
|
+
...thread.metadata
|
|
200
|
+
},
|
|
201
|
+
channelName: thread.roomId,
|
|
202
|
+
isDM: false
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async fetchMessage(threadId, messageId) {
|
|
206
|
+
try {
|
|
207
|
+
const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
|
|
208
|
+
const comment = await this.#client.getComment({
|
|
209
|
+
roomId,
|
|
210
|
+
threadId: threadId_liveblocks,
|
|
211
|
+
commentId: messageId
|
|
212
|
+
});
|
|
213
|
+
if (comment.deletedAt !== void 0) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
return this.#convertLiveblocksCommentDataToChatMessage(comment);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (error instanceof LiveblocksError && error.status === 404) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async listThreads(channelId, options) {
|
|
225
|
+
const roomId = getRoomIdFromChannelId(channelId);
|
|
226
|
+
const { data } = await this.#client.getThreads({ roomId });
|
|
227
|
+
const threads = data.map((thread) => {
|
|
228
|
+
const nonDeletedComments = thread.comments.filter((comment) => comment.deletedAt === void 0).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
229
|
+
const firstNonDeletedComment = nonDeletedComments[0];
|
|
230
|
+
if (firstNonDeletedComment === void 0) return null;
|
|
231
|
+
return {
|
|
232
|
+
id: this.encodeThreadId({
|
|
233
|
+
roomId,
|
|
234
|
+
threadId: thread.id
|
|
235
|
+
}),
|
|
236
|
+
updatedAt: thread.updatedAt,
|
|
237
|
+
numOfComments: nonDeletedComments.length,
|
|
238
|
+
firstComment: firstNonDeletedComment
|
|
239
|
+
};
|
|
240
|
+
}).filter((thread) => thread !== null);
|
|
241
|
+
const limit = options?.limit;
|
|
242
|
+
const startingAfter = options?.cursor;
|
|
243
|
+
const sliced = slicePageByUpdatedAt(threads, {
|
|
244
|
+
limit,
|
|
245
|
+
startingAfter
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
threads: await Promise.all(
|
|
249
|
+
sliced.data.map(async (thread) => ({
|
|
250
|
+
id: thread.id,
|
|
251
|
+
rootMessage: await this.#convertLiveblocksCommentDataToChatMessage(
|
|
252
|
+
thread.firstComment
|
|
253
|
+
),
|
|
254
|
+
lastReplyAt: thread.updatedAt,
|
|
255
|
+
replyCount: thread.numOfComments - 1
|
|
256
|
+
}))
|
|
257
|
+
),
|
|
258
|
+
nextCursor: sliced.nextCursor
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async fetchChannelInfo(channelId) {
|
|
262
|
+
const room = await this.#client.getRoom(getRoomIdFromChannelId(channelId));
|
|
263
|
+
return {
|
|
264
|
+
id: room.id,
|
|
265
|
+
name: room.id,
|
|
266
|
+
isDM: false,
|
|
267
|
+
metadata: {}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
async fetchChannelMessages(channelId, options) {
|
|
271
|
+
const roomId = getRoomIdFromChannelId(channelId);
|
|
272
|
+
const { data } = await this.#client.getThreads({ roomId });
|
|
273
|
+
const comments = data.map((thread) => {
|
|
274
|
+
const nonDeletedComments = thread.comments.filter((comment) => comment.deletedAt === void 0).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
275
|
+
const firstNonDeletedComment = nonDeletedComments[0];
|
|
276
|
+
if (firstNonDeletedComment !== void 0) {
|
|
277
|
+
return firstNonDeletedComment;
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}).filter((comment) => comment !== null);
|
|
281
|
+
const direction = options?.direction ?? "backward";
|
|
282
|
+
const limit = options?.limit;
|
|
283
|
+
const startingAfter = options?.cursor;
|
|
284
|
+
const sliced = slicePageByCreatedAt(comments, {
|
|
285
|
+
direction: direction === "forward" ? "ascending" : "descending",
|
|
286
|
+
limit,
|
|
287
|
+
startingAfter
|
|
288
|
+
});
|
|
289
|
+
const messages = await Promise.all(
|
|
290
|
+
sliced.data.map(
|
|
291
|
+
(comment) => this.#convertLiveblocksCommentDataToChatMessage(comment)
|
|
292
|
+
)
|
|
293
|
+
);
|
|
294
|
+
return { messages, nextCursor: sliced.nextCursor };
|
|
295
|
+
}
|
|
296
|
+
async postChannelMessage(channelId, message) {
|
|
297
|
+
const roomId = getRoomIdFromChannelId(channelId);
|
|
298
|
+
const thread = await this.#client.createThread({
|
|
299
|
+
roomId,
|
|
300
|
+
data: {
|
|
301
|
+
comment: {
|
|
302
|
+
userId: this.#botUserId,
|
|
303
|
+
body: convertPostableMessageToCommentBody(message)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
const firstComment = thread.comments[0];
|
|
308
|
+
if (firstComment === void 0) {
|
|
309
|
+
throw new Error(`Failed to create thread in room ${channelId}`);
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
id: firstComment.id,
|
|
313
|
+
threadId: this.encodeThreadId({
|
|
314
|
+
roomId,
|
|
315
|
+
threadId: thread.id
|
|
316
|
+
}),
|
|
317
|
+
raw: firstComment
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// This method isn't used by the Chat SDK, but it's required to implement the Adapter interface,
|
|
321
|
+
// so we will return a less rich message here. We have a separate (asynchronous) method for converting a comment to a message.
|
|
322
|
+
parseMessage(data) {
|
|
323
|
+
return new Message({
|
|
324
|
+
id: data.id,
|
|
325
|
+
threadId: this.encodeThreadId({
|
|
326
|
+
roomId: data.roomId,
|
|
327
|
+
threadId: data.threadId
|
|
328
|
+
}),
|
|
329
|
+
raw: data,
|
|
330
|
+
formatted: { type: "root", children: [] },
|
|
331
|
+
text: "",
|
|
332
|
+
author: {
|
|
333
|
+
userId: data.userId,
|
|
334
|
+
userName: data.userId,
|
|
335
|
+
fullName: data.userId,
|
|
336
|
+
isBot: data.userId === this.#botUserId,
|
|
337
|
+
isMe: data.userId === this.#botUserId
|
|
338
|
+
},
|
|
339
|
+
metadata: {
|
|
340
|
+
dateSent: data.createdAt,
|
|
341
|
+
edited: !!data.editedAt,
|
|
342
|
+
editedAt: data.editedAt
|
|
343
|
+
},
|
|
344
|
+
attachments: data.attachments.map(
|
|
345
|
+
(att) => this.#createAttachment(data.roomId, att)
|
|
346
|
+
)
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
renderFormatted(content) {
|
|
350
|
+
return toPlainText(content);
|
|
351
|
+
}
|
|
352
|
+
channelIdFromThreadId(threadId) {
|
|
353
|
+
const { roomId } = this.decodeThreadId(threadId);
|
|
354
|
+
return `${ADAPTER_PREFIX}:${roomId}`;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* This method is a no-op as typing indicators are not supported by Liveblocks Comments.
|
|
358
|
+
*/
|
|
359
|
+
startTyping(_threadId, _status) {
|
|
360
|
+
return Promise.resolve();
|
|
361
|
+
}
|
|
362
|
+
#createAttachment(roomId, attachment) {
|
|
363
|
+
const client = this.#client;
|
|
364
|
+
return {
|
|
365
|
+
type: getAttachmentType(attachment.mimeType),
|
|
366
|
+
name: attachment.name,
|
|
367
|
+
mimeType: attachment.mimeType,
|
|
368
|
+
size: attachment.size,
|
|
369
|
+
fetchData: async () => {
|
|
370
|
+
const { url } = await client.getAttachment({
|
|
371
|
+
roomId,
|
|
372
|
+
attachmentId: attachment.id
|
|
373
|
+
});
|
|
374
|
+
const response = await fetch(url);
|
|
375
|
+
if (!response.ok) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`Failed to fetch attachment "${attachment.name}": ${response.status} ${response.statusText}`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
return Buffer.from(await response.arrayBuffer());
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Encodes a Liveblocks room ID and thread ID into a single thread ID string.
|
|
386
|
+
*
|
|
387
|
+
* Format: `liveblocks:{roomId}:{threadId}`
|
|
388
|
+
*
|
|
389
|
+
* **Note**: Room IDs may contain colons (':'), which are preserved during encoding/decoding.
|
|
390
|
+
* However, Liveblocks thread IDs must not contain colons as the last colon is used as the delimiter when decoding.
|
|
391
|
+
*/
|
|
392
|
+
encodeThreadId(data) {
|
|
393
|
+
return `${ADAPTER_PREFIX}:${data.roomId}:${data.threadId}`;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Decodes an encoded thread ID string back into its room ID and thread ID components.
|
|
397
|
+
*
|
|
398
|
+
* @throws {Error} If the thread ID format is invalid
|
|
399
|
+
*/
|
|
400
|
+
decodeThreadId(threadId) {
|
|
401
|
+
const parts = threadId.split(":");
|
|
402
|
+
if (parts.length < 3 || parts[0] !== ADAPTER_PREFIX) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`Invalid thread ID: ${threadId}. Expected format: liveblocks:{roomId}:{threadId}`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
roomId: parts.slice(1, -1).join(":"),
|
|
409
|
+
threadId: parts[parts.length - 1]
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
async #convertLiveblocksCommentDataToChatMessage(comment) {
|
|
413
|
+
const mentions = getMentionsFromCommentBody(comment.body);
|
|
414
|
+
const userIds = /* @__PURE__ */ new Set([comment.userId]);
|
|
415
|
+
const groupIds = /* @__PURE__ */ new Set();
|
|
416
|
+
for (const mention of mentions) {
|
|
417
|
+
if (mention.kind === "user") {
|
|
418
|
+
userIds.add(mention.id);
|
|
419
|
+
} else if (mention.kind === "group") {
|
|
420
|
+
groupIds.add(mention.id);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const [users, groups] = await Promise.all([
|
|
424
|
+
this.#resolveUsers ? this.#resolveUsers({ userIds: Array.from(userIds) }) : void 0,
|
|
425
|
+
this.#resolveGroupsInfo && groupIds.size > 0 ? this.#resolveGroupsInfo({ groupIds: Array.from(groupIds) }) : void 0
|
|
426
|
+
]);
|
|
427
|
+
const resolvedUsers = /* @__PURE__ */ new Map();
|
|
428
|
+
if (users !== void 0) {
|
|
429
|
+
for (const [index, userId] of Array.from(userIds).entries()) {
|
|
430
|
+
const user = users[index];
|
|
431
|
+
if (user === void 0) continue;
|
|
432
|
+
resolvedUsers.set(userId, user);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const resolvedGroups = /* @__PURE__ */ new Map();
|
|
436
|
+
if (groups !== void 0) {
|
|
437
|
+
for (const [index, groupId] of Array.from(groupIds).entries()) {
|
|
438
|
+
const group = groups[index];
|
|
439
|
+
if (group === void 0) continue;
|
|
440
|
+
resolvedGroups.set(groupId, group);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const links = /* @__PURE__ */ new Set();
|
|
444
|
+
const nodes = comment.body.content.map((block) => {
|
|
445
|
+
const children = [];
|
|
446
|
+
for (const inline of block.children) {
|
|
447
|
+
if (isCommentBodyMention(inline)) {
|
|
448
|
+
if (inline.kind === "user") {
|
|
449
|
+
children.push({
|
|
450
|
+
type: "text",
|
|
451
|
+
value: resolvedUsers.get(inline.id)?.name ?? inline.id
|
|
452
|
+
});
|
|
453
|
+
} else {
|
|
454
|
+
children.push({
|
|
455
|
+
type: "text",
|
|
456
|
+
value: resolvedGroups.get(inline.id)?.name ?? inline.id
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
} else if (isCommentBodyLink(inline)) {
|
|
460
|
+
links.add(inline.url);
|
|
461
|
+
children.push({
|
|
462
|
+
type: "link",
|
|
463
|
+
children: [{ type: "text", value: inline.text ?? "" }],
|
|
464
|
+
url: inline.url
|
|
465
|
+
});
|
|
466
|
+
} else if (isCommentBodyText(inline)) {
|
|
467
|
+
if (inline.code) {
|
|
468
|
+
children.push({
|
|
469
|
+
type: "inlineCode",
|
|
470
|
+
value: inline.text
|
|
471
|
+
});
|
|
472
|
+
} else {
|
|
473
|
+
let node = {
|
|
474
|
+
type: "text",
|
|
475
|
+
value: inline.text
|
|
476
|
+
};
|
|
477
|
+
if (inline.strikethrough) {
|
|
478
|
+
node = { type: "delete", children: [node] };
|
|
479
|
+
}
|
|
480
|
+
if (inline.italic) {
|
|
481
|
+
node = { type: "emphasis", children: [node] };
|
|
482
|
+
}
|
|
483
|
+
if (inline.bold) {
|
|
484
|
+
node = { type: "strong", children: [node] };
|
|
485
|
+
}
|
|
486
|
+
children.push(node);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { type: "paragraph", children };
|
|
491
|
+
});
|
|
492
|
+
const text = comment.body.content.reduce((acc, block) => {
|
|
493
|
+
return acc + block.children.reduce((acc2, inline) => {
|
|
494
|
+
if (isCommentBodyMention(inline)) {
|
|
495
|
+
if (inline.kind === "user") {
|
|
496
|
+
return acc2 + (resolvedUsers.get(inline.id)?.name ?? inline.id);
|
|
497
|
+
} else {
|
|
498
|
+
return acc2 + (resolvedGroups.get(inline.id)?.name ?? inline.id);
|
|
499
|
+
}
|
|
500
|
+
} else if (isCommentBodyLink(inline)) {
|
|
501
|
+
if (inline.text) {
|
|
502
|
+
return acc2 + inline.text;
|
|
503
|
+
} else {
|
|
504
|
+
return acc2 + inline.url;
|
|
505
|
+
}
|
|
506
|
+
} else if (isCommentBodyText(inline)) {
|
|
507
|
+
return acc2 + inline.text;
|
|
508
|
+
}
|
|
509
|
+
return acc2;
|
|
510
|
+
}, "");
|
|
511
|
+
}, "");
|
|
512
|
+
return new Message({
|
|
513
|
+
id: comment.id,
|
|
514
|
+
threadId: this.encodeThreadId({
|
|
515
|
+
roomId: comment.roomId,
|
|
516
|
+
threadId: comment.threadId
|
|
517
|
+
}),
|
|
518
|
+
raw: comment,
|
|
519
|
+
formatted: { type: "root", children: nodes },
|
|
520
|
+
text,
|
|
521
|
+
isMention: resolvedUsers.has(this.#botUserId),
|
|
522
|
+
links: Array.from(links.values()).map((url) => ({ url })),
|
|
523
|
+
author: {
|
|
524
|
+
userId: comment.userId,
|
|
525
|
+
userName: resolvedUsers.get(comment.userId)?.name ?? comment.userId,
|
|
526
|
+
fullName: resolvedUsers.get(comment.userId)?.name ?? comment.userId,
|
|
527
|
+
// This assumes that the current bot is the only bot in the thread; if we want
|
|
528
|
+
// to support multiple bots, we need to add a way to determine the bot's user id.
|
|
529
|
+
isBot: comment.userId === this.#botUserId,
|
|
530
|
+
isMe: comment.userId === this.#botUserId
|
|
531
|
+
},
|
|
532
|
+
metadata: {
|
|
533
|
+
dateSent: comment.createdAt,
|
|
534
|
+
edited: comment.editedAt !== void 0,
|
|
535
|
+
editedAt: comment.editedAt
|
|
536
|
+
},
|
|
537
|
+
attachments: comment.attachments.map(
|
|
538
|
+
(attachment) => this.#createAttachment(comment.roomId, attachment)
|
|
539
|
+
)
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
function getRoomIdFromChannelId(channelId) {
|
|
544
|
+
const prefix = `${ADAPTER_PREFIX}:`;
|
|
545
|
+
if (!channelId.startsWith(prefix)) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
`Invalid channel ID: "${channelId}". Expected format: ${prefix}{roomId}`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
const roomId = channelId.slice(prefix.length);
|
|
551
|
+
if (roomId === "") {
|
|
552
|
+
throw new Error(
|
|
553
|
+
`Invalid channel ID: "${channelId}". Expected format: ${prefix}{roomId}`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return roomId;
|
|
557
|
+
}
|
|
558
|
+
function convertPostableMessageToCommentBody(message) {
|
|
559
|
+
if (typeof message === "string") {
|
|
560
|
+
return convertChatRootElementToCommentBodyRootElement(
|
|
561
|
+
parseMarkdown(message)
|
|
562
|
+
);
|
|
563
|
+
} else if ("raw" in message) {
|
|
564
|
+
return convertChatRootElementToCommentBodyRootElement(
|
|
565
|
+
parseMarkdown(message.raw)
|
|
566
|
+
);
|
|
567
|
+
} else if ("markdown" in message) {
|
|
568
|
+
return convertChatRootElementToCommentBodyRootElement(
|
|
569
|
+
parseMarkdown(message.markdown)
|
|
570
|
+
);
|
|
571
|
+
} else if ("ast" in message) {
|
|
572
|
+
return convertChatRootElementToCommentBodyRootElement(message.ast);
|
|
573
|
+
} else if ("card" in message) {
|
|
574
|
+
return convertChatRootElementToCommentBodyRootElement(
|
|
575
|
+
parseMarkdown(
|
|
576
|
+
message.fallbackText ?? convertCardToMarkdownString(message.card)
|
|
577
|
+
)
|
|
578
|
+
);
|
|
579
|
+
} else if ("type" in message && message.type === "card") {
|
|
580
|
+
return convertChatRootElementToCommentBodyRootElement(
|
|
581
|
+
parseMarkdown(convertCardToMarkdownString(message))
|
|
582
|
+
);
|
|
583
|
+
} else {
|
|
584
|
+
console.error(`Unexpected message type: ${JSON.stringify(message)}`);
|
|
585
|
+
return {
|
|
586
|
+
version: 1,
|
|
587
|
+
content: []
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function convertCardToMarkdownString(card) {
|
|
592
|
+
const parts = [];
|
|
593
|
+
if (card.title) {
|
|
594
|
+
parts.push(`**${card.title}**`);
|
|
595
|
+
}
|
|
596
|
+
if (card.subtitle) {
|
|
597
|
+
parts.push(card.subtitle);
|
|
598
|
+
}
|
|
599
|
+
for (const child of card.children) {
|
|
600
|
+
parts.push(convertCardChildToMarkdownString(child));
|
|
601
|
+
}
|
|
602
|
+
return parts.join("\n");
|
|
603
|
+
}
|
|
604
|
+
function convertCardChildToMarkdownString(child) {
|
|
605
|
+
switch (child.type) {
|
|
606
|
+
case "text":
|
|
607
|
+
return child.content;
|
|
608
|
+
case "fields":
|
|
609
|
+
return child.children.map((field) => `**${field.label}**: ${field.value}`).join("\n");
|
|
610
|
+
case "actions":
|
|
611
|
+
return "";
|
|
612
|
+
case "table": {
|
|
613
|
+
let markdown = "|";
|
|
614
|
+
for (const header of child.headers) {
|
|
615
|
+
markdown += ` ${header} |`;
|
|
616
|
+
}
|
|
617
|
+
markdown += "\n|";
|
|
618
|
+
for (const _ of child.headers) {
|
|
619
|
+
markdown += "--- |";
|
|
620
|
+
}
|
|
621
|
+
markdown += "\n";
|
|
622
|
+
for (const row of child.rows) {
|
|
623
|
+
markdown += "|";
|
|
624
|
+
for (const cell of row) {
|
|
625
|
+
markdown += ` ${cell} |`;
|
|
626
|
+
}
|
|
627
|
+
markdown += "\n";
|
|
628
|
+
}
|
|
629
|
+
return markdown;
|
|
630
|
+
}
|
|
631
|
+
case "section":
|
|
632
|
+
return child.children.map((c) => convertCardChildToMarkdownString(c)).filter(Boolean).join("\n");
|
|
633
|
+
case "link":
|
|
634
|
+
return `[${child.label}](${child.url})`;
|
|
635
|
+
case "divider":
|
|
636
|
+
return "---";
|
|
637
|
+
case "image":
|
|
638
|
+
return ``;
|
|
639
|
+
default:
|
|
640
|
+
return "";
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function convertChatRootElementToCommentBodyRootElement(root) {
|
|
644
|
+
return {
|
|
645
|
+
version: 1,
|
|
646
|
+
content: root.children.flatMap(
|
|
647
|
+
(child) => convertChatBlockElementToCommentBodyBlockElement(child)
|
|
648
|
+
)
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function convertChatBlockElementToCommentBodyBlockElement(node) {
|
|
652
|
+
switch (node.type) {
|
|
653
|
+
case "paragraph": {
|
|
654
|
+
const children = [];
|
|
655
|
+
for (const child of node.children) {
|
|
656
|
+
children.push(
|
|
657
|
+
convertChatInlineElementToCommentBodyInlineElement(child)
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
type: "paragraph",
|
|
662
|
+
children
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
case "blockquote": {
|
|
666
|
+
return node.children.flatMap((child) => {
|
|
667
|
+
return convertChatBlockElementToCommentBodyBlockElement(child);
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
case "list": {
|
|
671
|
+
return node.children.flatMap((child) => {
|
|
672
|
+
return convertChatBlockElementToCommentBodyBlockElement(child);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
case "listItem": {
|
|
676
|
+
return node.children.flatMap((child) => {
|
|
677
|
+
return convertChatBlockElementToCommentBodyBlockElement(child);
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
case "heading": {
|
|
681
|
+
return convertChatBlockElementToCommentBodyBlockElement({
|
|
682
|
+
type: "paragraph",
|
|
683
|
+
children: node.children
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
case "code": {
|
|
687
|
+
return convertChatBlockElementToCommentBodyBlockElement({
|
|
688
|
+
type: "paragraph",
|
|
689
|
+
children: [{ type: "text", value: node.value }]
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
case "html": {
|
|
693
|
+
return convertChatBlockElementToCommentBodyBlockElement({
|
|
694
|
+
type: "paragraph",
|
|
695
|
+
children: [{ type: "text", value: node.value }]
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
case "table": {
|
|
699
|
+
return {
|
|
700
|
+
type: "paragraph",
|
|
701
|
+
children: [{ text: tableToAscii(node) }]
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
case "link":
|
|
705
|
+
case "image":
|
|
706
|
+
case "strong":
|
|
707
|
+
case "emphasis":
|
|
708
|
+
case "inlineCode":
|
|
709
|
+
case "delete":
|
|
710
|
+
case "text": {
|
|
711
|
+
return {
|
|
712
|
+
type: "paragraph",
|
|
713
|
+
children: [convertChatInlineElementToCommentBodyInlineElement(node)]
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
case "break":
|
|
717
|
+
case "thematicBreak":
|
|
718
|
+
case "definition":
|
|
719
|
+
case "tableCell":
|
|
720
|
+
case "tableRow":
|
|
721
|
+
case "yaml":
|
|
722
|
+
case "footnoteDefinition":
|
|
723
|
+
case "footnoteReference":
|
|
724
|
+
case "imageReference":
|
|
725
|
+
case "linkReference":
|
|
726
|
+
default: {
|
|
727
|
+
return [];
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function convertChatInlineElementToCommentBodyInlineElement(inline) {
|
|
732
|
+
switch (inline.type) {
|
|
733
|
+
case "text":
|
|
734
|
+
return { text: inline.value };
|
|
735
|
+
case "link":
|
|
736
|
+
return {
|
|
737
|
+
type: "link",
|
|
738
|
+
url: inline.url,
|
|
739
|
+
// Link elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),
|
|
740
|
+
// so we convert the children to plain text to match the expected format
|
|
741
|
+
text: inline.children.map((child) => {
|
|
742
|
+
return convertChatInlineElementToPlainText(child);
|
|
743
|
+
}).join("")
|
|
744
|
+
};
|
|
745
|
+
case "image":
|
|
746
|
+
return { text: inline.url };
|
|
747
|
+
case "emphasis":
|
|
748
|
+
return {
|
|
749
|
+
// Emphasis elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),
|
|
750
|
+
// so we convert the children to plain text to match the expected format
|
|
751
|
+
text: inline.children.map((child) => {
|
|
752
|
+
return convertChatInlineElementToPlainText(child);
|
|
753
|
+
}).join(""),
|
|
754
|
+
italic: true
|
|
755
|
+
};
|
|
756
|
+
case "strong":
|
|
757
|
+
return {
|
|
758
|
+
text: inline.children.map((child) => {
|
|
759
|
+
return convertChatInlineElementToPlainText(child);
|
|
760
|
+
}).join(""),
|
|
761
|
+
bold: true
|
|
762
|
+
};
|
|
763
|
+
case "delete":
|
|
764
|
+
return {
|
|
765
|
+
text: inline.children.map((child) => {
|
|
766
|
+
return convertChatInlineElementToPlainText(child);
|
|
767
|
+
}).join(""),
|
|
768
|
+
strikethrough: true
|
|
769
|
+
};
|
|
770
|
+
case "inlineCode":
|
|
771
|
+
return {
|
|
772
|
+
text: inline.value,
|
|
773
|
+
code: true
|
|
774
|
+
};
|
|
775
|
+
case "html":
|
|
776
|
+
return { text: inline.value };
|
|
777
|
+
case "break":
|
|
778
|
+
case "linkReference":
|
|
779
|
+
case "imageReference":
|
|
780
|
+
case "footnoteReference":
|
|
781
|
+
default: {
|
|
782
|
+
return { text: "" };
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function convertChatInlineElementToPlainText(inline) {
|
|
787
|
+
switch (inline.type) {
|
|
788
|
+
case "text":
|
|
789
|
+
return inline.value;
|
|
790
|
+
case "link":
|
|
791
|
+
return inline.children.map((child) => {
|
|
792
|
+
return convertChatInlineElementToPlainText(child);
|
|
793
|
+
}).join("");
|
|
794
|
+
case "image":
|
|
795
|
+
return inline.url;
|
|
796
|
+
case "emphasis":
|
|
797
|
+
return inline.children.map((child) => {
|
|
798
|
+
return convertChatInlineElementToPlainText(child);
|
|
799
|
+
}).join("");
|
|
800
|
+
case "strong":
|
|
801
|
+
return inline.children.map((child) => {
|
|
802
|
+
return convertChatInlineElementToPlainText(child);
|
|
803
|
+
}).join("");
|
|
804
|
+
case "delete":
|
|
805
|
+
return inline.children.map((child) => {
|
|
806
|
+
return convertChatInlineElementToPlainText(child);
|
|
807
|
+
}).join("");
|
|
808
|
+
case "inlineCode":
|
|
809
|
+
return inline.value;
|
|
810
|
+
case "html":
|
|
811
|
+
return inline.value;
|
|
812
|
+
case "break":
|
|
813
|
+
case "linkReference":
|
|
814
|
+
case "imageReference":
|
|
815
|
+
case "footnoteReference":
|
|
816
|
+
default:
|
|
817
|
+
return "";
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
function encodePaginationCursorByCreatedAt(id, createdAt) {
|
|
821
|
+
return base64UrlEncode(
|
|
822
|
+
JSON.stringify([
|
|
823
|
+
["id", id],
|
|
824
|
+
["createdAt", createdAt.getTime()]
|
|
825
|
+
])
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
function decodePaginationCursorByCreatedAt(cursor) {
|
|
829
|
+
try {
|
|
830
|
+
const parsed = JSON.parse(base64UrlDecode(cursor));
|
|
831
|
+
if (!Array.isArray(parsed) || parsed.length !== 2 || parsed[0]?.[0] !== "id" || parsed[1]?.[0] !== "createdAt") {
|
|
832
|
+
throw new Error("Invalid cursor structure");
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
id: parsed[0][1],
|
|
836
|
+
createdAt: new Date(parsed[1][1])
|
|
837
|
+
};
|
|
838
|
+
} catch {
|
|
839
|
+
throw new Error(`Invalid pagination cursor: ${cursor}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function encodePaginationCursorByUpdatedAt(id, updatedAt) {
|
|
843
|
+
return base64UrlEncode(
|
|
844
|
+
JSON.stringify([
|
|
845
|
+
["id", id],
|
|
846
|
+
["updatedAt", updatedAt.getTime()]
|
|
847
|
+
])
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
function decodePaginationCursorByUpdatedAt(cursor) {
|
|
851
|
+
try {
|
|
852
|
+
const parsed = JSON.parse(base64UrlDecode(cursor));
|
|
853
|
+
if (!Array.isArray(parsed) || parsed.length !== 2 || parsed[0]?.[0] !== "id" || parsed[1]?.[0] !== "updatedAt") {
|
|
854
|
+
throw new Error("Invalid cursor structure");
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
id: parsed[0][1],
|
|
858
|
+
updatedAt: new Date(parsed[1][1])
|
|
859
|
+
};
|
|
860
|
+
} catch {
|
|
861
|
+
throw new Error(`Invalid pagination cursor: ${cursor}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function base64UrlEncode(str) {
|
|
865
|
+
const bytes = new TextEncoder().encode(str);
|
|
866
|
+
const binary = String.fromCharCode(...bytes);
|
|
867
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
868
|
+
}
|
|
869
|
+
function base64UrlDecode(str) {
|
|
870
|
+
let s = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
871
|
+
while (s.length % 4) s += "=";
|
|
872
|
+
const binary = atob(s);
|
|
873
|
+
const bytes = Uint8Array.from(binary, (c) => c.codePointAt(0));
|
|
874
|
+
return new TextDecoder().decode(bytes);
|
|
875
|
+
}
|
|
876
|
+
function slicePageByCreatedAt(data, options) {
|
|
877
|
+
const { direction, limit, startingAfter } = options;
|
|
878
|
+
data = data.slice().sort((a, b) => {
|
|
879
|
+
if (a.createdAt.getTime() !== b.createdAt.getTime()) {
|
|
880
|
+
return a.createdAt.getTime() - b.createdAt.getTime();
|
|
881
|
+
}
|
|
882
|
+
return b.id.localeCompare(a.id);
|
|
883
|
+
});
|
|
884
|
+
let startIndex;
|
|
885
|
+
let endIndex;
|
|
886
|
+
if (direction === "descending") {
|
|
887
|
+
if (startingAfter) {
|
|
888
|
+
const cursor = decodePaginationCursorByCreatedAt(startingAfter);
|
|
889
|
+
endIndex = data.findIndex(
|
|
890
|
+
(c) => c.createdAt.getTime() > cursor.createdAt.getTime() || c.createdAt.getTime() === cursor.createdAt.getTime() && c.id <= cursor.id
|
|
891
|
+
);
|
|
892
|
+
if (endIndex === -1) {
|
|
893
|
+
endIndex = data.length;
|
|
894
|
+
}
|
|
895
|
+
} else {
|
|
896
|
+
endIndex = data.length;
|
|
897
|
+
}
|
|
898
|
+
startIndex = limit !== void 0 ? Math.max(0, endIndex - limit) : 0;
|
|
899
|
+
} else {
|
|
900
|
+
if (startingAfter) {
|
|
901
|
+
const cursor = decodePaginationCursorByCreatedAt(startingAfter);
|
|
902
|
+
startIndex = data.findIndex(
|
|
903
|
+
(c) => c.createdAt.getTime() > cursor.createdAt.getTime() || c.createdAt.getTime() === cursor.createdAt.getTime() && c.id < cursor.id
|
|
904
|
+
);
|
|
905
|
+
if (startIndex === -1) {
|
|
906
|
+
return { data: [], nextCursor: void 0 };
|
|
907
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
startIndex = 0;
|
|
910
|
+
}
|
|
911
|
+
endIndex = limit !== void 0 ? Math.min(data.length, startIndex + limit) : data.length;
|
|
912
|
+
}
|
|
913
|
+
const page = data.slice(startIndex, endIndex);
|
|
914
|
+
if (page.length === 0) {
|
|
915
|
+
return { data: [], nextCursor: void 0 };
|
|
916
|
+
}
|
|
917
|
+
let nextCursor;
|
|
918
|
+
if (direction === "descending") {
|
|
919
|
+
nextCursor = startIndex > 0 ? encodePaginationCursorByCreatedAt(page[0].id, page[0].createdAt) : void 0;
|
|
920
|
+
} else {
|
|
921
|
+
nextCursor = endIndex < data.length ? encodePaginationCursorByCreatedAt(
|
|
922
|
+
page[page.length - 1].id,
|
|
923
|
+
page[page.length - 1].createdAt
|
|
924
|
+
) : void 0;
|
|
925
|
+
}
|
|
926
|
+
return { data: page, nextCursor };
|
|
927
|
+
}
|
|
928
|
+
function slicePageByUpdatedAt(data, options) {
|
|
929
|
+
const { limit, startingAfter } = options;
|
|
930
|
+
data = data.slice().sort((a, b) => {
|
|
931
|
+
if (a.updatedAt.getTime() !== b.updatedAt.getTime()) {
|
|
932
|
+
return a.updatedAt.getTime() - b.updatedAt.getTime();
|
|
933
|
+
}
|
|
934
|
+
return b.id.localeCompare(a.id);
|
|
935
|
+
});
|
|
936
|
+
let endIndex;
|
|
937
|
+
if (startingAfter) {
|
|
938
|
+
const cursor = decodePaginationCursorByUpdatedAt(startingAfter);
|
|
939
|
+
endIndex = data.findIndex(
|
|
940
|
+
(c) => c.updatedAt.getTime() > cursor.updatedAt.getTime() || c.updatedAt.getTime() === cursor.updatedAt.getTime() && c.id <= cursor.id
|
|
941
|
+
);
|
|
942
|
+
if (endIndex === -1) {
|
|
943
|
+
endIndex = data.length;
|
|
944
|
+
}
|
|
945
|
+
} else {
|
|
946
|
+
endIndex = data.length;
|
|
947
|
+
}
|
|
948
|
+
const startIndex = limit !== void 0 ? Math.max(0, endIndex - limit) : 0;
|
|
949
|
+
const page = data.slice(startIndex, endIndex);
|
|
950
|
+
if (page.length === 0) {
|
|
951
|
+
return { data: [], nextCursor: void 0 };
|
|
952
|
+
}
|
|
953
|
+
const nextCursor = startIndex > 0 ? encodePaginationCursorByUpdatedAt(page[0].id, page[0].updatedAt) : void 0;
|
|
954
|
+
return { data: page, nextCursor };
|
|
955
|
+
}
|
|
956
|
+
function getAttachmentType(mimeType) {
|
|
957
|
+
if (mimeType.startsWith("image/")) {
|
|
958
|
+
return "image";
|
|
959
|
+
} else if (mimeType.startsWith("video/")) {
|
|
960
|
+
return "video";
|
|
961
|
+
} else if (mimeType.startsWith("audio/")) {
|
|
962
|
+
return "audio";
|
|
963
|
+
}
|
|
964
|
+
return "file";
|
|
965
|
+
}
|
|
966
|
+
function createLiveblocksAdapter(config) {
|
|
967
|
+
return new LiveblocksAdapter(config);
|
|
968
|
+
}
|
|
969
|
+
export {
|
|
970
|
+
createLiveblocksAdapter
|
|
971
|
+
};
|
|
972
|
+
//# sourceMappingURL=index.js.map
|