From 893d6b8c82da48c43a53f0938ba6d2bf741defcb Mon Sep 17 00:00:00 2001 From: Sravan S Date: Thu, 23 Mar 2023 15:23:32 +0900 Subject: [PATCH 1/4] feat: replace react-dom/server.renderToString with custom fn --- src/ui/MentionUserLabel/renderToString.ts | 23 ++++++++++++++++ .../hooks/usePaste/insertTemplate.tsx | 13 ++------- src/ui/MessageInput/index.jsx | 27 +++++++------------ 3 files changed, 35 insertions(+), 28 deletions(-) create mode 100644 src/ui/MentionUserLabel/renderToString.ts diff --git a/src/ui/MentionUserLabel/renderToString.ts b/src/ui/MentionUserLabel/renderToString.ts new file mode 100644 index 000000000..91a5d9643 --- /dev/null +++ b/src/ui/MentionUserLabel/renderToString.ts @@ -0,0 +1,23 @@ +// cretes a sanitized string from a mention user label +import DOMPurify from 'dompurify'; + +type renderToStringParams = { + userId: string; + nickname: string; +}; + +export default function renderToString({ userId, nickname }: renderToStringParams): string { + const el = ` + + ${nickname} + + `; + const purifier = DOMPurify(window); + const sanitized = purifier.sanitize(el, { ALLOWED_TAGS: ['span'] }); + return sanitized; +} diff --git a/src/ui/MessageInput/hooks/usePaste/insertTemplate.tsx b/src/ui/MessageInput/hooks/usePaste/insertTemplate.tsx index 9d14decc8..4b9039056 100644 --- a/src/ui/MessageInput/hooks/usePaste/insertTemplate.tsx +++ b/src/ui/MessageInput/hooks/usePaste/insertTemplate.tsx @@ -1,23 +1,14 @@ 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 = [ From 2b711fef6b47181b8c916b4151742b2d1b1ce9b5 Mon Sep 17 00:00:00 2001 From: Sravan S Date: Thu, 23 Mar 2023 15:50:46 +0900 Subject: [PATCH 2/4] feat: set conteneditable false manually --- src/ui/MentionUserLabel/renderToString.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/ui/MentionUserLabel/renderToString.ts b/src/ui/MentionUserLabel/renderToString.ts index 91a5d9643..0696e740f 100644 --- a/src/ui/MentionUserLabel/renderToString.ts +++ b/src/ui/MentionUserLabel/renderToString.ts @@ -7,17 +7,13 @@ type renderToStringParams = { }; export default function renderToString({ userId, nickname }: renderToStringParams): string { - const el = ` - - ${nickname} - - `; + // donot change this template, it wont work + const el = `${nickname}`; const purifier = DOMPurify(window); - const sanitized = purifier.sanitize(el, { ALLOWED_TAGS: ['span'] }); + 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; } From ceb5797b4371481d0a3b1ba27a79e87ee2e66342 Mon Sep 17 00:00:00 2001 From: Sravan S Date: Thu, 23 Mar 2023 16:24:03 +0900 Subject: [PATCH 3/4] add tests --- .../__snapshots__/MentionUserLabel.spec.js.snap | 1 + .../MentionUserLabel/__tests__/renderToString.spec.js | 11 +++++++++++ src/ui/MentionUserLabel/consts.ts | 1 + src/ui/MentionUserLabel/index.tsx | 7 +++++-- src/ui/MentionUserLabel/renderToString.ts | 4 +++- .../{insertTemplate.tsx => insertTemplate.ts} | 2 -- 6 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 src/ui/MentionUserLabel/__tests__/renderToString.spec.js create mode 100644 src/ui/MentionUserLabel/consts.ts rename src/ui/MessageInput/hooks/usePaste/{insertTemplate.tsx => insertTemplate.ts} (95%) 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 index 0696e740f..3ebc1ead0 100644 --- a/src/ui/MentionUserLabel/renderToString.ts +++ b/src/ui/MentionUserLabel/renderToString.ts @@ -1,5 +1,7 @@ // cretes a sanitized string from a mention user label import DOMPurify from 'dompurify'; +import { MENTION_CLASS } from '../MessageInput/hooks/usePaste/consts'; +import { MENTION_USER_LABEL_CLASSNAME } from './consts'; type renderToStringParams = { userId: string; @@ -8,7 +10,7 @@ type renderToStringParams = { export default function renderToString({ userId, nickname }: renderToStringParams): string { // donot change this template, it wont work - const el = `${nickname}`; + const el = `${nickname}`; const purifier = DOMPurify(window); const sanitized_ = purifier.sanitize(el); const token = sanitized_.split(' '); diff --git a/src/ui/MessageInput/hooks/usePaste/insertTemplate.tsx b/src/ui/MessageInput/hooks/usePaste/insertTemplate.ts similarity index 95% rename from src/ui/MessageInput/hooks/usePaste/insertTemplate.tsx rename to src/ui/MessageInput/hooks/usePaste/insertTemplate.ts index 4b9039056..74c0b4782 100644 --- a/src/ui/MessageInput/hooks/usePaste/insertTemplate.tsx +++ b/src/ui/MessageInput/hooks/usePaste/insertTemplate.ts @@ -1,5 +1,3 @@ -import React from 'react'; - import { Word } from './types'; import { sanitizeString } from '../../utils'; import renderMentionLabelToString from '../../../MentionUserLabel/renderToString'; From d9a76b7f0758daf8c3832de1a35834f468e6401e Mon Sep 17 00:00:00 2001 From: Sravan S Date: Thu, 23 Mar 2023 16:41:04 +0900 Subject: [PATCH 4/4] fix: lint --- src/ui/MentionUserLabel/renderToString.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui/MentionUserLabel/renderToString.ts b/src/ui/MentionUserLabel/renderToString.ts index 3ebc1ead0..ebfd129ae 100644 --- a/src/ui/MentionUserLabel/renderToString.ts +++ b/src/ui/MentionUserLabel/renderToString.ts @@ -1,6 +1,5 @@ // cretes a sanitized string from a mention user label import DOMPurify from 'dompurify'; -import { MENTION_CLASS } from '../MessageInput/hooks/usePaste/consts'; import { MENTION_USER_LABEL_CLASSNAME } from './consts'; type renderToStringParams = {