@ckeditor/ckeditor5-emoji 44.2.0-alpha.16 → 44.2.0-alpha.2
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 +0 -1
- package/build/emoji.js +1 -1
- package/ckeditor5-metadata.json +2 -15
- package/dist/index-editor.css +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.js +151 -371
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/augmentation.d.ts +1 -2
- package/src/emojiconfig.d.ts +2 -20
- package/src/emojimention.d.ts +6 -13
- package/src/emojimention.js +26 -43
- package/src/emojipicker.d.ts +3 -3
- package/src/emojipicker.js +14 -14
- package/src/emojirepository.d.ts +17 -47
- package/src/emojirepository.js +125 -147
- package/src/index.d.ts +0 -1
- package/src/index.js +0 -1
- package/src/ui/emojipickerview.js +0 -1
- package/theme/emojigrid.css +1 -1
- package/src/emojiutils.d.ts +0 -58
- package/src/emojiutils.js +0 -141
- package/src/utils/isemojisupported.d.ts +0 -11
- package/src/utils/isemojisupported.js +0 -68
package/dist/index.js
CHANGED
|
@@ -3,76 +3,16 @@
|
|
|
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 {
|
|
6
|
+
import { 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
|
-
|
|
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 = {
|
|
12
|
+
// An endpoint from which the emoji database will be downloaded during plugin initialization.
|
|
13
|
+
// The `{version}` placeholder is replaced with the value from editor config.
|
|
14
|
+
const EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{version}/en.json';
|
|
15
|
+
const SKIN_TONE_MAP = {
|
|
76
16
|
0: 'default',
|
|
77
17
|
1: 'light',
|
|
78
18
|
2: 'medium-light',
|
|
@@ -80,142 +20,22 @@ function getCanvas() {
|
|
|
80
20
|
4: 'medium-dark',
|
|
81
21
|
5: 'dark'
|
|
82
22
|
};
|
|
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
|
-
};
|
|
90
23
|
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 DEFAULT_EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{version}/en.json';
|
|
196
|
-
const DEFAULT_EMOJI_VERSION = 16;
|
|
197
24
|
/**
|
|
198
25
|
* The emoji repository plugin.
|
|
199
26
|
*
|
|
200
|
-
* Loads the emoji
|
|
27
|
+
* Loads the emoji database from URL during plugin initialization and provides utility methods to search it.
|
|
201
28
|
*/ class EmojiRepository extends Plugin {
|
|
202
29
|
/**
|
|
203
|
-
*
|
|
204
|
-
*/
|
|
205
|
-
/**
|
|
206
|
-
* The resolved URL from which the emoji repository is downloaded.
|
|
207
|
-
*/ _url;
|
|
30
|
+
* Emoji database.
|
|
31
|
+
*/ _database;
|
|
208
32
|
/**
|
|
209
|
-
* A promise resolved after downloading the emoji
|
|
210
|
-
* The promise resolves with `true` when the
|
|
211
|
-
*/
|
|
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;
|
|
212
36
|
/**
|
|
213
|
-
*
|
|
214
|
-
*/
|
|
215
|
-
return [
|
|
216
|
-
EmojiUtils
|
|
217
|
-
];
|
|
218
|
-
}
|
|
37
|
+
* An instance of the [Fuse.js](https://www.fusejs.io/) library.
|
|
38
|
+
*/ _fuseSearch;
|
|
219
39
|
/**
|
|
220
40
|
* @inheritDoc
|
|
221
41
|
*/ static get pluginName() {
|
|
@@ -230,30 +50,33 @@ const DEFAULT_EMOJI_VERSION = 16;
|
|
|
230
50
|
* @inheritDoc
|
|
231
51
|
*/ constructor(editor){
|
|
232
52
|
super(editor);
|
|
233
|
-
editor.config.define('emoji', {
|
|
234
|
-
version:
|
|
235
|
-
skinTone: 'default'
|
|
236
|
-
definitionsUrl: undefined
|
|
53
|
+
this.editor.config.define('emoji', {
|
|
54
|
+
version: 16,
|
|
55
|
+
skinTone: 'default'
|
|
237
56
|
});
|
|
238
|
-
this.
|
|
239
|
-
this.
|
|
240
|
-
this.
|
|
57
|
+
this._database = [];
|
|
58
|
+
this._databasePromise = new Promise((resolve)=>{
|
|
59
|
+
this._databasePromiseResolveCallback = resolve;
|
|
241
60
|
});
|
|
242
61
|
this._fuseSearch = null;
|
|
243
62
|
}
|
|
244
63
|
/**
|
|
245
64
|
* @inheritDoc
|
|
246
65
|
*/ async init() {
|
|
247
|
-
this.
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
// Skip
|
|
251
|
-
//
|
|
252
|
-
if (!
|
|
253
|
-
return this.
|
|
66
|
+
const emojiVersion = this.editor.config.get('emoji.version');
|
|
67
|
+
const emojiDatabaseUrl = EMOJI_DATABASE_URL.replace('{version}', `${emojiVersion}`);
|
|
68
|
+
const emojiDatabase = await loadEmojiDatabase(emojiDatabaseUrl);
|
|
69
|
+
// Skip the initialization if the emoji database download has failed.
|
|
70
|
+
// An empty database prevents the initialization of other dependent plugins, such as `EmojiMention` and `EmojiPicker`.
|
|
71
|
+
if (!emojiDatabase.length) {
|
|
72
|
+
return this._databasePromiseResolveCallback(false);
|
|
254
73
|
}
|
|
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();
|
|
255
78
|
// Create instance of the Fuse.js library with configured weighted search keys and disabled fuzzy search.
|
|
256
|
-
this._fuseSearch = new Fuse(
|
|
79
|
+
this._fuseSearch = new Fuse(this._database, {
|
|
257
80
|
keys: [
|
|
258
81
|
{
|
|
259
82
|
name: 'emoticon',
|
|
@@ -272,11 +95,11 @@ const DEFAULT_EMOJI_VERSION = 16;
|
|
|
272
95
|
threshold: 0,
|
|
273
96
|
ignoreLocation: true
|
|
274
97
|
});
|
|
275
|
-
return this.
|
|
98
|
+
return this._databasePromiseResolveCallback(true);
|
|
276
99
|
}
|
|
277
100
|
/**
|
|
278
101
|
* Returns an array of emoji entries that match the search query.
|
|
279
|
-
* If the emoji
|
|
102
|
+
* If the emoji database is not loaded, the [Fuse.js](https://www.fusejs.io/) instance is not created,
|
|
280
103
|
* hence this method returns an empty array.
|
|
281
104
|
*
|
|
282
105
|
* @param searchQuery A search query to match emoji.
|
|
@@ -311,12 +134,11 @@ const DEFAULT_EMOJI_VERSION = 16;
|
|
|
311
134
|
}
|
|
312
135
|
/**
|
|
313
136
|
* Groups all emojis by categories.
|
|
314
|
-
* If the emoji
|
|
137
|
+
* If the emoji database is not loaded, it returns an empty array.
|
|
315
138
|
*
|
|
316
139
|
* @returns An array of emoji entries grouped by categories.
|
|
317
140
|
*/ getEmojiCategories() {
|
|
318
|
-
|
|
319
|
-
if (!repository) {
|
|
141
|
+
if (!this._database.length) {
|
|
320
142
|
return [];
|
|
321
143
|
}
|
|
322
144
|
const { t } = this.editor.locale;
|
|
@@ -367,7 +189,7 @@ const DEFAULT_EMOJI_VERSION = 16;
|
|
|
367
189
|
groupId: 9
|
|
368
190
|
}
|
|
369
191
|
];
|
|
370
|
-
const groups = groupBy(
|
|
192
|
+
const groups = groupBy(this._database, 'group');
|
|
371
193
|
return categories.map((category)=>{
|
|
372
194
|
return {
|
|
373
195
|
...category,
|
|
@@ -413,120 +235,94 @@ const DEFAULT_EMOJI_VERSION = 16;
|
|
|
413
235
|
];
|
|
414
236
|
}
|
|
415
237
|
/**
|
|
416
|
-
* Indicates whether the emoji
|
|
238
|
+
* Indicates whether the emoji database has been successfully downloaded and the plugin is operational.
|
|
417
239
|
*/ isReady() {
|
|
418
|
-
return this.
|
|
240
|
+
return this._databasePromise;
|
|
419
241
|
}
|
|
420
242
|
/**
|
|
421
|
-
*
|
|
422
|
-
* in the configuration, the default URL is used with the version from the configuration.
|
|
243
|
+
* A function used to check if the given emoji is supported in the operating system.
|
|
423
244
|
*
|
|
424
|
-
*
|
|
425
|
-
*/
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return
|
|
433
|
-
}
|
|
434
|
-
if (version$1) {
|
|
435
|
-
/**
|
|
436
|
-
* Both {@link module:emoji/emojiconfig~EmojiConfig#definitionsUrl `emoji.definitionsUrl`} and
|
|
437
|
-
* {@link module:emoji/emojiconfig~EmojiConfig#version `emoji.version`} configuration options
|
|
438
|
-
* are set. Only the `emoji.definitionsUrl` option will be used.
|
|
439
|
-
*
|
|
440
|
-
* The `emoji.version` option will be ignored and should be removed from the configuration.
|
|
441
|
-
*
|
|
442
|
-
* @error emoji-repository-redundant-version
|
|
443
|
-
*/ logWarning('emoji-repository-redundant-version');
|
|
444
|
-
}
|
|
445
|
-
return new URL(definitionsUrl);
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Warn users on self-hosted installations that this plugin uses a CDN to fetch the emoji repository.
|
|
449
|
-
*/ _warnAboutCdnUse() {
|
|
450
|
-
const editor = this.editor;
|
|
451
|
-
const config = editor.config.get('emoji');
|
|
452
|
-
const licenseKey = editor.config.get('licenseKey');
|
|
453
|
-
const distributionChannel = window[Symbol.for('cke distribution')];
|
|
454
|
-
if (licenseKey === 'GPL') {
|
|
455
|
-
// Don't warn GPL users.
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
if (distributionChannel === 'cloud') {
|
|
459
|
-
// Don't warn cloud users, because they already use our CDN.
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
if (config && config.definitionsUrl) {
|
|
463
|
-
// Don't warn users who have configured their own definitions URL.
|
|
464
|
-
return;
|
|
245
|
+
* Referenced for unit testing purposes.
|
|
246
|
+
*/ static _isEmojiSupported = isEmojiSupported;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Makes the HTTP request to download the emoji database.
|
|
250
|
+
*/ async function loadEmojiDatabase(emojiDatabaseUrl) {
|
|
251
|
+
const result = await fetch(emojiDatabaseUrl).then((response)=>{
|
|
252
|
+
if (!response.ok) {
|
|
253
|
+
return [];
|
|
465
254
|
}
|
|
255
|
+
return response.json();
|
|
256
|
+
}).catch(()=>{
|
|
257
|
+
return [];
|
|
258
|
+
});
|
|
259
|
+
if (!result.length) {
|
|
466
260
|
/**
|
|
467
|
-
*
|
|
468
|
-
* you can use the {@link module:emoji/emojiconfig~EmojiConfig#definitionsUrl `emoji.definitionsUrl`}
|
|
469
|
-
* configuration option to provide a URL to your own emoji repository.
|
|
261
|
+
* Unable to load the emoji database from CDN.
|
|
470
262
|
*
|
|
471
|
-
* If
|
|
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.
|
|
472
266
|
*
|
|
473
|
-
* @error emoji-
|
|
474
|
-
*/ logWarning('emoji-
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Returns the emoji repository in a configured version if it is a non-empty array. Returns `null` otherwise.
|
|
478
|
-
*/ _getItems() {
|
|
479
|
-
const repository = EmojiRepository._results[this._url.href];
|
|
480
|
-
return repository && repository.length ? repository : null;
|
|
267
|
+
* @error emoji-database-load-failed
|
|
268
|
+
*/ logWarning('emoji-database-load-failed');
|
|
481
269
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
|
489
311
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
return response.json();
|
|
497
|
-
}).catch(()=>{
|
|
498
|
-
return [];
|
|
312
|
+
};
|
|
313
|
+
if (item.skins) {
|
|
314
|
+
item.skins.forEach((skin)=>{
|
|
315
|
+
const skinTone = SKIN_TONE_MAP[skin.tone];
|
|
316
|
+
entry.skins[skinTone] = skin.emoji;
|
|
499
317
|
});
|
|
500
|
-
if (!result.length) {
|
|
501
|
-
/**
|
|
502
|
-
* Unable to load the emoji repository from the URL.
|
|
503
|
-
*
|
|
504
|
-
* If the URL works properly and there is no disruption of communication, please check your
|
|
505
|
-
* {@glink getting-started/setup/csp Content Security Policy (CSP)} setting and make sure
|
|
506
|
-
* the URL connection is allowed by the editor.
|
|
507
|
-
*
|
|
508
|
-
* @error emoji-repository-load-failed
|
|
509
|
-
*/ logWarning('emoji-repository-load-failed');
|
|
510
|
-
}
|
|
511
|
-
EmojiRepository._results[this._url.href] = this._normalizeEmoji(result);
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* Normalizes the raw data fetched from CDN. By normalization, we meant:
|
|
515
|
-
*
|
|
516
|
-
* * Filter out unsupported emoji (these that will not render correctly),
|
|
517
|
-
* * Prepare skin tone variants if an emoji defines them.
|
|
518
|
-
*/ _normalizeEmoji(data) {
|
|
519
|
-
const emojiUtils = this.editor.plugins.get('EmojiUtils');
|
|
520
|
-
const emojiSupportedVersionByOs = emojiUtils.getEmojiSupportedVersionByOs();
|
|
521
|
-
const container = emojiUtils.createEmojiWidthTestingContainer();
|
|
522
|
-
document.body.appendChild(container);
|
|
523
|
-
const results = data.filter((item)=>emojiUtils.isEmojiCategoryAllowed(item)).filter((item)=>emojiUtils.isEmojiSupported(item, emojiSupportedVersionByOs, container)).map((item)=>emojiUtils.normalizeEmojiSkinTone(item));
|
|
524
|
-
container.remove();
|
|
525
|
-
return results;
|
|
526
318
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
319
|
+
return entry;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Checks whether the emoji belongs to a group that is allowed.
|
|
323
|
+
*/ function isEmojiCategoryAllowed(item) {
|
|
324
|
+
// Category group=2 contains skin tones only, which we do not want to render.
|
|
325
|
+
return item.group !== 2;
|
|
530
326
|
}
|
|
531
327
|
|
|
532
328
|
const EMOJI_MENTION_MARKER = ':';
|
|
@@ -573,18 +369,11 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
|
|
|
573
369
|
});
|
|
574
370
|
this._emojiDropdownLimit = editor.config.get('emoji.dropdownLimit');
|
|
575
371
|
this._skinTone = editor.config.get('emoji.skinTone');
|
|
576
|
-
this._setupMentionConfiguration(editor);
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
|
-
* Initializes the configuration for emojis in the mention feature.
|
|
580
|
-
* If the marker used by emoji mention is already registered, it displays a warning.
|
|
581
|
-
* If emoji mention configuration is detected, it does not register it for a second time.
|
|
582
|
-
*/ _setupMentionConfiguration(editor) {
|
|
583
|
-
const mergeFieldsPrefix = editor.config.get('mergeFields.prefix');
|
|
584
372
|
const mentionFeedsConfigs = editor.config.get('mention.feeds');
|
|
585
|
-
const
|
|
586
|
-
const
|
|
587
|
-
|
|
373
|
+
const mergeFieldsPrefix = editor.config.get('mergeFields.prefix');
|
|
374
|
+
const markerAlreadyUsed = mentionFeedsConfigs.some((config)=>config.marker === EMOJI_MENTION_MARKER);
|
|
375
|
+
const isMarkerUsedByMergeFields = mergeFieldsPrefix ? mergeFieldsPrefix[0] === EMOJI_MENTION_MARKER : false;
|
|
376
|
+
if (markerAlreadyUsed || isMarkerUsedByMergeFields) {
|
|
588
377
|
/**
|
|
589
378
|
* The `marker` in the `emoji` config is already used by other plugin configuration.
|
|
590
379
|
*
|
|
@@ -595,12 +384,24 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
|
|
|
595
384
|
});
|
|
596
385
|
return;
|
|
597
386
|
}
|
|
598
|
-
|
|
599
|
-
|
|
387
|
+
this._setupMentionConfiguration(mentionFeedsConfigs);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* @inheritDoc
|
|
391
|
+
*/ async init() {
|
|
392
|
+
const editor = this.editor;
|
|
393
|
+
this._emojiPickerPlugin = editor.plugins.has('EmojiPicker') ? editor.plugins.get('EmojiPicker') : null;
|
|
394
|
+
this._emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
|
|
395
|
+
// Skip overriding the `mention` command listener if the emoji repository is not ready.
|
|
396
|
+
if (!await this._emojiRepositoryPlugin.isReady()) {
|
|
600
397
|
return;
|
|
601
398
|
}
|
|
399
|
+
editor.once('ready', this._overrideMentionExecuteListener.bind(this));
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Initializes the configuration for emojis in the mention feature.
|
|
403
|
+
*/ _setupMentionConfiguration(mentionFeedsConfigs) {
|
|
602
404
|
const emojiMentionFeedConfig = {
|
|
603
|
-
_isEmojiMarker: true,
|
|
604
405
|
marker: EMOJI_MENTION_MARKER,
|
|
605
406
|
dropdownLimit: this._emojiDropdownLimit,
|
|
606
407
|
itemRenderer: this._customItemRendererFactory(this.editor.t),
|
|
@@ -611,18 +412,6 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
|
|
|
611
412
|
emojiMentionFeedConfig
|
|
612
413
|
]);
|
|
613
414
|
}
|
|
614
|
-
/**
|
|
615
|
-
* @inheritDoc
|
|
616
|
-
*/ async init() {
|
|
617
|
-
const editor = this.editor;
|
|
618
|
-
this.emojiPickerPlugin = editor.plugins.has('EmojiPicker') ? editor.plugins.get('EmojiPicker') : null;
|
|
619
|
-
this.emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
|
|
620
|
-
this._isEmojiRepositoryAvailable = await this.emojiRepositoryPlugin.isReady();
|
|
621
|
-
// Override the `mention` command listener if the emoji repository is ready.
|
|
622
|
-
if (this._isEmojiRepositoryAvailable) {
|
|
623
|
-
editor.once('ready', this._overrideMentionExecuteListener.bind(this));
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
415
|
/**
|
|
627
416
|
* Returns the `itemRenderer()` callback for mention config.
|
|
628
417
|
*/ _customItemRendererFactory(t) {
|
|
@@ -674,7 +463,7 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
|
|
|
674
463
|
editor.model.change((writer)=>{
|
|
675
464
|
editor.model.deleteContent(writer.createSelection(eventData.range));
|
|
676
465
|
});
|
|
677
|
-
const emojiPickerPlugin = this.
|
|
466
|
+
const emojiPickerPlugin = this._emojiPickerPlugin;
|
|
678
467
|
emojiPickerPlugin.showUI(text.slice(1));
|
|
679
468
|
setTimeout(()=>{
|
|
680
469
|
emojiPickerPlugin.emojiPickerView.focus();
|
|
@@ -697,25 +486,17 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
|
|
|
697
486
|
if (searchQuery.startsWith(' ')) {
|
|
698
487
|
return [];
|
|
699
488
|
}
|
|
700
|
-
|
|
701
|
-
if (searchQuery.startsWith(EMOJI_MENTION_MARKER)) {
|
|
702
|
-
return [];
|
|
703
|
-
}
|
|
704
|
-
// If the repository plugin is not available, return an empty feed to avoid confusion. See: #17842.
|
|
705
|
-
if (!this._isEmojiRepositoryAvailable) {
|
|
706
|
-
return [];
|
|
707
|
-
}
|
|
708
|
-
const emojis = this.emojiRepositoryPlugin.getEmojiByQuery(searchQuery).map((emoji)=>{
|
|
489
|
+
const emojis = this._emojiRepositoryPlugin.getEmojiByQuery(searchQuery).map((emoji)=>{
|
|
709
490
|
let text = emoji.skins[this._skinTone] || emoji.skins.default;
|
|
710
|
-
if (this.
|
|
711
|
-
text = emoji.skins[this.
|
|
491
|
+
if (this._emojiPickerPlugin) {
|
|
492
|
+
text = emoji.skins[this._emojiPickerPlugin.skinTone] || emoji.skins.default;
|
|
712
493
|
}
|
|
713
494
|
return {
|
|
714
495
|
id: `:${emoji.annotation}:`,
|
|
715
496
|
text
|
|
716
497
|
};
|
|
717
498
|
});
|
|
718
|
-
if (!this.
|
|
499
|
+
if (!this._emojiPickerPlugin) {
|
|
719
500
|
return emojis.slice(0, this._emojiDropdownLimit);
|
|
720
501
|
}
|
|
721
502
|
const actionItem = {
|
|
@@ -1407,7 +1188,6 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
|
|
|
1407
1188
|
// Emit an update event to react to balloon dimensions changes.
|
|
1408
1189
|
this.searchView.on('search', ()=>{
|
|
1409
1190
|
this.fire('update');
|
|
1410
|
-
this.gridView.element.scrollTo(0, 0);
|
|
1411
1191
|
});
|
|
1412
1192
|
// Update the grid of emojis when the selected category is changed.
|
|
1413
1193
|
this.categoriesView.on('change:categoryName', (ev, args, categoryName)=>{
|
|
@@ -1453,10 +1233,10 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1453
1233
|
* @inheritDoc
|
|
1454
1234
|
*/ async init() {
|
|
1455
1235
|
const editor = this.editor;
|
|
1456
|
-
this.
|
|
1457
|
-
this.
|
|
1236
|
+
this._balloonPlugin = editor.plugins.get('ContextualBalloon');
|
|
1237
|
+
this._emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
|
|
1458
1238
|
// Skip registering a button in the toolbar and list item in the menu bar if the emoji repository is not ready.
|
|
1459
|
-
if (!await this.
|
|
1239
|
+
if (!await this._emojiRepositoryPlugin.isReady()) {
|
|
1460
1240
|
return;
|
|
1461
1241
|
}
|
|
1462
1242
|
const command = new EmojiCommand(editor);
|
|
@@ -1507,8 +1287,8 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1507
1287
|
this.emojiPickerView.searchView.setInputValue(searchValue);
|
|
1508
1288
|
}
|
|
1509
1289
|
this.emojiPickerView.searchView.search(searchValue);
|
|
1510
|
-
if (!this.
|
|
1511
|
-
this.
|
|
1290
|
+
if (!this._balloonPlugin.hasView(this.emojiPickerView)) {
|
|
1291
|
+
this._balloonPlugin.add({
|
|
1512
1292
|
view: this.emojiPickerView,
|
|
1513
1293
|
position: this._getBalloonPositionData()
|
|
1514
1294
|
});
|
|
@@ -1535,11 +1315,11 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1535
1315
|
* Creates an instance of the `EmojiPickerView` class that represents an emoji balloon.
|
|
1536
1316
|
*/ _createEmojiPickerView() {
|
|
1537
1317
|
const emojiPickerView = new EmojiPickerView(this.editor.locale, {
|
|
1538
|
-
emojiCategories: this.
|
|
1318
|
+
emojiCategories: this._emojiRepositoryPlugin.getEmojiCategories(),
|
|
1539
1319
|
skinTone: this.editor.config.get('emoji.skinTone'),
|
|
1540
|
-
skinTones: this.
|
|
1320
|
+
skinTones: this._emojiRepositoryPlugin.getSkinTones(),
|
|
1541
1321
|
getEmojiByQuery: (query)=>{
|
|
1542
|
-
return this.
|
|
1322
|
+
return this._emojiRepositoryPlugin.getEmojiByQuery(query);
|
|
1543
1323
|
}
|
|
1544
1324
|
});
|
|
1545
1325
|
// Insert an emoji on a tile click.
|
|
@@ -1553,8 +1333,8 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1553
1333
|
});
|
|
1554
1334
|
// Update the balloon position when layout is changed.
|
|
1555
1335
|
this.listenTo(emojiPickerView, 'update', ()=>{
|
|
1556
|
-
if (this.
|
|
1557
|
-
this.
|
|
1336
|
+
if (this._balloonPlugin.visibleView === emojiPickerView) {
|
|
1337
|
+
this._balloonPlugin.updatePosition();
|
|
1558
1338
|
}
|
|
1559
1339
|
});
|
|
1560
1340
|
// Close the panel on `Esc` key press when the **actions have focus**.
|
|
@@ -1566,17 +1346,17 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1566
1346
|
clickOutsideHandler({
|
|
1567
1347
|
emitter: emojiPickerView,
|
|
1568
1348
|
contextElements: [
|
|
1569
|
-
this.
|
|
1349
|
+
this._balloonPlugin.view.element
|
|
1570
1350
|
],
|
|
1571
1351
|
callback: ()=>this._hideUI(),
|
|
1572
|
-
activator: ()=>this.
|
|
1352
|
+
activator: ()=>this._balloonPlugin.visibleView === emojiPickerView
|
|
1573
1353
|
});
|
|
1574
1354
|
return emojiPickerView;
|
|
1575
1355
|
}
|
|
1576
1356
|
/**
|
|
1577
1357
|
* Hides the balloon with the emoji picker.
|
|
1578
1358
|
*/ _hideUI() {
|
|
1579
|
-
this.
|
|
1359
|
+
this._balloonPlugin.remove(this.emojiPickerView);
|
|
1580
1360
|
this.emojiPickerView.searchView.setInputValue('');
|
|
1581
1361
|
this.editor.editing.view.focus();
|
|
1582
1362
|
this._hideFakeVisualSelection();
|
|
@@ -1611,7 +1391,7 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1611
1391
|
});
|
|
1612
1392
|
}
|
|
1613
1393
|
/**
|
|
1614
|
-
* Returns positioning options for the {@link #
|
|
1394
|
+
* Returns positioning options for the {@link #_balloonPlugin}. They control the way the balloon is attached
|
|
1615
1395
|
* to the target element or selection.
|
|
1616
1396
|
*/ _getBalloonPositionData() {
|
|
1617
1397
|
const view = this.editor.editing.view;
|
|
@@ -1694,5 +1474,5 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
|
|
|
1694
1474
|
}
|
|
1695
1475
|
}
|
|
1696
1476
|
|
|
1697
|
-
export { Emoji, EmojiCommand, EmojiMention, EmojiPicker, EmojiRepository
|
|
1477
|
+
export { Emoji, EmojiCommand, EmojiMention, EmojiPicker, EmojiRepository };
|
|
1698
1478
|
//# sourceMappingURL=index.js.map
|