@atlaskit/editor-plugin-emoji
Version:
Emoji plugin for @atlaskit/editor-core
475 lines (465 loc) • 30.2 kB
JavaScript
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _regeneratorRuntime from "@babel/runtime/regenerator";
import { bind } from 'bind-event-listener';
import isEqual from 'lodash/isEqual';
import uniqueId from 'lodash/uniqueId';
import { getDocument } from '@atlaskit/browser-apis';
import { isSSR } from '@atlaskit/editor-common/core-utils';
import { messages, EmojiSharedCssClassName, defaultEmojiHeight } from '@atlaskit/editor-common/emoji';
import { logException } from '@atlaskit/editor-common/monitoring';
import { VanillaTooltip } from '@atlaskit/editor-common/vanilla-tooltip';
import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
import { emojiIdToEmoji } from '@atlaskit/emoji/emoji-id-to-emoji';
import { fg } from '@atlaskit/platform-feature-flags';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { emojiToDom } from './emojiNodeSpec';
var SINGLE_EMOJI_REGEX =
// Regular expression to match a single emoji character
// @ts-ignore - TS1501 TypeScript 5.9.2 upgrade
/^((?:[\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-\uDED8\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE70-\uDE7C\uDE80-\uDE8A\uDE8E-\uDEC6\uDEC8\uDECD-\uDEDC\uDEDF-\uDEEA\uDEEF-\uDEF8])(?:(?:\uD83C[\uDFFB-\uDFFF]))?|(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDC2C-\uDC2F\uDC94-\uDC9F\uDCAF\uDCB0\uDCC0\uDCCF\uDCD0\uDCF6-\uDCFF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAE-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDE5F\uDE66-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFA]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDEE5\uDEE9\uDEEB-\uDEF0\uDEF3-\uDEFF\uDFDA-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE\uDCAF\uDCBC-\uDCBF\uDCC2-\uDCCF\uDCD9-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE58-\uDE5F\uDE6E-\uDEFF]|\uD83F[\uDC00-\uDFFD])\uFE0F(?:(?:\uD83C[\uDFFB-\uDFFF]))?(?:\u200D(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDC2C-\uDC2F\uDC94-\uDC9F\uDCAF\uDCB0\uDCC0\uDCCF\uDCD0\uDCF6-\uDCFF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAE-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDE5F\uDE66-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFA]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDEE5\uDEE9\uDEEB-\uDEF0\uDEF3-\uDEFF\uDFDA-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE\uDCAF\uDCBC-\uDCBF\uDCC2-\uDCCF\uDCD9-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE58-\uDE5F\uDE6E-\uDEFF]|\uD83F[\uDC00-\uDFFD])\uFE0F?(?:(?:\uD83C[\uDFFB-\uDFFF]))?)*|(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDC2C-\uDC2F\uDC94-\uDC9F\uDCAF\uDCB0\uDCC0\uDCCF\uDCD0\uDCF6-\uDCFF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAE-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDE5F\uDE66-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFA]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDEE5\uDEE9\uDEEB-\uDEF0\uDEF3-\uDEFF\uDFDA-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE\uDCAF\uDCBC-\uDCBF\uDCC2-\uDCCF\uDCD9-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE58-\uDE5F\uDE6E-\uDEFF]|\uD83F[\uDC00-\uDFFD])\uFE0F?(?:(?:\uD83C[\uDFFB-\uDFFF]))?(?:\u200D(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDC2C-\uDC2F\uDC94-\uDC9F\uDCAF\uDCB0\uDCC0\uDCCF\uDCD0\uDCF6-\uDCFF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAE-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDE5F\uDE66-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFA]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDEE5\uDEE9\uDEEB-\uDEF0\uDEF3-\uDEFF\uDFDA-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE\uDCAF\uDCBC-\uDCBF\uDCC2-\uDCCF\uDCD9-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE58-\uDE5F\uDE6E-\uDEFF]|\uD83F[\uDC00-\uDFFD])\uFE0F?(?:(?:\uD83C[\uDFFB-\uDFFF]))?)+|(?:\uD83C[\uDDE6-\uDDFF])(?:\uD83C[\uDDE6-\uDDFF]))$/;
/**
* Check if we can nicely fallback to the nodes text
*
* @param fallbackText string of the nodes fallback text
*
* @example
* isSingleEmoji('😀') // true
*/
export function isSingleEmoji(fallbackText) {
return SINGLE_EMOJI_REGEX.test(fallbackText);
}
/**
* Emoji node view for renderering emoji nodes
*/
var EMOJI_TOOLTIP_CLASS = 'emoji-tooltip-editor';
export var EmojiNodeView = /*#__PURE__*/function () {
/**
* Prosemirror node view for rendering emoji nodes. This class is responsible for
* rendering emoji nodes in the editor, handling updates, and managing fallback rendering.
*
* @param node - The ProseMirror node representing the emoji.
* @param extraProps - An object containing additional parameters.
* @param extraProps.intl - The internationalization object for formatting messages.
* @param extraProps.api - The editor API for accessing shared state and connectivity features.
* @param extraProps.emojiNodeDataProvider - (Optional) A provider for fetching emoji data.
*
* @example
* const emojiNodeView = new EmojiNodeView(node, { intl, api, emojiNodeDataProvider });
*/
function EmojiNodeView(node, _ref) {
var _this = this;
var intl = _ref.intl,
api = _ref.api,
emojiNodeDataProvider = _ref.emojiNodeDataProvider;
_classCallCheck(this, EmojiNodeView);
_defineProperty(this, "renderingFallback", false);
_defineProperty(this, "destroy", function () {
if (_this.tooltipInstance || _this.tooltipTarget || _this.destroyLazyTooltipListeners) {
_this.destroyTooltip();
}
});
this.node = node;
this.intl = intl;
var _DOMSerializer$render = DOMSerializer.renderSpec(document, emojiToDom(this.node)),
dom = _DOMSerializer$render.dom;
this.dom = dom;
this.domElement = this.isHTMLElement(dom) ? dom : undefined;
if (emojiNodeDataProvider) {
var previousEmojiDescription;
emojiNodeDataProvider.getData(node, function (payload) {
if (payload.error) {
EmojiNodeView.logError(payload.error);
_this.renderFallback();
return;
}
var optionalEmojiDescription = payload.data;
if (!optionalEmojiDescription) {
_this.renderFallback();
return;
}
var emojiRepresentation = optionalEmojiDescription === null || optionalEmojiDescription === void 0 ? void 0 : optionalEmojiDescription.representation;
if (!EmojiNodeView.isEmojiRepresentationSupported(emojiRepresentation)) {
_this.renderFallback();
return;
}
if (isEqual(previousEmojiDescription, optionalEmojiDescription)) {
// Do not re-render if the emoji description is the same as before
return;
}
previousEmojiDescription = optionalEmojiDescription;
_this.renderEmoji(optionalEmojiDescription, emojiRepresentation);
});
} else {
var _api$emoji, _sharedState$currentS, _api$connectivity;
if (isSSR()) {
// The provider doesn't work in SSR, and we don't want to render fallback in SSR,
// that's why we don't need to continue node rendering.
// In SSR we want to show a placeholder, that `emojiToDom()` returns.
return;
}
// We use the `emojiProvider` from the shared state
// because it supports the `emojiProvider` prop in the `ComposableEditor` options
// as well as the `emojiProvider` in the `EmojiPlugin` options.
var sharedState = api === null || api === void 0 || (_api$emoji = api.emoji) === null || _api$emoji === void 0 ? void 0 : _api$emoji.sharedState;
if (!sharedState) {
return;
}
var emojiProvider = (_sharedState$currentS = sharedState.currentState()) === null || _sharedState$currentS === void 0 ? void 0 : _sharedState$currentS.emojiProvider;
if (emojiProvider) {
void this.updateDom(emojiProvider);
}
var unsubscribe = sharedState.onChange(function (_ref2) {
var nextSharedState = _ref2.nextSharedState;
if (emojiProvider === (nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.emojiProvider)) {
// Do not update if the provider is the same
return;
}
emojiProvider = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.emojiProvider;
void _this.updateDom(emojiProvider);
});
// Refresh emojis if we go back online
var subscribeToConnection = api === null || api === void 0 || (_api$connectivity = api.connectivity) === null || _api$connectivity === void 0 ? void 0 : _api$connectivity.sharedState.onChange(function (_ref3) {
var prevSharedState = _ref3.prevSharedState,
nextSharedState = _ref3.nextSharedState;
if (isOfflineMode(prevSharedState === null || prevSharedState === void 0 ? void 0 : prevSharedState.mode) && (nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.mode) === 'online' && _this.renderingFallback && editorExperiment('platform_editor_offline_editing_web', true)) {
var _sharedState$currentS2;
_this.updateDom((_sharedState$currentS2 = sharedState.currentState()) === null || _sharedState$currentS2 === void 0 ? void 0 : _sharedState$currentS2.emojiProvider);
}
});
this.destroy = function () {
unsubscribe();
subscribeToConnection === null || subscribeToConnection === void 0 || subscribeToConnection();
_this.destroyTooltip();
};
}
}
/** Type guard to check if a Node is an HTMLElement in a safe way. */
return _createClass(EmojiNodeView, [{
key: "destroyTooltip",
value: function destroyTooltip() {
var _this$destroyLazyTool, _this$tooltipInstance, _this$tooltipTarget, _this$tooltipTarget2;
(_this$destroyLazyTool = this.destroyLazyTooltipListeners) === null || _this$destroyLazyTool === void 0 || _this$destroyLazyTool.call(this);
(_this$tooltipInstance = this.tooltipInstance) === null || _this$tooltipInstance === void 0 || _this$tooltipInstance.destroy();
(_this$tooltipTarget = this.tooltipTarget) === null || _this$tooltipTarget === void 0 || _this$tooltipTarget.removeAttribute('popovertarget');
(_this$tooltipTarget2 = this.tooltipTarget) === null || _this$tooltipTarget2 === void 0 || _this$tooltipTarget2.removeAttribute('aria-describedby');
this.destroyLazyTooltipListeners = undefined;
this.tooltipInstance = undefined;
this.tooltipTarget = undefined;
}
}, {
key: "isHTMLElement",
value: function isHTMLElement(element) {
if (element === null) {
return false;
}
// In SSR `HTMLElement` is not defined, so we need to use duck typing here
return 'innerHTML' in element && 'style' in element && 'classList' in element;
}
}, {
key: "updateDom",
value: function () {
var _updateDom = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(emojiProvider) {
var _this$node$attrs, shortName, id, fallback, emojiDescription, unicodeEmoji, emojiRepresentation, _t;
return _regeneratorRuntime.wrap(function (_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.prev = 0;
_this$node$attrs = this.node.attrs, shortName = _this$node$attrs.shortName, id = _this$node$attrs.id, fallback = _this$node$attrs.text;
_context.next = 1;
return emojiProvider === null || emojiProvider === void 0 ? void 0 : emojiProvider.fetchByEmojiId({
id: id,
shortName: shortName,
fallback: fallback
}, true);
case 1:
emojiDescription = _context.sent;
if (emojiDescription) {
_context.next = 2;
break;
}
EmojiNodeView.logError(new Error('Emoji description is not loaded'));
this.renderFallback();
return _context.abrupt("return");
case 2:
unicodeEmoji = id && expValEqualsNoExposure('platform_use_unicode_emojis', 'isEnabled', true) ? emojiIdToEmoji(id) : undefined;
emojiRepresentation = unicodeEmoji ? {
unicodeEmoji: unicodeEmoji
} : emojiDescription === null || emojiDescription === void 0 ? void 0 : emojiDescription.representation;
if (EmojiNodeView.isEmojiRepresentationSupported(emojiRepresentation)) {
_context.next = 3;
break;
}
EmojiNodeView.logError(new Error('Emoji representation is not supported'));
this.renderFallback();
return _context.abrupt("return");
case 3:
this.renderEmoji(emojiDescription, emojiRepresentation);
_context.next = 5;
break;
case 4:
_context.prev = 4;
_t = _context["catch"](0);
EmojiNodeView.logError(_t instanceof Error ? _t : new Error('Unknown error on EmojiNodeView updateDom'));
this.renderFallback();
case 5:
case "end":
return _context.stop();
}
}, _callee, this, [[0, 4]]);
}));
function updateDom(_x) {
return _updateDom.apply(this, arguments);
}
return updateDom;
}()
}, {
key: "cleanUpAndRenderCommonAttributes",
value:
// Pay attention, this method should be called only when the emoji provider returns
// emoji data to prevent rendering empty emoji during loading.
function cleanUpAndRenderCommonAttributes() {
this.destroyTooltip();
// Clean up the DOM before rendering the new emoji
if (this.domElement) {
this.domElement.innerHTML = '';
this.domElement.style.cssText = '';
this.domElement.classList.remove(EmojiSharedCssClassName.EMOJI_PLACEHOLDER);
this.domElement.removeAttribute('aria-label'); // The label is set in the renderEmoji method
this.domElement.removeAttribute('aria-busy');
}
}
/**
* Lazily creates a VanillaTooltip on the given element showing the emoji shortName.
* Gated behind the platform_editor_emoji_hover_show_tooltip experiment.
* When the tooltip is active, the native `title` attribute is removed to avoid
* showing both the browser tooltip and the custom tooltip.
*/
}, {
key: "createTooltip",
value: function createTooltip(element, shortName) {
var _this2 = this;
if (!expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true)) {
return;
}
if (isSSR()) {
return;
}
var initTooltip = function initTooltip(event) {
var _this2$destroyLazyToo;
(_this2$destroyLazyToo = _this2.destroyLazyTooltipListeners) === null || _this2$destroyLazyToo === void 0 || _this2$destroyLazyToo.call(_this2);
_this2.destroyLazyTooltipListeners = undefined;
var tooltipId = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? "emoji-tooltip-".concat(crypto.randomUUID()) : uniqueId('emoji-tooltip-');
try {
var tooltipInstance = new VanillaTooltip(
// VanillaTooltip types expect HTMLButtonElement but works with any HTMLElement
element, shortName, tooltipId, EMOJI_TOOLTIP_CLASS,
// default timeout
300,
// Inline styles are required because the Popover API promotes the tooltip
// to the browser's top layer, where ancestor CSS selectors cannot reach it.
{
boxSizing: 'border-box',
maxWidth: '240px',
backgroundColor: "var(--ds-background-neutral-bold, #292A2E)",
border: 'none',
borderRadius: "var(--ds-radius-small, 3px)",
color: "var(--ds-text-inverse, #FFFFFF)",
font: "var(--ds-font-body-small, normal 400 12px/16px \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
fontFamily: "var(--ds-font-family-body, \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
insetBlockStart: "var(--ds-space-0, 0px)",
insetInlineStart: "var(--ds-space-0, 0px)",
overflowWrap: 'break-word',
paddingBlockStart: "var(--ds-space-050, 4px)",
paddingBlockEnd: "var(--ds-space-050, 4px)",
paddingInlineEnd: "var(--ds-space-075, 6px)",
paddingInlineStart: "var(--ds-space-075, 6px)",
whiteSpace: 'normal'
});
_this2.tooltipInstance = tooltipInstance;
_this2.tooltipTarget = element;
element.removeAttribute('title');
element.dispatchEvent(new Event(event.type));
} catch (error) {
element.removeAttribute('popovertarget');
element.removeAttribute('aria-describedby');
EmojiNodeView.logError(error instanceof Error ? error : new Error(String(error)));
}
};
var unbindMouseEnter = bind(element, {
type: 'mouseenter',
listener: initTooltip,
options: {
once: true
}
});
var unbindFocus = bind(element, {
type: 'focus',
listener: initTooltip,
options: {
once: true
}
});
this.destroyLazyTooltipListeners = function () {
unbindMouseEnter();
unbindFocus();
};
}
}, {
key: "renderFallback",
value: function renderFallback() {
this.renderingFallback = true;
this.cleanUpAndRenderCommonAttributes();
var doc = getDocument();
if (!doc) {
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
doc = document;
}
var fallbackElement = doc.createElement('span');
var _this$node$attrs2 = this.node.attrs,
text = _this$node$attrs2.text,
shortName = _this$node$attrs2.shortName;
// When the gate is enabled and the emoji is "custom" (i.e. its fallback
// text is not a single standard Unicode emoji), render the Unicode
// Replacement Character (U+FFFD) instead of the shortName text. Standard
// emojis continue to fall back to their Unicode text representation.
var fallbackText = text || shortName;
var useReplacementChar = fg('platform_editor_custom_emoji_unicode_fallback') && !isSingleEmoji(fallbackText);
var renderedFallbackText = useReplacementChar ? "\uFFFD" : fallbackText;
fallbackElement.innerText = renderedFallbackText;
fallbackElement.setAttribute('role', 'img');
fallbackElement.setAttribute('title', shortName);
fallbackElement.setAttribute('aria-label', shortName);
fallbackElement.setAttribute('data-testid', "fallback-emoji-".concat(shortName));
fallbackElement.setAttribute('data-emoji-type', 'fallback');
this.dom.appendChild(fallbackElement);
this.createTooltip(fallbackElement, shortName);
}
}, {
key: "renderEmoji",
value: function renderEmoji(description, representation) {
this.renderingFallback = false;
this.cleanUpAndRenderCommonAttributes();
var emojiType = 'unicodeEmoji' in representation ? 'unicode' : 'sprite' in representation ? 'sprite' : 'image';
var doc = getDocument();
if (!doc) {
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
doc = document;
}
// Add wrapper for the emoji
var containerElement = doc.createElement('span');
containerElement.setAttribute('role', 'img');
containerElement.setAttribute('title', description.shortName);
containerElement.classList.add(EmojiSharedCssClassName.EMOJI_CONTAINER);
containerElement.setAttribute('data-testid', "".concat(emojiType, "-emoji-").concat(description.shortName));
containerElement.setAttribute('data-emoji-type', emojiType);
containerElement.setAttribute('aria-label', "".concat(this.intl.formatMessage(messages.emojiNodeLabel), " ").concat(description.shortName));
var emojiElement = 'unicodeEmoji' in representation ? this.createUnicodeEmojiElement(representation.unicodeEmoji) : 'sprite' in representation ? this.createSpriteEmojiElement(representation) : this.createImageEmojiElement(description, representation);
containerElement.appendChild(emojiElement);
this.dom.appendChild(containerElement);
if (EmojiNodeView.shouldRecordUnicodeEmojiExposure(description, representation)) {
expValEquals('platform_use_unicode_emojis', 'isEnabled', true);
}
this.createTooltip(containerElement, description.shortName);
}
}, {
key: "createUnicodeEmojiElement",
value: function createUnicodeEmojiElement(emoji) {
var doc = getDocument();
if (!doc) {
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
doc = document;
}
var spanElement = doc.createElement('span');
spanElement.classList.add(EmojiSharedCssClassName.EMOJI_UNICODE);
spanElement.textContent = emoji;
spanElement.style.display = 'inline-flex';
spanElement.style.fontSize = "var(--emoji-common-unicode-size, ".concat(defaultEmojiHeight, "px)");
spanElement.style.alignItems = 'center';
spanElement.style.aspectRatio = '1/1';
spanElement.style.lineHeight = '1em';
spanElement.style.margin = '-1px 0';
spanElement.style.verticalAlign = 'middle'; // to keep vertical alignment consistent with images
return spanElement;
}
}, {
key: "createSpriteEmojiElement",
value: function createSpriteEmojiElement(representation) {
var doc = getDocument();
if (!doc) {
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
doc = document;
}
var spriteElement = doc.createElement('span');
spriteElement.classList.add(EmojiSharedCssClassName.EMOJI_SPRITE);
var sprite = representation.sprite;
var xPositionInPercent = 100 / (sprite.column - 1) * representation.xIndex;
var yPositionInPercent = 100 / (sprite.row - 1) * representation.yIndex;
spriteElement.style.backgroundImage = "url(".concat(sprite.url, ")");
spriteElement.style.backgroundPosition = "".concat(xPositionInPercent, "% ").concat(yPositionInPercent, "%");
spriteElement.style.backgroundSize = "".concat(sprite.column * 100, "% ").concat(sprite.row * 100, "%");
spriteElement.style.minWidth = "".concat(defaultEmojiHeight, "px");
spriteElement.style.minHeight = "".concat(defaultEmojiHeight, "px");
if (!expValEquals('platform_editor_lovability_emoji_scaling', 'isEnabled', true)) {
spriteElement.style.width = "".concat(defaultEmojiHeight, "px");
spriteElement.style.height = "".concat(defaultEmojiHeight, "px");
}
return spriteElement;
}
}, {
key: "createImageEmojiElement",
value: function createImageEmojiElement(emojiDescription, representation) {
var _this3 = this;
var doc = getDocument();
if (!doc) {
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
doc = document;
}
var imageElement = doc.createElement('img');
imageElement.classList.add(EmojiSharedCssClassName.EMOJI_IMAGE);
imageElement.src = 'imagePath' in representation ? representation.imagePath : representation.mediaPath;
imageElement.loading = 'lazy';
imageElement.alt = emojiDescription.name || emojiDescription.shortName;
imageElement.style.minWidth = "".concat(defaultEmojiHeight, "px");
imageElement.style.objectFit = 'contain';
imageElement.height = defaultEmojiHeight;
imageElement.onerror = function () {
if (editorExperiment('platform_editor_offline_editing_web', true)) {
// If there's an error (ie. offline) render the ascii fallback if possible, otherwise
// mark the node to refresh when returning online.
// Create a check that confirms if this.node.attrs.text if an ascii emoji
if (isSingleEmoji(_this3.node.attrs.text)) {
_this3.renderFallback();
} else {
_this3.renderingFallback = true;
}
} else {
_this3.renderFallback();
}
};
return imageElement;
}
}], [{
key: "logError",
value: function logError(error) {
void logException(error, {
location: 'editor-plugin-emoji/EmojiNodeView'
});
}
}, {
key: "isEmojiRepresentationSupported",
value: function isEmojiRepresentationSupported(representation) {
return !!representation && ('sprite' in representation || 'imagePath' in representation || 'mediaPath' in representation || 'unicodeEmoji' in representation);
}
}, {
key: "shouldRecordUnicodeEmojiExposure",
value: function shouldRecordUnicodeEmojiExposure(description, representation) {
return description.type === 'STANDARD' || 'unicodeEmoji' in representation;
}
}]);
}();