|
1 | | -import React from 'react'; |
| 1 | +import React, { PropsWithChildren } from 'react'; |
2 | 2 | import emojiRegex from 'emoji-regex'; |
3 | 3 | import * as linkify from 'linkifyjs'; |
4 | 4 | //@ts-expect-error |
@@ -107,9 +107,53 @@ const emojiMarkdownPlugin = () => { |
107 | 107 | return transform; |
108 | 108 | }; |
109 | 109 |
|
| 110 | +const mentionsMarkdownPlugin = < |
| 111 | + Us extends DefaultUserType<Us> = DefaultUserType |
| 112 | +>( |
| 113 | + mentioned_users: UserResponse<Us>[], |
| 114 | +) => () => { |
| 115 | + const mentioned_usernames = mentioned_users |
| 116 | + .map((user) => user.name || user.id) |
| 117 | + .filter(Boolean) |
| 118 | + .map(escapeRegExp); |
| 119 | + |
| 120 | + const mentionedUsersRegex = new RegExp( |
| 121 | + mentioned_usernames.map((username) => `@${username}`).join('|'), |
| 122 | + 'g', |
| 123 | + ); |
| 124 | + |
| 125 | + function replace(match: string) { |
| 126 | + const usernameOrId = match.replace('@', ''); |
| 127 | + const user = mentioned_users.find( |
| 128 | + ({ id, name }) => name === usernameOrId || id === usernameOrId, |
| 129 | + ); |
| 130 | + return { |
| 131 | + children: [{ type: 'text', value: match }], |
| 132 | + mentioned_user: user, |
| 133 | + type: 'mention', |
| 134 | + }; |
| 135 | + } |
| 136 | + |
| 137 | + const transform = <T extends unknown>(markdownAST: T) => { |
| 138 | + findAndReplace(markdownAST, mentionedUsersRegex, replace); |
| 139 | + return markdownAST; |
| 140 | + }; |
| 141 | + |
| 142 | + return transform; |
| 143 | +}; |
| 144 | + |
| 145 | +type MentionProps<Us extends DefaultUserType<Us> = DefaultUserType> = { |
| 146 | + mentioned_user: UserResponse<Us>; |
| 147 | +}; |
| 148 | + |
| 149 | +const Mention = <Us extends DefaultUserType<Us> = DefaultUserType>( |
| 150 | + props: PropsWithChildren<Us>, |
| 151 | +) => <span className='str-chat__message-mention'>{props.children}</span>; |
| 152 | + |
110 | 153 | export const renderText = <Us extends DefaultUserType<Us> = DefaultUserType>( |
111 | 154 | text?: string, |
112 | 155 | mentioned_users?: UserResponse<Us>[], |
| 156 | + MentionComponent: React.ComponentType<MentionProps<Us>> = Mention, |
113 | 157 | ) => { |
114 | 158 | // take the @ mentions and turn them into markdown? |
115 | 159 | // translate links |
@@ -146,27 +190,23 @@ export const renderText = <Us extends DefaultUserType<Us> = DefaultUserType>( |
146 | 190 | newText = newText.replace(value, `[${displayLink}](${encodeURI(href)})`); |
147 | 191 | }); |
148 | 192 |
|
149 | | - if (mentioned_users && mentioned_users.length) { |
150 | | - for (let i = 0; i < mentioned_users.length; i++) { |
151 | | - let username = mentioned_users[i].name || mentioned_users[i].id; |
| 193 | + const plugins = [emojiMarkdownPlugin]; |
152 | 194 |
|
153 | | - if (username) { |
154 | | - username = escapeRegExp(username); |
155 | | - } |
156 | | - |
157 | | - const nameMarkdown = `**@${username}**`; |
158 | | - const nameRegex = new RegExp(`@${username}`, 'g'); |
159 | | - |
160 | | - newText = newText.replace(nameRegex, nameMarkdown); |
161 | | - } |
| 195 | + if (mentioned_users?.length) { |
| 196 | + plugins.push(mentionsMarkdownPlugin(mentioned_users)); |
162 | 197 | } |
163 | 198 |
|
| 199 | + const renderers = { |
| 200 | + ...markDownRenderers, |
| 201 | + mention: MentionComponent, |
| 202 | + }; |
| 203 | + |
164 | 204 | return ( |
165 | 205 | <ReactMarkdown |
166 | 206 | allowedTypes={allowedMarkups} |
167 | 207 | escapeHtml={true} |
168 | | - plugins={[emojiMarkdownPlugin]} |
169 | | - renderers={markDownRenderers} |
| 208 | + plugins={plugins} |
| 209 | + renderers={renderers} |
170 | 210 | source={newText} |
171 | 211 | transformLinkUri={(uri) => |
172 | 212 | uri.startsWith('app://') ? uri : RootReactMarkdown.uriTransformer(uri) |
|
0 commit comments