Skip to content

Commit 061e432

Browse files
Merge pull request #35838 from dukenv0307/fix/34307
Handle emoji tooltip
2 parents 982e2ee + bcb3510 commit 061e432

File tree

13 files changed

+120
-13
lines changed

13 files changed

+120
-13
lines changed

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
"date-fns-tz": "^2.0.0",
102102
"dom-serializer": "^0.2.2",
103103
"domhandler": "^4.3.0",
104-
"expensify-common": "git+ssh://[email protected]/Expensify/expensify-common.git#a8ed0f8e1be3a1e09016e07a74cfd13c85bbc167",
104+
"expensify-common": "git+ssh://[email protected]/Expensify/expensify-common.git#45d3b61bb38b4f9a19ddf573ce1e212369b242db",
105105
"expo": "^50.0.3",
106106
"expo-av": "~13.10.4",
107107
"expo-image": "1.10.1",
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Text from '@components/Text';
2+
import type EmojiWithTooltipProps from './types';
3+
4+
function EmojiWithTooltip({emojiCode, style = {}}: EmojiWithTooltipProps) {
5+
return <Text style={style}>{emojiCode}</Text>;
6+
}
7+
8+
EmojiWithTooltip.displayName = 'EmojiWithTooltip';
9+
10+
export default EmojiWithTooltip;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React, {useCallback} from 'react';
2+
import {View} from 'react-native';
3+
import Text from '@components/Text';
4+
import Tooltip from '@components/Tooltip';
5+
import useThemeStyles from '@hooks/useThemeStyles';
6+
import * as EmojiUtils from '@libs/EmojiUtils';
7+
import type EmojiWithTooltipProps from './types';
8+
9+
function EmojiWithTooltip({emojiCode, style = {}}: EmojiWithTooltipProps) {
10+
const styles = useThemeStyles();
11+
const emoji = EmojiUtils.findEmojiByCode(emojiCode);
12+
const emojiName = EmojiUtils.getEmojiName(emoji);
13+
14+
const emojiTooltipContent = useCallback(
15+
() => (
16+
<View style={[styles.alignItemsCenter, styles.ph2]}>
17+
<View style={[styles.flexRow, styles.emojiTooltipWrapper]}>
18+
<Text
19+
key={emojiCode}
20+
style={styles.onlyEmojisText}
21+
>
22+
{emojiCode}
23+
</Text>
24+
</View>
25+
<Text style={[styles.textMicro, styles.fontColorReactionLabel]}>{`:${emojiName}:`}</Text>
26+
</View>
27+
),
28+
[emojiCode, emojiName, styles.alignItemsCenter, styles.ph2, styles.flexRow, styles.emojiTooltipWrapper, styles.fontColorReactionLabel, styles.onlyEmojisText, styles.textMicro],
29+
);
30+
31+
return (
32+
<Tooltip renderTooltipContent={emojiTooltipContent}>
33+
<Text style={[style, styles.cursorDefault]}>{emojiCode}</Text>
34+
</Tooltip>
35+
);
36+
}
37+
38+
EmojiWithTooltip.displayName = 'EmojiWithTooltip';
39+
40+
export default EmojiWithTooltip;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type {StyleProp, TextStyle} from 'react-native';
2+
3+
type EmojiWithTooltipProps = {
4+
emojiCode: string;
5+
style?: StyleProp<TextStyle>;
6+
};
7+
8+
export default EmojiWithTooltipProps;

src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim
7070
mixedUAStyles: {whiteSpace: 'pre'},
7171
contentModel: HTMLContentModel.block,
7272
}),
73+
emoji: HTMLElementModel.fromCustomModel({tagName: 'emoji', contentModel: HTMLContentModel.textual}),
7374
}),
7475
[styles.colorMuted, styles.formError, styles.mb0, styles.textLabelSupporting, styles.lh16],
7576
);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
3+
import EmojiWithTooltip from '@components/EmojiWithTooltip';
4+
import useThemeStyles from '@hooks/useThemeStyles';
5+
6+
function EmojiRenderer({tnode}: CustomRendererProps<TText | TPhrasing>) {
7+
const styles = useThemeStyles();
8+
const style = 'islarge' in tnode.attributes ? styles.onlyEmojisText : {};
9+
return (
10+
<EmojiWithTooltip
11+
style={[style, styles.cursorDefault]}
12+
emojiCode={'data' in tnode ? tnode.data : ''}
13+
/>
14+
);
15+
}
16+
17+
EmojiRenderer.displayName = 'EmojiRenderer';
18+
19+
export default EmojiRenderer;

