Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ exports[`ui/MentionUserLabel should do a snapshot test of the MentionUserLabel D
<span
class="sendbird-mention-user-label undefined"
contenteditable="false"
data-sb-mention="true"
/>
</DocumentFragment>
`;
11 changes: 11 additions & 0 deletions src/ui/MentionUserLabel/__tests__/renderToString.spec.js
Original file line number Diff line number Diff line change
@@ -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 = `<span contenteditable="false" class="sendbird-mention-user-label" data-sb-mention="true" data-userid="me">nickname</span>`;
const result = renderToString({ userId, nickname });
expect(result).toEqual(expected);
});
});
1 change: 1 addition & 0 deletions src/ui/MentionUserLabel/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MENTION_USER_LABEL_CLASSNAME = 'sendbird-mention-user-label';
7 changes: 5 additions & 2 deletions src/ui/MentionUserLabel/index.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,9 +20,10 @@ export default function MentionUserLabel({
}: MentionUserLabelProps): JSX.Element {
return (
<span
className={`sendbird-mention-user-label ${className} ${isReverse ? 'reverse' : ''} ${color}`}
className={`${MENTION_USER_LABEL_CLASSNAME} ${className} ${isReverse ? 'reverse' : ''} ${color}`}
contentEditable={false}
data-userid={userId}
data-sb-mention={true}
>
{children}
</span>
Expand Down
20 changes: 20 additions & 0 deletions src/ui/MentionUserLabel/renderToString.ts
Original file line number Diff line number Diff line change
@@ -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 = `<span data-userid="${userId}" data-sb-mention="true" class="${MENTION_USER_LABEL_CLASSNAME}">${nickname}</span>`;
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;
}
Original file line number Diff line number Diff line change
@@ -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(
<>
<MentionUserLabel userId={userId}>
{text}
</MentionUserLabel>
</>
)
);
return renderMentionLabelToString({ userId, nickname: text });
}
return sanitizeString(text);
})
Expand Down
27 changes: 10 additions & 17 deletions src/ui/MessageInput/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import React, {
useCallback,
useContext,
} from 'react';
import { renderToString } from 'react-dom/server';
import PropTypes from 'prop-types';

import './index.scss';
Expand All @@ -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';
Expand Down Expand Up @@ -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(
<MentionUserLabel userId={userId}>
{
`${USER_MENTION_TEMP_CHAR}${mentionedUsers.find((user) => user?.userId === userId)?.nickname
|| value
|| stringSet.MENTION_NAME__NO_NAME
}`
}
</MentionUserLabel>,
);
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('')
Expand Down Expand Up @@ -220,11 +214,10 @@ const MessageInput = React.forwardRef((props, ref) => {
const backTextNode = document?.createTextNode(
`\u00A0${childNodes[endNodeIndex]?.textContent.slice(endOffsetIndex)}`,
);
const mentionLabel = renderToString(
<MentionUserLabel userId={mentionSelectedUser?.userId}>
{`${USER_MENTION_TEMP_CHAR}${mentionSelectedUser?.nickname || stringSet.MENTION_NAME__NO_NAME}`}
</MentionUserLabel>,
);
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 = [
Expand Down