UNPKG

@atlaskit/editor-plugin-text-formatting

Version:

Text-formatting plugin for @atlaskit/editor-core

302 lines (293 loc) 13.6 kB
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;