diff --git a/server/commands/commentUpdater.ts b/server/commands/commentUpdater.ts index 4a78646c2..8d1a42ae3 100644 --- a/server/commands/commentUpdater.ts +++ b/server/commands/commentUpdater.ts @@ -1,5 +1,6 @@ import { Transaction } from "sequelize"; import { Event, Comment, User } from "@server/models"; +import ProsemirrorHelper from "@server/models/helpers/ProsemirrorHelper"; type Props = { /** The user updating the comment */ @@ -29,6 +30,10 @@ export default async function commentUpdater({ ip, transaction, }: Props): Promise { + const mentionIdsBefore = ProsemirrorHelper.parseMentions( + ProsemirrorHelper.toProsemirror(comment.data) + ).map((mention) => mention.id); + if (resolvedBy !== undefined) { comment.resolvedBy = resolvedBy; } @@ -36,6 +41,14 @@ export default async function commentUpdater({ comment.data = data; } + const mentionsAfter = ProsemirrorHelper.parseMentions( + ProsemirrorHelper.toProsemirror(comment.data) + ); + + const newMentionIds = mentionsAfter + .filter((mention) => !mentionIdsBefore.includes(mention.id)) + .map((mention) => mention.id); + await comment.save({ transaction }); await Event.create( @@ -46,6 +59,9 @@ export default async function commentUpdater({ actorId: user.id, documentId: comment.documentId, ip, + data: { + newMentionIds, + }, }, { transaction } ); diff --git a/server/emails/templates/CommentMentionedEmail.tsx b/server/emails/templates/CommentMentionedEmail.tsx new file mode 100644 index 000000000..4515ed130 --- /dev/null +++ b/server/emails/templates/CommentMentionedEmail.tsx @@ -0,0 +1,143 @@ +import inlineCss from "inline-css"; +import * as React from "react"; +import { NotificationEventType } from "@shared/types"; +import env from "@server/env"; +import { Document, User } from "@server/models"; +import NotificationSettingsHelper from "@server/models/helpers/NotificationSettingsHelper"; +import BaseEmail, { EmailProps } from "./BaseEmail"; +import Body from "./components/Body"; +import Button from "./components/Button"; +import Diff from "./components/Diff"; +import EmailTemplate from "./components/EmailLayout"; +import EmptySpace from "./components/EmptySpace"; +import Footer from "./components/Footer"; +import Header from "./components/Header"; +import Heading from "./components/Heading"; + +type InputProps = EmailProps & { + userId: string; + documentId: string; + actorName: string; + commentId: string; + collectionName: string | undefined; + teamUrl: string; + content: string; +}; + +type BeforeSend = { + document: Document; + body: string | undefined; + unsubscribeUrl: string; +}; + +type Props = InputProps & BeforeSend; + +/** + * Email sent to a user when a new comment is created in a document they are + * subscribed to. + */ +export default class CommentMentionedEmail extends BaseEmail< + InputProps, + BeforeSend +> { + protected async beforeSend({ documentId, userId, content }: InputProps) { + const document = await Document.unscoped().findByPk(documentId); + if (!document) { + return false; + } + + const user = await User.findByPk(userId); + if (!user) { + return false; + } + + // inline all css so that it works in as many email providers as possible. + let body; + if (content) { + body = await inlineCss(content, { + url: env.URL, + applyStyleTags: true, + applyLinkTags: false, + removeStyleTags: true, + }); + } + + return { + document, + body, + unsubscribeUrl: NotificationSettingsHelper.unsubscribeUrl( + user, + NotificationEventType.Mentioned + ), + }; + } + + protected subject({ actorName, document }: Props) { + return `${actorName} mentioned you in “${document.title}”`; + } + + protected preview({ actorName }: Props): string { + return `${actorName} mentioned you in a thread`; + } + + protected fromName({ actorName }: Props): string { + return actorName; + } + + protected renderAsText({ + actorName, + teamUrl, + document, + commentId, + collectionName, + }: Props): string { + return ` +${actorName} mentioned you in a comment on "${document.title}"${ + collectionName ? `in the ${collectionName} collection` : "" + }. + +Open Thread: ${teamUrl}${document.url}?commentId=${commentId} +`; + } + + protected render({ + document, + actorName, + collectionName, + teamUrl, + commentId, + unsubscribeUrl, + body, + }: Props) { + const link = `${teamUrl}${document.url}?commentId=${commentId}&ref=notification-email`; + + return ( + +
+ + + {document.title} +

+ {actorName} mentioned you in a comment on{" "} + {document.title}{" "} + {collectionName ? `in the ${collectionName} collection` : ""}. +

+ {body && ( + <> + + +
+ + + + )} +

+ +

+ + +