feat(plugin-md-power): add links to markdown env (#631)

This commit is contained in:
pengzhanbo 2025-06-29 14:37:15 +08:00 committed by GitHub
parent a8fac92bf0
commit cd2b7fd26d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 87 additions and 2 deletions

View File

@ -26,4 +26,17 @@ describe('linksPlugin', () => {
expect(md.render('[link](/path)')).toContain('href="/path"')
expect(md.render('[link](/path)')).toContain('</VPLink>')
})
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"')
})
})

View File

@ -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,
}
}