@atlaskit/editor-plugin-emoji
Version:
Emoji plugin for @atlaskit/editor-core
234 lines (233 loc) • 10.5 kB
JavaScript
"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;