Skip to content

Commit

Permalink
Remove fallback replies & implement intentional mentions (#2138)
Browse files Browse the repository at this point in the history
* Remove reply fallbacks & add m.mentions

(WIP) the typing on line 301 and 303 needs fixing but apart from that this is mint

* Less jank typing

* Mention the reply author in m.mentions

* Improve typing

* Fix typing in m.mentions finder

* Correctly iterate through editor children, properly handle @room, ...

..., don't mention the reply author when the reply author is ourself, don't add own user IDs when mentioning intentionally

* Formatting

* Add intentional mentions to edited messages

* refactor reusable code and fix todo

* parse mentions from all nodes

---------

Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
  • Loading branch information
nexy7574 and ajbura authored Feb 23, 2025
1 parent dd4c1a9 commit 8d95758
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 28 deletions.
38 changes: 36 additions & 2 deletions src/app/components/editor/output.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Descendant, Text } from 'slate';

import { Descendant, Editor, Text } from 'slate';
import { MatrixClient } from 'matrix-js-sdk';
import { sanitizeText } from '../../utils/sanitize';
import { BlockType } from './types';
import { CustomElement } from './slate';
Expand All @@ -11,6 +11,7 @@ import {
} from '../../plugins/markdown';
import { findAndReplace } from '../../utils/findAndReplace';
import { sanitizeForRegex } from '../../utils/regex';
import { getCanonicalAliasOrRoomId, isUserId } from '../../utils/matrix';

export type OutputOptions = {
allowTextFormatting?: boolean;
Expand Down Expand Up @@ -195,3 +196,36 @@ export const trimCommand = (cmdName: string, str: string) => {
if (!match) return str;
return str.slice(match[0].length);
};

export type MentionsData = {
room: boolean;
users: Set<string>;
};
export const getMentions = (mx: MatrixClient, roomId: string, editor: Editor): MentionsData => {
const mentionData: MentionsData = {
room: false,
users: new Set(),
};

const parseMentions = (node: Descendant): void => {
if (Text.isText(node)) return;
if (node.type === BlockType.CodeBlock) return;

if (node.type === BlockType.Mention) {
if (node.id === getCanonicalAliasOrRoomId(mx, roomId)) {
mentionData.room = true;
}
if (isUserId(node.id) && node.id !== mx.getUserId()) {
mentionData.users.add(node.id);
}

return;
}

node.children.forEach(parseMentions);
};

editor.children.forEach(parseMentions);

return mentionData;
};
32 changes: 13 additions & 19 deletions src/app/features/room/RoomInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
isEmptyEditor,
getBeginCommand,
trimCommand,
getMentions,
} from '../../components/editor';
import { EmojiBoard, EmojiBoardTab } from '../../components/emoji-board';
import { UseStateProvider } from '../../components/UseStateProvider';
Expand Down Expand Up @@ -102,12 +103,9 @@ import colorMXID from '../../../util/colorMXID';
import {
getAllParents,
getMemberDisplayName,
parseReplyBody,
parseReplyFormattedBody,
getMentionContent,
trimReplyFromBody,
trimReplyFromFormattedBody,
} from '../../utils/room';
import { sanitizeText } from '../../utils/sanitize';
import { CommandAutocomplete } from './CommandAutocomplete';
import { Command, SHRUG, TABLEFLIP, UNFLIP, useCommands } from '../../hooks/useCommands';
import { mobileOrTablet } from '../../utils/user-agent';
Expand Down Expand Up @@ -268,7 +266,6 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
uploadBoardHandlers.current?.handleSend();

const commandName = getBeginCommand(editor);

