diff --git a/src/ui/MentionUserLabel/__tests__/__snapshots__/MentionUserLabel.spec.js.snap b/src/ui/MentionUserLabel/__tests__/__snapshots__/MentionUserLabel.spec.js.snap index 41c7db5ee..c14293da5 100644 --- a/src/ui/MentionUserLabel/__tests__/__snapshots__/MentionUserLabel.spec.js.snap +++ b/src/ui/MentionUserLabel/__tests__/__snapshots__/MentionUserLabel.spec.js.snap @@ -5,6 +5,7 @@ exports[`ui/MentionUserLabel should do a snapshot test of the MentionUserLabel D `; diff --git a/src/ui/MentionUserLabel/__tests__/renderToString.spec.js b/src/ui/MentionUserLabel/__tests__/renderToString.spec.js new file mode 100644 index 000000000..d7c76ebb2 --- /dev/null +++ b/src/ui/MentionUserLabel/__tests__/renderToString.spec.js @@ -0,0 +1,11 @@ +import renderToString from "../renderToString"; + +describe("ui/MentionUserLabel/renderToString", () => { + it("should render userId and nickname as expected", () => { + const userId = "me"; + const nickname = "nickname"; + const expected = `nickname`; + const result = renderToString({ userId, nickname }); + expect(result).toEqual(expected); + }); +}); diff --git a/src/ui/MentionUserLabel/consts.ts b/src/ui/MentionUserLabel/consts.ts new file mode 100644 index 000000000..c3e2ab763 --- /dev/null +++ b/src/ui/MentionUserLabel/consts.ts @@ -0,0 +1 @@ +export const MENTION_USER_LABEL_CLASSNAME = 'sendbird-mention-user-label'; diff --git a/src/ui/MentionUserLabel/index.tsx b/src/ui/MentionUserLabel/index.tsx index 3ed14cec2..50e03d236 100644 --- a/src/ui/MentionUserLabel/index.tsx +++ b/src/ui/MentionUserLabel/index.tsx @@ -1,6 +1,8 @@ -import React from 'react'; import './index.scss'; +import React from 'react'; +import { MENTION_USER_LABEL_CLASSNAME } from './consts'; + interface MentionUserLabelProps { className?: string children?: string; @@ -18,9 +20,10 @@ export default function MentionUserLabel({ }: MentionUserLabelProps): JSX.Element { return ( {children} diff --git a/src/ui/MentionUserLabel/renderToString.ts b/src/ui/MentionUserLabel/renderToString.ts new file mode 100644 index 000000000..ebfd129ae --- /dev/null +++ b/src/ui/MentionUserLabel/renderToString.ts @@ -0,0 +1,20 @@ +// cretes a sanitized string from a mention user label +import DOMPurify from 'dompurify'; +import { MENTION_USER_LABEL_CLASSNAME } from './consts'; + +type renderToStringParams = { + userId: string; + nickname: string; +}; + +export default function renderToString({ userId, nickname }: renderToStringParams): string { + // donot change this template, it wont work + const el = `${nickname}`; + const purifier = DOMPurify(window); + const sanitized_ = purifier.sanitize(el); + const token = sanitized_.split(' '); + const [spanTag, ...rest] = token; + // we do this because DOMPurify removes the contenteditable attribute + const sanitized = [spanTag, 'contenteditable="false"', ...rest].join(' '); + return sanitized; +} diff --git a/src/ui/MessageInput/hooks/usePaste/insertTemplate.tsx b/src/ui/MessageInput/hooks/usePaste/insertTemplate.ts similarity index 58% rename from src/ui/MessageInput/hooks/usePaste/insertTemplate.tsx rename to src/ui/MessageInput/hooks/usePaste/insertTemplate.ts index 9d14decc8..74c0b4782 100644 --- a/src/ui/MessageInput/hooks/usePaste/insertTemplate.tsx +++ b/src/ui/MessageInput/hooks/usePaste/insertTemplate.ts @@ -1,23 +1,12 @@ -import React from 'react'; -import { renderToString } from 'react-dom/server'; - import { Word } from './types'; import { sanitizeString } from '../../utils'; -import MentionUserLabel from '../../../MentionUserLabel'; +import renderMentionLabelToString from '../../../MentionUserLabel/renderToString'; export function inserTemplateToDOM(templateList: Word[]): void { const nodes = templateList.map((template) => { const { text, userId } = template; if (userId) { - return ( - renderToString( - <> - - {text} - - - ) - ); + return renderMentionLabelToString({ userId, nickname: text }); } return sanitizeString(text); }) diff --git a/src/ui/MessageInput/index.jsx b/src/ui/MessageInput/index.jsx index 64ef25a88..c6af70938 100644 --- a/src/ui/MessageInput/index.jsx +++ b/src/ui/MessageInput/index.jsx @@ -6,7 +6,6 @@ import React, { useCallback, useContext, } from 'react'; -import { renderToString } from 'react-dom/server'; import PropTypes from 'prop-types'; import './index.scss'; @@ -15,7 +14,7 @@ import { MessageInputKeys, NodeNames, NodeTypes } from './const'; import { USER_MENTION_TEMP_CHAR } from '../../smart-components/Channel/context/const'; import IconButton from '../IconButton'; import Button, { ButtonTypes, ButtonSizes } from '../Button'; -import MentionUserLabel from '../MentionUserLabel'; +import renderMentionLabelToString from '../MentionUserLabel/renderToString'; import Icon, { IconTypes, IconColors } from '../Icon'; import Label, { LabelTypography, LabelColors } from '../Label'; import { LocalizationContext } from '../../lib/LocalizationContext'; @@ -161,16 +160,11 @@ const MessageInput = React.forwardRef((props, ref) => { convertWordToStringObj(word, mentionedUsers).map((stringObj) => { const { type, value, userId } = stringObj; if (type === StringObjType.mention && mentionedUsers.some((user) => user?.userId === userId)) { - return renderToString( - - { - `${USER_MENTION_TEMP_CHAR}${mentionedUsers.find((user) => user?.userId === userId)?.nickname - || value - || stringSet.MENTION_NAME__NO_NAME - }` - } - , - ); + const nickname = `${USER_MENTION_TEMP_CHAR}${mentionedUsers.find((user) => user?.userId === userId)?.nickname + || value + || stringSet.MENTION_NAME__NO_NAME + }` + return renderMentionLabelToString({ userId, nickname }); } return sanitizeString(value); }).join('') @@ -220,11 +214,10 @@ const MessageInput = React.forwardRef((props, ref) => { const backTextNode = document?.createTextNode( `\u00A0${childNodes[endNodeIndex]?.textContent.slice(endOffsetIndex)}`, ); - const mentionLabel = renderToString( - - {`${USER_MENTION_TEMP_CHAR}${mentionSelectedUser?.nickname || stringSet.MENTION_NAME__NO_NAME}`} - , - ); + const mentionLabel = renderMentionLabelToString({ + userId: mentionSelectedUser?.userId, + nickname: `${USER_MENTION_TEMP_CHAR}${mentionSelectedUser?.nickname || stringSet.MENTION_NAME__NO_NAME}`, + }); const div = document.createElement('div'); div.innerHTML = mentionLabel; const newNodes = [