From c8dcb1751058129bf299876a33e821fc53de7538 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 7 Feb 2026 22:04:31 +0000 Subject: [PATCH] feat: update card router to send emails on mention --- packages/api/src/routers/card.ts | 41 +++++++++++++++++++++++++++ packages/shared/src/utils/index.ts | 1 + packages/shared/src/utils/mentions.ts | 22 ++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 packages/shared/src/utils/mentions.ts diff --git a/packages/api/src/routers/card.ts b/packages/api/src/routers/card.ts index b39256c5..949804d1 100644 --- a/packages/api/src/routers/card.ts +++ b/packages/api/src/routers/card.ts @@ -10,6 +10,7 @@ import * as workspaceRepo from "@kan/db/repository/workspace.repo"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; import { mergeActivities } from "../utils/activities"; +import { sendMentionEmails } from "../utils/notifications"; import { assertCanDelete, assertCanEdit, assertPermission } from "../utils/permissions"; import { generateAttachmentUrl, generateAvatarUrl } from "@kan/shared/utils"; @@ -153,6 +154,17 @@ export const cardRouter = createTRPCRouter({ await cardActivityRepo.bulkCreate(ctx.db, cardActivitesInsert); } + if (input.description) { + sendMentionEmails({ + db: ctx.db, + cardPublicId: newCard.publicId, + commentHtml: input.description, + commenterUserId: userId, + }).catch((error) => { + console.error("Failed to send mention emails:", error); + }); + } + return newCard; }), addComment: protectedProcedure @@ -215,6 +227,16 @@ export const cardRouter = createTRPCRouter({ createdBy: userId, }); + sendMentionEmails({ + db: ctx.db, + cardPublicId: input.cardPublicId, + commentHtml: input.comment, + commenterUserId: userId, + commentId: newComment.id, + }).catch((error) => { + console.error("Failed to send mention emails:", error); + }); + return newComment; }), updateComment: protectedProcedure @@ -295,6 +317,16 @@ export const cardRouter = createTRPCRouter({ createdBy: userId, }); + sendMentionEmails({ + db: ctx.db, + cardPublicId: input.cardPublicId, + commentHtml: input.comment, + commenterUserId: userId, + commentId: updatedComment.id, + }).catch((error) => { + console.error("Failed to send mention emails:", error); + }); + return updatedComment; }), deleteComment: protectedProcedure @@ -924,6 +956,15 @@ export const cardRouter = createTRPCRouter({ fromDescription: existingCard.description ?? undefined, toDescription: input.description, }); + + sendMentionEmails({ + db: ctx.db, + cardPublicId: input.cardPublicId, + commentHtml: input.description, + commenterUserId: userId, + }).catch((error) => { + console.error("Failed to send mention emails:", error); + }); } if ( diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts index 0e6ab7d1..28c63eb6 100644 --- a/packages/shared/src/utils/index.ts +++ b/packages/shared/src/utils/index.ts @@ -4,3 +4,4 @@ export * from "./subscriptions"; export * from "./email"; export * from "./dueDateFilters"; export * from "./s3"; +export * from "./mentions"; diff --git a/packages/shared/src/utils/mentions.ts b/packages/shared/src/utils/mentions.ts new file mode 100644 index 00000000..42f16088 --- /dev/null +++ b/packages/shared/src/utils/mentions.ts @@ -0,0 +1,22 @@ +/** + * Parses mention data-id attributes from HTML content + * Mentions are stored as: @label + * @param htmlContent - The HTML content to parse + * @returns Array of unique mention public IDs + */ +export function parseMentionsFromHTML(htmlContent: string): string[] { + if (!htmlContent) return []; + + // Match all mention spans with data-id attributes + const mentionRegex = /]*data-type="mention"[^>]*data-id="([^"]+)"[^>]*>/gi; + const matches = Array.from(htmlContent.matchAll(mentionRegex)); + + // Extract unique mention IDs + const mentionIds = matches + .map((match) => match[1]) + .filter((id): id is string => !!id && id.length >= 12); + + // Return unique IDs + return Array.from(new Set(mentionIds)); +} +