Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pr465-reply-mention-indicator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: minor
---

Replies that mention the OP are now indicated by the OP username being prefixed with @
12 changes: 9 additions & 3 deletions src/app/components/message/Reply.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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) => (
<Box
className={classNames(css.Reply, className)}
alignItems="Center"
Expand All @@ -55,6 +56,7 @@ export const ReplyLayout = as<'div', ReplyLayoutProps>(
</Box>
{!!icon && <Icon style={{ opacity: 0.6 }} size="50" src={icon} />}
<Box style={{ color: userColor, maxWidth: toRem(200) }} alignItems="Center" shrink="No">
{mentioned && <Icon size="100" src={Icons.Mention} />}
{username}
</Box>
<Box grow="Yes" className={css.ReplyContent}>
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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(
() => ({
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -202,6 +207,7 @@ export const Reply = as<'div', ReplyProps>(
as="button"
userColor={usernameColor}
icon={image}
mentioned={mentioned}
username={
sender &&
eventType !== StateEvent.RoomMember && (
Expand Down
1 change: 1 addition & 0 deletions src/app/features/message-search/SearchResultGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ export function SearchResultGroup({
room={room}
replyEventId={replyEventId}
threadRootId={threadRootId}
mentions={event.content['m.mentions']}
onClick={handleOpenClick}
/>
)}
Expand Down
1 change: 1 addition & 0 deletions src/app/features/room/ThreadBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
/>
)}
Expand Down
1 change: 1 addition & 0 deletions src/app/features/room/ThreadDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ function ThreadMessage({
room={room}
timelineSet={timelineSet}
replyEventId={replyEventId}
mentions={baseContent['m.mentions']}
onClick={onReferenceClick}
/>
)
Expand Down
2 changes: 2 additions & 0 deletions src/app/features/room/room-pin-menu/RoomPinMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -183,6 +184,7 @@ function PinnedMessageActiveContent(
room={room}
replyEventId={pinnedEvent.replyEventId}
threadRootId={pinnedEvent.threadRootId}
mentions={content['m.mentions']}
onClick={handleOpenClick}
/>
)}
Expand Down
8 changes: 6 additions & 2 deletions src/app/hooks/timeline/useTimelineEventRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -294,10 +295,10 @@ export function useTimelineEventRenderer({
editedNewContent = getEditedContent.call(editedEvent)['m.new_content'];
}

const baseContent = (getEventContent.call(mEvent) || {}) as Record<string, any>;
const baseContent = getEventContent.call(mEvent) || {};
const safeContent = (
Object.keys(baseContent).length > 0 ? baseContent : getOriginalContent.call(mEvent)
) as Record<string, any>;
) as IContent;

const getContent = (() => editedNewContent ?? safeContent) as GetContentCallback;

Expand Down Expand Up @@ -364,6 +365,7 @@ export function useTimelineEventRenderer({
timelineSet={timelineSet}
replyEventId={replyEventId}
threadRootId={threadRootId}
mentions={baseContent['m.mentions']}
onClick={handleOpenReply}
/>
)
Expand Down Expand Up @@ -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 (
<Message
Expand Down Expand Up @@ -643,6 +646,7 @@ export function useTimelineEventRenderer({
timelineSet={timelineSet}
replyEventId={replyEventId}
threadRootId={threadRootId}
mentions={content['m.mentions']}
onClick={handleOpenReply}
/>
)
Expand Down
4 changes: 4 additions & 0 deletions src/app/pages/client/inbox/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -543,6 +546,7 @@ function RoomNotificationsGroupComp({
room={room}
replyEventId={replyEventId}
threadRootId={threadRootId}
mentions={mentions}
onClick={handleOpenClick}
/>
)}
Expand Down
Loading