src/components/HTMLEngineProvider/HTMLRenderers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {CustomTagRendererRecord} from 'react-native-render-html';
22
import AnchorRenderer from './AnchorRenderer';
33
import CodeRenderer from './CodeRenderer';
44
import EditedRenderer from './EditedRenderer';
5+
import EmojiRenderer from './EmojiRenderer';
56
import ImageRenderer from './ImageRenderer';
67
import MentionHereRenderer from './MentionHereRenderer';
78
import MentionUserRenderer from './MentionUserRenderer';
@@ -25,6 +26,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = {
2526
/* eslint-disable @typescript-eslint/naming-convention */
2627
'mention-user': MentionUserRenderer,
2728
'mention-here': MentionHereRenderer,
29+
emoji: EmojiRenderer,
2830
'next-step-email': NextStepEmailRenderer,
2931
/* eslint-enable @typescript-eslint/naming-convention */
3032
};

src/libs/EmojiUtils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ const findEmojiByName = (name: string): Emoji => Emojis.emojiNameTable[name];
3838
const findEmojiByCode = (code: string): Emoji => Emojis.emojiCodeTableWithSkinTones[code];
3939

4040
const getEmojiName = (emoji: Emoji, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT): string => {
41+
if (!emoji) {
42+
return '';
43+
}
4144
if (lang === CONST.LOCALES.DEFAULT) {
4245
return emoji.name;
4346
}

src/pages/home/report/comment/TextCommentFragment.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import CONST from '@src/CONST';
1616
import type {OriginalMessageSource} from '@src/types/onyx/OriginalMessage';
1717
import type {Message} from '@src/types/onyx/ReportAction';
1818
import RenderCommentHTML from './RenderCommentHTML';
19+
import shouldRenderAsText from './shouldRenderAsText';
1920

2021
type TextCommentFragmentProps = {
2122
/** The reportAction's source */
@@ -44,15 +45,15 @@ function TextCommentFragment({fragment, styleAsDeleted, source, style, displayAs
4445
const {translate} = useLocalize();
4546
const {isSmallScreenWidth} = useWindowDimensions();
4647

47-
// If the only difference between fragment.text and fragment.html is <br /> tags
48-
// we render it as text, not as html.
49-
// This is done to render emojis with line breaks between them as text.
50-
const differByLineBreaksOnly = Str.replaceAll(html, '<br />', '\n') === text;
51-
52-
// Only render HTML if we have html in the fragment
53-
if (!differByLineBreaksOnly) {
48+
// If the only difference between fragment.text and fragment.html is <br /> tags and emoji tag
49+
// on native, we render it as text, not as html
50+
// on other device, only render it as text if the only difference is <br /> tag
51+
const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text);
52+
if (!shouldRenderAsText(html, text) && !(containsOnlyEmojis && styleAsDeleted)) {
5453
const editedTag = fragment.isEdited ? `<edited ${styleAsDeleted ? 'deleted' : ''}></edited>` : '';
55-
const htmlContent = styleAsDeleted ? `<del>${html}</del>` : html;
54+
const htmlWithDeletedTag = styleAsDeleted ? `<del>${html}</del>` : html;
55+
56+
const htmlContent = containsOnlyEmojis ? Str.replaceAll(htmlWithDeletedTag, '<emoji>', '<emoji islarge>') : htmlWithDeletedTag;
5657

5758
const htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent;
5859

@@ -64,7 +65,6 @@ function TextCommentFragment({fragment, styleAsDeleted, source, style, displayAs
6465
);
6566
}
6667

67-
const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text);
6868
const message = isEmpty(iouMessage) ? text : iouMessage;
6969

7070
return (
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Str from 'expensify-common/lib/str';
2+
3+
/**
4+
* Whether to render the report action as text
5+
*/
6+
export default function shouldRenderAsText(html: string, text: string): boolean {
7+
// On native, we render emoji as text to prevent the large emoji is cut off when the action is edited.
8+
// More info: https://github.com/Expensify/App/pull/35838#issuecomment-1964839350
9+
const htmlWithoutLineBreak = Str.replaceAll(html, '<br />', '\n');
10+
const htmlWithoutEmojiOpenTag = Str.replaceAll(htmlWithoutLineBreak, '<emoji>', '');
11+
return Str.replaceAll(htmlWithoutEmojiOpenTag, '</emoji>', '') === text;
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Str from 'expensify-common/lib/str';
2+
3+
/**
4+
* Whether to render the report action as text
5+
*/
6+
export default function shouldRenderAsText(html: string, text: string): boolean {
7+
return Str.replaceAll(html, '<br />', '\n') === text;
8+
}

src/styles/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ const styles = (theme: ThemeColors) =>
286286
...wordBreak.breakWord,
287287
...spacing.pr4,
288288
},
289+
emojiTooltipWrapper: {
290+
...spacing.p2,
291+
borderRadius: 8,
292+
},
289293

290294
mentionSuggestionsAvatarContainer: {
291295
width: 24,

0 commit comments

Comments
 (0)