1010import Fuse from 'fuse.js' ;
1111import { groupBy } from 'lodash-es' ;
1212
13- import { Plugin , type Editor } from 'ckeditor5/src/core.js' ;
13+ import { type Editor , Plugin } from 'ckeditor5/src/core.js' ;
1414import { logWarning } from 'ckeditor5/src/utils.js' ;
15+ import EmojiUtils from './emojiutils.js' ;
1516import type { SkinToneId } from './emojiconfig.js' ;
1617
1718// An endpoint from which the emoji database will be downloaded during plugin initialization.
1819// The `{version}` placeholder is replaced with the value from editor config.
1920const EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{version}/en.json' ;
2021
21- const SKIN_TONE_MAP : Record < number , SkinToneId > = {
22- 0 : 'default' ,
23- 1 : 'light' ,
24- 2 : 'medium-light' ,
25- 3 : 'medium' ,
26- 4 : 'medium-dark' ,
27- 5 : 'dark'
28- } ;
29-
30- const BASELINE_EMOJI_WIDTH = 24 ;
31-
3222/**
3323 * The emoji repository plugin.
3424 *
3525 * Loads the emoji database from URL during plugin initialization and provides utility methods to search it.
3626 */
3727export default class EmojiRepository extends Plugin {
28+ /**
29+ * A callback to resolve the {@link #_databasePromise} to control the return value of this promise.
30+ */
31+ declare private _databasePromiseResolveCallback : ( value : boolean ) => void ;
32+
33+ /**
34+ * An instance of the [Fuse.js](https://www.fusejs.io/) library.
35+ */
36+ private _fuseSearch : Fuse < EmojiEntry > | null ;
37+
3838 /**
3939 * Emoji database.
4040 */
@@ -44,17 +44,14 @@ export default class EmojiRepository extends Plugin {
4444 * A promise resolved after downloading the emoji database.
4545 * The promise resolves with `true` when the database is successfully downloaded or `false` otherwise.
4646 */
47- private _databasePromise : Promise < boolean > ;
48-
49- /**
50- * A callback to resolve the {@link #_databasePromise} to control the return value of this promise.
51- */
52- declare private _databasePromiseResolveCallback : ( value : boolean ) => void ;
47+ private readonly _databasePromise : Promise < boolean > ;
5348
5449 /**
55- * An instance of the [Fuse.js](https://www.fusejs.io/) library.
50+ * @inheritDoc
5651 */
57- private _fuseSearch : Fuse < EmojiEntry > | null ;
52+ public static get requires ( ) {
53+ return [ EmojiUtils ] as const ;
54+ }
5855
5956 /**
6057 * @inheritDoc
@@ -93,23 +90,27 @@ export default class EmojiRepository extends Plugin {
9390 * @inheritDoc
9491 */
9592 public async init ( ) : Promise < void > {
93+ const emojiUtils = this . editor . plugins . get ( 'EmojiUtils' ) ;
9694 const emojiVersion = this . editor . config . get ( 'emoji.version' ) ! ;
95+
9796 const emojiDatabaseUrl = EMOJI_DATABASE_URL . replace ( '{version}' , `${ emojiVersion } ` ) ;
9897 const emojiDatabase = await loadEmojiDatabase ( emojiDatabaseUrl ) ;
98+ const emojiSupportedVersionByOs = emojiUtils . getEmojiSupportedVersionByOs ( ) ;
9999
100100 // Skip the initialization if the emoji database download has failed.
101101 // An empty database prevents the initialization of other dependent plugins, such as `EmojiMention` and `EmojiPicker`.
102102 if ( ! emojiDatabase . length ) {
103103 return this . _databasePromiseResolveCallback ( false ) ;
104104 }
105105
106- const container = createEmojiWidthTestingContainer ( ) ;
106+ const container = emojiUtils . createEmojiWidthTestingContainer ( ) ;
107+ document . body . appendChild ( container ) ;
107108
108109 // Store the emoji database after normalizing the raw data.
109110 this . _database = emojiDatabase
110- . filter ( item => isEmojiCategoryAllowed ( item ) )
111- . filter ( item => EmojiRepository . _isEmojiSupported ( item , container ) )
112- . map ( item => normalizeEmojiSkinTone ( item ) ) ;
111+ . filter ( item => emojiUtils . isEmojiCategoryAllowed ( item ) )
112+ . filter ( item => emojiUtils . isEmojiSupported ( item , emojiSupportedVersionByOs , container ) )
113+ . map ( item => emojiUtils . normalizeEmojiSkinTone ( item ) ) ;
113114
114115 container . remove ( ) ;
115116
@@ -224,13 +225,6 @@ export default class EmojiRepository extends Plugin {
224225 public isReady ( ) : Promise < boolean > {
225226 return this . _databasePromise ;
226227 }
227-
228- /**
229- * A function used to check if the given emoji is supported in the operating system.
230- *
231- * Referenced for unit testing purposes.
232- */
233- private static _isEmojiSupported = isEmojiSupported ;
234228}
235229
236230/**
@@ -265,78 +259,6 @@ async function loadEmojiDatabase( emojiDatabaseUrl: string ): Promise<Array<Emoj
265259 return result ;
266260}
267261
268- /**
269- * Creates a div for emoji width testing purposes.
270- */
271- function createEmojiWidthTestingContainer ( ) : HTMLDivElement {
272- const container = document . createElement ( 'div' ) ;
273-
274- container . setAttribute ( 'aria-hidden' , 'true' ) ;
275- container . style . position = 'absolute' ;
276- container . style . left = '-9999px' ;
277- container . style . whiteSpace = 'nowrap' ;
278- container . style . fontSize = BASELINE_EMOJI_WIDTH + 'px' ;
279- document . body . appendChild ( container ) ;
280-
281- return container ;
282- }
283-
284- /**
285- * Returns the width of the provided node.
286- */
287- function getNodeWidth ( container : HTMLDivElement , node : string ) : number {
288- const span = document . createElement ( 'span' ) ;
289- span . textContent = node ;
290- container . appendChild ( span ) ;
291- const nodeWidth = span . offsetWidth ;
292- container . removeChild ( span ) ;
293-
294- return nodeWidth ;
295- }
296-
297- /**
298- * Checks whether the emoji is supported in the operating system.
299- */
300- function isEmojiSupported ( item : EmojiCdnResource , container : HTMLDivElement ) : boolean {
301- const emojiWidth = getNodeWidth ( container , item . emoji ) ;
302-
303- // On Windows, some supported emoji are ~50% bigger than the baseline emoji, but what we really want to guard
304- // against are the ones that are 2x the size, because those are truly broken (person with red hair = person with
305- // floating red wig, black cat = cat with black square, polar bear = bear with snowflake, etc.)
306- // So here we set the threshold at 1.8 times the size of the baseline emoji.
307- return ( emojiWidth / 1.8 < BASELINE_EMOJI_WIDTH ) && ( emojiWidth >= BASELINE_EMOJI_WIDTH ) ;
308- }
309-
310- /**
311- * Adds default skin tone property to each emoji. If emoji defines other skin tones, they are added as well.
312- */
313- function normalizeEmojiSkinTone ( item : EmojiCdnResource ) : EmojiEntry {
314- const entry : EmojiEntry = {
315- ...item ,
316- skins : {
317- default : item . emoji
318- }
319- } ;
320-
321- if ( item . skins ) {
322- item . skins . forEach ( skin => {
323- const skinTone = SKIN_TONE_MAP [ skin . tone ] ;
324-
325- entry . skins [ skinTone ] = skin . emoji ;
326- } ) ;
327- }
328-
329- return entry ;
330- }
331-
332- /**
333- * Checks whether the emoji belongs to a group that is allowed.
334- */
335- function isEmojiCategoryAllowed ( item : EmojiCdnResource ) : boolean {
336- // Category group=2 contains skin tones only, which we do not want to render.
337- return item . group !== 2 ;
338- }
339-
340262/**
341263 * Represents a single group of the emoji category, e.g., "Smileys & Expressions".
342264 */
0 commit comments