-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';
} from '../../plugins/markdown';
import { findAndReplace } from '../../utils/findAndReplace';
import { sanitizeForRegex } from '../../utils/regex';
+import { getCanonicalAliasOrRoomId, isUserId } from '../../utils/matrix';
export type OutputOptions = {
allowTextFormatting?: boolean;
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;
+};
isEmptyEditor,
getBeginCommand,
trimCommand,
+ getMentions,
} from '../../components/editor';
import { EmojiBoard, EmojiBoardTab } from '../../components/emoji-board';
import { UseStateProvider } from '../../components/UseStateProvider';
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';
uploadBoardHandlers.current?.handleSend();
const commandName = getBeginCommand(editor);
-
let plainText = toPlainText(editor.children, isMarkdown).trim();
let customHtml = trimCustomHtml(
toMatrixCustomHTML(editor.children, {
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;
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,
} 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,
toPlainText,
trimCustomHtml,
useEditor,
+ getMentions,
} from '../../../components/editor';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
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 = {
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]);
})
);
- const [prevBody, prevCustomHtml] = getPrevBodyAndFormattedBody();
+ const [prevBody, prevCustomHtml, prevMentions] = getPrevBodyAndFormattedBody();
if (plainText === '') return undefined;
if (prevBody) {
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;
EventTimeline,
EventTimelineSet,
EventType,
+ IMentions,
IPushRule,
IPushRules,
JoinRule,
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;
+};