diff --git a/package.json b/package.json
index bfdbacfdea..d654419108 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"lodash.uniqby": "^4.7.0",
- "mdast-util-find-and-replace": "1.1.1",
+ "mdast-util-find-and-replace": "^2.2.1",
"nanoid": "^3.3.4",
"pretty-bytes": "^5.4.1",
"prop-types": "^15.7.2",
@@ -103,6 +103,7 @@
"@types/lodash.isequal": "^4.5.5",
"@types/lodash.throttle": "^4.1.6",
"@types/lodash.uniqby": "^4.7.6",
+ "@types/mdast": "^3.0.10",
"@types/moment": "^2.13.0",
"@types/react": "^18.0.8",
"@types/react-dom": "^18.0.3",
@@ -208,7 +209,8 @@
"e2e-container": "./e2e/scripts/run_in_container.sh"
},
"resolutions": {
- "ast-types": "^0.14.0"
+ "ast-types": "^0.14.0",
+ "@types/unist": "^2.0.6"
},
"browserslist": [
">0.2%",
diff --git a/src/__tests__/__snapshots__/utils.test.js.snap b/src/__tests__/__snapshots__/utils.test.js.snap
new file mode 100644
index 0000000000..8a80dc0894
--- /dev/null
+++ b/src/__tests__/__snapshots__/utils.test.js.snap
@@ -0,0 +1,142 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renderText handles the special case where user name matches to an e-mail pattern - 1 1`] = `
+
+ Hello
+
+ @username@email.com
+
+ , is
+
+ username@email.com
+
+ your @primary e-mail?
+
+`;
+
+exports[`renderText handles the special case where user name matches to an e-mail pattern - 2 1`] = `
+
+
+ username@email.com
+
+
+
+ @username@email.com
+
+ is this the right address?
+
+`;
+
+exports[`renderText handles the special case where user name matches to an e-mail pattern - 3 1`] = `
+
+
+ @username@email.com
+
+
+
+ @username@email.com
+
+
+
+ @username@email.com
+
+
+
+ @username@email.com
+
+
+`;
+
+exports[`renderText handles the special case where user name matches to an e-mail pattern - 4 1`] = `
+
+
+ @username@email.com
+
+
+
+ @username@email.com
+
+
+
+ username@email.com
+
+
+
+ @username@email.com
+
+
+`;
+
+exports[`renderText renders custom mention 1`] = `
+
+
+ @username@email.com
+
+
+
+ @username@email.com
+
+
+
+ username@email.com
+
+
+
+ @username@email.com
+
+
+`;
+
+exports[`renderText renders standard markdown text 1`] = `
+
+ Hi, shall we meet on
+
+ Tuesday
+
+ ?
+
+`;
diff --git a/src/__tests__/utils.test.js b/src/__tests__/utils.test.js
new file mode 100644
index 0000000000..2ecf2d43e3
--- /dev/null
+++ b/src/__tests__/utils.test.js
@@ -0,0 +1,63 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { renderText } from '../utils';
+
+describe(`renderText`, () => {
+ it('handles the special case where user name matches to an e-mail pattern - 1', () => {
+ const Markdown = renderText(
+ 'Hello @username@email.com, is username@email.com your @primary e-mail?',
+ [{ id: 'id-username@email.com', name: 'username@email.com' }],
+ );
+ const tree = renderer.create(Markdown).toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ it('handles the special case where user name matches to an e-mail pattern - 2', () => {
+ const Markdown = renderText(
+ 'username@email.com @username@email.com is this the right address?',
+ [{ id: 'id-username@email.com', name: 'username@email.com' }],
+ );
+ const tree = renderer.create(Markdown).toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ it('handles the special case where user name matches to an e-mail pattern - 3', () => {
+ const Markdown = renderText(
+ '@username@email.com @username@email.com @username@email.com @username@email.com',
+ [{ id: 'id-username@email.com', name: 'username@email.com' }],
+ );
+ const tree = renderer.create(Markdown).toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ it('handles the special case where user name matches to an e-mail pattern - 4', () => {
+ const Markdown = renderText(
+ '@username@email.com @username@email.com username@email.com @username@email.com',
+ [{ id: 'id-username@email.com', name: 'username@email.com' }],
+ );
+ const tree = renderer.create(Markdown).toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ it('renders custom mention', () => {
+ const Markdown = renderText(
+ '@username@email.com @username@email.com username@email.com @username@email.com',
+ [{ id: 'id-username@email.com', name: 'username@email.com' }],
+ {
+ customMarkDownRenderers: {
+ mention: function MyMention(props) {
+ return {props.children};
+ },
+ },
+ },
+ );
+ const tree = renderer.create(Markdown).toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ it('renders standard markdown text', () => {
+ const Markdown = renderText('Hi, shall we meet on **Tuesday**?', []);
+ const tree = renderer.create(Markdown).toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/src/utils.tsx b/src/utils.tsx
index d534aced8b..628dfb4f6b 100644
--- a/src/utils.tsx
+++ b/src/utils.tsx
@@ -2,14 +2,13 @@ import React, { PropsWithChildren } from 'react';
import emojiRegex from 'emoji-regex';
import * as linkify from 'linkifyjs';
import { nanoid } from 'nanoid';
-//@ts-expect-error
-import findAndReplace from 'mdast-util-find-and-replace';
+import { findAndReplace, ReplaceFunction } from 'mdast-util-find-and-replace';
import RootReactMarkdown, { NodeType } from 'react-markdown';
import ReactMarkdown from 'react-markdown/with-html';
import uniqBy from 'lodash.uniqby';
import type { UserResponse } from 'stream-chat';
-
+import type { Root } from 'mdast';
import type { DefaultStreamChatGenerics } from './types/types';
export const isOnlyEmojis = (text?: string) => {
@@ -119,7 +118,7 @@ export const emojiMarkdownPlugin = () => {
}
const transform = (markdownAST: T) => {
- findAndReplace(markdownAST, emojiRegex(), replace);
+ findAndReplace(markdownAST as Root, emojiRegex(), replace as ReplaceFunction);
return markdownAST;
};
@@ -156,7 +155,7 @@ export const mentionsMarkdownPlugin = <
mentioned_usernames.map((username) => `@${username}`).join('|'),
'g',
);
- findAndReplace(markdownAST, mentionedUsersRegex, replace);
+ findAndReplace(markdownAST as Root, mentionedUsersRegex, replace as ReplaceFunction);
return markdownAST;
};
@@ -213,6 +212,24 @@ export const renderText = <
if (noParsingNeeded.length > 0 || linkIsInBlock) return;
try {
+ // special case for mentions:
+ // it could happen that a user's name matches with an e-mail format pattern.
+ // in that case, we check whether the found e-mail is actually a mention
+ // by naively checking for an existence of @ sign in front of it.
+ if (type === 'email' && mentioned_users) {
+ const emailMatchesWithName = mentioned_users.some((u) => u.name === value);
+ if (emailMatchesWithName) {
+ newText = newText.replace(new RegExp(escapeRegExp(value), 'g'), (match, position) => {
+ const isMention = newText.charAt(position - 1) === '@';
+ // in case of mention, we leave the match in its original form,
+ // and we let `mentionsMarkdownPlugin` to do its job
+ return isMention ? match : `[${match}](${encodeDecode(href)})`;
+ });
+
+ return;
+ }
+ }
+
const displayLink = type === 'email' ? value : formatUrlForDisplay(href);
newText = newText.replace(
diff --git a/yarn.lock b/yarn.lock
index 6c399baafb..221441b40f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3207,10 +3207,10 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008"
integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==
-"@types/mdast@^3.0.0", "@types/mdast@^3.0.3":
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb"
- integrity sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==
+"@types/mdast@^3.0.0", "@types/mdast@^3.0.3", "@types/mdast@^3.0.10":
+ version "3.0.10"
+ resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
+ integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==
dependencies:
"@types/unist" "*"
@@ -3342,10 +3342,10 @@
resolved "https://registry.yarnpkg.com/@types/textarea-caret/-/textarea-caret-3.0.0.tgz#4c5c5e3de5c59511f93ffe929e5383471b828896"
integrity sha512-RNXko6Kl+oQibqxuQZJZ+RgsQAGez4VQxeC4zq+GXjUlHcfjy5EthnsPIQXC5wUSRf5WbyA+4+//mVSc6XwxNw==
-"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3":
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
- integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
+"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3", "@types/unist@^2.0.6":
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
+ integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
"@types/uuid@^8.3.0":
version "8.3.0"
@@ -7481,10 +7481,10 @@ escape-string-regexp@^2.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
-escape-string-regexp@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
- integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+escape-string-regexp@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
+ integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
escodegen@^2.0.0:
version "2.0.0"
@@ -11846,14 +11846,14 @@ mdast-add-list-metadata@1.0.1:
dependencies:
unist-util-visit-parents "1.1.2"
-mdast-util-find-and-replace@1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz#b7db1e873f96f66588c321f1363069abf607d1b5"
- integrity sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==
+mdast-util-find-and-replace@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz#249901ef43c5f41d6e8a8d446b3b63b17e592d7c"
+ integrity sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==
dependencies:
- escape-string-regexp "^4.0.0"
- unist-util-is "^4.0.0"
- unist-util-visit-parents "^3.0.0"
+ escape-string-regexp "^5.0.0"
+ unist-util-is "^5.0.0"
+ unist-util-visit-parents "^5.0.0"
mdast-util-from-markdown@^0.8.0:
version "0.8.5"
@@ -17114,6 +17114,11 @@ unist-util-is@^4.0.0:
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797"
integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==
+unist-util-is@^5.0.0:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.1.1.tgz#e8aece0b102fa9bc097b0fef8f870c496d4a6236"
+ integrity sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==
+
unist-util-remove-position@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz#ec037348b6102c897703eee6d0294ca4755a2020"
@@ -17153,6 +17158,14 @@ unist-util-visit-parents@^3.0.0:
"@types/unist" "^2.0.0"
unist-util-is "^4.0.0"
+unist-util-visit-parents@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz#44bbc5d25f2411e7dfc5cecff12de43296aa8521"
+ integrity sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-is "^5.0.0"
+
unist-util-visit@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3"