-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enlarge emojis in other contexts than just single character messages #47547
Changes from all commits
8d93e58
8fdd879
1a776ab
957737a
18997d9
27fb058
3ab61f4
2b0caa2
e750501
3175d40
8b0828b
776aacd
6981d7c
41ffb05
2007c58
9250cd9
46ddde2
2dd7773
cc8e8b1
435e777
d8a3fd2
6ef2595
25a4ee9
7cf9b77
3af678f
272a32d
87e41e5
dda7fdf
0705fee
8ecc202
3e2f369
4f4ca61
9c30d73
528c08f
eb3a958
1befeeb
ba756a5
4713c7e
35af453
70190a4
743d3c2
bce816d
9a4883b
cde40ee
8e703d2
592dc56
8010e02
b2fe231
0c14e87
8241ac7
4c584f0
9d4a4e2
8457803
471c98e
8d036e7
921b364
582d4ba
068a496
5301fe8
fb197bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ import * as Browser from '@libs/Browser'; | |
import * as EmojiUtils from '@libs/EmojiUtils'; | ||
import * as FileUtils from '@libs/fileDownload/FileUtils'; | ||
import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition'; | ||
import variables from '@styles/variables'; | ||
import CONST from '@src/CONST'; | ||
|
||
const excludeNoStyles: Array<keyof MarkdownStyle> = []; | ||
|
@@ -70,6 +71,7 @@ function Composer( | |
start: selectionProp.start, | ||
end: selectionProp.end, | ||
}); | ||
const [hasMultipleLines, setHasMultipleLines] = useState(false); | ||
const [isRendered, setIsRendered] = useState(false); | ||
const isScrollBarVisible = useIsScrollBarVisible(textInput, value ?? ''); | ||
const [prevScroll, setPrevScroll] = useState<number | undefined>(); | ||
|
@@ -328,10 +330,10 @@ function Composer( | |
scrollStyleMemo, | ||
StyleUtils.getComposerMaxHeightStyle(maxLines, isComposerFullSize), | ||
isComposerFullSize ? {height: '100%', maxHeight: 'none'} : undefined, | ||
textContainsOnlyEmojis ? styles.onlyEmojisTextLineHeight : {}, | ||
textContainsOnlyEmojis && hasMultipleLines ? styles.onlyEmojisTextLineHeight : {}, | ||
Comment on lines
-331
to
+333
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @VickyStash any idea why this was added here? It causes #55068 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @CyberAndrii Without There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We decide to remove |
||
], | ||
|
||
[style, styles.rtlTextRenderForSafari, styles.onlyEmojisTextLineHeight, scrollStyleMemo, StyleUtils, maxLines, isComposerFullSize, textContainsOnlyEmojis], | ||
[style, styles.rtlTextRenderForSafari, styles.onlyEmojisTextLineHeight, scrollStyleMemo, hasMultipleLines, StyleUtils, maxLines, isComposerFullSize, textContainsOnlyEmojis], | ||
); | ||
|
||
return ( | ||
|
@@ -350,6 +352,9 @@ function Composer( | |
/* eslint-disable-next-line react/jsx-props-no-spreading */ | ||
{...props} | ||
onSelectionChange={addCursorPositionToSelectionChange} | ||
onContentSizeChange={(e) => { | ||
setHasMultipleLines(e.nativeEvent.contentSize.height > variables.componentSizeLarge); | ||
}} | ||
disabled={isDisabled} | ||
onKeyPress={handleKeyPress} | ||
addAuthTokenToImageURLCallback={addEncryptedAuthTokenToURL} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import React from 'react'; | ||
import Text from '@components/Text'; | ||
import useThemeStyles from '@hooks/useThemeStyles'; | ||
import * as EmojiUtils from '@libs/EmojiUtils'; | ||
import type WorkspacesListRowDisplayNameProps from './types'; | ||
|
||
function WorkspacesListRowDisplayName({isDeleted, ownerName}: WorkspacesListRowDisplayNameProps) { | ||
const styles = useThemeStyles(); | ||
const processedOwnerName = EmojiUtils.splitTextWithEmojis(ownerName); | ||
|
||
return ( | ||
<Text | ||
numberOfLines={1} | ||
style={[styles.labelStrong, isDeleted ? styles.offlineFeedback.deleted : {}]} | ||
> | ||
{processedOwnerName.length !== 0 | ||
? EmojiUtils.getProcessedText(processedOwnerName, [styles.labelStrong, isDeleted ? styles.offlineFeedback.deleted : {}, styles.emojisWithTextFontFamily]) | ||
: ownerName} | ||
</Text> | ||
); | ||
} | ||
|
||
WorkspacesListRowDisplayName.displayName = 'WorkspacesListRowDisplayName'; | ||
|
||
export default WorkspacesListRowDisplayName; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React from 'react'; | ||
import Text from '@components/Text'; | ||
import useThemeStyles from '@hooks/useThemeStyles'; | ||
import type WorkspacesListRowDisplayNameProps from './types'; | ||
|
||
function WorkspacesListRowDisplayName({isDeleted, ownerName}: WorkspacesListRowDisplayNameProps) { | ||
const styles = useThemeStyles(); | ||
|
||
return ( | ||
<Text | ||
numberOfLines={1} | ||
style={[styles.labelStrong, isDeleted ? styles.offlineFeedback.deleted : {}]} | ||
> | ||
{ownerName} | ||
</Text> | ||
); | ||
} | ||
|
||
WorkspacesListRowDisplayName.displayName = 'WorkspacesListRowDisplayName'; | ||
|
||
export default WorkspacesListRowDisplayName; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
type WorkspacesListRowDisplayNameProps = { | ||
/** Should the deleted style be applied */ | ||
isDeleted: boolean; | ||
|
||
/** Workspace owner name */ | ||
ownerName: string; | ||
}; | ||
|
||
export default WorkspacesListRowDisplayNameProps; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,12 @@ | ||
import {Str} from 'expensify-common'; | ||
import lodashSortBy from 'lodash/sortBy'; | ||
import React from 'react'; | ||
import type {StyleProp, TextStyle} from 'react-native'; | ||
import Onyx from 'react-native-onyx'; | ||
import type {OnyxEntry} from 'react-native-onyx'; | ||
import * as Emojis from '@assets/emojis'; | ||
import type {Emoji, HeaderEmoji, PickerEmojis} from '@assets/emojis/types'; | ||
import Text from '@components/Text'; | ||
import CONST from '@src/CONST'; | ||
import ONYXKEYS from '@src/ONYXKEYS'; | ||
import type {FrequentlyUsedEmoji, Locale} from '@src/types/onyx'; | ||
|
@@ -19,6 +22,10 @@ type EmojiPickerListItem = EmojiSpacer | Emoji | HeaderEmoji; | |
type EmojiPickerList = EmojiPickerListItem[]; | ||
type ReplacedEmoji = {text: string; emojis: Emoji[]; cursorPosition?: number}; | ||
type EmojiTrieModule = {default: typeof EmojiTrie}; | ||
type TextWithEmoji = { | ||
text: string; | ||
isEmoji: boolean; | ||
}; | ||
|
||
const findEmojiByName = (name: string): Emoji => Emojis.emojiNameTable[name]; | ||
|
||
|
@@ -151,7 +158,7 @@ function trimEmojiUnicode(emojiCode: string): string { | |
*/ | ||
function isFirstLetterEmoji(message: string): boolean { | ||
const trimmedMessage = Str.replaceAll(message.replace(/ /g, ''), '\n', ''); | ||
const match = trimmedMessage.match(CONST.REGEX.EMOJIS); | ||
const match = trimmedMessage.match(CONST.REGEX.ALL_EMOJIS); | ||
|
||
if (!match) { | ||
return false; | ||
|
@@ -165,7 +172,7 @@ function isFirstLetterEmoji(message: string): boolean { | |
*/ | ||
function containsOnlyEmojis(message: string): boolean { | ||
const trimmedMessage = Str.replaceAll(message.replace(/ /g, ''), '\n', ''); | ||
const match = trimmedMessage.match(CONST.REGEX.EMOJIS); | ||
const match = trimmedMessage.match(CONST.REGEX.ALL_EMOJIS); | ||
|
||
if (!match) { | ||
return false; | ||
|
@@ -288,7 +295,7 @@ function extractEmojis(text: string): Emoji[] { | |
} | ||
|
||
// Parse Emojis including skin tones - Eg: ['👩🏻', '👩🏻', '👩🏼', '👩🏻', '👩🏼', '👩'] | ||
const parsedEmojis = text.match(CONST.REGEX.EMOJIS); | ||
const parsedEmojis = text.match(CONST.REGEX.ALL_EMOJIS); | ||
|
||
if (!parsedEmojis) { | ||
return []; | ||
|
@@ -598,13 +605,83 @@ function getSpacersIndexes(allEmojis: EmojiPickerList): number[] { | |
return spacersIndexes; | ||
} | ||
|
||
/** Splits the text with emojis into array if emojis exist in the text */ | ||
function splitTextWithEmojis(text = ''): TextWithEmoji[] { | ||
VickyStash marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!text) { | ||
return []; | ||
} | ||
|
||
const doesTextContainEmojis = new RegExp(CONST.REGEX.EMOJIS, CONST.REGEX.EMOJIS.flags.concat('g')).test(text); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we not use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replacing it here with |
||
|
||
if (!doesTextContainEmojis) { | ||
return []; | ||
} | ||
|
||
// The regex needs to be cloned because `exec()` is a stateful operation and maintains the state inside | ||
// the regex variable itself, so we must have an independent instance for each function's call. | ||
const emojisRegex = new RegExp(CONST.REGEX.EMOJIS, CONST.REGEX.EMOJIS.flags.concat('g')); | ||
|
||
const splitText: TextWithEmoji[] = []; | ||
let regexResult: RegExpExecArray | null; | ||
let lastMatchIndexEnd = 0; | ||
|
||
do { | ||
regexResult = emojisRegex.exec(text); | ||
|
||
if (regexResult?.indices) { | ||
const matchIndexStart = regexResult.indices[0][0]; | ||
const matchIndexEnd = regexResult.indices[0][1]; | ||
|
||
if (matchIndexStart > lastMatchIndexEnd) { | ||
splitText.push({ | ||
text: text.slice(lastMatchIndexEnd, matchIndexStart), | ||
isEmoji: false, | ||
}); | ||
} | ||
|
||
splitText.push({ | ||
text: text.slice(matchIndexStart, matchIndexEnd), | ||
isEmoji: true, | ||
}); | ||
|
||
lastMatchIndexEnd = matchIndexEnd; | ||
} | ||
} while (regexResult !== null); | ||
|
||
if (lastMatchIndexEnd < text.length) { | ||
splitText.push({ | ||
text: text.slice(lastMatchIndexEnd, text.length), | ||
isEmoji: false, | ||
}); | ||
} | ||
|
||
return splitText; | ||
} | ||
|
||
function getProcessedText(processedTextArray: TextWithEmoji[], style: StyleProp<TextStyle>): Array<React.JSX.Element | string> { | ||
return processedTextArray.map(({text, isEmoji}, index) => | ||
isEmoji ? ( | ||
<Text | ||
// eslint-disable-next-line react/no-array-index-key | ||
key={index} | ||
style={style} | ||
> | ||
{text} | ||
</Text> | ||
) : ( | ||
text | ||
), | ||
); | ||
} | ||
|
||
export type {HeaderIndice, EmojiPickerList, EmojiSpacer, EmojiPickerListItem}; | ||
|
||
export { | ||
findEmojiByName, | ||
findEmojiByCode, | ||
getEmojiName, | ||
getLocalizedEmojiName, | ||
getProcessedText, | ||
getHeaderEmojis, | ||
mergeEmojisWithFrequentlyUsedEmojis, | ||
containsOnlyEmojis, | ||
|
@@ -623,4 +700,5 @@ export { | |
hasAccountIDEmojiReacted, | ||
getRemovedSkinToneEmoji, | ||
getSpacersIndexes, | ||
splitTextWithEmojis, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we even need this, or could we directly add the
g
flag inEMOJIS
above? As far as I can tell, the only place we useEMOJIS
is inEmojiUtils.splitTextWithEmojis
, and we also addg
flag there.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@francoisl I think the answer to your question is here:
#40692 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, thanks.
Is there any situation where we'll want to use
CONST.REGEX.EMOJIS
without theg
flag? If not, let's at least add the comment about cloning that's currently insplitTextWithEmojis()
in here, to add context to other people that read it and wouldn't know if they need to useEMOJIS
orALL_EMOJIS
.Ideally though, we could move it to a more private scope that's not available outside of this file, so that someone can't accidentally use
CONST.REGEX.EMOJIS
.