@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.
Files changed (114) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +201 -0
  3. package/README.md +581 -0
  4. package/dist/client/_generated/_ignore.d.ts +1 -0
  5. package/dist/client/_generated/_ignore.d.ts.map +1 -0
  6. package/dist/client/_generated/_ignore.js +3 -0
  7. package/dist/client/_generated/_ignore.js.map +1 -0
  8. package/dist/client/index.d.ts +745 -0
  9. package/dist/client/index.d.ts.map +1 -0
  10. package/dist/client/index.js +579 -0
  11. package/dist/client/index.js.map +1 -0
  12. package/dist/component/_generated/api.d.ts +44 -0
  13. package/dist/component/_generated/api.d.ts.map +1 -0
  14. package/dist/component/_generated/api.js +31 -0
  15. package/dist/component/_generated/api.js.map +1 -0
  16. package/dist/component/_generated/component.d.ts +673 -0
  17. package/dist/component/_generated/component.d.ts.map +1 -0
  18. package/dist/component/_generated/component.js +11 -0
  19. package/dist/component/_generated/component.js.map +1 -0
  20. package/dist/component/_generated/dataModel.d.ts +46 -0
  21. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  22. package/dist/component/_generated/dataModel.js +11 -0
  23. package/dist/component/_generated/dataModel.js.map +1 -0
  24. package/dist/component/_generated/server.d.ts +121 -0
  25. package/dist/component/_generated/server.d.ts.map +1 -0
  26. package/dist/component/_generated/server.js +78 -0
  27. package/dist/component/_generated/server.js.map +1 -0
  28. package/dist/component/convex.config.d.ts +3 -0
  29. package/dist/component/convex.config.d.ts.map +1 -0
  30. package/dist/component/convex.config.js +3 -0
  31. package/dist/component/convex.config.js.map +1 -0
  32. package/dist/component/lib.d.ts +17 -0
  33. package/dist/component/lib.d.ts.map +1 -0
  34. package/dist/component/lib.js +18 -0
  35. package/dist/component/lib.js.map +1 -0
  36. package/dist/component/messages.d.ts +173 -0
  37. package/dist/component/messages.d.ts.map +1 -0
  38. package/dist/component/messages.js +410 -0
  39. package/dist/component/messages.js.map +1 -0
  40. package/dist/component/reactions.d.ts +51 -0
  41. package/dist/component/reactions.d.ts.map +1 -0
  42. package/dist/component/reactions.js +191 -0
  43. package/dist/component/reactions.js.map +1 -0
  44. package/dist/component/schema.d.ts +274 -0
  45. package/dist/component/schema.d.ts.map +1 -0
  46. package/dist/component/schema.js +159 -0
  47. package/dist/component/schema.js.map +1 -0
  48. package/dist/component/threads.d.ts +110 -0
  49. package/dist/component/threads.d.ts.map +1 -0
  50. package/dist/component/threads.js +276 -0
  51. package/dist/component/threads.js.map +1 -0
  52. package/dist/component/typing.d.ts +31 -0
  53. package/dist/component/typing.d.ts.map +1 -0
  54. package/dist/component/typing.js +147 -0
  55. package/dist/component/typing.js.map +1 -0
  56. package/dist/component/zones.d.ts +63 -0
  57. package/dist/component/zones.d.ts.map +1 -0
  58. package/dist/component/zones.js +159 -0
  59. package/dist/component/zones.js.map +1 -0
  60. package/dist/react/AddComment.d.ts +57 -0
  61. package/dist/react/AddComment.d.ts.map +1 -0
  62. package/dist/react/AddComment.js +285 -0
  63. package/dist/react/AddComment.js.map +1 -0
  64. package/dist/react/Comment.d.ts +70 -0
  65. package/dist/react/Comment.d.ts.map +1 -0
  66. package/dist/react/Comment.js +259 -0
  67. package/dist/react/Comment.js.map +1 -0
  68. package/dist/react/Comments.d.ts +74 -0
  69. package/dist/react/Comments.d.ts.map +1 -0
  70. package/dist/react/Comments.js +108 -0
  71. package/dist/react/Comments.js.map +1 -0
  72. package/dist/react/CommentsProvider.d.ts +104 -0
  73. package/dist/react/CommentsProvider.d.ts.map +1 -0
  74. package/dist/react/CommentsProvider.js +98 -0
  75. package/dist/react/CommentsProvider.js.map +1 -0
  76. package/dist/react/ReactionPicker.d.ts +28 -0
  77. package/dist/react/ReactionPicker.d.ts.map +1 -0
  78. package/dist/react/ReactionPicker.js +56 -0
  79. package/dist/react/ReactionPicker.js.map +1 -0
  80. package/dist/react/Thread.d.ts +84 -0
  81. package/dist/react/Thread.d.ts.map +1 -0
  82. package/dist/react/Thread.js +124 -0
  83. package/dist/react/Thread.js.map +1 -0
  84. package/dist/react/TypingIndicator.d.ts +25 -0
  85. package/dist/react/TypingIndicator.d.ts.map +1 -0
  86. package/dist/react/TypingIndicator.js +99 -0
  87. package/dist/react/TypingIndicator.js.map +1 -0
  88. package/dist/react/index.d.ts +15 -0
  89. package/dist/react/index.d.ts.map +1 -0
  90. package/dist/react/index.js +15 -0
  91. package/dist/react/index.js.map +1 -0
  92. package/package.json +106 -0
  93. package/src/client/_generated/_ignore.ts +1 -0
  94. package/src/client/index.ts +813 -0
  95. package/src/component/_generated/api.ts +60 -0
  96. package/src/component/_generated/component.ts +784 -0
  97. package/src/component/_generated/dataModel.ts +60 -0
  98. package/src/component/_generated/server.ts +156 -0
  99. package/src/component/convex.config.ts +3 -0
  100. package/src/component/lib.ts +57 -0
  101. package/src/component/messages.ts +476 -0
  102. package/src/component/reactions.ts +222 -0
  103. package/src/component/schema.ts +169 -0
  104. package/src/component/threads.ts +319 -0
  105. package/src/component/typing.ts +168 -0
  106. package/src/component/zones.ts +180 -0
  107. package/src/react/AddComment.tsx +463 -0
  108. package/src/react/Comment.tsx +519 -0
  109. package/src/react/Comments.tsx +276 -0
  110. package/src/react/CommentsProvider.tsx +197 -0
  111. package/src/react/ReactionPicker.tsx +95 -0
  112. package/src/react/Thread.tsx +336 -0
  113. package/src/react/TypingIndicator.tsx +144 -0
  114. package/src/react/index.ts +45 -0
