-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
emoji.ts
157 lines (133 loc) Β· 4.37 KB
/
emoji.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import type { Emoji } from "./types.ts";
import emojis from "./all.json" with { type: "json" };
import { reUnicode } from "./unicode.ts";
export type { Emoji };
// Regex to parse emoji in a string - e.g. :coffee:
const reEmojiName = /:([a-zA-Z0-9_\-\+]+):/g;
// Non spacing mark, some emoticons have them. It's the 'Variant Form',
// which provides more information so that emoticons can be rendered as
// more colorful graphics. FE0E is a unicode text version, where as FE0F
// should be rendered as a graphical version. The code gracefully degrades.
const NON_SPACING_MARK = String.fromCharCode(65039); // 65039 - 'οΈ' - 0xFE0F;
const reNonSpacing = new RegExp(NON_SPACING_MARK, "g");
/** Remove NON_SPACING_MARK from string. See above. */
function stripNSB(code: string): string {
return code.replace(reNonSpacing, "");
}
/** Removes colons on either side of the string. */
function stripColons(str: string): string {
const colonIndex = str.indexOf(":");
if (colonIndex > -1) {
if (colonIndex === str.length - 1) {
str = str.substring(0, colonIndex);
} else {
str = str.substring(colonIndex + 1);
}
return stripColons(str);
}
return str;
}
/** Adds colons to either side of the string. */
function wrapColons(str: string): string {
return str.length > 0 ? ":" + str + ":" : str;
}
type EmojiMap = { [alias: string]: Emoji };
const byAlias: EmojiMap = Object.fromEntries(
emojis.map((emoji) => emoji.aliases.map((alias) => [alias, emoji])).flat(),
);
const byCode: EmojiMap = Object.fromEntries(
emojis.map((emoji) => {
return [stripNSB(emoji.emoji), emoji];
}),
);
/** Get all emojis. */
export function all(): Emoji[] {
return emojis;
}
/** Get all emojis as alias map. */
export function aliasMap(): EmojiMap {
return byAlias;
}
/** Get all emojis as code map. */
export function codeMap(): EmojiMap {
return byCode;
}
/** Get emoji from alias. - e.g. "unicorn" -> π¦ */
export function get(alias: string): string {
return byAlias[stripColons(alias)]?.emoji;
}
/** Get alias from emoji. - e.g. π -> "shirt" */
export function alias(emoji: string): string {
return byCode[stripNSB(emoji)]?.aliases[0];
}
/** Get alias from emoji. - e.g. π -> ["shirt", "tshirt"] */
export function aliases(emoji: string): string[] {
return byCode[stripNSB(emoji)]?.aliases;
}
/** Get emoji info from alias or emoji */
export function infoByAlias(raw: string): Emoji | undefined {
return byAlias[stripColons(raw)];
}
/** Get emoji info from alias or emoji */
export function infoByCode(raw: string): Emoji | undefined {
return byCode[stripNSB(raw)];
}
/** Get random emoji. - e.g. {emoji: "π", alias: "shirt"} */
export function random(): { emoji: string; alias: string } {
const random = emojis[Math.floor(Math.random() * emojis.length)];
const emoji = random.emoji;
const alias = random.aliases[0];
return { emoji, alias };
}
/** Strip all emojis in a string. */
export function strip(str: string): string {
return replace(str, "", true);
}
/** Replace all emojis in a string. */
export function replace(
str: string,
replacement: string | ((emoji: Emoji) => string) = "",
trim = false,
): string {
if (!str) return "";
const replace = typeof replacement === "function" ? replacement : () => {
return replacement;
};
const match = str.match(reUnicode) ?? [];
const result = match
.map((s, i) => {
const emoji = byCode[stripNSB(s)];
if (emoji && trim && match[i + 1] === " ") {
match[i + 1] = "";
}
return emoji ? replace(emoji) : s;
})
.join("");
return trim ? result.trim() : result;
}
/** Replace all emoji names in a string with actual emojis. */
export function emojify(str: string): string {
if (!str) return "";
return str
.split(reEmojiName)
.map((s, i) => {
if (i % 2 === 0) return s;
let emoji = get(s);
if (!emoji) emoji = wrapColons(s);
return emoji;
})
.join("");
}
/** Replace all emoji in a string with actual emoji names. */
export function unemojify(str: string): string {
return replace(str, (emoji) => wrapColons(emoji.aliases[0]));
}
/** Tagged template version of emojify */
export function emoji(template: TemplateStringsArray, ...args: string[]): string {
const chunks = [];
for (let i = 0; i < template.length; i++) {
chunks.push(emojify(template[i]));
if (args[i]) chunks.push(emojify(args[i]));
}
return chunks.join("");
}