ant-design-vue/plugin/md/markdown/plugins/link.ts

70 lines
2.1 KiB
TypeScript

// markdown-it plugin for:
// 1. adding target="_blank" to external links
// 2. normalize internal links to end with `.html`
import type MarkdownIt from 'markdown-it';
import type { MarkdownParsedData } from '../markdown';
import { URL } from 'url';
const indexRE = /(^|.*\/)index.md(#?.*)$/i;
export const linkPlugin = (md: MarkdownIt, externalAttrs: Record<string, string>) => {
md.renderer.rules.link_open = (tokens, idx, options, _env, self) => {
const token = tokens[idx];
const hrefIndex = token.attrIndex('href');
if (hrefIndex >= 0) {
const hrefAttr = token.attrs![hrefIndex];
const url = hrefAttr[1];
const isExternal = /^https?:/.test(url);
if (isExternal) {
Object.entries(externalAttrs).forEach(([key, val]) => {
token.attrSet(key, val);
});
} else if (
// internal anchor links
!url.startsWith('#') &&
// mail links
!url.startsWith('mailto:')
) {
normalizeHref(hrefAttr);
}
}
return self.renderToken(tokens, idx, options);
};
function normalizeHref(hrefAttr: [string, string]) {
let url = hrefAttr[1];
const indexMatch = url.match(indexRE);
if (indexMatch) {
const [, path, hash] = indexMatch;
url = path + hash;
} else {
let cleanUrl = url.replace(/\#.*$/, '').replace(/\?.*$/, '');
// .md -> .html
if (cleanUrl.endsWith('.md')) {
cleanUrl = cleanUrl.replace(/\.md$/, '.html');
}
// ./foo -> ./foo.html
if (!cleanUrl.endsWith('.html') && !cleanUrl.endsWith('/')) {
cleanUrl += '.html';
}
const parsed = new URL(url, 'http://a.com');
url = cleanUrl + parsed.search + parsed.hash;
}
// ensure leading . for relative paths
if (!url.startsWith('/') && !/^\.\//.test(url)) {
url = './' + url;
}
// export it for existence check
const data = (md as any).__data as MarkdownParsedData;
const links = data.links || (data.links = []);
links.push(url.replace(/\.html$/, ''));
// markdown-it encodes the uri
hrefAttr[1] = decodeURI(url);
}
};