vitepress 2.0.0-alpha.4 → 2.0.0-alpha.6

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 (50) hide show
  1. package/README.md +1 -1
  2. package/bin/vitepress.js +14 -0
  3. package/dist/client/app/composables/copyCode.js +8 -6
  4. package/dist/client/app/utils.js +3 -2
  5. package/dist/client/shared.js +112 -25
  6. package/dist/client/theme-default/Layout.vue +10 -9
  7. package/dist/client/theme-default/NotFound.vue +1 -1
  8. package/dist/client/theme-default/components/VPButton.vue +2 -2
  9. package/dist/client/theme-default/components/VPContent.vue +3 -6
  10. package/dist/client/theme-default/components/VPDoc.vue +2 -2
  11. package/dist/client/theme-default/components/VPDocAsideOutline.vue +8 -17
  12. package/dist/client/theme-default/components/VPDocAsideSponsors.vue +1 -1
  13. package/dist/client/theme-default/components/VPDocOutlineItem.vue +5 -3
  14. package/dist/client/theme-default/components/VPFeature.vue +2 -2
  15. package/dist/client/theme-default/components/VPFlyout.vue +1 -1
  16. package/dist/client/theme-default/components/VPFooter.vue +2 -2
  17. package/dist/client/theme-default/components/VPHero.vue +4 -1
  18. package/dist/client/theme-default/components/VPHomeSponsors.vue +1 -1
  19. package/dist/client/theme-default/components/VPLocalNav.vue +7 -23
  20. package/dist/client/theme-default/components/VPLocalNavOutlineDropdown.vue +5 -4
  21. package/dist/client/theme-default/components/VPLocalSearchBox.vue +4 -0
  22. package/dist/client/theme-default/components/VPMenu.vue +1 -1
  23. package/dist/client/theme-default/components/VPMenuGroup.vue +1 -1
  24. package/dist/client/theme-default/components/VPNavBar.vue +3 -5
  25. package/dist/client/theme-default/components/VPNavBarMenu.vue +2 -2
  26. package/dist/client/theme-default/components/VPNavBarSearch.vue +2 -2
  27. package/dist/client/theme-default/components/VPNavBarTitle.vue +4 -4
  28. package/dist/client/theme-default/components/VPNavScreenMenu.vue +1 -1
  29. package/dist/client/theme-default/components/VPNavScreenMenuGroup.vue +1 -1
  30. package/dist/client/theme-default/components/VPNavScreenMenuGroupSection.vue +1 -5
  31. package/dist/client/theme-default/components/VPSidebar.vue +3 -3
  32. package/dist/client/theme-default/components/VPSidebarGroup.vue +1 -1
  33. package/dist/client/theme-default/components/VPSidebarItem.vue +2 -2
  34. package/dist/client/theme-default/components/VPSocialLinks.vue +3 -3
  35. package/dist/client/theme-default/components/VPTeamMembers.vue +1 -1
  36. package/dist/client/theme-default/composables/aside.js +2 -2
  37. package/dist/client/theme-default/composables/layout.js +75 -0
  38. package/dist/client/theme-default/composables/sidebar.js +21 -63
  39. package/dist/client/theme-default/styles/components/vp-code.css +2 -2
  40. package/dist/client/theme-default/styles/components/vp-doc.css +2 -12
  41. package/dist/client/theme-default/without-fonts.js +1 -2
  42. package/dist/node/{chunk-BBDUp32J.js → chunk-cyL7XSn2.js} +1657 -1513
  43. package/dist/node/cli.js +2 -2
  44. package/dist/node/index.d.ts +48 -19
  45. package/dist/node/index.js +3 -3
  46. package/package.json +48 -43
  47. package/theme.d.ts +24 -3
  48. package/types/default-theme.d.ts +13 -37
  49. package/types/shared.d.ts +29 -2
  50. 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,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 = /language-(shellscript|shell|bash|sh|zsh)/.test(parent.className);
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
- .querySelectorAll(ignoredNodes.join(','))
19
- .forEach((node) => node.remove());
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();
@@ -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
 
@@ -663,6 +663,10 @@ function onMouseMove(e: MouseEvent) {
663
663
  width: 100%;
664
664
  }
665
665
 
666
+ .search-input::-webkit-search-cancel-button {
667
+ display: none;
668
+ }
669
+
666
670
  @media (max-width: 767px) {
667
671
  .search-input {
668
672
  padding: 6px 4px;
@@ -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>