-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
Copy pathTextCommentFragment.tsx
150 lines (131 loc) · 6.19 KB
/
TextCommentFragment.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import {Str} from 'expensify-common';
import isEmpty from 'lodash/isEmpty';
import React, {memo, useEffect, useMemo} from 'react';
import type {StyleProp, TextStyle} from 'react-native';
import Text from '@components/Text';
import ZeroWidthView from '@components/ZeroWidthView';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import convertToLTR from '@libs/convertToLTR';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as EmojiUtils from '@libs/EmojiUtils';
import Performance from '@libs/Performance';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import variables from '@styles/variables';
import Timing from '@userActions/Timing';
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';
import TextWithEmojiFragment from './TextWithEmojiFragment';
type TextCommentFragmentProps = {
/** The reportAction's source */
source: OriginalMessageSource;
/** The message fragment needing to be displayed */
fragment: Message | undefined;
/** Should this message fragment be styled as deleted? */
styleAsDeleted: boolean;
/** Should this message fragment be styled as muted */
styleAsMuted?: boolean;
/** Should the comment have the appearance of being grouped with the previous comment? */
displayAsGroup: boolean;
/** Additional styles to add after local styles. */
style: StyleProp<TextStyle>;
/** Text of an IOU report action */
iouMessage?: string;
};
function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, source, style, displayAsGroup, iouMessage = ''}: TextCommentFragmentProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {html = ''} = fragment ?? {};
const text = ReportActionsUtils.getTextFromHtml(html);
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const message = isEmpty(iouMessage) ? text : iouMessage;
const processedTextArray = useMemo(() => EmojiUtils.splitTextWithEmojis(message), [message]);
useEffect(() => {
Performance.markEnd(CONST.TIMING.SEND_MESSAGE, {message: text});
Timing.end(CONST.TIMING.SEND_MESSAGE);
}, [text]);
// 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 ?? '');
const containsEmojis = CONST.REGEX.ALL_EMOJIS.test(text ?? '');
if (!shouldRenderAsText(html, text ?? '') && !(containsOnlyEmojis && styleAsDeleted)) {
const editedTag = fragment?.isEdited ? `<edited ${styleAsDeleted ? 'deleted' : ''} ${containsOnlyEmojis ? 'islarge' : ''}></edited>` : '';
const htmlWithDeletedTag = styleAsDeleted ? `<del>${html}</del>` : html;
let htmlContent = htmlWithDeletedTag;
if (containsOnlyEmojis) {
htmlContent = Str.replaceAll(htmlContent, '<emoji>', '<emoji islarge>');
htmlContent = Str.replaceAll(htmlContent, '<blockquote>', '<blockquote isemojisonly>');
} else if (containsEmojis) {
htmlContent = Str.replaceAll(htmlWithDeletedTag, '<emoji>', '<emoji ismedium>');
}
let htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent;
if (styleAsMuted) {
htmlWithTag = `<muted-text>${htmlWithTag}<muted-text>`;
}
return (
<RenderCommentHTML
source={source}
html={htmlWithTag}
/>
);
}
return (
<Text style={[containsOnlyEmojis && styles.onlyEmojisText, styles.ltr, style]}>
<ZeroWidthView
text={text}
displayAsGroup={displayAsGroup}
/>
{processedTextArray.length !== 0 && !containsOnlyEmojis ? (
<TextWithEmojiFragment
message={message}
style={[
styles.ltr,
style,
styleAsDeleted ? styles.offlineFeedback.deleted : undefined,
styleAsMuted ? styles.colorMuted : undefined,
!DeviceCapabilities.canUseTouchScreen() || !shouldUseNarrowLayout ? styles.userSelectText : styles.userSelectNone,
]}
/>
) : (
<Text
style={[
containsOnlyEmojis ? styles.onlyEmojisText : undefined,
styles.ltr,
style,
styleAsDeleted ? styles.offlineFeedback.deleted : undefined,
styleAsMuted ? styles.colorMuted : undefined,
!DeviceCapabilities.canUseTouchScreen() || !shouldUseNarrowLayout ? styles.userSelectText : styles.userSelectNone,
]}
>
{convertToLTR(message ?? '')}
</Text>
)}
{!!fragment?.isEdited && (
<>
<Text
style={[containsOnlyEmojis && styles.onlyEmojisTextLineHeight, styles.userSelectNone]}
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
{' '}
</Text>
<Text
fontSize={variables.fontSizeSmall}
color={theme.textSupporting}
style={[styles.editedLabelStyles, styleAsDeleted && styles.offlineFeedback.deleted, style]}
>
{translate('reportActionCompose.edited')}
</Text>
</>
)}
</Text>
);
}
TextCommentFragment.displayName = 'TextCommentFragment';
export default memo(TextCommentFragment);