UNPKG

@atlaskit/editor-plugin-emoji

Version:

Emoji plugin for @atlaskit/editor-core

234 lines (233 loc) 10.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; exports.inputRulePlugin = inputRulePlugin; var _readOnlyError2 = _interopRequireDefault(require("@babel/runtime/helpers/readOnlyError")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _get2 = _interopRequireDefault(require("@babel/runtime/helpers/get")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _analytics = require("@atlaskit/editor-common/analytics"); var _safePlugin = require("@atlaskit/editor-common/safe-plugin"); var _utils = require("@atlaskit/editor-common/utils"); var _prosemirrorInputRules = require("@atlaskit/prosemirror-input-rules"); function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } function _superPropGet(t, o, e, r) { var p = (0, _get2.default)((0, _getPrototypeOf2.default)(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; } var matcher; function inputRulePlugin(schema, editorAnalyticsAPI, pluginInjectionApi, disableAutoformat) { if (disableAutoformat) { return; } if (schema.nodes.emoji) { initMatcher(pluginInjectionApi); var asciiEmojiRule = (0, _utils.createRule)(AsciiEmojiMatcher.REGEX, inputRuleHandler(editorAnalyticsAPI)); return new _safePlugin.SafePlugin((0, _prosemirrorInputRules.createPlugin)('emoji', [asciiEmojiRule])); } return; } function initMatcher(pluginInjectionApi) { pluginInjectionApi === null || pluginInjectionApi === void 0 || pluginInjectionApi.emoji.sharedState.onChange(function (_ref) { var nextSharedState = _ref.nextSharedState; var emojiProvider = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.emojiProvider; if (emojiProvider) { emojiProvider.getAsciiMap().then(function (map) { matcher = new RecordingAsciiEmojiMatcher(emojiProvider, map); }); } }); } var inputRuleHandler = function inputRuleHandler(editorAnalyticsAPI) { return function (state, matchParts, start, end) { if (!matcher) { return null; } var match = matcher.match(matchParts); if (match) { var transactionCreator = new AsciiEmojiTransactionCreator(state, match, start, end, editorAnalyticsAPI); return transactionCreator.create(); } return null; }; }; var REGEX_LEADING_CAPTURE_INDEX = 1; var REGEX_EMOJI_LEADING_PARENTHESES = 2; var REGEX_EMOJI_ASCII_CAPTURE_INDEX = 3; var REGEX_TRAILING_CAPTURE_INDEX = 4; var getLeadingString = function getLeadingString(match) { var withParenthesis = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; return match[REGEX_LEADING_CAPTURE_INDEX] + (withParenthesis ? match[REGEX_EMOJI_LEADING_PARENTHESES] : ''); }; var getLeadingStringWithoutParentheses = function getLeadingStringWithoutParentheses(match) { return getLeadingString(match, false); }; var getAscii = function getAscii(match) { var withParentheses = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; return (withParentheses ? match[REGEX_EMOJI_LEADING_PARENTHESES] : '') + match[REGEX_EMOJI_ASCII_CAPTURE_INDEX].trim(); }; var getAsciiWithParentheses = function getAsciiWithParentheses(matchParts) { return getAscii(matchParts, true); }; var getTrailingString = function getTrailingString(match) { return match[REGEX_TRAILING_CAPTURE_INDEX] || ''; }; var AsciiEmojiMatcher = /*#__PURE__*/function () { function AsciiEmojiMatcher(asciiToEmojiMap) { (0, _classCallCheck2.default)(this, AsciiEmojiMatcher); this.asciiToEmojiMap = asciiToEmojiMap; } return (0, _createClass2.default)(AsciiEmojiMatcher, [{ key: "match", value: function match(matchParts) { return this.getAsciiEmojiMatch(getLeadingStringWithoutParentheses(matchParts), getAsciiWithParentheses(matchParts), getTrailingString(matchParts)) || this.getAsciiEmojiMatch(getLeadingString(matchParts), getAscii(matchParts), getTrailingString(matchParts)); } }, { key: "getAsciiEmojiMatch", value: function getAsciiEmojiMatch(leading, ascii, trailing) { var emoji = this.asciiToEmojiMap.get(ascii); return emoji ? { emoji: emoji, leadingString: leading, trailingString: trailing } : undefined; } }]); }(); /** * A matcher that will record ascii matches as usages of the matched emoji. */ /** * This regex matches 2 scenarios: * 1. an emoticon starting with a colon character (e.g. :D => 😃) * 2. an emoticon not starting with a colon character (e.g. 8-D => 😎) * * Explanation (${leafNodeReplacementCharacter} is replaced with character \ufffc) * * 1st Capturing Group ((?:^|[\s\ufffc])(?:\(*?)) * Non-capturing group (?:^|[\s\ufffc]) * 1st Alternative ^ * ^ asserts position at start of the string * 2nd Alternative [\s\ufffc] * matches a single character present in [\s\ufffc] * Non-capturing group (?:\(*?) * matches the character ( literally between zero and unlimited times, as few times as possible, expanding as needed (lazy) * 2nd Capturing Group (\(?) * matches a single ( if present * 3rd Capturing Group ([^:\s\ufffc\(]\S{1,3}|:\S{1,3}( )) * 1st Alternative [^:\s\ufffc\(]\S{1,3} * matches a single character not present in [^:\s\ufffc\(] between 1 and 3 times, as many times as possible, giving back as needed (greedy) * 2nd Alternative :\S{1,3}( ) * : matches the character : literally * \S{1,3} matches any non-whitespace character between 1 and 3 times, as many times as possible, giving back as needed (greedy) * 4th Capturing Group ( ) * * See https://regex101.com/r/HRS9O2/4 */ // New behavior: All emoticons require whitespace after them // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp (0, _defineProperty2.default)(AsciiEmojiMatcher, "REGEX", new RegExp("((?:^|[\\s".concat(_prosemirrorInputRules.leafNodeReplacementCharacter, "])(?:\\(*?))(\\(?)([^:\\s").concat(_prosemirrorInputRules.leafNodeReplacementCharacter, "\\(]\\S{1,3}|:\\S{1,3})([\\s\\t\\n])$"))); var RecordingAsciiEmojiMatcher = /*#__PURE__*/function (_AsciiEmojiMatcher2) { function RecordingAsciiEmojiMatcher(emojiProvider, asciiToEmojiMap) { var _this; (0, _classCallCheck2.default)(this, RecordingAsciiEmojiMatcher); _this = _callSuper(this, RecordingAsciiEmojiMatcher, [asciiToEmojiMap]); _this.emojiProvider = emojiProvider; return _this; } (0, _inherits2.default)(RecordingAsciiEmojiMatcher, _AsciiEmojiMatcher2); return (0, _createClass2.default)(RecordingAsciiEmojiMatcher, [{ key: "match", value: function match(matchParts) { var match = _superPropGet(RecordingAsciiEmojiMatcher, "match", this, 3)([matchParts]); if (match && this.emojiProvider.recordSelection) { this.emojiProvider.recordSelection(match.emoji); } return match; } }]); }(AsciiEmojiMatcher); var AsciiEmojiTransactionCreator = /*#__PURE__*/function () { function AsciiEmojiTransactionCreator(state, match, start, end, editorAnalyticsAPI) { (0, _classCallCheck2.default)(this, AsciiEmojiTransactionCreator); this.state = state; this.match = match; this.start = start; this.end = end; this.editorAnalyticsAPI = editorAnalyticsAPI; } return (0, _createClass2.default)(AsciiEmojiTransactionCreator, [{ key: "create", value: function create() { var _this$editorAnalytics; var tr = this.state.tr.replaceWith(this.from, this.to, this.createNodes()); (_this$editorAnalytics = this.editorAnalyticsAPI) === null || _this$editorAnalytics === void 0 || _this$editorAnalytics.attachAnalyticsEvent({ action: _analytics.ACTION.INSERTED, actionSubject: _analytics.ACTION_SUBJECT.DOCUMENT, actionSubjectId: _analytics.ACTION_SUBJECT_ID.EMOJI, attributes: { inputMethod: _analytics.INPUT_METHOD.ASCII }, eventType: _analytics.EVENT_TYPE.TRACK })(tr); return tr; } }, { key: "from", get: function get() { return this.start + this.match.leadingString.length; } }, { key: "to", get: function get() { return this.end; } }, { key: "createNodes", value: function createNodes() { var nodes = [this.createEmojiNode()]; if (this.trailingTextNodeRequired()) { nodes.push(this.createTrailingTextNode()); } return nodes; } }, { key: "createEmojiNode", value: function createEmojiNode() { var emojiTypeNode = this.state.schema.nodes.emoji; return emojiTypeNode.create(this.getEmojiNodeAttrs()); } }, { key: "getEmojiNodeAttrs", value: function getEmojiNodeAttrs() { var emoji = this.match.emoji; return { id: emoji.id, shortName: emoji.shortName, text: emoji.fallback || emoji.shortName }; } }, { key: "trailingTextNodeRequired", value: function trailingTextNodeRequired() { return this.match.trailingString.length > 0; } }, { key: "createTrailingTextNode", value: function createTrailingTextNode() { return this.state.schema.text(this.match.trailingString); } }]); }(); var plugins = function plugins(schema, providerFactory, featureFlags, editorAnalyticsAPI, pluginInjectionApi) { return [inputRulePlugin(schema, editorAnalyticsAPI, pluginInjectionApi)].filter(function (plugin) { return !!plugin; }); }; var _default = exports.default = plugins;