UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

240 lines (230 loc) • 9.01 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.findFilepaths = exports.canLinkBeCreatedInRange = exports.LinkMatcher = exports.FILEPATH_REGEXP = exports.DONTLINKIFY_REGEXP = void 0; exports.getLinkCreationAnalyticsEvent = getLinkCreationAnalyticsEvent; exports.getLinkDomain = getLinkDomain; exports.isFromCurrentDomain = isFromCurrentDomain; exports.isLinkInMatches = void 0; exports.linkifyContent = linkifyContent; exports.normalizeUrl = normalizeUrl; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _adfSchema = require("@atlaskit/adf-schema"); var _enums = require("../analytics/types/enums"); var _shouldAutoLinkifyTld = require("./should-auto-linkify-tld"); var _slice = require("./slice"); // File has been copied to packages/editor/editor-plugin-ai/src/provider/markdown-transformer/utils/hyperlink.ts // If changes are made to this file, please make the same update in the linked file. /* eslint-disable require-unicode-regexp -- Omit `u` so emitDeclarationOnly build (pre-ES2015 lib) does not error TS1501; patterns are ASCII-only paths and $/{ prefix. */ // Regular expression for a windows filepath in the format <DRIVE LETTER>:\<folder name>\ // Ignored via go/ees005 var FILEPATH_REGEXP = exports.FILEPATH_REGEXP = /([a-zA-Z]:|\\)([^\/:*?<>"|]+\\)?([^\/:*?<>"| ]+(?=\s?))?/gim; // Don't linkify if starts with $ or { // Ignored via go/ees005 var DONTLINKIFY_REGEXP = exports.DONTLINKIFY_REGEXP = /^(\$|\{)/; /* eslint-enable require-unicode-regexp */ /** * Instance of class LinkMatcher are used in autoformatting in place of Regex. * Hence it has been made similar to regex with an exec method. * Extending it directly from class Regex was introducing some issues, thus that has been avoided. */ var LinkMatcher = exports.LinkMatcher = /*#__PURE__*/function () { function LinkMatcher() { (0, _classCallCheck2.default)(this, LinkMatcher); } return (0, _createClass2.default)(LinkMatcher, null, [{ key: "create", value: function create() { var LinkMatcherRegex = /*#__PURE__*/function () { function LinkMatcherRegex() { (0, _classCallCheck2.default)(this, LinkMatcherRegex); } return (0, _createClass2.default)(LinkMatcherRegex, [{ key: "exec", value: function exec(str) { var stringsBySpace = str.slice(0, str.length - 1).split(' '); var lastStringBeforeSpace = stringsBySpace[stringsBySpace.length - 1]; var isLastStringValid = lastStringBeforeSpace.length > 0; if (!str.endsWith(' ') || !isLastStringValid) { return null; } if (DONTLINKIFY_REGEXP.test(lastStringBeforeSpace)) { return null; } var links = _adfSchema.linkify.match(lastStringBeforeSpace); if (!links || links.length === 0) { return null; } var lastMatch = links[links.length - 1]; var lastLink = links[links.length - 1]; lastLink.input = str.substring(lastMatch.index); lastLink.length = lastLink.lastIndex - lastLink.index + 1; lastLink.index = str.lastIndexOf(lastStringBeforeSpace) + lastMatch.index; return lastLink; } }]); }(); return new LinkMatcherRegex(); } }]); }(); /** * Adds protocol to url if needed. */ function normalizeUrl(url) { if (!url) { return ''; } if ((0, _adfSchema.isSafeUrl)(url)) { return url; } return (0, _adfSchema.normalizeUrl)(url); } /** * Linkify content in a slice (eg. after a rich text paste) */ function linkifyContent(schema) { return function (slice) { return (0, _slice.mapSlice)(slice, function (node, parent) { var isAllowedInParent = !parent || parent.type !== schema.nodes.codeBlock; var link = node.type.schema.marks.link; if (link === undefined) { throw new Error('Link not in schema - unable to linkify content'); } if (isAllowedInParent && node.isText && !link.isInSet(node.marks)) { var linkified = []; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var text = node.text; var matches = findLinkMatches(text).filter(_shouldAutoLinkifyTld.shouldAutoLinkifyMatch); var pos = 0; var filepaths = findFilepaths(text); matches.forEach(function (match) { if (isLinkInMatches(match.index, filepaths)) { return; } if (match.index > 0) { linkified.push(node.cut(pos, match.index)); } linkified.push(node.cut(match.index, match.lastIndex).mark(link.create({ href: normalizeUrl(match.url) }).addToSet(node.marks))); pos = match.lastIndex; }); if (pos < text.length) { linkified.push(node.cut(pos)); } return linkified; } return node; }); }; } // eslint-disable-next-line jsdoc/require-jsdoc function getLinkDomain(url) { // Remove protocol and www., if either exists // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp var withoutProtocol = url.toLowerCase().replace(/^(.*):\/\//, ''); // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp var withoutWWW = withoutProtocol.replace(/^(www\.)/, ''); // Remove port, fragment, path, query string // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp return withoutWWW.replace(/[:\/?#](.*)$/, ''); } // eslint-disable-next-line jsdoc/require-jsdoc function isFromCurrentDomain(url) { if (!window || !window.location) { return false; } var currentDomain = window.location.hostname; var linkDomain = getLinkDomain(url); return currentDomain === linkDomain; } /** * Fetch linkify matches from text * @param text Input text from a node * @returns Array of linkify matches. Returns empty array if text is empty or no matches found; */ function findLinkMatches(text) { if (text === '') { return []; } return _adfSchema.linkify.match(text) || []; } var findFilepaths = exports.findFilepaths = function findFilepaths(text) { var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; // Creation of a copy of the RegExp is necessary as lastIndex is stored on it when we run .exec() // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp -- Ignored via go/ees005 var localRegExp = new RegExp(FILEPATH_REGEXP); var match; var matchesList = []; var maxFilepathSize = 260; // Ignored via go/ees005 // eslint-disable-next-line no-cond-assign while ((match = localRegExp.exec(text)) !== null) { var start = match.index + offset; var end = localRegExp.lastIndex + offset; if (end - start > maxFilepathSize) { end = start + maxFilepathSize; } // We don't care about big strings of text that are pretending to be filepaths!! matchesList.push({ startIndex: start, endIndex: end }); } return matchesList; }; var isLinkInMatches = exports.isLinkInMatches = function isLinkInMatches(linkStart, matchesList) { for (var i = 0; i < matchesList.length; i++) { if (linkStart >= matchesList[i].startIndex && linkStart < matchesList[i].endIndex) { return true; } } return false; }; // eslint-disable-next-line jsdoc/require-jsdoc function getLinkCreationAnalyticsEvent(inputMethod, url) { return { action: _enums.ACTION.INSERTED, actionSubject: _enums.ACTION_SUBJECT.DOCUMENT, actionSubjectId: _enums.ACTION_SUBJECT_ID.LINK, attributes: { inputMethod: inputMethod, fromCurrentDomain: isFromCurrentDomain(url) }, eventType: _enums.EVENT_TYPE.TRACK, nonPrivacySafeAttributes: { linkDomain: getLinkDomain(url) } }; } var canLinkBeCreatedInRange = exports.canLinkBeCreatedInRange = function canLinkBeCreatedInRange(from, to) { return function (state) { if (!state.doc.rangeHasMark(from, to, state.schema.marks.link)) { var $from = state.doc.resolve(from); var $to = state.doc.resolve(to); var link = state.schema.marks.link; if ($from.parent === $to.parent && $from.parent.isTextblock) { if ($from.parent.type.allowsMarkType(link)) { var allowed = true; state.doc.nodesBetween(from, to, function (node) { var hasInlineCard = node.type === state.schema.nodes.inlineCard; allowed = allowed && !node.marks.some(function (m) { return m.type.excludes(link); }) && !hasInlineCard; return allowed; }); return allowed; } } } return false; }; };