Skip to content

Commit

Permalink
Merge pull request #37814 from dukenv0307/fix/34307
Browse files Browse the repository at this point in the history
Handle emoji tooltip and fix regression
  • Loading branch information
stitesExpensify authored Mar 21, 2024
2 parents 1b6054b + ba37e26 commit 106c367
Show file tree
Hide file tree
Showing 16 changed files with 146 additions and 21 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
"expensify-common": "git+ssh://[email protected]/Expensify/expensify-common.git#615f4a8662cd1abea9fdeee4d04847197c5e36ae",
"expensify-common": "git+ssh://[email protected]/Expensify/expensify-common.git#4e020cfa13ffabde14313c92b341285aeb919f29",
"expo": "^50.0.3",
"expo-av": "~13.10.4",
"expo-image": "1.11.0",
Expand Down
10 changes: 10 additions & 0 deletions src/components/EmojiWithTooltip/index.native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Text from '@components/Text';
import type EmojiWithTooltipProps from './types';

function EmojiWithTooltip({emojiCode, style = {}}: EmojiWithTooltipProps) {
return <Text style={style}>{emojiCode}</Text>;
}

EmojiWithTooltip.displayName = 'EmojiWithTooltip';

export default EmojiWithTooltip;
42 changes: 42 additions & 0 deletions src/components/EmojiWithTooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -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(
() => (
<View style={[styles.alignItemsCenter, styles.ph2]}>
<View style={[styles.flexRow, styles.emojiTooltipWrapper]}>
<Text
key={emojiCode}
style={styles.onlyEmojisText}
>
{emojiCode}
</Text>
</View>
<Text style={[styles.textMicro, styles.fontColorReactionLabel]}>{`:${emojiName}:`}</Text>
</View>
),
[emojiCode, emojiName, styles.alignItemsCenter, styles.ph2, styles.flexRow, styles.emojiTooltipWrapper, styles.fontColorReactionLabel, styles.onlyEmojisText, styles.textMicro],
);

return (
<Tooltip renderTooltipContent={emojiTooltipContent}>
<Text style={[style, styles.cursorDefault]}>{emojiCode}</Text>
</Tooltip>
);
}

EmojiWithTooltip.displayName = 'EmojiWithTooltip';

export default EmojiWithTooltip;
8 changes: 8 additions & 0 deletions src/components/EmojiWithTooltip/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type {StyleProp, TextStyle} from 'react-native';

type EmojiWithTooltipProps = {
emojiCode: string;
style?: StyleProp<TextStyle>;
};

export default EmojiWithTooltipProps;
Original file line number Diff line number Diff line change
Expand Up @@ -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],
);
Expand Down
19 changes: 19 additions & 0 deletions src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx
Original file line number Diff line number Diff line change
@@ -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<TText | TPhrasing>) {
const styles = useThemeStyles();
const style = 'islarge' in tnode.attributes ? styles.onlyEmojisText : {};
return (
<EmojiWithTooltip
style={[style, styles.cursorDefault]}
emojiCode={'data' in tnode ? tnode.data : ''}
/>
);
}

EmojiRenderer.displayName = 'EmojiRenderer';

export default EmojiRenderer;
2 changes: 2 additions & 0 deletions src/components/HTMLEngineProvider/HTMLRenderers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 */
};
Expand Down
11 changes: 11 additions & 0 deletions src/components/InlineCodeBlock/getCurrentData.ts
Original file line number Diff line number Diff line change
@@ -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<TTextOrTPhrasing>): string {
if ('data' in defaultRendererProps.tnode) {
return defaultRendererProps.tnode.data;
}
return defaultRendererProps.tnode.children.map((child) => ('data' in child ? child.data : '')).join('');
}
4 changes: 3 additions & 1 deletion src/components/InlineCodeBlock/index.native.tsx
Original file line number Diff line number Diff line change
@@ -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<TComponent extends TTextOrTPhrasing>({TDefaultRenderer, defaultRendererProps, textStyle, boxModelStyle}: InlineCodeBlockProps<TComponent>) {
const styles = useThemeStyles();
const data = getCurrentData(defaultRendererProps);

return (
<TDefaultRenderer
Expand All @@ -16,7 +18,7 @@ function InlineCodeBlock<TComponent extends TTextOrTPhrasing>({TDefaultRenderer,
textStyles={textStyle}
wordStyles={[boxModelStyle, styles.codeWordStyle]}
>
{'data' in defaultRendererProps.tnode && defaultRendererProps.tnode.data}
{data}
</WrappedText>
</TDefaultRenderer>
);
Expand Down
5 changes: 4 additions & 1 deletion src/components/InlineCodeBlock/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
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';

function InlineCodeBlock<TComponent extends TTextOrTPhrasing>({TDefaultRenderer, textStyle, defaultRendererProps, boxModelStyle}: InlineCodeBlockProps<TComponent>) {
const flattenTextStyle = StyleSheet.flatten(textStyle);
const {textDecorationLine, ...textStyles} = flattenTextStyle;

const data = getCurrentData(defaultRendererProps);

return (
<TDefaultRenderer
// eslint-disable-next-line react/jsx-props-no-spreading
{...defaultRendererProps}
>
<Text style={[boxModelStyle, textStyles]}>{'data' in defaultRendererProps.tnode && defaultRendererProps.tnode.data}</Text>
<Text style={[boxModelStyle, textStyles]}>{data}</Text>
</TDefaultRenderer>
);
}
Expand Down
5 changes: 4 additions & 1 deletion src/libs/EmojiUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
18 changes: 9 additions & 9 deletions src/pages/home/report/comment/TextCommentFragment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 <br /> 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, '<br />', '\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 <br /> 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 <br /> tag
const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text);
if (!shouldRenderAsText(html, text) && !(containsOnlyEmojis && styleAsDeleted)) {
const editedTag = fragment.isEdited ? `<edited ${styleAsDeleted ? 'deleted' : ''}></edited>` : '';
const htmlContent = styleAsDeleted ? `<del>${html}</del>` : html;
const htmlWithDeletedTag = styleAsDeleted ? `<del>${html}</del>` : html;

const htmlContent = containsOnlyEmojis ? Str.replaceAll(htmlWithDeletedTag, '<emoji>', '<emoji islarge>') : htmlWithDeletedTag;
let htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent;

if (styleAsMuted) {
htmlWithTag = `<muted-text>${htmlWithTag}<muted-text>`;
}
Expand All @@ -70,7 +71,6 @@ function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, so
);
}

const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text);
const message = isEmpty(iouMessage) ? text : iouMessage;

return (
Expand Down
12 changes: 12 additions & 0 deletions src/pages/home/report/comment/shouldRenderAsText/index.native.ts
Original file line number Diff line number Diff line change
@@ -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, '<br />', '\n');
const htmlWithoutEmojiOpenTag = Str.replaceAll(htmlWithoutLineBreak, '<emoji>', '');
return Str.replaceAll(htmlWithoutEmojiOpenTag, '</emoji>', '') === text;
}
8 changes: 8 additions & 0 deletions src/pages/home/report/comment/shouldRenderAsText/index.ts
Original file line number Diff line number Diff line change
@@ -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, '<br />', '\n') === text;
}
4 changes: 4 additions & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,10 @@ const styles = (theme: ThemeColors) =>
...wordBreak.breakWord,
...spacing.pr4,
},
emojiTooltipWrapper: {
...spacing.p2,
borderRadius: 8,
},

mentionSuggestionsAvatarContainer: {
width: 24,
Expand Down

0 comments on commit 106c367

Please sign in to comment.