diff --git a/.changeset/pr465-reply-mention-indicator.md b/.changeset/pr465-reply-mention-indicator.md
new file mode 100644
index 000000000..dbc5c876a
--- /dev/null
+++ b/.changeset/pr465-reply-mention-indicator.md
@@ -0,0 +1,5 @@
+---
+default: minor
+---
+
+Replies that mention the OP are now indicated by the OP username being prefixed with @
diff --git a/src/app/components/message/Reply.tsx b/src/app/components/message/Reply.tsx
index 5ae0ebdec..f67db051f 100644
--- a/src/app/components/message/Reply.tsx
+++ b/src/app/components/message/Reply.tsx
@@ -1,5 +1,5 @@
import { Box, Chip, Icon, IconSrc, Icons, Text, as, color, toRem } from 'folds';
-import { EventTimelineSet, Room, SessionMembershipData } from '$types/matrix-sdk';
+import { EventTimelineSet, IMentions, Room, SessionMembershipData } from '$types/matrix-sdk';
import { MouseEventHandler, ReactNode, useCallback, useMemo } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import classNames from 'classnames';
@@ -40,9 +40,10 @@ type ReplyLayoutProps = {
userColor?: string;
username?: ReactNode;
icon?: IconSrc;
+ mentioned: boolean;
};
export const ReplyLayout = as<'div', ReplyLayoutProps>(
- ({ username, userColor, icon, className, children, ...props }, ref) => (
+ ({ username, userColor, icon, className, mentioned, children, ...props }, ref) => (
(
{!!icon && }
+ {mentioned && }
{username}
@@ -83,11 +85,12 @@ type ReplyProps = {
timelineSet?: EventTimelineSet;
replyEventId: string;
threadRootId?: string;
+ mentions?: IMentions;
onClick?: MouseEventHandler;
};
export const Reply = as<'div', ReplyProps>(
- ({ room, timelineSet, replyEventId, threadRootId, onClick, ...props }, ref) => {
+ ({ room, timelineSet, replyEventId, threadRootId, mentions, onClick, ...props }, ref) => {
const placeholderWidth = useMemo(() => randomNumberBetween(40, 400), []);
const getFromLocalTimeline = useCallback(
() => timelineSet?.findEventById(replyEventId),
@@ -131,6 +134,7 @@ export const Reply = as<'div', ReplyProps>(
let bodyJSX: ReactNode = fallbackBody;
let image: IconSrc | undefined;
+ let mentioned = sender != null && (mentions?.user_ids?.includes(sender) ?? false);
const replyLinkifyOpts = useMemo(
() => ({
@@ -169,6 +173,7 @@ export const Reply = as<'div', ReplyProps>(
} else if (eventType === StateEvent.RoomMember && !!replyEvent) {
const parsedMemberEvent = parseMemberEvent(replyEvent);
image = parsedMemberEvent.icon;
+ mentioned = false;
bodyJSX = parsedMemberEvent.body;
} else if (eventType === StateEvent.RoomName) {
image = Icons.Hash;
@@ -202,6 +207,7 @@ export const Reply = as<'div', ReplyProps>(
as="button"
userColor={usernameColor}
icon={image}
+ mentioned={mentioned}
username={
sender &&
eventType !== StateEvent.RoomMember && (
diff --git a/src/app/features/message-search/SearchResultGroup.tsx b/src/app/features/message-search/SearchResultGroup.tsx
index 86a67553c..7e4bcf533 100644
--- a/src/app/features/message-search/SearchResultGroup.tsx
+++ b/src/app/features/message-search/SearchResultGroup.tsx
@@ -323,6 +323,7 @@ export function SearchResultGroup({
room={room}
replyEventId={replyEventId}
threadRootId={threadRootId}
+ mentions={event.content['m.mentions']}
onClick={handleOpenClick}
/>
)}
diff --git a/src/app/features/room/ThreadBrowser.tsx b/src/app/features/room/ThreadBrowser.tsx
index 2762cb688..7c1e830f0 100644
--- a/src/app/features/room/ThreadBrowser.tsx
+++ b/src/app/features/room/ThreadBrowser.tsx
@@ -185,6 +185,7 @@ function ThreadPreview({ room, thread, onClick }: ThreadPreviewProps) {
room={room}
replyEventId={rootEvent.replyEventId}
threadRootId={rootEvent.threadRootId}
+ mentions={rootEvent.getContent()['m.mentions']}
onClick={handleJumpClick}
/>
)}
diff --git a/src/app/features/room/ThreadDrawer.tsx b/src/app/features/room/ThreadDrawer.tsx
index d7a1fe63a..163d40ff0 100644
--- a/src/app/features/room/ThreadDrawer.tsx
+++ b/src/app/features/room/ThreadDrawer.tsx
@@ -255,6 +255,7 @@ function ThreadMessage({
room={room}
timelineSet={timelineSet}
replyEventId={replyEventId}
+ mentions={baseContent['m.mentions']}
onClick={onReferenceClick}
/>
)
diff --git a/src/app/features/room/room-pin-menu/RoomPinMenu.tsx b/src/app/features/room/room-pin-menu/RoomPinMenu.tsx
index d05284da0..9762f216a 100644
--- a/src/app/features/room/room-pin-menu/RoomPinMenu.tsx
+++ b/src/app/features/room/room-pin-menu/RoomPinMenu.tsx
@@ -134,6 +134,7 @@ function PinnedMessageActiveContent(
getMemberDisplayName(room, sender, nicknames) ?? getMxIdLocalPart(sender) ?? sender;
const senderAvatarMxc = getMemberAvatarMxc(room, sender);
const getContent = (() => pinnedEvent.getContent()) as GetContentCallback;
+ const content = pinnedEvent.getContent();
const memberPowerTag = getMemberPowerTag(sender);
const tagIconSrc = memberPowerTag?.icon
@@ -183,6 +184,7 @@ function PinnedMessageActiveContent(
room={room}
replyEventId={pinnedEvent.replyEventId}
threadRootId={pinnedEvent.threadRootId}
+ mentions={content['m.mentions']}
onClick={handleOpenClick}
/>
)}
diff --git a/src/app/hooks/timeline/useTimelineEventRenderer.tsx b/src/app/hooks/timeline/useTimelineEventRenderer.tsx
index eb525e818..86e703452 100644
--- a/src/app/hooks/timeline/useTimelineEventRenderer.tsx
+++ b/src/app/hooks/timeline/useTimelineEventRenderer.tsx
@@ -7,6 +7,7 @@ import {
Room,
PushProcessor,
EventTimelineSet,
+ IContent,
} from '$types/matrix-sdk';
import { SessionMembershipData } from 'matrix-js-sdk/lib/matrixrtc/CallMembership';
import { HTMLReactParserOptions } from 'html-react-parser';
@@ -294,10 +295,10 @@ export function useTimelineEventRenderer({
editedNewContent = getEditedContent.call(editedEvent)['m.new_content'];
}
- const baseContent = (getEventContent.call(mEvent) || {}) as Record;
+ const baseContent = getEventContent.call(mEvent) || {};
const safeContent = (
Object.keys(baseContent).length > 0 ? baseContent : getOriginalContent.call(mEvent)
- ) as Record;
+ ) as IContent;
const getContent = (() => editedNewContent ?? safeContent) as GetContentCallback;
@@ -364,6 +365,7 @@ export function useTimelineEventRenderer({
timelineSet={timelineSet}
replyEventId={replyEventId}
threadRootId={threadRootId}
+ mentions={baseContent['m.mentions']}
onClick={handleOpenReply}
/>
)
@@ -609,6 +611,7 @@ export function useTimelineEventRenderer({
const senderId = getSender.call(mEvent) ?? '';
const senderDisplayName =
getMemberDisplayName(room, senderId, nicknames) ?? getMxIdLocalPart(senderId) ?? senderId;
+ const content = getEventContent.call(mEvent) ?? {};
return (
)
diff --git a/src/app/pages/client/inbox/Notifications.tsx b/src/app/pages/client/inbox/Notifications.tsx
index 7543720d0..dc3d35297 100644
--- a/src/app/pages/client/inbox/Notifications.tsx
+++ b/src/app/pages/client/inbox/Notifications.tsx
@@ -468,6 +468,9 @@ function RoomNotificationsGroupComp({
const replyEventId = relation?.['m.in_reply_to']?.event_id;
const threadRootId =
relation?.rel_type === RelationType.Thread ? relation.event_id : undefined;
+ // doesn't work for encrypted rooms
+ // not a big deal really, don't want to bother with finding the event by id and decrypting
+ const mentions = event.content['m.mentions'];
const memberPowerTag = getMemberPowerTag(event.sender);
const tagColor = memberPowerTag?.color
@@ -543,6 +546,7 @@ function RoomNotificationsGroupComp({
room={room}
replyEventId={replyEventId}
threadRootId={threadRootId}
+ mentions={mentions}
onClick={handleOpenClick}
/>
)}