@ckeditor/ckeditor5-emoji 44.2.0-alpha.15 → 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/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 { version, logWarning, FocusTracker, KeystrokeHandler, global, Collection } from '@ckeditor/ckeditor5-utils/dist/index.js';
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
- * @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 = {
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,141 +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 EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{version}/en.json';
196
24
  /**
197
25
  * The emoji repository plugin.
198
26
  *
199
- * Loads the emoji repository from URL during plugin initialization and provides utility methods to search it.
27
+ * Loads the emoji database from URL during plugin initialization and provides utility methods to search it.
200
28
  */ class EmojiRepository extends Plugin {
201
29
  /**
202
- * An instance of the [Fuse.js](https://www.fusejs.io/) library.
203
- */ _fuseSearch;
204
- /**
205
- * The emoji version that is used to prepare the emoji repository.
206
- */ _version;
30
+ * Emoji database.
31
+ */ _database;
207
32
  /**
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;
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;
211
36
  /**
212
- * @inheritDoc
213
- */ static get requires() {
214
- return [
215
- EmojiUtils
216
- ];
217
- }
37
+ * An instance of the [Fuse.js](https://www.fusejs.io/) library.
38
+ */ _fuseSearch;
218
39
  /**
219
40
  * @inheritDoc
220
41
  */ static get pluginName() {
@@ -229,31 +50,33 @@ const EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{versi
229
50
  * @inheritDoc
230
51
  */ constructor(editor){
231
52
  super(editor);
232
- editor.config.define('emoji', {
53
+ this.editor.config.define('emoji', {
233
54
  version: 16,
234
55
  skinTone: 'default'
235
56
  });
236
- this._version = editor.config.get('emoji.version');
237
- this._repositoryPromise = new Promise((resolve)=>{
238
- this._repositoryPromiseResolveCallback = resolve;
57
+ this._database = [];
58
+ this._databasePromise = new Promise((resolve)=>{
59
+ this._databasePromiseResolveCallback = resolve;
239
60
  });
240
61
  this._fuseSearch = null;
241
62
  }
242
63
  /**
243
64
  * @inheritDoc
244
65
  */ async init() {
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);
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(items, {
79
+ this._fuseSearch = new Fuse(this._database, {
257
80
  keys: [
258
81
  {
259
82
  name: 'emoticon',
@@ -272,11 +95,11 @@ const EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{versi
272
95
  threshold: 0,
273
96
  ignoreLocation: true
274
97
  });
275
- return this._repositoryPromiseResolveCallback(true);
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 repository is not loaded, the [Fuse.js](https://www.fusejs.io/) instance is not created,
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 EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{versi
311
134
  }
312
135
  /**
313
136
  * Groups all emojis by categories.
314
- * If the emoji repository is not loaded, it returns an empty array.
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
- const repository = this._getItems();
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 EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{versi
367
189
  groupId: 9
368
190
  }
369
191
  ];
370
- const groups = groupBy(repository, 'group');
192
+ const groups = groupBy(this._database, 'group');
371
193
  return categories.map((category)=>{
372
194
  return {
373
195
  ...category,
@@ -413,61 +235,94 @@ const EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{versi
413
235
  ];
414
236
  }
415
237
  /**
416
- * Indicates whether the emoji repository has been successfully downloaded and the plugin is operational.
238
+ * Indicates whether the emoji database has been successfully downloaded and the plugin is operational.
417
239
  */ isReady() {
418
- return this._repositoryPromise;
419
- }
420
- /**
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;
240
+ return this._databasePromise;
425
241
  }
426
242
  /**
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(()=>{
243
+ * A function used to check if the given emoji is supported in the operating system.
244
+ *
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) {
439
253
  return [];
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
254
  }
452
- return result;
453
- }
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;
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
+ });
467
318
  }
468
- /**
469
- * Versioned emoji repository.
470
- */ static _results = {};
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;
471
326
  }
472
327
 
473
328
  const EMOJI_MENTION_MARKER = ':';
@@ -514,18 +369,11 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
514
369
  });
515
370
  this._emojiDropdownLimit = editor.config.get('emoji.dropdownLimit');
516
371
  this._skinTone = editor.config.get('emoji.skinTone');
517
- this._setupMentionConfiguration(editor);
518
- }
519
- /**
520
- * Initializes the configuration for emojis in the mention feature.
521
- * If the marker used by emoji mention is already registered, it displays a warning.
522
- * If emoji mention configuration is detected, it does not register it for a second time.
523
- */ _setupMentionConfiguration(editor) {
524
- const mergeFieldsPrefix = editor.config.get('mergeFields.prefix');
525
372
  const mentionFeedsConfigs = editor.config.get('mention.feeds');
526
- const isEmojiMarkerUsedByMergeFields = mergeFieldsPrefix ? mergeFieldsPrefix[0] === EMOJI_MENTION_MARKER : false;
527
- const isEmojiMarkerUsedByMention = mentionFeedsConfigs.filter((config)=>!config._isEmojiMarker).some((config)=>config.marker === EMOJI_MENTION_MARKER);
528
- if (isEmojiMarkerUsedByMention || isEmojiMarkerUsedByMergeFields) {
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) {
529
377
  /**
530
378
  * The `marker` in the `emoji` config is already used by other plugin configuration.
531
379
  *
@@ -536,12 +384,24 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
536
384
  });
537
385
  return;
538
386
  }
539
- const isEmojiConfigDefined = mentionFeedsConfigs.some((config)=>config._isEmojiMarker);
540
- if (isEmojiConfigDefined) {
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()) {
541
397
  return;
542
398
  }
399
+ editor.once('ready', this._overrideMentionExecuteListener.bind(this));
400
+ }
401
+ /**
402
+ * Initializes the configuration for emojis in the mention feature.
403
+ */ _setupMentionConfiguration(mentionFeedsConfigs) {
543
404
  const emojiMentionFeedConfig = {
544
- _isEmojiMarker: true,
545
405
  marker: EMOJI_MENTION_MARKER,
546
406
  dropdownLimit: this._emojiDropdownLimit,
547
407
  itemRenderer: this._customItemRendererFactory(this.editor.t),
@@ -552,18 +412,6 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
552
412
  emojiMentionFeedConfig
553
413
  ]);
554
414
  }
555
- /**
556
- * @inheritDoc
557
- */ async init() {
558
- const editor = this.editor;
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();
562
- // Override the `mention` command listener if the emoji repository is ready.
563
- if (this._isEmojiRepositoryAvailable) {
564
- editor.once('ready', this._overrideMentionExecuteListener.bind(this));
565
- }
566
- }
567
415
  /**
568
416
  * Returns the `itemRenderer()` callback for mention config.
569
417
  */ _customItemRendererFactory(t) {
@@ -615,7 +463,7 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
615
463
  editor.model.change((writer)=>{
616
464
  editor.model.deleteContent(writer.createSelection(eventData.range));
617
465
  });
618
- const emojiPickerPlugin = this.emojiPickerPlugin;
466
+ const emojiPickerPlugin = this._emojiPickerPlugin;
619
467
  emojiPickerPlugin.showUI(text.slice(1));
620
468
  setTimeout(()=>{
621
469
  emojiPickerPlugin.emojiPickerView.focus();
@@ -638,25 +486,17 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
638
486
  if (searchQuery.startsWith(' ')) {
639
487
  return [];
640
488
  }
641
- // Do not show anything when a query starts with a marker character.
642
- if (searchQuery.startsWith(EMOJI_MENTION_MARKER)) {
643
- return [];
644
- }
645
- // If the repository plugin is not available, return an empty feed to avoid confusion. See: #17842.
646
- if (!this._isEmojiRepositoryAvailable) {
647
- return [];
648
- }
649
- const emojis = this.emojiRepositoryPlugin.getEmojiByQuery(searchQuery).map((emoji)=>{
489
+ const emojis = this._emojiRepositoryPlugin.getEmojiByQuery(searchQuery).map((emoji)=>{
650
490
  let text = emoji.skins[this._skinTone] || emoji.skins.default;
651
- if (this.emojiPickerPlugin) {
652
- text = emoji.skins[this.emojiPickerPlugin.skinTone] || emoji.skins.default;
491
+ if (this._emojiPickerPlugin) {
492
+ text = emoji.skins[this._emojiPickerPlugin.skinTone] || emoji.skins.default;
653
493
  }
654
494
  return {
655
495
  id: `:${emoji.annotation}:`,
656
496
  text
657
497
  };
658
498
  });
659
- if (!this.emojiPickerPlugin) {
499
+ if (!this._emojiPickerPlugin) {
660
500
  return emojis.slice(0, this._emojiDropdownLimit);
661
501
  }
662
502
  const actionItem = {
@@ -1348,7 +1188,6 @@ const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
1348
1188
  // Emit an update event to react to balloon dimensions changes.
1349
1189
  this.searchView.on('search', ()=>{
1350
1190
  this.fire('update');
1351
- this.gridView.element.scrollTo(0, 0);
1352
1191
  });
1353
1192
  // Update the grid of emojis when the selected category is changed.
1354
1193
  this.categoriesView.on('change:categoryName', (ev, args, categoryName)=>{
@@ -1394,10 +1233,10 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
1394
1233
  * @inheritDoc
1395
1234
  */ async init() {
1396
1235
  const editor = this.editor;
1397
- this.balloonPlugin = editor.plugins.get('ContextualBalloon');
1398
- this.emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
1236
+ this._balloonPlugin = editor.plugins.get('ContextualBalloon');
1237
+ this._emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
1399
1238
  // Skip registering a button in the toolbar and list item in the menu bar if the emoji repository is not ready.
1400
- if (!await this.emojiRepositoryPlugin.isReady()) {
1239
+ if (!await this._emojiRepositoryPlugin.isReady()) {
1401
1240
  return;
1402
1241
  }
1403
1242
  const command = new EmojiCommand(editor);
@@ -1448,8 +1287,8 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
1448
1287
  this.emojiPickerView.searchView.setInputValue(searchValue);
1449
1288
  }
1450
1289
  this.emojiPickerView.searchView.search(searchValue);
1451
- if (!this.balloonPlugin.hasView(this.emojiPickerView)) {
1452
- this.balloonPlugin.add({
1290
+ if (!this._balloonPlugin.hasView(this.emojiPickerView)) {
1291
+ this._balloonPlugin.add({
1453
1292
  view: this.emojiPickerView,
1454
1293
  position: this._getBalloonPositionData()
1455
1294
  });
@@ -1476,11 +1315,11 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
1476
1315
  * Creates an instance of the `EmojiPickerView` class that represents an emoji balloon.
1477
1316
  */ _createEmojiPickerView() {
1478
1317
  const emojiPickerView = new EmojiPickerView(this.editor.locale, {
1479
- emojiCategories: this.emojiRepositoryPlugin.getEmojiCategories(),
1318
+ emojiCategories: this._emojiRepositoryPlugin.getEmojiCategories(),
1480
1319
  skinTone: this.editor.config.get('emoji.skinTone'),
1481
- skinTones: this.emojiRepositoryPlugin.getSkinTones(),
1320
+ skinTones: this._emojiRepositoryPlugin.getSkinTones(),
1482
1321
  getEmojiByQuery: (query)=>{
1483
- return this.emojiRepositoryPlugin.getEmojiByQuery(query);
1322
+ return this._emojiRepositoryPlugin.getEmojiByQuery(query);
1484
1323
  }
1485
1324
  });
1486
1325
  // Insert an emoji on a tile click.
@@ -1494,8 +1333,8 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
1494
1333
  });
1495
1334
  // Update the balloon position when layout is changed.
1496
1335
  this.listenTo(emojiPickerView, 'update', ()=>{
1497
- if (this.balloonPlugin.visibleView === emojiPickerView) {
1498
- this.balloonPlugin.updatePosition();
1336
+ if (this._balloonPlugin.visibleView === emojiPickerView) {
1337
+ this._balloonPlugin.updatePosition();
1499
1338
  }
1500
1339
  });
1501
1340
  // Close the panel on `Esc` key press when the **actions have focus**.
@@ -1507,17 +1346,17 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
1507
1346
  clickOutsideHandler({
1508
1347
  emitter: emojiPickerView,
1509
1348
  contextElements: [
1510
- this.balloonPlugin.view.element
1349
+ this._balloonPlugin.view.element
1511
1350
  ],
1512
1351
  callback: ()=>this._hideUI(),
1513
- activator: ()=>this.balloonPlugin.visibleView === emojiPickerView
1352
+ activator: ()=>this._balloonPlugin.visibleView === emojiPickerView
1514
1353
  });
1515
1354
  return emojiPickerView;
1516
1355
  }
1517
1356
  /**
1518
1357
  * Hides the balloon with the emoji picker.
1519
1358
  */ _hideUI() {
1520
- this.balloonPlugin.remove(this.emojiPickerView);
1359
+ this._balloonPlugin.remove(this.emojiPickerView);
1521
1360
  this.emojiPickerView.searchView.setInputValue('');
1522
1361
  this.editor.editing.view.focus();
1523
1362
  this._hideFakeVisualSelection();
@@ -1552,7 +1391,7 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
1552
1391
  });
1553
1392
  }
1554
1393
  /**
1555
- * Returns positioning options for the {@link #balloonPlugin}. They control the way the balloon is attached
1394
+ * Returns positioning options for the {@link #_balloonPlugin}. They control the way the balloon is attached
1556
1395
  * to the target element or selection.
1557
1396
  */ _getBalloonPositionData() {
1558
1397
  const view = this.editor.editing.view;
@@ -1635,5 +1474,5 @@ const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
1635
1474
  }
1636
1475
  }
1637
1476
 
1638
- export { Emoji, EmojiCommand, EmojiMention, EmojiPicker, EmojiRepository, EmojiUtils };
1477
+ export { Emoji, EmojiCommand, EmojiMention, EmojiPicker, EmojiRepository };
1639
1478
  //# sourceMappingURL=index.js.map