UNPKG

@atlaskit/editor-common

Version:

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

153 lines (142 loc) 5.68 kB
// File has been copied to packages/editor/editor-plugin-ai/src/provider/markdown-transformer/md/linkify-md-plugin.ts // If changes are made to this file, please make the same update in the linked file. // See https://product-fabric.atlassian.net/browse/ED-3097 for why this file exists. import LinkifyIt from 'linkify-it'; import { linkifyMatch } from '@atlaskit/adf-schema'; import { findFilepaths, isLinkInMatches, shouldAutoLinkifyMatch } from '../../utils'; // modified version of the original markdown-it Linkify plugin // https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any var arrayReplaceAt = function arrayReplaceAt(src, pos, newElements) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)); }; var isLinkOpen = function isLinkOpen(str) { // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp return /^<a[>\s]/i.test(str); }; var isLinkClose = function isLinkClose(str) { // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp return /^<\/a\s*>/i.test(str); }; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any var linkify = function linkify(state) { var blockTokens = state.tokens; var linkify = new LinkifyIt(); for (var j = 0, l = blockTokens.length; j < l; j++) { if (blockTokens[j].type !== 'inline' || !linkify.pretest(blockTokens[j].content)) { continue; } var tokens = blockTokens[j].children; var htmlLinkLevel = 0; // We scan from the end, to keep position when new tags added. // Use reversed logic in links start/end match for (var i = tokens.length - 1; i >= 0; i--) { var currentToken = tokens[i]; // Skip content of markdown links if (currentToken.type === 'link_close') { i--; while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') { i--; } continue; } // Skip content of html tag links if (currentToken.type === 'html_inline') { if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) { htmlLinkLevel--; } if (isLinkClose(currentToken.content)) { htmlLinkLevel++; } } if (htmlLinkLevel > 0) { continue; } if (currentToken.type === 'text' && linkify.test(currentToken.content)) { var text = currentToken.content; var links = linkifyMatch(text); if (!links.length) { links = linkify.match(text) || []; } links = links.filter(function (link) { return shouldAutoLinkifyMatch(link); }); // Now split string to nodes var nodes = []; var level = currentToken.level; var lastPos = 0; var filepaths = findFilepaths(text); for (var ln = 0; ln < links.length; ln++) { if (isLinkInMatches(links[ln].index, filepaths)) { continue; } var url = links[ln].url; var fullUrl = state.md.normalizeLink(url); if (!state.md.validateLink(fullUrl)) { continue; } var urlText = links[ln].text; // Linkifier might send raw hostnames like "example.com", where url // starts with domain name. So we prepend http:// in those cases, // and remove it afterwards. // if (!links[ln].schema) { // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, ''); // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) { // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, ''); } else { urlText = state.md.normalizeLinkText(urlText); } var pos = links[ln].index; if (pos > lastPos) { var _token = new state.Token('text', '', 0); _token.content = text.slice(lastPos, pos); _token.level = level; nodes.push(_token); } var token = new state.Token('link_open', 'a', 1); token.attrs = [['href', fullUrl]]; token.level = level++; token.markup = 'linkify'; token.info = 'auto'; nodes.push(token); token = new state.Token('text', '', 0); token.content = urlText; token.level = level; nodes.push(token); token = new state.Token('link_close', 'a', -1); token.level = --level; token.markup = 'linkify'; token.info = 'auto'; nodes.push(token); lastPos = links[ln].lastIndex; } if (lastPos < text.length) { var _token2 = new state.Token('text', '', 0); _token2.content = text.slice(lastPos); _token2.level = level; nodes.push(_token2); } // replace current node blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); } } } }; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any export default (function (md) { return md.core.ruler.push('custom-linkify', linkify); });