@@ -11,40 +11,40 @@ import Fuse from 'fuse.js';
1111import { groupBy } from 'lodash-es' ;
1212
1313import { type Editor , Plugin } from 'ckeditor5/src/core.js' ;
14- import { logWarning } from 'ckeditor5/src/utils.js' ;
14+ import { logWarning , version } from 'ckeditor5/src/utils.js' ;
1515import EmojiUtils from './emojiutils.js' ;
16- import type { SkinToneId } from './emojiconfig.js' ;
16+ import type { EmojiVersion , SkinToneId } from './emojiconfig.js' ;
1717
18- // An endpoint from which the emoji database will be downloaded during plugin initialization.
18+ // An endpoint from which the emoji data will be downloaded during plugin initialization.
1919// The `{version}` placeholder is replaced with the value from editor config.
2020const EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{version}/en.json' ;
2121
2222/**
2323 * The emoji repository plugin.
2424 *
25- * Loads the emoji database from URL during plugin initialization and provides utility methods to search it.
25+ * Loads the emoji repository from URL during plugin initialization and provides utility methods to search it.
2626 */
2727export default class EmojiRepository extends Plugin {
2828 /**
29- * A callback to resolve the {@link #_databasePromise } to control the return value of this promise.
29+ * A callback to resolve the {@link #_repositoryPromise } to control the return value of this promise.
3030 */
31- declare private _databasePromiseResolveCallback : ( value : boolean ) => void ;
31+ declare private _repositoryPromiseResolveCallback : ( value : boolean ) => void ;
3232
3333 /**
3434 * An instance of the [Fuse.js](https://www.fusejs.io/) library.
3535 */
3636 private _fuseSearch : Fuse < EmojiEntry > | null ;
3737
3838 /**
39- * Emoji database .
39+ * The emoji version that is used to prepare the emoji repository .
4040 */
41- private _database : Array < EmojiEntry > ;
41+ private readonly _version : EmojiVersion ;
4242
4343 /**
44- * A promise resolved after downloading the emoji database .
45- * The promise resolves with `true` when the database is successfully downloaded or `false` otherwise.
44+ * A promise resolved after downloading the emoji collection .
45+ * The promise resolves with `true` when the repository is successfully downloaded or `false` otherwise.
4646 */
47- private readonly _databasePromise : Promise < boolean > ;
47+ private readonly _repositoryPromise : Promise < boolean > ;
4848
4949 /**
5050 * @inheritDoc
@@ -73,14 +73,15 @@ export default class EmojiRepository extends Plugin {
7373 constructor ( editor : Editor ) {
7474 super ( editor ) ;
7575
76- this . editor . config . define ( 'emoji' , {
76+ editor . config . define ( 'emoji' , {
7777 version : 16 ,
7878 skinTone : 'default'
7979 } ) ;
8080
81- this . _database = [ ] ;
82- this . _databasePromise = new Promise < boolean > ( resolve => {
83- this . _databasePromiseResolveCallback = resolve ;
81+ this . _version = editor . config . get ( 'emoji.version' ) ! ;
82+
83+ this . _repositoryPromise = new Promise < boolean > ( resolve => {
84+ this . _repositoryPromiseResolveCallback = resolve ;
8485 } ) ;
8586
8687 this . _fuseSearch = null ;
@@ -90,32 +91,22 @@ export default class EmojiRepository extends Plugin {
9091 * @inheritDoc
9192 */
9293 public async init ( ) : Promise < void > {
93- const emojiUtils = this . editor . plugins . get ( 'EmojiUtils' ) ;
94- const emojiVersion = this . editor . config . get ( 'emoji.version' ) ! ;
95-
96- const emojiDatabaseUrl = EMOJI_DATABASE_URL . replace ( '{version}' , `${ emojiVersion } ` ) ;
97- const emojiDatabase = await loadEmojiDatabase ( emojiDatabaseUrl ) ;
98- const emojiSupportedVersionByOs = emojiUtils . getEmojiSupportedVersionByOs ( ) ;
94+ if ( ! ( this . _version in EmojiRepository . _results ) ) {
95+ const cdnResult = await this . _loadItemsFromCdn ( ) ;
9996
100- // Skip the initialization if the emoji database download has failed.
101- // An empty database prevents the initialization of other dependent plugins, such as `EmojiMention` and `EmojiPicker`.
102- if ( ! emojiDatabase . length ) {
103- return this . _databasePromiseResolveCallback ( false ) ;
97+ EmojiRepository . _results [ this . _version ] = this . _normalizeEmoji ( cdnResult ) ;
10498 }
10599
106- const container = emojiUtils . createEmojiWidthTestingContainer ( ) ;
107- document . body . appendChild ( container ) ;
108-
109- // Store the emoji database after normalizing the raw data.
110- this . _database = emojiDatabase
111- . filter ( item => emojiUtils . isEmojiCategoryAllowed ( item ) )
112- . filter ( item => emojiUtils . isEmojiSupported ( item , emojiSupportedVersionByOs , container ) )
113- . map ( item => emojiUtils . normalizeEmojiSkinTone ( item ) ) ;
100+ const items = this . _getItems ( ) ;
114101
115- container . remove ( ) ;
102+ // Skip plugin initialization if the emoji repository is not available.
103+ // The initialization of other dependent plugins, such as `EmojiMention` and `EmojiPicker`, is prevented as well.
104+ if ( ! items ) {
105+ return this . _repositoryPromiseResolveCallback ( false ) ;
106+ }
116107
117108 // Create instance of the Fuse.js library with configured weighted search keys and disabled fuzzy search.
118- this . _fuseSearch = new Fuse ( this . _database , {
109+ this . _fuseSearch = new Fuse ( items , {
119110 keys : [
120111 { name : 'emoticon' , weight : 5 } ,
121112 { name : 'annotation' , weight : 3 } ,
@@ -126,12 +117,12 @@ export default class EmojiRepository extends Plugin {
126117 ignoreLocation : true
127118 } ) ;
128119
129- return this . _databasePromiseResolveCallback ( true ) ;
120+ return this . _repositoryPromiseResolveCallback ( true ) ;
130121 }
131122
132123 /**
133124 * Returns an array of emoji entries that match the search query.
134- * If the emoji database is not loaded, the [Fuse.js](https://www.fusejs.io/) instance is not created,
125+ * If the emoji repository is not loaded, the [Fuse.js](https://www.fusejs.io/) instance is not created,
135126 * hence this method returns an empty array.
136127 *
137128 * @param searchQuery A search query to match emoji.
@@ -170,12 +161,14 @@ export default class EmojiRepository extends Plugin {
170161
171162 /**
172163 * Groups all emojis by categories.
173- * If the emoji database is not loaded, it returns an empty array.
164+ * If the emoji repository is not loaded, it returns an empty array.
174165 *
175166 * @returns An array of emoji entries grouped by categories.
176167 */
177168 public getEmojiCategories ( ) : Array < EmojiCategory > {
178- if ( ! this . _database . length ) {
169+ const repository = this . _getItems ( ) ;
170+
171+ if ( ! repository ) {
179172 return [ ] ;
180173 }
181174
@@ -193,7 +186,7 @@ export default class EmojiRepository extends Plugin {
193186 { title : t ( 'Flags' ) , icon : '🏁' , groupId : 9 }
194187 ] ;
195188
196- const groups = groupBy ( this . _database , 'group' ) ;
189+ const groups = groupBy ( repository , 'group' ) ;
197190
198191 return categories . map ( category => {
199192 return {
@@ -220,43 +213,86 @@ export default class EmojiRepository extends Plugin {
220213 }
221214
222215 /**
223- * Indicates whether the emoji database has been successfully downloaded and the plugin is operational.
216+ * Indicates whether the emoji repository has been successfully downloaded and the plugin is operational.
224217 */
225218 public isReady ( ) : Promise < boolean > {
226- return this . _databasePromise ;
219+ return this . _repositoryPromise ;
227220 }
228- }
229221
230- /**
231- * Makes the HTTP request to download the emoji database.
232- */
233- async function loadEmojiDatabase ( emojiDatabaseUrl : string ) : Promise < Array < EmojiCdnResource > > {
234- const result = await fetch ( emojiDatabaseUrl )
235- . then ( response => {
236- if ( ! response . ok ) {
222+ /**
223+ * Returns the emoji repository in a configured version if it is a non-empty array. Returns `null` otherwise.
224+ */
225+ private _getItems ( ) : Array < EmojiEntry > | null {
226+ const repository = EmojiRepository . _results [ this . _version ] ;
227+
228+ return repository && repository . length ? repository : null ;
229+ }
230+
231+ /**
232+ * Makes the HTTP request to download the emoji repository in a configured version.
233+ */
234+ private async _loadItemsFromCdn ( ) : Promise < Array < EmojiCdnResource > > {
235+ const repositoryUrl = new URL ( EMOJI_DATABASE_URL . replace ( '{version}' , `${ this . _version } ` ) ) ;
236+
237+ repositoryUrl . searchParams . set ( 'editorVersion' , version ) ;
238+
239+ const result : Array < EmojiCdnResource > = await fetch ( repositoryUrl )
240+ . then ( response => {
241+ if ( ! response . ok ) {
242+ return [ ] ;
243+ }
244+
245+ return response . json ( ) ;
246+ } )
247+ . catch ( ( ) => {
237248 return [ ] ;
238- }
249+ } ) ;
250+
251+ if ( ! result . length ) {
252+ /**
253+ * Unable to load the emoji repository from CDN.
254+ *
255+ * If the CDN works properly and there is no disruption of communication, please check your
256+ * {@glink getting-started/setup/csp Content Security Policy (CSP)} setting and make sure
257+ * the CDN connection is allowed by the editor.
258+ *
259+ * @error emoji-repository-load-failed
260+ */
261+ logWarning ( 'emoji-repository-load-failed' ) ;
262+ }
239263
240- return response . json ( ) ;
241- } )
242- . catch ( ( ) => {
243- return [ ] ;
244- } ) ;
264+ return result ;
265+ }
266+
267+ /**
268+ * Normalizes the raw data fetched from CDN. By normalization, we meant:
269+ *
270+ * * Filter out unsupported emoji (these that will not render correctly),
271+ * * Prepare skin tone variants if an emoji defines them.
272+ */
273+ private _normalizeEmoji ( data : Array < EmojiCdnResource > ) : Array < EmojiEntry > {
274+ const emojiUtils = this . editor . plugins . get ( 'EmojiUtils' ) ;
275+ const emojiSupportedVersionByOs = emojiUtils . getEmojiSupportedVersionByOs ( ) ;
276+
277+ const container = emojiUtils . createEmojiWidthTestingContainer ( ) ;
278+ document . body . appendChild ( container ) ;
279+
280+ const results = data
281+ . filter ( item => emojiUtils . isEmojiCategoryAllowed ( item ) )
282+ . filter ( item => emojiUtils . isEmojiSupported ( item , emojiSupportedVersionByOs , container ) )
283+ . map ( item => emojiUtils . normalizeEmojiSkinTone ( item ) ) ;
284+
285+ container . remove ( ) ;
245286
246- if ( ! result . length ) {
247- /**
248- * Unable to load the emoji database from CDN.
249- *
250- * If the CDN works properly and there is no disruption of communication, please check your
251- * {@glink getting-started/setup/csp Content Security Policy (CSP)} setting and make sure
252- * the CDN connection is allowed by the editor.
253- *
254- * @error emoji-database-load-failed
255- */
256- logWarning ( 'emoji-database-load-failed' ) ;
287+ return results ;
257288 }
258289
259- return result ;
290+ /**
291+ * Versioned emoji repository.
292+ */
293+ private static _results : {
294+ [ key in EmojiVersion ] ?: Array < EmojiEntry >
295+ } = { } ;
260296}
261297
262298/**
0 commit comments