@ckeditor/ckeditor5-emoji 44.2.0-alpha.7 → 44.2.0-alpha.8
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 +8 -2
- package/dist/index.js +211 -91
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/augmentation.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 +13 -14
- package/src/emojirepository.js +15 -81
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-emoji",
|
|
3
|
-
"version": "44.2.0-alpha.
|
|
3
|
+
"version": "44.2.0-alpha.8",
|
|
4
4
|
"description": "Emoji feature for CKEditor 5.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ckeditor",
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"main": "src/index.js",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@ckeditor/ckeditor5-core": "44.2.0-alpha.
|
|
17
|
-
"@ckeditor/ckeditor5-mention": "44.2.0-alpha.
|
|
18
|
-
"@ckeditor/ckeditor5-typing": "44.2.0-alpha.
|
|
19
|
-
"@ckeditor/ckeditor5-ui": "44.2.0-alpha.
|
|
20
|
-
"@ckeditor/ckeditor5-utils": "44.2.0-alpha.
|
|
21
|
-
"ckeditor5": "44.2.0-alpha.
|
|
16
|
+
"@ckeditor/ckeditor5-core": "44.2.0-alpha.8",
|
|
17
|
+
"@ckeditor/ckeditor5-mention": "44.2.0-alpha.8",
|
|
18
|
+
"@ckeditor/ckeditor5-typing": "44.2.0-alpha.8",
|
|
19
|
+
"@ckeditor/ckeditor5-ui": "44.2.0-alpha.8",
|
|
20
|
+
"@ckeditor/ckeditor5-utils": "44.2.0-alpha.8",
|
|
21
|
+
"ckeditor5": "44.2.0-alpha.8",
|
|
22
22
|
"fuse.js": "7.0.0",
|
|
23
23
|
"lodash-es": "4.17.21"
|
|
24
24
|
},
|
package/src/augmentation.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
|
-
import type { Emoji, EmojiConfig, EmojiMention, EmojiPicker, EmojiRepository, EmojiCommand } from './index.js';
|
|
5
|
+
import type { Emoji, EmojiConfig, EmojiMention, EmojiPicker, EmojiRepository, EmojiUtils, EmojiCommand } from './index.js';
|
|
6
6
|
declare module '@ckeditor/ckeditor5-core' {
|
|
7
7
|
interface EditorConfig {
|
|
8
8
|
/**
|
|
@@ -17,6 +17,7 @@ declare module '@ckeditor/ckeditor5-core' {
|
|
|
17
17
|
[EmojiMention.pluginName]: EmojiMention;
|
|
18
18
|
[EmojiPicker.pluginName]: EmojiPicker;
|
|
19
19
|
[EmojiRepository.pluginName]: EmojiRepository;
|
|
20
|
+
[EmojiUtils.pluginName]: EmojiUtils;
|
|
20
21
|
}
|
|
21
22
|
interface CommandsMap {
|
|
22
23
|
emoji: EmojiCommand;
|
package/src/emojimention.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Plugin, type Editor } from 'ckeditor5/src/core.js';
|
|
6
6
|
import { Typing } from 'ckeditor5/src/typing.js';
|
|
7
7
|
import EmojiRepository from './emojirepository.js';
|
|
8
|
+
import type EmojiPicker from './emojipicker.js';
|
|
8
9
|
/**
|
|
9
10
|
* The emoji mention plugin.
|
|
10
11
|
*
|
|
@@ -14,11 +15,11 @@ export default class EmojiMention extends Plugin {
|
|
|
14
15
|
/**
|
|
15
16
|
* An instance of the {@link module:emoji/emojipicker~EmojiPicker} plugin if it is loaded in the editor.
|
|
16
17
|
*/
|
|
17
|
-
|
|
18
|
+
emojiPickerPlugin: EmojiPicker | null;
|
|
18
19
|
/**
|
|
19
20
|
* An instance of the {@link module:emoji/emojirepository~EmojiRepository} plugin.
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
+
emojiRepositoryPlugin: EmojiRepository;
|
|
22
23
|
/**
|
|
23
24
|
* A flag that informs if the {@link module:emoji/emojirepository~EmojiRepository} plugin is loaded correctly.
|
|
24
25
|
*/
|
package/src/emojimention.js
CHANGED
|
@@ -88,9 +88,9 @@ export default class EmojiMention extends Plugin {
|
|
|
88
88
|
*/
|
|
89
89
|
async init() {
|
|
90
90
|
const editor = this.editor;
|
|
91
|
-
this.
|
|
92
|
-
this.
|
|
93
|
-
this._isEmojiRepositoryAvailable = await this.
|
|
91
|
+
this.emojiPickerPlugin = editor.plugins.has('EmojiPicker') ? editor.plugins.get('EmojiPicker') : null;
|
|
92
|
+
this.emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
|
|
93
|
+
this._isEmojiRepositoryAvailable = await this.emojiRepositoryPlugin.isReady();
|
|
94
94
|
// Override the `mention` command listener if the emoji repository is ready.
|
|
95
95
|
if (this._isEmojiRepositoryAvailable) {
|
|
96
96
|
editor.once('ready', this._overrideMentionExecuteListener.bind(this));
|
|
@@ -152,7 +152,7 @@ export default class EmojiMention extends Plugin {
|
|
|
152
152
|
editor.model.change(writer => {
|
|
153
153
|
editor.model.deleteContent(writer.createSelection(eventData.range));
|
|
154
154
|
});
|
|
155
|
-
const emojiPickerPlugin = this.
|
|
155
|
+
const emojiPickerPlugin = this.emojiPickerPlugin;
|
|
156
156
|
emojiPickerPlugin.showUI(text.slice(1));
|
|
157
157
|
setTimeout(() => {
|
|
158
158
|
emojiPickerPlugin.emojiPickerView.focus();
|
|
@@ -180,18 +180,18 @@ export default class EmojiMention extends Plugin {
|
|
|
180
180
|
if (!this._isEmojiRepositoryAvailable) {
|
|
181
181
|
return [];
|
|
182
182
|
}
|
|
183
|
-
const emojis = this.
|
|
183
|
+
const emojis = this.emojiRepositoryPlugin.getEmojiByQuery(searchQuery)
|
|
184
184
|
.map(emoji => {
|
|
185
185
|
let text = emoji.skins[this._skinTone] || emoji.skins.default;
|
|
186
|
-
if (this.
|
|
187
|
-
text = emoji.skins[this.
|
|
186
|
+
if (this.emojiPickerPlugin) {
|
|
187
|
+
text = emoji.skins[this.emojiPickerPlugin.skinTone] || emoji.skins.default;
|
|
188
188
|
}
|
|
189
189
|
return {
|
|
190
190
|
id: `:${emoji.annotation}:`,
|
|
191
191
|
text
|
|
192
192
|
};
|
|
193
193
|
});
|
|
194
|
-
if (!this.
|
|
194
|
+
if (!this.emojiPickerPlugin) {
|
|
195
195
|
return emojis.slice(0, this._emojiDropdownLimit);
|
|
196
196
|
}
|
|
197
197
|
const actionItem = {
|
package/src/emojipicker.d.ts
CHANGED
|
@@ -25,11 +25,11 @@ export default class EmojiPicker extends Plugin {
|
|
|
25
25
|
/**
|
|
26
26
|
* The contextual balloon plugin instance.
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
balloonPlugin: ContextualBalloon;
|
|
29
29
|
/**
|
|
30
30
|
* An instance of the {@link module:emoji/emojirepository~EmojiRepository} plugin.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
emojiRepositoryPlugin: EmojiRepository;
|
|
33
33
|
/**
|
|
34
34
|
* @inheritDoc
|
|
35
35
|
*/
|
|
@@ -80,7 +80,7 @@ export default class EmojiPicker extends Plugin {
|
|
|
80
80
|
*/
|
|
81
81
|
private _setupConversion;
|
|
82
82
|
/**
|
|
83
|
-
* Returns positioning options for the {@link #
|
|
83
|
+
* Returns positioning options for the {@link #balloonPlugin}. They control the way the balloon is attached
|
|
84
84
|
* to the target element or selection.
|
|
85
85
|
*/
|
|
86
86
|
private _getBalloonPositionData;
|
package/src/emojipicker.js
CHANGED
|
@@ -42,10 +42,10 @@ export default class EmojiPicker extends Plugin {
|
|
|
42
42
|
*/
|
|
43
43
|
async init() {
|
|
44
44
|
const editor = this.editor;
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
45
|
+
this.balloonPlugin = editor.plugins.get('ContextualBalloon');
|
|
46
|
+
this.emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
|
|
47
47
|
// Skip registering a button in the toolbar and list item in the menu bar if the emoji repository is not ready.
|
|
48
|
-
if (!await this.
|
|
48
|
+
if (!await this.emojiRepositoryPlugin.isReady()) {
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
const command = new EmojiCommand(editor);
|
|
@@ -99,8 +99,8 @@ export default class EmojiPicker extends Plugin {
|
|
|
99
99
|
this.emojiPickerView.searchView.setInputValue(searchValue);
|
|
100
100
|
}
|
|
101
101
|
this.emojiPickerView.searchView.search(searchValue);
|
|
102
|
-
if (!this.
|
|
103
|
-
this.
|
|
102
|
+
if (!this.balloonPlugin.hasView(this.emojiPickerView)) {
|
|
103
|
+
this.balloonPlugin.add({
|
|
104
104
|
view: this.emojiPickerView,
|
|
105
105
|
position: this._getBalloonPositionData()
|
|
106
106
|
});
|
|
@@ -129,11 +129,11 @@ export default class EmojiPicker extends Plugin {
|
|
|
129
129
|
*/
|
|
130
130
|
_createEmojiPickerView() {
|
|
131
131
|
const emojiPickerView = new EmojiPickerView(this.editor.locale, {
|
|
132
|
-
emojiCategories: this.
|
|
132
|
+
emojiCategories: this.emojiRepositoryPlugin.getEmojiCategories(),
|
|
133
133
|
skinTone: this.editor.config.get('emoji.skinTone'),
|
|
134
|
-
skinTones: this.
|
|
134
|
+
skinTones: this.emojiRepositoryPlugin.getSkinTones(),
|
|
135
135
|
getEmojiByQuery: (query) => {
|
|
136
|
-
return this.
|
|
136
|
+
return this.emojiRepositoryPlugin.getEmojiByQuery(query);
|
|
137
137
|
}
|
|
138
138
|
});
|
|
139
139
|
// Insert an emoji on a tile click.
|
|
@@ -145,8 +145,8 @@ export default class EmojiPicker extends Plugin {
|
|
|
145
145
|
});
|
|
146
146
|
// Update the balloon position when layout is changed.
|
|
147
147
|
this.listenTo(emojiPickerView, 'update', () => {
|
|
148
|
-
if (this.
|
|
149
|
-
this.
|
|
148
|
+
if (this.balloonPlugin.visibleView === emojiPickerView) {
|
|
149
|
+
this.balloonPlugin.updatePosition();
|
|
150
150
|
}
|
|
151
151
|
});
|
|
152
152
|
// Close the panel on `Esc` key press when the **actions have focus**.
|
|
@@ -157,9 +157,9 @@ export default class EmojiPicker extends Plugin {
|
|
|
157
157
|
// Close the dialog when clicking outside of it.
|
|
158
158
|
clickOutsideHandler({
|
|
159
159
|
emitter: emojiPickerView,
|
|
160
|
-
contextElements: [this.
|
|
160
|
+
contextElements: [this.balloonPlugin.view.element],
|
|
161
161
|
callback: () => this._hideUI(),
|
|
162
|
-
activator: () => this.
|
|
162
|
+
activator: () => this.balloonPlugin.visibleView === emojiPickerView
|
|
163
163
|
});
|
|
164
164
|
return emojiPickerView;
|
|
165
165
|
}
|
|
@@ -167,7 +167,7 @@ export default class EmojiPicker extends Plugin {
|
|
|
167
167
|
* Hides the balloon with the emoji picker.
|
|
168
168
|
*/
|
|
169
169
|
_hideUI() {
|
|
170
|
-
this.
|
|
170
|
+
this.balloonPlugin.remove(this.emojiPickerView);
|
|
171
171
|
this.emojiPickerView.searchView.setInputValue('');
|
|
172
172
|
this.editor.editing.view.focus();
|
|
173
173
|
this._hideFakeVisualSelection();
|
|
@@ -198,7 +198,7 @@ export default class EmojiPicker extends Plugin {
|
|
|
198
198
|
});
|
|
199
199
|
}
|
|
200
200
|
/**
|
|
201
|
-
* Returns positioning options for the {@link #
|
|
201
|
+
* Returns positioning options for the {@link #balloonPlugin}. They control the way the balloon is attached
|
|
202
202
|
* to the target element or selection.
|
|
203
203
|
*/
|
|
204
204
|
_getBalloonPositionData() {
|
package/src/emojirepository.d.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { type Editor, Plugin } from 'ckeditor5/src/core.js';
|
|
6
|
+
import EmojiUtils from './emojiutils.js';
|
|
6
7
|
import type { SkinToneId } from './emojiconfig.js';
|
|
7
8
|
/**
|
|
8
9
|
* The emoji repository plugin.
|
|
@@ -10,6 +11,14 @@ import type { SkinToneId } from './emojiconfig.js';
|
|
|
10
11
|
* Loads the emoji database from URL during plugin initialization and provides utility methods to search it.
|
|
11
12
|
*/
|
|
12
13
|
export default class EmojiRepository extends Plugin {
|
|
14
|
+
/**
|
|
15
|
+
* A callback to resolve the {@link #_databasePromise} to control the return value of this promise.
|
|
16
|
+
*/
|
|
17
|
+
private _databasePromiseResolveCallback;
|
|
18
|
+
/**
|
|
19
|
+
* An instance of the [Fuse.js](https://www.fusejs.io/) library.
|
|
20
|
+
*/
|
|
21
|
+
private _fuseSearch;
|
|
13
22
|
/**
|
|
14
23
|
* Emoji database.
|
|
15
24
|
*/
|
|
@@ -18,15 +27,11 @@ export default class EmojiRepository extends Plugin {
|
|
|
18
27
|
* A promise resolved after downloading the emoji database.
|
|
19
28
|
* The promise resolves with `true` when the database is successfully downloaded or `false` otherwise.
|
|
20
29
|
*/
|
|
21
|
-
private _databasePromise;
|
|
22
|
-
/**
|
|
23
|
-
* A callback to resolve the {@link #_databasePromise} to control the return value of this promise.
|
|
24
|
-
*/
|
|
25
|
-
private _databasePromiseResolveCallback;
|
|
30
|
+
private readonly _databasePromise;
|
|
26
31
|
/**
|
|
27
|
-
*
|
|
32
|
+
* @inheritDoc
|
|
28
33
|
*/
|
|
29
|
-
|
|
34
|
+
static get requires(): readonly [typeof EmojiUtils];
|
|
30
35
|
/**
|
|
31
36
|
* @inheritDoc
|
|
32
37
|
*/
|
|
@@ -67,12 +72,6 @@ export default class EmojiRepository extends Plugin {
|
|
|
67
72
|
* Indicates whether the emoji database has been successfully downloaded and the plugin is operational.
|
|
68
73
|
*/
|
|
69
74
|
isReady(): Promise<boolean>;
|
|
70
|
-
/**
|
|
71
|
-
* A function used to check if the given emoji is supported in the operating system.
|
|
72
|
-
*
|
|
73
|
-
* Referenced for unit testing purposes.
|
|
74
|
-
*/
|
|
75
|
-
private static _isEmojiSupported;
|
|
76
75
|
}
|
|
77
76
|
/**
|
|
78
77
|
* Represents a single group of the emoji category, e.g., "Smileys & Expressions".
|
package/src/emojirepository.js
CHANGED
|
@@ -9,24 +9,22 @@ import Fuse from 'fuse.js';
|
|
|
9
9
|
import { groupBy } from 'lodash-es';
|
|
10
10
|
import { Plugin } from 'ckeditor5/src/core.js';
|
|
11
11
|
import { logWarning } from 'ckeditor5/src/utils.js';
|
|
12
|
+
import EmojiUtils from './emojiutils.js';
|
|
12
13
|
// An endpoint from which the emoji database will be downloaded during plugin initialization.
|
|
13
14
|
// The `{version}` placeholder is replaced with the value from editor config.
|
|
14
15
|
const EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{version}/en.json';
|
|
15
|
-
const SKIN_TONE_MAP = {
|
|
16
|
-
0: 'default',
|
|
17
|
-
1: 'light',
|
|
18
|
-
2: 'medium-light',
|
|
19
|
-
3: 'medium',
|
|
20
|
-
4: 'medium-dark',
|
|
21
|
-
5: 'dark'
|
|
22
|
-
};
|
|
23
|
-
const BASELINE_EMOJI_WIDTH = 24;
|
|
24
16
|
/**
|
|
25
17
|
* The emoji repository plugin.
|
|
26
18
|
*
|
|
27
19
|
* Loads the emoji database from URL during plugin initialization and provides utility methods to search it.
|
|
28
20
|
*/
|
|
29
|
-
class EmojiRepository extends Plugin {
|
|
21
|
+
export default class EmojiRepository extends Plugin {
|
|
22
|
+
/**
|
|
23
|
+
* @inheritDoc
|
|
24
|
+
*/
|
|
25
|
+
static get requires() {
|
|
26
|
+
return [EmojiUtils];
|
|
27
|
+
}
|
|
30
28
|
/**
|
|
31
29
|
* @inheritDoc
|
|
32
30
|
*/
|
|
@@ -58,20 +56,23 @@ class EmojiRepository extends Plugin {
|
|
|
58
56
|
* @inheritDoc
|
|
59
57
|
*/
|
|
60
58
|
async init() {
|
|
59
|
+
const emojiUtils = this.editor.plugins.get('EmojiUtils');
|
|
61
60
|
const emojiVersion = this.editor.config.get('emoji.version');
|
|
62
61
|
const emojiDatabaseUrl = EMOJI_DATABASE_URL.replace('{version}', `${emojiVersion}`);
|
|
63
62
|
const emojiDatabase = await loadEmojiDatabase(emojiDatabaseUrl);
|
|
63
|
+
const emojiSupportedVersionByOs = emojiUtils.getEmojiSupportedVersionByOs();
|
|
64
64
|
// Skip the initialization if the emoji database download has failed.
|
|
65
65
|
// An empty database prevents the initialization of other dependent plugins, such as `EmojiMention` and `EmojiPicker`.
|
|
66
66
|
if (!emojiDatabase.length) {
|
|
67
67
|
return this._databasePromiseResolveCallback(false);
|
|
68
68
|
}
|
|
69
|
-
const container = createEmojiWidthTestingContainer();
|
|
69
|
+
const container = emojiUtils.createEmojiWidthTestingContainer();
|
|
70
|
+
document.body.appendChild(container);
|
|
70
71
|
// Store the emoji database after normalizing the raw data.
|
|
71
72
|
this._database = emojiDatabase
|
|
72
|
-
.filter(item => isEmojiCategoryAllowed(item))
|
|
73
|
-
.filter(item =>
|
|
74
|
-
.map(item => normalizeEmojiSkinTone(item));
|
|
73
|
+
.filter(item => emojiUtils.isEmojiCategoryAllowed(item))
|
|
74
|
+
.filter(item => emojiUtils.isEmojiSupported(item, emojiSupportedVersionByOs, container))
|
|
75
|
+
.map(item => emojiUtils.normalizeEmojiSkinTone(item));
|
|
75
76
|
container.remove();
|
|
76
77
|
// Create instance of the Fuse.js library with configured weighted search keys and disabled fuzzy search.
|
|
77
78
|
this._fuseSearch = new Fuse(this._database, {
|
|
@@ -171,13 +172,6 @@ class EmojiRepository extends Plugin {
|
|
|
171
172
|
return this._databasePromise;
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
|
-
/**
|
|
175
|
-
* A function used to check if the given emoji is supported in the operating system.
|
|
176
|
-
*
|
|
177
|
-
* Referenced for unit testing purposes.
|
|
178
|
-
*/
|
|
179
|
-
EmojiRepository._isEmojiSupported = isEmojiSupported;
|
|
180
|
-
export default EmojiRepository;
|
|
181
175
|
/**
|
|
182
176
|
* Makes the HTTP request to download the emoji database.
|
|
183
177
|
*/
|
|
@@ -206,63 +200,3 @@ async function loadEmojiDatabase(emojiDatabaseUrl) {
|
|
|
206
200
|
}
|
|
207
201
|
return result;
|
|
208
202
|
}
|
|
209
|
-
/**
|
|
210
|
-
* Creates a div for emoji width testing purposes.
|
|
211
|
-
*/
|
|
212
|
-
function createEmojiWidthTestingContainer() {
|
|
213
|
-
const container = document.createElement('div');
|
|
214
|
-
container.setAttribute('aria-hidden', 'true');
|
|
215
|
-
container.style.position = 'absolute';
|
|
216
|
-
container.style.left = '-9999px';
|
|
217
|
-
container.style.whiteSpace = 'nowrap';
|
|
218
|
-
container.style.fontSize = BASELINE_EMOJI_WIDTH + 'px';
|
|
219
|
-
document.body.appendChild(container);
|
|
220
|
-
return container;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Returns the width of the provided node.
|
|
224
|
-
*/
|
|
225
|
-
function getNodeWidth(container, node) {
|
|
226
|
-
const span = document.createElement('span');
|
|
227
|
-
span.textContent = node;
|
|
228
|
-
container.appendChild(span);
|
|
229
|
-
const nodeWidth = span.offsetWidth;
|
|
230
|
-
container.removeChild(span);
|
|
231
|
-
return nodeWidth;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Checks whether the emoji is supported in the operating system.
|
|
235
|
-
*/
|
|
236
|
-
function isEmojiSupported(item, container) {
|
|
237
|
-
const emojiWidth = getNodeWidth(container, item.emoji);
|
|
238
|
-
// On Windows, some supported emoji are ~50% bigger than the baseline emoji, but what we really want to guard
|
|
239
|
-
// against are the ones that are 2x the size, because those are truly broken (person with red hair = person with
|
|
240
|
-
// floating red wig, black cat = cat with black square, polar bear = bear with snowflake, etc.)
|
|
241
|
-
// So here we set the threshold at 1.8 times the size of the baseline emoji.
|
|
242
|
-
return (emojiWidth / 1.8 < BASELINE_EMOJI_WIDTH) && (emojiWidth >= BASELINE_EMOJI_WIDTH);
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Adds default skin tone property to each emoji. If emoji defines other skin tones, they are added as well.
|
|
246
|
-
*/
|
|
247
|
-
function normalizeEmojiSkinTone(item) {
|
|
248
|
-
const entry = {
|
|
249
|
-
...item,
|
|
250
|
-
skins: {
|
|
251
|
-
default: item.emoji
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
if (item.skins) {
|
|
255
|
-
item.skins.forEach(skin => {
|
|
256
|
-
const skinTone = SKIN_TONE_MAP[skin.tone];
|
|
257
|
-
entry.skins[skinTone] = skin.emoji;
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
return entry;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Checks whether the emoji belongs to a group that is allowed.
|
|
264
|
-
*/
|
|
265
|
-
function isEmojiCategoryAllowed(item) {
|
|
266
|
-
// Category group=2 contains skin tones only, which we do not want to render.
|
|
267
|
-
return item.group !== 2;
|
|
268
|
-
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
|
+
*/
|
|
5
|
+
import { Plugin } from 'ckeditor5/src/core.js';
|
|
6
|
+
import type { EmojiCdnResource, EmojiEntry } from './emojirepository.js';
|
|
7
|
+
/**
|
|
8
|
+
* The Emoji utilities plugin.
|
|
9
|
+
*/
|
|
10
|
+
export default class EmojiUtils extends Plugin {
|
|
11
|
+
/**
|
|
12
|
+
* @inheritDoc
|
|
13
|
+
*/
|
|
14
|
+
static get pluginName(): "EmojiUtils";
|
|
15
|
+
/**
|
|
16
|
+
* @inheritDoc
|
|
17
|
+
*/
|
|
18
|
+
static get isOfficialPlugin(): true;
|
|
19
|
+
/**
|
|
20
|
+
* Checks if the emoji is supported by verifying the emoji version supported by the system first.
|
|
21
|
+
* Then checks if emoji contains a zero width joiner (ZWJ), and if yes, then checks if it is supported by the system.
|
|
22
|
+
*/
|
|
23
|
+
isEmojiSupported(item: EmojiCdnResource, emojiSupportedVersionByOs: number, container: HTMLDivElement): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Checks the supported emoji version by the OS, by sampling some representatives from different emoji releases.
|
|
26
|
+
*/
|
|
27
|
+
getEmojiSupportedVersionByOs(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Check for ZWJ (zero width joiner) character.
|
|
30
|
+
*/
|
|
31
|
+
hasZwj(emoji: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Checks whether the emoji is supported in the operating system.
|
|
34
|
+
*/
|
|
35
|
+
isEmojiZwjSupported(item: EmojiCdnResource, container: HTMLDivElement): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Returns the width of the provided node.
|
|
38
|
+
*/
|
|
39
|
+
getNodeWidth(container: HTMLDivElement, node: string): number;
|
|
40
|
+
/**
|
|
41
|
+
* Creates a div for emoji width testing purposes.
|
|
42
|
+
*/
|
|
43
|
+
createEmojiWidthTestingContainer(): HTMLDivElement;
|
|
44
|
+
/**
|
|
45
|
+
* Adds default skin tone property to each emoji. If emoji defines other skin tones, they are added as well.
|
|
46
|
+
*/
|
|
47
|
+
normalizeEmojiSkinTone(item: EmojiCdnResource): EmojiEntry;
|
|
48
|
+
/**
|
|
49
|
+
* Checks whether the emoji belongs to a group that is allowed.
|
|
50
|
+
*/
|
|
51
|
+
isEmojiCategoryAllowed(item: EmojiCdnResource): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* A function used to determine if emoji is supported by detecting pixels.
|
|
54
|
+
*
|
|
55
|
+
* Referenced for unit testing purposes. Kept in a separate file because of licensing.
|
|
56
|
+
*/
|
|
57
|
+
private static _isEmojiSupported;
|
|
58
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
|
+
*/
|
|
5
|
+
import { Plugin } from 'ckeditor5/src/core.js';
|
|
6
|
+
import isEmojiSupported from './utils/isemojisupported.js';
|
|
7
|
+
/**
|
|
8
|
+
* @module emoji/emojiutils
|
|
9
|
+
*/
|
|
10
|
+
const SKIN_TONE_MAP = {
|
|
11
|
+
0: 'default',
|
|
12
|
+
1: 'light',
|
|
13
|
+
2: 'medium-light',
|
|
14
|
+
3: 'medium',
|
|
15
|
+
4: 'medium-dark',
|
|
16
|
+
5: 'dark'
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A map representing an emoji and its release version.
|
|
20
|
+
* It's used to identify a user's minimal supported emoji level.
|
|
21
|
+
*/
|
|
22
|
+
const EMOJI_SUPPORT_LEVEL = {
|
|
23
|
+
'': 16,
|
|
24
|
+
'🫨': 15.1 // Shaking head. Although the version of emoji is 15, it is used to detect versions 15 and 15.1.
|
|
25
|
+
};
|
|
26
|
+
const BASELINE_EMOJI_WIDTH = 24;
|
|
27
|
+
/**
|
|
28
|
+
* The Emoji utilities plugin.
|
|
29
|
+
*/
|
|
30
|
+
class EmojiUtils extends Plugin {
|
|
31
|
+
/**
|
|
32
|
+
* @inheritDoc
|
|
33
|
+
*/
|
|
34
|
+
static get pluginName() {
|
|
35
|
+
return 'EmojiUtils';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* @inheritDoc
|
|
39
|
+
*/
|
|
40
|
+
static get isOfficialPlugin() {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Checks if the emoji is supported by verifying the emoji version supported by the system first.
|
|
45
|
+
* Then checks if emoji contains a zero width joiner (ZWJ), and if yes, then checks if it is supported by the system.
|
|
46
|
+
*/
|
|
47
|
+
isEmojiSupported(item, emojiSupportedVersionByOs, container) {
|
|
48
|
+
const isEmojiVersionSupported = item.version <= emojiSupportedVersionByOs;
|
|
49
|
+
if (!isEmojiVersionSupported) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (!this.hasZwj(item.emoji)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return this.isEmojiZwjSupported(item, container);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Checks the supported emoji version by the OS, by sampling some representatives from different emoji releases.
|
|
59
|
+
*/
|
|
60
|
+
getEmojiSupportedVersionByOs() {
|
|
61
|
+
return Object.entries(EMOJI_SUPPORT_LEVEL)
|
|
62
|
+
.reduce((currentVersion, [emoji, newVersion]) => {
|
|
63
|
+
if (newVersion > currentVersion && EmojiUtils._isEmojiSupported(emoji)) {
|
|
64
|
+
return newVersion;
|
|
65
|
+
}
|
|
66
|
+
return currentVersion;
|
|
67
|
+
}, 0);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check for ZWJ (zero width joiner) character.
|
|
71
|
+
*/
|
|
72
|
+
hasZwj(emoji) {
|
|
73
|
+
return emoji.includes('\u200d');
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Checks whether the emoji is supported in the operating system.
|
|
77
|
+
*/
|
|
78
|
+
isEmojiZwjSupported(item, container) {
|
|
79
|
+
const emojiWidth = this.getNodeWidth(container, item.emoji);
|
|
80
|
+
// On Windows, some supported emoji are ~50% bigger than the baseline emoji, but what we really want to guard
|
|
81
|
+
// against are the ones that are 2x the size, because those are truly broken (person with red hair = person with
|
|
82
|
+
// floating red wig, black cat = cat with black square, polar bear = bear with snowflake, etc.)
|
|
83
|
+
// So here we set the threshold at 1.8 times the size of the baseline emoji.
|
|
84
|
+
return emojiWidth < BASELINE_EMOJI_WIDTH * 1.8;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Returns the width of the provided node.
|
|
88
|
+
*/
|
|
89
|
+
getNodeWidth(container, node) {
|
|
90
|
+
const span = document.createElement('span');
|
|
91
|
+
span.textContent = node;
|
|
92
|
+
container.appendChild(span);
|
|
93
|
+
const nodeWidth = span.offsetWidth;
|
|
94
|
+
container.removeChild(span);
|
|
95
|
+
return nodeWidth;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a div for emoji width testing purposes.
|
|
99
|
+
*/
|
|
100
|
+
createEmojiWidthTestingContainer() {
|
|
101
|
+
const container = document.createElement('div');
|
|
102
|
+
container.setAttribute('aria-hidden', 'true');
|
|
103
|
+
container.style.position = 'absolute';
|
|
104
|
+
container.style.left = '-9999px';
|
|
105
|
+
container.style.whiteSpace = 'nowrap';
|
|
106
|
+
container.style.fontSize = BASELINE_EMOJI_WIDTH + 'px';
|
|
107
|
+
return container;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Adds default skin tone property to each emoji. If emoji defines other skin tones, they are added as well.
|
|
111
|
+
*/
|
|
112
|
+
normalizeEmojiSkinTone(item) {
|
|
113
|
+
const entry = {
|
|
114
|
+
...item,
|
|
115
|
+
skins: {
|
|
116
|
+
default: item.emoji
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
if (item.skins) {
|
|
120
|
+
item.skins.forEach(skin => {
|
|
121
|
+
const skinTone = SKIN_TONE_MAP[skin.tone];
|
|
122
|
+
entry.skins[skinTone] = skin.emoji;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return entry;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Checks whether the emoji belongs to a group that is allowed.
|
|
129
|
+
*/
|
|
130
|
+
isEmojiCategoryAllowed(item) {
|
|
131
|
+
// Category group=2 contains skin tones only, which we do not want to render.
|
|
132
|
+
return item.group !== 2;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A function used to determine if emoji is supported by detecting pixels.
|
|
137
|
+
*
|
|
138
|
+
* Referenced for unit testing purposes. Kept in a separate file because of licensing.
|
|
139
|
+
*/
|
|
140
|
+
EmojiUtils._isEmojiSupported = isEmojiSupported;
|
|
141
|
+
export default EmojiUtils;
|
package/src/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { default as Emoji } from './emoji.js';
|
|
|
9
9
|
export { default as EmojiMention } from './emojimention.js';
|
|
10
10
|
export { default as EmojiPicker } from './emojipicker.js';
|
|
11
11
|
export { default as EmojiRepository } from './emojirepository.js';
|
|
12
|
+
export { default as EmojiUtils } from './emojiutils.js';
|
|
12
13
|
export { default as EmojiCommand } from './emojicommand.js';
|
|
13
14
|
export type { EmojiConfig } from './emojiconfig.js';
|
|
14
15
|
import './augmentation.js';
|
package/src/index.js
CHANGED
|
@@ -9,5 +9,6 @@ export { default as Emoji } from './emoji.js';
|
|
|
9
9
|
export { default as EmojiMention } from './emojimention.js';
|
|
10
10
|
export { default as EmojiPicker } from './emojipicker.js';
|
|
11
11
|
export { default as EmojiRepository } from './emojirepository.js';
|
|
12
|
+
export { default as EmojiUtils } from './emojiutils.js';
|
|
12
13
|
export { default as EmojiCommand } from './emojicommand.js';
|
|
13
14
|
import './augmentation.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2023, Koala Interactive SAS
|
|
3
|
+
* For licensing, see https://github.com/koala-interactive/is-emoji-supported/blob/master/LICENSE.md
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @module emoji/utils/isemojisupported
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Checks if the two pixels parts are the same using canvas.
|
|
10
|
+
*/
|
|
11
|
+
export default function isEmojiSupported(unicode: string): boolean;
|