@atlaskit/editor-plugin-emoji 3.6.6 → 3.7.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @atlaskit/editor-plugin-emoji
2
2
 
3
+ ## 3.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#160575](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/160575)
8
+ [`c340cf0e2d6c2`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/c340cf0e2d6c2) -
9
+ Expose emoji provider promise to initialise in the toolbar earlier.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+
15
+ ## 3.6.7
16
+
17
+ ### Patch Changes
18
+
19
+ - [#154163](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/154163)
20
+ [`82b017a2d9588`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/82b017a2d9588) -
21
+ Add error handling if offline editing to refresh the emoji node when returning online.
22
+
3
23
  ## 3.6.6
4
24
 
5
25
  ### Patch Changes
@@ -266,12 +266,14 @@ var emojiPlugin = exports.emojiPlugin = function emojiPlugin(_ref2) {
266
266
  emojiResourceConfig = _ref9.emojiResourceConfig,
267
267
  asciiMap = _ref9.asciiMap,
268
268
  emojiProvider = _ref9.emojiProvider,
269
- inlineEmojiPopupOpen = _ref9.inlineEmojiPopupOpen;
269
+ inlineEmojiPopupOpen = _ref9.inlineEmojiPopupOpen,
270
+ emojiProviderPromise = _ref9.emojiProviderPromise;
270
271
  return {
271
272
  emojiResourceConfig: emojiResourceConfig,
272
273
  asciiMap: asciiMap,
273
274
  typeAheadHandler: typeAhead,
274
275
  emojiProvider: emojiProvider,
276
+ emojiProviderPromise: emojiProviderPromise,
275
277
  inlineEmojiPopupOpen: inlineEmojiPopupOpen
276
278
  };
277
279
  },
@@ -506,6 +508,11 @@ function createEmojiPlugin(pmPluginFactoryParams, options, api) {
506
508
  key: emojiPluginKey,
507
509
  state: {
508
510
  init: function init() {
511
+ if (options !== null && options !== void 0 && options.emojiProvider && (0, _experiments.editorExperiment)('platform_editor_prevent_toolbar_layout_shifts', true)) {
512
+ return {
513
+ emojiProviderPromise: options.emojiProvider
514
+ };
515
+ }
509
516
  return {};
510
517
  },
511
518
  apply: function apply(tr, pluginState) {
@@ -519,7 +526,8 @@ function createEmojiPlugin(pmPluginFactoryParams, options, api) {
519
526
  switch (action) {
520
527
  case _actions.ACTIONS.SET_PROVIDER:
521
528
  newPluginState = _objectSpread(_objectSpread({}, pluginState), {}, {
522
- emojiProvider: params.provider
529
+ emojiProvider: params.provider,
530
+ emojiProviderPromise: (0, _experiments.editorExperiment)('platform_editor_prevent_toolbar_layout_shifts', true) ? Promise.resolve(params.provider) : undefined
523
531
  });
524
532
  pmPluginFactoryParams.dispatch(emojiPluginKey, newPluginState);
525
533
  return newPluginState;
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.EmojiNodeView = void 0;
8
+ exports.isSingleEmoji = isSingleEmoji;
8
9
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
10
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
11
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
@@ -14,15 +15,47 @@ var _coreUtils = require("@atlaskit/editor-common/core-utils");
14
15
  var _emoji = require("@atlaskit/editor-common/emoji");
15
16
  var _monitoring = require("@atlaskit/editor-common/monitoring");
16
17
  var _model = require("@atlaskit/editor-prosemirror/model");
18
+ var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
17
19
  var _emojiNodeSpec = require("./emojiNodeSpec");
20
+ /**
21
+ * Check if we can nicely fallback to the nodes text
22
+ *
23
+ * @param fallbackText string of the nodes fallback text
24
+ *
25
+ * @example
26
+ * isSingleEmoji('😀') // true
27
+ */
28
+ function isSingleEmoji(fallbackText) {
29
+ // Regular expression to match a single emoji character
30
+ var emojiRegex = /^((?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9\uDEF0-\uDEF8])|(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u2388\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2605\u2607-\u2612\u2614-\u2685\u2690-\u2705\u2708-\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2767\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC00-\uDCFF\uDD0D-\uDD0F\uDD2F\uDD6C-\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAD-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDFFA]|\uD83D[\uDC00-\uDD3D\uDD46-\uDE4F\uDE80-\uDEFF\uDF74-\uDF7F\uDFD5-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDEFF]|\uD83F[\uDC00-\uDFFD])\uFE0F?(?:\u200D(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u2388\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2605\u2607-\u2612\u2614-\u2685\u2690-\u2705\u2708-\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2767\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC00-\uDCFF\uDD0D-\uDD0F\uDD2F\uDD6C-\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAD-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDFFA]|\uD83D[\uDC00-\uDD3D\uDD46-\uDE4F\uDE80-\uDEFF\uDF74-\uDF7F\uDFD5-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDEFF]|\uD83F[\uDC00-\uDFFD])\uFE0F?)+|(?:\uD83C[\uDDE6-\uDDFF])(?:\uD83C[\uDDE6-\uDDFF]))$/;
31
+ return emojiRegex.test(fallbackText);
32
+ }
33
+
34
+ /**
35
+ * Emoji node view for renderering emoji nodes
36
+ */
18
37
  var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
38
+ /**
39
+ * Prosemirror node view for rendering emoji nodes. This class is responsible for
40
+ * rendering emoji nodes in the editor, handling updates, and managing fallback rendering.
41
+ *
42
+ * @param node - The ProseMirror node representing the emoji.
43
+ * @param extraProps - An object containing additional parameters.
44
+ * @param extraProps.intl - The internationalization object for formatting messages.
45
+ * @param extraProps.api - The editor API for accessing shared state and connectivity features.
46
+ *
47
+ * @example
48
+ * const emojiNodeView = new EmojiNodeView(node, { intl, api });
49
+ */
19
50
  function EmojiNodeView(node, _ref) {
20
51
  var _api$emoji,
21
52
  _sharedState$currentS,
22
- _this = this;
53
+ _this = this,
54
+ _api$connectivity;
23
55
  var intl = _ref.intl,
24
56
  api = _ref.api;
25
57
  (0, _classCallCheck2.default)(this, EmojiNodeView);
58
+ (0, _defineProperty2.default)(this, "renderingFallback", false);
26
59
  (0, _defineProperty2.default)(this, "destroy", function () {});
27
60
  this.node = node;
28
61
  this.intl = intl;
@@ -57,8 +90,19 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
57
90
  emojiProvider = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.emojiProvider;
58
91
  void _this.updateDom(emojiProvider);
59
92
  });
93
+
94
+ // Refresh emojis if we go back online
95
+ var subscribeToConnection = api === null || api === void 0 || (_api$connectivity = api.connectivity) === null || _api$connectivity === void 0 ? void 0 : _api$connectivity.sharedState.onChange(function (_ref3) {
96
+ var prevSharedState = _ref3.prevSharedState,
97
+ nextSharedState = _ref3.nextSharedState;
98
+ if ((prevSharedState === null || prevSharedState === void 0 ? void 0 : prevSharedState.mode) === 'offline' && (nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.mode) === 'online' && _this.renderingFallback && (0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true)) {
99
+ var _sharedState$currentS2;
100
+ _this.updateDom((_sharedState$currentS2 = sharedState.currentState()) === null || _sharedState$currentS2 === void 0 ? void 0 : _sharedState$currentS2.emojiProvider);
101
+ }
102
+ });
60
103
  this.destroy = function () {
61
104
  unsubscribe();
105
+ subscribeToConnection === null || subscribeToConnection === void 0 || subscribeToConnection();
62
106
  };
63
107
  }
64
108
  return (0, _createClass2.default)(EmojiNodeView, [{
@@ -136,6 +180,7 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
136
180
  }, {
137
181
  key: "renderFallback",
138
182
  value: function renderFallback() {
183
+ this.renderingFallback = true;
139
184
  this.cleanUpAndRenderCommonAttributes();
140
185
  var fallbackElement = document.createElement('span');
141
186
  fallbackElement.innerText = this.node.attrs.text || this.node.attrs.shortName;
@@ -146,6 +191,7 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
146
191
  }, {
147
192
  key: "renderEmoji",
148
193
  value: function renderEmoji(description, representation) {
194
+ this.renderingFallback = false;
149
195
  this.cleanUpAndRenderCommonAttributes();
150
196
  var emojiType = 'sprite' in representation ? 'sprite' : 'image';
151
197
 
@@ -180,6 +226,7 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
180
226
  }, {
181
227
  key: "createImageEmojiElement",
182
228
  value: function createImageEmojiElement(emojiDescription, representation) {
229
+ var _this2 = this;
183
230
  var imageElement = document.createElement('img');
184
231
  imageElement.classList.add(_emoji.EmojiSharedCssClassName.EMOJI_IMAGE);
185
232
  imageElement.src = 'imagePath' in representation ? representation.imagePath : representation.mediaPath;
@@ -190,6 +237,18 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
190
237
  // Because img.width is round to the nearest integer.
191
238
  imageElement.setAttribute('width', "".concat(_emoji.defaultEmojiHeight / representation.height * representation.width));
192
239
  }
240
+ if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true)) {
241
+ // If there's an error (ie. offline) render the ascii fallback if possible, otherwise
242
+ // mark the node to refresh when returning online.
243
+ imageElement.onerror = function () {
244
+ // Create a check that confirms if this.node.attrs.text if an ascii emoji
245
+ if (isSingleEmoji(_this2.node.attrs.text)) {
246
+ _this2.renderFallback();
247
+ } else {
248
+ _this2.renderingFallback = true;
249
+ }
250
+ };
251
+ }
193
252
  return imageElement;
194
253
  }
195
254
  }], [{
@@ -248,13 +248,15 @@ export const emojiPlugin = ({
248
248
  emojiResourceConfig,
249
249
  asciiMap,
250
250
  emojiProvider,
251
- inlineEmojiPopupOpen
251
+ inlineEmojiPopupOpen,
252
+ emojiProviderPromise
252
253
  } = (_emojiPluginKey$getSt = emojiPluginKey.getState(editorState)) !== null && _emojiPluginKey$getSt !== void 0 ? _emojiPluginKey$getSt : {};
253
254
  return {
254
255
  emojiResourceConfig,
255
256
  asciiMap,
256
257
  typeAheadHandler: typeAhead,
257
258
  emojiProvider,
259
+ emojiProviderPromise,
258
260
  inlineEmojiPopupOpen
259
261
  };
260
262
  },
@@ -456,6 +458,11 @@ export function createEmojiPlugin(pmPluginFactoryParams, options, api) {
456
458
  key: emojiPluginKey,
457
459
  state: {
458
460
  init() {
461
+ if (options !== null && options !== void 0 && options.emojiProvider && editorExperiment('platform_editor_prevent_toolbar_layout_shifts', true)) {
462
+ return {
463
+ emojiProviderPromise: options.emojiProvider
464
+ };
465
+ }
459
466
  return {};
460
467
  },
461
468
  apply(tr, pluginState) {
@@ -471,7 +478,8 @@ export function createEmojiPlugin(pmPluginFactoryParams, options, api) {
471
478
  case ACTIONS.SET_PROVIDER:
472
479
  newPluginState = {
473
480
  ...pluginState,
474
- emojiProvider: params.provider
481
+ emojiProvider: params.provider,
482
+ emojiProviderPromise: editorExperiment('platform_editor_prevent_toolbar_layout_shifts', true) ? Promise.resolve(params.provider) : undefined
475
483
  };
476
484
  pmPluginFactoryParams.dispatch(emojiPluginKey, newPluginState);
477
485
  return newPluginState;
@@ -3,18 +3,50 @@ import { isSSR } from '@atlaskit/editor-common/core-utils';
3
3
  import { messages, EmojiSharedCssClassName, defaultEmojiHeight } from '@atlaskit/editor-common/emoji';
4
4
  import { logException } from '@atlaskit/editor-common/monitoring';
5
5
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
6
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
6
7
  import { emojiToDom } from './emojiNodeSpec';
8
+ /**
9
+ * Check if we can nicely fallback to the nodes text
10
+ *
11
+ * @param fallbackText string of the nodes fallback text
12
+ *
13
+ * @example
14
+ * isSingleEmoji('😀') // true
15
+ */
16
+ export function isSingleEmoji(fallbackText) {
17
+ // Regular expression to match a single emoji character
18
+ const emojiRegex = /^(\p{Emoji_Presentation}|\p{Extended_Pictographic}\u{FE0F}?(?:\u{200D}\p{Extended_Pictographic}\u{FE0F}?)+|\p{Regional_Indicator}\p{Regional_Indicator})$/u;
19
+ return emojiRegex.test(fallbackText);
20
+ }
21
+
22
+ /**
23
+ * Emoji node view for renderering emoji nodes
24
+ */
7
25
  export class EmojiNodeView {
8
26
  static logError(error) {
9
27
  void logException(error, {
10
28
  location: 'editor-plugin-emoji/EmojiNodeView'
11
29
  });
12
30
  }
31
+
32
+ /**
33
+ * Prosemirror node view for rendering emoji nodes. This class is responsible for
34
+ * rendering emoji nodes in the editor, handling updates, and managing fallback rendering.
35
+ *
36
+ * @param node - The ProseMirror node representing the emoji.
37
+ * @param extraProps - An object containing additional parameters.
38
+ * @param extraProps.intl - The internationalization object for formatting messages.
39
+ * @param extraProps.api - The editor API for accessing shared state and connectivity features.
40
+ *
41
+ * @example
42
+ * const emojiNodeView = new EmojiNodeView(node, { intl, api });
43
+ */
13
44
  constructor(node, {
14
45
  intl,
15
46
  api
16
47
  }) {
17
- var _api$emoji, _sharedState$currentS;
48
+ var _api$emoji, _sharedState$currentS, _api$connectivity;
49
+ _defineProperty(this, "renderingFallback", false);
18
50
  _defineProperty(this, "destroy", () => {});
19
51
  this.node = node;
20
52
  this.intl = intl;
@@ -51,8 +83,20 @@ export class EmojiNodeView {
51
83
  emojiProvider = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.emojiProvider;
52
84
  void this.updateDom(emojiProvider);
53
85
  });
86
+
87
+ // Refresh emojis if we go back online
88
+ const subscribeToConnection = api === null || api === void 0 ? void 0 : (_api$connectivity = api.connectivity) === null || _api$connectivity === void 0 ? void 0 : _api$connectivity.sharedState.onChange(({
89
+ prevSharedState,
90
+ nextSharedState
91
+ }) => {
92
+ if ((prevSharedState === null || prevSharedState === void 0 ? void 0 : prevSharedState.mode) === 'offline' && (nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.mode) === 'online' && this.renderingFallback && editorExperiment('platform_editor_offline_editing_web', true)) {
93
+ var _sharedState$currentS2;
94
+ this.updateDom((_sharedState$currentS2 = sharedState.currentState()) === null || _sharedState$currentS2 === void 0 ? void 0 : _sharedState$currentS2.emojiProvider);
95
+ }
96
+ });
54
97
  this.destroy = () => {
55
98
  unsubscribe();
99
+ subscribeToConnection === null || subscribeToConnection === void 0 ? void 0 : subscribeToConnection();
56
100
  };
57
101
  }
58
102
  async updateDom(emojiProvider) {
@@ -104,6 +148,7 @@ export class EmojiNodeView {
104
148
  }
105
149
  }
106
150
  renderFallback() {
151
+ this.renderingFallback = true;
107
152
  this.cleanUpAndRenderCommonAttributes();
108
153
  const fallbackElement = document.createElement('span');
109
154
  fallbackElement.innerText = this.node.attrs.text || this.node.attrs.shortName;
@@ -112,6 +157,7 @@ export class EmojiNodeView {
112
157
  this.dom.appendChild(fallbackElement);
113
158
  }
114
159
  renderEmoji(description, representation) {
160
+ this.renderingFallback = false;
115
161
  this.cleanUpAndRenderCommonAttributes();
116
162
  const emojiType = 'sprite' in representation ? 'sprite' : 'image';
117
163
 
@@ -152,6 +198,18 @@ export class EmojiNodeView {
152
198
  // Because img.width is round to the nearest integer.
153
199
  imageElement.setAttribute('width', `${defaultEmojiHeight / representation.height * representation.width}`);
154
200
  }
201
+ if (editorExperiment('platform_editor_offline_editing_web', true)) {
202
+ // If there's an error (ie. offline) render the ascii fallback if possible, otherwise
203
+ // mark the node to refresh when returning online.
204
+ imageElement.onerror = () => {
205
+ // Create a check that confirms if this.node.attrs.text if an ascii emoji
206
+ if (isSingleEmoji(this.node.attrs.text)) {
207
+ this.renderFallback();
208
+ } else {
209
+ this.renderingFallback = true;
210
+ }
211
+ };
212
+ }
155
213
  return imageElement;
156
214
  }
157
215
  }
@@ -251,12 +251,14 @@ export var emojiPlugin = function emojiPlugin(_ref2) {
251
251
  emojiResourceConfig = _ref9.emojiResourceConfig,
252
252
  asciiMap = _ref9.asciiMap,
253
253
  emojiProvider = _ref9.emojiProvider,
254
- inlineEmojiPopupOpen = _ref9.inlineEmojiPopupOpen;
254
+ inlineEmojiPopupOpen = _ref9.inlineEmojiPopupOpen,
255
+ emojiProviderPromise = _ref9.emojiProviderPromise;
255
256
  return {
256
257
  emojiResourceConfig: emojiResourceConfig,
257
258
  asciiMap: asciiMap,
258
259
  typeAheadHandler: typeAhead,
259
260
  emojiProvider: emojiProvider,
261
+ emojiProviderPromise: emojiProviderPromise,
260
262
  inlineEmojiPopupOpen: inlineEmojiPopupOpen
261
263
  };
262
264
  },
@@ -491,6 +493,11 @@ export function createEmojiPlugin(pmPluginFactoryParams, options, api) {
491
493
  key: emojiPluginKey,
492
494
  state: {
493
495
  init: function init() {
496
+ if (options !== null && options !== void 0 && options.emojiProvider && editorExperiment('platform_editor_prevent_toolbar_layout_shifts', true)) {
497
+ return {
498
+ emojiProviderPromise: options.emojiProvider
499
+ };
500
+ }
494
501
  return {};
495
502
  },
496
503
  apply: function apply(tr, pluginState) {
@@ -504,7 +511,8 @@ export function createEmojiPlugin(pmPluginFactoryParams, options, api) {
504
511
  switch (action) {
505
512
  case ACTIONS.SET_PROVIDER:
506
513
  newPluginState = _objectSpread(_objectSpread({}, pluginState), {}, {
507
- emojiProvider: params.provider
514
+ emojiProvider: params.provider,
515
+ emojiProviderPromise: editorExperiment('platform_editor_prevent_toolbar_layout_shifts', true) ? Promise.resolve(params.provider) : undefined
508
516
  });
509
517
  pmPluginFactoryParams.dispatch(emojiPluginKey, newPluginState);
510
518
  return newPluginState;
@@ -7,15 +7,47 @@ import { isSSR } from '@atlaskit/editor-common/core-utils';
7
7
  import { messages, EmojiSharedCssClassName, defaultEmojiHeight } from '@atlaskit/editor-common/emoji';
8
8
  import { logException } from '@atlaskit/editor-common/monitoring';
9
9
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
10
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
10
11
  import { emojiToDom } from './emojiNodeSpec';
12
+ /**
13
+ * Check if we can nicely fallback to the nodes text
14
+ *
15
+ * @param fallbackText string of the nodes fallback text
16
+ *
17
+ * @example
18
+ * isSingleEmoji('😀') // true
19
+ */
20
+ export function isSingleEmoji(fallbackText) {
21
+ // Regular expression to match a single emoji character
22
+ var emojiRegex = /^((?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9\uDEF0-\uDEF8])|(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u2388\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2605\u2607-\u2612\u2614-\u2685\u2690-\u2705\u2708-\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2767\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC00-\uDCFF\uDD0D-\uDD0F\uDD2F\uDD6C-\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAD-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDFFA]|\uD83D[\uDC00-\uDD3D\uDD46-\uDE4F\uDE80-\uDEFF\uDF74-\uDF7F\uDFD5-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDEFF]|\uD83F[\uDC00-\uDFFD])\uFE0F?(?:\u200D(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u2388\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2605\u2607-\u2612\u2614-\u2685\u2690-\u2705\u2708-\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2767\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC00-\uDCFF\uDD0D-\uDD0F\uDD2F\uDD6C-\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAD-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDFFA]|\uD83D[\uDC00-\uDD3D\uDD46-\uDE4F\uDE80-\uDEFF\uDF74-\uDF7F\uDFD5-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDEFF]|\uD83F[\uDC00-\uDFFD])\uFE0F?)+|(?:\uD83C[\uDDE6-\uDDFF])(?:\uD83C[\uDDE6-\uDDFF]))$/;
23
+ return emojiRegex.test(fallbackText);
24
+ }
25
+
26
+ /**
27
+ * Emoji node view for renderering emoji nodes
28
+ */
11
29
  export var EmojiNodeView = /*#__PURE__*/function () {
30
+ /**
31
+ * Prosemirror node view for rendering emoji nodes. This class is responsible for
32
+ * rendering emoji nodes in the editor, handling updates, and managing fallback rendering.
33
+ *
34
+ * @param node - The ProseMirror node representing the emoji.
35
+ * @param extraProps - An object containing additional parameters.
36
+ * @param extraProps.intl - The internationalization object for formatting messages.
37
+ * @param extraProps.api - The editor API for accessing shared state and connectivity features.
38
+ *
39
+ * @example
40
+ * const emojiNodeView = new EmojiNodeView(node, { intl, api });
41
+ */
12
42
  function EmojiNodeView(node, _ref) {
13
43
  var _api$emoji,
14
44
  _sharedState$currentS,
15
- _this = this;
45
+ _this = this,
46
+ _api$connectivity;
16
47
  var intl = _ref.intl,
17
48
  api = _ref.api;
18
49
  _classCallCheck(this, EmojiNodeView);
50
+ _defineProperty(this, "renderingFallback", false);
19
51
  _defineProperty(this, "destroy", function () {});
20
52
  this.node = node;
21
53
  this.intl = intl;
@@ -50,8 +82,19 @@ export var EmojiNodeView = /*#__PURE__*/function () {
50
82
  emojiProvider = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.emojiProvider;
51
83
  void _this.updateDom(emojiProvider);
52
84
  });
85
+
86
+ // Refresh emojis if we go back online
87
+ var subscribeToConnection = api === null || api === void 0 || (_api$connectivity = api.connectivity) === null || _api$connectivity === void 0 ? void 0 : _api$connectivity.sharedState.onChange(function (_ref3) {
88
+ var prevSharedState = _ref3.prevSharedState,
89
+ nextSharedState = _ref3.nextSharedState;
90
+ if ((prevSharedState === null || prevSharedState === void 0 ? void 0 : prevSharedState.mode) === 'offline' && (nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.mode) === 'online' && _this.renderingFallback && editorExperiment('platform_editor_offline_editing_web', true)) {
91
+ var _sharedState$currentS2;
92
+ _this.updateDom((_sharedState$currentS2 = sharedState.currentState()) === null || _sharedState$currentS2 === void 0 ? void 0 : _sharedState$currentS2.emojiProvider);
93
+ }
94
+ });
53
95
  this.destroy = function () {
54
96
  unsubscribe();
97
+ subscribeToConnection === null || subscribeToConnection === void 0 || subscribeToConnection();
55
98
  };
56
99
  }
57
100
  return _createClass(EmojiNodeView, [{
@@ -129,6 +172,7 @@ export var EmojiNodeView = /*#__PURE__*/function () {
129
172
  }, {
130
173
  key: "renderFallback",
131
174
  value: function renderFallback() {
175
+ this.renderingFallback = true;
132
176
  this.cleanUpAndRenderCommonAttributes();
133
177
  var fallbackElement = document.createElement('span');
134
178
  fallbackElement.innerText = this.node.attrs.text || this.node.attrs.shortName;
@@ -139,6 +183,7 @@ export var EmojiNodeView = /*#__PURE__*/function () {
139
183
  }, {
140
184
  key: "renderEmoji",
141
185
  value: function renderEmoji(description, representation) {
186
+ this.renderingFallback = false;
142
187
  this.cleanUpAndRenderCommonAttributes();
143
188
  var emojiType = 'sprite' in representation ? 'sprite' : 'image';
144
189
 
@@ -173,6 +218,7 @@ export var EmojiNodeView = /*#__PURE__*/function () {
173
218
  }, {
174
219
  key: "createImageEmojiElement",
175
220
  value: function createImageEmojiElement(emojiDescription, representation) {
221
+ var _this2 = this;
176
222
  var imageElement = document.createElement('img');
177
223
  imageElement.classList.add(EmojiSharedCssClassName.EMOJI_IMAGE);
178
224
  imageElement.src = 'imagePath' in representation ? representation.imagePath : representation.mediaPath;
@@ -183,6 +229,18 @@ export var EmojiNodeView = /*#__PURE__*/function () {
183
229
  // Because img.width is round to the nearest integer.
184
230
  imageElement.setAttribute('width', "".concat(defaultEmojiHeight / representation.height * representation.width));
185
231
  }
232
+ if (editorExperiment('platform_editor_offline_editing_web', true)) {
233
+ // If there's an error (ie. offline) render the ascii fallback if possible, otherwise
234
+ // mark the node to refresh when returning online.
235
+ imageElement.onerror = function () {
236
+ // Create a check that confirms if this.node.attrs.text if an ascii emoji
237
+ if (isSingleEmoji(_this2.node.attrs.text)) {
238
+ _this2.renderFallback();
239
+ } else {
240
+ _this2.renderingFallback = true;
241
+ }
242
+ };
243
+ }
186
244
  return imageElement;
187
245
  }
188
246
  }], [{
@@ -3,6 +3,7 @@ import type { Command, EditorCommand, NextEditorPlugin, OptionalPlugin, TypeAhea
3
3
  import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
4
4
  import type { InlineCommentInputMethod, InlineCommentMap } from '@atlaskit/editor-plugin-annotation';
5
5
  import type { BasePlugin } from '@atlaskit/editor-plugin-base';
6
+ import type { ConnectivityPlugin } from '@atlaskit/editor-plugin-connectivity';
6
7
  import { type EditorViewModePluginState } from '@atlaskit/editor-plugin-editor-viewmode';
7
8
  import type { MetricsPlugin } from '@atlaskit/editor-plugin-metrics';
8
9
  import type { TypeAheadInputMethod, TypeAheadPlugin } from '@atlaskit/editor-plugin-type-ahead';
@@ -36,6 +37,12 @@ export type EmojiPluginState = {
36
37
  emojiResourceConfig?: EmojiResourceConfig;
37
38
  asciiMap?: Map<string, EmojiDescription>;
38
39
  inlineEmojiPopupOpen?: boolean;
40
+ /**
41
+ * Occassionally it may be more convenient to deal with the
42
+ * promise version of the emoji provider. This is available
43
+ * immediately if used for the initial configuration
44
+ */
45
+ emojiProviderPromise?: Promise<EmojiProvider>;
39
46
  };
40
47
  export type EmojiPluginSharedState = EmojiPluginState & {
41
48
  typeAheadHandler: TypeAheadHandler;
@@ -53,7 +60,8 @@ export type EmojiPluginDependencies = [
53
60
  OptionalPlugin<AnnotationPluginType>,
54
61
  OptionalPlugin<EditorViewModePluginType>,
55
62
  OptionalPlugin<BasePlugin>,
56
- OptionalPlugin<MetricsPlugin>
63
+ OptionalPlugin<MetricsPlugin>,
64
+ OptionalPlugin<ConnectivityPlugin>
57
65
  ];
58
66
  export type EmojiPlugin = NextEditorPlugin<'emoji', {
59
67
  pluginConfiguration: EmojiPluginOptions | undefined;
@@ -7,13 +7,38 @@ interface Params {
7
7
  intl: IntlShape;
8
8
  api: ExtractInjectionAPI<EmojiPlugin> | undefined;
9
9
  }
10
+ /**
11
+ * Check if we can nicely fallback to the nodes text
12
+ *
13
+ * @param fallbackText string of the nodes fallback text
14
+ *
15
+ * @example
16
+ * isSingleEmoji('😀') // true
17
+ */
18
+ export declare function isSingleEmoji(fallbackText: string): boolean;
19
+ /**
20
+ * Emoji node view for renderering emoji nodes
21
+ */
10
22
  export declare class EmojiNodeView implements NodeView {
11
23
  dom: Node;
12
24
  domElement: HTMLElement | undefined;
13
25
  private readonly node;
14
26
  private readonly intl;
27
+ private renderingFallback;
15
28
  readonly destroy: () => void;
16
29
  private static logError;
30
+ /**
31
+ * Prosemirror node view for rendering emoji nodes. This class is responsible for
32
+ * rendering emoji nodes in the editor, handling updates, and managing fallback rendering.
33
+ *
34
+ * @param node - The ProseMirror node representing the emoji.
35
+ * @param extraProps - An object containing additional parameters.
36
+ * @param extraProps.intl - The internationalization object for formatting messages.
37
+ * @param extraProps.api - The editor API for accessing shared state and connectivity features.
38
+ *
39
+ * @example
40
+ * const emojiNodeView = new EmojiNodeView(node, { intl, api });
41
+ */
17
42
  constructor(node: PMNode, { intl, api }: Params);
18
43
  private updateDom;
19
44
  private static isEmojiRepresentationSupported;
@@ -3,6 +3,7 @@ import type { Command, EditorCommand, NextEditorPlugin, OptionalPlugin, TypeAhea
3
3
  import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
4
4
  import type { InlineCommentInputMethod, InlineCommentMap } from '@atlaskit/editor-plugin-annotation';
5
5
  import type { BasePlugin } from '@atlaskit/editor-plugin-base';
6
+ import type { ConnectivityPlugin } from '@atlaskit/editor-plugin-connectivity';
6
7
  import { type EditorViewModePluginState } from '@atlaskit/editor-plugin-editor-viewmode';
7
8
  import type { MetricsPlugin } from '@atlaskit/editor-plugin-metrics';
8
9
  import type { TypeAheadInputMethod, TypeAheadPlugin } from '@atlaskit/editor-plugin-type-ahead';
@@ -36,6 +37,12 @@ export type EmojiPluginState = {
36
37
  emojiResourceConfig?: EmojiResourceConfig;
37
38
  asciiMap?: Map<string, EmojiDescription>;
38
39
  inlineEmojiPopupOpen?: boolean;
40
+ /**
41
+ * Occassionally it may be more convenient to deal with the
42
+ * promise version of the emoji provider. This is available
43
+ * immediately if used for the initial configuration
44
+ */
45
+ emojiProviderPromise?: Promise<EmojiProvider>;
39
46
  };
40
47
  export type EmojiPluginSharedState = EmojiPluginState & {
41
48
  typeAheadHandler: TypeAheadHandler;
@@ -53,7 +60,8 @@ export type EmojiPluginDependencies = [
53
60
  OptionalPlugin<AnnotationPluginType>,
54
61
  OptionalPlugin<EditorViewModePluginType>,
55
62
  OptionalPlugin<BasePlugin>,
56
- OptionalPlugin<MetricsPlugin>
63
+ OptionalPlugin<MetricsPlugin>,
64
+ OptionalPlugin<ConnectivityPlugin>
57
65
  ];
58
66
  export type EmojiPlugin = NextEditorPlugin<'emoji', {
59
67
  pluginConfiguration: EmojiPluginOptions | undefined;
@@ -7,13 +7,38 @@ interface Params {
7
7
  intl: IntlShape;
8
8
  api: ExtractInjectionAPI<EmojiPlugin> | undefined;
9
9
  }
10
+ /**
11
+ * Check if we can nicely fallback to the nodes text
12
+ *
13
+ * @param fallbackText string of the nodes fallback text
14
+ *
15
+ * @example
16
+ * isSingleEmoji('😀') // true
17
+ */
18
+ export declare function isSingleEmoji(fallbackText: string): boolean;
19
+ /**
20
+ * Emoji node view for renderering emoji nodes
21
+ */
10
22
  export declare class EmojiNodeView implements NodeView {
11
23
  dom: Node;
12
24
  domElement: HTMLElement | undefined;
13
25
  private readonly node;
14
26
  private readonly intl;
27
+ private renderingFallback;
15
28
  readonly destroy: () => void;
16
29
  private static logError;
30
+ /**
31
+ * Prosemirror node view for rendering emoji nodes. This class is responsible for
32
+ * rendering emoji nodes in the editor, handling updates, and managing fallback rendering.
33
+ *
34
+ * @param node - The ProseMirror node representing the emoji.
35
+ * @param extraProps - An object containing additional parameters.
36
+ * @param extraProps.intl - The internationalization object for formatting messages.
37
+ * @param extraProps.api - The editor API for accessing shared state and connectivity features.
38
+ *
39
+ * @example
40
+ * const emojiNodeView = new EmojiNodeView(node, { intl, api });
41
+ */
17
42
  constructor(node: PMNode, { intl, api }: Params);
18
43
  private updateDom;
19
44
  private static isEmojiRepresentationSupported;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-emoji",
3
- "version": "3.6.6",
3
+ "version": "3.7.0",
4
4
  "description": "Emoji plugin for @atlaskit/editor-core",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -23,23 +23,23 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@atlaskit/adf-schema": "^47.6.0",
26
- "@atlaskit/editor-common": "^105.0.0",
27
- "@atlaskit/editor-plugin-analytics": "^2.2.0",
26
+ "@atlaskit/editor-common": "^105.10.0",
27
+ "@atlaskit/editor-plugin-analytics": "^2.3.0",
28
28
  "@atlaskit/editor-plugin-annotation": "^2.8.0",
29
29
  "@atlaskit/editor-plugin-base": "^3.0.0",
30
- "@atlaskit/editor-plugin-editor-viewmode": "^3.1.0",
30
+ "@atlaskit/editor-plugin-editor-viewmode": "^4.0.0",
31
31
  "@atlaskit/editor-plugin-metrics": "^3.4.0",
32
- "@atlaskit/editor-plugin-type-ahead": "^2.6.0",
32
+ "@atlaskit/editor-plugin-type-ahead": "^2.7.0",
33
33
  "@atlaskit/editor-prosemirror": "7.0.0",
34
34
  "@atlaskit/editor-shared-styles": "^3.4.0",
35
- "@atlaskit/emoji": "^69.1.0",
36
- "@atlaskit/icon": "^26.0.0",
35
+ "@atlaskit/emoji": "^69.2.0",
36
+ "@atlaskit/icon": "^26.3.0",
37
37
  "@atlaskit/node-data-provider": "^4.1.0",
38
38
  "@atlaskit/platform-feature-flags": "^1.1.0",
39
39
  "@atlaskit/prosemirror-input-rules": "^3.3.0",
40
40
  "@atlaskit/theme": "^18.0.0",
41
- "@atlaskit/tmp-editor-statsig": "^4.19.0",
42
- "@atlaskit/tokens": "^4.8.0",
41
+ "@atlaskit/tmp-editor-statsig": "^5.0.0",
42
+ "@atlaskit/tokens": "^4.9.0",
43
43
  "@babel/runtime": "^7.0.0",
44
44
  "@emotion/react": "^11.7.1",
45
45
  "react-intl-next": "npm:react-intl@^5.18.1",