signal-desktop/ts/components/emoji/lib.ts
2019-05-24 17:03:13 -07:00

192 lines
4.1 KiB
TypeScript

// @ts-ignore: untyped json
import untypedData from 'emoji-datasource';
import {
compact,
flatMap,
groupBy,
isNumber,
keyBy,
map,
mapValues,
sortBy,
} from 'lodash';
import Fuse from 'fuse.js';
export type ValuesOf<T extends Array<any>> = T[number];
export const skinTones = ['1F3FB', '1F3FC', '1F3FD', '1F3FE', '1F3FF'];
export type SkinToneKey = '1F3FB' | '1F3FC' | '1F3FD' | '1F3FE' | '1F3FF';
export type EmojiData = {
name: string;
unified: string;
non_qualified: string | null;
docomo: string | null;
au: string | null;
softbank: string | null;
google: string | null;
image: string;
sheet_x: number;
sheet_y: number;
short_name: string;
short_names: Array<string>;
text: string | null;
texts: Array<string> | null;
category: string;
sort_order: number;
added_in: string;
has_img_apple: boolean;
has_img_google: boolean;
has_img_twitter: boolean;
has_img_emojione: boolean;
has_img_facebook: boolean;
has_img_messenger: boolean;
skin_variations?: {
[key: string]: {
unified: string;
non_qualified: null;
image: string;
sheet_x: number;
sheet_y: number;
added_in: string;
has_img_apple: boolean;
has_img_google: boolean;
has_img_twitter: boolean;
has_img_emojione: boolean;
has_img_facebook: boolean;
has_img_messenger: boolean;
};
};
};
const data: Array<EmojiData> = untypedData;
export const dataByShortName = keyBy(data, 'short_name');
data.forEach(emoji => {
const { short_names } = emoji;
if (short_names) {
short_names.forEach(name => {
dataByShortName[name] = emoji;
});
}
});
export const dataByCategory = mapValues(
groupBy(data, ({ category }) => {
if (category === 'Activities') {
return 'activity';
}
if (category === 'Animals & Nature') {
return 'animal';
}
if (category === 'Flags') {
return 'flag';
}
if (category === 'Food & Drink') {
return 'food';
}
if (category === 'Objects') {
return 'object';
}
if (category === 'Travel & Places') {
return 'travel';
}
if (category === 'Smileys & People') {
return 'emoji';
}
if (category === 'Symbols') {
return 'symbol';
}
return 'misc';
}),
arr => sortBy(arr, 'sort_order')
);
export function getSheetCoordinates(
shortName: keyof typeof dataByShortName,
skinTone?: SkinToneKey | number
): [number, number] {
const base = dataByShortName[shortName];
if (skinTone && base.skin_variations) {
const variation = isNumber(skinTone) ? skinTones[skinTone - 1] : skinTone;
const { sheet_x, sheet_y } = base.skin_variations[variation];
return [sheet_x, sheet_y];
}
return [base.sheet_x, base.sheet_y];
}
const fuse = new Fuse(data, {
shouldSort: true,
threshold: 0.3,
location: 0,
distance: 5,
maxPatternLength: 20,
minMatchCharLength: 1,
keys: ['name', 'short_name', 'short_names'],
});
export function search(query: string) {
return fuse.search(query);
}
const shortNames = new Set([
...map(data, 'short_name'),
...compact<string>(flatMap(data, 'short_names')),
]);
export function isShortName(name: string) {
return shortNames.has(name);
}
export function unifiedToEmoji(unified: string) {
return unified
.split('-')
.map(c => String.fromCodePoint(parseInt(c, 16)))
.join('');
}
export function convertShortName(shortName: string, skinTone: number = 0) {
const base = dataByShortName[shortName];
if (!base) {
return '';
}
if (skinTone && base.skin_variations) {
const toneKey = skinTones[0];
const variation = base.skin_variations[toneKey];
if (variation) {
return unifiedToEmoji(variation.unified);
}
}
return unifiedToEmoji(base.unified);
}
export function replaceColons(str: string) {
return str.replace(/:[a-z0-9-_+]+:(?::skin-tone-[1-4]:)?/gi, m => {
const [shortName = '', skinTone = '0'] = m
.replace('skin-tone-', '')
.split(':')
.filter(Boolean);
if (shortName) {
return convertShortName(shortName, parseInt(skinTone, 10));
}
return m;
});
}