@atlaskit/editor-plugin-text-formatting
Version:
Text-formatting plugin for @atlaskit/editor-core
302 lines (293 loc) • 13.6 kB
JavaScript
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
import _get from "@babel/runtime/helpers/get";
import _inherits from "@babel/runtime/helpers/inherits";
import _wrapNativeSuper from "@babel/runtime/helpers/wrapNativeSuper";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(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 = _get(_getPrototypeOf(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; }
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
import { createRule, inputRuleWithAnalytics } from '@atlaskit/editor-common/utils';
import { createPlugin, leafNodeReplacementCharacter } from '@atlaskit/prosemirror-input-rules';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
var ValidAutoformatChars = /*#__PURE__*/function (ValidAutoformatChars) {
ValidAutoformatChars["STRONG"] = "__";
ValidAutoformatChars["STRIKE"] = "~~";
ValidAutoformatChars["STRONG_MARKDOWN"] = "**";
ValidAutoformatChars["ITALIC_MARKDOWN"] = "*";
ValidAutoformatChars["ITALIC"] = "_";
ValidAutoformatChars["CODE"] = "`";
return ValidAutoformatChars;
}(ValidAutoformatChars || {});
export var ValidCombinations = _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, ValidAutoformatChars.STRIKE, [
// e.g: _~~lol~~_
ValidAutoformatChars.ITALIC,
// e.g: __~~lol~~__
ValidAutoformatChars.STRONG,
// e.g: **~~lol~~**
ValidAutoformatChars.STRONG_MARKDOWN,
// e.g: *~~lol~~*
ValidAutoformatChars.ITALIC_MARKDOWN]), ValidAutoformatChars.STRONG, [
// e.g: ~~__lol__~~
ValidAutoformatChars.STRIKE,
// e.g: *__lol__*
ValidAutoformatChars.ITALIC_MARKDOWN]), ValidAutoformatChars.STRONG_MARKDOWN, [
// e.g: _**lol**_
ValidAutoformatChars.ITALIC,
// e.g: ~~**lol**~~
ValidAutoformatChars.STRIKE]), ValidAutoformatChars.ITALIC_MARKDOWN, [
// e.g: ~~*lol*~~
ValidAutoformatChars.STRIKE,
// e.g: __*lol*__
ValidAutoformatChars.STRONG]), ValidAutoformatChars.ITALIC, [
// e.g: ~~_lol_~~
ValidAutoformatChars.STRIKE,
// e.g: **_lol_**
ValidAutoformatChars.STRONG_MARKDOWN]), ValidAutoformatChars.CODE, [
// e.g: loko (`some code`
'( ']);
function addMark(markType, _schema, char, api) {
return function (state, _match, start, end) {
var _schema$marks;
var doc = state.doc,
schema = state.schema,
tr = state.tr;
var textPrefix = state.doc.textBetween(start, start + char.length);
// fixes the following case: my `*name` is *
// expected result: should ignore special characters inside "code"
if (textPrefix !== char || schema !== null && schema !== void 0 && (_schema$marks = schema.marks) !== null && _schema$marks !== void 0 && (_schema$marks = _schema$marks.code) !== null && _schema$marks !== void 0 && _schema$marks.isInSet(doc.resolve(start + 1).marks())) {
if (!expValEquals('platform_editor_lovability_inline_code', 'isEnabled', true)) {
return null;
}
// if the prefix is not a character but the suffix is, continue
var suffix = state.doc.textBetween(end - char.length, end);
if (suffix !== char) {
return null;
}
}
// Prevent autoformatting across hardbreaks
var containsHardBreak;
doc.nodesBetween(start, end, function (node) {
if (node.type === schema.nodes.hardBreak) {
containsHardBreak = true;
return false;
}
return !containsHardBreak;
});
if (containsHardBreak) {
return null;
}
// fixes autoformatting in heading nodes: # Heading *bold*
// expected result: should not autoformat *bold*; <h1>Heading *bold*</h1>
var startPosResolved = doc.resolve(start);
var endPosResolved = doc.resolve(end);
if (startPosResolved.sameParent(endPosResolved) && !startPosResolved.parent.type.allowsMarkType(markType)) {
return null;
}
if (markType.name === 'code') {
var _api$base;
api === null || api === void 0 || (_api$base = api.base) === null || _api$base === void 0 || (_api$base = _api$base.actions) === null || _api$base === void 0 || _api$base.resolveMarks(tr.mapping.map(start), tr.mapping.map(end), tr);
}
var mappedStart = tr.mapping.map(start);
var mappedEnd = tr.mapping.map(end);
tr.addMark(mappedStart, mappedEnd, markType.create());
var textSuffix = tr.doc.textBetween(mappedEnd - char.length, mappedEnd);
if (textSuffix === char) {
tr.delete(mappedEnd - char.length, mappedEnd);
}
if (textPrefix === char) {
tr.delete(mappedStart, mappedStart + char.length);
}
return tr.removeStoredMark(markType);
};
}
var ReverseRegexExp = /*#__PURE__*/function (_RegExp) {
function ReverseRegexExp() {
_classCallCheck(this, ReverseRegexExp);
return _callSuper(this, ReverseRegexExp, arguments);
}
_inherits(ReverseRegexExp, _RegExp);
return _createClass(ReverseRegexExp, [{
key: "exec",
value: function exec(str) {
if (!str) {
return null;
}
var reverseStr = _toConsumableArray(str).reverse().join('');
var result = _superPropGet(ReverseRegexExp, "exec", this, 3)([reverseStr]);
if (!result) {
return null;
}
for (var i = 0; i < result.length; i++) {
if (result[i] && typeof result[i] === 'string') {
result[i] = _toConsumableArray(result[i]).reverse().join('');
}
}
if (result.input && typeof result.input === 'string') {
result.input = _toConsumableArray(result.input).reverse().join('');
}
if (result.input && result[0]) {
result.index = result.input.length - result[0].length;
}
return result;
}
}]);
}( /*#__PURE__*/_wrapNativeSuper(RegExp));
var buildRegex = function buildRegex(char) {
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
var escapedChar = char.replace(/(\W)/g, '\\$1');
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp, @atlassian/perf-linting/no-expensive-split-replace -- Ignored via go/ees017 (to be fixed)
var combinations = ValidCombinations[char].map(function (c) {
return c.replace(/(\W)/g, '\\$1');
}).join('|');
// Single X - https://regex101.com/r/McT3yq/14/
// Double X - https://regex101.com/r/pQUgjx/1/
var baseRegex = '^X(?=[^X\\s]).*?[^\\sX]X(?=[\\sOBJECT_REPLACEMENT_CHARACTER]COMBINATIONS|$)'.replace('OBJECT_REPLACEMENT_CHARACTER', leafNodeReplacementCharacter).replace('COMBINATIONS', combinations ? "|".concat(combinations) : '');
var replacedRegex = String.prototype.hasOwnProperty('replaceAll') ?
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
baseRegex.replaceAll('X', escapedChar) :
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
baseRegex.replace(/X/g, escapedChar);
return new ReverseRegexExp(replacedRegex);
};
var buildRegexNew = function buildRegexNew(char) {
var allowsBackwardMatch = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
var escapedChar = char.replace(/(\W)/g, '\\$1');
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp, @atlassian/perf-linting/no-expensive-split-replace -- Ignored via go/ees017 (to be fixed)
var combinations = ValidCombinations[char].map(function (c) {
return c.replace(/(\W)/g, '\\$1');
}).join('|');
// Single X - https://regex101.com/r/McT3yq/14/
// Double X - https://regex101.com/r/pQUgjx/1/
// if backwards matches are allowed, do not prefix the regex with an anchor (^)
var maybeAnchor = allowsBackwardMatch ? '' : '^';
var orCombinations = combinations ? "|".concat(combinations) : '';
var baseRegex = "".concat(maybeAnchor, "X(?=[^X\\s]).*?[^\\sX]X(?=[\\s").concat(leafNodeReplacementCharacter, "]").concat(orCombinations, "|$)");
var replacedRegex = String.prototype.hasOwnProperty('replaceAll') ?
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
baseRegex.replaceAll('X', escapedChar) :
// Ignored via go/ees005
// eslint-disable-next-line require-unicode-regexp
baseRegex.replace(/X/g, escapedChar);
return new ReverseRegexExp(replacedRegex);
};
export var strongRegex1 = buildRegex(ValidAutoformatChars.STRONG);
export var strongRegex2 = buildRegex(ValidAutoformatChars.STRONG_MARKDOWN);
export var italicRegex1 = buildRegex(ValidAutoformatChars.ITALIC);
export var italicRegex2 = buildRegex(ValidAutoformatChars.ITALIC_MARKDOWN);
export var strikeRegex = buildRegex(ValidAutoformatChars.STRIKE);
export var codeRegex = buildRegex(ValidAutoformatChars.CODE);
export var codeRegexWithBackwardMatch = buildRegexNew(ValidAutoformatChars.CODE, true);
/**
* Create input rules for strong mark
*
* @param {Schema} schema
* @returns {InputRuleWrapper[]}
*/
function getStrongInputRules(schema, editorAnalyticsAPI) {
var ruleWithStrongAnalytics = inputRuleWithAnalytics({
action: ACTION.FORMATTED,
actionSubject: ACTION_SUBJECT.TEXT,
actionSubjectId: ACTION_SUBJECT_ID.FORMAT_STRONG,
eventType: EVENT_TYPE.TRACK,
attributes: {
inputMethod: INPUT_METHOD.FORMATTING
}
}, editorAnalyticsAPI);
// **string** or __strong__ should bold the text
var doubleUnderscoreRule = createRule(strongRegex1, addMark(schema.marks.strong, schema, ValidAutoformatChars.STRONG));
var doubleAsterixRule = createRule(strongRegex2, addMark(schema.marks.strong, schema, ValidAutoformatChars.STRONG_MARKDOWN));
return [ruleWithStrongAnalytics(doubleUnderscoreRule), ruleWithStrongAnalytics(doubleAsterixRule)];
}
/**
* Create input rules for em mark
*
* @param {Schema} schema
* @returns {InputRuleWrapper[]}
*/
function getItalicInputRules(schema, editorAnalyticsAPI) {
var ruleWithItalicAnalytics = inputRuleWithAnalytics({
action: ACTION.FORMATTED,
actionSubject: ACTION_SUBJECT.TEXT,
actionSubjectId: ACTION_SUBJECT_ID.FORMAT_ITALIC,
eventType: EVENT_TYPE.TRACK,
attributes: {
inputMethod: INPUT_METHOD.FORMATTING
}
}, editorAnalyticsAPI);
var underscoreRule = createRule(italicRegex1, addMark(schema.marks.em, schema, ValidAutoformatChars.ITALIC));
var asterixRule = createRule(italicRegex2, addMark(schema.marks.em, schema, ValidAutoformatChars.ITALIC_MARKDOWN));
return [ruleWithItalicAnalytics(underscoreRule), ruleWithItalicAnalytics(asterixRule)];
}
/**
* Create input rules for strike mark
*
* @param {Schema} schema
* @returns {InputRuleWrapper[]}
*/
function getStrikeInputRules(schema, editorAnalyticsAPI) {
var ruleWithStrikeAnalytics = inputRuleWithAnalytics({
action: ACTION.FORMATTED,
actionSubject: ACTION_SUBJECT.TEXT,
actionSubjectId: ACTION_SUBJECT_ID.FORMAT_STRIKE,
eventType: EVENT_TYPE.TRACK,
attributes: {
inputMethod: INPUT_METHOD.FORMATTING
}
}, editorAnalyticsAPI);
var doubleTildeRule = createRule(strikeRegex, addMark(schema.marks.strike, schema, ValidAutoformatChars.STRIKE));
return [ruleWithStrikeAnalytics(doubleTildeRule)];
}
/**
* Create input rules for code mark
*
* @param {Schema} schema
* @returns {InputRuleWrapper[]}
*/
function getCodeInputRules(schema, editorAnalyticsAPI, api) {
var ruleWithCodeAnalytics = inputRuleWithAnalytics({
action: ACTION.FORMATTED,
actionSubject: ACTION_SUBJECT.TEXT,
actionSubjectId: ACTION_SUBJECT_ID.FORMAT_CODE,
eventType: EVENT_TYPE.TRACK,
attributes: {
inputMethod: INPUT_METHOD.FORMATTING
}
}, editorAnalyticsAPI);
var allowsBackwardMatch = expValEquals('platform_editor_lovability_inline_code', 'isEnabled', true);
var backTickRule = createRule(allowsBackwardMatch ? codeRegexWithBackwardMatch : codeRegex, addMark(schema.marks.code, schema, ValidAutoformatChars.CODE, api), allowsBackwardMatch);
return [ruleWithCodeAnalytics(backTickRule)];
}
export function inputRulePlugin(schema, editorAnalyticsAPI, api) {
var rules = [];
if (schema.marks.strong) {
rules.push.apply(rules, _toConsumableArray(getStrongInputRules(schema, editorAnalyticsAPI)));
}
if (schema.marks.em) {
rules.push.apply(rules, _toConsumableArray(getItalicInputRules(schema, editorAnalyticsAPI)));
}
if (schema.marks.strike) {
rules.push.apply(rules, _toConsumableArray(getStrikeInputRules(schema, editorAnalyticsAPI)));
}
if (schema.marks.code) {
rules.push.apply(rules, _toConsumableArray(getCodeInputRules(schema, editorAnalyticsAPI, api)));
}
if (rules.length !== 0) {
return new SafePlugin(createPlugin('text-formatting', rules));
}
return;
}
export default inputRulePlugin;