Skip to content

Commit 192708d

Browse files
authored
feat(theme): editLink can accept function (#2058)
1 parent 90e924a commit 192708d

File tree

7 files changed

+75
-11
lines changed

7 files changed

+75
-11
lines changed

docs/reference/default-theme-edit-link.md

+20
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@ export default {
1414

1515
The `pattern` option defines the URL structure for the link, and `:path` is going to be replaced with the page path.
1616

17+
You can also put a pure function that accepts `relativePath` as the argument and returns the URL string.
18+
19+
```js
20+
export default {
21+
themeConfig: {
22+
editLink: {
23+
pattern: ({ relativePath }) => {
24+
if (relativePath.startsWith('packages/')) {
25+
return `https://github.com/acme/monorepo/edit/main/${relativePath}`
26+
} else {
27+
return `https://github.com/acme/monorepo/edit/main/docs/${relativePath}`
28+
}
29+
}
30+
}
31+
}
32+
}
33+
```
34+
35+
It should not have side-effects nor access anything outside of its scope since it will be serialized and executed in the browser.
36+
1737
By default, this will add the link text "Edit this page" at the bottom of the doc page. You may customize this text by defining the `text` option.
1838

1939
```js

src/client/app/utils.ts

+15
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ export function pathToFile(path: string): string {
5858
return pagePath
5959
}
6060

61+
export function deserializeFunctions(value: any): any {
62+
if (Array.isArray(value)) {
63+
return value.map(deserializeFunctions)
64+
} else if (typeof value === 'object' && value !== null) {
65+
return Object.keys(value).reduce((acc, key) => {
66+
acc[key] = deserializeFunctions(value[key])
67+
return acc
68+
}, {} as any)
69+
} else if (typeof value === 'string' && value.startsWith('_vp-fn_')) {
70+
return new Function(`return ${value.slice(7)}`)()
71+
} else {
72+
return value
73+
}
74+
}
75+
6176
export let contentUpdatedCallbacks: (() => any)[] = []
6277

6378
/**

src/client/index.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,31 @@
22
// so the user can do `import { useRoute, useSiteData } from 'vitepress'`
33

44
// generic types
5-
export type { Router, Route } from './app/router.js'
65
export type { VitePressData } from './app/data.js'
6+
export type { Route, Router } from './app/router.js'
7+
78
// theme types
8-
export type { Theme, EnhanceAppContext } from './app/theme.js'
9+
export type { EnhanceAppContext, Theme } from './app/theme.js'
10+
911
// shared types
1012
export type {
11-
PageData,
12-
SiteData,
1313
HeadConfig,
14-
Header
14+
Header,
15+
PageData,
16+
SiteData
1517
} from '../../types/shared.js'
1618

1719
// composables
1820
export { useData } from './app/data.js'
19-
export { useRouter, useRoute } from './app/router.js'
21+
export { useRoute, useRouter } from './app/router.js'
2022

2123
// utilities
22-
export { inBrowser, withBase, onContentUpdated } from './app/utils.js'
24+
export {
25+
deserializeFunctions,
26+
inBrowser,
27+
onContentUpdated,
28+
withBase
29+
} from './app/utils.js'
2330

2431
// components
2532
export { Content } from './app/components/Content.js'

src/client/theme-default/composables/edit-link.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ export function useEditLink() {
77
return computed(() => {
88
const { text = 'Edit this page', pattern = '' } = theme.value.editLink || {}
99
const { relativePath } = page.value
10-
const url = pattern.replace(/:path/g, relativePath)
10+
let url: string
11+
if (typeof pattern === 'function') {
12+
url = pattern({ relativePath })
13+
} else {
14+
url = pattern.replace(/:path/g, relativePath)
15+
}
1116

1217
return { url, text }
1318
})

src/node/plugin.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { staticDataPlugin } from './plugins/staticDataPlugin'
2222
import { webFontsPlugin } from './plugins/webFontsPlugin'
2323
import { dynamicRoutesPlugin } from './plugins/dynamicRoutesPlugin'
2424
import { rewritesPlugin } from './plugins/rewritesPlugin'
25+
import { serializeFunctions } from './utils/fnSerialize.js'
2526

2627
declare module 'vite' {
2728
interface UserConfig {
@@ -158,9 +159,11 @@ export async function createVitePressPlugin(
158159
if (config.command === 'build') {
159160
data = { ...siteData, head: [] }
160161
}
161-
return `export default JSON.parse(${JSON.stringify(
162+
data = serializeFunctions(data)
163+
return `import { deserializeFunctions } from 'vitepress/client'
164+
export default deserializeFunctions(JSON.parse(${JSON.stringify(
162165
JSON.stringify(data)
163-
)})`
166+
)}))`
164167
}
165168
},
166169

src/node/utils/fnSerialize.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export function serializeFunctions(value: any): any {
2+
if (Array.isArray(value)) {
3+
return value.map(serializeFunctions)
4+
} else if (typeof value === 'object' && value !== null) {
5+
return Object.keys(value).reduce((acc, key) => {
6+
acc[key] = serializeFunctions(value[key])
7+
return acc
8+
}, {} as any)
9+
} else if (typeof value === 'function') {
10+
return `_vp-fn_${value.toString()}`
11+
} else {
12+
return value
13+
}
14+
}

types/default-theme.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ export namespace DefaultTheme {
209209
*
210210
* @example 'https://github.com/vuejs/vitepress/edit/main/docs/:path'
211211
*/
212-
pattern: string
212+
pattern: string | ((payload: { relativePath: string }) => string)
213213

214214
/**
215215
* Custom text for edit link.

0 commit comments

Comments
 (0)