@tiptap/extension-emoji 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data.d.ts +5 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/emoji.d.ts +67 -0
- package/dist/emoji.d.ts.map +1 -0
- package/dist/helpers/emojiToShortcode.d.ts +3 -0
- package/dist/helpers/emojiToShortcode.d.ts.map +1 -0
- package/dist/helpers/removeDuplicates.d.ts +9 -0
- package/dist/helpers/removeDuplicates.d.ts.map +1 -0
- package/dist/helpers/removeVariationSelector.d.ts +2 -0
- package/dist/helpers/removeVariationSelector.d.ts.map +1 -0
- package/dist/helpers/shortcodeToEmoji.d.ts +3 -0
- package/dist/helpers/shortcodeToEmoji.d.ts.map +1 -0
- package/dist/index.cjs +31916 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31898 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +31911 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +53 -0
- package/src/data.ts +31560 -0
- package/src/emoji.ts +460 -0
- package/src/generate.ts +85 -0
- package/src/helpers/emojiToShortcode.ts +6 -0
- package/src/helpers/removeDuplicates.ts +15 -0
- package/src/helpers/removeVariationSelector.ts +3 -0
- package/src/helpers/shortcodeToEmoji.ts +5 -0
- package/src/index.ts +8 -0
package/src/emoji.ts
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import {
|
|
2
|
+
combineTransactionSteps,
|
|
3
|
+
escapeForRegEx,
|
|
4
|
+
findChildrenInRange,
|
|
5
|
+
getChangedRanges,
|
|
6
|
+
InputRule,
|
|
7
|
+
mergeAttributes,
|
|
8
|
+
Node,
|
|
9
|
+
nodeInputRule,
|
|
10
|
+
PasteRule,
|
|
11
|
+
} from '@tiptap/core'
|
|
12
|
+
import { Plugin, PluginKey, Transaction } from '@tiptap/pm/state'
|
|
13
|
+
import Suggestion, { SuggestionOptions } from '@tiptap/suggestion'
|
|
14
|
+
import emojiRegex from 'emoji-regex'
|
|
15
|
+
import { isEmojiSupported } from 'is-emoji-supported'
|
|
16
|
+
|
|
17
|
+
import { emojis as defaultEmojis } from './data.js'
|
|
18
|
+
import { emojiToShortcode } from './helpers/emojiToShortcode.js'
|
|
19
|
+
import { removeDuplicates } from './helpers/removeDuplicates.js'
|
|
20
|
+
import { shortcodeToEmoji } from './helpers/shortcodeToEmoji.js'
|
|
21
|
+
|
|
22
|
+
declare module '@tiptap/core' {
|
|
23
|
+
interface Commands<ReturnType> {
|
|
24
|
+
emoji: {
|
|
25
|
+
/**
|
|
26
|
+
* Add an emoji
|
|
27
|
+
*/
|
|
28
|
+
setEmoji: (shortcode: string) => ReturnType,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type EmojiItem = {
|
|
34
|
+
/**
|
|
35
|
+
* A unique name of the emoji which will be stored as attribute
|
|
36
|
+
*/
|
|
37
|
+
name: string,
|
|
38
|
+
/**
|
|
39
|
+
* The emoji unicode character
|
|
40
|
+
*/
|
|
41
|
+
emoji?: string,
|
|
42
|
+
/**
|
|
43
|
+
* A list of unique shortcodes that are used by input rules to find the emoji
|
|
44
|
+
*/
|
|
45
|
+
shortcodes: string[],
|
|
46
|
+
/**
|
|
47
|
+
* A list of tags that can help for searching emojis
|
|
48
|
+
*/
|
|
49
|
+
tags: string[],
|
|
50
|
+
/**
|
|
51
|
+
* A name that can help to group emojis
|
|
52
|
+
*/
|
|
53
|
+
group?: string,
|
|
54
|
+
/**
|
|
55
|
+
* A list of unique emoticons
|
|
56
|
+
*/
|
|
57
|
+
emoticons?: string[],
|
|
58
|
+
/**
|
|
59
|
+
* The unicode version the emoji was introduced
|
|
60
|
+
*/
|
|
61
|
+
version?: number,
|
|
62
|
+
/**
|
|
63
|
+
* A fallback image if the current system doesn't support the emoji or for custom emojis
|
|
64
|
+
*/
|
|
65
|
+
fallbackImage?: string,
|
|
66
|
+
/**
|
|
67
|
+
* Store some custom data
|
|
68
|
+
*/
|
|
69
|
+
[key: string]: any,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type EmojiOptions = {
|
|
73
|
+
HTMLAttributes: Record<string, any>,
|
|
74
|
+
emojis: EmojiItem[],
|
|
75
|
+
enableEmoticons: boolean,
|
|
76
|
+
forceFallbackImages: boolean,
|
|
77
|
+
suggestion: Omit<SuggestionOptions, 'editor'>,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type EmojiStorage = {
|
|
81
|
+
emojis: EmojiItem[],
|
|
82
|
+
isSupported: (item: EmojiItem) => boolean,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const EmojiSuggestionPluginKey = new PluginKey('emojiSuggestion')
|
|
86
|
+
|
|
87
|
+
export const inputRegex = /:([a-zA-Z0-9_+-]+):$/
|
|
88
|
+
|
|
89
|
+
export const pasteRegex = /:([a-zA-Z0-9_+-]+):/g
|
|
90
|
+
|
|
91
|
+
export const Emoji = Node.create<EmojiOptions, EmojiStorage>({
|
|
92
|
+
name: 'emoji',
|
|
93
|
+
|
|
94
|
+
inline: true,
|
|
95
|
+
|
|
96
|
+
group: 'inline',
|
|
97
|
+
|
|
98
|
+
selectable: false,
|
|
99
|
+
|
|
100
|
+
addOptions() {
|
|
101
|
+
return {
|
|
102
|
+
HTMLAttributes: {},
|
|
103
|
+
emojis: defaultEmojis,
|
|
104
|
+
enableEmoticons: false,
|
|
105
|
+
forceFallbackImages: false,
|
|
106
|
+
suggestion: {
|
|
107
|
+
char: ':',
|
|
108
|
+
pluginKey: EmojiSuggestionPluginKey,
|
|
109
|
+
command: ({ editor, range, props }) => {
|
|
110
|
+
// increase range.to by one when the next node is of type "text"
|
|
111
|
+
// and starts with a space character
|
|
112
|
+
const nodeAfter = editor.view.state.selection.$to.nodeAfter
|
|
113
|
+
const overrideSpace = nodeAfter?.text?.startsWith(' ')
|
|
114
|
+
|
|
115
|
+
if (overrideSpace) {
|
|
116
|
+
range.to += 1
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
editor
|
|
120
|
+
.chain()
|
|
121
|
+
.focus()
|
|
122
|
+
.insertContentAt(range, [
|
|
123
|
+
{
|
|
124
|
+
type: this.name,
|
|
125
|
+
attrs: props,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: 'text',
|
|
129
|
+
text: ' ',
|
|
130
|
+
},
|
|
131
|
+
])
|
|
132
|
+
.command(({ tr, state }) => {
|
|
133
|
+
tr.setStoredMarks(state.doc.resolve(state.selection.to - 2).marks())
|
|
134
|
+
return true
|
|
135
|
+
})
|
|
136
|
+
.run()
|
|
137
|
+
},
|
|
138
|
+
allow: ({ state, range }) => {
|
|
139
|
+
const $from = state.doc.resolve(range.from)
|
|
140
|
+
const type = state.schema.nodes[this.name]
|
|
141
|
+
const allow = !!$from.parent.type.contentMatch.matchType(type)
|
|
142
|
+
|
|
143
|
+
return allow
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
addStorage() {
|
|
150
|
+
const { emojis } = this.options
|
|
151
|
+
const supportMap: Record<number, boolean> = removeDuplicates(emojis.map(item => item.version))
|
|
152
|
+
.filter(version => typeof version === 'number')
|
|
153
|
+
.reduce((versions, version) => {
|
|
154
|
+
const emoji = emojis.find(item => item.version === version && item.emoji)
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
...versions,
|
|
158
|
+
[version as number]: emoji
|
|
159
|
+
? isEmojiSupported(emoji.emoji as string)
|
|
160
|
+
: false,
|
|
161
|
+
}
|
|
162
|
+
}, {})
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
emojis: this.options.emojis,
|
|
166
|
+
isSupported: emojiItem => {
|
|
167
|
+
return emojiItem.version
|
|
168
|
+
? supportMap[emojiItem.version]
|
|
169
|
+
: false
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
addAttributes() {
|
|
175
|
+
return {
|
|
176
|
+
name: {
|
|
177
|
+
default: null,
|
|
178
|
+
parseHTML: element => element.dataset.name,
|
|
179
|
+
renderHTML: attributes => ({
|
|
180
|
+
'data-name': attributes.name,
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
parseHTML() {
|
|
187
|
+
return [
|
|
188
|
+
{
|
|
189
|
+
tag: `span[data-type="${this.name}"]`,
|
|
190
|
+
},
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
renderHTML({ HTMLAttributes, node }) {
|
|
195
|
+
const emojiItem = shortcodeToEmoji(node.attrs.name, this.options.emojis)
|
|
196
|
+
const attributes = mergeAttributes(
|
|
197
|
+
HTMLAttributes,
|
|
198
|
+
this.options.HTMLAttributes,
|
|
199
|
+
{ 'data-type': this.name },
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if (!emojiItem) {
|
|
203
|
+
return [
|
|
204
|
+
'span',
|
|
205
|
+
attributes,
|
|
206
|
+
`:${node.attrs.name}:`,
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const isSupported = this.storage.isSupported(emojiItem)
|
|
211
|
+
const hasEmoji = !!emojiItem?.emoji
|
|
212
|
+
const hasFallbackImage = !!emojiItem?.fallbackImage
|
|
213
|
+
|
|
214
|
+
const renderFallbackImage = (this.options.forceFallbackImages && !hasEmoji)
|
|
215
|
+
|| (this.options.forceFallbackImages && hasFallbackImage)
|
|
216
|
+
|| (this.options.forceFallbackImages && !isSupported && hasFallbackImage)
|
|
217
|
+
|| ((!isSupported || !hasEmoji) && hasFallbackImage)
|
|
218
|
+
|
|
219
|
+
return [
|
|
220
|
+
'span',
|
|
221
|
+
attributes,
|
|
222
|
+
renderFallbackImage
|
|
223
|
+
? [
|
|
224
|
+
'img',
|
|
225
|
+
{
|
|
226
|
+
src: emojiItem.fallbackImage,
|
|
227
|
+
draggable: 'false',
|
|
228
|
+
loading: 'lazy',
|
|
229
|
+
align: 'absmiddle',
|
|
230
|
+
},
|
|
231
|
+
]
|
|
232
|
+
: emojiItem.emoji || `:${emojiItem.shortcodes[0]}:`,
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
renderText({ node }) {
|
|
237
|
+
const emojiItem = shortcodeToEmoji(node.attrs.name, this.options.emojis)
|
|
238
|
+
|
|
239
|
+
return emojiItem?.emoji || `:${node.attrs.name}:`
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
addCommands() {
|
|
243
|
+
return {
|
|
244
|
+
setEmoji: shortcode => ({ chain }) => {
|
|
245
|
+
const emojiItem = shortcodeToEmoji(shortcode, this.options.emojis)
|
|
246
|
+
|
|
247
|
+
if (!emojiItem) {
|
|
248
|
+
return false
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
chain()
|
|
252
|
+
.insertContent({
|
|
253
|
+
type: this.name,
|
|
254
|
+
attrs: {
|
|
255
|
+
name: emojiItem.name,
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
.command(({ tr, state }) => {
|
|
259
|
+
tr.setStoredMarks(state.doc.resolve(state.selection.to - 1).marks())
|
|
260
|
+
return true
|
|
261
|
+
}).run()
|
|
262
|
+
|
|
263
|
+
return true
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
addInputRules() {
|
|
269
|
+
const inputRules: InputRule[] = []
|
|
270
|
+
|
|
271
|
+
inputRules.push(
|
|
272
|
+
new InputRule({
|
|
273
|
+
find: inputRegex,
|
|
274
|
+
handler: ({ range, match, chain }) => {
|
|
275
|
+
const name = match[1]
|
|
276
|
+
|
|
277
|
+
if (!shortcodeToEmoji(name, this.options.emojis)) {
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
chain()
|
|
282
|
+
.insertContentAt(range, {
|
|
283
|
+
type: this.name,
|
|
284
|
+
attrs: {
|
|
285
|
+
name,
|
|
286
|
+
},
|
|
287
|
+
})
|
|
288
|
+
.command(({ tr, state }) => {
|
|
289
|
+
tr.setStoredMarks(state.doc.resolve(state.selection.to - 1).marks())
|
|
290
|
+
return true
|
|
291
|
+
}).run()
|
|
292
|
+
},
|
|
293
|
+
}),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if (this.options.enableEmoticons) {
|
|
297
|
+
// get the list of supported emoticons
|
|
298
|
+
const emoticons = this.options.emojis
|
|
299
|
+
.map(item => item.emoticons)
|
|
300
|
+
.flat()
|
|
301
|
+
.filter(item => item) as string[]
|
|
302
|
+
|
|
303
|
+
const emoticonRegex = new RegExp(`(?:^|\\s)(${emoticons.map(item => escapeForRegEx(item)).join('|')}) $`)
|
|
304
|
+
|
|
305
|
+
inputRules.push(
|
|
306
|
+
nodeInputRule({
|
|
307
|
+
find: emoticonRegex,
|
|
308
|
+
type: this.type,
|
|
309
|
+
getAttributes: match => {
|
|
310
|
+
const emoji = this.options.emojis.find(item => item.emoticons?.includes(match[1]))
|
|
311
|
+
|
|
312
|
+
if (!emoji) {
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
name: emoji.name,
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
}),
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return inputRules
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
addPasteRules() {
|
|
328
|
+
return [
|
|
329
|
+
new PasteRule({
|
|
330
|
+
find: pasteRegex,
|
|
331
|
+
handler: ({ range, match, chain }) => {
|
|
332
|
+
const name = match[1]
|
|
333
|
+
|
|
334
|
+
if (!shortcodeToEmoji(name, this.options.emojis)) {
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
chain()
|
|
339
|
+
.insertContentAt(range, {
|
|
340
|
+
type: this.name,
|
|
341
|
+
attrs: {
|
|
342
|
+
name,
|
|
343
|
+
},
|
|
344
|
+
}, {
|
|
345
|
+
updateSelection: false,
|
|
346
|
+
})
|
|
347
|
+
.command(({ tr, state }) => {
|
|
348
|
+
tr.setStoredMarks(state.doc.resolve(state.selection.to - 1).marks())
|
|
349
|
+
return true
|
|
350
|
+
})
|
|
351
|
+
.run()
|
|
352
|
+
},
|
|
353
|
+
}),
|
|
354
|
+
]
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
addProseMirrorPlugins() {
|
|
358
|
+
return [
|
|
359
|
+
Suggestion({
|
|
360
|
+
editor: this.editor,
|
|
361
|
+
...this.options.suggestion,
|
|
362
|
+
}),
|
|
363
|
+
|
|
364
|
+
new Plugin({
|
|
365
|
+
key: new PluginKey('emoji'),
|
|
366
|
+
props: {
|
|
367
|
+
// double click to select emoji doesn’t work by default
|
|
368
|
+
// that’s why we simulate this behavior
|
|
369
|
+
handleDoubleClickOn: (view, pos, node) => {
|
|
370
|
+
if (node.type !== this.type) {
|
|
371
|
+
return false
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const from = pos
|
|
375
|
+
const to = from + node.nodeSize
|
|
376
|
+
|
|
377
|
+
this.editor.commands.setTextSelection({
|
|
378
|
+
from,
|
|
379
|
+
to,
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
return true
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
// replace text emojis with emoji node on any change
|
|
387
|
+
appendTransaction: (transactions, oldState, newState) => {
|
|
388
|
+
const docChanges = transactions.some(transaction => transaction.docChanged)
|
|
389
|
+
&& !oldState.doc.eq(newState.doc)
|
|
390
|
+
|
|
391
|
+
if (!docChanges) {
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const { tr } = newState
|
|
396
|
+
const transform = combineTransactionSteps(oldState.doc, transactions as Transaction[])
|
|
397
|
+
const changes = getChangedRanges(transform)
|
|
398
|
+
|
|
399
|
+
changes.forEach(({ newRange }) => {
|
|
400
|
+
// We don’t want to add emoji inline nodes within code blocks.
|
|
401
|
+
// Because this would split the code block.
|
|
402
|
+
|
|
403
|
+
// This only works if the range of changes is within a code node.
|
|
404
|
+
// For all other cases (e.g. the whole document is set/pasted and the parent of the range is `doc`)
|
|
405
|
+
// it doesn't and we have to double check later.
|
|
406
|
+
if (newState.doc.resolve(newRange.from).parent.type.spec.code) {
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const textNodes = findChildrenInRange(newState.doc, newRange, node => node.type.isText)
|
|
411
|
+
|
|
412
|
+
textNodes.forEach(({ node, pos }) => {
|
|
413
|
+
if (!node.text) {
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const matches = [...node.text.matchAll(emojiRegex())]
|
|
418
|
+
|
|
419
|
+
matches.forEach(match => {
|
|
420
|
+
if (match.index === undefined) {
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const emoji = match[0]
|
|
425
|
+
const name = emojiToShortcode(emoji, this.options.emojis)
|
|
426
|
+
|
|
427
|
+
if (!name) {
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const from = tr.mapping.map(pos + match.index)
|
|
432
|
+
|
|
433
|
+
// Double check parent node is not a code block.
|
|
434
|
+
if (newState.doc.resolve(from).parent.type.spec.code) {
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const to = from + emoji.length
|
|
439
|
+
const emojiNode = this.type.create({
|
|
440
|
+
name,
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
tr.replaceRangeWith(from, to, emojiNode)
|
|
444
|
+
|
|
445
|
+
tr.setStoredMarks(newState.doc.resolve(from).marks())
|
|
446
|
+
|
|
447
|
+
})
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
if (!tr.steps.length) {
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return tr
|
|
456
|
+
},
|
|
457
|
+
}),
|
|
458
|
+
]
|
|
459
|
+
},
|
|
460
|
+
})
|
package/src/generate.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import dataSource from 'emoji-datasource/emoji.json'
|
|
2
|
+
import data from 'emojibase-data/en/data.json'
|
|
3
|
+
import messages from 'emojibase-data/en/messages.json'
|
|
4
|
+
import emojibaseShortcodes from 'emojibase-data/en/shortcodes/emojibase.json'
|
|
5
|
+
import gitHubShortcodes from 'emojibase-data/en/shortcodes/github.json'
|
|
6
|
+
import fs from 'fs'
|
|
7
|
+
import json5 from 'json5'
|
|
8
|
+
|
|
9
|
+
import { EmojiItem } from './emoji.js'
|
|
10
|
+
import { removeVariationSelector } from './helpers/removeVariationSelector.js'
|
|
11
|
+
|
|
12
|
+
const emojis: EmojiItem[] = data
|
|
13
|
+
// .filter(emoji => emoji.version > 0 && emoji.version < 14)
|
|
14
|
+
.map(emoji => {
|
|
15
|
+
const dataSourceEmoji = dataSource.find(item => {
|
|
16
|
+
return item.unified === emoji.hexcode || item.non_qualified === emoji.hexcode
|
|
17
|
+
})
|
|
18
|
+
const hasFallbackImage = dataSourceEmoji?.has_img_apple
|
|
19
|
+
const name = [gitHubShortcodes[emoji.hexcode]].flat()[0]
|
|
20
|
+
|| [emojibaseShortcodes[emoji.hexcode]].flat()[0]
|
|
21
|
+
const shortcodes = emojibaseShortcodes[emoji.hexcode]
|
|
22
|
+
? [emojibaseShortcodes[emoji.hexcode]].flat()
|
|
23
|
+
: []
|
|
24
|
+
const emoticons = emoji.emoticon
|
|
25
|
+
? [emoji.emoticon].flat()
|
|
26
|
+
: []
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
emoji: removeVariationSelector(emoji.emoji),
|
|
30
|
+
name,
|
|
31
|
+
shortcodes,
|
|
32
|
+
tags: emoji.tags || [],
|
|
33
|
+
group: emoji.group ? messages.groups[emoji.group].message : '',
|
|
34
|
+
emoticons,
|
|
35
|
+
version: emoji.version,
|
|
36
|
+
fallbackImage: hasFallbackImage
|
|
37
|
+
? `https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/${dataSourceEmoji.image}`
|
|
38
|
+
: undefined,
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const gitHubCustomEmojiNames = [
|
|
43
|
+
'atom',
|
|
44
|
+
'basecamp',
|
|
45
|
+
'basecampy',
|
|
46
|
+
'bowtie',
|
|
47
|
+
'electron',
|
|
48
|
+
'feelsgood',
|
|
49
|
+
'finnadie',
|
|
50
|
+
'goberserk',
|
|
51
|
+
'godmode',
|
|
52
|
+
'hurtrealbad',
|
|
53
|
+
'neckbeard',
|
|
54
|
+
'octocat',
|
|
55
|
+
'rage1',
|
|
56
|
+
'rage2',
|
|
57
|
+
'rage3',
|
|
58
|
+
'rage4',
|
|
59
|
+
'shipit',
|
|
60
|
+
'suspect',
|
|
61
|
+
'trollface',
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
const gitHubCustomEmojis: EmojiItem[] = gitHubCustomEmojiNames.map(name => {
|
|
65
|
+
return {
|
|
66
|
+
name,
|
|
67
|
+
shortcodes: [name],
|
|
68
|
+
tags: [],
|
|
69
|
+
group: 'GitHub',
|
|
70
|
+
fallbackImage: `https://github.githubassets.com/images/icons/emoji/${name}.png`,
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const content = `// This is a generated file
|
|
75
|
+
|
|
76
|
+
import { EmojiItem } from './emoji'
|
|
77
|
+
|
|
78
|
+
export const emojis: EmojiItem[] = ${json5.stringify(emojis, { space: 2 })}
|
|
79
|
+
|
|
80
|
+
export const gitHubCustomEmojis: EmojiItem[] = ${json5.stringify(gitHubCustomEmojis, { space: 2 })}
|
|
81
|
+
|
|
82
|
+
export const gitHubEmojis: EmojiItem[] = [...emojis, ...gitHubCustomEmojis]
|
|
83
|
+
`
|
|
84
|
+
|
|
85
|
+
fs.writeFileSync('./src/data.ts', content)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { EmojiItem } from '../emoji.js'
|
|
2
|
+
import { removeVariationSelector } from './removeVariationSelector.js'
|
|
3
|
+
|
|
4
|
+
export function emojiToShortcode(emoji: string, emojis: EmojiItem[]): string | undefined {
|
|
5
|
+
return emojis.find(item => item.emoji === removeVariationSelector(emoji))?.shortcodes[0]
|
|
6
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes duplicated values within an array.
|
|
3
|
+
* Supports numbers, strings and objects.
|
|
4
|
+
*/
|
|
5
|
+
export function removeDuplicates<T>(array: T[], by = JSON.stringify): T[] {
|
|
6
|
+
const seen: Record<any, any> = {}
|
|
7
|
+
|
|
8
|
+
return array.filter(item => {
|
|
9
|
+
const key = by(item)
|
|
10
|
+
|
|
11
|
+
return Object.prototype.hasOwnProperty.call(seen, key)
|
|
12
|
+
? false
|
|
13
|
+
: (seen[key] = true)
|
|
14
|
+
})
|
|
15
|
+
}
|