package/README.md ADDED
@@ -0,0 +1,581 @@
1
+ # Convex Comments Component
2
+
3
+ A comments system for Convex with threads, mentions, reactions, and typing indicators. Includes backend functions and optional React UI components.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @hamzasaleemorg/convex-comments
9
+ ```
10
+
11
+ Add the component to your Convex app:
12
+
13
+ ```typescript
14
+ // convex/convex.config.ts
15
+ import { defineApp } from "convex/server";
16
+ import comments from "@hamzasaleemorg/convex-comments/convex.config.js";
17
+
18
+ const app = defineApp();
19
+ app.use(comments);
20
+
21
+ export default app;
22
+ ```
23
+
24
+ ## Quick Start (5 minutes)
25
+
26
+ Get comments working in your Convex app:
27
+
28
+ **1. Create backend functions:**
29
+
30
+ ```typescript
31
+ // convex/comments.ts
32
+ import { v } from "convex/values";
33
+ import { Comments } from "@hamzasaleemorg/convex-comments";
34
+ import { mutation, query } from "./_generated/server";
35
+ import { components } from "./_generated/api";
36
+
37
+ const comments = new Comments(components.comments);
38
+
39
+ // Create a zone for an entity (e.g., a document)
40
+ export const createZone = mutation({
41
+ args: { documentId: v.string() },
42
+ handler: async (ctx, args) => {
43
+ return await comments.getOrCreateZone(ctx, {
44
+ entityId: args.documentId
45
+ });
46
+ },
47
+ });
48
+
49
+ // Add a comment
50
+ export const addComment = mutation({
51
+ args: {
52
+ threadId: v.string(),
53
+ userId: v.string(),
54
+ body: v.string()
55
+ },
56
+ handler: async (ctx, args) => {
57
+ return await comments.addComment(ctx, {
58
+ threadId: args.threadId,
59
+ authorId: args.userId,
60
+ body: args.body,
61
+ });
62
+ },
63
+ });
64
+
65
+ // Get messages in a thread
66
+ export const getMessages = query({
67
+ args: { threadId: v.string() },
68
+ handler: async (ctx, args) => {
69
+ return await comments.getMessages(ctx, {
70
+ threadId: args.threadId
71
+ });
72
+ },
73
+ });
74
+ ```
75
+
76
+ **2. Use in React:**
77
+
78
+ ```typescript
79
+ import { useMutation, useQuery } from "convex/react";
80
+ import { api } from "../convex/_generated/api";
81
+
82
+ function CommentThread({ threadId, userId }) {
83
+ const messages = useQuery(api.comments.getMessages, { threadId });
84
+ const addComment = useMutation(api.comments.addComment);
85
+
86
+ return (
87
+ <div>
88
+ {messages?.messages.map((msg) => (
89
+ <div key={msg.message._id}>
90
+ <strong>{msg.message.authorId}:</strong> {msg.message.body}
91
+ </div>
92
+ ))}
93
+ <button onClick={() =>
94
+ addComment({ threadId, userId, body: "Hello!" })
95
+ }>
96
+ Add Comment
97
+ </button>
98
+ </div>
99
+ );
100
+ }
101
+ ```
102
+
103
+ **That's it!** You now have threaded comments with mentions, reactions, and typing indicators available.
104
+
105
+ See below for the complete API reference and React components.
106
+
107
+ ## Data Model
108
+
109
+ The component organizes comments into three levels:
110
+
111
+ - **Zones** - Containers for threads, tied to your entities (documents, tasks, etc.)
112
+ - **Threads** - Groups of messages within a zone
113
+ - **Messages** - Individual comments with mentions, reactions, and attachments
114
+
115
+ Each message can have:
116
+ - Body text with automatic mention and link parsing
117
+ - Attachments (URLs, files, images)
118
+ - Emoji reactions
119
+ - Resolved state
120
+ - Edit history
121
+
122
+ ## Backend Usage
123
+
124
+ ### Method 1: Comments Class
125
+
126
+ The recommended approach. Provides type-safe methods and optional callbacks.
127
+
128
+ ```typescript
129
+ import { Comments } from "@hamzasaleemorg/convex-comments";
130
+ import { components } from "./_generated/api";
131
+ import { mutation, query } from "./_generated/server";
132
+
133
+ const comments = new Comments(components.comments);
134
+
135
+ export const createZone = mutation({
136
+ args: { entityId: v.string() },
137
+ handler: async (ctx, args) => {
138
+ return await comments.getOrCreateZone(ctx, {
139
+ entityId: args.entityId,
140
+ });
141
+ },
142
+ });
143
+
144
+ export const addComment = mutation({
145
+ args: { threadId: v.string(), body: v.string() },
146
+ handler: async (ctx, args) => {
147
+ const userId = await getAuthUserId(ctx);
148
+ return await comments.addComment(ctx, {
149
+ threadId: args.threadId,
150
+ authorId: userId,
151
+ body: args.body,
152
+ });
153
+ },
154
+ });
155
+ ```
156
+
157
+ ### Method 2: Direct Component Calls
158
+
159
+ Call component functions directly through `ctx.runMutation` or `ctx.runQuery`.
160
+
161
+ ```typescript
162
+ import { components } from "./_generated/api";
163
+
164
+ export const addComment = mutation({
165
+ args: { threadId: v.string(), body: v.string() },
166
+ handler: async (ctx, args) => {
167
+ return await ctx.runMutation(components.comments.lib.addComment, {
168
+ threadId: args.threadId,
169
+ authorId: await getAuthUserId(ctx),
170
+ body: args.body,
171
+ });
172
+ },
173
+ });
174
+ ```
175
+
176
+ ### Method 3: Exposed API
177
+
178
+ Generate wrapper functions for frontend use.
179
+
180
+ ```typescript
181
+ import { exposeApi } from "@hamzasaleemorg/convex-comments";
182
+ import { components } from "./_generated/api";
183
+
184
+ export const {
185
+ getThreads,
186
+ addThread,
187
+ addComment,
188
+ toggleReaction,
189
+ setIsTyping,
190
+ getTypingUsers,
191
+ } = exposeApi(components.comments, {
192
+ auth: async (ctx, operation) => {
193
+ return await getAuthUserId(ctx);
194
+ },
195
+ });
196
+ ```
197
+
198
+ ## API Reference
199
+
200
+ ### Zones
201
+
202
+ **getOrCreateZone(ctx, args)**
203
+ - Args: `{ entityId: string, metadata?: any }`
204
+ - Returns: `Id<"zones">`
205
+ - Creates a zone if it doesn't exist, otherwise returns existing zone
206
+
207
+ **getZone(ctx, args)**
208
+ - Args: `{ entityId: string }`
209
+ - Returns: Zone or null
210
+ - Get zone by entity ID
211
+
212
+ **deleteZone(ctx, args)**
213
+ - Args: `{ zoneId: Id<"zones"> }`
214
+ - Deletes zone and all threads/messages within it
215
+
216
+ ### Threads
217
+
218
+ **addThread(ctx, args)**
219
+ - Args: `{ zoneId: Id<"zones">, position?: { x: number, y: number, anchor?: string }, metadata?: any }`
220
+ - Returns: `Id<"threads">`
221
+ - Creates a new thread in the zone
222
+ - The `position` field is **optional** - use it for positioned comments (document editors, design tools, video timestamps). See [#positioned-comments-optional](#positioned-comments-optional) for examples.
223
+
224
+ **getThreads(ctx, args)**
225
+ - Args: `{ zoneId: Id<"zones">, limit?: number, cursor?: string, includeResolved?: boolean }`
226
+ - Returns: `{ threads: Thread[], nextCursor?: string, hasMore: boolean }`
227
+ - Lists threads with first message preview and pagination
228
+
229
+ **resolveThread(ctx, args)**
230
+ - Args: `{ threadId: Id<"threads">, userId: string }`
231
+ - Marks thread as resolved
232
+
233
+ **unresolveThread(ctx, args)**
234
+ - Args: `{ threadId: Id<"threads"> }`
235
+ - Reopens a resolved thread
236
+
237
+ **deleteThread(ctx, args)**
238
+ - Args: `{ threadId: Id<"threads"> }`
239
+ - Deletes thread and all messages
240
+
241
+ ### Messages
242
+
243
+ **addComment(ctx, args)**
244
+ - Args: `{ threadId: Id<"threads">, authorId: string, body: string, attachments?: Attachment[] }`
245
+ - Returns: `{ messageId: Id<"messages">, mentions: Mention[], links: Link[] }`
246
+ - Creates message and parses mentions/links automatically
247
+
248
+ **getMessages(ctx, args)**
249
+ - Args: `{ threadId: Id<"threads">, limit?: number, cursor?: string, currentUserId?: string }`
250
+ - Returns: `{ messages: Message[], nextCursor?: string, hasMore: boolean }`
251
+ - Lists messages with reactions, supports pagination
252
+
253
+ **editMessage(ctx, args)**
254
+ - Args: `{ messageId: Id<"messages">, body: string, authorId?: string }`
255
+ - Updates message body
256
+
257
+ **deleteMessage(ctx, args)**
258
+ - Args: `{ messageId: Id<"messages">, authorId?: string }`
259
+ - Soft deletes message (marks as deleted, preserves data)
260
+
261
+ ### Reactions
262
+
263
+ **toggleReaction(ctx, args)**
264
+ - Args: `{ messageId: Id<"messages">, userId: string, emoji: string }`
265
+ - Returns: `{ added: boolean }`
266
+ - Adds reaction if not present, removes if already exists
267
+
268
+ **addReaction(ctx, args)**
269
+ - Args: `{ messageId: Id<"messages">, userId: string, emoji: string }`
270
+ - Adds reaction (idempotent)
271
+
272
+ **removeReaction(ctx, args)**
273
+ - Args: `{ messageId: Id<"messages">, userId: string, emoji: string }`
274
+ - Removes reaction
275
+
276
+ **getReactions(ctx, args)**
277
+ - Args: `{ messageId: Id<"messages">, currentUserId?: string }`
278
+ - Returns grouped reactions with counts and user lists
279
+
280
+ ### Typing Indicators
281
+
282
+ **setIsTyping(ctx, args)**
283
+ - Args: `{ threadId: Id<"threads">, userId: string, isTyping: boolean }`
284
+ - Sets typing state, automatically expires after 3 seconds
285
+
286
+ **getTypingUsers(ctx, args)**
287
+ - Args: `{ threadId: Id<"threads">, excludeUserId?: string }`
288
+ - Returns list of users currently typing (filters expired)
289
+
290
+ **clearUserTyping(ctx, args)**
291
+ - Args: `{ userId: string }`
292
+ - Clears all typing indicators for user
293
+
294
+ ## React Components
295
+
296
+ Optional UI components for displaying comments.
297
+
298
+ ```typescript
299
+ import {
300
+ CommentsProvider,
301
+ Comments,
302
+ Thread,
303
+ Comment,
304
+ AddComment,
305
+ } from "@hamzasaleemorg/convex-comments/react";
306
+
307
+ function App() {
308
+ return (
309
+ <CommentsProvider
310
+ userId={currentUser.id}
311
+ resolveUser={(id) => ({ name: users[id].name })}
312
+ reactionChoices={["👍", "❤️", "😄", "🎉"]}
313
+ >
314
+ <Comments threads={threads} />
315
+ </CommentsProvider>
316
+ );
317
+ }
318
+ ```
319
+
320
+ ### CommentsProvider
321
+
322
+ Required wrapper that provides configuration to child components.
323
+
324
+ Props:
325
+ - `userId: string | null` - Current user ID
326
+ - `resolveUser?: (userId: string) => Promise<{ name: string, avatar?: string }>` - Function to fetch user details
327
+ - `reactionChoices?: string[]` - Available emoji reactions
328
+ - `canModerate?: boolean` - Whether user can moderate comments
329
+ - `styles?: CommentsStyles` - Custom styling
330
+
331
+ ### Comments
332
+
333
+ Displays list of threads.
334
+
335
+ Props:
336
+ - `threads: Thread[]` - Array of threads to display
337
+ - `hasMore?: boolean` - Whether more threads exist
338
+ - `onThreadClick?: (threadId: string) => void` - Thread click handler
339
+ - `onNewThread?: () => void` - New thread button handler
340
+
341
+ ### Thread
342
+
343
+ Displays single thread with messages.
344
+
345
+ Props:
346
+ - `thread: Thread` - Thread data
347
+ - `messages: Message[]` - Array of messages
348
+ - `typingUsers?: TypingUser[]` - Users currently typing
349
+ - `onSubmit?: (body: string) => void` - Submit handler
350
+ - `onToggleReaction?: (messageId: string, emoji: string) => void`
351
+ - `onResolve?: () => void`
352
+
353
+ ### Comment
354
+
355
+ Displays single message.
356
+
357
+ Props:
358
+ - `comment: Message` - Message data
359
+ - `mine?: boolean` - Whether current user authored message
360
+ - `onToggleReaction?: (emoji: string) => void`
361
+ - `onEdit?: (newBody: string) => void`
362
+ - `onDelete?: () => void`
363
+
364
+ ### AddComment
365
+
366
+ Message composer with mention autocomplete.
367
+
368
+ Props:
369
+ - `onSubmit?: (body: string, attachments?: Attachment[]) => void`
370
+ - `onTypingChange?: (isTyping: boolean) => void`
371
+ - `mentionableUsers?: MentionableUser[]` - Users for autocomplete
372
+ - `placeholder?: string`
373
+ - `allowEditing?: boolean`
374
+
375
+ ## Positioned Comments (Optional)
376
+
377
+ The `position` field in `addThread()` is an **optional feature** for anchoring comments to specific locations. This is useful for:
378
+
379
+ - Document editors (comment on specific paragraphs)
380
+ - Design tools (comment at x/y coordinates on canvas)
381
+ - Video players (comment at specific timestamps)
382
+ - Code review (comment on specific line numbers)
383
+
384
+ ### Position Object
385
+
386
+ ```typescript
387
+ position?: {
388
+ x: number; // X coordinate (pixels, percentage, line number, etc.)
389
+ y: number; // Y coordinate (pixels, percentage, timestamp, etc.)
390
+ anchor?: string; // Optional identifier (element ID, paragraph, filename)
391
+ }
392
+ ```
393
+
394
+ ### When to Use Position
395
+
396
+ **Use positioned comments if:**
397
+ - Your UI needs to show comments at specific visual locations
398
+ - You want to anchor threads to content that can move (paragraphs, code blocks)
399
+ - You're building collaborative editing tools
400
+ - Comments need to appear as overlays or annotations
401
+
402
+ **Skip position if:**
403
+ - You only need general discussions (like GitHub issue comments)
404
+ - All comments appear in a single list/feed
405
+ - Location doesn't matter for your use case
406
+
407
+ ### Examples
408
+
409
+ **Document editor (like Google Docs):**
410
+ ```typescript
411
+ await comments.addThread(ctx, {
412
+ zoneId,
413
+ position: {
414
+ x: 120, // Pixels from left
415
+ y: 450, // Pixels from top
416
+ anchor: "para-3" // Paragraph identifier
417
+ }
418
+ });
419
+ ```
420
+
421
+ **Design tool (like Figma):**
422
+ ```typescript
423
+ await comments.addThread(ctx, {
424
+ zoneId,
425
+ position: {
426
+ x: 500, // Canvas X coordinate
427
+ y: 300, // Canvas Y coordinate
428
+ anchor: "layer-5" // Layer name
429
+ }
430
+ });
431
+ ```
432
+
433
+ **Video player:**
434
+ ```typescript
435
+ await comments.addThread(ctx, {
436
+ zoneId,
437
+ position: {
438
+ x: 0, // Not used for video
439
+ y: 125, // Timestamp in seconds
440
+ anchor: "timecode" // Indicates this is a timestamp
441
+ }
442
+ });
443
+ ```
444
+
445
+ **Code review:**
446
+ ```typescript
447
+ await comments.addThread(ctx, {
448
+ zoneId,
449
+ position: {
450
+ x: 0, // Not used
451
+ y: 42, // Line number
452
+ anchor: "src/main.ts" // File path
453
+ }
454
+ });
455
+ ```
456
+
457
+ ### No Position Needed
458
+
459
+ For simple comment threads (like chat, issue tracking, general discussions), just omit the position field:
460
+
461
+ ```typescript
462
+ // Simple thread without position
463
+ await comments.addThread(ctx, { zoneId });
464
+ ```
465
+
466
+ **Key Point:** The component stores position data but doesn't render it. Your UI decides how to display positioned threads based on the stored coordinates.
467
+
468
+
469
+
470
+ ## Mention Parsing
471
+
472
+ Mentions use `@userId` format and are parsed automatically when creating messages.
473
+
474
+ Supported characters in user IDs:
475
+ - Letters and numbers
476
+ - Underscores, hyphens, colons
477
+
478
+ Examples:
479
+ - `@alice`
480
+ - `@user_123`
481
+ - `@clerk:user_abc`
482
+
483
+ The `addComment` function returns parsed mentions with their positions in the text:
484
+
485
+ ```typescript
486
+ {
487
+ mentions: [
488
+ { userId: "alice", start: 0, end: 6 },
489
+ { userId: "bob", start: 11, end: 15 }
490
+ ]
491
+ }
492
+ ```
493
+
494
+ ## Attachments
495
+
496
+ Messages support attachments with metadata:
497
+
498
+ ```typescript
499
+ await comments.addComment(ctx, {
500
+ threadId: "...",
501
+ authorId: "...",
502
+ body: "Attached files",
503
+ attachments: [
504
+ {
505
+ type: "image",
506
+ url: "https://example.com/image.png",
507
+ name: "Screenshot.png",
508
+ mimeType: "image/png",
509
+ size: 145678,
510
+ },
511
+ {
512
+ type: "file",
513
+ url: "https://example.com/doc.pdf",
514
+ name: "Document.pdf",
515
+ },
516
+ ],
517
+ });
518
+ ```
519
+
520
+ Supported types: `"url"`, `"file"`, `"image"`
521
+
522
+ ## Callbacks
523
+
524
+ The Comments class accepts optional callbacks for notifications:
525
+
526
+ ```typescript
527
+ const comments = new Comments(components.comments, {
528
+ onNewMessage: async ({ messageId, threadId, authorId, body, mentions }) => {
529
+ // Send notification about new message
530
+ },
531
+ onMention: async ({ messageId, mentionedUserId, authorId, body }) => {
532
+ // Send notification to mentioned user
533
+ },
534
+ });
535
+ ```
536
+
537
+ Both callbacks are triggered automatically when messages are created through the Comments class methods.
538
+
539
+ ## HTTP Routes
540
+
541
+ Expose comments data through HTTP endpoints:
542
+
543
+ ```typescript
544
+ // convex/http.ts
545
+ import { httpRouter } from "convex/server";
546
+ import { registerRoutes } from "@hamzasaleemorg/convex-comments";
547
+ import { components } from "./_generated/api";
548
+
549
+ const http = httpRouter();
550
+ registerRoutes(http, components.comments, {
551
+ pathPrefix: "/api/comments",
552
+ });
553
+
554
+ export default http;
555
+ ```
556
+
557
+ Endpoints:
558
+ - `GET /api/comments/zones?entityId=...`
559
+ - `GET /api/comments/threads?zoneId=...`
560
+ - `GET /api/comments/messages?threadId=...`
561
+
562
+ ## Development
563
+
564
+ ```bash
565
+ npm install
566
+ npm run dev
567
+ ```
568
+
569
+ Runs:
570
+ - Component build watcher
571
+ - Example app with Vite and Convex dev
572
+
573
+ ## Testing
574
+
575
+ ```bash
576
+ npm test
577
+ ```
578
+
579
+ ## License
580
+
581
+ Apache-2.0
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=_ignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_ignore.d.ts","sourceRoot":"","sources":["../../../src/client/_generated/_ignore.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // This is only here so convex-test can detect a _generated folder
3
+ //# sourceMappingURL=_ignore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_ignore.js","sourceRoot":"","sources":["../../../src/client/_generated/_ignore.ts"],"names":[],"mappings":";AAAA,kEAAkE"}