@hamzasaleemorg/convex-comments 1.0.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 +17 -0
- package/LICENSE +201 -0
- package/README.md +581 -0
- package/dist/client/_generated/_ignore.d.ts +1 -0
- package/dist/client/_generated/_ignore.d.ts.map +1 -0
- package/dist/client/_generated/_ignore.js +3 -0
- package/dist/client/_generated/_ignore.js.map +1 -0
- package/dist/client/index.d.ts +745 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +579 -0
- package/dist/client/index.js.map +1 -0
- package/dist/component/_generated/api.d.ts +44 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +673 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +3 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/lib.d.ts +17 -0
- package/dist/component/lib.d.ts.map +1 -0
- package/dist/component/lib.js +18 -0
- package/dist/component/lib.js.map +1 -0
- package/dist/component/messages.d.ts +173 -0
- package/dist/component/messages.d.ts.map +1 -0
- package/dist/component/messages.js +410 -0
- package/dist/component/messages.js.map +1 -0
- package/dist/component/reactions.d.ts +51 -0
- package/dist/component/reactions.d.ts.map +1 -0
- package/dist/component/reactions.js +191 -0
- package/dist/component/reactions.js.map +1 -0
- package/dist/component/schema.d.ts +274 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +159 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/threads.d.ts +110 -0
- package/dist/component/threads.d.ts.map +1 -0
- package/dist/component/threads.js +276 -0
- package/dist/component/threads.js.map +1 -0
- package/dist/component/typing.d.ts +31 -0
- package/dist/component/typing.d.ts.map +1 -0
- package/dist/component/typing.js +147 -0
- package/dist/component/typing.js.map +1 -0
- package/dist/component/zones.d.ts +63 -0
- package/dist/component/zones.d.ts.map +1 -0
- package/dist/component/zones.js +159 -0
- package/dist/component/zones.js.map +1 -0
- package/dist/react/AddComment.d.ts +57 -0
- package/dist/react/AddComment.d.ts.map +1 -0
- package/dist/react/AddComment.js +285 -0
- package/dist/react/AddComment.js.map +1 -0
- package/dist/react/Comment.d.ts +70 -0
- package/dist/react/Comment.d.ts.map +1 -0
- package/dist/react/Comment.js +259 -0
- package/dist/react/Comment.js.map +1 -0
- package/dist/react/Comments.d.ts +74 -0
- package/dist/react/Comments.d.ts.map +1 -0
- package/dist/react/Comments.js +108 -0
- package/dist/react/Comments.js.map +1 -0
- package/dist/react/CommentsProvider.d.ts +104 -0
- package/dist/react/CommentsProvider.d.ts.map +1 -0
- package/dist/react/CommentsProvider.js +98 -0
- package/dist/react/CommentsProvider.js.map +1 -0
- package/dist/react/ReactionPicker.d.ts +28 -0
- package/dist/react/ReactionPicker.d.ts.map +1 -0
- package/dist/react/ReactionPicker.js +56 -0
- package/dist/react/ReactionPicker.js.map +1 -0
- package/dist/react/Thread.d.ts +84 -0
- package/dist/react/Thread.d.ts.map +1 -0
- package/dist/react/Thread.js +124 -0
- package/dist/react/Thread.js.map +1 -0
- package/dist/react/TypingIndicator.d.ts +25 -0
- package/dist/react/TypingIndicator.d.ts.map +1 -0
- package/dist/react/TypingIndicator.js +99 -0
- package/dist/react/TypingIndicator.js.map +1 -0
- package/dist/react/index.d.ts +15 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +15 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +106 -0
- package/src/client/_generated/_ignore.ts +1 -0
- package/src/client/index.ts +813 -0
- package/src/component/_generated/api.ts +60 -0
- package/src/component/_generated/component.ts +784 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/lib.ts +57 -0
- package/src/component/messages.ts +476 -0
- package/src/component/reactions.ts +222 -0
- package/src/component/schema.ts +169 -0
- package/src/component/threads.ts +319 -0
- package/src/component/typing.ts +168 -0
- package/src/component/zones.ts +180 -0
- package/src/react/AddComment.tsx +463 -0
- package/src/react/Comment.tsx +519 -0
- package/src/react/Comments.tsx +276 -0
- package/src/react/CommentsProvider.tsx +197 -0
- package/src/react/ReactionPicker.tsx +95 -0
- package/src/react/Thread.tsx +336 -0
- package/src/react/TypingIndicator.tsx +144 -0
- package/src/react/index.ts +45 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query } from "./_generated/server.js";
|
|
3
|
+
import { attachmentValidator, mentionValidator, linkValidator } from "./schema.js";
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Message Validators
|
|
6
|
+
// ============================================================================
|
|
7
|
+
const messageValidator = v.object({
|
|
8
|
+
_id: v.id("messages"),
|
|
9
|
+
_creationTime: v.number(),
|
|
10
|
+
threadId: v.id("threads"),
|
|
11
|
+
authorId: v.string(),
|
|
12
|
+
body: v.string(),
|
|
13
|
+
mentions: v.array(mentionValidator),
|
|
14
|
+
links: v.array(linkValidator),
|
|
15
|
+
attachments: v.array(attachmentValidator),
|
|
16
|
+
isEdited: v.boolean(),
|
|
17
|
+
isDeleted: v.boolean(),
|
|
18
|
+
resolved: v.optional(v.boolean()),
|
|
19
|
+
resolvedBy: v.optional(v.string()),
|
|
20
|
+
resolvedAt: v.optional(v.number()),
|
|
21
|
+
createdAt: v.number(),
|
|
22
|
+
editedAt: v.optional(v.number()),
|
|
23
|
+
});
|
|
24
|
+
const reactionSummaryValidator = v.object({
|
|
25
|
+
emoji: v.string(),
|
|
26
|
+
count: v.number(),
|
|
27
|
+
users: v.array(v.string()),
|
|
28
|
+
includesMe: v.boolean(),
|
|
29
|
+
});
|
|
30
|
+
const messageWithReactionsValidator = v.object({
|
|
31
|
+
message: messageValidator,
|
|
32
|
+
reactions: v.array(reactionSummaryValidator),
|
|
33
|
+
});
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Mention & Link Parsing
|
|
36
|
+
// ============================================================================
|
|
37
|
+
/**
|
|
38
|
+
* Parse mentions from message body.
|
|
39
|
+
* Supports @userId format where userId can contain alphanumeric, underscore, hyphen.
|
|
40
|
+
*/
|
|
41
|
+
function parseMentions(body) {
|
|
42
|
+
const mentions = [];
|
|
43
|
+
// Match @mentions - supports various ID formats
|
|
44
|
+
const mentionRegex = /@([a-zA-Z0-9_\-:]+)/g;
|
|
45
|
+
let match;
|
|
46
|
+
while ((match = mentionRegex.exec(body)) !== null) {
|
|
47
|
+
mentions.push({
|
|
48
|
+
userId: match[1],
|
|
49
|
+
start: match.index,
|
|
50
|
+
end: match.index + match[0].length,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return mentions;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Parse links from message body.
|
|
57
|
+
*/
|
|
58
|
+
function parseLinks(body) {
|
|
59
|
+
const links = [];
|
|
60
|
+
// Simple URL regex - matches http(s) URLs
|
|
61
|
+
const urlRegex = /(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/g;
|
|
62
|
+
let match;
|
|
63
|
+
while ((match = urlRegex.exec(body)) !== null) {
|
|
64
|
+
links.push({
|
|
65
|
+
url: match[1],
|
|
66
|
+
start: match.index,
|
|
67
|
+
end: match.index + match[0].length,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return links;
|
|
71
|
+
}
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Message Functions
|
|
74
|
+
// ============================================================================
|
|
75
|
+
/**
|
|
76
|
+
* Add a new comment/message to a thread.
|
|
77
|
+
* Parses mentions and links from the body.
|
|
78
|
+
*/
|
|
79
|
+
export const addComment = mutation({
|
|
80
|
+
args: {
|
|
81
|
+
threadId: v.id("threads"),
|
|
82
|
+
authorId: v.string(),
|
|
83
|
+
body: v.string(),
|
|
84
|
+
attachments: v.optional(v.array(attachmentValidator)),
|
|
85
|
+
},
|
|
86
|
+
returns: v.object({
|
|
87
|
+
messageId: v.id("messages"),
|
|
88
|
+
mentions: v.array(mentionValidator),
|
|
89
|
+
links: v.array(linkValidator),
|
|
90
|
+
}),
|
|
91
|
+
handler: async (ctx, args) => {
|
|
92
|
+
// Verify thread exists
|
|
93
|
+
const thread = await ctx.db.get(args.threadId);
|
|
94
|
+
if (!thread) {
|
|
95
|
+
throw new Error(`Thread ${args.threadId} not found`);
|
|
96
|
+
}
|
|
97
|
+
// Parse mentions and links
|
|
98
|
+
const mentions = parseMentions(args.body);
|
|
99
|
+
const links = parseLinks(args.body);
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
// Insert message
|
|
102
|
+
const messageId = await ctx.db.insert("messages", {
|
|
103
|
+
threadId: args.threadId,
|
|
104
|
+
authorId: args.authorId,
|
|
105
|
+
body: args.body,
|
|
106
|
+
mentions,
|
|
107
|
+
links,
|
|
108
|
+
attachments: args.attachments ?? [],
|
|
109
|
+
isEdited: false,
|
|
110
|
+
isDeleted: false,
|
|
111
|
+
createdAt: now,
|
|
112
|
+
});
|
|
113
|
+
// Update thread's last activity
|
|
114
|
+
await ctx.db.patch(args.threadId, {
|
|
115
|
+
lastActivityAt: now,
|
|
116
|
+
});
|
|
117
|
+
// Clear typing indicator for this user
|
|
118
|
+
const typingIndicator = await ctx.db
|
|
119
|
+
.query("typingIndicators")
|
|
120
|
+
.withIndex("threadId_userId", (q) => q.eq("threadId", args.threadId).eq("userId", args.authorId))
|
|
121
|
+
.first();
|
|
122
|
+
if (typingIndicator) {
|
|
123
|
+
await ctx.db.delete(typingIndicator._id);
|
|
124
|
+
}
|
|
125
|
+
return { messageId, mentions, links };
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
/**
|
|
129
|
+
* Get a single message by ID.
|
|
130
|
+
*/
|
|
131
|
+
export const getMessage = query({
|
|
132
|
+
args: {
|
|
133
|
+
messageId: v.id("messages"),
|
|
134
|
+
currentUserId: v.optional(v.string()),
|
|
135
|
+
},
|
|
136
|
+
returns: v.union(v.null(), messageWithReactionsValidator),
|
|
137
|
+
handler: async (ctx, args) => {
|
|
138
|
+
const message = await ctx.db.get(args.messageId);
|
|
139
|
+
if (!message) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
// Get reactions grouped by emoji
|
|
143
|
+
const reactions = await ctx.db
|
|
144
|
+
.query("reactions")
|
|
145
|
+
.withIndex("messageId", (q) => q.eq("messageId", args.messageId))
|
|
146
|
+
.collect();
|
|
147
|
+
const reactionMap = new Map();
|
|
148
|
+
for (const reaction of reactions) {
|
|
149
|
+
const existing = reactionMap.get(reaction.emoji);
|
|
150
|
+
if (existing) {
|
|
151
|
+
existing.count++;
|
|
152
|
+
existing.users.push(reaction.userId);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
reactionMap.set(reaction.emoji, { count: 1, users: [reaction.userId] });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const reactionSummaries = Array.from(reactionMap.entries()).map(([emoji, data]) => ({
|
|
159
|
+
emoji,
|
|
160
|
+
count: data.count,
|
|
161
|
+
users: data.users,
|
|
162
|
+
includesMe: args.currentUserId
|
|
163
|
+
? data.users.includes(args.currentUserId)
|
|
164
|
+
: false,
|
|
165
|
+
}));
|
|
166
|
+
return {
|
|
167
|
+
message,
|
|
168
|
+
reactions: reactionSummaries,
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
/**
|
|
173
|
+
* Get messages in a thread with pagination.
|
|
174
|
+
* Uses cursor-based pagination for efficiency.
|
|
175
|
+
*/
|
|
176
|
+
export const getMessages = query({
|
|
177
|
+
args: {
|
|
178
|
+
threadId: v.id("threads"),
|
|
179
|
+
limit: v.optional(v.number()),
|
|
180
|
+
cursor: v.optional(v.string()),
|
|
181
|
+
order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
|
|
182
|
+
currentUserId: v.optional(v.string()),
|
|
183
|
+
includeDeleted: v.optional(v.boolean()),
|
|
184
|
+
},
|
|
185
|
+
returns: v.object({
|
|
186
|
+
messages: v.array(messageWithReactionsValidator),
|
|
187
|
+
nextCursor: v.optional(v.string()),
|
|
188
|
+
hasMore: v.boolean(),
|
|
189
|
+
}),
|
|
190
|
+
handler: async (ctx, args) => {
|
|
191
|
+
const limit = args.limit ?? 50;
|
|
192
|
+
const order = args.order ?? "asc";
|
|
193
|
+
const includeDeleted = args.includeDeleted ?? false;
|
|
194
|
+
// Build query
|
|
195
|
+
let query;
|
|
196
|
+
if (args.cursor) {
|
|
197
|
+
const cursorTime = parseInt(args.cursor, 10);
|
|
198
|
+
if (order === "asc") {
|
|
199
|
+
query = ctx.db
|
|
200
|
+
.query("messages")
|
|
201
|
+
.withIndex("threadId_createdAt", (q) => q.eq("threadId", args.threadId).gt("createdAt", cursorTime))
|
|
202
|
+
.order("asc");
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
query = ctx.db
|
|
206
|
+
.query("messages")
|
|
207
|
+
.withIndex("threadId_createdAt", (q) => q.eq("threadId", args.threadId).lt("createdAt", cursorTime))
|
|
208
|
+
.order("desc");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
query = ctx.db
|
|
213
|
+
.query("messages")
|
|
214
|
+
.withIndex("threadId_createdAt", (q) => q.eq("threadId", args.threadId))
|
|
215
|
+
.order(order);
|
|
216
|
+
}
|
|
217
|
+
// Fetch one extra to check if there are more
|
|
218
|
+
const messages = await query.take(limit + 1);
|
|
219
|
+
// Filter deleted if needed
|
|
220
|
+
const filteredMessages = includeDeleted
|
|
221
|
+
? messages
|
|
222
|
+
: messages.filter((m) => !m.isDeleted);
|
|
223
|
+
const hasMore = filteredMessages.length > limit;
|
|
224
|
+
const resultMessages = filteredMessages.slice(0, limit);
|
|
225
|
+
// Get reactions for each message
|
|
226
|
+
const messagesWithReactions = await Promise.all(resultMessages.map(async (message) => {
|
|
227
|
+
const reactions = await ctx.db
|
|
228
|
+
.query("reactions")
|
|
229
|
+
.withIndex("messageId", (q) => q.eq("messageId", message._id))
|
|
230
|
+
.collect();
|
|
231
|
+
const reactionMap = new Map();
|
|
232
|
+
for (const reaction of reactions) {
|
|
233
|
+
const existing = reactionMap.get(reaction.emoji);
|
|
234
|
+
if (existing) {
|
|
235
|
+
existing.count++;
|
|
236
|
+
existing.users.push(reaction.userId);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
reactionMap.set(reaction.emoji, { count: 1, users: [reaction.userId] });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const reactionSummaries = Array.from(reactionMap.entries()).map(([emoji, data]) => ({
|
|
243
|
+
emoji,
|
|
244
|
+
count: data.count,
|
|
245
|
+
users: data.users,
|
|
246
|
+
includesMe: args.currentUserId
|
|
247
|
+
? data.users.includes(args.currentUserId)
|
|
248
|
+
: false,
|
|
249
|
+
}));
|
|
250
|
+
return {
|
|
251
|
+
message,
|
|
252
|
+
reactions: reactionSummaries,
|
|
253
|
+
};
|
|
254
|
+
}));
|
|
255
|
+
// Generate next cursor
|
|
256
|
+
const lastMessage = resultMessages[resultMessages.length - 1];
|
|
257
|
+
const nextCursor = hasMore && lastMessage
|
|
258
|
+
? lastMessage.createdAt.toString()
|
|
259
|
+
: undefined;
|
|
260
|
+
return {
|
|
261
|
+
messages: messagesWithReactions,
|
|
262
|
+
nextCursor,
|
|
263
|
+
hasMore,
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
/**
|
|
268
|
+
* Edit a message.
|
|
269
|
+
*/
|
|
270
|
+
export const editMessage = mutation({
|
|
271
|
+
args: {
|
|
272
|
+
messageId: v.id("messages"),
|
|
273
|
+
body: v.string(),
|
|
274
|
+
/** Optional: User ID to verify ownership */
|
|
275
|
+
authorId: v.optional(v.string()),
|
|
276
|
+
},
|
|
277
|
+
returns: v.object({
|
|
278
|
+
mentions: v.array(mentionValidator),
|
|
279
|
+
links: v.array(linkValidator),
|
|
280
|
+
}),
|
|
281
|
+
handler: async (ctx, args) => {
|
|
282
|
+
const message = await ctx.db.get(args.messageId);
|
|
283
|
+
if (!message) {
|
|
284
|
+
throw new Error(`Message ${args.messageId} not found`);
|
|
285
|
+
}
|
|
286
|
+
if (message.isDeleted) {
|
|
287
|
+
throw new Error("Cannot edit a deleted message");
|
|
288
|
+
}
|
|
289
|
+
// Optionally verify ownership
|
|
290
|
+
if (args.authorId && message.authorId !== args.authorId) {
|
|
291
|
+
throw new Error("You can only edit your own messages");
|
|
292
|
+
}
|
|
293
|
+
// Re-parse mentions and links
|
|
294
|
+
const mentions = parseMentions(args.body);
|
|
295
|
+
const links = parseLinks(args.body);
|
|
296
|
+
await ctx.db.patch(args.messageId, {
|
|
297
|
+
body: args.body,
|
|
298
|
+
mentions,
|
|
299
|
+
links,
|
|
300
|
+
isEdited: true,
|
|
301
|
+
editedAt: Date.now(),
|
|
302
|
+
});
|
|
303
|
+
// Update thread activity
|
|
304
|
+
const thread = await ctx.db.get(message.threadId);
|
|
305
|
+
if (thread) {
|
|
306
|
+
await ctx.db.patch(message.threadId, {
|
|
307
|
+
lastActivityAt: Date.now(),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return { mentions, links };
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
/**
|
|
314
|
+
* Soft delete a message.
|
|
315
|
+
*/
|
|
316
|
+
export const deleteMessage = mutation({
|
|
317
|
+
args: {
|
|
318
|
+
messageId: v.id("messages"),
|
|
319
|
+
/** Optional: User ID to verify ownership */
|
|
320
|
+
authorId: v.optional(v.string()),
|
|
321
|
+
},
|
|
322
|
+
returns: v.null(),
|
|
323
|
+
handler: async (ctx, args) => {
|
|
324
|
+
const message = await ctx.db.get(args.messageId);
|
|
325
|
+
if (!message) {
|
|
326
|
+
throw new Error(`Message ${args.messageId} not found`);
|
|
327
|
+
}
|
|
328
|
+
// Optionally verify ownership
|
|
329
|
+
if (args.authorId && message.authorId !== args.authorId) {
|
|
330
|
+
throw new Error("You can only delete your own messages");
|
|
331
|
+
}
|
|
332
|
+
await ctx.db.patch(args.messageId, {
|
|
333
|
+
isDeleted: true,
|
|
334
|
+
body: "[deleted]",
|
|
335
|
+
mentions: [],
|
|
336
|
+
links: [],
|
|
337
|
+
attachments: [],
|
|
338
|
+
});
|
|
339
|
+
return null;
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
/**
|
|
343
|
+
* Hard delete a message (permanently remove).
|
|
344
|
+
*/
|
|
345
|
+
export const permanentlyDeleteMessage = mutation({
|
|
346
|
+
args: {
|
|
347
|
+
messageId: v.id("messages"),
|
|
348
|
+
},
|
|
349
|
+
returns: v.null(),
|
|
350
|
+
handler: async (ctx, args) => {
|
|
351
|
+
// Delete all reactions on this message
|
|
352
|
+
const reactions = await ctx.db
|
|
353
|
+
.query("reactions")
|
|
354
|
+
.withIndex("messageId", (q) => q.eq("messageId", args.messageId))
|
|
355
|
+
.collect();
|
|
356
|
+
for (const reaction of reactions) {
|
|
357
|
+
await ctx.db.delete(reaction._id);
|
|
358
|
+
}
|
|
359
|
+
// Delete the message
|
|
360
|
+
await ctx.db.delete(args.messageId);
|
|
361
|
+
return null;
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
/**
|
|
365
|
+
* Resolve a message (mark as addressed/completed).
|
|
366
|
+
*/
|
|
367
|
+
export const resolveMessage = mutation({
|
|
368
|
+
args: {
|
|
369
|
+
messageId: v.id("messages"),
|
|
370
|
+
userId: v.string(),
|
|
371
|
+
},
|
|
372
|
+
returns: v.null(),
|
|
373
|
+
handler: async (ctx, args) => {
|
|
374
|
+
const message = await ctx.db.get(args.messageId);
|
|
375
|
+
if (!message) {
|
|
376
|
+
throw new Error(`Message ${args.messageId} not found`);
|
|
377
|
+
}
|
|
378
|
+
if (message.isDeleted) {
|
|
379
|
+
throw new Error("Cannot resolve a deleted message");
|
|
380
|
+
}
|
|
381
|
+
await ctx.db.patch(args.messageId, {
|
|
382
|
+
resolved: true,
|
|
383
|
+
resolvedBy: args.userId,
|
|
384
|
+
resolvedAt: Date.now(),
|
|
385
|
+
});
|
|
386
|
+
return null;
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
/**
|
|
390
|
+
* Unresolve a message (mark as not addressed).
|
|
391
|
+
*/
|
|
392
|
+
export const unresolveMessage = mutation({
|
|
393
|
+
args: {
|
|
394
|
+
messageId: v.id("messages"),
|
|
395
|
+
},
|
|
396
|
+
returns: v.null(),
|
|
397
|
+
handler: async (ctx, args) => {
|
|
398
|
+
const message = await ctx.db.get(args.messageId);
|
|
399
|
+
if (!message) {
|
|
400
|
+
throw new Error(`Message ${args.messageId} not found`);
|
|
401
|
+
}
|
|
402
|
+
await ctx.db.patch(args.messageId, {
|
|
403
|
+
resolved: undefined,
|
|
404
|
+
resolvedBy: undefined,
|
|
405
|
+
resolvedAt: undefined,
|
|
406
|
+
});
|
|
407
|
+
return null;
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/component/messages.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add a reaction to a message.
|
|
3
|
+
* If the user already has the same reaction, this is a no-op.
|
|
4
|
+
*/
|
|
5
|
+
export declare const addReaction: import("convex/server").RegisteredMutation<"public", {
|
|
6
|
+
emoji: string;
|
|
7
|
+
messageId: import("convex/values").GenericId<"messages">;
|
|
8
|
+
userId: string;
|
|
9
|
+
}, Promise<import("convex/values").GenericId<"reactions"> | null>>;
|
|
10
|
+
/**
|
|
11
|
+
* Remove a reaction from a message.
|
|
12
|
+
*/
|
|
13
|
+
export declare const removeReaction: import("convex/server").RegisteredMutation<"public", {
|
|
14
|
+
emoji: string;
|
|
15
|
+
messageId: import("convex/values").GenericId<"messages">;
|
|
16
|
+
userId: string;
|
|
17
|
+
}, Promise<boolean>>;
|
|
18
|
+
/**
|
|
19
|
+
* Toggle a reaction (add if not present, remove if present).
|
|
20
|
+
*/
|
|
21
|
+
export declare const toggleReaction: import("convex/server").RegisteredMutation<"public", {
|
|
22
|
+
emoji: string;
|
|
23
|
+
messageId: import("convex/values").GenericId<"messages">;
|
|
24
|
+
userId: string;
|
|
25
|
+
}, Promise<{
|
|
26
|
+
added: boolean;
|
|
27
|
+
reactionId: undefined;
|
|
28
|
+
} | {
|
|
29
|
+
added: boolean;
|
|
30
|
+
reactionId: import("convex/values").GenericId<"reactions">;
|
|
31
|
+
}>>;
|
|
32
|
+
/**
|
|
33
|
+
* Get all reactions for a message.
|
|
34
|
+
*/
|
|
35
|
+
export declare const getReactions: import("convex/server").RegisteredQuery<"public", {
|
|
36
|
+
currentUserId?: string | undefined;
|
|
37
|
+
messageId: import("convex/values").GenericId<"messages">;
|
|
38
|
+
}, Promise<{
|
|
39
|
+
emoji: string;
|
|
40
|
+
count: number;
|
|
41
|
+
users: string[];
|
|
42
|
+
includesMe: boolean;
|
|
43
|
+
}[]>>;
|
|
44
|
+
/**
|
|
45
|
+
* Get users who reacted with a specific emoji.
|
|
46
|
+
*/
|
|
47
|
+
export declare const getReactionUsers: import("convex/server").RegisteredQuery<"public", {
|
|
48
|
+
emoji: string;
|
|
49
|
+
messageId: import("convex/values").GenericId<"messages">;
|
|
50
|
+
}, Promise<string[]>>;
|
|
51
|
+
//# sourceMappingURL=reactions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reactions.d.ts","sourceRoot":"","sources":["../../src/component/reactions.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;kEAoDtB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,cAAc;;;;oBA0BzB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;GAwDzB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;KAwCvB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;qBAgB3B,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query } from "./_generated/server.js";
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Reaction Functions
|
|
5
|
+
// ============================================================================
|
|
6
|
+
/**
|
|
7
|
+
* Add a reaction to a message.
|
|
8
|
+
* If the user already has the same reaction, this is a no-op.
|
|
9
|
+
*/
|
|
10
|
+
export const addReaction = mutation({
|
|
11
|
+
args: {
|
|
12
|
+
messageId: v.id("messages"),
|
|
13
|
+
userId: v.string(),
|
|
14
|
+
emoji: v.string(),
|
|
15
|
+
},
|
|
16
|
+
returns: v.union(v.id("reactions"), v.null()),
|
|
17
|
+
handler: async (ctx, args) => {
|
|
18
|
+
// Verify message exists
|
|
19
|
+
const message = await ctx.db.get(args.messageId);
|
|
20
|
+
if (!message) {
|
|
21
|
+
throw new Error(`Message ${args.messageId} not found`);
|
|
22
|
+
}
|
|
23
|
+
if (message.isDeleted) {
|
|
24
|
+
throw new Error("Cannot react to a deleted message");
|
|
25
|
+
}
|
|
26
|
+
// Check if reaction already exists
|
|
27
|
+
const existing = await ctx.db
|
|
28
|
+
.query("reactions")
|
|
29
|
+
.withIndex("messageId_emoji_userId", (q) => q
|
|
30
|
+
.eq("messageId", args.messageId)
|
|
31
|
+
.eq("emoji", args.emoji)
|
|
32
|
+
.eq("userId", args.userId))
|
|
33
|
+
.first();
|
|
34
|
+
if (existing) {
|
|
35
|
+
// Already reacted with this emoji
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
// Add reaction
|
|
39
|
+
const reactionId = await ctx.db.insert("reactions", {
|
|
40
|
+
messageId: args.messageId,
|
|
41
|
+
userId: args.userId,
|
|
42
|
+
emoji: args.emoji,
|
|
43
|
+
createdAt: Date.now(),
|
|
44
|
+
});
|
|
45
|
+
// Update thread activity
|
|
46
|
+
const thread = await ctx.db.get(message.threadId);
|
|
47
|
+
if (thread) {
|
|
48
|
+
await ctx.db.patch(message.threadId, {
|
|
49
|
+
lastActivityAt: Date.now(),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return reactionId;
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Remove a reaction from a message.
|
|
57
|
+
*/
|
|
58
|
+
export const removeReaction = mutation({
|
|
59
|
+
args: {
|
|
60
|
+
messageId: v.id("messages"),
|
|
61
|
+
userId: v.string(),
|
|
62
|
+
emoji: v.string(),
|
|
63
|
+
},
|
|
64
|
+
returns: v.boolean(),
|
|
65
|
+
handler: async (ctx, args) => {
|
|
66
|
+
// Find the reaction
|
|
67
|
+
const reaction = await ctx.db
|
|
68
|
+
.query("reactions")
|
|
69
|
+
.withIndex("messageId_emoji_userId", (q) => q
|
|
70
|
+
.eq("messageId", args.messageId)
|
|
71
|
+
.eq("emoji", args.emoji)
|
|
72
|
+
.eq("userId", args.userId))
|
|
73
|
+
.first();
|
|
74
|
+
if (!reaction) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
await ctx.db.delete(reaction._id);
|
|
78
|
+
return true;
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* Toggle a reaction (add if not present, remove if present).
|
|
83
|
+
*/
|
|
84
|
+
export const toggleReaction = mutation({
|
|
85
|
+
args: {
|
|
86
|
+
messageId: v.id("messages"),
|
|
87
|
+
userId: v.string(),
|
|
88
|
+
emoji: v.string(),
|
|
89
|
+
},
|
|
90
|
+
returns: v.object({
|
|
91
|
+
added: v.boolean(),
|
|
92
|
+
reactionId: v.optional(v.id("reactions")),
|
|
93
|
+
}),
|
|
94
|
+
handler: async (ctx, args) => {
|
|
95
|
+
// Verify message exists
|
|
96
|
+
const message = await ctx.db.get(args.messageId);
|
|
97
|
+
if (!message) {
|
|
98
|
+
throw new Error(`Message ${args.messageId} not found`);
|
|
99
|
+
}
|
|
100
|
+
if (message.isDeleted) {
|
|
101
|
+
throw new Error("Cannot react to a deleted message");
|
|
102
|
+
}
|
|
103
|
+
// Check if reaction already exists
|
|
104
|
+
const existing = await ctx.db
|
|
105
|
+
.query("reactions")
|
|
106
|
+
.withIndex("messageId_emoji_userId", (q) => q
|
|
107
|
+
.eq("messageId", args.messageId)
|
|
108
|
+
.eq("emoji", args.emoji)
|
|
109
|
+
.eq("userId", args.userId))
|
|
110
|
+
.first();
|
|
111
|
+
if (existing) {
|
|
112
|
+
// Remove existing reaction
|
|
113
|
+
await ctx.db.delete(existing._id);
|
|
114
|
+
return { added: false, reactionId: undefined };
|
|
115
|
+
}
|
|
116
|
+
// Add new reaction
|
|
117
|
+
const reactionId = await ctx.db.insert("reactions", {
|
|
118
|
+
messageId: args.messageId,
|
|
119
|
+
userId: args.userId,
|
|
120
|
+
emoji: args.emoji,
|
|
121
|
+
createdAt: Date.now(),
|
|
122
|
+
});
|
|
123
|
+
// Update thread activity
|
|
124
|
+
const thread = await ctx.db.get(message.threadId);
|
|
125
|
+
if (thread) {
|
|
126
|
+
await ctx.db.patch(message.threadId, {
|
|
127
|
+
lastActivityAt: Date.now(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return { added: true, reactionId };
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
/**
|
|
134
|
+
* Get all reactions for a message.
|
|
135
|
+
*/
|
|
136
|
+
export const getReactions = query({
|
|
137
|
+
args: {
|
|
138
|
+
messageId: v.id("messages"),
|
|
139
|
+
currentUserId: v.optional(v.string()),
|
|
140
|
+
},
|
|
141
|
+
returns: v.array(v.object({
|
|
142
|
+
emoji: v.string(),
|
|
143
|
+
count: v.number(),
|
|
144
|
+
users: v.array(v.string()),
|
|
145
|
+
includesMe: v.boolean(),
|
|
146
|
+
})),
|
|
147
|
+
handler: async (ctx, args) => {
|
|
148
|
+
const reactions = await ctx.db
|
|
149
|
+
.query("reactions")
|
|
150
|
+
.withIndex("messageId", (q) => q.eq("messageId", args.messageId))
|
|
151
|
+
.collect();
|
|
152
|
+
// Group by emoji
|
|
153
|
+
const reactionMap = new Map();
|
|
154
|
+
for (const reaction of reactions) {
|
|
155
|
+
const existing = reactionMap.get(reaction.emoji);
|
|
156
|
+
if (existing) {
|
|
157
|
+
existing.count++;
|
|
158
|
+
existing.users.push(reaction.userId);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
reactionMap.set(reaction.emoji, { count: 1, users: [reaction.userId] });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return Array.from(reactionMap.entries()).map(([emoji, data]) => ({
|
|
165
|
+
emoji,
|
|
166
|
+
count: data.count,
|
|
167
|
+
users: data.users,
|
|
168
|
+
includesMe: args.currentUserId
|
|
169
|
+
? data.users.includes(args.currentUserId)
|
|
170
|
+
: false,
|
|
171
|
+
}));
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
/**
|
|
175
|
+
* Get users who reacted with a specific emoji.
|
|
176
|
+
*/
|
|
177
|
+
export const getReactionUsers = query({
|
|
178
|
+
args: {
|
|
179
|
+
messageId: v.id("messages"),
|
|
180
|
+
emoji: v.string(),
|
|
181
|
+
},
|
|
182
|
+
returns: v.array(v.string()),
|
|
183
|
+
handler: async (ctx, args) => {
|
|
184
|
+
const reactions = await ctx.db
|
|
185
|
+
.query("reactions")
|
|
186
|
+
.withIndex("messageId_emoji", (q) => q.eq("messageId", args.messageId).eq("emoji", args.emoji))
|
|
187
|
+
.collect();
|
|
188
|
+
return reactions.map((r) => r.userId);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
//# sourceMappingURL=reactions.js.map
|