From 0daf449ffbf2d878d577bf82614efceaba4e582c Mon Sep 17 00:00:00 2001 From: abhisek7154 Date: Fri, 12 Sep 2025 23:16:57 +0530 Subject: [PATCH 1/3] refactor(emoji): centralize emoji rendering into app/components/Emoji (#6535) --- app/components/Emoji/Emoji.tsx | 30 +++++++++++++++++++ app/containers/EmojiPicker/Emoji.tsx | 21 ++++++------- .../markdown/components/emoji/Emoji.tsx | 25 +++++++++------- 3 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 app/components/Emoji/Emoji.tsx diff --git a/app/components/Emoji/Emoji.tsx b/app/components/Emoji/Emoji.tsx new file mode 100644 index 00000000000..55aca6f9344 --- /dev/null +++ b/app/components/Emoji/Emoji.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Text, StyleProp, TextStyle, ImageStyle } from 'react-native'; + +import useShortnameToUnicode from '../../lib/hooks/useShortnameToUnicode'; +import CustomEmoji from '../../containers/EmojiPicker/CustomEmoji'; +import { ICustomEmoji } from '../../definitions/IEmoji'; + +interface IEmojiProps { + emoji: string | ICustomEmoji; + style?: StyleProp; + size?: number; +} + +const Emoji = ({ emoji, style, size = 16 }: IEmojiProps) => { + const { formatShortnameToUnicode } = useShortnameToUnicode(true); + + if (typeof emoji === 'string') { + const unicodeEmoji = formatShortnameToUnicode(`:${emoji}:`); + return ]}>{unicodeEmoji}; + } + + return ( + ]} + emoji={emoji} + /> + ); +}; + +export default Emoji; diff --git a/app/containers/EmojiPicker/Emoji.tsx b/app/containers/EmojiPicker/Emoji.tsx index 434fd3d1c63..e5dfe95a75b 100644 --- a/app/containers/EmojiPicker/Emoji.tsx +++ b/app/containers/EmojiPicker/Emoji.tsx @@ -1,17 +1,14 @@ import React from 'react'; -import { Text } from 'react-native'; -import useShortnameToUnicode from '../../lib/hooks/useShortnameToUnicode'; -import styles from './styles'; -import CustomEmoji from './CustomEmoji'; +import Emoji from '../../components/Emoji/Emoji'; import { IEmojiProps } from './interfaces'; +import styles from './styles'; -export const Emoji = ({ emoji }: IEmojiProps): React.ReactElement => { - const { formatShortnameToUnicode } = useShortnameToUnicode(true); - const unicodeEmoji = formatShortnameToUnicode(`:${emoji}:`); +export const EmojiPickerEmoji = ({ emoji }: IEmojiProps): React.ReactElement => ( + +); - if (typeof emoji === 'string') { - return {unicodeEmoji}; - } - return ; -}; +export { Emoji }; \ No newline at end of file diff --git a/app/containers/markdown/components/emoji/Emoji.tsx b/app/containers/markdown/components/emoji/Emoji.tsx index 7aee7e4d7f0..11f675286ee 100644 --- a/app/containers/markdown/components/emoji/Emoji.tsx +++ b/app/containers/markdown/components/emoji/Emoji.tsx @@ -1,16 +1,15 @@ import React, { useContext } from 'react'; -import { Text, useWindowDimensions } from 'react-native'; +import {useWindowDimensions } from 'react-native'; import { Emoji as EmojiProps } from '@rocket.chat/message-parser'; -import Plain from '../Plain'; import useShortnameToUnicode from '../../../../lib/hooks/useShortnameToUnicode'; import { useTheme } from '../../../../theme'; import styles from '../../styles'; -import CustomEmoji from '../../../EmojiPicker/CustomEmoji'; import MarkdownContext from '../../contexts/MarkdownContext'; import { useAppSelector } from '../../../../lib/hooks'; import { getUserSelector } from '../../../../selectors/login'; import { useResponsiveLayout } from '../../../../lib/hooks/useResponsiveLayout/useResponsiveLayout'; +import EmojiRenderer from '../../../../components/Emoji/Emoji'; interface IEmojiProps { block: EmojiProps; @@ -42,7 +41,13 @@ const Emoji = ({ block, isBigEmoji, style = {}, index, isAvatar = false }: IEmoj const convertAsciiEmoji = useAppSelector(state => getUserSelector(state)?.settings?.preferences?.convertAsciiEmoji); if ('unicode' in block) { - return {block.unicode}; + return ( + + ); } const emojiToken = getEmojiToken(block, isAvatar); @@ -68,20 +73,20 @@ const Emoji = ({ block, isBigEmoji, style = {}, index, isAvatar = false }: IEmoj }; if (emoji) { - return ; + return ; } return ( - - {spaceLeft} - {displayAsciiEmoji ? : emojiUnicode} - + ]} + /> ); }; From b3daed380450859928b609cfdd84091242622024 Mon Sep 17 00:00:00 2001 From: abhisek7154 Date: Sat, 13 Sep 2025 00:07:02 +0530 Subject: [PATCH 2/3] fix(emoji): handle unicode/ASCII safely and remove nested ternary --- app/components/Emoji/Emoji.tsx | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/app/components/Emoji/Emoji.tsx b/app/components/Emoji/Emoji.tsx index 55aca6f9344..af055047f6b 100644 --- a/app/components/Emoji/Emoji.tsx +++ b/app/components/Emoji/Emoji.tsx @@ -15,8 +15,29 @@ const Emoji = ({ emoji, style, size = 16 }: IEmojiProps) => { const { formatShortnameToUnicode } = useShortnameToUnicode(true); if (typeof emoji === 'string') { - const unicodeEmoji = formatShortnameToUnicode(`:${emoji}:`); - return ]}>{unicodeEmoji}; + const trimmed = emoji.trim(); + + const isColonShortname = /^:[^:\s]+:$/.test(trimmed); + const isBareShortname = /^[a-z0-9_+\-]+$/i.test(trimmed); + + const leading = emoji.match(/^\s*/)?.[0] ?? ''; + const trailing = emoji.match(/\s*$/)?.[0] ?? ''; + + let converted: string; + if (isColonShortname) { + converted = formatShortnameToUnicode(trimmed); + } else if (isBareShortname) { + converted = formatShortnameToUnicode(`:${trimmed}:`); + } else { + converted = emoji; + } + + + return ( + ]}> + {`${leading}${converted}${trailing}`} + + ); } return ( From fe650ebfe2e98b4800ecaeb6c37a2a723504ba9f Mon Sep 17 00:00:00 2001 From: abhisek7154 Date: Sat, 13 Sep 2025 02:08:07 +0530 Subject: [PATCH 3/3] fix(emoji): fix linting and update snapshots --- app/components/Emoji/Emoji.tsx | 64 ++-- app/containers/EmojiPicker/Emoji.tsx | 7 +- .../__snapshots__/UiKitModal.test.tsx.snap | 16 +- .../__snapshots__/Markdown.test.tsx.snap | 147 +++++---- .../markdown/components/emoji/Emoji.tsx | 16 +- .../__snapshots__/Message.test.tsx.snap | 307 +++++++++--------- 6 files changed, 267 insertions(+), 290 deletions(-) diff --git a/app/components/Emoji/Emoji.tsx b/app/components/Emoji/Emoji.tsx index af055047f6b..da35d9fef1a 100644 --- a/app/components/Emoji/Emoji.tsx +++ b/app/components/Emoji/Emoji.tsx @@ -6,46 +6,36 @@ import CustomEmoji from '../../containers/EmojiPicker/CustomEmoji'; import { ICustomEmoji } from '../../definitions/IEmoji'; interface IEmojiProps { - emoji: string | ICustomEmoji; - style?: StyleProp; - size?: number; + emoji: string | ICustomEmoji; + style?: StyleProp; + size?: number; } const Emoji = ({ emoji, style, size = 16 }: IEmojiProps) => { - const { formatShortnameToUnicode } = useShortnameToUnicode(true); - - if (typeof emoji === 'string') { - const trimmed = emoji.trim(); - - const isColonShortname = /^:[^:\s]+:$/.test(trimmed); - const isBareShortname = /^[a-z0-9_+\-]+$/i.test(trimmed); - - const leading = emoji.match(/^\s*/)?.[0] ?? ''; - const trailing = emoji.match(/\s*$/)?.[0] ?? ''; - - let converted: string; - if (isColonShortname) { - converted = formatShortnameToUnicode(trimmed); - } else if (isBareShortname) { - converted = formatShortnameToUnicode(`:${trimmed}:`); - } else { - converted = emoji; - } - - - return ( - ]}> - {`${leading}${converted}${trailing}`} - - ); - } - - return ( - ]} - emoji={emoji} - /> - ); + const { formatShortnameToUnicode } = useShortnameToUnicode(true); + + if (typeof emoji === 'string') { + const trimmed = emoji.trim(); + + const isColonShortname = /^:[^:\s]+:$/.test(trimmed); + const isBareShortname = /^[a-z0-9_+\-]+$/i.test(trimmed); + + const leading = emoji.match(/^\s*/)?.[0] ?? ''; + const trailing = emoji.match(/\s*$/)?.[0] ?? ''; + + let converted: string; + if (isColonShortname) { + converted = formatShortnameToUnicode(trimmed); + } else if (isBareShortname) { + converted = formatShortnameToUnicode(`:${trimmed}:`); + } else { + converted = emoji; + } + + return ]}>{`${leading}${converted}${trailing}`}; + } + + return ]} emoji={emoji} />; }; export default Emoji; diff --git a/app/containers/EmojiPicker/Emoji.tsx b/app/containers/EmojiPicker/Emoji.tsx index e5dfe95a75b..6048f5d3888 100644 --- a/app/containers/EmojiPicker/Emoji.tsx +++ b/app/containers/EmojiPicker/Emoji.tsx @@ -5,10 +5,7 @@ import { IEmojiProps } from './interfaces'; import styles from './styles'; export const EmojiPickerEmoji = ({ emoji }: IEmojiProps): React.ReactElement => ( - + ); -export { Emoji }; \ No newline at end of file +export { Emoji }; diff --git a/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap b/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap index 21b5638a37e..163b4230373 100644 --- a/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap +++ b/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap @@ -3769,16 +3769,14 @@ exports[`Story Snapshots: ModalSectionSelects should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > diff --git a/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap b/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap index af00378f8ac..0a45328b821 100644 --- a/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap +++ b/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap @@ -485,16 +485,14 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -504,16 +502,14 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -523,16 +519,14 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -597,23 +591,27 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, - {}, - false, + [ + { + "color": "#2F343D", + }, + { + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 22, + "textAlign": "left", + }, + {}, + false, + ], ] } > - - 😂 + 😂 - - 👍 + 👍 @@ -860,16 +862,14 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", "fontSize": 30, - "fontWeight": "400", - "lineHeight": 43, - "textAlign": "left", }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -879,18 +879,23 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", "fontSize": 30, - "fontWeight": "400", - "lineHeight": 43, - "textAlign": "left", }, - {}, - false, + [ + { + "color": "#2F343D", + }, + { + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 30, + "fontWeight": "400", + "lineHeight": 43, + "textAlign": "left", + }, + {}, + false, + ], ] } > diff --git a/app/containers/markdown/components/emoji/Emoji.tsx b/app/containers/markdown/components/emoji/Emoji.tsx index 11f675286ee..c065419b46e 100644 --- a/app/containers/markdown/components/emoji/Emoji.tsx +++ b/app/containers/markdown/components/emoji/Emoji.tsx @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import {useWindowDimensions } from 'react-native'; +import { useWindowDimensions } from 'react-native'; import { Emoji as EmojiProps } from '@rocket.chat/message-parser'; import useShortnameToUnicode from '../../../../lib/hooks/useShortnameToUnicode'; @@ -42,11 +42,11 @@ const Emoji = ({ block, isBigEmoji, style = {}, index, isAvatar = false }: IEmoj if ('unicode' in block) { return ( - + ); } @@ -78,8 +78,8 @@ const Emoji = ({ block, isBigEmoji, style = {}, index, isAvatar = false }: IEmoj return ( @@ -21178,16 +21183,14 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", "fontSize": 30, - "fontWeight": "400", - "lineHeight": 43, - "textAlign": "left", }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -21197,16 +21200,14 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", "fontSize": 30, - "fontWeight": "400", - "lineHeight": 43, - "textAlign": "left", }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -21519,16 +21520,14 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", "fontSize": 30, - "fontWeight": "400", - "lineHeight": 43, - "textAlign": "left", }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -22575,16 +22574,14 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", "fontSize": 30, - "fontWeight": "400", - "lineHeight": 43, - "textAlign": "left", }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -22950,16 +22947,14 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -22986,16 +22981,14 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -23005,16 +22998,14 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -23341,16 +23332,14 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", "fontSize": 30, - "fontWeight": "400", - "lineHeight": 43, - "textAlign": "left", }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -23360,16 +23349,14 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", "fontSize": 30, - "fontWeight": "400", - "lineHeight": 43, - "textAlign": "left", }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -23682,16 +23669,14 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", "fontSize": 30, - "fontWeight": "400", - "lineHeight": 43, - "textAlign": "left", }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -24738,16 +24723,14 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", "fontSize": 30, - "fontWeight": "400", - "lineHeight": 43, - "textAlign": "left", }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -25113,16 +25096,14 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -25149,16 +25130,14 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -25168,16 +25147,14 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, + [ + { + "color": "#2F343D", + }, + {}, + ], ] } > @@ -74134,18 +74111,23 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` style={ [ { - "color": "#2F343D", - }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", + "fontSize": 15, }, - {}, - false, + [ + { + "color": "#2F343D", + }, + { + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 22, + "textAlign": "left", + }, + {}, + false, + ], ] } > @@ -75128,18 +75110,23 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot style={ [ { - "color": "#2F343D", + "fontSize": 15, }, - { - "backgroundColor": "transparent", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, - "textAlign": "left", - }, - {}, - false, + [ + { + "color": "#2F343D", + }, + { + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 22, + "textAlign": "left", + }, + {}, + false, + ], ] } >