let plainText = toPlainText(editor.children, isMarkdown).trim();
let customHtml = trimCustomHtml(
toMatrixCustomHTML(editor.children, {
Expand Down Expand Up @@ -309,25 +306,22 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(

if (plainText === '') return;

let body = plainText;
let formattedBody = customHtml;
if (replyDraft) {
body = parseReplyBody(replyDraft.userId, trimReplyFromBody(replyDraft.body)) + body;
formattedBody =
parseReplyFormattedBody(
roomId,
replyDraft.userId,
replyDraft.eventId,
replyDraft.formattedBody
? trimReplyFromFormattedBody(replyDraft.formattedBody)
: sanitizeText(replyDraft.body)
) + formattedBody;
}
const body = plainText;
const formattedBody = customHtml;
const mentionData = getMentions(mx, roomId, editor);

const content: IContent = {
msgtype: msgType,
body,
};

if (replyDraft && replyDraft.userId !== mx.getUserId()) {
mentionData.users.add(replyDraft.userId);
}

const mMentions = getMentionContent(Array.from(mentionData.users), mentionData.room);
content['m.mentions'] = mMentions;

if (replyDraft || !customHtmlEqualsPlainText(formattedBody, body)) {
content.format = 'org.matrix.custom.html';
content.formatted_body = formattedBody;
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/room/message/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { useHover, useFocusWithin } from 'react-aria';
import { MatrixEvent, Room } from 'matrix-js-sdk';
import { Relations } from 'matrix-js-sdk/lib/models/relations';
import classNames from 'classnames';
import { EventType, RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types';
import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types';
import {
AvatarBase,
BubbleLayout,
Expand Down
26 changes: 20 additions & 6 deletions src/app/features/room/message/MessageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from 'folds';
import { Editor, Transforms } from 'slate';
import { ReactEditor } from 'slate-react';
import { IContent, MatrixEvent, RelationType, Room } from 'matrix-js-sdk';
import { IContent, IMentions, MatrixEvent, RelationType, Room } from 'matrix-js-sdk';
import { isKeyHotkey } from 'is-hotkey';
import {
AUTOCOMPLETE_PREFIXES,
Expand All @@ -43,14 +43,15 @@ import {
toPlainText,
trimCustomHtml,
useEditor,
getMentions,
} from '../../../components/editor';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import { UseStateProvider } from '../../../components/UseStateProvider';
import { EmojiBoard } from '../../../components/emoji-board';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getEditedEvent, trimReplyFromFormattedBody } from '../../../utils/room';
import { getEditedEvent, getMentionContent, trimReplyFromFormattedBody } from '../../../utils/room';
import { mobileOrTablet } from '../../../utils/user-agent';

type MessageEditorProps = {
Expand All @@ -74,19 +75,23 @@ export const MessageEditor = as<'div', MessageEditorProps>(

const getPrevBodyAndFormattedBody = useCallback((): [
string | undefined,
string | undefined
string | undefined,
IMentions | undefined
] => {
const evtId = mEvent.getId()!;
const evtTimeline = room.getTimelineForEvent(evtId);
const editedEvent =
evtTimeline && getEditedEvent(evtId, mEvent, evtTimeline.getTimelineSet());

const { body, formatted_body: customHtml }: Record<string, unknown> =
editedEvent?.getContent()['m.new_content'] ?? mEvent.getContent();
const content: IContent = editedEvent?.getContent()['m.new_content'] ?? mEvent.getContent();
const { body, formatted_body: customHtml }: Record<string, unknown> = content;

const mMentions: IMentions | undefined = content['m.mentions'];

return [
typeof body === 'string' ? body : undefined,
typeof customHtml === 'string' ? customHtml : undefined,
mMentions,
];
}, [room, mEvent]);

Expand All @@ -101,7 +106,7 @@ export const MessageEditor = as<'div', MessageEditorProps>(
})
);

const [prevBody, prevCustomHtml] = getPrevBodyAndFormattedBody();
const [prevBody, prevCustomHtml, prevMentions] = getPrevBodyAndFormattedBody();

if (plainText === '') return undefined;
if (prevBody) {
Expand All @@ -122,6 +127,15 @@ export const MessageEditor = as<'div', MessageEditorProps>(
body: plainText,
};

const mentionData = getMentions(mx, roomId, editor);

prevMentions?.user_ids?.forEach((prevMentionId) => {
mentionData.users.add(prevMentionId);
});

const mMentions = getMentionContent(Array.from(mentionData.users), mentionData.room);
newContent['m.mentions'] = mMentions;

if (!customHtmlEqualsPlainText(customHtml, plainText)) {
newContent.format = 'org.matrix.custom.html';
newContent.formatted_body = customHtml;
Expand Down
13 changes: 13 additions & 0 deletions src/app/utils/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
EventTimeline,
EventTimelineSet,
EventType,
IMentions,
IPushRule,
IPushRules,
JoinRule,
Expand Down Expand Up @@ -430,3 +431,15 @@ export const getLatestEditableEvt = (
export const reactionOrEditEvent = (mEvent: MatrixEvent) =>
mEvent.getRelation()?.rel_type === RelationType.Annotation ||
mEvent.getRelation()?.rel_type === RelationType.Replace;

export const getMentionContent = (userIds: string[], room: boolean): IMentions => {
const mMentions: IMentions = {};
if (userIds.length > 0) {
mMentions.user_ids = userIds;
}
if (room) {
mMentions.room = true;
}

return mMentions;
};

0 comments on commit 8d95758

Please sign in to comment.