From 970353921483caf7d7977c862a8f22191ab81f85 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 27 Sep 2023 14:25:56 +0200 Subject: [PATCH 01/11] Migrate emoji utils lib to TS --- assets/emojis/{common.js => common.ts} | 5 +- assets/emojis/{en.js => en.ts} | 5 +- assets/emojis/{es.js => es.ts} | 5 +- assets/emojis/index.js | 41 ---- assets/emojis/index.ts | 35 ++++ assets/emojis/types.ts | 17 ++ src/libs/EmojiTrie.ts | 2 + src/libs/{EmojiUtils.js => EmojiUtils.ts} | 221 +++++++++------------- 8 files changed, 156 insertions(+), 175 deletions(-) rename assets/emojis/{common.js => common.ts} (99%) rename assets/emojis/{en.js => en.ts} (99%) rename assets/emojis/{es.js => es.ts} (99%) delete mode 100644 assets/emojis/index.js create mode 100644 assets/emojis/index.ts create mode 100644 assets/emojis/types.ts rename src/libs/{EmojiUtils.js => EmojiUtils.ts} (67%) diff --git a/assets/emojis/common.js b/assets/emojis/common.ts similarity index 99% rename from assets/emojis/common.js rename to assets/emojis/common.ts index e8a8b15c2dd5..dc3a78ff3228 100644 --- a/assets/emojis/common.js +++ b/assets/emojis/common.ts @@ -7,6 +7,7 @@ import Objects from '../images/emojiCategoryIcons/light-bulb.svg'; import Symbols from '../images/emojiCategoryIcons/peace-sign.svg'; import Flags from '../images/emojiCategoryIcons/flag.svg'; import FrequentlyUsed from '../images/history.svg'; +import {Emoji, HeaderEmoji} from './types'; const skinTones = [ { @@ -33,9 +34,9 @@ const skinTones = [ code: '🖐🏿', skinTone: 0, }, -]; +] as const; -const emojis = [ +const emojis: Array = [ { header: true, icon: Smiley, diff --git a/assets/emojis/en.js b/assets/emojis/en.ts similarity index 99% rename from assets/emojis/en.js rename to assets/emojis/en.ts index f32a91afe03c..0a1ca7611117 100644 --- a/assets/emojis/en.js +++ b/assets/emojis/en.ts @@ -1,4 +1,7 @@ -const enEmojis = { +import {EmojisList} from './types'; + +/* eslint-disable @typescript-eslint/naming-convention */ +const enEmojis: EmojisList = { '😀': { keywords: ['smile', 'happy', 'face', 'grin'], }, diff --git a/assets/emojis/es.js b/assets/emojis/es.ts similarity index 99% rename from assets/emojis/es.js rename to assets/emojis/es.ts index c38aec7aa754..4e8afa907bdf 100644 --- a/assets/emojis/es.js +++ b/assets/emojis/es.ts @@ -1,4 +1,7 @@ -const esEmojis = { +import {EmojisList} from './types'; + +/* eslint-disable @typescript-eslint/naming-convention */ +const esEmojis: EmojisList = { '😀': { name: 'sonriendo', keywords: ['cara', 'divertido', 'feliz', 'sonrisa', 'cara sonriendo'], diff --git a/assets/emojis/index.js b/assets/emojis/index.js deleted file mode 100644 index c8dab36f57d9..000000000000 --- a/assets/emojis/index.js +++ /dev/null @@ -1,41 +0,0 @@ -import _ from 'underscore'; -import emojis from './common'; -import enEmojis from './en'; -import esEmojis from './es'; - -const emojiNameTable = _.reduce( - emojis, - (prev, cur) => { - const newValue = prev; - if (!cur.header) { - newValue[cur.name] = cur; - } - return newValue; - }, - {}, -); - -const emojiCodeTableWithSkinTones = _.reduce( - emojis, - (prev, cur) => { - const newValue = prev; - if (!cur.header) { - newValue[cur.code] = cur; - } - if (cur.types) { - cur.types.forEach((type) => { - newValue[type] = cur; - }); - } - return newValue; - }, - {}, -); - -const localeEmojis = { - en: enEmojis, - es: esEmojis, -}; - -export {emojiNameTable, emojiCodeTableWithSkinTones, localeEmojis}; -export {skinTones, categoryFrequentlyUsed, default} from './common'; diff --git a/assets/emojis/index.ts b/assets/emojis/index.ts new file mode 100644 index 000000000000..37b3dc572025 --- /dev/null +++ b/assets/emojis/index.ts @@ -0,0 +1,35 @@ +import emojis from './common'; +import enEmojis from './en'; +import esEmojis from './es'; +import {Emoji} from './types'; + +type EmojiTable = Record; + +const emojiNameTable: EmojiTable = emojis.reduce((prev, cur) => { + const newValue: EmojiTable = prev; + if (!('header' in cur) && cur.name) { + newValue[cur.name] = cur; + } + return newValue; +}, {}); + +const emojiCodeTableWithSkinTones: EmojiTable = emojis.reduce((prev, cur) => { + const newValue: EmojiTable = prev; + if (!('header' in cur)) { + newValue[cur.code] = cur; + } + if ('types' in cur && cur.types) { + cur.types.forEach((type) => { + newValue[type] = cur; + }); + } + return newValue; +}, {}); + +const localeEmojis = { + en: enEmojis, + es: esEmojis, +} as const; + +export {emojiNameTable, emojiCodeTableWithSkinTones, localeEmojis}; +export {skinTones, categoryFrequentlyUsed, default} from './common'; diff --git a/assets/emojis/types.ts b/assets/emojis/types.ts new file mode 100644 index 000000000000..0fdd7086fdf5 --- /dev/null +++ b/assets/emojis/types.ts @@ -0,0 +1,17 @@ +import {SvgProps} from 'react-native-svg'; + +type Emoji = { + code: string; + name: string; + types?: string[]; +}; + +type HeaderEmoji = { + header: true; + icon: React.FC; + code: string; +}; + +type EmojisList = Record; + +export type {Emoji, HeaderEmoji, EmojisList}; diff --git a/src/libs/EmojiTrie.ts b/src/libs/EmojiTrie.ts index d0a53acf29c9..74025bb329c6 100644 --- a/src/libs/EmojiTrie.ts +++ b/src/libs/EmojiTrie.ts @@ -28,6 +28,8 @@ type Suggestion = { type EmojiMetaData = { suggestions?: Suggestion[]; + code?: string; + types?: string[]; }; Timing.start(CONST.TIMING.TRIE_INITIALIZATION); diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.ts similarity index 67% rename from src/libs/EmojiUtils.js rename to src/libs/EmojiUtils.ts index 80665541e24b..56956b679150 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.ts @@ -1,81 +1,75 @@ -import _ from 'underscore'; import moment from 'moment'; import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; -import lodashGet from 'lodash/get'; import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import emojisTrie from './EmojiTrie'; import * as Emojis from '../../assets/emojis'; +import * as OnyxTypes from '../types/onyx'; +import {Emoji, HeaderEmoji} from '../../assets/emojis/types'; +import {SvgProps} from 'react-native-svg'; -let frequentlyUsedEmojis = []; +type HeaderIndice = {code: string; index: number; icon: React.FC}; +type EmojiSpacer = {code: string; spacer: boolean}; + +let frequentlyUsedEmojis: Array = []; Onyx.connect({ key: ONYXKEYS.FREQUENTLY_USED_EMOJIS, callback: (val) => { - frequentlyUsedEmojis = _.map(val, (item) => { - const emoji = Emojis.emojiCodeTableWithSkinTones[item.code]; - if (emoji) { - return {...emoji, count: item.count, lastUpdatedAt: item.lastUpdatedAt}; - } - }); + if (!val) { + return; + } + frequentlyUsedEmojis = + val + ?.map((item) => { + const emoji = Emojis.emojiCodeTableWithSkinTones?.[item.code]; + if (emoji) { + return {...emoji, count: item.count, lastUpdatedAt: item.lastUpdatedAt}; + } + }) + .filter((emoji): emoji is OnyxTypes.FrequentlyUsedEmoji => !!emoji) ?? []; }, }); -/** - * - * @param {String} name - * @returns {Object} - */ -const findEmojiByName = (name) => Emojis.emojiNameTable[name]; +const findEmojiByName = (name: string) => Emojis.emojiNameTable[name]; -/** - * - * @param {String} code - * @returns {Object} - */ -const findEmojiByCode = (code) => Emojis.emojiCodeTableWithSkinTones[code]; +const findEmojiByCode = (code: string) => Emojis.emojiCodeTableWithSkinTones[code]; -/** - * - * @param {Object} emoji - * @param {String} lang - * @returns {String} - */ -const getEmojiName = (emoji, lang = CONST.LOCALES.DEFAULT) => { +const getEmojiName = (emoji: Emoji, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT) => { if (lang === CONST.LOCALES.DEFAULT) { return emoji.name; } - return _.get(Emojis.localeEmojis, [lang, emoji.code, 'name'], ''); + return Emojis.localeEmojis?.[lang]?.[emoji.code]?.name ?? ''; }; /** * Given an English emoji name, get its localized version - * - * @param {String} name - * @param {String} lang - * @returns {String} */ -const getLocalizedEmojiName = (name, lang) => { +const getLocalizedEmojiName = (name: string, lang: 'en' | 'es') => { if (lang === CONST.LOCALES.DEFAULT) { return name; } - return _.get(Emojis.localeEmojis, [lang, _.get(Emojis.emojiNameTable, [name, 'code'], ''), 'name'], ''); + const emojiCode = Emojis.emojiNameTable[name]?.code ?? ''; + return Emojis.localeEmojis[lang]?.[emojiCode]?.name ?? ''; }; /** * Get the unicode code of an emoji in base 16. - * @param {String} input - * @returns {String} */ -const getEmojiUnicode = _.memoize((input) => { +const getEmojiUnicode = _.memoize((input: string) => { if (input.length === 0) { return ''; } if (input.length === 1) { - return _.map(input.charCodeAt(0).toString().split(' '), (val) => parseInt(val, 10).toString(16)).join(' '); + return input + .charCodeAt(0) + .toString() + .split(' ') + .map((val) => parseInt(val, 10).toString(16)) + .join(' '); } const pairs = []; @@ -98,25 +92,20 @@ const getEmojiUnicode = _.memoize((input) => { pairs.push(input.charCodeAt(i)); } } - return _.map(pairs, (val) => parseInt(val, 10).toString(16)).join(' '); + return pairs.map((val) => parseInt(String(val), 10).toString(16)).join(' '); }); /** * Function to remove Skin Tone and utf16 surrogates from Emoji - * @param {String} emojiCode - * @returns {String} */ -function trimEmojiUnicode(emojiCode) { +function trimEmojiUnicode(emojiCode: string) { return emojiCode.replace(/(fe0f|1f3fb|1f3fc|1f3fd|1f3fe|1f3ff)$/, '').trim(); } /** * Validates that this message contains only emojis - * - * @param {String} message - * @returns {Boolean} */ -function containsOnlyEmojis(message) { +function containsOnlyEmojis(message: string): boolean { const trimmedMessage = Str.replaceAll(message.replace(/ /g, ''), '\n', ''); const match = trimmedMessage.match(CONST.REGEX.EMOJIS); @@ -125,33 +114,35 @@ function containsOnlyEmojis(message) { } const codes = []; - _.map(match, (emoji) => - _.map(getEmojiUnicode(emoji).split(' '), (code) => { - if (!CONST.INVISIBLE_CODEPOINTS.includes(code)) { - codes.push(code); - } - return code; - }), + match.map((emoji) => + getEmojiUnicode(emoji) + .split(' ') + .map((code) => { + if (!(CONST.INVISIBLE_CODEPOINTS as readonly string[]).includes(code)) { + //TODO - ask BK + 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 && !CONST.INVISIBLE_CODEPOINTS.includes(string), - ); + const messageCodes = [...trimmedMessage] + .map((char) => getEmojiUnicode(char)) + .filter((string) => string.length > 0 && !(CONST.INVISIBLE_CODEPOINTS as readonly string[]).includes(string)); return codes.length === messageCodes.length; } /** * Get the header emojis with their code, icon and index - * @param {Object[]} emojis - * @returns {Object[]} */ -function getHeaderEmojis(emojis) { - const headerIndices = []; - _.each(emojis, (emoji, index) => { - if (!emoji.header) { +function getHeaderEmojis(emojis: Array): HeaderIndice[] { + // TODO - move into separate type + const headerIndices: HeaderIndice[] = []; + emojis.forEach((emoji, index) => { + if (!('header' in emoji)) { + // TODO - ask BK why it doesn't work the other way return; } headerIndices.push({code: emoji.code, index, icon: emoji.icon}); @@ -161,11 +152,8 @@ function getHeaderEmojis(emojis) { /** * Get number of empty spaces to be filled to get equal emojis for every row - * @param {Number} emojiCount - * @param {Number} suffix - * @returns {Object[]} */ -function getDynamicSpacing(emojiCount, suffix) { +function getDynamicSpacing(emojiCount: number, suffix: number): EmojiSpacer[] { const spacerEmojis = []; let modLength = CONST.EMOJI_NUM_PER_ROW - (emojiCount % CONST.EMOJI_NUM_PER_ROW); @@ -182,13 +170,11 @@ function getDynamicSpacing(emojiCount, suffix) { /** * Add dynamic spaces to emoji categories - * @param {Object[]} emojis - * @returns {Object[]} */ -function addSpacesToEmojiCategories(emojis) { - let updatedEmojis = []; - _.each(emojis, (emoji, index) => { - if (emoji.header) { +function addSpacesToEmojiCategories(emojis: Array) { + let updatedEmojis: Array = []; + emojis.forEach((emoji, index) => { + if ('header' in emoji) { updatedEmojis = updatedEmojis.concat(getDynamicSpacing(updatedEmojis.length, index), [emoji], getDynamicSpacing(1, index)); return; } @@ -202,12 +188,12 @@ function addSpacesToEmojiCategories(emojis) { * @param {Object[]} emojis * @returns {Object[]} */ -function mergeEmojisWithFrequentlyUsedEmojis(emojis) { +function mergeEmojisWithFrequentlyUsedEmojis(emojis: Array) { if (frequentlyUsedEmojis.length === 0) { return addSpacesToEmojiCategories(emojis); } - const mergedEmojis = [Emojis.categoryFrequentlyUsed].concat(frequentlyUsedEmojis, emojis); + const mergedEmojis = ([Emojis.categoryFrequentlyUsed] as Array).concat(frequentlyUsedEmojis, emojis); return addSpacesToEmojiCategories(mergedEmojis); } @@ -216,14 +202,14 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis) { * @param {Object|Object[]} newEmoji * @return {Object[]} */ -function getFrequentlyUsedEmojis(newEmoji) { +function getFrequentlyUsedEmojis(newEmoji: Emoji | Emoji[]) { let frequentEmojiList = [...frequentlyUsedEmojis]; const maxFrequentEmojiCount = CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW - 1; const currentTimestamp = moment().unix(); - _.each([].concat(newEmoji), (emoji) => { + ([] as Emoji[]).concat(newEmoji).forEach((emoji) => { let currentEmojiCount = 1; - const emojiIndex = _.findIndex(frequentEmojiList, (e) => e.code === emoji.code); + const emojiIndex = frequentEmojiList.findIndex((e) => e.code === emoji.code); if (emojiIndex >= 0) { currentEmojiCount = frequentEmojiList[emojiIndex].count + 1; frequentEmojiList.splice(emojiIndex, 1); @@ -245,12 +231,8 @@ function getFrequentlyUsedEmojis(newEmoji) { /** * Given an emoji item object, return an emoji code based on its type. - * - * @param {Object} item - * @param {Number} preferredSkinToneIndex - * @returns {String} */ -const getEmojiCodeWithSkinColor = (item, preferredSkinToneIndex) => { +const getEmojiCodeWithSkinColor = (item: Emoji, preferredSkinToneIndex: number): string => { const {code, types} = item; if (types && types[preferredSkinToneIndex]) { return types[preferredSkinToneIndex]; @@ -262,10 +244,10 @@ const getEmojiCodeWithSkinColor = (item, preferredSkinToneIndex) => { /** * Extracts emojis from a given text. * - * @param {String} text - The text to extract emojis from. - * @returns {Object[]} An array of emoji codes. + * @param text - The text to extract emojis from. + * @returns An array of emoji codes. */ -function extractEmojis(text) { +function extractEmojis(text: string) { if (!text) { return []; } @@ -299,20 +281,15 @@ function extractEmojis(text) { /** * Replace any emoji name in a text with the emoji icon. * If we're on mobile, we also add a space after the emoji granted there's no text after it. - * - * @param {String} text - * @param {Number} preferredSkinTone - * @param {String} lang - * @returns {Object} */ -function replaceEmojis(text, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang = CONST.LOCALES.DEFAULT) { +function replaceEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT) { const trie = emojisTrie[lang]; if (!trie) { return {text, emojis: []}; } let newText = text; - const emojis = []; + const emojis: Emoji[] = []; const emojiData = text.match(CONST.REGEX.EMOJI_NAME); if (!emojiData || emojiData.length === 0) { return {text: newText, emojis}; @@ -322,18 +299,18 @@ function replaceEmojis(text, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, let checkEmoji = trie.search(name); // If the user has selected a language other than English, and the emoji doesn't exist in that language, // we will check if the emoji exists in English. - if (lang !== CONST.LOCALES.DEFAULT && (!checkEmoji || !checkEmoji.metaData.code)) { + if (lang !== CONST.LOCALES.DEFAULT && (!checkEmoji || !checkEmoji.metaData?.code)) { const englishTrie = emojisTrie[CONST.LOCALES.DEFAULT]; if (englishTrie) { const englishEmoji = englishTrie.search(name); checkEmoji = englishEmoji; } } - if (checkEmoji && checkEmoji.metaData.code) { + if (checkEmoji && checkEmoji.metaData?.code) { let emojiReplacement = getEmojiCodeWithSkinColor(checkEmoji.metaData, preferredSkinTone); emojis.push({ name, - code: checkEmoji.metaData.code, + code: checkEmoji.metaData?.code, types: checkEmoji.metaData.types, }); @@ -352,12 +329,8 @@ function replaceEmojis(text, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, /** * Find all emojis in a text and replace them with their code. - * @param {String} text - * @param {Number} preferredSkinTone - * @param {String} lang - * @returns {Object} */ -function replaceAndExtractEmojis(text, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang = CONST.LOCALES.DEFAULT) { +function replaceAndExtractEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang = CONST.LOCALES.DEFAULT) { const {text: convertedText = '', emojis = []} = replaceEmojis(text, preferredSkinTone, lang); return { @@ -368,12 +341,9 @@ function replaceAndExtractEmojis(text, preferredSkinTone = CONST.EMOJI_DEFAULT_S /** * Suggest emojis when typing emojis prefix after colon - * @param {String} text - * @param {String} lang - * @param {Number} [limit] - matching emojis limit - * @returns {Array} + * @param [limit] - matching emojis limit */ -function suggestEmojis(text, lang, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS) { +function suggestEmojis(text: string, lang: keyof typeof emojisTrie, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS) { const trie = emojisTrie[lang]; if (!trie) { return []; @@ -384,23 +354,26 @@ function suggestEmojis(text, lang, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMO return []; } - const matching = []; + const matching: Emoji[] = []; const nodes = trie.getAllMatchingWords(emojiData[0].toLowerCase().slice(1), limit); for (let j = 0; j < nodes.length; j++) { - if (nodes[j].metaData.code && !_.find(matching, (obj) => obj.name === nodes[j].name)) { + if (nodes[j].metaData.code && !matching.find((obj) => obj.name === nodes[j].name)) { if (matching.length === limit) { return matching; } - matching.push({code: nodes[j].metaData.code, name: nodes[j].name, types: nodes[j].metaData.types}); + matching.push({code: nodes[j].metaData?.code ?? '', name: nodes[j].name, types: nodes[j].metaData.types}); } const suggestions = nodes[j].metaData.suggestions; + if (!suggestions) { + return; + } for (let i = 0; i < suggestions.length; i++) { if (matching.length === limit) { return matching; } const suggestion = suggestions[i]; - if (!_.find(matching, (obj) => obj.name === suggestion.name)) { + if (!matching.find((obj) => obj.name === suggestion.name)) { matching.push({...suggestion}); } } @@ -410,12 +383,9 @@ function suggestEmojis(text, lang, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMO /** * Retrieve preferredSkinTone as Number to prevent legacy 'default' String value - * - * @param {Number | String} val - * @returns {Number} */ -const getPreferredSkinToneIndex = (val) => { - if (!_.isNull(val) && Number.isInteger(Number(val))) { +const getPreferredSkinToneIndex = (val: string | number): number | string => { + if (Number.isInteger(Number(val))) { return val; } @@ -425,11 +395,8 @@ const getPreferredSkinToneIndex = (val) => { /** * Given an emoji object it returns the correct emoji code * based on the users preferred skin tone. - * @param {Object} emoji - * @param {String | Number} preferredSkinTone - * @returns {String} */ -const getPreferredEmojiCode = (emoji, preferredSkinTone) => { +const getPreferredEmojiCode = (emoji: Emoji, preferredSkinTone: number): string => { if (emoji.types) { const emojiCodeWithSkinTone = emoji.types[preferredSkinTone]; @@ -447,17 +414,11 @@ const getPreferredEmojiCode = (emoji, preferredSkinTone) => { * Given an emoji object and a list of senders it will return an * array of emoji codes, that represents all used variations of the * emoji. - * @param {Object} emojiAsset - * @param {String} emojiAsset.name - * @param {String} emojiAsset.code - * @param {String[]} [emojiAsset.types] - * @param {Array} users - * @return {string[]} * */ -const getUniqueEmojiCodes = (emojiAsset, users) => { - const uniqueEmojiCodes = []; - _.each(users, (userSkinTones) => { - _.each(lodashGet(userSkinTones, 'skinTones'), (createdAt, skinTone) => { +const getUniqueEmojiCodes = (emojiAsset: Emoji, users: Array<{skinTones: Array<{skinTone: number; createdAt: string}>}>): string[] => { + const uniqueEmojiCodes: string[] = []; + users.forEach((userSkinTones) => { + userSkinTones.skinTones.forEach(({skinTone}) => { const emojiCode = getPreferredEmojiCode(emojiAsset, skinTone); if (emojiCode && !uniqueEmojiCodes.includes(emojiCode)) { uniqueEmojiCodes.push(emojiCode); From 53e7db7e91357cbcb27740b15294f32c24c9efd1 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 29 Sep 2023 12:14:25 +0200 Subject: [PATCH 02/11] Fix types --- src/libs/EmojiTrie.ts | 3 ++- src/libs/EmojiUtils.ts | 37 ++++++++++++++----------------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/libs/EmojiTrie.ts b/src/libs/EmojiTrie.ts index 74025bb329c6..8a625d436ce6 100644 --- a/src/libs/EmojiTrie.ts +++ b/src/libs/EmojiTrie.ts @@ -23,13 +23,14 @@ type LocalizedEmojis = Record; type Suggestion = { code: string; types?: string[]; - name?: string; + name: string; }; type EmojiMetaData = { suggestions?: Suggestion[]; code?: string; types?: string[]; + name?: string; }; Timing.start(CONST.TIMING.TRIE_INITIALIZATION); diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 56956b679150..364e45b1630d 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -1,3 +1,4 @@ +import {SvgProps} from 'react-native-svg'; import moment from 'moment'; import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; @@ -7,12 +8,11 @@ import emojisTrie from './EmojiTrie'; import * as Emojis from '../../assets/emojis'; import * as OnyxTypes from '../types/onyx'; import {Emoji, HeaderEmoji} from '../../assets/emojis/types'; -import {SvgProps} from 'react-native-svg'; type HeaderIndice = {code: string; index: number; icon: React.FC}; type EmojiSpacer = {code: string; spacer: boolean}; -let frequentlyUsedEmojis: Array = []; +let frequentlyUsedEmojis: OnyxTypes.FrequentlyUsedEmoji[] = []; Onyx.connect({ key: ONYXKEYS.FREQUENTLY_USED_EMOJIS, callback: (val) => { @@ -22,10 +22,8 @@ Onyx.connect({ frequentlyUsedEmojis = val ?.map((item) => { - const emoji = Emojis.emojiCodeTableWithSkinTones?.[item.code]; - if (emoji) { - return {...emoji, count: item.count, lastUpdatedAt: item.lastUpdatedAt}; - } + const emoji = Emojis.emojiCodeTableWithSkinTones[item.code]; + return {...emoji, count: item.count, lastUpdatedAt: item.lastUpdatedAt}; }) .filter((emoji): emoji is OnyxTypes.FrequentlyUsedEmoji => !!emoji) ?? []; }, @@ -119,7 +117,6 @@ function containsOnlyEmojis(message: string): boolean { .split(' ') .map((code) => { if (!(CONST.INVISIBLE_CODEPOINTS as readonly string[]).includes(code)) { - //TODO - ask BK codes.push(code); } return code; @@ -185,8 +182,6 @@ function addSpacesToEmojiCategories(emojis: Array) { /** * Get a merged array with frequently used emojis - * @param {Object[]} emojis - * @returns {Object[]} */ function mergeEmojisWithFrequentlyUsedEmojis(emojis: Array) { if (frequentlyUsedEmojis.length === 0) { @@ -199,8 +194,6 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis: Array) /** * Get the updated frequently used emojis list by usage - * @param {Object|Object[]} newEmoji - * @return {Object[]} */ function getFrequentlyUsedEmojis(newEmoji: Emoji | Emoji[]) { let frequentEmojiList = [...frequentlyUsedEmojis]; @@ -234,7 +227,7 @@ function getFrequentlyUsedEmojis(newEmoji: Emoji | Emoji[]) { */ const getEmojiCodeWithSkinColor = (item: Emoji, preferredSkinToneIndex: number): string => { const {code, types} = item; - if (types && types[preferredSkinToneIndex]) { + if (types?.[preferredSkinToneIndex]) { return types[preferredSkinToneIndex]; } @@ -264,8 +257,7 @@ function extractEmojis(text: string) { // Text can contain similar emojis as well as their skin tone variants. Create a Set to remove duplicate emojis from the search. const foundEmojiCodes = new Set(); - for (let i = 0; i < parsedEmojis.length; i++) { - const character = parsedEmojis[i]; + for (const character of parsedEmojis) { const emoji = Emojis.emojiCodeTableWithSkinTones[character]; // Add the parsed emoji to the final emojis if not already present. @@ -299,15 +291,15 @@ function replaceEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKI let checkEmoji = trie.search(name); // If the user has selected a language other than English, and the emoji doesn't exist in that language, // we will check if the emoji exists in English. - if (lang !== CONST.LOCALES.DEFAULT && (!checkEmoji || !checkEmoji.metaData?.code)) { + if (lang !== CONST.LOCALES.DEFAULT && !checkEmoji?.metaData?.code) { const englishTrie = emojisTrie[CONST.LOCALES.DEFAULT]; if (englishTrie) { const englishEmoji = englishTrie.search(name); checkEmoji = englishEmoji; } } - if (checkEmoji && checkEmoji.metaData?.code) { - let emojiReplacement = getEmojiCodeWithSkinColor(checkEmoji.metaData, preferredSkinTone); + if (checkEmoji?.metaData?.code && checkEmoji?.metaData?.name) { + let emojiReplacement = getEmojiCodeWithSkinColor(checkEmoji.metaData as Emoji, preferredSkinTone); emojis.push({ name, code: checkEmoji.metaData?.code, @@ -356,23 +348,22 @@ function suggestEmojis(text: string, lang: keyof typeof emojisTrie, limit = CONS const matching: Emoji[] = []; const nodes = trie.getAllMatchingWords(emojiData[0].toLowerCase().slice(1), limit); - for (let j = 0; j < nodes.length; j++) { - if (nodes[j].metaData.code && !matching.find((obj) => obj.name === nodes[j].name)) { + for (const node of nodes) { + if (node.metaData.code && !matching.find((obj) => obj.name === node.name)) { if (matching.length === limit) { return matching; } - matching.push({code: nodes[j].metaData?.code ?? '', name: nodes[j].name, types: nodes[j].metaData.types}); + matching.push({code: node.metaData?.code ?? '', name: node.name, types: node.metaData.types}); } - const suggestions = nodes[j].metaData.suggestions; + const suggestions = node.metaData.suggestions; if (!suggestions) { return; } - for (let i = 0; i < suggestions.length; i++) { + for (const suggestion of suggestions) { if (matching.length === limit) { return matching; } - const suggestion = suggestions[i]; if (!matching.find((obj) => obj.name === suggestion.name)) { matching.push({...suggestion}); } From 6092e1ddd8534d5b4966647c7c89d914bce53f8e Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 2 Oct 2023 11:16:21 +0200 Subject: [PATCH 03/11] Migrate emoji utils to TS --- src/libs/EmojiUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 364e45b1630d..33202d461320 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -1,3 +1,4 @@ +import _ from 'lodash'; import {SvgProps} from 'react-native-svg'; import moment from 'moment'; import Str from 'expensify-common/lib/str'; From f2ee9a01459476ccdb0c96eeb945900c445c66f1 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 2 Oct 2023 11:48:53 +0200 Subject: [PATCH 04/11] Rename types --- assets/emojis/common.ts | 4 ++-- assets/emojis/types.ts | 4 +++- src/libs/EmojiUtils.ts | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/assets/emojis/common.ts b/assets/emojis/common.ts index dc3a78ff3228..402d59815d43 100644 --- a/assets/emojis/common.ts +++ b/assets/emojis/common.ts @@ -7,7 +7,7 @@ import Objects from '../images/emojiCategoryIcons/light-bulb.svg'; import Symbols from '../images/emojiCategoryIcons/peace-sign.svg'; import Flags from '../images/emojiCategoryIcons/flag.svg'; import FrequentlyUsed from '../images/history.svg'; -import {Emoji, HeaderEmoji} from './types'; +import {PickerEmojis} from './types'; const skinTones = [ { @@ -36,7 +36,7 @@ const skinTones = [ }, ] as const; -const emojis: Array = [ +const emojis: PickerEmojis = [ { header: true, icon: Smiley, diff --git a/assets/emojis/types.ts b/assets/emojis/types.ts index 0fdd7086fdf5..a42f44ed7fa7 100644 --- a/assets/emojis/types.ts +++ b/assets/emojis/types.ts @@ -12,6 +12,8 @@ type HeaderEmoji = { code: string; }; +type PickerEmojis = Array; + type EmojisList = Record; -export type {Emoji, HeaderEmoji, EmojisList}; +export type {Emoji, HeaderEmoji, EmojisList, PickerEmojis}; diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 33202d461320..8b61c7f395a0 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -7,13 +7,15 @@ import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import emojisTrie from './EmojiTrie'; import * as Emojis from '../../assets/emojis'; -import * as OnyxTypes from '../types/onyx'; -import {Emoji, HeaderEmoji} from '../../assets/emojis/types'; +import {FrequentlyUsedEmoji} from '../types/onyx'; +import {Emoji, HeaderEmoji, PickerEmojis} from '../../assets/emojis/types'; type HeaderIndice = {code: string; index: number; icon: React.FC}; type EmojiSpacer = {code: string; spacer: boolean}; +type UserSkinTone = {skinTone: number; createdAt: string}; +type UserEmojiPreference = {skinTones: UserSkinTone[]}; -let frequentlyUsedEmojis: OnyxTypes.FrequentlyUsedEmoji[] = []; +let frequentlyUsedEmojis: FrequentlyUsedEmoji[] = []; Onyx.connect({ key: ONYXKEYS.FREQUENTLY_USED_EMOJIS, callback: (val) => { @@ -26,7 +28,7 @@ Onyx.connect({ const emoji = Emojis.emojiCodeTableWithSkinTones[item.code]; return {...emoji, count: item.count, lastUpdatedAt: item.lastUpdatedAt}; }) - .filter((emoji): emoji is OnyxTypes.FrequentlyUsedEmoji => !!emoji) ?? []; + .filter((emoji): emoji is FrequentlyUsedEmoji => !!emoji) ?? []; }, }); @@ -135,12 +137,10 @@ function containsOnlyEmojis(message: string): boolean { /** * Get the header emojis with their code, icon and index */ -function getHeaderEmojis(emojis: Array): HeaderIndice[] { - // TODO - move into separate type +function getHeaderEmojis(emojis: PickerEmojis): HeaderIndice[] { const headerIndices: HeaderIndice[] = []; emojis.forEach((emoji, index) => { if (!('header' in emoji)) { - // TODO - ask BK why it doesn't work the other way return; } headerIndices.push({code: emoji.code, index, icon: emoji.icon}); @@ -169,7 +169,7 @@ function getDynamicSpacing(emojiCount: number, suffix: number): EmojiSpacer[] { /** * Add dynamic spaces to emoji categories */ -function addSpacesToEmojiCategories(emojis: Array) { +function addSpacesToEmojiCategories(emojis: PickerEmojis) { let updatedEmojis: Array = []; emojis.forEach((emoji, index) => { if ('header' in emoji) { @@ -184,12 +184,12 @@ function addSpacesToEmojiCategories(emojis: Array) { /** * Get a merged array with frequently used emojis */ -function mergeEmojisWithFrequentlyUsedEmojis(emojis: Array) { +function mergeEmojisWithFrequentlyUsedEmojis(emojis: PickerEmojis) { if (frequentlyUsedEmojis.length === 0) { return addSpacesToEmojiCategories(emojis); } - const mergedEmojis = ([Emojis.categoryFrequentlyUsed] as Array).concat(frequentlyUsedEmojis, emojis); + const mergedEmojis = ([Emojis.categoryFrequentlyUsed] as PickerEmojis).concat(frequentlyUsedEmojis, emojis); return addSpacesToEmojiCategories(mergedEmojis); } @@ -407,7 +407,7 @@ const getPreferredEmojiCode = (emoji: Emoji, preferredSkinTone: number): string * array of emoji codes, that represents all used variations of the * emoji. * */ -const getUniqueEmojiCodes = (emojiAsset: Emoji, users: Array<{skinTones: Array<{skinTone: number; createdAt: string}>}>): string[] => { +const getUniqueEmojiCodes = (emojiAsset: Emoji, users: UserEmojiPreference[]): string[] => { const uniqueEmojiCodes: string[] = []; users.forEach((userSkinTones) => { userSkinTones.skinTones.forEach(({skinTone}) => { From 1e005a83bc8663e5bfa2e35d25c4c03c5d9b952f Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 3 Oct 2023 18:34:14 +0200 Subject: [PATCH 05/11] Fixes after review for emojiUtils lib --- assets/emojis/common.ts | 4 ++-- assets/emojis/index.ts | 8 ++++---- src/libs/EmojiUtils.ts | 38 ++++++++++++++++++++------------------ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/assets/emojis/common.ts b/assets/emojis/common.ts index 402d59815d43..a8709d9207d6 100644 --- a/assets/emojis/common.ts +++ b/assets/emojis/common.ts @@ -7,7 +7,7 @@ import Objects from '../images/emojiCategoryIcons/light-bulb.svg'; import Symbols from '../images/emojiCategoryIcons/peace-sign.svg'; import Flags from '../images/emojiCategoryIcons/flag.svg'; import FrequentlyUsed from '../images/history.svg'; -import {PickerEmojis} from './types'; +import {HeaderEmoji, PickerEmojis} from './types'; const skinTones = [ { @@ -7620,7 +7620,7 @@ const emojis: PickerEmojis = [ }, ]; -const categoryFrequentlyUsed = { +const categoryFrequentlyUsed: HeaderEmoji = { header: true, code: 'frequentlyUsed', icon: FrequentlyUsed, diff --git a/assets/emojis/index.ts b/assets/emojis/index.ts index 37b3dc572025..aade4e557a64 100644 --- a/assets/emojis/index.ts +++ b/assets/emojis/index.ts @@ -5,16 +5,16 @@ import {Emoji} from './types'; type EmojiTable = Record; -const emojiNameTable: EmojiTable = emojis.reduce((prev, cur) => { - const newValue: EmojiTable = prev; +const emojiNameTable = emojis.reduce((prev, cur) => { + const newValue = prev; if (!('header' in cur) && cur.name) { newValue[cur.name] = cur; } return newValue; }, {}); -const emojiCodeTableWithSkinTones: EmojiTable = emojis.reduce((prev, cur) => { - const newValue: EmojiTable = prev; +const emojiCodeTableWithSkinTones = emojis.reduce((prev, cur) => { + const newValue = prev; if (!('header' in cur)) { newValue[cur.code] = cur; } diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 8b61c7f395a0..65f48276c863 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import memoize from 'lodash/memoize'; import {SvgProps} from 'react-native-svg'; import moment from 'moment'; import Str from 'expensify-common/lib/str'; @@ -12,8 +12,10 @@ import {Emoji, HeaderEmoji, PickerEmojis} from '../../assets/emojis/types'; type HeaderIndice = {code: string; index: number; icon: React.FC}; type EmojiSpacer = {code: string; spacer: boolean}; +type EmojiPickerList = Array; type UserSkinTone = {skinTone: number; createdAt: string}; type UserEmojiPreference = {skinTones: UserSkinTone[]}; +type ReplacedEmoji = {text: string; emojis: Emoji[]}; let frequentlyUsedEmojis: FrequentlyUsedEmoji[] = []; Onyx.connect({ @@ -32,11 +34,11 @@ Onyx.connect({ }, }); -const findEmojiByName = (name: string) => Emojis.emojiNameTable[name]; +const findEmojiByName = (name: string): Emoji => Emojis.emojiNameTable[name]; -const findEmojiByCode = (code: string) => Emojis.emojiCodeTableWithSkinTones[code]; +const findEmojiByCode = (code: string): Emoji => Emojis.emojiCodeTableWithSkinTones[code]; -const getEmojiName = (emoji: Emoji, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT) => { +const getEmojiName = (emoji: Emoji, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT): string => { if (lang === CONST.LOCALES.DEFAULT) { return emoji.name; } @@ -47,7 +49,7 @@ const getEmojiName = (emoji: Emoji, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT) = /** * Given an English emoji name, get its localized version */ -const getLocalizedEmojiName = (name: string, lang: 'en' | 'es') => { +const getLocalizedEmojiName = (name: string, lang: 'en' | 'es'): string => { if (lang === CONST.LOCALES.DEFAULT) { return name; } @@ -59,7 +61,7 @@ const getLocalizedEmojiName = (name: string, lang: 'en' | 'es') => { /** * Get the unicode code of an emoji in base 16. */ -const getEmojiUnicode = _.memoize((input: string) => { +const getEmojiUnicode = memoize((input: string) => { if (input.length === 0) { return ''; } @@ -99,7 +101,7 @@ const getEmojiUnicode = _.memoize((input: string) => { /** * Function to remove Skin Tone and utf16 surrogates from Emoji */ -function trimEmojiUnicode(emojiCode: string) { +function trimEmojiUnicode(emojiCode: string): string { return emojiCode.replace(/(fe0f|1f3fb|1f3fc|1f3fd|1f3fe|1f3ff)$/, '').trim(); } @@ -169,8 +171,8 @@ function getDynamicSpacing(emojiCount: number, suffix: number): EmojiSpacer[] { /** * Add dynamic spaces to emoji categories */ -function addSpacesToEmojiCategories(emojis: PickerEmojis) { - let updatedEmojis: Array = []; +function addSpacesToEmojiCategories(emojis: PickerEmojis): EmojiPickerList { + let updatedEmojis: EmojiPickerList = []; emojis.forEach((emoji, index) => { if ('header' in emoji) { updatedEmojis = updatedEmojis.concat(getDynamicSpacing(updatedEmojis.length, index), [emoji], getDynamicSpacing(1, index)); @@ -184,24 +186,24 @@ function addSpacesToEmojiCategories(emojis: PickerEmojis) { /** * Get a merged array with frequently used emojis */ -function mergeEmojisWithFrequentlyUsedEmojis(emojis: PickerEmojis) { +function mergeEmojisWithFrequentlyUsedEmojis(emojis: PickerEmojis): EmojiPickerList { if (frequentlyUsedEmojis.length === 0) { return addSpacesToEmojiCategories(emojis); } - const mergedEmojis = ([Emojis.categoryFrequentlyUsed] as PickerEmojis).concat(frequentlyUsedEmojis, emojis); + const mergedEmojis = [Emojis.categoryFrequentlyUsed, ...frequentlyUsedEmojis, ...emojis]; return addSpacesToEmojiCategories(mergedEmojis); } /** * Get the updated frequently used emojis list by usage */ -function getFrequentlyUsedEmojis(newEmoji: Emoji | Emoji[]) { +function getFrequentlyUsedEmojis(newEmoji: Emoji | Emoji[]): FrequentlyUsedEmoji[] { let frequentEmojiList = [...frequentlyUsedEmojis]; const maxFrequentEmojiCount = CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW - 1; const currentTimestamp = moment().unix(); - ([] as Emoji[]).concat(newEmoji).forEach((emoji) => { + (Array.isArray(newEmoji) ? [...newEmoji] : [newEmoji]).forEach((emoji) => { let currentEmojiCount = 1; const emojiIndex = frequentEmojiList.findIndex((e) => e.code === emoji.code); if (emojiIndex >= 0) { @@ -241,7 +243,7 @@ const getEmojiCodeWithSkinColor = (item: Emoji, preferredSkinToneIndex: number): * @param text - The text to extract emojis from. * @returns An array of emoji codes. */ -function extractEmojis(text: string) { +function extractEmojis(text: string): Emoji[] { if (!text) { return []; } @@ -275,7 +277,7 @@ function extractEmojis(text: string) { * Replace any emoji name in a text with the emoji icon. * If we're on mobile, we also add a space after the emoji granted there's no text after it. */ -function replaceEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT) { +function replaceEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT): ReplacedEmoji { const trie = emojisTrie[lang]; if (!trie) { return {text, emojis: []}; @@ -323,7 +325,7 @@ function replaceEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKI /** * Find all emojis in a text and replace them with their code. */ -function replaceAndExtractEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang = CONST.LOCALES.DEFAULT) { +function replaceAndExtractEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang = CONST.LOCALES.DEFAULT): ReplacedEmoji { const {text: convertedText = '', emojis = []} = replaceEmojis(text, preferredSkinTone, lang); return { @@ -336,7 +338,7 @@ function replaceAndExtractEmojis(text: string, preferredSkinTone = CONST.EMOJI_D * Suggest emojis when typing emojis prefix after colon * @param [limit] - matching emojis limit */ -function suggestEmojis(text: string, lang: keyof typeof emojisTrie, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS) { +function suggestEmojis(text: string, lang: keyof typeof emojisTrie, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined { const trie = emojisTrie[lang]; if (!trie) { return []; @@ -377,7 +379,7 @@ function suggestEmojis(text: string, lang: keyof typeof emojisTrie, limit = CONS * Retrieve preferredSkinTone as Number to prevent legacy 'default' String value */ const getPreferredSkinToneIndex = (val: string | number): number | string => { - if (Number.isInteger(Number(val))) { + if (val !== null && Number.isInteger(Number(val))) { return val; } From ab49c912baaf4e8f7f172fb36a66b8dd76913d6d Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 24 Oct 2023 11:01:19 +0200 Subject: [PATCH 06/11] Remove code from comment --- src/libs/EmojiUtils.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 521151177ce4..585ed5ceb3f7 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -451,17 +451,8 @@ const getPreferredEmojiCode = (emoji: Emoji, preferredSkinTone: number): string /** * Given an emoji object and a list of senders it will return an * array of emoji codes, that represents all used variations of the - * emoji, sorted by the reaction timestamp. - * - * const getUniqueEmojiCodes = (emojiAsset: Emoji, users: UserEmojiPreference[]): string[] => { - * const uniqueEmojiCodes: string[] = []; - * users.forEach((userSkinTones) => { - * userSkinTones.skinTones.forEach(({skinTone}) => { - * const emojiCode = getPreferredEmojiCode(emojiAsset, skinTone); - * if (emojiCode && !uniqueEmojiCodes.includes(emojiCode)) { - * uniqueEmojiCodes.push(emojiCode); - * */ + */ const getUniqueEmojiCodes = (emojiAsset: Emoji, users: TimestampedUsersReactions): string[] => { const emojiCodes: Record = Object.values(users ?? {}).reduce((result: Record, userSkinTones) => { Object.keys(userSkinTones.skinTones).forEach((skinTone) => { From 3b528340d15f1c548b5e227cb7e6061aae0a7a71 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 30 Oct 2023 12:52:05 +0100 Subject: [PATCH 07/11] Fixes for skintone --- src/libs/EmojiUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 881a61beff7d..5ff36b72555d 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -285,7 +285,7 @@ function extractEmojis(text: string): Emoji[] { return []; } - const emojis = []; + const emojis: Emoji[] = []; // Text can contain similar emojis as well as their skin tone variants. Create a Set to remove duplicate emojis from the search. @@ -423,7 +423,7 @@ function suggestEmojis(text: string, lang: keyof typeof emojisTrie, limit = CONS * Retrieve preferredSkinTone as Number to prevent legacy 'default' String value */ const getPreferredSkinToneIndex = (val: string | number): number | string => { - if (!!val && Number.isInteger(Number(val))) { + if (val !== null && Number.isInteger(Number(val))) { return val; } From 509b36dac690f79807d3ae6b173b6f28e0955747 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 30 Oct 2023 13:54:45 +0100 Subject: [PATCH 08/11] Remove not needed checks --- src/libs/EmojiUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 5ff36b72555d..471978ce279e 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -396,11 +396,11 @@ function suggestEmojis(text: string, lang: keyof typeof emojisTrie, limit = CONS const matching: Emoji[] = []; const nodes = trie.getAllMatchingWords(emojiData[0].toLowerCase().slice(1), limit); for (const node of nodes) { - if (node.metaData.code && !matching.find((obj) => obj.name === node.name)) { + if (node.metaData?.code && !matching.find((obj) => obj.name === node.name)) { if (matching.length === limit) { return matching; } - matching.push({code: node.metaData?.code ?? '', name: node.name, types: node.metaData.types}); + matching.push({code: node.metaData.code, name: node.name, types: node.metaData.types}); } const suggestions = node.metaData.suggestions; if (!suggestions) { From 0453d00d205a91535bc5f5cb6e0b6d459ee38e41 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 7 Nov 2023 12:30:57 +0100 Subject: [PATCH 09/11] Add default value to reduce --- src/libs/EmojiUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 471978ce279e..452f2f4536b4 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -478,7 +478,8 @@ const enrichEmojiReactionWithTimestamps = (emoji: UsersReactionsList, emojiName: const usersWithTimestamps: Record = {}; Object.keys(emoji.users ?? {}).forEach((id) => { const user = emoji?.users?.[id]; - const oldestUserTimestamp = Object.values(user?.skinTones ?? {}).reduce((min, curr) => (curr < min ? curr : min)); + const userTimestamps = Object.values(user?.skinTones ?? {}); + const oldestUserTimestamp = userTimestamps.reduce((min, curr) => (curr < min ? curr : min), userTimestamps[0]); if (!oldestEmojiTimestamp || oldestUserTimestamp < oldestEmojiTimestamp) { oldestEmojiTimestamp = oldestUserTimestamp; From 7f417c72f8aa8b10b096cf73052922a5de1e92f5 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 7 Nov 2023 15:43:47 +0100 Subject: [PATCH 10/11] fix: app crashing on android --- src/libs/EmojiUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 452f2f4536b4..b52a87ee3c6e 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -455,7 +455,7 @@ const getPreferredEmojiCode = (emoji: Emoji, preferredSkinTone: number): string */ const getUniqueEmojiCodes = (emojiAsset: Emoji, users: TimestampedUsersReactions): string[] => { const emojiCodes: Record = Object.values(users ?? {}).reduce((result: Record, userSkinTones) => { - Object.keys(userSkinTones.skinTones).forEach((skinTone) => { + Object.keys(userSkinTones?.skinTones ?? {}).forEach((skinTone) => { const createdAt = userSkinTones.skinTones[Number(skinTone)]; const emojiCode = getPreferredEmojiCode(emojiAsset, Number(skinTone)); if (!!emojiCode && (!result[emojiCode] || createdAt < result[emojiCode])) { From 082a2101b5fe9ffba595f68a5a04d8a9808682e8 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 7 Nov 2023 15:50:18 +0100 Subject: [PATCH 11/11] fix: ensure undefined objects are handled --- src/libs/EmojiUtils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index b52a87ee3c6e..1308faa65d20 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -466,7 +466,7 @@ const getUniqueEmojiCodes = (emojiAsset: Emoji, users: TimestampedUsersReactions return result; }, {}); - return Object.keys(emojiCodes).sort((a, b) => (new Date(emojiCodes[a]) > new Date(emojiCodes[b]) ? 1 : -1)); + return Object.keys(emojiCodes ?? {}).sort((a, b) => (new Date(emojiCodes[a]) > new Date(emojiCodes[b]) ? 1 : -1)); }; /** @@ -512,7 +512,7 @@ function hasAccountIDEmojiReacted(accountID: string, usersReactions: Timestamped return Boolean(usersReactions[accountID]); } const userReaction = usersReactions[accountID]; - if (!userReaction?.skinTones || !Object.values(userReaction.skinTones).length) { + if (!userReaction?.skinTones || !Object.values(userReaction?.skinTones ?? {}).length) { return false; } return Boolean(userReaction.skinTones[skinTone]); @@ -526,11 +526,11 @@ const getEmojiReactionDetails = (emojiName: string, reaction: UsersReactionsList const emoji = findEmojiByName(emojiName); const emojiCodes = getUniqueEmojiCodes(emoji, users); - const reactionCount = Object.values(users) - .map((user) => Object.values(user.skinTones).length) + const reactionCount = Object.values(users ?? {}) + .map((user) => Object.values(user?.skinTones ?? {}).length) .reduce((sum, curr) => sum + curr, 0); const hasUserReacted = hasAccountIDEmojiReacted(currentUserAccountID, users); - const userAccountIDs = Object.values(users) + const userAccountIDs = Object.values(users ?? {}) .sort((a, b) => (a.oldestTimestamp > b.oldestTimestamp ? 1 : -1)) .map((user) => Number(user.id));