From eb0c981d3ca85a888633b65b6d22df40982b91a7 Mon Sep 17 00:00:00 2001
From: gosangam
Date: Sat, 13 Jun 2026 13:10:34 +0530
Subject: [PATCH] feat: add inline mentions in comments
---
app/(app)/notifications/page.tsx | 6 +-
.../notifications/notifications-provider.tsx | 2 +
components/social/comment/comment-row.tsx | 23 +-
.../social/comment/post-comments-drawer.tsx | 14 +-
components/social/mention-textarea.tsx | 238 ++++++++++++++++++
5 files changed, 272 insertions(+), 11 deletions(-)
create mode 100644 components/social/mention-textarea.tsx
diff --git a/app/(app)/notifications/page.tsx b/app/(app)/notifications/page.tsx
index f890f278..bab81e83 100644
--- a/app/(app)/notifications/page.tsx
+++ b/app/(app)/notifications/page.tsx
@@ -145,7 +145,9 @@ function NotificationRow({
? `/u/${selfHandle}/posts#post-${n.post_id}`
: n.kind === "post_mention" && n.post_id && n.actor_handle
? `/u/${n.actor_handle}/posts#post-${n.post_id}`
- : (n.kind === "post_comment" || n.kind === "comment_reply") &&
+ : (n.kind === "post_comment" ||
+ n.kind === "comment_reply" ||
+ n.kind === "comment_mention") &&
n.post_id &&
n.post_author_handle
? `/u/${n.post_author_handle}/posts#post-${n.post_id}`
@@ -190,6 +192,8 @@ function messageFor(n: AppNotification): string {
return "commented on your post.";
case "comment_reply":
return "replied to your comment.";
+ case "comment_mention":
+ return "mentioned you in a comment.";
default:
return "sent you a notification.";
}
diff --git a/components/notifications/notifications-provider.tsx b/components/notifications/notifications-provider.tsx
index a3ed8e08..d80ca185 100644
--- a/components/notifications/notifications-provider.tsx
+++ b/components/notifications/notifications-provider.tsx
@@ -67,6 +67,8 @@ function messageFor(e: IncomingEvent): string {
return `${who} commented on your post`;
case "comment_reply":
return `${who} replied to your comment`;
+ case "comment_mention":
+ return `${who} mentioned you in a comment`;
default:
return "You have a new notification";
}
diff --git a/components/social/comment/comment-row.tsx b/components/social/comment/comment-row.tsx
index 689910a6..f5efddfc 100644
--- a/components/social/comment/comment-row.tsx
+++ b/components/social/comment/comment-row.tsx
@@ -11,6 +11,7 @@
*/
import { useEffect, useRef, useState } from "react";
+import Link from "next/link";
import { HiHeart, HiOutlineHeart } from "react-icons/hi2";
import { Pin } from "lucide-react";
import { toast } from "sonner";
@@ -20,6 +21,26 @@ import { UserAvatar } from "@/components/ui/user-avatar";
import { timeAgo } from "@/lib/time";
import { CommentMenu } from "./comment-menu";
+// Split a comment body into text + clickable @handle links. The capturing
+// group keeps the delimiters so they can be turned into profile links.
+const MENTION_TOKEN = /(@[A-Za-z0-9_]+)/g;
+function renderBody(text: string) {
+ return text.split(MENTION_TOKEN).map((part, i) => {
+ if (part.length > 1 && part[0] === "@") {
+ return (
+
+ {part}
+
+ );
+ }
+ return part;
+ });
+}
+
export function CommentRow({
comment,
viewerId,
@@ -151,7 +172,7 @@ export function CommentRow({
@{comment.reply_to_handle}{" "}
) : null}
- {comment.body}
+ {renderBody(comment.body)}
{/* Inline action row */}
diff --git a/components/social/comment/post-comments-drawer.tsx b/components/social/comment/post-comments-drawer.tsx
index edd0ca7d..d9eb5a75 100644
--- a/components/social/comment/post-comments-drawer.tsx
+++ b/components/social/comment/post-comments-drawer.tsx
@@ -37,6 +37,7 @@ import { useMediaQuery } from "@/components/selection-toolbar/shared";
import { Drawer, DrawerContent } from "@/components/ui/drawer";
import { ConfirmModal } from "@/components/ui/confirm-modal";
import { EmojiPicker } from "@/components/ui/emoji-picker";
+import { MentionTextarea } from "@/components/social/mention-textarea";
import { CommentRow } from "./comment-row";
import { ReportCommentModal } from "./comment-report-modal";
@@ -324,20 +325,15 @@ export function PostCommentsDrawer({
) : null}