From 6c0365a770361b74c43d4cced2d2d4e9b6405e0e Mon Sep 17 00:00:00 2001 From: Ken Powers Date: Thu, 25 Jul 2019 12:28:44 -0400 Subject: [PATCH] One emoji image set for picker, composition, message bubble --- js/modules/emojis.js | 6 +- js/modules/signal.js | 2 - package.json | 1 - ts/components/MessageBodyHighlight.tsx | 2 +- ts/components/conversation/Emojify.tsx | 27 ++---- ts/components/conversation/MessageBody.tsx | 2 +- ts/components/emoji/lib.ts | 105 +++++++++++++------- ts/util/emoji.ts | 106 --------------------- ts/util/lint/exceptions.json | 22 +---- yarn.lock | 12 --- 10 files changed, 84 insertions(+), 201 deletions(-) delete mode 100644 ts/util/emoji.ts diff --git a/js/modules/emojis.js b/js/modules/emojis.js index c4220f1cbb..47730c2f33 100644 --- a/js/modules/emojis.js +++ b/js/modules/emojis.js @@ -1,15 +1,11 @@ const { take } = require('lodash'); const { getRecentEmojis } = require('./data'); -const { - replaceColons, - hasVariation, -} = require('../../ts/components/emoji/lib'); +const { replaceColons } = require('../../ts/components/emoji/lib'); module.exports = { getInitialState, load, replaceColons, - hasVariation, }; let initialState = null; diff --git a/js/modules/signal.js b/js/modules/signal.js index 2f206fdbab..3aed32062e 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -6,7 +6,6 @@ const Crypto = require('./crypto'); const Data = require('./data'); const Database = require('./database'); const Emojis = require('./emojis'); -const Emoji = require('../../ts/util/emoji'); const EmojiLib = require('../../ts/components/emoji/lib'); const IndexedDB = require('./indexeddb'); const Notifications = require('../../ts/notifications'); @@ -333,7 +332,6 @@ exports.setup = (options = {}) => { Data, Database, Emojis, - Emoji, EmojiLib, IndexedDB, LinkPreviews, diff --git a/package.json b/package.json index ae11cb0361..6b3b84aa16 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "electron-is-dev": "0.3.0", "emoji-datasource": "4.1.0", "emoji-datasource-apple": "4.1.0", - "emoji-js": "3.4.0", "emoji-regex": "8.0.0", "filesize": "3.6.1", "firstline": "1.2.1", diff --git a/ts/components/MessageBodyHighlight.tsx b/ts/components/MessageBodyHighlight.tsx index d28676c72a..642adb4835 100644 --- a/ts/components/MessageBodyHighlight.tsx +++ b/ts/components/MessageBodyHighlight.tsx @@ -4,7 +4,7 @@ import { MessageBody } from './conversation/MessageBody'; import { Emojify } from './conversation/Emojify'; import { AddNewLines } from './conversation/AddNewLines'; -import { SizeClassType } from '../util/emoji'; +import { SizeClassType } from './emoji/lib'; import { LocalizerType, RenderTextCallbackType } from '../types/Util'; diff --git a/ts/components/conversation/Emojify.tsx b/ts/components/conversation/Emojify.tsx index 61ffc1c80e..39021608a7 100644 --- a/ts/components/conversation/Emojify.tsx +++ b/ts/components/conversation/Emojify.tsx @@ -1,16 +1,11 @@ import React from 'react'; import classNames from 'classnames'; -import is from '@sindresorhus/is'; -import { - findImage, - getRegex, - getReplacementData, - SizeClassType, -} from '../../util/emoji'; +import emojiRegex from 'emoji-regex'; import { RenderTextCallbackType } from '../../types/Util'; +import { emojiToImage, SizeClassType } from '../emoji/lib'; // Some of this logic taken from emoji-js/replacement function getImageTag({ @@ -22,18 +17,9 @@ function getImageTag({ sizeClass?: SizeClassType; key: string | number; }) { - const result = getReplacementData(match[0], match[1], match[2]); + const img = emojiToImage(match[0]); - if (is.string(result)) { - return match[0]; - } - - const img = findImage(result.value, result.variation); - - if ( - !img.path || - !img.path.startsWith('node_modules/emoji-datasource-apple') - ) { + if (!img) { return match[0]; } @@ -41,10 +27,9 @@ function getImageTag({ // tslint:disable-next-line react-a11y-img-has-alt ); @@ -66,7 +51,7 @@ export class Emojify extends React.Component { public render() { const { text, sizeClass, renderNonEmoji } = this.props; const results: Array = []; - const regex = getRegex(); + const regex = emojiRegex(); // We have to do this, because renderNonEmoji is not required in our Props object, // but it is always provided via defaultProps. diff --git a/ts/components/conversation/MessageBody.tsx b/ts/components/conversation/MessageBody.tsx index 1801ed9ed2..c4b7ce2496 100644 --- a/ts/components/conversation/MessageBody.tsx +++ b/ts/components/conversation/MessageBody.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { getSizeClass, SizeClassType } from '../../util/emoji'; +import { getSizeClass, SizeClassType } from '../emoji/lib'; import { Emojify } from './Emojify'; import { AddNewLines } from './AddNewLines'; import { Linkify } from './Linkify'; diff --git a/ts/components/emoji/lib.ts b/ts/components/emoji/lib.ts index 072621f78a..edddb196cc 100644 --- a/ts/components/emoji/lib.ts +++ b/ts/components/emoji/lib.ts @@ -1,5 +1,6 @@ // @ts-ignore: untyped json import untypedData from 'emoji-datasource'; +import emojiRegex from 'emoji-regex'; import { compact, flatMap, @@ -13,12 +14,14 @@ import { } from 'lodash'; import Fuse from 'fuse.js'; import PQueue from 'p-queue'; +import is from '@sindresorhus/is'; export type ValuesOf> = T[number]; export const skinTones = ['1F3FB', '1F3FC', '1F3FD', '1F3FE', '1F3FF']; export type SkinToneKey = '1F3FB' | '1F3FC' | '1F3FD' | '1F3FE' | '1F3FF'; +export type SizeClassType = '' | 'small' | 'medium' | 'large' | 'jumbo'; export type EmojiSkinVariation = { unified: string; @@ -111,16 +114,8 @@ export const preloadImages = async () => { console.log(`Done preloading emoji images in ${end - start}ms`); }; -export const dataByShortName = keyBy(data, 'short_name'); - -data.forEach(emoji => { - const { short_names } = emoji; - if (short_names) { - short_names.forEach(name => { - dataByShortName[name] = emoji; - }); - } -}); +const dataByShortName = keyBy(data, 'short_name'); +const imageByEmoji: { [key: string]: string } = {}; export const dataByCategory = mapValues( groupBy(data, ({ category }) => { @@ -221,34 +216,19 @@ export function unifiedToEmoji(unified: string) { .join(''); } -export function hasVariation(shortName: string, skinTone: number = 0) { - if (skinTone === 0) { - return false; - } - - const base = dataByShortName[shortName]; - if (!base) { - return false; - } - - if (skinTone > 0 && base.skin_variations) { - const toneKey = skinTones[skinTone - 1]; - - return Boolean(base.skin_variations[toneKey]); - } - - return false; -} - -export function convertShortName(shortName: string, skinTone: number = 0) { +export function convertShortName( + shortName: string, + skinTone: number | SkinToneKey = 0 +) { const base = dataByShortName[shortName]; if (!base) { return ''; } - if (skinTone > 0 && base.skin_variations) { - const toneKey = skinTones[skinTone - 1]; + const toneKey = is.number(skinTone) ? skinTones[skinTone - 1] : skinTone; + + if (skinTone && base.skin_variations) { const variation = base.skin_variations[toneKey]; if (variation) { return unifiedToEmoji(variation.unified); @@ -258,6 +238,10 @@ export function convertShortName(shortName: string, skinTone: number = 0) { return unifiedToEmoji(base.unified); } +export function emojiToImage(emoji: string): string | undefined { + return imageByEmoji[emoji]; +} + export function replaceColons(str: string) { return str.replace(/:[a-z0-9-_+]+:(?::skin-tone-[1-5]:)?/gi, m => { const [shortName = '', skinTone = '0'] = m @@ -273,3 +257,60 @@ export function replaceColons(str: string) { return m; }); } + +function getCountOfAllMatches(str: string, regex: RegExp) { + let match = regex.exec(str); + let count = 0; + + if (!regex.global) { + return match ? 1 : 0; + } + + while (match) { + count += 1; + match = regex.exec(str); + } + + return count; +} + +export function getSizeClass(str: string): SizeClassType { + // Do we have non-emoji characters? + if (str.replace(emojiRegex(), '').trim().length > 0) { + return ''; + } + + const emojiCount = getCountOfAllMatches(str, emojiRegex()); + + if (emojiCount > 8) { + return ''; + } else if (emojiCount > 6) { + return 'small'; + } else if (emojiCount > 4) { + return 'medium'; + } else if (emojiCount > 2) { + return 'large'; + } else { + return 'jumbo'; + } +} + +data.forEach(emoji => { + const { short_name, short_names, skin_variations, image } = emoji; + + if (short_names) { + short_names.forEach(name => { + dataByShortName[name] = emoji; + }); + } + + imageByEmoji[convertShortName(short_name)] = makeImagePath(image); + + if (skin_variations) { + Object.entries(skin_variations).forEach(([tone, variation]) => { + imageByEmoji[ + convertShortName(short_name, tone as SkinToneKey) + ] = makeImagePath(variation.image); + }); + } +}); diff --git a/ts/util/emoji.ts b/ts/util/emoji.ts deleted file mode 100644 index 9593aa6257..0000000000 --- a/ts/util/emoji.ts +++ /dev/null @@ -1,106 +0,0 @@ -// @ts-ignore -import EmojiConvertor from 'emoji-js'; - -const instance = new EmojiConvertor(); -instance.init_unified(); -instance.init_colons(); -instance.img_sets.apple.path = - 'node_modules/emoji-datasource-apple/img/apple/64/'; -instance.include_title = true; -instance.replace_mode = 'img'; -instance.supports_css = false; // needed to avoid spans with background-image - -export type SizeClassType = '' | 'small' | 'medium' | 'large' | 'jumbo'; - -export function getRegex(): RegExp { - return instance.rx_unified; -} - -export function getTitle(value: string): string | undefined { - return instance.data[value][3][0]; -} - -export function findImage(value: string, variation?: string) { - return instance.find_image(value, variation); -} - -function getCountOfAllMatches(str: string, regex: RegExp) { - let match = regex.exec(str); - let count = 0; - - if (!regex.global) { - return match ? 1 : 0; - } - - while (match) { - count += 1; - match = regex.exec(str); - } - - return count; -} - -function hasNormalCharacters(str: string) { - const noEmoji = str.replace(instance.rx_unified, '').trim(); - - return noEmoji.length > 0; -} - -export function getSizeClass(str: string): SizeClassType { - if (hasNormalCharacters(str)) { - return ''; - } - - const emojiCount = getCountOfAllMatches(str, instance.rx_unified); - if (emojiCount > 8) { - return ''; - } else if (emojiCount > 6) { - return 'small'; - } else if (emojiCount > 4) { - return 'medium'; - } else if (emojiCount > 2) { - return 'large'; - } else { - return 'jumbo'; - } -} - -const VARIATION_LOOKUP: { [index: string]: string } = { - '\uD83C\uDFFB': '1f3fb', - '\uD83C\uDFFC': '1f3fc', - '\uD83C\uDFFD': '1f3fd', - '\uD83C\uDFFE': '1f3fe', - '\uD83C\uDFFF': '1f3ff', -}; - -// Taken from emoji-js/replace_unified -export function getReplacementData( - m: string, - p1: string | undefined, - p2: string | undefined -): string | { value: string; variation?: string } { - const unified = instance.map.unified[p1]; - if (unified) { - const variation = VARIATION_LOOKUP[p2 || '']; - if (variation) { - return { - value: unified, - variation, - }; - } - - return { - value: unified, - }; - } - - const unifiedVars = instance.map.unified_vars[p1]; - if (unifiedVars) { - return { - value: unifiedVars[0], - variation: unifiedVars[1], - }; - } - - return m; -} diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index e0cd083386..694e2afc2c 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -231,7 +231,7 @@ "rule": "jQuery-load(", "path": "js/modules/emojis.js", "line": "async function load() {", - "lineNumber": 17, + "lineNumber": 13, "reasonCategory": "falseMatch", "updated": "2019-05-23T22:27:53.554Z" }, @@ -3549,24 +3549,6 @@ "reasonCategory": "otherUtilityCode", "updated": "2019-04-03T00:52:04.925Z" }, - { - "rule": "jQuery-$(", - "path": "node_modules/emoji-js/lib/jquery.emoji.js", - "line": "\t\t$(this).html(function (i, oldHtml){", - "lineNumber": 5, - "reasonCategory": "usageTrusted", - "updated": "2018-09-19T21:59:32.770Z", - "reasonDetail": "Protected from arbitrary input" - }, - { - "rule": "jQuery-html(", - "path": "node_modules/emoji-js/lib/jquery.emoji.js", - "line": "\t\t$(this).html(function (i, oldHtml){", - "lineNumber": 5, - "reasonCategory": "usageTrusted", - "updated": "2018-09-18T19:19:27.699Z", - "reasonDetail": "It's setting the html of the element to the previous HTML, just with the emoji replaced" - }, { "rule": "thenify-multiArgs", "path": "node_modules/es6-promisify/dist/promisify.js", @@ -7904,4 +7886,4 @@ "reasonCategory": "falseMatch", "updated": "2019-05-02T20:44:56.470Z" } -] \ No newline at end of file +] diff --git a/yarn.lock b/yarn.lock index ba0e3a37af..d1c801cbdf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2793,23 +2793,11 @@ emoji-datasource-apple@4.1.0: resolved "https://registry.yarnpkg.com/emoji-datasource-apple/-/emoji-datasource-apple-4.1.0.tgz#e6725311b115144a32fb60043416a755fea30bf5" integrity sha1-5nJTEbEVFEoy+2AENBanVf6jC/U= -emoji-datasource@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-4.0.0.tgz#3fc9c0c2f4fb321d9291138819f6d100603d3e2f" - integrity sha1-P8nAwvT7Mh2SkROIGfbRAGA9Pi8= - emoji-datasource@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-4.1.0.tgz#b44557f78a2dfac2f350393391b170a567ec28ad" integrity sha1-tEVX94ot+sLzUDkzkbFwpWfsKK0= -emoji-js@3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/emoji-js/-/emoji-js-3.4.0.tgz#dabdeda60c92d1948a5177e51ba9421d2029b052" - integrity sha1-2r3tpgyS0ZSKUXflG6lCHSApsFI= - dependencies: - emoji-datasource "4.0.0" - emoji-regex@8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"