diff options
author | altaf-creator <dev@altafcreator.com> | 2024-05-12 12:14:02 +0700 |
---|---|---|
committer | altaf-creator <dev@altafcreator.com> | 2024-05-12 12:14:02 +0700 |
commit | d607ac12097afb5cb6f398a4e7b5cf4316efedc6 (patch) | |
tree | 6f4bc5b98a6ff3a1c3189f7ef9b570c0481e100d /packages/markdown-it-14.1.0/lib/rules_core/linkify.mjs | |
parent | 7441f212967256ac4c9a93ba0b1f026308a8bfb6 (diff) |
self host
Diffstat (limited to 'packages/markdown-it-14.1.0/lib/rules_core/linkify.mjs')
-rw-r--r-- | packages/markdown-it-14.1.0/lib/rules_core/linkify.mjs | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/packages/markdown-it-14.1.0/lib/rules_core/linkify.mjs b/packages/markdown-it-14.1.0/lib/rules_core/linkify.mjs new file mode 100644 index 0000000..a225280 --- /dev/null +++ b/packages/markdown-it-14.1.0/lib/rules_core/linkify.mjs @@ -0,0 +1,134 @@ +// Replace link-like texts with link nodes. +// +// Currently restricted by `md.validateLink()` to http/https/ftp +// + +import { arrayReplaceAt } from '../common/utils.mjs' + +function isLinkOpen (str) { + return /^<a[>\s]/i.test(str) +} +function isLinkClose (str) { + return /^<\/a\s*>/i.test(str) +} + +export default function linkify (state) { + const blockTokens = state.tokens + + if (!state.md.options.linkify) { return } + + for (let j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== 'inline' || + !state.md.linkify.pretest(blockTokens[j].content)) { + continue + } + + let tokens = blockTokens[j].children + + let htmlLinkLevel = 0 + + // We scan from the end, to keep position when new tags added. + // Use reversed logic in links start/end match + for (let i = tokens.length - 1; i >= 0; i--) { + const 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' && state.md.linkify.test(currentToken.content)) { + const text = currentToken.content + let links = state.md.linkify.match(text) + + // Now split string to nodes + const nodes = [] + let level = currentToken.level + let lastPos = 0 + + // forbid escape sequence at the start of the string, + // this avoids http\://example.com/ from being linkified as + // http:<a href="//example.com/">//example.com/</a> + if (links.length > 0 && + links[0].index === 0 && + i > 0 && + tokens[i - 1].type === 'text_special') { + links = links.slice(1) + } + + for (let ln = 0; ln < links.length; ln++) { + const url = links[ln].url + const fullUrl = state.md.normalizeLink(url) + if (!state.md.validateLink(fullUrl)) { continue } + + let 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) { + urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, '') + } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) { + urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, '') + } else { + urlText = state.md.normalizeLinkText(urlText) + } + + const pos = links[ln].index + + if (pos > lastPos) { + const token = new state.Token('text', '', 0) + token.content = text.slice(lastPos, pos) + token.level = level + nodes.push(token) + } + + const token_o = new state.Token('link_open', 'a', 1) + token_o.attrs = [['href', fullUrl]] + token_o.level = level++ + token_o.markup = 'linkify' + token_o.info = 'auto' + nodes.push(token_o) + + const token_t = new state.Token('text', '', 0) + token_t.content = urlText + token_t.level = level + nodes.push(token_t) + + const token_c = new state.Token('link_close', 'a', -1) + token_c.level = --level + token_c.markup = 'linkify' + token_c.info = 'auto' + nodes.push(token_c) + + lastPos = links[ln].lastIndex + } + if (lastPos < text.length) { + const token = new state.Token('text', '', 0) + token.content = text.slice(lastPos) + token.level = level + nodes.push(token) + } + + // replace current node + blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes) + } + } + } +} |