One emoji image set for picker, composition, message bubble
This commit is contained in:
parent
464361b2eb
commit
6c0365a770
10 changed files with 84 additions and 201 deletions
|
@ -1,15 +1,11 @@
|
||||||
const { take } = require('lodash');
|
const { take } = require('lodash');
|
||||||
const { getRecentEmojis } = require('./data');
|
const { getRecentEmojis } = require('./data');
|
||||||
const {
|
const { replaceColons } = require('../../ts/components/emoji/lib');
|
||||||
replaceColons,
|
|
||||||
hasVariation,
|
|
||||||
} = require('../../ts/components/emoji/lib');
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getInitialState,
|
getInitialState,
|
||||||
load,
|
load,
|
||||||
replaceColons,
|
replaceColons,
|
||||||
hasVariation,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let initialState = null;
|
let initialState = null;
|
||||||
|
|
|
@ -6,7 +6,6 @@ const Crypto = require('./crypto');
|
||||||
const Data = require('./data');
|
const Data = require('./data');
|
||||||
const Database = require('./database');
|
const Database = require('./database');
|
||||||
const Emojis = require('./emojis');
|
const Emojis = require('./emojis');
|
||||||
const Emoji = require('../../ts/util/emoji');
|
|
||||||
const EmojiLib = require('../../ts/components/emoji/lib');
|
const EmojiLib = require('../../ts/components/emoji/lib');
|
||||||
const IndexedDB = require('./indexeddb');
|
const IndexedDB = require('./indexeddb');
|
||||||
const Notifications = require('../../ts/notifications');
|
const Notifications = require('../../ts/notifications');
|
||||||
|
@ -333,7 +332,6 @@ exports.setup = (options = {}) => {
|
||||||
Data,
|
Data,
|
||||||
Database,
|
Database,
|
||||||
Emojis,
|
Emojis,
|
||||||
Emoji,
|
|
||||||
EmojiLib,
|
EmojiLib,
|
||||||
IndexedDB,
|
IndexedDB,
|
||||||
LinkPreviews,
|
LinkPreviews,
|
||||||
|
|
|
@ -59,7 +59,6 @@
|
||||||
"electron-is-dev": "0.3.0",
|
"electron-is-dev": "0.3.0",
|
||||||
"emoji-datasource": "4.1.0",
|
"emoji-datasource": "4.1.0",
|
||||||
"emoji-datasource-apple": "4.1.0",
|
"emoji-datasource-apple": "4.1.0",
|
||||||
"emoji-js": "3.4.0",
|
|
||||||
"emoji-regex": "8.0.0",
|
"emoji-regex": "8.0.0",
|
||||||
"filesize": "3.6.1",
|
"filesize": "3.6.1",
|
||||||
"firstline": "1.2.1",
|
"firstline": "1.2.1",
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { MessageBody } from './conversation/MessageBody';
|
||||||
import { Emojify } from './conversation/Emojify';
|
import { Emojify } from './conversation/Emojify';
|
||||||
import { AddNewLines } from './conversation/AddNewLines';
|
import { AddNewLines } from './conversation/AddNewLines';
|
||||||
|
|
||||||
import { SizeClassType } from '../util/emoji';
|
import { SizeClassType } from './emoji/lib';
|
||||||
|
|
||||||
import { LocalizerType, RenderTextCallbackType } from '../types/Util';
|
import { LocalizerType, RenderTextCallbackType } from '../types/Util';
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import is from '@sindresorhus/is';
|
|
||||||
|
|
||||||
import {
|
import emojiRegex from 'emoji-regex';
|
||||||
findImage,
|
|
||||||
getRegex,
|
|
||||||
getReplacementData,
|
|
||||||
SizeClassType,
|
|
||||||
} from '../../util/emoji';
|
|
||||||
|
|
||||||
import { RenderTextCallbackType } from '../../types/Util';
|
import { RenderTextCallbackType } from '../../types/Util';
|
||||||
|
import { emojiToImage, SizeClassType } from '../emoji/lib';
|
||||||
|
|
||||||
// Some of this logic taken from emoji-js/replacement
|
// Some of this logic taken from emoji-js/replacement
|
||||||
function getImageTag({
|
function getImageTag({
|
||||||
|
@ -22,18 +17,9 @@ function getImageTag({
|
||||||
sizeClass?: SizeClassType;
|
sizeClass?: SizeClassType;
|
||||||
key: string | number;
|
key: string | number;
|
||||||
}) {
|
}) {
|
||||||
const result = getReplacementData(match[0], match[1], match[2]);
|
const img = emojiToImage(match[0]);
|
||||||
|
|
||||||
if (is.string(result)) {
|
if (!img) {
|
||||||
return match[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const img = findImage(result.value, result.variation);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!img.path ||
|
|
||||||
!img.path.startsWith('node_modules/emoji-datasource-apple')
|
|
||||||
) {
|
|
||||||
return match[0];
|
return match[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,10 +27,9 @@ function getImageTag({
|
||||||
// tslint:disable-next-line react-a11y-img-has-alt
|
// tslint:disable-next-line react-a11y-img-has-alt
|
||||||
<img
|
<img
|
||||||
key={key}
|
key={key}
|
||||||
src={img.path}
|
src={img}
|
||||||
aria-label={match[0]}
|
aria-label={match[0]}
|
||||||
className={classNames('emoji', sizeClass)}
|
className={classNames('emoji', sizeClass)}
|
||||||
data-codepoints={img.full_idx}
|
|
||||||
title={match[0]}
|
title={match[0]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -66,7 +51,7 @@ export class Emojify extends React.Component<Props> {
|
||||||
public render() {
|
public render() {
|
||||||
const { text, sizeClass, renderNonEmoji } = this.props;
|
const { text, sizeClass, renderNonEmoji } = this.props;
|
||||||
const results: Array<any> = [];
|
const results: Array<any> = [];
|
||||||
const regex = getRegex();
|
const regex = emojiRegex();
|
||||||
|
|
||||||
// We have to do this, because renderNonEmoji is not required in our Props object,
|
// We have to do this, because renderNonEmoji is not required in our Props object,
|
||||||
// but it is always provided via defaultProps.
|
// but it is always provided via defaultProps.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { getSizeClass, SizeClassType } from '../../util/emoji';
|
import { getSizeClass, SizeClassType } from '../emoji/lib';
|
||||||
import { Emojify } from './Emojify';
|
import { Emojify } from './Emojify';
|
||||||
import { AddNewLines } from './AddNewLines';
|
import { AddNewLines } from './AddNewLines';
|
||||||
import { Linkify } from './Linkify';
|
import { Linkify } from './Linkify';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @ts-ignore: untyped json
|
// @ts-ignore: untyped json
|
||||||
import untypedData from 'emoji-datasource';
|
import untypedData from 'emoji-datasource';
|
||||||
|
import emojiRegex from 'emoji-regex';
|
||||||
import {
|
import {
|
||||||
compact,
|
compact,
|
||||||
flatMap,
|
flatMap,
|
||||||
|
@ -13,12 +14,14 @@ import {
|
||||||
} from 'lodash';
|
} from 'lodash';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
|
||||||
export type ValuesOf<T extends Array<any>> = T[number];
|
export type ValuesOf<T extends Array<any>> = T[number];
|
||||||
|
|
||||||
export const skinTones = ['1F3FB', '1F3FC', '1F3FD', '1F3FE', '1F3FF'];
|
export const skinTones = ['1F3FB', '1F3FC', '1F3FD', '1F3FE', '1F3FF'];
|
||||||
|
|
||||||
export type SkinToneKey = '1F3FB' | '1F3FC' | '1F3FD' | '1F3FE' | '1F3FF';
|
export type SkinToneKey = '1F3FB' | '1F3FC' | '1F3FD' | '1F3FE' | '1F3FF';
|
||||||
|
export type SizeClassType = '' | 'small' | 'medium' | 'large' | 'jumbo';
|
||||||
|
|
||||||
export type EmojiSkinVariation = {
|
export type EmojiSkinVariation = {
|
||||||
unified: string;
|
unified: string;
|
||||||
|
@ -111,16 +114,8 @@ export const preloadImages = async () => {
|
||||||
console.log(`Done preloading emoji images in ${end - start}ms`);
|
console.log(`Done preloading emoji images in ${end - start}ms`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dataByShortName = keyBy(data, 'short_name');
|
const dataByShortName = keyBy(data, 'short_name');
|
||||||
|
const imageByEmoji: { [key: string]: string } = {};
|
||||||
data.forEach(emoji => {
|
|
||||||
const { short_names } = emoji;
|
|
||||||
if (short_names) {
|
|
||||||
short_names.forEach(name => {
|
|
||||||
dataByShortName[name] = emoji;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const dataByCategory = mapValues(
|
export const dataByCategory = mapValues(
|
||||||
groupBy(data, ({ category }) => {
|
groupBy(data, ({ category }) => {
|
||||||
|
@ -221,34 +216,19 @@ export function unifiedToEmoji(unified: string) {
|
||||||
.join('');
|
.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasVariation(shortName: string, skinTone: number = 0) {
|
export function convertShortName(
|
||||||
if (skinTone === 0) {
|
shortName: string,
|
||||||
return false;
|
skinTone: number | SkinToneKey = 0
|
||||||
}
|
) {
|
||||||
|
|
||||||
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) {
|
|
||||||
const base = dataByShortName[shortName];
|
const base = dataByShortName[shortName];
|
||||||
|
|
||||||
if (!base) {
|
if (!base) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skinTone > 0 && base.skin_variations) {
|
const toneKey = is.number(skinTone) ? skinTones[skinTone - 1] : skinTone;
|
||||||
const toneKey = skinTones[skinTone - 1];
|
|
||||||
|
if (skinTone && base.skin_variations) {
|
||||||
const variation = base.skin_variations[toneKey];
|
const variation = base.skin_variations[toneKey];
|
||||||
if (variation) {
|
if (variation) {
|
||||||
return unifiedToEmoji(variation.unified);
|
return unifiedToEmoji(variation.unified);
|
||||||
|
@ -258,6 +238,10 @@ export function convertShortName(shortName: string, skinTone: number = 0) {
|
||||||
return unifiedToEmoji(base.unified);
|
return unifiedToEmoji(base.unified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emojiToImage(emoji: string): string | undefined {
|
||||||
|
return imageByEmoji[emoji];
|
||||||
|
}
|
||||||
|
|
||||||
export function replaceColons(str: string) {
|
export function replaceColons(str: string) {
|
||||||
return str.replace(/:[a-z0-9-_+]+:(?::skin-tone-[1-5]:)?/gi, m => {
|
return str.replace(/:[a-z0-9-_+]+:(?::skin-tone-[1-5]:)?/gi, m => {
|
||||||
const [shortName = '', skinTone = '0'] = m
|
const [shortName = '', skinTone = '0'] = m
|
||||||
|
@ -273,3 +257,60 @@ export function replaceColons(str: string) {
|
||||||
return m;
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
106
ts/util/emoji.ts
106
ts/util/emoji.ts
|
@ -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;
|
|
||||||
}
|
|
|
@ -231,7 +231,7 @@
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
"path": "js/modules/emojis.js",
|
"path": "js/modules/emojis.js",
|
||||||
"line": "async function load() {",
|
"line": "async function load() {",
|
||||||
"lineNumber": 17,
|
"lineNumber": 13,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-05-23T22:27:53.554Z"
|
"updated": "2019-05-23T22:27:53.554Z"
|
||||||
},
|
},
|
||||||
|
@ -3549,24 +3549,6 @@
|
||||||
"reasonCategory": "otherUtilityCode",
|
"reasonCategory": "otherUtilityCode",
|
||||||
"updated": "2019-04-03T00:52:04.925Z"
|
"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",
|
"rule": "thenify-multiArgs",
|
||||||
"path": "node_modules/es6-promisify/dist/promisify.js",
|
"path": "node_modules/es6-promisify/dist/promisify.js",
|
||||||
|
|
12
yarn.lock
12
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"
|
resolved "https://registry.yarnpkg.com/emoji-datasource-apple/-/emoji-datasource-apple-4.1.0.tgz#e6725311b115144a32fb60043416a755fea30bf5"
|
||||||
integrity sha1-5nJTEbEVFEoy+2AENBanVf6jC/U=
|
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:
|
emoji-datasource@4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-4.1.0.tgz#b44557f78a2dfac2f350393391b170a567ec28ad"
|
resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-4.1.0.tgz#b44557f78a2dfac2f350393391b170a567ec28ad"
|
||||||
integrity sha1-tEVX94ot+sLzUDkzkbFwpWfsKK0=
|
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:
|
emoji-regex@8.0.0:
|
||||||
version "8.0.0"
|
version "8.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||||
|
|
Loading…
Reference in a new issue