vitepress 2.0.0-alpha.4 → 2.0.0-alpha.5

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.
Files changed (48) hide show
  1. package/README.md +1 -1
  2. package/bin/vitepress.js +14 -0
  3. package/dist/client/app/utils.js +3 -2
  4. package/dist/client/shared.js +112 -25
  5. package/dist/client/theme-default/Layout.vue +10 -9
  6. package/dist/client/theme-default/NotFound.vue +1 -1
  7. package/dist/client/theme-default/components/VPButton.vue +2 -2
  8. package/dist/client/theme-default/components/VPContent.vue +3 -6
  9. package/dist/client/theme-default/components/VPDoc.vue +2 -2
  10. package/dist/client/theme-default/components/VPDocAsideOutline.vue +8 -17
  11. package/dist/client/theme-default/components/VPDocAsideSponsors.vue +1 -1
  12. package/dist/client/theme-default/components/VPDocOutlineItem.vue +5 -3
  13. package/dist/client/theme-default/components/VPFeature.vue +2 -2
  14. package/dist/client/theme-default/components/VPFlyout.vue +1 -1
  15. package/dist/client/theme-default/components/VPFooter.vue +2 -2
  16. package/dist/client/theme-default/components/VPHero.vue +4 -1
  17. package/dist/client/theme-default/components/VPHomeSponsors.vue +1 -1
  18. package/dist/client/theme-default/components/VPLocalNav.vue +7 -23
  19. package/dist/client/theme-default/components/VPLocalNavOutlineDropdown.vue +5 -4
  20. package/dist/client/theme-default/components/VPMenu.vue +1 -1
  21. package/dist/client/theme-default/components/VPMenuGroup.vue +1 -1
  22. package/dist/client/theme-default/components/VPNavBar.vue +3 -5
  23. package/dist/client/theme-default/components/VPNavBarMenu.vue +2 -2
  24. package/dist/client/theme-default/components/VPNavBarSearch.vue +1 -1
  25. package/dist/client/theme-default/components/VPNavBarTitle.vue +4 -4
  26. package/dist/client/theme-default/components/VPNavScreenMenu.vue +1 -1
  27. package/dist/client/theme-default/components/VPNavScreenMenuGroup.vue +1 -1
  28. package/dist/client/theme-default/components/VPNavScreenMenuGroupSection.vue +1 -5
  29. package/dist/client/theme-default/components/VPSidebar.vue +3 -3
  30. package/dist/client/theme-default/components/VPSidebarGroup.vue +1 -1
  31. package/dist/client/theme-default/components/VPSidebarItem.vue +2 -2
  32. package/dist/client/theme-default/components/VPSocialLinks.vue +3 -3
  33. package/dist/client/theme-default/components/VPTeamMembers.vue +1 -1
  34. package/dist/client/theme-default/composables/aside.js +2 -2
  35. package/dist/client/theme-default/composables/layout.js +75 -0
  36. package/dist/client/theme-default/composables/sidebar.js +21 -63
  37. package/dist/client/theme-default/styles/components/vp-code.css +2 -2
  38. package/dist/client/theme-default/styles/components/vp-doc.css +0 -12
  39. package/dist/client/theme-default/without-fonts.js +1 -2
  40. package/dist/node/{chunk-BBDUp32J.js → chunk-CJIxUApD.js} +1162 -1015
  41. package/dist/node/cli.js +1 -2
  42. package/dist/node/index.d.ts +30 -6
  43. package/dist/node/index.js +2 -3
  44. package/package.json +35 -35
  45. package/theme.d.ts +24 -3
  46. package/types/default-theme.d.ts +13 -37
  47. package/types/shared.d.ts +29 -2
  48. package/dist/client/theme-default/composables/local-nav.js +0 -18
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # VitePress 📝💨
2
2
 
