Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,8 @@ const CONST = {

EMOJI_FREQUENT_ROW_COUNT: 3,

EMOJI_INVISIBLE_CODEPOINT: 'fe0f',

TOOLTIP_MAX_LINES: 3,

LOGIN_TYPE: {
Expand Down
33 changes: 31 additions & 2 deletions src/libs/EmojiUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as User from './actions/User';
* @param {String} input
* @returns {String}
*/
function getEmojiUnicode(input) {
const getEmojiUnicode = _.memoize((input) => {
if (input.length === 0) {
return '';
}
Expand Down Expand Up @@ -40,7 +40,7 @@ function getEmojiUnicode(input) {
}
}
return _.map(pairs, val => parseInt(val, 10).toString(16)).join(' ');
}
});

/**
* Function to remove Skin Tone and utf16 surrogates from Emoji
Expand Down Expand Up @@ -70,6 +70,34 @@ function isSingleEmoji(message) {
return matchedUnicode === currentMessageUnicode;
}

/**
* Validates that this message contains only emojis
*
* @param {String} message
* @returns {Boolean}
*/
function containsOnlyEmojis(message) {
const trimmedMessage = message.replace(/ /g, '').replaceAll('\n', '');
const match = trimmedMessage.match(CONST.REGEX.EMOJIS);

if (!match) {
return false;
}

const codes = [];
_.map(match, emoji => _.map(getEmojiUnicode(emoji).split(' '), (code) => {
if (code !== CONST.EMOJI_INVISIBLE_CODEPOINT) {
codes.push(code);
}
return code;
}));

// Emojis are stored as multiple characters, so we're using spread operator
// to iterate over the actual emojis, not just characters that compose them
const messageCodes = _.filter(_.map([...trimmedMessage], char => getEmojiUnicode(char)), string => string.length > 0 && string !== CONST.EMOJI_INVISIBLE_CODEPOINT);
return codes.length === messageCodes.length;
}

/**
* Get the header indices based on the max emojis per row
* @param {Object[]} emojis
Expand Down Expand Up @@ -176,4 +204,5 @@ export {
getDynamicHeaderIndices,
mergeEmojisWithFrequentlyUsedEmojis,
addToFrequentlyUsedEmojis,
containsOnlyEmojis,
};
15 changes: 13 additions & 2 deletions src/pages/home/report/ReportActionItemFragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const defaultProps = {

const ReportActionItemFragment = (props) => {
switch (props.fragment.type) {
case 'COMMENT':
case 'COMMENT': {
// If this is an attachment placeholder, return the placeholder component
if (props.isAttachment && props.loading) {
return (
Expand Down Expand Up @@ -97,6 +97,16 @@ const ReportActionItemFragment = (props) => {
);
}

// If the only difference between fragment.text and fragment.html is <br /> tags
// we replace them with line breaks and render it as text, not as html.
// This is done to render emojis with line breaks between them as text.
const differByLineBreaksOnly = props.fragment.html.replaceAll('<br />', ' ') === props.fragment.text;
Comment thread
Julesssss marked this conversation as resolved.
if (differByLineBreaksOnly) {
const textWithLineBreaks = props.fragment.html.replaceAll('<br />', '\n');
// eslint-disable-next-line no-param-reassign
props.fragment = {...props.fragment, text: textWithLineBreaks, html: textWithLineBreaks};
}

// Only render HTML if we have html in the fragment
return props.fragment.html !== props.fragment.text
? (
Expand All @@ -106,7 +116,7 @@ const ReportActionItemFragment = (props) => {
) : (
<Text
selectable={!canUseTouchScreen() || !props.isSmallScreenWidth}
style={EmojiUtils.isSingleEmoji(props.fragment.text) ? styles.singleEmojiText : undefined}
style={EmojiUtils.containsOnlyEmojis(props.fragment.text) ? styles.onlyEmojisText : undefined}
>
{Str.htmlDecode(props.fragment.text)}
{props.fragment.isEdited && (
Expand All @@ -119,6 +129,7 @@ const ReportActionItemFragment = (props) => {
)}
</Text>
);
}
case 'TEXT':
return (
<Tooltip text={props.tooltipText}>
Expand Down
6 changes: 3 additions & 3 deletions src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -1042,9 +1042,9 @@ const styles = {
textDecorationLine: 'none',
},

singleEmojiText: {
fontSize: variables.fontSizeSingleEmoji,
lineHeight: variables.fontSizeSingleEmojiHeight,
onlyEmojisText: {
fontSize: variables.fontSizeOnlyEmojis,
lineHeight: variables.fontSizeOnlyEmojisHeight,
},

createMenuPositionSidebar: {
Expand Down
4 changes: 2 additions & 2 deletions src/styles/variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export default {
avatarSizeSmall: 28,
avatarSizeSubscript: 20,
avatarSizeSmallSubscript: 14,
fontSizeSingleEmoji: 30,
fontSizeSingleEmojiHeight: 35,
fontSizeOnlyEmojis: 30,
fontSizeOnlyEmojisHeight: 35,
fontSizeSmall: 11,
fontSizeExtraSmall: 9,
fontSizeLabel: 13,
Expand Down