Skip to content

Commit

Permalink
make mentions clickable in messages
Browse files Browse the repository at this point in the history
  • Loading branch information
ajbura committed Oct 4, 2023
1 parent c90e60b commit 6a70df1
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 11 deletions.
31 changes: 29 additions & 2 deletions src/app/organisms/room/RoomTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ import {
eventWithShortcode,
factoryEventSentBy,
getMxIdLocalPart,
isRoomAlias,
isRoomId,
isUserId,
matrixEventByRecency,
} from '../../utils/matrix';
import { sanitizeCustomHtml } from '../../utils/sanitize';
Expand Down Expand Up @@ -84,7 +87,7 @@ import {
} from '../../utils/room';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { openProfileViewer } from '../../../client/action/navigation';
import { openJoinAlias, openProfileViewer, selectRoom } from '../../../client/action/navigation';
import { useForceUpdate } from '../../hooks/useForceUpdate';
import { parseGeoUri, scaleYDimension } from '../../utils/common';
import { useMatrixEventRenderer } from '../../hooks/useMatrixEventRenderer';
Expand Down Expand Up @@ -503,7 +506,31 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const [, forceUpdate] = useForceUpdate();

const htmlReactParserOptions = useMemo<HTMLReactParserOptions>(
() => getReactCustomHtmlParser(mx, room),
() =>
getReactCustomHtmlParser(mx, room, {
handleSpoilerClick: (evt) => {
const target = evt.currentTarget;
if (target.getAttribute('aria-pressed') === 'true') {
evt.stopPropagation();
target.setAttribute('aria-pressed', 'false');
target.style.cursor = 'initial';
}
},
handleMentionClick: (evt) => {
const target = evt.currentTarget;
const mentionId = target.getAttribute('data-mention-id');
if (typeof mentionId !== 'string') return;
if (isUserId(mentionId)) {
openProfileViewer(mentionId, room.roomId);
return;
}
if (isRoomId(mentionId) && mx.getRoom(mentionId)) {
selectRoom(mentionId);
return;
}
openJoinAlias(mentionId);
},
}),
[mx, room]
);
const parseMemberEvent = useMemberEventParser();
Expand Down
44 changes: 35 additions & 9 deletions src/app/plugins/react-custom-html-parser.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable jsx-a11y/alt-text */
import React, { Suspense, lazy } from 'react';
import React, { ReactEventHandler, Suspense, lazy } from 'react';
import {
Element,
Text as DOMText,
Expand Down Expand Up @@ -29,7 +29,14 @@ export const LINKIFY_OPTS: LinkifyOpts = {
},
};

export const getReactCustomHtmlParser = (mx: MatrixClient, room: Room): HTMLReactParserOptions => {
export const getReactCustomHtmlParser = (
mx: MatrixClient,
room: Room,
params: {
handleSpoilerClick?: ReactEventHandler<HTMLElement>;
handleMentionClick?: ReactEventHandler<HTMLElement>;
}
): HTMLReactParserOptions => {
const opts: HTMLReactParserOptions = {
replace: (domNode) => {
if (domNode instanceof Element && 'name' in domNode) {
Expand Down Expand Up @@ -179,11 +186,17 @@ export const getReactCustomHtmlParser = (mx: MatrixClient, room: Room): HTMLReac
mentionName && (mentionName.startsWith('#') ? mentionName : `#${mentionName}`);
return (
<span
{...props}
className={css.Mention({
highlight: room.roomId === (mentionRoom?.roomId ?? mentionId),
})}
data-mx-pill={mentionId}
{...props}
data-mention-id={mentionRoom?.roomId ?? mentionId}
data-mention-href={props.href}
role="button"
tabIndex={params.handleMentionClick ? 0 : -1}
onKeyDown={params.handleMentionClick}
onClick={params.handleMentionClick}
style={{ cursor: 'pointer' }}
>
{mentionDisplayName ?? mentionId}
</span>
Expand All @@ -192,11 +205,15 @@ export const getReactCustomHtmlParser = (mx: MatrixClient, room: Room): HTMLReac
if (mentionPrefix === '@')
return (
<span
className={css.Mention({
highlight: mx.getUserId() === mentionId,
})}
data-mx-pill={mentionId}
{...props}
className={css.Mention({ highlight: mx.getUserId() === mentionId })}
data-mention-id={mentionId}
data-mention-href={props.href}
role="button"
tabIndex={params.handleMentionClick ? 0 : -1}
onKeyDown={params.handleMentionClick}
onClick={params.handleMentionClick}
style={{ cursor: 'pointer' }}
>
{`@${getMemberDisplayName(room, mentionId) ?? getMxIdLocalPart(mentionId)}`}
</span>
Expand All @@ -206,7 +223,16 @@ export const getReactCustomHtmlParser = (mx: MatrixClient, room: Room): HTMLReac

if (name === 'span' && 'data-mx-spoiler' in props) {
return (
<span className={css.Spoiler()} {...props}>
<span
{...props}
role="button"
tabIndex={params.handleSpoilerClick ? 0 : -1}
onKeyDown={params.handleSpoilerClick}
onClick={params.handleSpoilerClick}
className={css.Spoiler()}
aria-pressed
style={{ cursor: 'pointer' }}
>
{domToReact(children, opts)}
</span>
);
Expand Down
5 changes: 5 additions & 0 deletions src/app/styles/CustomHtml.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ export const Spoiler = recipe({
padding: `0 ${config.space.S100}`,
backgroundColor: color.SurfaceVariant.ContainerActive,
borderRadius: config.radii.R300,
selectors: {
'&[aria-pressed=true]': {
color: 'transparent',
},
},
},
],
variants: {
Expand Down
4 changes: 4 additions & 0 deletions src/app/utils/matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export const getMxIdLocalPart = (userId: string): string | undefined => matchMxI

export const isUserId = (id: string): boolean => validMxId(id) && id.startsWith('@');

export const isRoomId = (id: string): boolean => validMxId(id) && id.startsWith('!');

export const isRoomAlias = (id: string): boolean => validMxId(id) && id.startsWith('#');

export const getRoomWithCanonicalAlias = (mx: MatrixClient, alias: string): Room | undefined =>
mx.getRooms()?.find((room) => room.getCanonicalAlias() === alias);

Expand Down

0 comments on commit 6a70df1

Please sign in to comment.