3
- [![test](https://github.com/vuejs/vitepress/workflows/Test/badge.svg)](https://github.com/vuejs/vitepress/actions)
3
+ [![test](https://github.com/vuejs/vitepress/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/vuejs/vitepress/actions/workflows/test.yml)
4
4
  [![npm](https://img.shields.io/npm/v/vitepress)](https://www.npmjs.com/package/vitepress)
5
5
  [![nightly releases](https://img.shields.io/badge/nightly-releases-orange)](https://nightly.akryum.dev/vuejs/vitepress)
6
6
  [![chat](https://img.shields.io/badge/chat-discord-blue?logo=discord)](https://chat.vuejs.org)
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,5 @@
1
- import { h, onMounted, onUnmounted, shallowRef } from 'vue';
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
- onUnmounted(() => {
66
+ tryOnUnmounted(() => {
66
67
  contentUpdatedCallbacks = contentUpdatedCallbacks.filter((f) => f !== fn);
67
68
  });
68
69
  }
@@ -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, `/${key}/`, true)) || 'root');
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
- return Object.assign({}, siteData, {
53
- localeIndex,
54
- lang: siteData.locales[localeIndex]?.lang ?? siteData.lang,
55
- dir: siteData.locales[localeIndex]?.dir ?? siteData.dir,
56
- title: siteData.locales[localeIndex]?.title ?? siteData.title,
57
- titleTemplate: siteData.locales[localeIndex]?.titleTemplate ?? siteData.titleTemplate,
58
- description: siteData.locales[localeIndex]?.description ?? siteData.description,
59
- head: mergeHead(siteData.head, siteData.locales[localeIndex]?.head ?? []),
60
- themeConfig: {
61
- ...siteData.themeConfig,
62
- ...siteData.locales[localeIndex]?.themeConfig
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 hasTag(head, tag) {
94
- const [tagType, tagAttrs] = tag;
95
- if (tagType !== 'meta')
96
- return false;
97
- const keyAttr = Object.entries(tagAttrs)[0]; // First key
98
- if (keyAttr == null)
99
- return false;
100
- return head.some(([type, attrs]) => type === tagType && attrs[keyAttr[0]] === keyAttr[1]);
101
- }
102
- export function mergeHead(prev, curr) {
103
- return [...prev.filter((tagAttrs) => !hasTag(curr, tagAttrs)), ...curr];
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, '&quot;')
151
168
  .replace(/&(?![\w#]+;)/g, '&amp;');
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 { useRoute } from 'vitepress'
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,18 +8,16 @@ 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 { useCloseSidebarOnEscape, useSidebar } from './composables/sidebar'
11
+ import { 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
- } = useSidebar()
18
+ } = useSidebarControl()
19
19
 
20
- const route = useRoute()
21
- watch(() => route.path, closeSidebar)
22
-
23
- useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
20
+ registerWatchers({ closeSidebar })
24
21
 
25
22
  const { frontmatter } = useData()
26
23
 
@@ -31,7 +28,11 @@ provide('hero-image-slot-exists', heroImageSlotExists)
31
28
  </script>
32
29
 
33
30
  <template>
34
- <div v-if="frontmatter.layout !== false" class="Layout" :class="frontmatter.pageClass" >
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' }}
@@ -7,7 +7,7 @@ interface Props {
7
7
  tag?: string
8
8
  size?: 'medium' | 'big'
9
9
  theme?: 'brand' | 'alt' | 'sponsor'
10
- text: string
10
+ text?: string
11
11
  href?: string
12
12
  target?: string;
13
13
  rel?: string;
@@ -35,7 +35,7 @@ const component = computed(() => {
35
35
  :target="props.target ?? (isExternal ? '_blank' : undefined)"
36
36
  :rel="props.rel ?? (isExternal ? 'noreferrer' : undefined)"
37
37
  >
38
- {{ text }}
38
+ <slot>{{ text }}</slot>
39
39
  </component>
40
40
  </template>
41
41
 
@@ -1,23 +1,20 @@
1
1
  <script setup lang="ts">
2
2
  import NotFound from '../NotFound.vue'
3
3
  import { useData } from '../composables/data'
4
- import { useSidebar } from '../composables/sidebar'
4
+ import { useLayout } from '../composables/layout'
5
5
  import VPDoc from './VPDoc.vue'
6
6
  import VPHome from './VPHome.vue'
7
7
  import VPPage from './VPPage.vue'
8
8
 
9
9
  const { page, frontmatter } = useData()
10
- const { hasSidebar } = useSidebar()
10
+ const { isHome, hasSidebar } = useLayout()
11
11
  </script>
12
12
 
13
13
  <template>
14
14
  <div
15
15
  class="VPContent"
16
16
  id="VPContent"
17
- :class="{
18
- 'has-sidebar': hasSidebar,
19
- 'is-home': frontmatter.layout === 'home'
20
- }"
17
+ :class="{ 'has-sidebar': hasSidebar, 'is-home': isHome }"
21
18
  >
22
19
  <slot name="not-found" v-if="page.isNotFound"><NotFound /></slot>
23
20
 
@@ -2,14 +2,14 @@
2
2
  import { useRoute } from 'vitepress'
3
3
  import { computed } from 'vue'
4
4
  import { useData } from '../composables/data'
5
- import { useSidebar } from '../composables/sidebar'
5
+ import { useLayout } from '../composables/layout'
6
6
  import VPDocAside from './VPDocAside.vue'
7
7
  import VPDocFooter from './VPDocFooter.vue'
8
8
 
9
9
  const { theme } = useData()
10
10
 
11
11
  const route = useRoute()
12
- const { hasSidebar, hasAside, leftAside } = useSidebar()
12
+ const { hasSidebar, hasAside, leftAside } = useLayout()
13
13
 
14
14
  const pageName = computed(() =>
15
15
  route.path.replace(/[./]+/g, '_').replace(/_html$/, '')
@@ -1,26 +1,17 @@
1
1
  <script setup lang="ts">
2
- import { onContentUpdated } from 'vitepress'
3
- import { ref, shallowRef } from 'vue'
2
+ import { ref } from 'vue'
4
3
  import { useData } from '../composables/data'
5
- import {
6
- getHeaders,
7
- resolveTitle,
8
- useActiveAnchor,
9
- type MenuItem
10
- } from '../composables/outline'
4
+ import { resolveTitle, useActiveAnchor } from '../composables/outline'
11
5
  import VPDocOutlineItem from './VPDocOutlineItem.vue'
6
+ import { useLayout } from '../composables/layout'
12
7
 
13
- const { frontmatter, theme } = useData()
14
-
15
- const headers = shallowRef<MenuItem[]>([])
16
-
17
- onContentUpdated(() => {
18
- headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
19
- })
8
+ const { theme } = useData()
20
9
 
21
10
  const container = ref()
22
11
  const marker = ref()
23
12
 
13
+ const { headers, hasLocalNav } = useLayout()
14
+
24
15
  useActiveAnchor(container, marker)
25
16
  </script>
26
17
 
@@ -28,7 +19,7 @@ useActiveAnchor(container, marker)
28
19
  <nav
29
20
  aria-labelledby="doc-outline-aria-label"
30
21
  class="VPDocAsideOutline"
31
- :class="{ 'has-outline': headers.length > 0 }"
22
+ :class="{ 'has-outline': hasLocalNav }"
32
23
  ref="container"
33
24
  >
34
25
  <div class="content">
@@ -43,7 +34,7 @@ useActiveAnchor(container, marker)
43
34
  {{ resolveTitle(theme) }}
44
35
  </div>
45
36
 
46
- <VPDocOutlineItem :headers="headers" :root="true" />
37
+ <VPDocOutlineItem :headers :root="true" />
47
38
  </div>
48
39
  </nav>
49
40
  </template>
@@ -12,6 +12,6 @@ defineProps<{
12
12
 
13
13
  <template>
14
14
  <div class="VPDocAsideSponsors">
15
- <VPSponsors mode="aside" :tier="tier" :size="size" :data="data" />
15
+ <VPSponsors mode="aside" :tier :size :data />
16
16
  </div>
17
17
  </template>
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
- import type { MenuItem } from '../composables/outline'
2
+ import type { DefaultTheme } from 'vitepress/theme'
3
3
 
4
4
  defineProps<{
5
- headers: MenuItem[]
5
+ headers: DefaultTheme.OutlineItem[]
6
6
  root?: boolean
7
7
  }>()
8
8
 
@@ -16,7 +16,9 @@ function onClick({ target: el }: Event) {
16
16
  <template>
17
17
  <ul class="VPDocOutlineItem" :class="root ? 'root' : 'nested'">
18
18
  <li v-for="{ children, link, title } in headers">
19
- <a class="outline-link" :href="link" @click="onClick" :title="title">{{ title }}</a>
19
+ <a class="outline-link" :href="link" @click="onClick" :title>
20
+ {{ title }}
21
+ </a>
20
22
  <template v-if="children?.length">
21
23
  <VPDocOutlineItem :headers="children" />
22
24
  </template>
@@ -18,8 +18,8 @@ defineProps<{
18
18
  <VPLink
19
19
  class="VPFeature"
20
20
  :href="link"
21
- :rel="rel"
22
- :target="target"
21
+ :rel
22
+ :target
23
23
  :no-icon="true"
24
24
  :tag="link ? 'a' : 'div'"
25
25
  >
@@ -45,7 +45,7 @@ function onBlur() {
45
45
  </button>
46
46
 
47
47
  <div class="menu">
48
- <VPMenu :items="items">
48
+ <VPMenu :items>
49
49
  <slot />
50
50
  </VPMenu>
51
51
  </div>
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { useData } from '../composables/data'
3
- import { useSidebar } from '../composables/sidebar'
3
+ import { useLayout } from '../composables/layout'
4
4
 
5
5
  const { theme, frontmatter } = useData()
6
- const { hasSidebar } = useSidebar()
6
+ const { hasSidebar } = useLayout()
7
7
  </script>
8
8
 
9
9
  <template>
@@ -57,7 +57,7 @@ const heroImageSlotExists = inject('hero-image-slot-exists') as Ref<boolean>
57
57
  <div class="image-container">
58
58
  <div class="image-bg" />
59
59
  <slot name="home-hero-image">
60
- <VPImage v-if="image" class="image-src" :image="image" />
60
+ <VPImage v-if="image" class="image-src" :image />
61
61
  </slot>
62
62
  </div>
63
63
  </div>
@@ -322,6 +322,9 @@ const heroImageSlotExists = inject('hero-image-slot-exists') as Ref<boolean>
322
322
  left: 50%;
323
323
  max-width: 192px;
324
324
  max-height: 192px;
325
+ width: 100%;
326
+ height: 100%;
327
+ object-fit: contain;
325
328
  /*rtl:ignore*/
326
329
  transform: translate(-50%, -50%);
327
330
  }
@@ -36,7 +36,7 @@ withDefaults(defineProps<Props>(), {
36
36
  </div>
37
37
 
38
38
  <div class="sponsors">
39
- <VPSponsors :data="data" />
39
+ <VPSponsors :data />
40
40
  </div>
41
41
 
42
42
  <div v-if="actionLink" class="action">
@@ -1,11 +1,8 @@
1
1
  <script lang="ts" setup>
2
2
  import { useWindowScroll } from '@vueuse/core'
3
- import { onContentUpdated } from 'vitepress'
4
3
  import { computed, onMounted, ref } from 'vue'
5
4
  import { useData } from '../composables/data'
6
- import { useLocalNav } from '../composables/local-nav'
7
- import { getHeaders } from '../composables/outline'
8
- import { useSidebar } from '../composables/sidebar'
5
+ import { useLayout } from '../composables/layout'
9
6
  import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue'
10
7
 
11
8
  defineProps<{
@@ -16,9 +13,8 @@ defineEmits<{
16
13
  (e: 'open-menu'): void
17
14
  }>()
18
15
 
19
- const { theme, frontmatter } = useData()
20
- const { hasSidebar } = useSidebar()
21
- const { headers } = useLocalNav()
16
+ const { theme } = useData()
17
+ const { isHome, hasSidebar, headers, hasLocalNav } = useLayout()
22
18
  const { y } = useWindowScroll()
23
19
 
24
20
  const navHeight = ref(0)
@@ -31,31 +27,19 @@ onMounted(() => {
31
27
  )
32
28
  })
33
29
 
34
- onContentUpdated(() => {
35
- headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
36
- })
37
-
38
- const empty = computed(() => {
39
- return headers.value.length === 0
40
- })
41
-
42
- const emptyAndNoSidebar = computed(() => {
43
- return empty.value && !hasSidebar.value
44
- })
45
-
46
30
  const classes = computed(() => {
47
31
  return {
48
32
  VPLocalNav: true,
49
33
  'has-sidebar': hasSidebar.value,
50
- empty: empty.value,
51
- fixed: emptyAndNoSidebar.value
34
+ empty: !hasLocalNav.value,
35
+ fixed: !hasLocalNav.value && !hasSidebar.value,
52
36
  }
53
37
  })
54
38
  </script>
55
39
 
56
40
  <template>
57
41
  <div
58
- v-if="frontmatter.layout !== 'home' && (!emptyAndNoSidebar || y >= navHeight)"
42
+ v-if="!isHome && (hasLocalNav || hasSidebar || y >= navHeight)"
59
43
  :class="classes"
60
44
  >
61
45
  <div class="container">
@@ -72,7 +56,7 @@ const classes = computed(() => {
72
56
  </span>
73
57
  </button>
74
58
 
75
- <VPLocalNavOutlineDropdown :headers="headers" :navHeight="navHeight" />
59
+ <VPLocalNavOutlineDropdown :headers :navHeight />
76
60
  </div>
77
61
  </div>
78
62
  </template>
@@ -1,13 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import { onKeyStroke } from '@vueuse/core'
3
3
  import { onContentUpdated } from 'vitepress'
4
+ import type { DefaultTheme } from 'vitepress/theme'
4
5
  import { nextTick, ref, watch } from 'vue'
5
6
  import { useData } from '../composables/data'
6
- import { resolveTitle, type MenuItem } from '../composables/outline'
7
+ import { resolveTitle } from '../composables/outline'
7
8
  import VPDocOutlineItem from './VPDocOutlineItem.vue'
8
9
 
9
10
  const props = defineProps<{
10
- headers: MenuItem[]
11
+ headers: DefaultTheme.OutlineItem[]
11
12
  navHeight: number
12
13
  }>()
13
14
 
@@ -83,7 +84,7 @@ function scrollToTop() {
83
84
  </a>
84
85
  </div>
85
86
  <div class="outline">
86
- <VPDocOutlineItem :headers="headers" />
87
+ <VPDocOutlineItem :headers />
87
88
  </div>
88
89
  </div>
89
90
  </Transition>
@@ -125,7 +126,7 @@ function scrollToTop() {
125
126
  vertical-align: middle;
126
127
  margin-left: 2px;
127
128
  font-size: 14px;
128
- transform: rotate(0)/*rtl:rotate(180deg)*/;
129
+ transform: rotate(0) /*rtl:rotate(180deg)*/;
129
130
  transition: transform 0.25s;
130
131
  }
131
132
 
@@ -11,7 +11,7 @@ defineProps<{
11
11
  <div class="VPMenu">
12
12
  <div v-if="items" class="items">
13
13
  <template v-for="item in items" :key="JSON.stringify(item)">
14
- <VPMenuLink v-if="'link' in item" :item="item" />
14
+ <VPMenuLink v-if="'link' in item" :item />
15
15
  <component
16
16
  v-else-if="'component' in item"
17
17
  :is="item.component"
@@ -12,7 +12,7 @@ defineProps<{
12
12
  <p v-if="text" class="title">{{ text }}</p>
13
13
 
14
14
  <template v-for="item in items">
15
- <VPMenuLink v-if="'link' in item" :item="item" />
15
+ <VPMenuLink v-if="'link' in item" :item />
16
16
  </template>
17
17
  </div>
18
18
  </template>
@@ -1,8 +1,7 @@
1
1
  <script lang="ts" setup>
2
2
  import { useWindowScroll } from '@vueuse/core'
3
3
  import { ref, watchPostEffect } from 'vue'
4
- import { useData } from '../composables/data'
5
- import { useSidebar } from '../composables/sidebar'
4
+ import { useLayout } from '../composables/layout'
6
5
  import VPNavBarAppearance from './VPNavBarAppearance.vue'
7
6
  import VPNavBarExtra from './VPNavBarExtra.vue'
8
7
  import VPNavBarHamburger from './VPNavBarHamburger.vue'
@@ -21,15 +20,14 @@ defineEmits<{
21
20
  }>()
22
21
 
23
22
  const { y } = useWindowScroll()
24
- const { hasSidebar } = useSidebar()
25
- const { frontmatter } = useData()
23
+ const { isHome, hasSidebar } = useLayout()
26
24
 
27
25
  const classes = ref<Record<string, boolean>>({})
28
26
 
29
27
  watchPostEffect(() => {
30
28
  classes.value = {
31
29
  'has-sidebar': hasSidebar.value,
32
- 'home': frontmatter.value.layout === 'home',
30
+ 'home': isHome.value,
33
31
  'top': y.value === 0,
34
32
  'screen-open': props.isScreenOpen
35
33
  }
@@ -16,13 +16,13 @@ const { theme } = useData()
16
16
  Main Navigation
17
17
  </span>
18
18
  <template v-for="item in theme.nav" :key="JSON.stringify(item)">
19
- <VPNavBarMenuLink v-if="'link' in item" :item="item" />
19
+ <VPNavBarMenuLink v-if="'link' in item" :item />
20
20
  <component
21
21
  v-else-if="'component' in item"
22
22
  :is="item.component"
23
23
  v-bind="item.props"
24
24
  />
25
- <VPNavBarMenuGroup v-else :item="item" />
25
+ <VPNavBarMenuGroup v-else :item />
26
26
  </template>
27
27
  </nav>
28
28
  </template>