diff --git a/plugins/plugin-md-power/__test__/linksPlugin.spec.ts b/plugins/plugin-md-power/__test__/linksPlugin.spec.ts index 34401a4a..ec277ab6 100644 --- a/plugins/plugin-md-power/__test__/linksPlugin.spec.ts +++ b/plugins/plugin-md-power/__test__/linksPlugin.spec.ts @@ -26,4 +26,17 @@ describe('linksPlugin', () => { expect(md.render('[link](/path)')).toContain('href="/path"') expect(md.render('[link](/path)')).toContain('') }) + + it('should work with internal link extension and empty env', () => { + const env = {} + expect(md.render('[link](/path.md)', env)).toContain('href="/path.md"') + expect(md.render('[link](../path.md)', env)).toContain('href="../path.md"') + }) + + it('should work with internal link extension and env', () => { + const env = { base: '/', filePathRelative: '../foo.md' } + expect(md.render('[link](/path.html)', env)).toContain('href="/path.html"') + expect(md.render('[link](/path.md)', env)).toContain('href="/path.md"') + expect(md.render('[link](../path.md)', env)).toContain('href="../path.md"') + }) }) diff --git a/plugins/plugin-md-power/src/node/enhance/links.ts b/plugins/plugin-md-power/src/node/enhance/links.ts index 176b0494..91f2a7ff 100644 --- a/plugins/plugin-md-power/src/node/enhance/links.ts +++ b/plugins/plugin-md-power/src/node/enhance/links.ts @@ -1,5 +1,7 @@ import type Token from 'markdown-it/lib/token.mjs' import type { Markdown, MarkdownEnv } from 'vuepress/markdown' +import { removeLeadingSlash } from '@vuepress/shared' +import { path } from '@vuepress/utils' import { isLinkWithProtocol } from 'vuepress/shared' export function linksPlugin(md: Markdown): void { @@ -12,7 +14,7 @@ export function linksPlugin(md: Markdown): void { let hasOpenInternalLink = false const internalTag = 'VPLink' - function handleLinkOpen(tokens: Token[], idx: number) { + function handleLinkOpen(tokens: Token[], idx: number, env: MarkdownEnv) { hasOpenInternalLink = false const token = tokens[idx] // get `href` attr index @@ -42,10 +44,29 @@ export function linksPlugin(md: Markdown): void { // convert starting tag of internal link hasOpenInternalLink = true token.tag = internalTag + + const matched = hrefLink.match(/^([^#?]*?(?:\/|\.md|\.html))([#?].*)?$/) + + if (matched) { + const rawPath = matched[1] + const { absolutePath, relativePath } = resolvePaths( + rawPath, + env.base || '/', + env.filePathRelative ?? null, + ) + // extract internal links for file / page existence check + // 并不能保证文件存在,以及是否属于内部链接 + // 需要进一步在 extendPage 阶段中与 page.path / page.filePath 进行对比 + ;(env.links ??= []).push({ + raw: rawPath, + absolute: absolutePath, + relative: relativePath, + }) + } } md.renderer.rules.link_open = (tokens, idx, opts, env: MarkdownEnv, self) => { - handleLinkOpen(tokens, idx) + handleLinkOpen(tokens, idx, env) return self.renderToken(tokens, idx, opts) } @@ -58,3 +79,54 @@ export function linksPlugin(md: Markdown): void { return self.renderToken(tokens, idx, opts) } } + +/** + * Resolve relative and absolute paths according to the `base` and `filePathRelative` + */ +export function resolvePaths(rawPath: string, base: string, filePathRelative: string | null): { + absolutePath: string | null + relativePath: string +} { + let absolutePath: string | null + let relativePath: string + + // if raw path is absolute + if (rawPath.startsWith('/')) { + // if raw path is a link to markdown file + if (rawPath.endsWith('.md')) { + // prepend `base` to the link + absolutePath = path.join(base, rawPath) + relativePath = removeLeadingSlash(rawPath) + } + // if raw path is a link to other kind of file + else { + // keep the link as is + absolutePath = rawPath + relativePath = path.relative(base, absolutePath) + } + } + // if raw path is relative + // if `filePathRelative` is available + else if (filePathRelative) { + // resolve relative path according to `filePathRelative` + relativePath = path.join( + // file path may contain non-ASCII characters + path.dirname(encodeURI(filePathRelative)), + rawPath, + ) + // resolve absolute path according to `base` + absolutePath = path.join(base, relativePath) + } + // if `filePathRelative` is not available + else { + // remove leading './' + relativePath = rawPath.replace(/^(?:\.\/)?(.*)$/, '$1') + // just take relative link as absolute link + absolutePath = null + } + + return { + absolutePath, + relativePath, + } +}