-
Notifications
You must be signed in to change notification settings - Fork 925
/
linksPlugin.ts
153 lines (133 loc) · 4.2 KB
/
linksPlugin.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { inferRoutePath, isLinkExternal } from '@vuepress/shared'
import type { PluginWithOptions } from 'markdown-it'
import type Token from 'markdown-it/lib/token.mjs'
import type { MarkdownEnv } from '../../types.js'
import { resolvePaths } from './resolvePaths.js'
export interface LinksPluginOptions {
/**
* Tag for internal links
*
* @default 'RouteLink'
*/
internalTag?: 'a' | 'RouteLink' | 'RouterLink'
/**
* Additional attributes for external links
*
* @default
* ```js
* ({
* target: '_blank',
* rel: 'noopener noreferrer',
* })
* ```
*/
externalAttrs?: Record<string, string>
}
/**
* Process links in markdown file
*
* - internal links: convert `<a>` tag into internalTag
* - external links: add extra attrs
*/
export const linksPlugin: PluginWithOptions<LinksPluginOptions> = (
md,
options: LinksPluginOptions = {},
): void => {
// tag of internal links
const internalTag = options.internalTag || 'RouteLink'
// attrs that going to be added to external links
const externalAttrs = {
target: '_blank',
rel: 'noopener noreferrer',
...options.externalAttrs,
}
let hasOpenInternalLink = false
const handleLinkOpen = (
tokens: Token[],
idx: number,
env: MarkdownEnv,
): void => {
// get current token
const token = tokens[idx]
// get `href` attr index
const hrefIndex = token.attrIndex('href')
// if `href` attr does not exist, skip
/* istanbul ignore if */
if (hrefIndex < 0) {
return
}
// if `href` attr exists, `token.attrs` is not `null`
const hrefAttr = token.attrs![hrefIndex]
const hrefLink: string = hrefAttr[1]
// get `base` and `filePathRelative` from `env`
const { base = '/', filePathRelative = null } = env
// check if a link is an external link
if (isLinkExternal(hrefLink, base)) {
// set `externalAttrs` to current token
Object.entries(externalAttrs).forEach(([key, val]) => {
token.attrSet(key, val)
})
return
}
// check if a link is an internal link
const internalLinkMatch = hrefLink.match(
/^([^#?]*?(?:\/|\.md|\.html))([#?].*)?$/,
)
if (!internalLinkMatch) {
return
}
// convert
// <a href="hrefLink">
// to
// <RouteLink to="toProp">
// notice that the path and hash are encoded by markdown-it
const rawPath = internalLinkMatch[1]
const rawHashAndQueries = internalLinkMatch[2] || ''
// resolve relative and absolute path
const { relativePath, absolutePath } = resolvePaths(
rawPath,
base,
filePathRelative,
)
if (['RouterLink', 'RouteLink'].includes(internalTag)) {
// convert starting tag of internal link to `internalTag`
token.tag = internalTag
// replace the original `href` attr with `to` attr
hrefAttr[0] = 'to'
// normalize markdown file path to route path
// we are removing the `base` from absolute path because it should not be
// passed to `<RouteLink>` or `<RouterLink>`
const normalizedPath = inferRoutePath(
absolutePath
? absolutePath.replace(new RegExp(`^${base}`), '/')
: relativePath,
)
// replace the original href link with the normalized path
hrefAttr[1] = `${normalizedPath}${rawHashAndQueries}`
// set `hasOpenInternalLink` to modify the ending tag
hasOpenInternalLink = true
} else {
const normalizedPath = inferRoutePath(absolutePath ?? relativePath)
// replace the original href link with the normalized path
hrefAttr[1] = `${normalizedPath}${rawHashAndQueries}`
}
// extract internal links for file / page existence check
;(env.links ??= []).push({
raw: hrefLink,
relative: relativePath,
absolute: absolutePath,
})
}
md.renderer.rules.link_open = (tokens, idx, opts, env: MarkdownEnv, self) => {
handleLinkOpen(tokens, idx, env)
return self.renderToken(tokens, idx, opts)
}
md.renderer.rules.link_close = (tokens, idx, opts, _env, self) => {
// convert ending tag of internal link
if (hasOpenInternalLink) {
hasOpenInternalLink = false
tokens[idx].tag = internalTag
}
return self.renderToken(tokens, idx, opts)
}
}