vitepress 1.6.4 → 2.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/vitepress.js +14 -0
- package/dist/client/app/composables/copyCode.js +8 -6
- package/dist/client/app/data.js +1 -1
- package/dist/client/app/devtools.js +0 -1
- package/dist/client/app/index.js +2 -5
- package/dist/client/app/router.js +84 -74
- package/dist/client/app/utils.js +3 -2
- package/dist/client/index.d.ts +8 -6
- package/dist/client/shared.js +112 -25
- package/dist/client/theme-default/Layout.vue +11 -10
- package/dist/client/theme-default/NotFound.vue +1 -1
- package/dist/client/theme-default/components/VPAlgoliaSearchBox.vue +28 -35
- package/dist/client/theme-default/components/VPButton.vue +2 -2
- package/dist/client/theme-default/components/VPContent.vue +3 -6
- package/dist/client/theme-default/components/VPDoc.vue +3 -2
- package/dist/client/theme-default/components/VPDocAsideOutline.vue +8 -17
- package/dist/client/theme-default/components/VPDocAsideSponsors.vue +1 -1
- package/dist/client/theme-default/components/VPDocOutlineItem.vue +5 -3
- package/dist/client/theme-default/components/VPFeature.vue +2 -2
- package/dist/client/theme-default/components/VPFlyout.vue +4 -3
- package/dist/client/theme-default/components/VPFooter.vue +2 -2
- package/dist/client/theme-default/components/VPHero.vue +7 -3
- package/dist/client/theme-default/components/VPHomeSponsors.vue +1 -1
- package/dist/client/theme-default/components/VPLocalNav.vue +7 -23
- package/dist/client/theme-default/components/VPLocalNavOutlineDropdown.vue +5 -4
- package/dist/client/theme-default/components/VPLocalSearchBox.vue +5 -1
- package/dist/client/theme-default/components/VPMenu.vue +4 -3
- package/dist/client/theme-default/components/VPMenuGroup.vue +5 -4
- package/dist/client/theme-default/components/VPMenuLink.vue +2 -2
- package/dist/client/theme-default/components/VPNav.vue +2 -2
- package/dist/client/theme-default/components/VPNavBar.vue +3 -5
- package/dist/client/theme-default/components/VPNavBarMenu.vue +2 -2
- package/dist/client/theme-default/components/VPNavBarSearch.vue +3 -25
- package/dist/client/theme-default/components/VPNavBarSearchButton.vue +80 -146
- package/dist/client/theme-default/components/VPNavBarTitle.vue +4 -4
- package/dist/client/theme-default/components/VPNavScreenMenu.vue +1 -1
- package/dist/client/theme-default/components/VPNavScreenMenuGroup.vue +1 -1
- package/dist/client/theme-default/components/VPNavScreenMenuGroupLink.vue +2 -1
- package/dist/client/theme-default/components/VPNavScreenMenuGroupSection.vue +1 -5
- package/dist/client/theme-default/components/VPNavScreenMenuLink.vue +2 -1
- package/dist/client/theme-default/components/VPSidebar.vue +3 -3
- package/dist/client/theme-default/components/VPSidebarGroup.vue +1 -1
- package/dist/client/theme-default/components/VPSidebarItem.vue +2 -2
- package/dist/client/theme-default/components/VPSocialLink.vue +2 -1
- package/dist/client/theme-default/components/VPSocialLinks.vue +9 -5
- package/dist/client/theme-default/components/VPSponsorsGrid.vue +0 -1
- package/dist/client/theme-default/components/VPTeamMembers.vue +1 -1
- package/dist/client/theme-default/components/VPTeamMembersItem.vue +1 -1
- package/dist/client/theme-default/composables/aside.js +2 -2
- package/dist/client/theme-default/composables/layout.js +76 -0
- package/dist/client/theme-default/composables/nav.js +1 -0
- package/dist/client/theme-default/composables/sidebar.js +21 -63
- package/dist/client/theme-default/styles/components/vp-code.css +2 -2
- package/dist/client/theme-default/styles/components/vp-doc.css +2 -12
- package/dist/client/theme-default/styles/fonts.css +50 -36
- package/dist/client/theme-default/styles/icons.css +16 -16
- package/dist/client/theme-default/styles/vars.css +13 -10
- package/dist/client/theme-default/support/sidebar.js +1 -1
- package/dist/client/theme-default/without-fonts.js +1 -2
- package/dist/node/{chunk-D3CUZ4fa.js → chunk-BNWqDZk7.js} +6615 -9516
- package/dist/node/cli.js +13 -16
- package/dist/node/index.d.ts +526 -1812
- package/dist/node/index.js +35 -42
- package/package.json +64 -58
- package/template/.vitepress/config.js +3 -1
- package/template/.vitepress/theme/style.css +1 -1
- package/theme.d.ts +24 -3
- package/types/default-theme.d.ts +30 -83
- package/types/docsearch.d.ts +66 -5
- package/types/shared.d.ts +29 -2
- package/dist/client/theme-default/composables/local-nav.js +0 -18
- package/dist/node/chunk-C-d2RJOW.js +0 -470
- package/dist/node/worker_shikiResolveLang.js +0 -13
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# VitePress 📝💨
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/vitepress)
|
|
3
|
+
[](https://github.com/vuejs/vitepress/actions/workflows/test.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/vitepress/v/next)
|
|
5
5
|
[](https://nightly.akryum.dev/vuejs/vitepress)
|
|
6
6
|
[](https://chat.vuejs.org)
|
|
7
7
|
|
package/bin/vitepress.js
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
3
|
+
|
|
4
|
+
import module from 'node:module'
|
|
5
|
+
|
|
6
|
+
// https://github.com/vitejs/vite/blob/6c8a5a27e645a182f5b03a4ed6aa726eab85993f/packages/vite/bin/vite.js#L48-L63
|
|
7
|
+
try {
|
|
8
|
+
module.enableCompileCache?.()
|
|
9
|
+
setTimeout(() => {
|
|
10
|
+
try {
|
|
11
|
+
module.flushCompileCache?.()
|
|
12
|
+
} catch {}
|
|
13
|
+
}, 10 * 1000).unref()
|
|
14
|
+
} catch {}
|
|
15
|
+
|
|
2
16
|
import('../dist/node/cli.js')
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { inBrowser } from 'vitepress';
|
|
2
|
+
const shellRE = /language-(shellscript|shell|bash|sh|zsh)/;
|
|
3
|
+
const ignoredNodes = ['.vp-copy-ignore', '.diff.remove'].join(', ');
|
|
2
4
|
export function useCopyCode() {
|
|
3
5
|
if (inBrowser) {
|
|
4
6
|
const timeoutIdMap = new WeakMap();
|
|
@@ -6,17 +8,17 @@ export function useCopyCode() {
|
|
|
6
8
|
const el = e.target;
|
|
7
9
|
if (el.matches('div[class*="language-"] > button.copy')) {
|
|
8
10
|
const parent = el.parentElement;
|
|
9
|
-
const sibling = el.nextElementSibling?.nextElementSibling;
|
|
11
|
+
const sibling = el.nextElementSibling?.nextElementSibling; // <pre> tag
|
|
10
12
|
if (!parent || !sibling) {
|
|
11
13
|
return;
|
|
12
14
|
}
|
|
13
|
-
const isShell =
|
|
14
|
-
const ignoredNodes = ['.vp-copy-ignore', '.diff.remove'];
|
|
15
|
+
const isShell = shellRE.test(parent.className);
|
|
15
16
|
// Clone the node and remove the ignored nodes
|
|
16
17
|
const clone = sibling.cloneNode(true);
|
|
17
|
-
clone
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
clone.querySelectorAll(ignoredNodes).forEach((node) => node.remove());
|
|
19
|
+
// remove extra newlines left after removing ignored nodes (affecting textContent because it is inside `<pre>`)
|
|
20
|
+
// doesn't affect the newlines already in the code because they are rendered as `\n<span class="line"></span>`
|
|
21
|
+
clone.innerHTML = clone.innerHTML.replace(/\n+/g, '\n');
|
|
20
22
|
let text = clone.textContent || '';
|
|
21
23
|
if (isShell) {
|
|
22
24
|
text = text.replace(/^ *(\$|>) /gm, '').trim();
|
package/dist/client/app/data.js
CHANGED
|
@@ -7,7 +7,7 @@ export const dataSymbol = Symbol();
|
|
|
7
7
|
export const siteDataRef = shallowRef((import.meta.env.PROD ? siteData : readonly(siteData)));
|
|
8
8
|
// hmr
|
|
9
9
|
if (import.meta.hot) {
|
|
10
|
-
import.meta.hot.accept('
|
|
10
|
+
import.meta.hot.accept('@siteData', (m) => {
|
|
11
11
|
if (m) {
|
|
12
12
|
siteDataRef.value = m.default;
|
|
13
13
|
}
|
|
@@ -10,7 +10,6 @@ export const setupDevtools = (app, router, data) => {
|
|
|
10
10
|
homepage: 'https://vitepress.dev',
|
|
11
11
|
componentStateTypes: [COMPONENT_STATE_TYPE]
|
|
12
12
|
}, (api) => {
|
|
13
|
-
// TODO: remove any
|
|
14
13
|
api.on.inspectComponent((payload) => {
|
|
15
14
|
payload.instanceData.state.push({
|
|
16
15
|
type: COMPONENT_STATE_TYPE,
|
package/dist/client/app/index.js
CHANGED
|
@@ -128,16 +128,13 @@ function newRouter() {
|
|
|
128
128
|
if (inBrowser) {
|
|
129
129
|
createApp().then(({ app, router, data }) => {
|
|
130
130
|
// wait until page component is fetched before mounting
|
|
131
|
-
router.go().then(() => {
|
|
131
|
+
router.go(location.href, { initialLoad: true }).then(() => {
|
|
132
132
|
// dynamically update head tags
|
|
133
133
|
useUpdateHead(router.route, data.site);
|
|
134
134
|
app.mount('#app');
|
|
135
135
|
// scroll to hash on new tab during dev
|
|
136
136
|
if (import.meta.env.DEV && location.hash) {
|
|
137
|
-
|
|
138
|
-
if (target) {
|
|
139
|
-
scrollTo(target, location.hash);
|
|
140
|
-
}
|
|
137
|
+
scrollTo(location.hash);
|
|
141
138
|
}
|
|
142
139
|
});
|
|
143
140
|
});
|
|
@@ -8,6 +8,8 @@ export const RouterSymbol = Symbol();
|
|
|
8
8
|
const fakeHost = 'http://a.com';
|
|
9
9
|
const getDefaultRoute = () => ({
|
|
10
10
|
path: '/',
|
|
11
|
+
hash: '',
|
|
12
|
+
query: '',
|
|
11
13
|
component: null,
|
|
12
14
|
data: notFoundPageData
|
|
13
15
|
});
|
|
@@ -15,20 +17,16 @@ export function createRouter(loadPageModule, fallbackComponent) {
|
|
|
15
17
|
const route = reactive(getDefaultRoute());
|
|
16
18
|
const router = {
|
|
17
19
|
route,
|
|
18
|
-
go
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
history.replaceState({ scrollPosition: window.scrollY }, '');
|
|
27
|
-
history.pushState({}, '', href);
|
|
20
|
+
async go(href, options) {
|
|
21
|
+
href = normalizeHref(href);
|
|
22
|
+
if ((await router.onBeforeRouteChange?.(href)) === false)
|
|
23
|
+
return;
|
|
24
|
+
if (!inBrowser || (await changeRoute(href, options)))
|
|
25
|
+
await loadPage(href);
|
|
26
|
+
syncRouteQueryAndHash();
|
|
27
|
+
await router.onAfterRouteChange?.(href);
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
await (router.onAfterRouteChange ?? router.onAfterRouteChanged)?.(href);
|
|
31
|
-
}
|
|
29
|
+
};
|
|
32
30
|
let latestPendingPath = null;
|
|
33
31
|
async function loadPage(href, scrollPosition = 0, isRetry = false) {
|
|
34
32
|
if ((await router.onBeforePageLoad?.(href)) === false)
|
|
@@ -37,21 +35,20 @@ export function createRouter(loadPageModule, fallbackComponent) {
|
|
|
37
35
|
const pendingPath = (latestPendingPath = targetLoc.pathname);
|
|
38
36
|
try {
|
|
39
37
|
let page = await loadPageModule(pendingPath);
|
|
40
|
-
if (!page)
|
|
38
|
+
if (!page)
|
|
41
39
|
throw new Error(`Page not found: ${pendingPath}`);
|
|
42
|
-
}
|
|
43
40
|
if (latestPendingPath === pendingPath) {
|
|
44
41
|
latestPendingPath = null;
|
|
45
42
|
const { default: comp, __pageData } = page;
|
|
46
|
-
if (!comp)
|
|
43
|
+
if (!comp)
|
|
47
44
|
throw new Error(`Invalid route component: ${comp}`);
|
|
48
|
-
}
|
|
49
45
|
await router.onAfterPageLoad?.(href);
|
|
50
46
|
route.path = inBrowser ? pendingPath : withBase(pendingPath);
|
|
51
47
|
route.component = markRaw(comp);
|
|
52
48
|
route.data = import.meta.env.PROD
|
|
53
49
|
? markRaw(__pageData)
|
|
54
50
|
: readonly(__pageData);
|
|
51
|
+
syncRouteQueryAndHash(targetLoc);
|
|
55
52
|
if (inBrowser) {
|
|
56
53
|
nextTick(() => {
|
|
57
54
|
let actualPathname = siteDataRef.value.base +
|
|
@@ -64,20 +61,7 @@ export function createRouter(loadPageModule, fallbackComponent) {
|
|
|
64
61
|
href = actualPathname + targetLoc.search + targetLoc.hash;
|
|
65
62
|
history.replaceState({}, '', href);
|
|
66
63
|
}
|
|
67
|
-
|
|
68
|
-
let target = null;
|
|
69
|
-
try {
|
|
70
|
-
target = document.getElementById(decodeURIComponent(targetLoc.hash).slice(1));
|
|
71
|
-
}
|
|
72
|
-
catch (e) {
|
|
73
|
-
console.warn(e);
|
|
74
|
-
}
|
|
75
|
-
if (target) {
|
|
76
|
-
scrollTo(target, targetLoc.hash);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
window.scrollTo(0, scrollPosition);
|
|
64
|
+
return scrollTo(targetLoc.hash, false, scrollPosition);
|
|
81
65
|
});
|
|
82
66
|
}
|
|
83
67
|
}
|
|
@@ -104,19 +88,25 @@ export function createRouter(loadPageModule, fallbackComponent) {
|
|
|
104
88
|
route.path = inBrowser ? pendingPath : withBase(pendingPath);
|
|
105
89
|
route.component = fallbackComponent ? markRaw(fallbackComponent) : null;
|
|
106
90
|
const relativePath = inBrowser
|
|
107
|
-
?
|
|
91
|
+
? route.path
|
|
108
92
|
.replace(/(^|\/)$/, '$1index')
|
|
109
93
|
.replace(/(\.html)?$/, '.md')
|
|
110
|
-
.
|
|
94
|
+
.slice(siteDataRef.value.base.length)
|
|
111
95
|
: '404.md';
|
|
112
96
|
route.data = { ...notFoundPageData, relativePath };
|
|
97
|
+
syncRouteQueryAndHash(targetLoc);
|
|
113
98
|
}
|
|
114
99
|
}
|
|
115
100
|
}
|
|
101
|
+
function syncRouteQueryAndHash(loc = inBrowser
|
|
102
|
+
? location
|
|
103
|
+
: { search: '', hash: '' }) {
|
|
104
|
+
route.query = loc.search;
|
|
105
|
+
route.hash = decodeURIComponent(loc.hash);
|
|
106
|
+
}
|
|
116
107
|
if (inBrowser) {
|
|
117
|
-
if (history.state === null)
|
|
108
|
+
if (history.state === null)
|
|
118
109
|
history.replaceState({}, '');
|
|
119
|
-
}
|
|
120
110
|
window.addEventListener('click', (e) => {
|
|
121
111
|
if (e.defaultPrevented ||
|
|
122
112
|
!(e.target instanceof Element) ||
|
|
@@ -125,46 +115,29 @@ export function createRouter(loadPageModule, fallbackComponent) {
|
|
|
125
115
|
e.ctrlKey ||
|
|
126
116
|
e.shiftKey ||
|
|
127
117
|
e.altKey ||
|
|
128
|
-
e.metaKey)
|
|
118
|
+
e.metaKey) {
|
|
129
119
|
return;
|
|
120
|
+
}
|
|
130
121
|
const link = e.target.closest('a');
|
|
131
122
|
if (!link ||
|
|
132
123
|
link.closest('.vp-raw') ||
|
|
133
124
|
link.hasAttribute('download') ||
|
|
134
|
-
link.hasAttribute('target'))
|
|
125
|
+
link.hasAttribute('target')) {
|
|
135
126
|
return;
|
|
127
|
+
}
|
|
136
128
|
const linkHref = link.getAttribute('href') ??
|
|
137
129
|
(link instanceof SVGAElement ? link.getAttribute('xlink:href') : null);
|
|
138
130
|
if (linkHref == null)
|
|
139
131
|
return;
|
|
140
|
-
const { href, origin, pathname
|
|
141
|
-
const
|
|
132
|
+
const { href, origin, pathname } = new URL(linkHref, link.baseURI);
|
|
133
|
+
const currentLoc = new URL(location.href); // copy to keep old data
|
|
142
134
|
// only intercept inbound html links
|
|
143
|
-
if (origin ===
|
|
135
|
+
if (origin === currentLoc.origin && treatAsHtml(pathname)) {
|
|
144
136
|
e.preventDefault();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (hash !== currentUrl.hash) {
|
|
150
|
-
history.pushState({}, '', href);
|
|
151
|
-
// still emit the event so we can listen to it in themes
|
|
152
|
-
window.dispatchEvent(new HashChangeEvent('hashchange', {
|
|
153
|
-
oldURL: currentUrl.href,
|
|
154
|
-
newURL: href
|
|
155
|
-
}));
|
|
156
|
-
}
|
|
157
|
-
if (hash) {
|
|
158
|
-
// use smooth scroll when clicking on header anchor links
|
|
159
|
-
scrollTo(link, hash, link.classList.contains('header-anchor'));
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
window.scrollTo(0, 0);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
go(href);
|
|
167
|
-
}
|
|
137
|
+
router.go(href, {
|
|
138
|
+
// use smooth scroll when clicking on header anchor links
|
|
139
|
+
smoothScroll: link.classList.contains('header-anchor')
|
|
140
|
+
});
|
|
168
141
|
}
|
|
169
142
|
}, { capture: true });
|
|
170
143
|
window.addEventListener('popstate', async (e) => {
|
|
@@ -172,10 +145,12 @@ export function createRouter(loadPageModule, fallbackComponent) {
|
|
|
172
145
|
return;
|
|
173
146
|
const href = normalizeHref(location.href);
|
|
174
147
|
await loadPage(href, (e.state && e.state.scrollPosition) || 0);
|
|
175
|
-
|
|
148
|
+
syncRouteQueryAndHash();
|
|
149
|
+
await router.onAfterRouteChange?.(href);
|
|
176
150
|
});
|
|
177
151
|
window.addEventListener('hashchange', (e) => {
|
|
178
152
|
e.preventDefault();
|
|
153
|
+
syncRouteQueryAndHash();
|
|
179
154
|
});
|
|
180
155
|
}
|
|
181
156
|
handleHMR(route);
|
|
@@ -183,20 +158,21 @@ export function createRouter(loadPageModule, fallbackComponent) {
|
|
|
183
158
|
}
|
|
184
159
|
export function useRouter() {
|
|
185
160
|
const router = inject(RouterSymbol);
|
|
186
|
-
if (!router)
|
|
161
|
+
if (!router)
|
|
187
162
|
throw new Error('useRouter() is called without provider.');
|
|
188
|
-
}
|
|
189
163
|
return router;
|
|
190
164
|
}
|
|
191
165
|
export function useRoute() {
|
|
192
166
|
return useRouter().route;
|
|
193
167
|
}
|
|
194
|
-
export function scrollTo(
|
|
168
|
+
export function scrollTo(hash, smooth = false, scrollPosition = 0) {
|
|
169
|
+
if (!hash || scrollPosition) {
|
|
170
|
+
window.scrollTo(0, scrollPosition);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
195
173
|
let target = null;
|
|
196
174
|
try {
|
|
197
|
-
target =
|
|
198
|
-
? el
|
|
199
|
-
: document.getElementById(decodeURIComponent(hash).slice(1));
|
|
175
|
+
target = document.getElementById(decodeURIComponent(hash).slice(1));
|
|
200
176
|
}
|
|
201
177
|
catch (e) {
|
|
202
178
|
console.warn(e);
|
|
@@ -222,9 +198,8 @@ function handleHMR(route) {
|
|
|
222
198
|
if (import.meta.hot) {
|
|
223
199
|
// hot reload pageData
|
|
224
200
|
import.meta.hot.on('vitepress:pageData', (payload) => {
|
|
225
|
-
if (shouldHotReload(payload))
|
|
201
|
+
if (shouldHotReload(payload))
|
|
226
202
|
route.data = payload.pageData;
|
|
227
|
-
}
|
|
228
203
|
});
|
|
229
204
|
}
|
|
230
205
|
}
|
|
@@ -239,9 +214,44 @@ function normalizeHref(href) {
|
|
|
239
214
|
const url = new URL(href, fakeHost);
|
|
240
215
|
url.pathname = url.pathname.replace(/(^|\/)index(\.html)?$/, '$1');
|
|
241
216
|
// ensure correct deep link so page refresh lands on correct files.
|
|
242
|
-
if (siteDataRef.value.cleanUrls)
|
|
217
|
+
if (siteDataRef.value.cleanUrls) {
|
|
243
218
|
url.pathname = url.pathname.replace(/\.html$/, '');
|
|
244
|
-
|
|
219
|
+
}
|
|
220
|
+
else if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) {
|
|
245
221
|
url.pathname += '.html';
|
|
222
|
+
}
|
|
246
223
|
return url.pathname + url.search + url.hash;
|
|
247
224
|
}
|
|
225
|
+
async function changeRoute(href, { smoothScroll = false, initialLoad = false, replace = false } = {}) {
|
|
226
|
+
const loc = normalizeHref(location.href);
|
|
227
|
+
const nextUrl = new URL(href, location.origin);
|
|
228
|
+
const currentUrl = new URL(loc, location.origin);
|
|
229
|
+
if (href === loc) {
|
|
230
|
+
if (!initialLoad) {
|
|
231
|
+
scrollTo(nextUrl.hash, smoothScroll);
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
if (replace) {
|
|
237
|
+
history.replaceState({}, '', href);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// save scroll position before changing URL
|
|
241
|
+
history.replaceState({ scrollPosition: window.scrollY }, '');
|
|
242
|
+
history.pushState({}, '', href);
|
|
243
|
+
}
|
|
244
|
+
if (nextUrl.pathname === currentUrl.pathname) {
|
|
245
|
+
// scroll between hash anchors on the same page, avoid duplicate entries
|
|
246
|
+
if (nextUrl.hash !== currentUrl.hash) {
|
|
247
|
+
window.dispatchEvent(new HashChangeEvent('hashchange', {
|
|
248
|
+
oldURL: currentUrl.href,
|
|
249
|
+
newURL: nextUrl.href
|
|
250
|
+
}));
|
|
251
|
+
scrollTo(nextUrl.hash, smoothScroll);
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
}
|
package/dist/client/app/utils.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tryOnUnmounted } from '@vueuse/core';
|
|
2
|
+
import { h, onMounted, shallowRef } from 'vue';
|
|
2
3
|
import { EXTERNAL_URL_RE, inBrowser, sanitizeFileName } from '../shared';
|
|
3
4
|
import { siteDataRef } from './data';
|
|
4
5
|
export { escapeHtml as _escapeHtml, inBrowser } from '../shared';
|
|
@@ -62,7 +63,7 @@ export let contentUpdatedCallbacks = [];
|
|
|
62
63
|
*/
|
|
63
64
|
export function onContentUpdated(fn) {
|
|
64
65
|
contentUpdatedCallbacks.push(fn);
|
|
65
|
-
|
|
66
|
+
tryOnUnmounted(() => {
|
|
66
67
|
contentUpdatedCallbacks = contentUpdatedCallbacks.filter((f) => f !== fn);
|
|
67
68
|
});
|
|
68
69
|
}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
|
-
import { Component,
|
|
2
|
+
import { Component, Ref, InjectionKey, App, AsyncComponentLoader } from 'vue';
|
|
3
3
|
import { PageData, Awaitable, SiteData } from '../../types/shared.js';
|
|
4
4
|
export { HeadConfig, Header, PageData, SiteData } from '../../types/shared.js';
|
|
5
5
|
|
|
@@ -11,6 +11,8 @@ declare function escapeHtml(str: string): string;
|
|
|
11
11
|
|
|
12
12
|
interface Route {
|
|
13
13
|
path: string;
|
|
14
|
+
hash: string;
|
|
15
|
+
query: string;
|
|
14
16
|
data: PageData;
|
|
15
17
|
component: Component | null;
|
|
16
18
|
}
|
|
@@ -22,7 +24,11 @@ interface Router {
|
|
|
22
24
|
/**
|
|
23
25
|
* Navigate to a new URL.
|
|
24
26
|
*/
|
|
25
|
-
go: (to
|
|
27
|
+
go: (to: string, options?: {
|
|
28
|
+
initialLoad?: boolean;
|
|
29
|
+
smoothScroll?: boolean;
|
|
30
|
+
replace?: boolean;
|
|
31
|
+
}) => Promise<void>;
|
|
26
32
|
/**
|
|
27
33
|
* Called before the route changes. Return `false` to cancel the navigation.
|
|
28
34
|
*/
|
|
@@ -40,10 +46,6 @@ interface Router {
|
|
|
40
46
|
* Called after the route changes.
|
|
41
47
|
*/
|
|
42
48
|
onAfterRouteChange?: (to: string) => Awaitable<void>;
|
|
43
|
-
/**
|
|
44
|
-
* @deprecated use `onAfterRouteChange` instead
|
|
45
|
-
*/
|
|
46
|
-
onAfterRouteChanged?: (to: string) => Awaitable<void>;
|
|
47
49
|
}
|
|
48
50
|
declare function useRouter(): Router;
|
|
49
51
|
declare function useRoute(): Route;
|
package/dist/client/shared.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i;
|
|
2
2
|
export const APPEARANCE_KEY = 'vitepress-theme-appearance';
|
|
3
|
+
export const VP_SOURCE_KEY = '[VP_SOURCE]';
|
|
4
|
+
const UnpackStackView = Symbol('stack-view:unpack');
|
|
3
5
|
const HASH_RE = /#.*$/;
|
|
4
6
|
const HASH_OR_QUERY_RE = /[?#].*$/;
|
|
5
7
|
const INDEX_OR_EXT_RE = /(?:(^|\/)index)?\.(?:md|html)$/;
|
|
@@ -42,26 +44,29 @@ export function isExternal(path) {
|
|
|
42
44
|
export function getLocaleForPath(siteData, relativePath) {
|
|
43
45
|
return (Object.keys(siteData?.locales || {}).find((key) => key !== 'root' &&
|
|
44
46
|
!isExternal(key) &&
|
|
45
|
-
isActive(relativePath,
|
|
47
|
+
isActive(relativePath, `^/${key}/`, true)) || 'root');
|
|
46
48
|
}
|
|
47
49
|
/**
|
|
48
50
|
* this merges the locales data to the main data by the route
|
|
49
51
|
*/
|
|
50
52
|
export function resolveSiteDataByRoute(siteData, relativePath) {
|
|
51
53
|
const localeIndex = getLocaleForPath(siteData, relativePath);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
const { label, link, ...localeConfig } = siteData.locales[localeIndex] ?? {};
|
|
55
|
+
Object.assign(localeConfig, { localeIndex });
|
|
56
|
+
const additionalConfigs = resolveAdditionalConfig(siteData, relativePath);
|
|
57
|
+
if (inBrowser && import.meta.env?.DEV) {
|
|
58
|
+
;
|
|
59
|
+
localeConfig[VP_SOURCE_KEY] = `locale config (${localeIndex})`;
|
|
60
|
+
reportConfigLayers(relativePath, [
|
|
61
|
+
...additionalConfigs,
|
|
62
|
+
localeConfig,
|
|
63
|
+
siteData
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
const topLayer = {
|
|
67
|
+
head: mergeHead(siteData.head ?? [], localeConfig.head ?? [], ...additionalConfigs.map((data) => data.head ?? []).reverse())
|
|
68
|
+
};
|
|
69
|
+
return stackView(topLayer, ...additionalConfigs, localeConfig, siteData);
|
|
65
70
|
}
|
|
66
71
|
/**
|
|
67
72
|
* Create the page title string based on config.
|
|
@@ -90,17 +95,29 @@ function createTitleTemplate(siteTitle, template) {
|
|
|
90
95
|
}
|
|
91
96
|
return ` | ${template}`;
|
|
92
97
|
}
|
|
93
|
-
function
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
export function mergeHead(...headArrays) {
|
|
99
|
+
const merged = [];
|
|
100
|
+
const metaKeyMap = new Map();
|
|
101
|
+
for (const current of headArrays) {
|
|
102
|
+
for (const tag of current) {
|
|
103
|
+
const [type, attrs] = tag;
|
|
104
|
+
const keyAttr = Object.entries(attrs)[0];
|
|
105
|
+
if (type !== 'meta' || !keyAttr) {
|
|
106
|
+
merged.push(tag);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const key = `${keyAttr[0]}=${keyAttr[1]}`;
|
|
110
|
+
const existingIndex = metaKeyMap.get(key);
|
|
111
|
+
if (existingIndex != null) {
|
|
112
|
+
merged[existingIndex] = tag; // replace existing tag
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
metaKeyMap.set(key, merged.length);
|
|
116
|
+
merged.push(tag);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return merged;
|
|
104
121
|
}
|
|
105
122
|
// https://github.com/rollup/rollup/blob/fec513270c6ac350072425cc045db367656c623b/src/utils/sanitizeFileName.ts
|
|
106
123
|
const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g;
|
|
@@ -150,3 +167,73 @@ export function escapeHtml(str) {
|
|
|
150
167
|
.replace(/"/g, '"')
|
|
151
168
|
.replace(/&(?![\w#]+;)/g, '&');
|
|
152
169
|
}
|
|
170
|
+
function resolveAdditionalConfig({ additionalConfig }, path) {
|
|
171
|
+
if (additionalConfig === undefined)
|
|
172
|
+
return [];
|
|
173
|
+
if (typeof additionalConfig === 'function')
|
|
174
|
+
return additionalConfig(path) ?? [];
|
|
175
|
+
const configs = [];
|
|
176
|
+
const segments = path.split('/').slice(0, -1); // remove file name
|
|
177
|
+
while (segments.length) {
|
|
178
|
+
const key = `/${segments.join('/')}/`;
|
|
179
|
+
configs.push(additionalConfig[key]);
|
|
180
|
+
segments.pop();
|
|
181
|
+
}
|
|
182
|
+
configs.push(additionalConfig['/']);
|
|
183
|
+
return configs.filter((config) => config !== undefined);
|
|
184
|
+
}
|
|
185
|
+
// This helps users to understand which configuration files are active
|
|
186
|
+
function reportConfigLayers(path, layers) {
|
|
187
|
+
const summaryTitle = `Config Layers for ${path}:`;
|
|
188
|
+
const summary = layers.map((c, i, arr) => {
|
|
189
|
+
const n = i + 1;
|
|
190
|
+
if (n === arr.length)
|
|
191
|
+
return `${n}. .vitepress/config (root)`;
|
|
192
|
+
return `${n}. ${c?.[VP_SOURCE_KEY] ?? '(Unknown Source)'}`;
|
|
193
|
+
});
|
|
194
|
+
console.debug([summaryTitle, ''.padEnd(summaryTitle.length, '='), ...summary].join('\n'));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Creates a deep, merged view of multiple objects without mutating originals.
|
|
198
|
+
* Returns a readonly proxy behaving like a merged object of the input objects.
|
|
199
|
+
* Layers are merged in descending precedence, i.e. earlier layer is on top.
|
|
200
|
+
*/
|
|
201
|
+
export function stackView(..._layers) {
|
|
202
|
+
const layers = _layers.filter((layer) => isObject(layer));
|
|
203
|
+
if (layers.length <= 1)
|
|
204
|
+
return _layers[0];
|
|
205
|
+
const allKeys = new Set(layers.flatMap((layer) => Reflect.ownKeys(layer)));
|
|
206
|
+
const allKeysArray = [...allKeys];
|
|
207
|
+
return new Proxy({}, {
|
|
208
|
+
// TODO: optimize for performance, this is a hot path
|
|
209
|
+
get(_, prop) {
|
|
210
|
+
if (prop === UnpackStackView)
|
|
211
|
+
return layers;
|
|
212
|
+
return stackView(...layers
|
|
213
|
+
.map((layer) => layer[prop])
|
|
214
|
+
.filter((v) => v !== undefined));
|
|
215
|
+
},
|
|
216
|
+
set() {
|
|
217
|
+
throw new Error('StackView is read-only and cannot be mutated.');
|
|
218
|
+
},
|
|
219
|
+
has(_, prop) {
|
|
220
|
+
return allKeys.has(prop);
|
|
221
|
+
},
|
|
222
|
+
ownKeys() {
|
|
223
|
+
return allKeysArray;
|
|
224
|
+
},
|
|
225
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
226
|
+
for (const layer of layers) {
|
|
227
|
+
const descriptor = Object.getOwnPropertyDescriptor(layer, prop);
|
|
228
|
+
if (descriptor)
|
|
229
|
+
return descriptor;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
stackView.unpack = function (obj) {
|
|
235
|
+
return obj?.[UnpackStackView];
|
|
236
|
+
};
|
|
237
|
+
export function isObject(value) {
|
|
238
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
239
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import { computed, provide, useSlots, watch } from 'vue'
|
|
2
|
+
import { computed, provide, useSlots } from 'vue'
|
|
4
3
|
import VPBackdrop from './components/VPBackdrop.vue'
|
|
5
4
|
import VPContent from './components/VPContent.vue'
|
|
6
5
|
import VPFooter from './components/VPFooter.vue'
|
|
@@ -9,29 +8,31 @@ import VPNav from './components/VPNav.vue'
|
|
|
9
8
|
import VPSidebar from './components/VPSidebar.vue'
|
|
10
9
|
import VPSkipLink from './components/VPSkipLink.vue'
|
|
11
10
|
import { useData } from './composables/data'
|
|
12
|
-
import {
|
|
11
|
+
import { layoutInfoInjectionKey, registerWatchers } from './composables/layout'
|
|
12
|
+
import { useSidebarControl } from './composables/sidebar'
|
|
13
13
|
|
|
14
14
|
const {
|
|
15
15
|
isOpen: isSidebarOpen,
|
|
16
16
|
open: openSidebar,
|
|
17
17
|
close: closeSidebar
|
|
18
|
-
} =
|
|
18
|
+
} = useSidebarControl()
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
watch(() => route.path, closeSidebar)
|
|
22
|
-
|
|
23
|
-
useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
|
|
20
|
+
registerWatchers({ closeSidebar })
|
|
24
21
|
|
|
25
22
|
const { frontmatter } = useData()
|
|
26
23
|
|
|
27
24
|
const slots = useSlots()
|
|
28
25
|
const heroImageSlotExists = computed(() => !!slots['home-hero-image'])
|
|
29
26
|
|
|
30
|
-
provide(
|
|
27
|
+
provide(layoutInfoInjectionKey, { heroImageSlotExists })
|
|
31
28
|
</script>
|
|
32
29
|
|
|
33
30
|
<template>
|
|
34
|
-
<div
|
|
31
|
+
<div
|
|
32
|
+
v-if="frontmatter.layout !== false"
|
|
33
|
+
class="Layout"
|
|
34
|
+
:class="frontmatter.pageClass"
|
|
35
|
+
>
|
|
35
36
|
<slot name="layout-top" />
|
|
36
37
|
<VPSkipLink />
|
|
37
38
|
<VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" />
|
|
@@ -22,7 +22,7 @@ const { currentLang } = useLangs()
|
|
|
22
22
|
<div class="action">
|
|
23
23
|
<a
|
|
24
24
|
class="link"
|
|
25
|
-
:href="withBase(currentLang.link)"
|
|
25
|
+
:href="withBase(theme.notFound?.link ?? currentLang.link)"
|
|
26
26
|
:aria-label="theme.notFound?.linkLabel ?? 'go to home'"
|
|
27
27
|
>
|
|
28
28
|
{{ theme.notFound?.linkText ?? 'Take me home' }}
|