diff --git a/package-lock.json b/package-lock.json
index 0d0b11fff3f1..2b415ef9f137 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -51,7 +51,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
- "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#615f4a8662cd1abea9fdeee4d04847197c5e36ae",
+ "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#4e020cfa13ffabde14313c92b341285aeb919f29",
"expo": "^50.0.3",
"expo-av": "~13.10.4",
"expo-image": "1.11.0",
@@ -23087,9 +23087,9 @@
}
},
"node_modules/classnames": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.4.0.tgz",
- "integrity": "sha512-lWxiIlphgAhTLN657pwU/ofFxsUTOWc2CRIFeoV5st0MGRJHStUnWIUJgDHxjUO/F0mXzGufXIM4Lfu/8h+MpA=="
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.0.tgz",
+ "integrity": "sha512-FQuRlyKinxrb5gwJlfVASbSrDlikDJ07426TrfPsdGLvtochowmkbnSFdQGJ2aoXrSetq5KqGV9emvWpy+91xA=="
},
"node_modules/clean-css": {
"version": "5.3.2",
@@ -27370,11 +27370,11 @@
},
"node_modules/expensify-common": {
"version": "1.0.0",
- "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#615f4a8662cd1abea9fdeee4d04847197c5e36ae",
- "integrity": "sha512-k/SmW3EBR+gxFkJP/59LJsmBKjnKR07XS30yk/GkQ0lIfyYkNmFJ0dWm/S/54ezFweezR7MDaQ3zGc45Mb/U5A==",
+ "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#4e020cfa13ffabde14313c92b341285aeb919f29",
+ "integrity": "sha512-sx3cIYkmiydNaXRe4kJebPyEje8HfssUbsoB6uW8vvMLwFheCZfkmF9fRMBNLo8BQsfWIstT5TApEhwuWPjqZg==",
"license": "MIT",
"dependencies": {
- "classnames": "2.4.0",
+ "classnames": "2.5.0",
"clipboard": "2.0.11",
"html-entities": "^2.4.0",
"jquery": "3.6.0",
@@ -27383,7 +27383,7 @@
"prop-types": "15.8.1",
"react": "16.12.0",
"react-dom": "16.12.0",
- "semver": "^7.5.2",
+ "semver": "^7.6.0",
"simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
"ua-parser-js": "^1.0.37",
"underscore": "1.13.6"
diff --git a/package.json b/package.json
index 5001e6f0dd1a..13dbf58fbc52 100644
--- a/package.json
+++ b/package.json
@@ -102,7 +102,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
- "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#615f4a8662cd1abea9fdeee4d04847197c5e36ae",
+ "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#4e020cfa13ffabde14313c92b341285aeb919f29",
"expo": "^50.0.3",
"expo-av": "~13.10.4",
"expo-image": "1.11.0",
diff --git a/src/components/EmojiWithTooltip/index.native.tsx b/src/components/EmojiWithTooltip/index.native.tsx
new file mode 100644
index 000000000000..f6e9ee17fff8
--- /dev/null
+++ b/src/components/EmojiWithTooltip/index.native.tsx
@@ -0,0 +1,10 @@
+import Text from '@components/Text';
+import type EmojiWithTooltipProps from './types';
+
+function EmojiWithTooltip({emojiCode, style = {}}: EmojiWithTooltipProps) {
+ return {emojiCode};
+}
+
+EmojiWithTooltip.displayName = 'EmojiWithTooltip';
+
+export default EmojiWithTooltip;
diff --git a/src/components/EmojiWithTooltip/index.tsx b/src/components/EmojiWithTooltip/index.tsx
new file mode 100644
index 000000000000..32103544b3aa
--- /dev/null
+++ b/src/components/EmojiWithTooltip/index.tsx
@@ -0,0 +1,42 @@
+import React, {useCallback} from 'react';
+import {View} from 'react-native';
+import Text from '@components/Text';
+import Tooltip from '@components/Tooltip';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as EmojiUtils from '@libs/EmojiUtils';
+import type EmojiWithTooltipProps from './types';
+
+function EmojiWithTooltip({emojiCode, style = {}}: EmojiWithTooltipProps) {
+ const {preferredLocale} = useLocalize();
+ const styles = useThemeStyles();
+ const emoji = EmojiUtils.findEmojiByCode(emojiCode);
+ const emojiName = EmojiUtils.getEmojiName(emoji, preferredLocale);
+
+ const emojiTooltipContent = useCallback(
+ () => (
+
+
+
+ {emojiCode}
+
+
+ {`:${emojiName}:`}
+
+ ),
+ [emojiCode, emojiName, styles.alignItemsCenter, styles.ph2, styles.flexRow, styles.emojiTooltipWrapper, styles.fontColorReactionLabel, styles.onlyEmojisText, styles.textMicro],
+ );
+
+ return (
+
+ {emojiCode}
+
+ );
+}
+
+EmojiWithTooltip.displayName = 'EmojiWithTooltip';
+
+export default EmojiWithTooltip;
diff --git a/src/components/EmojiWithTooltip/types.ts b/src/components/EmojiWithTooltip/types.ts
new file mode 100644
index 000000000000..d13c389c0568
--- /dev/null
+++ b/src/components/EmojiWithTooltip/types.ts
@@ -0,0 +1,8 @@
+import type {StyleProp, TextStyle} from 'react-native';
+
+type EmojiWithTooltipProps = {
+ emojiCode: string;
+ style?: StyleProp;
+};
+
+export default EmojiWithTooltipProps;
diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx
index bd4f72c63ec3..af04c11de41e 100755
--- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx
+++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx
@@ -70,6 +70,7 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim
mixedUAStyles: {whiteSpace: 'pre'},
contentModel: HTMLContentModel.block,
}),
+ emoji: HTMLElementModel.fromCustomModel({tagName: 'emoji', contentModel: HTMLContentModel.textual}),
}),
[styles.colorMuted, styles.formError, styles.mb0, styles.textLabelSupporting, styles.lh16],
);
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx
new file mode 100644
index 000000000000..6582e99477a8
--- /dev/null
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
+import EmojiWithTooltip from '@components/EmojiWithTooltip';
+import useThemeStyles from '@hooks/useThemeStyles';
+
+function EmojiRenderer({tnode}: CustomRendererProps) {
+ const styles = useThemeStyles();
+ const style = 'islarge' in tnode.attributes ? styles.onlyEmojisText : {};
+ return (
+
+ );
+}
+
+EmojiRenderer.displayName = 'EmojiRenderer';
+
+export default EmojiRenderer;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts
index 1914bcf4b5ff..fdd0c89ec5a0 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts
@@ -2,6 +2,7 @@ import type {CustomTagRendererRecord} from 'react-native-render-html';
import AnchorRenderer from './AnchorRenderer';
import CodeRenderer from './CodeRenderer';
import EditedRenderer from './EditedRenderer';
+import EmojiRenderer from './EmojiRenderer';
import ImageRenderer from './ImageRenderer';
import MentionHereRenderer from './MentionHereRenderer';
import MentionUserRenderer from './MentionUserRenderer';
@@ -25,6 +26,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = {
/* eslint-disable @typescript-eslint/naming-convention */
'mention-user': MentionUserRenderer,
'mention-here': MentionHereRenderer,
+ emoji: EmojiRenderer,
'next-step-email': NextStepEmailRenderer,
/* eslint-enable @typescript-eslint/naming-convention */
};
diff --git a/src/components/InlineCodeBlock/getCurrentData.ts b/src/components/InlineCodeBlock/getCurrentData.ts
new file mode 100644
index 000000000000..591ec74c885d
--- /dev/null
+++ b/src/components/InlineCodeBlock/getCurrentData.ts
@@ -0,0 +1,11 @@
+import type {TDefaultRendererProps} from 'react-native-render-html';
+import type {TTextOrTPhrasing} from './types';
+
+// Create a temporary solution to display when there are emojis in the inline code block
+// We can remove this after https://github.com/Expensify/App/issues/14676 is fixed
+export default function getCurrentData(defaultRendererProps: TDefaultRendererProps): string {
+ if ('data' in defaultRendererProps.tnode) {
+ return defaultRendererProps.tnode.data;
+ }
+ return defaultRendererProps.tnode.children.map((child) => ('data' in child ? child.data : '')).join('');
+}
diff --git a/src/components/InlineCodeBlock/index.native.tsx b/src/components/InlineCodeBlock/index.native.tsx
index 85d02b7239ca..1c8a1bea4312 100644
--- a/src/components/InlineCodeBlock/index.native.tsx
+++ b/src/components/InlineCodeBlock/index.native.tsx
@@ -1,11 +1,13 @@
import React from 'react';
import useThemeStyles from '@hooks/useThemeStyles';
+import getCurrentData from './getCurrentData';
import type InlineCodeBlockProps from './types';
import type {TTextOrTPhrasing} from './types';
import WrappedText from './WrappedText';
function InlineCodeBlock({TDefaultRenderer, defaultRendererProps, textStyle, boxModelStyle}: InlineCodeBlockProps) {
const styles = useThemeStyles();
+ const data = getCurrentData(defaultRendererProps);
return (
({TDefaultRenderer,
textStyles={textStyle}
wordStyles={[boxModelStyle, styles.codeWordStyle]}
>
- {'data' in defaultRendererProps.tnode && defaultRendererProps.tnode.data}
+ {data}
);
diff --git a/src/components/InlineCodeBlock/index.tsx b/src/components/InlineCodeBlock/index.tsx
index 593a08aaad5e..26a4e8b7a31f 100644
--- a/src/components/InlineCodeBlock/index.tsx
+++ b/src/components/InlineCodeBlock/index.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import {StyleSheet} from 'react-native';
import Text from '@components/Text';
+import getCurrentData from './getCurrentData';
import type InlineCodeBlockProps from './types';
import type {TTextOrTPhrasing} from './types';
@@ -8,12 +9,14 @@ function InlineCodeBlock({TDefaultRenderer,
const flattenTextStyle = StyleSheet.flatten(textStyle);
const {textDecorationLine, ...textStyles} = flattenTextStyle;
+ const data = getCurrentData(defaultRendererProps);
+
return (
- {'data' in defaultRendererProps.tnode && defaultRendererProps.tnode.data}
+ {data}
);
}
diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts
index 29781e718c6f..05f6fbd17503 100644
--- a/src/libs/EmojiUtils.ts
+++ b/src/libs/EmojiUtils.ts
@@ -37,7 +37,10 @@ const findEmojiByName = (name: string): Emoji => Emojis.emojiNameTable[name];
const findEmojiByCode = (code: string): Emoji => Emojis.emojiCodeTableWithSkinTones[code];
-const getEmojiName = (emoji: Emoji, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT): string => {
+const getEmojiName = (emoji: Emoji, lang: Locale = CONST.LOCALES.DEFAULT): string => {
+ if (!emoji) {
+ return '';
+ }
if (lang === CONST.LOCALES.DEFAULT) {
return emoji.name;
}
diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx
index 951888a443c1..7ff413f554b8 100644
--- a/src/pages/home/report/comment/TextCommentFragment.tsx
+++ b/src/pages/home/report/comment/TextCommentFragment.tsx
@@ -16,6 +16,7 @@ import CONST from '@src/CONST';
import type {OriginalMessageSource} from '@src/types/onyx/OriginalMessage';
import type {Message} from '@src/types/onyx/ReportAction';
import RenderCommentHTML from './RenderCommentHTML';
+import shouldRenderAsText from './shouldRenderAsText';
type TextCommentFragmentProps = {
/** The reportAction's source */
@@ -47,17 +48,17 @@ function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, so
const {translate} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
- // If the only difference between fragment.text and fragment.html is
tags
- // we render it as text, not as html.
- // This is done to render emojis with line breaks between them as text.
- const differByLineBreaksOnly = Str.replaceAll(html, '
', '\n') === text;
-
- // Only render HTML if we have html in the fragment
- if (!differByLineBreaksOnly) {
+ // If the only difference between fragment.text and fragment.html is
tags and emoji tag
+ // on native, we render it as text, not as html
+ // on other device, only render it as text if the only difference is
tag
+ const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text);
+ if (!shouldRenderAsText(html, text) && !(containsOnlyEmojis && styleAsDeleted)) {
const editedTag = fragment.isEdited ? `` : '';
- const htmlContent = styleAsDeleted ? `${html}` : html;
+ const htmlWithDeletedTag = styleAsDeleted ? `${html}` : html;
+ const htmlContent = containsOnlyEmojis ? Str.replaceAll(htmlWithDeletedTag, '', '') : htmlWithDeletedTag;
let htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent;
+
if (styleAsMuted) {
htmlWithTag = `${htmlWithTag}`;
}
@@ -70,7 +71,6 @@ function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, so
);
}
- const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text);
const message = isEmpty(iouMessage) ? text : iouMessage;
return (
diff --git a/src/pages/home/report/comment/shouldRenderAsText/index.native.ts b/src/pages/home/report/comment/shouldRenderAsText/index.native.ts
new file mode 100644
index 000000000000..7c5758f8720d
--- /dev/null
+++ b/src/pages/home/report/comment/shouldRenderAsText/index.native.ts
@@ -0,0 +1,12 @@
+import Str from 'expensify-common/lib/str';
+
+/**
+ * Whether to render the report action as text
+ */
+export default function shouldRenderAsText(html: string, text: string): boolean {
+ // On native, we render emoji as text to prevent the large emoji is cut off when the action is edited.
+ // More info: https://github.com/Expensify/App/pull/35838#issuecomment-1964839350
+ const htmlWithoutLineBreak = Str.replaceAll(html, '
', '\n');
+ const htmlWithoutEmojiOpenTag = Str.replaceAll(htmlWithoutLineBreak, '', '');
+ return Str.replaceAll(htmlWithoutEmojiOpenTag, '', '') === text;
+}
diff --git a/src/pages/home/report/comment/shouldRenderAsText/index.ts b/src/pages/home/report/comment/shouldRenderAsText/index.ts
new file mode 100644
index 000000000000..f26f43c528eb
--- /dev/null
+++ b/src/pages/home/report/comment/shouldRenderAsText/index.ts
@@ -0,0 +1,8 @@
+import Str from 'expensify-common/lib/str';
+
+/**
+ * Whether to render the report action as text
+ */
+export default function shouldRenderAsText(html: string, text: string): boolean {
+ return Str.replaceAll(html, '
', '\n') === text;
+}
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 8a65cabaf19f..b0c88d151484 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -286,6 +286,10 @@ const styles = (theme: ThemeColors) =>
...wordBreak.breakWord,
...spacing.pr4,
},
+ emojiTooltipWrapper: {
+ ...spacing.p2,
+ borderRadius: 8,
+ },
mentionSuggestionsAvatarContainer: {
width: 24,