@ckeditor/ckeditor5-emoji 44.2.0-alpha.7 → 44.2.0-alpha.9
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/LICENSE.md +1 -0
- package/build/emoji.js +1 -1
- package/ckeditor5-metadata.json +15 -2
- package/dist/index.js +284 -139
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/augmentation.d.ts +2 -1
- package/src/emojiconfig.d.ts +2 -1
- package/src/emojimention.d.ts +3 -2
- package/src/emojimention.js +8 -8
- package/src/emojipicker.d.ts +3 -3
- package/src/emojipicker.js +14 -14
- package/src/emojirepository.d.ts +35 -17
- package/src/emojirepository.js +86 -127
- package/src/emojiutils.d.ts +58 -0
- package/src/emojiutils.js +141 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/utils/isemojisupported.d.ts +11 -0
- package/src/utils/isemojisupported.js +68 -0
package/dist/index.js
CHANGED
|
@@ -3,16 +3,76 @@
|
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
5
|
import { Plugin, Command, icons } from '@ckeditor/ckeditor5-core/dist/index.js';
|
|
6
|
-
import { logWarning, FocusTracker, KeystrokeHandler, global, Collection } from '@ckeditor/ckeditor5-utils/dist/index.js';
|
|
6
|
+
import { version, logWarning, FocusTracker, KeystrokeHandler, global, Collection } from '@ckeditor/ckeditor5-utils/dist/index.js';
|
|
7
7
|
import { Typing } from '@ckeditor/ckeditor5-typing/dist/index.js';
|
|
8
8
|
import Fuse from 'fuse.js';
|
|
9
9
|
import { groupBy, escapeRegExp } from 'lodash-es';
|
|
10
10
|
import { View, addKeyboardHandlingForGrid, ButtonView, FocusCycler, SearchTextView, createLabeledInputText, createDropdown, ViewModel, addListToDropdown, SearchInfoView, ContextualBalloon, Dialog, MenuBarMenuListItemButtonView, clickOutsideHandler } from '@ckeditor/ckeditor5-ui/dist/index.js';
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @license Copyright (c) 2023, Koala Interactive SAS
|
|
14
|
+
* For licensing, see https://github.com/koala-interactive/is-emoji-supported/blob/master/LICENSE.md
|
|
15
|
+
*/ /**
|
|
16
|
+
* @module emoji/utils/isemojisupported
|
|
17
|
+
*/ /**
|
|
18
|
+
* Checks if the two pixels parts are the same using canvas.
|
|
19
|
+
*/ function isEmojiSupported(unicode) {
|
|
20
|
+
const ctx = getCanvas();
|
|
21
|
+
/* istanbul ignore next -- @preserve */ if (!ctx) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const CANVAS_HEIGHT = 25;
|
|
25
|
+
const CANVAS_WIDTH = 20;
|
|
26
|
+
const textSize = Math.floor(CANVAS_HEIGHT / 2);
|
|
27
|
+
// Initialize canvas context.
|
|
28
|
+
ctx.font = textSize + 'px Arial, Sans-Serif';
|
|
29
|
+
ctx.textBaseline = 'top';
|
|
30
|
+
ctx.canvas.width = CANVAS_WIDTH * 2;
|
|
31
|
+
ctx.canvas.height = CANVAS_HEIGHT;
|
|
32
|
+
ctx.clearRect(0, 0, CANVAS_WIDTH * 2, CANVAS_HEIGHT);
|
|
33
|
+
// Draw in red on the left.
|
|
34
|
+
ctx.fillStyle = '#FF0000';
|
|
35
|
+
ctx.fillText(unicode, 0, 22);
|
|
36
|
+
// Draw in blue on right.
|
|
37
|
+
ctx.fillStyle = '#0000FF';
|
|
38
|
+
ctx.fillText(unicode, CANVAS_WIDTH, 22);
|
|
39
|
+
const a = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT).data;
|
|
40
|
+
const count = a.length;
|
|
41
|
+
let i = 0;
|
|
42
|
+
// Search the first visible pixel.
|
|
43
|
+
for(; i < count && !a[i + 3]; i += 4);
|
|
44
|
+
// No visible pixel.
|
|
45
|
+
/* istanbul ignore next -- @preserve */ if (i >= count) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
// Emoji has immutable color, so we check the color of the emoji in two different colors.
|
|
49
|
+
// the result show be the same.
|
|
50
|
+
const x = CANVAS_WIDTH + i / 4 % CANVAS_WIDTH;
|
|
51
|
+
const y = Math.floor(i / 4 / CANVAS_WIDTH);
|
|
52
|
+
const b = ctx.getImageData(x, y, 1, 1).data;
|
|
53
|
+
if (a[i] !== b[0] || a[i + 2] !== b[2]) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
//Some emojis consist of different ones, so they will show multiple characters if they are not supported.
|
|
57
|
+
/* istanbul ignore next -- @preserve */ if (ctx.measureText(unicode).width >= CANVAS_WIDTH) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
// Supported.
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
function getCanvas() {
|
|
64
|
+
try {
|
|
65
|
+
return document.createElement('canvas').getContext('2d', {
|
|
66
|
+
willReadFrequently: true
|
|
67
|
+
});
|
|
68
|
+
} catch {
|
|
69
|
+
/* istanbul ignore next -- @preserve */ return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @module emoji/emojiutils
|
|
75
|
+
*/ const SKIN_TONE_MAP = {
|
|
16
76
|
0: 'default',
|
|
17
77
|
1: 'light',
|
|
18
78
|
2: 'medium-light',
|
|
@@ -20,22 +80,141 @@ const SKIN_TONE_MAP = {
|
|
|
20
80
|
4: 'medium-dark',
|
|
21
81
|
5: 'dark'
|
|
22
82
|
};
|
|
83
|
+
/**
|
|
84
|
+
* A map representing an emoji and its release version.
|
|
85
|
+
* It's used to identify a user's minimal supported emoji level.
|
|
86
|
+
*/ const EMOJI_SUPPORT_LEVEL = {
|
|
87
|
+
'': 16,
|
|
88
|
+
'🫨': 15.1 // Shaking head. Although the version of emoji is 15, it is used to detect versions 15 and 15.1.
|
|
89
|
+
};
|
|
23
90
|
const BASELINE_EMOJI_WIDTH = 24;
|
|
91
|
+
/**
|
|
92
|
+
* The Emoji utilities plugin.
|
|
93
|
+
*/ class EmojiUtils extends Plugin {
|
|
94
|
+
/**
|
|
95
|
+
* @inheritDoc
|
|
96
|
+
*/ static get pluginName() {
|
|
97
|
+
return 'EmojiUtils';
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* @inheritDoc
|
|
101
|
+
*/ static get isOfficialPlugin() {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Checks if the emoji is supported by verifying the emoji version supported by the system first.
|
|
106
|
+
* Then checks if emoji contains a zero width joiner (ZWJ), and if yes, then checks if it is supported by the system.
|
|
107
|
+
*/ isEmojiSupported(item, emojiSupportedVersionByOs, container) {
|
|
108
|
+
const isEmojiVersionSupported = item.version <= emojiSupportedVersionByOs;
|
|
109
|
+
if (!isEmojiVersionSupported) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (!this.hasZwj(item.emoji)) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return this.isEmojiZwjSupported(item, container);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Checks the supported emoji version by the OS, by sampling some representatives from different emoji releases.
|
|
119
|
+
*/ getEmojiSupportedVersionByOs() {
|
|
120
|
+
return Object.entries(EMOJI_SUPPORT_LEVEL).reduce((currentVersion, [emoji, newVersion])=>{
|
|
121
|
+
if (newVersion > currentVersion && EmojiUtils._isEmojiSupported(emoji)) {
|
|
122
|
+
return newVersion;
|
|
123
|
+
}
|
|
124
|
+
return currentVersion;
|
|
125
|
+
}, 0);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check for ZWJ (zero width joiner) character.
|
|
129
|
+
*/ hasZwj(emoji) {
|
|
130
|
+
return emoji.includes('\u200d');
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Checks whether the emoji is supported in the operating system.
|
|
134
|
+
*/ isEmojiZwjSupported(item, container) {
|
|
135
|
+
const emojiWidth = this.getNodeWidth(container, item.emoji);
|
|
136
|
+
// On Windows, some supported emoji are ~50% bigger than the baseline emoji, but what we really want to guard
|
|
137
|
+
// against are the ones that are 2x the size, because those are truly broken (person with red hair = person with
|
|
138
|
+
// floating red wig, black cat = cat with black square, polar bear = bear with snowflake, etc.)
|
|
139
|
+
// So here we set the threshold at 1.8 times the size of the baseline emoji.
|
|
140
|
+
return emojiWidth < BASELINE_EMOJI_WIDTH * 1.8;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Returns the width of the provided node.
|
|
144
|
+
*/ getNodeWidth(container, node) {
|
|
145
|
+
const span = document.createElement('span');
|
|
146
|
+
span.textContent = node;
|
|
147
|
+
container.appendChild(span);
|
|
148
|
+
const nodeWidth = span.offsetWidth;
|
|
149
|
+
container.removeChild(span);
|
|
150
|
+
return nodeWidth;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Creates a div for emoji width testing purposes.
|
|
154
|
+
*/ createEmojiWidthTestingContainer() {
|
|
155
|
+
const container = document.createElement('div');
|
|
156
|
+
container.setAttribute('aria-hidden', 'true');
|
|
157
|
+
container.style.position = 'absolute';
|
|
158
|
+
container.style.left = '-9999px';
|
|
159
|
+
container.style.whiteSpace = 'nowrap';
|
|
160
|
+
container.style.fontSize = BASELINE_EMOJI_WIDTH + 'px';
|
|
161
|
+
return container;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Adds default skin tone property to each emoji. If emoji defines other skin tones, they are added as well.
|
|
165
|
+
*/ normalizeEmojiSkinTone(item) {
|
|
166
|
+
const entry = {
|
|
167
|
+
...item,
|
|
168
|
+
skins: {
|
|
169
|
+
default: item.emoji
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
if (item.skins) {
|
|
173
|
+
item.skins.forEach((skin)=>{
|
|
174
|
+
const skinTone = SKIN_TONE_MAP[skin.tone];
|
|
175
|
+
entry.skins[skinTone] = skin.emoji;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return entry;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Checks whether the emoji belongs to a group that is allowed.
|
|
182
|
+
*/ isEmojiCategoryAllowed(item) {
|
|
183
|
+
// Category group=2 contains skin tones only, which we do not want to render.
|
|
184
|
+
return item.group !== 2;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* A function used to determine if emoji is supported by detecting pixels.
|
|
188
|
+
*
|
|
189
|
+
* Referenced for unit testing purposes. Kept in a separate file because of licensing.
|
|
190
|
+
*/ static _isEmojiSupported = isEmojiSupported;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// An endpoint from which the emoji data will be downloaded during plugin initialization.
|
|
194
|
+
// The `{version}` placeholder is replaced with the value from editor config.
|
|
195
|
+
const EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{version}/en.json';
|
|
24
196
|
/**
|
|
25
197
|
* The emoji repository plugin.
|
|
26
198
|
*
|
|
27
|
-
* Loads the emoji
|
|
199
|
+
* Loads the emoji repository from URL during plugin initialization and provides utility methods to search it.
|
|
28
200
|
*/ class EmojiRepository extends Plugin {
|
|
29
|
-
/**
|
|
30
|
-
* Emoji database.
|
|
31
|
-
*/ _database;
|
|
32
|
-
/**
|
|
33
|
-
* A promise resolved after downloading the emoji database.
|
|
34
|
-
* The promise resolves with `true` when the database is successfully downloaded or `false` otherwise.
|
|
35
|
-
*/ _databasePromise;
|
|
36
201
|
/**
|
|
37
202
|
* An instance of the [Fuse.js](https://www.fusejs.io/) library.
|
|
38
203
|
*/ _fuseSearch;
|
|
204
|
+
/**
|
|
205
|
+
* The emoji version that is used to prepare the emoji repository.
|
|
206
|
+
*/ _version;
|
|
207
|
+
/**
|
|
208
|
+
* A promise resolved after downloading the emoji collection.
|
|
209
|
+
* The promise resolves with `true` when the repository is successfully downloaded or `false` otherwise.
|
|
210
|
+
*/ _repositoryPromise;
|
|
211
|
+
/**
|
|
212
|
+
* @inheritDoc
|
|
213
|
+
*/ static get requires() {
|
|
214
|
+
return [
|
|
215
|
+
EmojiUtils
|
|
216
|
+
];
|
|
217
|
+
}
|
|
39
218
|
/**
|
|
40
219
|
* @inheritDoc
|
|
41
220
|
*/ static get pluginName() {
|
|
@@ -50,33 +229,31 @@ const BASELINE_EMOJI_WIDTH = 24;
|
|
|
50
229
|
* @inheritDoc
|
|
51
230
|
*/ constructor(editor){
|
|
52
231
|
super(editor);
|
|
53
|
-
|
|
232
|
+
editor.config.define('emoji', {
|
|
54
233
|
version: 16,
|
|
55
234
|
skinTone: 'default'
|
|
56
235
|
});
|
|
57
|
-
this.
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
236
|
+
this._version = editor.config.get('emoji.version');
|
|
237
|
+
this._repositoryPromise = new Promise((resolve)=>{
|
|
238
|
+
this._repositoryPromiseResolveCallback = resolve;
|
|
60
239
|
});
|
|
61
240
|
this._fuseSearch = null;
|
|
62
241
|
}
|
|
63
242
|
/**
|
|
64
243
|
* @inheritDoc
|
|
65
244
|
*/ async init() {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if
|
|
72
|
-
|
|
245
|
+
if (!(this._version in EmojiRepository._results)) {
|
|
246
|
+
const cdnResult = await this._loadItemsFromCdn();
|
|
247
|
+
EmojiRepository._results[this._version] = this._normalizeEmoji(cdnResult);
|
|
248
|
+
}
|
|
249
|
+
const items = this._getItems();
|
|
250
|
+
// Skip plugin initialization if the emoji repository is not available.
|
|
251
|
+
// The initialization of other dependent plugins, such as `EmojiMention` and `EmojiPicker`, is prevented as well.
|
|
252
|
+
if (!items) {
|
|
253
|
+
return this._repositoryPromiseResolveCallback(false);
|
|
73
254
|
}
|
|
74
|
-
const container = createEmojiWidthTestingContainer();
|
|
75
|
-
// Store the emoji database after normalizing the raw data.
|
|
76
|
-
this._database = emojiDatabase.filter((item)=>isEmojiCategoryAllowed(item)).filter((item)=>EmojiRepository._isEmojiSupported(item, container)).map((item)=>normalizeEmojiSkinTone(item));
|
|
77
|
-
container.remove();
|
|
78
255
|
// Create instance of the Fuse.js library with configured weighted search keys and disabled fuzzy search.
|
|
79
|
-
this._fuseSearch = new Fuse(
|
|
256
|
+
this._fuseSearch = new Fuse(items, {
|
|
80
257
|
keys: [
|
|
81
258
|
{
|
|
82
259
|
name: 'emoticon',
|
|
@@ -95,11 +272,11 @@ const BASELINE_EMOJI_WIDTH = 24;
|
|
|
95
272
|
threshold: 0,
|
|
96
273
|
ignoreLocation: true
|
|
97
274
|
});
|
|
98
|
-
return this.
|
|
275
|
+
return this._repositoryPromiseResolveCallback(true);
|
|
99
276
|
}
|
|
100
277
|
/**
|
|
101
278
|
* Returns an array of emoji entries that match the search query.
|
|
102
|
-
* If the emoji
|
|
279
|
+
* If the emoji repository is not loaded, the [Fuse.js](https://www.fusejs.io/) instance is not created,
|
|
103
280
|
* hence this method returns an empty array.
|
|
104
281
|
*
|
|
105
282
|
* @param searchQuery A search query to match emoji.
|
|
@@ -134,11 +311,12 @@ const BASELINE_EMOJI_WIDTH = 24;
|
|
|
134
311
|
}
|
|
135
312
|
/**
|
|
136
313
|
* Groups all emojis by categories.
|
|
137
|
-
* If the emoji
|
|
314
|
+
* If the emoji repository is not loaded, it returns an empty array.
|
|
138
315
|
*
|
|
139
316
|
* @returns An array of emoji entries grouped by categories.
|
|
140
317
|
*/ getEmojiCategories() {
|
|
141
|
-
|
|
318
|
+
const repository = this._getItems();
|
|
319
|
+
if (!repository) {
|
|
142
320
|
return [];
|
|
143
321
|
}
|
|
144
322
|
const { t } = this.editor.locale;
|
|
@@ -189,7 +367,7 @@ const BASELINE_EMOJI_WIDTH = 24;
|
|
|
189
367
|
groupId: 9
|
|
190
368
|
}
|
|
191
369
|
];
|
|
192
|
-
const groups = groupBy(
|
|
370
|
+
const groups = groupBy(repository, 'group');
|
|
193
371
|
return categories.map((category)=>{
|
|
194
372
|
return {
|
|
195
373
|
...category,
|
|
@@ -235,94 +413,61 @@ const BASELINE_EMOJI_WIDTH = 24;
|
|
|
235
413
|
];
|
|
236
414
|
}
|
|
237
415
|
/**
|
|
238
|
-
* Indicates whether the emoji
|
|
416
|
+
* Indicates whether the emoji repository has been successfully downloaded and the plugin is operational.
|
|
239
417
|
*/ isReady() {
|
|
240
|
-
return this.
|
|
418
|
+
return this._repositoryPromise;
|
|
241
419
|
}
|
|
242
420
|
/**
|
|
243
|
-
*
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
421
|
+
* Returns the emoji repository in a configured version if it is a non-empty array. Returns `null` otherwise.
|
|
422
|
+
*/ _getItems() {
|
|
423
|
+
const repository = EmojiRepository._results[this._version];
|
|
424
|
+
return repository && repository.length ? repository : null;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Makes the HTTP request to download the emoji repository in a configured version.
|
|
428
|
+
*/ async _loadItemsFromCdn() {
|
|
429
|
+
const repositoryUrl = new URL(EMOJI_DATABASE_URL.replace('{version}', `${this._version}`));
|
|
430
|
+
repositoryUrl.searchParams.set('editorVersion', version);
|
|
431
|
+
const result = await fetch(repositoryUrl, {
|
|
432
|
+
cache: 'force-cache'
|
|
433
|
+
}).then((response)=>{
|
|
434
|
+
if (!response.ok) {
|
|
435
|
+
return [];
|
|
436
|
+
}
|
|
437
|
+
return response.json();
|
|
438
|
+
}).catch(()=>{
|
|
253
439
|
return [];
|
|
254
|
-
}
|
|
255
|
-
return response.json();
|
|
256
|
-
}).catch(()=>{
|
|
257
|
-
return [];
|
|
258
|
-
});
|
|
259
|
-
if (!result.length) {
|
|
260
|
-
/**
|
|
261
|
-
* Unable to load the emoji database from CDN.
|
|
262
|
-
*
|
|
263
|
-
* If the CDN works properly and there is no disruption of communication, please check your
|
|
264
|
-
* {@glink getting-started/setup/csp Content Security Policy (CSP)} setting and make sure
|
|
265
|
-
* the CDN connection is allowed by the editor.
|
|
266
|
-
*
|
|
267
|
-
* @error emoji-database-load-failed
|
|
268
|
-
*/ logWarning('emoji-database-load-failed');
|
|
269
|
-
}
|
|
270
|
-
return result;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Creates a div for emoji width testing purposes.
|
|
274
|
-
*/ function createEmojiWidthTestingContainer() {
|
|
275
|
-
const container = document.createElement('div');
|
|
276
|
-
container.setAttribute('aria-hidden', 'true');
|
|
277
|
-
container.style.position = 'absolute';
|
|
278
|
-
container.style.left = '-9999px';
|
|
279
|
-
container.style.whiteSpace = 'nowrap';
|
|
280
|
-
container.style.fontSize = BASELINE_EMOJI_WIDTH + 'px';
|
|
281
|
-
document.body.appendChild(container);
|
|
282
|
-
return container;
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Returns the width of the provided node.
|
|
286
|
-
*/ function getNodeWidth(container, node) {
|
|
287
|
-
const span = document.createElement('span');
|
|
288
|
-
span.textContent = node;
|
|
289
|
-
container.appendChild(span);
|
|
290
|
-
const nodeWidth = span.offsetWidth;
|
|
291
|
-
container.removeChild(span);
|
|
292
|
-
return nodeWidth;
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Checks whether the emoji is supported in the operating system.
|
|
296
|
-
*/ function isEmojiSupported(item, container) {
|
|
297
|
-
const emojiWidth = getNodeWidth(container, item.emoji);
|
|
298
|
-
// On Windows, some supported emoji are ~50% bigger than the baseline emoji, but what we really want to guard
|
|
299
|
-
// against are the ones that are 2x the size, because those are truly broken (person with red hair = person with
|
|
300
|
-
// floating red wig, black cat = cat with black square, polar bear = bear with snowflake, etc.)
|
|
301
|
-
// So here we set the threshold at 1.8 times the size of the baseline emoji.
|
|
302
|
-
return emojiWidth / 1.8 < BASELINE_EMOJI_WIDTH && emojiWidth >= BASELINE_EMOJI_WIDTH;
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Adds default skin tone property to each emoji. If emoji defines other skin tones, they are added as well.
|
|
306
|
-
*/ function normalizeEmojiSkinTone(item) {
|
|
307
|
-
const entry = {
|
|
308
|
-
...item,
|
|
309
|
-
skins: {
|
|
310
|
-
default: item.emoji
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
if (item.skins) {
|
|
314
|
-
item.skins.forEach((skin)=>{
|
|
315
|
-
const skinTone = SKIN_TONE_MAP[skin.tone];
|
|
316
|
-
entry.skins[skinTone] = skin.emoji;
|
|
317
440
|
});
|
|
441
|
+
if (!result.length) {
|
|
442
|
+
/**
|
|
443
|
+
* Unable to load the emoji repository from CDN.
|
|
444
|
+
*
|
|
445
|
+
* If the CDN works properly and there is no disruption of communication, please check your
|
|
446
|
+
* {@glink getting-started/setup/csp Content Security Policy (CSP)} setting and make sure
|
|
447
|
+
* the CDN connection is allowed by the editor.
|
|
448
|
+
*
|
|
449
|
+
* @error emoji-repository-load-failed
|
|
450
|
+
*/ logWarning('emoji-repository-load-failed');
|
|
451
|
+
}
|
|
452
|
+
return result;
|
|
318
453
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
454
|
+
/**
|
|
455
|
+
* Normalizes the raw data fetched from CDN. By normalization, we meant:
|
|
456
|
+
*
|
|
457
|
+
* * Filter out unsupported emoji (these that will not render correctly),
|
|
458
|
+
* * Prepare skin tone variants if an emoji defines them.
|
|
459
|
+
*/ _normalizeEmoji(data) {
|
|
460
|
+
const emojiUtils = this.editor.plugins.get('EmojiUtils');
|
|
461
|
+
const emojiSupportedVersionByOs = emojiUtils.getEmojiSupportedVersionByOs();
|
|
462
|
+
const container = emojiUtils.createEmojiWidthTestingContainer();
|
|
463
|
+
document.body.appendChild(container);
|
|
464
|
+
const results = data.filter((item)=>emojiUtils.isEmojiCategoryAllowed(item)).filter((item)=>emojiUtils.isEmojiSupported(item, emojiSupportedVersionByOs, container)).map((item)=>emojiUtils.normalizeEmojiSkinTone(item));
|
|
465
|
+
container.remove();
|
|
466
|
+
return results;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Versioned emoji repository.
|
|
470
|
+
*/ static _results = {};
|
|
326
471
|
}
|
|
327
472
|
|
|
328
473
|
const EMOJI_MENTION_MARKER = ':';
|
|
@@ -411,9 +556,9 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
|
|
|
411
556
|
* @inheritDoc
|
|
412
557
|
*/ async init() {
|
|
413
558
|
const editor = this.editor;
|
|
414
|
-
this.
|
|
415
|
-
this.
|
|
416
|
-
this._isEmojiRepositoryAvailable = await this.
|
|
559
|
+
this.emojiPickerPlugin = editor.plugins.has('EmojiPicker') ? editor.plugins.get('EmojiPicker') : null;
|
|
560
|
+
this.emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
|
|
561
|
+
this._isEmojiRepositoryAvailable = await this.emojiRepositoryPlugin.isReady();
|
|
417
562
|
// Override the `mention` command listener if the emoji repository is ready.
|
|
418
563
|
if (this._isEmojiRepositoryAvailable) {
|
|
419
564
|
editor.once('ready', this._overrideMentionExecuteListener.bind(this));
|
|
@@ -470,7 +615,7 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
|
|
|
470
615
|
editor.model.change((writer)=>{
|
|
471
616
|
editor.model.deleteContent(writer.createSelection(eventData.range));
|
|
472
617
|
});
|
|
473
|
-
const emojiPickerPlugin = this.
|
|
618
|
+
const emojiPickerPlugin = this.emojiPickerPlugin;
|
|
474
619
|
emojiPickerPlugin.showUI(text.slice(1));
|
|
475
620
|
setTimeout(()=>{
|
|
476
621
|
emojiPickerPlugin.emojiPickerView.focus();
|
|
@@ -497,17 +642,17 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
|
|
|
497
642
|
if (!this._isEmojiRepositoryAvailable) {
|
|
498
643
|
return [];
|
|
499
644
|
}
|
|
500
|
-
const emojis = this.
|
|
645
|
+
const emojis = this.emojiRepositoryPlugin.getEmojiByQuery(searchQuery).map((emoji)=>{
|
|
501
646
|
let text = emoji.skins[this._skinTone] || emoji.skins.default;
|
|
502
|
-
if (this.
|
|
503
|
-
text = emoji.skins[this.
|
|
647
|
+
if (this.emojiPickerPlugin) {
|
|
648
|
+
text = emoji.skins[this.emojiPickerPlugin.skinTone] || emoji.skins.default;
|
|
504
649
|
}
|
|
505
650
|
return {
|
|
506
651
|
id: `:${emoji.annotation}:`,
|
|
507
652
|
text
|
|
508
653
|
};
|
|
509
654
|
});
|
|
510
|
-
if (!this.
|
|
655
|
+
if (!this.emojiPickerPlugin) {
|
|
511
656
|
return emojis.slice(0, this._emojiDropdownLimit);
|
|
512
657
|
}
|
|
513
658
|
const actionItem = {
|
|
@@ -1245,10 +1390,10 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1245
1390
|
* @inheritDoc
|
|
1246
1391
|
*/ async init() {
|
|
1247
1392
|
const editor = this.editor;
|
|
1248
|
-
this.
|
|
1249
|
-
this.
|
|
1393
|
+
this.balloonPlugin = editor.plugins.get('ContextualBalloon');
|
|
1394
|
+
this.emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
|
|
1250
1395
|
// Skip registering a button in the toolbar and list item in the menu bar if the emoji repository is not ready.
|
|
1251
|
-
if (!await this.
|
|
1396
|
+
if (!await this.emojiRepositoryPlugin.isReady()) {
|
|
1252
1397
|
return;
|
|
1253
1398
|
}
|
|
1254
1399
|
const command = new EmojiCommand(editor);
|
|
@@ -1299,8 +1444,8 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1299
1444
|
this.emojiPickerView.searchView.setInputValue(searchValue);
|
|
1300
1445
|
}
|
|
1301
1446
|
this.emojiPickerView.searchView.search(searchValue);
|
|
1302
|
-
if (!this.
|
|
1303
|
-
this.
|
|
1447
|
+
if (!this.balloonPlugin.hasView(this.emojiPickerView)) {
|
|
1448
|
+
this.balloonPlugin.add({
|
|
1304
1449
|
view: this.emojiPickerView,
|
|
1305
1450
|
position: this._getBalloonPositionData()
|
|
1306
1451
|
});
|
|
@@ -1327,11 +1472,11 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1327
1472
|
* Creates an instance of the `EmojiPickerView` class that represents an emoji balloon.
|
|
1328
1473
|
*/ _createEmojiPickerView() {
|
|
1329
1474
|
const emojiPickerView = new EmojiPickerView(this.editor.locale, {
|
|
1330
|
-
emojiCategories: this.
|
|
1475
|
+
emojiCategories: this.emojiRepositoryPlugin.getEmojiCategories(),
|
|
1331
1476
|
skinTone: this.editor.config.get('emoji.skinTone'),
|
|
1332
|
-
skinTones: this.
|
|
1477
|
+
skinTones: this.emojiRepositoryPlugin.getSkinTones(),
|
|
1333
1478
|
getEmojiByQuery: (query)=>{
|
|
1334
|
-
return this.
|
|
1479
|
+
return this.emojiRepositoryPlugin.getEmojiByQuery(query);
|
|
1335
1480
|
}
|
|
1336
1481
|
});
|
|
1337
1482
|
// Insert an emoji on a tile click.
|
|
@@ -1345,8 +1490,8 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1345
1490
|
});
|
|
1346
1491
|
// Update the balloon position when layout is changed.
|
|
1347
1492
|
this.listenTo(emojiPickerView, 'update', ()=>{
|
|
1348
|
-
if (this.
|
|
1349
|
-
this.
|
|
1493
|
+
if (this.balloonPlugin.visibleView === emojiPickerView) {
|
|
1494
|
+
this.balloonPlugin.updatePosition();
|
|
1350
1495
|
}
|
|
1351
1496
|
});
|
|
1352
1497
|
// Close the panel on `Esc` key press when the **actions have focus**.
|
|
@@ -1358,17 +1503,17 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1358
1503
|
clickOutsideHandler({
|
|
1359
1504
|
emitter: emojiPickerView,
|
|
1360
1505
|
contextElements: [
|
|
1361
|
-
this.
|
|
1506
|
+
this.balloonPlugin.view.element
|
|
1362
1507
|
],
|
|
1363
1508
|
callback: ()=>this._hideUI(),
|
|
1364
|
-
activator: ()=>this.
|
|
1509
|
+
activator: ()=>this.balloonPlugin.visibleView === emojiPickerView
|
|
1365
1510
|
});
|
|
1366
1511
|
return emojiPickerView;
|
|
1367
1512
|
}
|
|
1368
1513
|
/**
|
|
1369
1514
|
* Hides the balloon with the emoji picker.
|
|
1370
1515
|
*/ _hideUI() {
|
|
1371
|
-
this.
|
|
1516
|
+
this.balloonPlugin.remove(this.emojiPickerView);
|
|
1372
1517
|
this.emojiPickerView.searchView.setInputValue('');
|
|
1373
1518
|
this.editor.editing.view.focus();
|
|
1374
1519
|
this._hideFakeVisualSelection();
|
|
@@ -1403,7 +1548,7 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1403
1548
|
});
|
|
1404
1549
|
}
|
|
1405
1550
|
/**
|
|
1406
|
-
* Returns positioning options for the {@link #
|
|
1551
|
+
* Returns positioning options for the {@link #balloonPlugin}. They control the way the balloon is attached
|
|
1407
1552
|
* to the target element or selection.
|
|
1408
1553
|
*/ _getBalloonPositionData() {
|
|
1409
1554
|
const view = this.editor.editing.view;
|
|
@@ -1486,5 +1631,5 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1486
1631
|
}
|
|
1487
1632
|
}
|
|
1488
1633
|
|
|
1489
|
-
export { Emoji, EmojiCommand, EmojiMention, EmojiPicker, EmojiRepository };
|
|
1634
|
+
export { Emoji, EmojiCommand, EmojiMention, EmojiPicker, EmojiRepository, EmojiUtils };
|
|
1490
1635
|
//# sourceMappingURL=index.js.map
|