feat(theme)!: add `isHome` frontmatter option (#4673)

BREAKING CHANGE: `useLocalNav` and `useSidebar` are removed in favor of `useLayout`. To migrate, just do find and replace. Sidebar controls are no longer exported, but I didn't find any usage on GitHub. If there is demand, we can export respective composables later. `DefaultTheme.DocSidebar` and `DefaultTheme.DocLocalNav` types are also removed.

---------

Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com>
Co-authored-by: userquin <userquin@gmail.com>
pull/4600/merge
Yuxuan Zhang 6 months ago committed by GitHub
parent ea5cbfca39
commit 544cd81259
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -30,7 +30,7 @@ function removeSpaces(str: string) {
<input <input
type="radio" type="radio"
:id="option.key" :id="option.key"
:name="name" :name
:value="option.value" :value="option.value"
v-model="selected" v-model="selected"
/> />

@ -457,3 +457,38 @@ Can be used to customize the label of the skip to content link. This link is sho
- Default: `false` - Default: `false`
Whether to show an external link icon next to external links in markdown. Whether to show an external link icon next to external links in markdown.
## `useLayout` <Badge type="info" text="composable" />
Returns layout-related data. The returned object has the following type:
```ts
interface {
isHome: ComputedRef<boolean>
sidebar: ComputedRef<DefaultTheme.SidebarItem[]>
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>
hasSidebar: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>
headers: ShallowRef<DefaultTheme.OutlineItem[]>
hasLocalNav: ComputedRef<boolean>
}
```
**Example:**
```vue
<script setup>
import { useLayout } from 'vitepress/theme'
const { hasSidebar } = useLayout()
</script>
<template>
<div v-if="hasSidebar">Only show when sidebar exists</div>
</template>
```

@ -180,36 +180,3 @@ export default {
} }
} }
``` ```
## `useSidebar` <Badge type="info" text="composable" />
Returns sidebar-related data. The returned object has the following type:
```ts
export interface DocSidebar {
isOpen: Ref<boolean>
sidebar: ComputedRef<DefaultTheme.SidebarItem[]>
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>
hasSidebar: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
open: () => void
close: () => void
toggle: () => void
}
```
**Example:**
```vue
<script setup>
import { useSidebar } from 'vitepress/theme'
const { hasSidebar } = useSidebar()
</script>
<template>
<div v-if="hasSidebar">Only show when sidebar exists</div>
</template>
```

@ -53,12 +53,12 @@ const members = [
Say hello to our awesome team. Say hello to our awesome team.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
``` ```
The above will display a team member in card looking element. It should display something similar to below. The above will display a team member in card looking element. It should display something similar to below.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
`<VPTeamMembers>` component comes in 2 different sizes, `small` and `medium`. While it boils down to your preference, usually `small` size should fit better when used in doc page. Also, you may add more properties to each member such as adding "description" or "sponsor" button. Learn more about it in [`<VPTeamMembers>`](#vpteammembers). `<VPTeamMembers>` component comes in 2 different sizes, `small` and `medium`. While it boils down to your preference, usually `small` size should fit better when used in doc page. Also, you may add more properties to each member such as adding "description" or "sponsor" button. Learn more about it in [`<VPTeamMembers>`](#vpteammembers).
@ -107,9 +107,7 @@ const members = [
team, some of whom have chosen to be featured below. team, some of whom have chosen to be featured below.
</template> </template>
</VPTeamPageTitle> </VPTeamPageTitle>
<VPTeamMembers <VPTeamMembers :members />
:members="members"
/>
</VPTeamPage> </VPTeamPage>
``` ```

@ -225,3 +225,16 @@ Then you can customize styles of this specific page in `.vitepress/theme/custom.
/* page-specific styles */ /* page-specific styles */
} }
``` ```
### isHome
- Type: `boolean`
The default theme relies on checks like `frontmatter.layout === 'home'` to determine if the current page is the home page.\
This is useful when you want to force show the home page elements in a custom layout.
```yaml
---
isHome: true
---
```

@ -181,36 +181,3 @@ export default {
} }
} }
``` ```
## `useSidebar` <Badge type="info" text="composable" />
Devuelve datos relacionados con la barra lateral. El objeto devuelto tiene el siguiente tipo:
```ts
export interface DocSidebar {
isOpen: Ref<boolean>
sidebar: ComputedRef<DefaultTheme.SidebarItem[]>
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>
hasSidebar: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
open: () => void
close: () => void
toggle: () => void
}
```
**Exemplo:**
```vue
<script setup>
import { useSidebar } from 'vitepress/theme'
const { hasSidebar } = useSidebar()
</script>
<template>
<div v-if="hasSidebar">Sólo visible cuando existe la barra lateral</div>
</template>
```

@ -53,12 +53,12 @@ const members = [
Saluda a nuestro increible equipo. Saluda a nuestro increible equipo.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
``` ```
El código anterior mostrará a un miembro del equipo en un elemento similar a una tarjeta. Debería mostrar algo similar a lo siguiente. El código anterior mostrará a un miembro del equipo en un elemento similar a una tarjeta. Debería mostrar algo similar a lo siguiente.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
El componente `<VPTeamMembers>` viene en dos tamaños diferentes, pequeño `small` y médio `medium`. Si bien es una cuestión de preferencia, generalmente el tamaño `small` debería encajar mejor cuando se use en la página del documento. Además, puede agregar más propiedades a cada miembro, como agregar el botón "descripción" o "patrocinador". Obtenga más información sobre en [`<VPTeamMembers>`](#vpteammembers). El componente `<VPTeamMembers>` viene en dos tamaños diferentes, pequeño `small` y médio `medium`. Si bien es una cuestión de preferencia, generalmente el tamaño `small` debería encajar mejor cuando se use en la página del documento. Además, puede agregar más propiedades a cada miembro, como agregar el botón "descripción" o "patrocinador". Obtenga más información sobre en [`<VPTeamMembers>`](#vpteammembers).
@ -107,9 +107,7 @@ const members = [
Algunos de los miembros han elegido aparecer a continuación. Algunos de los miembros han elegido aparecer a continuación.
</template> </template>
</VPTeamPageTitle> </VPTeamPageTitle>
<VPTeamMembers <VPTeamMembers :members />
:members="members"
/>
</VPTeamPage> </VPTeamPage>
``` ```

@ -178,38 +178,3 @@ export default {
} }
} }
``` ```
## `useSidebar` <Badge type="info" text="composable" /> {#usesidebar}
داده‌های مربوط به نوار کناری را برمی‌گرداند. شیء برگردانده شده دارای نوع‌های زیر است:
```ts
export interface DocSidebar {
isOpen: Ref<boolean>
sidebar: ComputedRef<DefaultTheme.SidebarItem[]>
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>
hasSidebar: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
open: () => void
close: () => void
toggle: () => void
}
```
**مثال:**
```vue
<script setup>
import { useSidebar } from 'vitepress/theme'
const { hasSidebar } = useSidebar()
</script>
<template>
<div v-if="hasSidebar">فقط ن
مایش داده شود زمانی که نوار کناری وجود دارد</div>
</template>
```

@ -53,12 +53,12 @@ const members = [
با سلام به تیم فوق‌العاده‌ی ما خوش آمدید. با سلام به تیم فوق‌العاده‌ی ما خوش آمدید.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
``` ```
بالا به صورت عنصری با شکل کارتی اعضای تیم را نمایش می‌دهد. باید به شکل زیر نمایش داده شود. بالا به صورت عنصری با شکل کارتی اعضای تیم را نمایش می‌دهد. باید به شکل زیر نمایش داده شود.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
کامپوننت `<VPTeamMembers>` دارای دو اندازه مختلف، `small` و `medium` است. معمولاً اندازه `small` برای استفاده در صفحات مستندات مناسب‌تر است. همچنین می‌توانید ویژگی‌های بیشتری برای هر عضو اضافه کنید مانند "توضیحات" یا "دکمه حامی". جهت کسب اطلاعات بیشتر به [`<VPTeamMembers>`](#vpteammembers) مراجعه کنید. کامپوننت `<VPTeamMembers>` دارای دو اندازه مختلف، `small` و `medium` است. معمولاً اندازه `small` برای استفاده در صفحات مستندات مناسب‌تر است. همچنین می‌توانید ویژگی‌های بیشتری برای هر عضو اضافه کنید مانند "توضیحات" یا "دکمه حامی". جهت کسب اطلاعات بیشتر به [`<VPTeamMembers>`](#vpteammembers) مراجعه کنید.
@ -106,9 +106,7 @@ const members = [
توسعه ویت‌پرس توسط تیمی بین‌المللی راهنمایی می‌شود، برخی از اعضا که انتخاب کرده‌اند تا در زیر نمایش داده شوند. توسعه ویت‌پرس توسط تیمی بین‌المللی راهنمایی می‌شود، برخی از اعضا که انتخاب کرده‌اند تا در زیر نمایش داده شوند.
</template> </template>
</VPTeamPageTitle> </VPTeamPageTitle>
<VPTeamMembers <VPTeamMembers :members />
:members="members"
/>
</VPTeamPage> </VPTeamPage>
``` ```

@ -180,36 +180,3 @@ export default {
} }
} }
``` ```
## `useSidebar` <Badge type="info" text="composable" />
사이드바 관련 데이터를 반환합니다. 반환된 객체는 다음과 같은 타입을 가집니다:
```ts
export interface DocSidebar {
isOpen: Ref<boolean>
sidebar: ComputedRef<DefaultTheme.SidebarItem[]>
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>
hasSidebar: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
open: () => void
close: () => void
toggle: () => void
}
```
**예제:**
```vue
<script setup>
import { useSidebar } from 'vitepress/theme'
const { hasSidebar } = useSidebar()
</script>
<template>
<div v-if="hasSidebar">사이드바가 있을 때만 보여줍니다</div>
</template>
```

@ -53,12 +53,12 @@ const members = [
Say hello to our awesome team. Say hello to our awesome team.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
``` ```
위 코드는 카드 형태의 엘리먼트로 팀 구성원을 표시합니다. 아래와 비슷한 형태로 표시됩니다. 위 코드는 카드 형태의 엘리먼트로 팀 구성원을 표시합니다. 아래와 비슷한 형태로 표시됩니다.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
`<VPTeamMembers>` 컴포넌트는 `small``medium` 두 가지 크기로 제공됩니다. 개인의 선호도에 따라 선택할 수 있지만, 일반적으로 `small` 사이즈가 문서 페이지에 더 적합합니다. 또한, 각 구성원에 "설명"이나 "후원" 버튼과 같은 프로퍼티를 추가할 수도 있습니다. 자세한 내용은 [`<VPTeamMembers>`](#vpteammembers)에서 확인할 수 있습니다. `<VPTeamMembers>` 컴포넌트는 `small``medium` 두 가지 크기로 제공됩니다. 개인의 선호도에 따라 선택할 수 있지만, 일반적으로 `small` 사이즈가 문서 페이지에 더 적합합니다. 또한, 각 구성원에 "설명"이나 "후원" 버튼과 같은 프로퍼티를 추가할 수도 있습니다. 자세한 내용은 [`<VPTeamMembers>`](#vpteammembers)에서 확인할 수 있습니다.
@ -107,9 +107,7 @@ const members = [
team, some of whom have chosen to be featured below. team, some of whom have chosen to be featured below.
</template> </template>
</VPTeamPageTitle> </VPTeamPageTitle>
<VPTeamMembers <VPTeamMembers :members />
:members="members"
/>
</VPTeamPage> </VPTeamPage>
``` ```

@ -180,36 +180,3 @@ export default {
} }
} }
``` ```
## `useSidebar` <Badge type="info" text="composable" />
Retorna dados relacionados à barra lateral. O objeto retornado tem o seguinte tipo:
```ts
export interface DocSidebar {
isOpen: Ref<boolean>
sidebar: ComputedRef<DefaultTheme.SidebarItem[]>
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>
hasSidebar: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
open: () => void
close: () => void
toggle: () => void
}
```
**Exemplo:**
```vue
<script setup>
import { useSidebar } from 'vitepress/theme'
const { hasSidebar } = useSidebar()
</script>
<template>
<div v-if="hasSidebar">Visível apenas quando a barra lateral existe</div>
</template>
```

@ -53,12 +53,12 @@ const members = [
Diga olá à nossa equipe incrível. Diga olá à nossa equipe incrível.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
``` ```
O código acima exibirá um membro da equipe em um elemento tipo cartão. Ele deve exibir algo semelhante ao abaixo. O código acima exibirá um membro da equipe em um elemento tipo cartão. Ele deve exibir algo semelhante ao abaixo.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
O componente `<VPTeamMembers>` vem em 2 tamanhos diferentes, pequeno `small` e médio `medium`. Enquanto é uma questão de preferência, geralmente o tamanho `small` deve encaixar melhor quando usado na página de documento. Além disso, você pode adicionar mais propriedades a cada membro, como adicionar o botão "descrição" ou "patrocinador". Saiba mais sobre em [`<VPTeamMembers>`](#vpteammembers). O componente `<VPTeamMembers>` vem em 2 tamanhos diferentes, pequeno `small` e médio `medium`. Enquanto é uma questão de preferência, geralmente o tamanho `small` deve encaixar melhor quando usado na página de documento. Além disso, você pode adicionar mais propriedades a cada membro, como adicionar o botão "descrição" ou "patrocinador". Saiba mais sobre em [`<VPTeamMembers>`](#vpteammembers).
@ -107,9 +107,7 @@ const members = [
alguns dos membros escolheram ser apresentados abaixo. alguns dos membros escolheram ser apresentados abaixo.
</template> </template>
</VPTeamPageTitle> </VPTeamPageTitle>
<VPTeamMembers <VPTeamMembers :members />
:members="members"
/>
</VPTeamPage> </VPTeamPage>
``` ```

@ -178,36 +178,3 @@ export default {
} }
} }
``` ```
## `useSidebar` <Badge type="info" text="композабл" /> {#usesidebar}
Возвращает данные, связанные с сайдбаром. Возвращаемый объект имеет следующий тип:
```ts
export interface DocSidebar {
isOpen: Ref<boolean>
sidebar: ComputedRef<DefaultTheme.SidebarItem[]>
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>
hasSidebar: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
open: () => void
close: () => void
toggle: () => void
}
```
**Пример:**
```vue
<script setup>
import { useSidebar } from 'vitepress/theme'
const { hasSidebar } = useSidebar()
</script>
<template>
<div v-if="hasSidebar">Показывать только при наличии сайдбара</div>
</template>
```

@ -51,12 +51,12 @@ const members = [
# Поприветствуйте нашу замечательную команду # Поприветствуйте нашу замечательную команду
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
``` ```
Вышеуказанное отобразит члена команды в виде карточки. Должно отобразиться что-то похожее на то, что показано ниже. Вышеуказанное отобразит члена команды в виде карточки. Должно отобразиться что-то похожее на то, что показано ниже.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
Компонент `<VPTeamMembers>` поставляется в двух различных размерах, `small` и `medium`. Хотя это зависит от ваших предпочтений, обычно размер `small` лучше подходит для использования на странице с макетом `doc`. Кроме того, вы можете добавить дополнительные свойства для карточки члена команды, например, добавить «описание» или кнопку «спонсировать». Подробнее об этом в секции [`<VPTeamMembers>`](#vpteammembers). Компонент `<VPTeamMembers>` поставляется в двух различных размерах, `small` и `medium`. Хотя это зависит от ваших предпочтений, обычно размер `small` лучше подходит для использования на странице с макетом `doc`. Кроме того, вы можете добавить дополнительные свойства для карточки члена команды, например, добавить «описание» или кнопку «спонсировать». Подробнее об этом в секции [`<VPTeamMembers>`](#vpteammembers).
@ -104,7 +104,7 @@ layout: page
которой представлены ниже. которой представлены ниже.
</template> </template>
</VPTeamPageTitle> </VPTeamPageTitle>
<VPTeamMembers :members="members" /> <VPTeamMembers :members />
</VPTeamPage> </VPTeamPage>
``` ```

@ -178,36 +178,3 @@ export default {
} }
} }
``` ```
## `useSidebar` <Badge type="info" text="composable" />
返回侧边栏相关数据。返回的对象具有以下类型:
```ts
export interface DocSidebar {
isOpen: Ref<boolean>
sidebar: ComputedRef<DefaultTheme.SidebarItem[]>
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>
hasSidebar: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
open: () => void
close: () => void
toggle: () => void
}
```
**示例:**
```vue
<script setup>
import { useSidebar } from 'vitepress/theme'
const { hasSidebar } = useSidebar()
</script>
<template>
<div v-if="hasSidebar">Only show when sidebar exists</div>
</template>
```

@ -53,12 +53,12 @@ const members = [
Say hello to our awesome team. Say hello to our awesome team.
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
``` ```
以上将在卡片外观元素中显示团队成员。它应该显示类似于下面的内容。 以上将在卡片外观元素中显示团队成员。它应该显示类似于下面的内容。
<VPTeamMembers size="small" :members="members" /> <VPTeamMembers size="small" :members />
`<VPTeamMembers>` 组件有 2 种不同的尺寸,`small` 和 `medium`。虽然它取决于你的偏好,但通常尺寸在文档页面中使用时 `small` 应该更适合。此外,你可以为每个成员添加更多属性,例如添加“描述”或“赞助”按钮。在 [`<VPTeamMembers>`](#vpteammembers) 中了解更多信息。 `<VPTeamMembers>` 组件有 2 种不同的尺寸,`small` 和 `medium`。虽然它取决于你的偏好,但通常尺寸在文档页面中使用时 `small` 应该更适合。此外,你可以为每个成员添加更多属性,例如添加“描述”或“赞助”按钮。在 [`<VPTeamMembers>`](#vpteammembers) 中了解更多信息。
@ -107,9 +107,7 @@ const members = [
team, some of whom have chosen to be featured below. team, some of whom have chosen to be featured below.
</template> </template>
</VPTeamPageTitle> </VPTeamPageTitle>
<VPTeamMembers <VPTeamMembers :members />
:members="members"
/>
</VPTeamPage> </VPTeamPage>
``` ```

@ -1,10 +1,5 @@
import { import { tryOnUnmounted } from '@vueuse/core'
h, import { h, onMounted, shallowRef, type AsyncComponentLoader } from 'vue'
onMounted,
onUnmounted,
shallowRef,
type AsyncComponentLoader
} from 'vue'
import { import {
EXTERNAL_URL_RE, EXTERNAL_URL_RE,
inBrowser, inBrowser,
@ -81,7 +76,7 @@ export let contentUpdatedCallbacks: (() => any)[] = []
*/ */
export function onContentUpdated(fn: () => any) { export function onContentUpdated(fn: () => any) {
contentUpdatedCallbacks.push(fn) contentUpdatedCallbacks.push(fn)
onUnmounted(() => { tryOnUnmounted(() => {
contentUpdatedCallbacks = contentUpdatedCallbacks.filter((f) => f !== fn) contentUpdatedCallbacks = contentUpdatedCallbacks.filter((f) => f !== fn)
}) })
} }

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRoute } from 'vitepress' import { computed, provide, useSlots } from 'vue'
import { computed, provide, useSlots, watch } from 'vue'
import VPBackdrop from './components/VPBackdrop.vue' import VPBackdrop from './components/VPBackdrop.vue'
import VPContent from './components/VPContent.vue' import VPContent from './components/VPContent.vue'
import VPFooter from './components/VPFooter.vue' import VPFooter from './components/VPFooter.vue'
@ -9,18 +8,16 @@ import VPNav from './components/VPNav.vue'
import VPSidebar from './components/VPSidebar.vue' import VPSidebar from './components/VPSidebar.vue'
import VPSkipLink from './components/VPSkipLink.vue' import VPSkipLink from './components/VPSkipLink.vue'
import { useData } from './composables/data' import { useData } from './composables/data'
import { useCloseSidebarOnEscape, useSidebar } from './composables/sidebar' import { registerWatchers } from './composables/layout'
import { useSidebarControl } from './composables/sidebar'
const { const {
isOpen: isSidebarOpen, isOpen: isSidebarOpen,
open: openSidebar, open: openSidebar,
close: closeSidebar close: closeSidebar
} = useSidebar() } = useSidebarControl()
const route = useRoute() registerWatchers({ closeSidebar })
watch(() => route.path, closeSidebar)
useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
const { frontmatter } = useData() const { frontmatter } = useData()
@ -31,7 +28,11 @@ provide('hero-image-slot-exists', heroImageSlotExists)
</script> </script>
<template> <template>
<div v-if="frontmatter.layout !== false" class="Layout" :class="frontmatter.pageClass" > <div
v-if="frontmatter.layout !== false"
class="Layout"
:class="frontmatter.pageClass"
>
<slot name="layout-top" /> <slot name="layout-top" />
<VPSkipLink /> <VPSkipLink />
<VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" /> <VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" />

@ -1,23 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import NotFound from '../NotFound.vue' import NotFound from '../NotFound.vue'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { useSidebar } from '../composables/sidebar' import { useLayout } from '../composables/layout'
import VPDoc from './VPDoc.vue' import VPDoc from './VPDoc.vue'
import VPHome from './VPHome.vue' import VPHome from './VPHome.vue'
import VPPage from './VPPage.vue' import VPPage from './VPPage.vue'
const { page, frontmatter } = useData() const { page, frontmatter } = useData()
const { hasSidebar } = useSidebar() const { isHome, hasSidebar } = useLayout()
</script> </script>
<template> <template>
<div <div
class="VPContent" class="VPContent"
id="VPContent" id="VPContent"
:class="{ :class="{ 'has-sidebar': hasSidebar, 'is-home': isHome }"
'has-sidebar': hasSidebar,
'is-home': frontmatter.layout === 'home'
}"
> >
<slot name="not-found" v-if="page.isNotFound"><NotFound /></slot> <slot name="not-found" v-if="page.isNotFound"><NotFound /></slot>

@ -2,14 +2,14 @@
import { useRoute } from 'vitepress' import { useRoute } from 'vitepress'
import { computed } from 'vue' import { computed } from 'vue'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { useSidebar } from '../composables/sidebar' import { useLayout } from '../composables/layout'
import VPDocAside from './VPDocAside.vue' import VPDocAside from './VPDocAside.vue'
import VPDocFooter from './VPDocFooter.vue' import VPDocFooter from './VPDocFooter.vue'
const { theme } = useData() const { theme } = useData()
const route = useRoute() const route = useRoute()
const { hasSidebar, hasAside, leftAside } = useSidebar() const { hasSidebar, hasAside, leftAside } = useLayout()
const pageName = computed(() => const pageName = computed(() =>
route.path.replace(/[./]+/g, '_').replace(/_html$/, '') route.path.replace(/[./]+/g, '_').replace(/_html$/, '')

@ -1,26 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { onContentUpdated } from 'vitepress' import { ref } from 'vue'
import { ref, shallowRef } from 'vue'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { import { resolveTitle, useActiveAnchor } from '../composables/outline'
getHeaders,
resolveTitle,
useActiveAnchor,
type MenuItem
} from '../composables/outline'
import VPDocOutlineItem from './VPDocOutlineItem.vue' import VPDocOutlineItem from './VPDocOutlineItem.vue'
import { useLayout } from '../composables/layout'
const { frontmatter, theme } = useData() const { theme } = useData()
const headers = shallowRef<MenuItem[]>([])
onContentUpdated(() => {
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
})
const container = ref() const container = ref()
const marker = ref() const marker = ref()
const { headers, hasLocalNav } = useLayout()
useActiveAnchor(container, marker) useActiveAnchor(container, marker)
</script> </script>
@ -28,7 +19,7 @@ useActiveAnchor(container, marker)
<nav <nav
aria-labelledby="doc-outline-aria-label" aria-labelledby="doc-outline-aria-label"
class="VPDocAsideOutline" class="VPDocAsideOutline"
:class="{ 'has-outline': headers.length > 0 }" :class="{ 'has-outline': hasLocalNav }"
ref="container" ref="container"
> >
<div class="content"> <div class="content">
@ -43,7 +34,7 @@ useActiveAnchor(container, marker)
{{ resolveTitle(theme) }} {{ resolveTitle(theme) }}
</div> </div>
<VPDocOutlineItem :headers="headers" :root="true" /> <VPDocOutlineItem :headers :root="true" />
</div> </div>
</nav> </nav>
</template> </template>

@ -12,6 +12,6 @@ defineProps<{
<template> <template>
<div class="VPDocAsideSponsors"> <div class="VPDocAsideSponsors">
<VPSponsors mode="aside" :tier="tier" :size="size" :data="data" /> <VPSponsors mode="aside" :tier :size :data />
</div> </div>
</template> </template>

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { MenuItem } from '../composables/outline' import type { DefaultTheme } from 'vitepress/theme'
defineProps<{ defineProps<{
headers: MenuItem[] headers: DefaultTheme.OutlineItem[]
root?: boolean root?: boolean
}>() }>()
@ -16,7 +16,9 @@ function onClick({ target: el }: Event) {
<template> <template>
<ul class="VPDocOutlineItem" :class="root ? 'root' : 'nested'"> <ul class="VPDocOutlineItem" :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title } in headers"> <li v-for="{ children, link, title } in headers">
<a class="outline-link" :href="link" @click="onClick" :title="title">{{ title }}</a> <a class="outline-link" :href="link" @click="onClick" :title>
{{ title }}
</a>
<template v-if="children?.length"> <template v-if="children?.length">
<VPDocOutlineItem :headers="children" /> <VPDocOutlineItem :headers="children" />
</template> </template>

@ -18,8 +18,8 @@ defineProps<{
<VPLink <VPLink
class="VPFeature" class="VPFeature"
:href="link" :href="link"
:rel="rel" :rel
:target="target" :target
:no-icon="true" :no-icon="true"
:tag="link ? 'a' : 'div'" :tag="link ? 'a' : 'div'"
> >

@ -45,7 +45,7 @@ function onBlur() {
</button> </button>
<div class="menu"> <div class="menu">
<VPMenu :items="items"> <VPMenu :items>
<slot /> <slot />
</VPMenu> </VPMenu>
</div> </div>

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { useSidebar } from '../composables/sidebar' import { useLayout } from '../composables/layout'
const { theme, frontmatter } = useData() const { theme, frontmatter } = useData()
const { hasSidebar } = useSidebar() const { hasSidebar } = useLayout()
</script> </script>
<template> <template>

@ -57,7 +57,7 @@ const heroImageSlotExists = inject('hero-image-slot-exists') as Ref<boolean>
<div class="image-container"> <div class="image-container">
<div class="image-bg" /> <div class="image-bg" />
<slot name="home-hero-image"> <slot name="home-hero-image">
<VPImage v-if="image" class="image-src" :image="image" /> <VPImage v-if="image" class="image-src" :image />
</slot> </slot>
</div> </div>
</div> </div>

@ -36,7 +36,7 @@ withDefaults(defineProps<Props>(), {
</div> </div>
<div class="sponsors"> <div class="sponsors">
<VPSponsors :data="data" /> <VPSponsors :data />
</div> </div>
<div v-if="actionLink" class="action"> <div v-if="actionLink" class="action">

@ -1,11 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useWindowScroll } from '@vueuse/core' import { useWindowScroll } from '@vueuse/core'
import { onContentUpdated } from 'vitepress'
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { useLocalNav } from '../composables/local-nav' import { useLayout } from '../composables/layout'
import { getHeaders } from '../composables/outline'
import { useSidebar } from '../composables/sidebar'
import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue' import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue'
defineProps<{ defineProps<{
@ -16,9 +13,8 @@ defineEmits<{
(e: 'open-menu'): void (e: 'open-menu'): void
}>() }>()
const { theme, frontmatter } = useData() const { theme } = useData()
const { hasSidebar } = useSidebar() const { isHome, hasSidebar, headers, hasLocalNav } = useLayout()
const { headers } = useLocalNav()
const { y } = useWindowScroll() const { y } = useWindowScroll()
const navHeight = ref(0) const navHeight = ref(0)
@ -31,31 +27,19 @@ onMounted(() => {
) )
}) })
onContentUpdated(() => {
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
})
const empty = computed(() => {
return headers.value.length === 0
})
const emptyAndNoSidebar = computed(() => {
return empty.value && !hasSidebar.value
})
const classes = computed(() => { const classes = computed(() => {
return { return {
VPLocalNav: true, VPLocalNav: true,
'has-sidebar': hasSidebar.value, 'has-sidebar': hasSidebar.value,
empty: empty.value, empty: !hasLocalNav.value,
fixed: emptyAndNoSidebar.value fixed: !hasLocalNav.value && !hasSidebar.value,
} }
}) })
</script> </script>
<template> <template>
<div <div
v-if="frontmatter.layout !== 'home' && (!emptyAndNoSidebar || y >= navHeight)" v-if="!isHome && (hasLocalNav || hasSidebar || y >= navHeight)"
:class="classes" :class="classes"
> >
<div class="container"> <div class="container">
@ -72,7 +56,7 @@ const classes = computed(() => {
</span> </span>
</button> </button>
<VPLocalNavOutlineDropdown :headers="headers" :navHeight="navHeight" /> <VPLocalNavOutlineDropdown :headers :navHeight />
</div> </div>
</div> </div>
</template> </template>

@ -1,13 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { onKeyStroke } from '@vueuse/core' import { onKeyStroke } from '@vueuse/core'
import { onContentUpdated } from 'vitepress' import { onContentUpdated } from 'vitepress'
import type { DefaultTheme } from 'vitepress/theme'
import { nextTick, ref, watch } from 'vue' import { nextTick, ref, watch } from 'vue'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { resolveTitle, type MenuItem } from '../composables/outline' import { resolveTitle } from '../composables/outline'
import VPDocOutlineItem from './VPDocOutlineItem.vue' import VPDocOutlineItem from './VPDocOutlineItem.vue'
const props = defineProps<{ const props = defineProps<{
headers: MenuItem[] headers: DefaultTheme.OutlineItem[]
navHeight: number navHeight: number
}>() }>()
@ -83,7 +84,7 @@ function scrollToTop() {
</a> </a>
</div> </div>
<div class="outline"> <div class="outline">
<VPDocOutlineItem :headers="headers" /> <VPDocOutlineItem :headers />
</div> </div>
</div> </div>
</Transition> </Transition>
@ -125,7 +126,7 @@ function scrollToTop() {
vertical-align: middle; vertical-align: middle;
margin-left: 2px; margin-left: 2px;
font-size: 14px; font-size: 14px;
transform: rotate(0)/*rtl:rotate(180deg)*/; transform: rotate(0) /*rtl:rotate(180deg)*/;
transition: transform 0.25s; transition: transform 0.25s;
} }

@ -11,7 +11,7 @@ defineProps<{
<div class="VPMenu"> <div class="VPMenu">
<div v-if="items" class="items"> <div v-if="items" class="items">
<template v-for="item in items" :key="JSON.stringify(item)"> <template v-for="item in items" :key="JSON.stringify(item)">
<VPMenuLink v-if="'link' in item" :item="item" /> <VPMenuLink v-if="'link' in item" :item />
<component <component
v-else-if="'component' in item" v-else-if="'component' in item"
:is="item.component" :is="item.component"

@ -12,7 +12,7 @@ defineProps<{
<p v-if="text" class="title">{{ text }}</p> <p v-if="text" class="title">{{ text }}</p>
<template v-for="item in items"> <template v-for="item in items">
<VPMenuLink v-if="'link' in item" :item="item" /> <VPMenuLink v-if="'link' in item" :item />
</template> </template>
</div> </div>
</template> </template>

@ -1,8 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useWindowScroll } from '@vueuse/core' import { useWindowScroll } from '@vueuse/core'
import { ref, watchPostEffect } from 'vue' import { ref, watchPostEffect } from 'vue'
import { useData } from '../composables/data' import { useLayout } from '../composables/layout'
import { useSidebar } from '../composables/sidebar'
import VPNavBarAppearance from './VPNavBarAppearance.vue' import VPNavBarAppearance from './VPNavBarAppearance.vue'
import VPNavBarExtra from './VPNavBarExtra.vue' import VPNavBarExtra from './VPNavBarExtra.vue'
import VPNavBarHamburger from './VPNavBarHamburger.vue' import VPNavBarHamburger from './VPNavBarHamburger.vue'
@ -21,15 +20,14 @@ defineEmits<{
}>() }>()
const { y } = useWindowScroll() const { y } = useWindowScroll()
const { hasSidebar } = useSidebar() const { isHome, hasSidebar } = useLayout()
const { frontmatter } = useData()
const classes = ref<Record<string, boolean>>({}) const classes = ref<Record<string, boolean>>({})
watchPostEffect(() => { watchPostEffect(() => {
classes.value = { classes.value = {
'has-sidebar': hasSidebar.value, 'has-sidebar': hasSidebar.value,
'home': frontmatter.value.layout === 'home', 'home': isHome.value,
'top': y.value === 0, 'top': y.value === 0,
'screen-open': props.isScreenOpen 'screen-open': props.isScreenOpen
} }

@ -16,13 +16,13 @@ const { theme } = useData()
Main Navigation Main Navigation
</span> </span>
<template v-for="item in theme.nav" :key="JSON.stringify(item)"> <template v-for="item in theme.nav" :key="JSON.stringify(item)">
<VPNavBarMenuLink v-if="'link' in item" :item="item" /> <VPNavBarMenuLink v-if="'link' in item" :item />
<component <component
v-else-if="'component' in item" v-else-if="'component' in item"
:is="item.component" :is="item.component"
v-bind="item.props" v-bind="item.props"
/> />
<VPNavBarMenuGroup v-else :item="item" /> <VPNavBarMenuGroup v-else :item />
</template> </template>
</nav> </nav>
</template> </template>

@ -1,13 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import '@docsearch/css' import '@docsearch/css'
import { onKeyStroke } from '@vueuse/core' import { onKeyStroke } from '@vueuse/core'
import type { DefaultTheme } from 'vitepress/theme'
import { import {
defineAsyncComponent, defineAsyncComponent,
onMounted, onMounted,
onUnmounted, onUnmounted,
ref ref
} from 'vue' } from 'vue'
import type { DefaultTheme } from '../../shared'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import VPNavBarSearchButton from './VPNavBarSearchButton.vue' import VPNavBarSearchButton from './VPNavBarSearchButton.vue'

@ -2,12 +2,12 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { useLangs } from '../composables/langs' import { useLangs } from '../composables/langs'
import { useSidebar } from '../composables/sidebar' import { useLayout } from '../composables/layout'
import { normalizeLink } from '../support/utils' import { normalizeLink } from '../support/utils'
import VPImage from './VPImage.vue' import VPImage from './VPImage.vue'
const { site, theme } = useData() const { site, theme } = useData()
const { hasSidebar } = useSidebar() const { hasSidebar } = useLayout()
const { currentLang } = useLangs() const { currentLang } = useLangs()
const link = computed(() => const link = computed(() =>
@ -34,8 +34,8 @@ const target = computed(() =>
<a <a
class="title" class="title"
:href="link ?? normalizeLink(currentLang.link)" :href="link ?? normalizeLink(currentLang.link)"
:rel="rel" :rel
:target="target" :target
> >
<slot name="nav-bar-title-before" /> <slot name="nav-bar-title-before" />
<VPImage v-if="theme.logo" class="logo" :image="theme.logo" /> <VPImage v-if="theme.logo" class="logo" :image="theme.logo" />

@ -9,7 +9,7 @@ const { theme } = useData()
<template> <template>
<nav v-if="theme.nav" class="VPNavScreenMenu"> <nav v-if="theme.nav" class="VPNavScreenMenu">
<template v-for="item in theme.nav" :key="JSON.stringify(item)"> <template v-for="item in theme.nav" :key="JSON.stringify(item)">
<VPNavScreenMenuLink v-if="'link' in item" :item="item" /> <VPNavScreenMenuLink v-if="'link' in item" :item />
<component <component
v-else-if="'component' in item" v-else-if="'component' in item"
:is="item.component" :is="item.component"

@ -34,7 +34,7 @@ function toggle() {
<div :id="groupId" class="items"> <div :id="groupId" class="items">
<template v-for="item in items" :key="JSON.stringify(item)"> <template v-for="item in items" :key="JSON.stringify(item)">
<div v-if="'link' in item" class="item"> <div v-if="'link' in item" class="item">
<VPNavScreenMenuGroupLink :item="item" /> <VPNavScreenMenuGroupLink :item />
</div> </div>
<div v-else-if="'component' in item" class="item"> <div v-else-if="'component' in item" class="item">

@ -11,11 +11,7 @@ defineProps<{
<template> <template>
<div class="VPNavScreenMenuGroupSection"> <div class="VPNavScreenMenuGroupSection">
<p v-if="text" class="title">{{ text }}</p> <p v-if="text" class="title">{{ text }}</p>
<VPNavScreenMenuGroupLink <VPNavScreenMenuGroupLink v-for="item in items" :key="item.text" :item />
v-for="item in items"
:key="item.text"
:item="item"
/>
</div> </div>
</template> </template>

@ -2,10 +2,10 @@
import { useScrollLock } from '@vueuse/core' import { useScrollLock } from '@vueuse/core'
import { inBrowser } from 'vitepress' import { inBrowser } from 'vitepress'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { useSidebar } from '../composables/sidebar' import { useLayout } from '../composables/layout'
import VPSidebarGroup from './VPSidebarGroup.vue' import VPSidebarGroup from './VPSidebarGroup.vue'
const { sidebarGroups, hasSidebar } = useSidebar() const { sidebarGroups, hasSidebar } = useLayout()
const props = defineProps<{ const props = defineProps<{
open: boolean open: boolean
@ -58,7 +58,7 @@ watch(
</span> </span>
<slot name="sidebar-nav-before" /> <slot name="sidebar-nav-before" />
<VPSidebarGroup :items="sidebarGroups" :key="key" /> <VPSidebarGroup :items="sidebarGroups" :key />
<slot name="sidebar-nav-after" /> <slot name="sidebar-nav-after" />
</nav> </nav>
</aside> </aside>

@ -33,7 +33,7 @@ onBeforeUnmount(() => {
class="group" class="group"
:class="{ 'no-transition': disableTransition }" :class="{ 'no-transition': disableTransition }"
> >
<VPSidebarItem :item="item" :depth="0" /> <VPSidebarItem :item :depth="0" />
</div> </div>
</template> </template>

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { computed } from 'vue' import { computed } from 'vue'
import { useSidebarControl } from '../composables/sidebar' import { useSidebarItemControl } from '../composables/sidebar'
import VPLink from './VPLink.vue' import VPLink from './VPLink.vue'
const props = defineProps<{ const props = defineProps<{
@ -17,7 +17,7 @@ const {
hasActiveLink, hasActiveLink,
hasChildren, hasChildren,
toggle toggle
} = useSidebarControl(computed(() => props.item)) } = useSidebarItemControl(computed(() => props.item))
const sectionTag = computed(() => (hasChildren.value ? 'section' : `div`)) const sectionTag = computed(() => (hasChildren.value ? 'section' : `div`))

@ -12,9 +12,9 @@ defineProps<{
<VPSocialLink <VPSocialLink
v-for="{ link, icon, ariaLabel } in links" v-for="{ link, icon, ariaLabel } in links"
:key="link" :key="link"
:icon="icon" :icon
:link="link" :link
:ariaLabel="ariaLabel" :ariaLabel
/> />
</div> </div>
</template> </template>

@ -19,7 +19,7 @@ const classes = computed(() => [props.size, `count-${props.members.length}`])
<div class="VPTeamMembers" :class="classes"> <div class="VPTeamMembers" :class="classes">
<div class="container"> <div class="container">
<div v-for="member in members" :key="member.name" class="item"> <div v-for="member in members" :key="member.name" class="item">
<VPTeamMembersItem :size="size" :member="member" /> <VPTeamMembersItem :size :member />
</div> </div>
</div> </div>
</div> </div>

@ -1,9 +1,9 @@
import { useMediaQuery } from '@vueuse/core' import { useMediaQuery } from '@vueuse/core'
import { computed } from 'vue' import { computed } from 'vue'
import { useSidebar } from './sidebar' import { useLayout } from './layout'
export function useAside() { export function useAside() {
const { hasSidebar } = useSidebar() const { hasSidebar } = useLayout()
const is960 = useMediaQuery('(min-width: 960px)') const is960 = useMediaQuery('(min-width: 960px)')
const is1280 = useMediaQuery('(min-width: 1280px)') const is1280 = useMediaQuery('(min-width: 1280px)')

@ -0,0 +1,85 @@
import { useMediaQuery } from '@vueuse/core'
import { onContentUpdated, useRoute } from 'vitepress'
import type { DefaultTheme } from 'vitepress/theme'
import { computed, shallowRef, watch } from 'vue'
import { getSidebar, getSidebarGroups } from '../support/sidebar'
import { useData } from './data'
import { getHeaders } from './outline'
import { useCloseSidebarOnEscape } from './sidebar'
const headers = shallowRef<DefaultTheme.OutlineItem[]>([])
export function useLayout() {
const { frontmatter, page, theme } = useData()
const is960 = useMediaQuery('(min-width: 960px)')
const isHome = computed(() => {
return !!(frontmatter.value.isHome ?? frontmatter.value.layout === 'home')
})
const sidebar = computed(() => {
const sidebarConfig = theme.value.sidebar
const relativePath = page.value.relativePath
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : []
})
const hasSidebar = computed(() => {
return (
frontmatter.value.sidebar !== false &&
sidebar.value.length > 0 &&
!isHome.value
)
})
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)
const sidebarGroups = computed(() => {
return hasSidebar.value ? getSidebarGroups(sidebar.value) : []
})
const hasAside = computed(() => {
if (isHome.value) return false
if (frontmatter.value.aside != null) return !!frontmatter.value.aside
return theme.value.aside !== false
})
const leftAside = computed(() => {
if (!hasAside.value) return false
return frontmatter.value.aside == null
? theme.value.aside === 'left'
: frontmatter.value.aside === 'left'
})
const hasLocalNav = computed(() => {
return headers.value.length > 0
})
return {
isHome,
sidebar,
sidebarGroups,
hasSidebar,
isSidebarEnabled,
hasAside,
leftAside,
headers,
hasLocalNav
}
}
interface RegisterWatchersOptions {
closeSidebar: () => void
}
export function registerWatchers({ closeSidebar }: RegisterWatchersOptions) {
const { frontmatter, theme } = useData()
onContentUpdated(() => {
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
})
const route = useRoute()
watch(() => route.path, closeSidebar)
useCloseSidebarOnEscape(closeSidebar)
}

@ -1,24 +0,0 @@
import { onContentUpdated } from 'vitepress'
import type { DefaultTheme } from 'vitepress/theme'
import { computed, shallowRef } from 'vue'
import { getHeaders, type MenuItem } from '../composables/outline'
import { useData } from './data'
export function useLocalNav(): DefaultTheme.DocLocalNav {
const { theme, frontmatter } = useData()
const headers = shallowRef<MenuItem[]>([])
const hasLocalNav = computed(() => {
return headers.value.length > 0
})
onContentUpdated(() => {
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
})
return {
headers,
hasLocalNav
}
}

@ -1,7 +1,6 @@
import { getScrollOffset } from 'vitepress' import { getScrollOffset } from 'vitepress'
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { onMounted, onUnmounted, onUpdated, type Ref } from 'vue' import { onMounted, onUnmounted, onUpdated, type Ref } from 'vue'
import type { Header } from '../../shared'
import { throttleAndDebounce } from '../support/utils' import { throttleAndDebounce } from '../support/utils'
import { useAside } from './aside' import { useAside } from './aside'
@ -10,11 +9,6 @@ const ignoreRE = /\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/
// cached list of anchor elements from resolveHeaders // cached list of anchor elements from resolveHeaders
const resolvedHeaders: { element: HTMLHeadElement; link: string }[] = [] const resolvedHeaders: { element: HTMLHeadElement; link: string }[] = []
export type MenuItem = Omit<Header, 'slug' | 'children'> & {
element: HTMLHeadElement
children?: MenuItem[]
}
export function resolveTitle(theme: DefaultTheme.Config): string { export function resolveTitle(theme: DefaultTheme.Config): string {
return ( return (
(typeof theme.outline === 'object' && (typeof theme.outline === 'object' &&
@ -25,7 +19,9 @@ export function resolveTitle(theme: DefaultTheme.Config): string {
) )
} }
export function getHeaders(range: DefaultTheme.Config['outline']): MenuItem[] { export function getHeaders(
range: DefaultTheme.Config['outline']
): DefaultTheme.OutlineItem[] {
const headers = [ const headers = [
...document.querySelectorAll('.VPDoc :where(h1,h2,h3,h4,h5,h6)') ...document.querySelectorAll('.VPDoc :where(h1,h2,h3,h4,h5,h6)')
] ]
@ -57,9 +53,9 @@ function serializeHeader(h: Element): string {
} }
export function resolveHeaders( export function resolveHeaders(
headers: MenuItem[], headers: DefaultTheme.OutlineItem[],
range?: DefaultTheme.Config['outline'] range?: DefaultTheme.Config['outline']
): MenuItem[] { ): DefaultTheme.OutlineItem[] {
if (range === false) { if (range === false) {
return [] return []
} }
@ -193,11 +189,18 @@ function getAbsoluteTop(element: HTMLElement): number {
return offsetTop return offsetTop
} }
function buildTree(data: MenuItem[], min: number, max: number): MenuItem[] { function buildTree(
data: DefaultTheme.OutlineItem[],
min: number,
max: number
): DefaultTheme.OutlineItem[] {
resolvedHeaders.length = 0 resolvedHeaders.length = 0
const result: MenuItem[] = [] const result: DefaultTheme.OutlineItem[] = []
const stack: (MenuItem | { level: number; shouldIgnore: true })[] = [] const stack: (
| DefaultTheme.OutlineItem
| { level: number; shouldIgnore: true }
)[] = []
data.forEach((item) => { data.forEach((item) => {
const node = { ...item, children: [] } const node = { ...item, children: [] }

@ -1,4 +1,3 @@
import { useMediaQuery } from '@vueuse/core'
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { import {
computed, computed,
@ -8,108 +7,19 @@ import {
watch, watch,
watchEffect, watchEffect,
watchPostEffect, watchPostEffect,
type ComputedRef, type ComputedRef
type Ref
} from 'vue' } from 'vue'
import { isActive } from '../../shared' import { isActive } from '../../shared'
import { import { hasActiveLink as containsActiveLink } from '../support/sidebar'
hasActiveLink as containsActiveLink,
getSidebar,
getSidebarGroups
} from '../support/sidebar'
import { useData } from './data' import { useData } from './data'
export interface SidebarControl { const isOpen = ref(false)
collapsed: Ref<boolean>
collapsible: ComputedRef<boolean>
isLink: ComputedRef<boolean>
isActiveLink: Ref<boolean>
hasActiveLink: ComputedRef<boolean>
hasChildren: ComputedRef<boolean>
toggle(): void
}
export function useSidebar() {
const { frontmatter, page, theme } = useData()
const is960 = useMediaQuery('(min-width: 960px)')
const isOpen = ref(false)
const _sidebar = computed(() => {
const sidebarConfig = theme.value.sidebar
const relativePath = page.value.relativePath
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : []
})
const sidebar = ref(_sidebar.value)
watch(_sidebar, (next, prev) => {
if (JSON.stringify(next) !== JSON.stringify(prev))
sidebar.value = _sidebar.value
})
const hasSidebar = computed(() => {
return (
frontmatter.value.sidebar !== false &&
sidebar.value.length > 0 &&
frontmatter.value.layout !== 'home'
)
})
const leftAside = computed(() => {
if (hasAside)
return frontmatter.value.aside == null
? theme.value.aside === 'left'
: frontmatter.value.aside === 'left'
return false
})
const hasAside = computed(() => {
if (frontmatter.value.layout === 'home') return false
if (frontmatter.value.aside != null) return !!frontmatter.value.aside
return theme.value.aside !== false
})
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)
const sidebarGroups = computed(() => {
return hasSidebar.value ? getSidebarGroups(sidebar.value) : []
})
function open() {
isOpen.value = true
}
function close() {
isOpen.value = false
}
function toggle() {
isOpen.value ? close() : open()
}
return {
isOpen,
sidebar,
sidebarGroups,
hasSidebar,
hasAside,
leftAside,
isSidebarEnabled,
open,
close,
toggle
}
}
/** /**
* a11y: cache the element that opened the Sidebar (the menu button) then * a11y: cache the element that opened the Sidebar (the menu button) then
* focus that button again when Menu is closed with Escape key. * focus that button again when Menu is closed with Escape key.
*/ */
export function useCloseSidebarOnEscape( export function useCloseSidebarOnEscape(close: () => void) {
isOpen: Ref<boolean>,
close: () => void
) {
let triggerElement: HTMLButtonElement | undefined let triggerElement: HTMLButtonElement | undefined
watchEffect(() => { watchEffect(() => {
@ -134,9 +44,30 @@ export function useCloseSidebarOnEscape(
} }
} }
export function useSidebarControl( export function useSidebarControl() {
function open() {
isOpen.value = true
}
function close() {
isOpen.value = false
}
function toggle() {
isOpen.value ? close() : open()
}
return {
isOpen,
open,
close,
toggle
}
}
export function useSidebarItemControl(
item: ComputedRef<DefaultTheme.SidebarItem> item: ComputedRef<DefaultTheme.SidebarItem>
): SidebarControl { ) {
const { page, hash } = useData() const { page, hash } = useData()
const collapsed = ref(false) const collapsed = ref(false)

@ -31,8 +31,7 @@ export { default as VPTeamPage } from './components/VPTeamPage.vue'
export { default as VPTeamPageSection } from './components/VPTeamPageSection.vue' export { default as VPTeamPageSection } from './components/VPTeamPageSection.vue'
export { default as VPTeamPageTitle } from './components/VPTeamPageTitle.vue' export { default as VPTeamPageTitle } from './components/VPTeamPageTitle.vue'
export { useLocalNav } from './composables/local-nav' export { useLayout } from './composables/layout'
export { useSidebar } from './composables/sidebar'
const theme: Theme = { const theme: Theme = {
Layout, Layout,

27
theme.d.ts vendored

@ -1,6 +1,6 @@
// so that users can do `import DefaultTheme from 'vitepress/theme'` // so that users can do `import DefaultTheme from 'vitepress/theme'`
import type { DefineComponent } from 'vue' import type { ComputedRef, DefineComponent, ShallowRef } from 'vue'
import type { EnhanceAppContext } from './dist/client/index.js' import type { EnhanceAppContext } from './dist/client/index.js'
import type { DefaultTheme } from './types/default-theme.js' import type { DefaultTheme } from './types/default-theme.js'
@ -12,8 +12,29 @@ declare const theme: {
} }
export default theme export default theme
export declare const useSidebar: () => DefaultTheme.DocSidebar
export declare const useLocalNav: () => DefaultTheme.DocLocalNav export declare const useLayout: () => {
isHome: ComputedRef<boolean>
sidebar: ComputedRef<DefaultTheme.SidebarItem[]>
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>
hasSidebar: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>
/**
* The outline headers of the current page.
*/
headers: ShallowRef<DefaultTheme.OutlineItem[]>
/**
* Whether the current page has a local nav. Local nav is shown when the
* "outline" is present in the page. However, note that the actual
* local nav visibility depends on the screen width as well.
*/
hasLocalNav: ComputedRef<boolean>
}
// TODO: add props for these // TODO: add props for these
export declare const VPBadge: DefineComponent export declare const VPBadge: DefineComponent

@ -1,8 +1,7 @@
import type { Options as _MiniSearchOptions } from 'minisearch' import type { Options as _MiniSearchOptions } from 'minisearch'
import type { ComputedRef, Ref, ShallowRef } from 'vue'
import type { DocSearchProps } from './docsearch.js' import type { DocSearchProps } from './docsearch.js'
import type { LocalSearchTranslations } from './local-search.js' import type { LocalSearchTranslations } from './local-search.js'
import type { PageData } from './shared.js' import type { Header, PageData } from './shared.js'
export namespace DefaultTheme { export namespace DefaultTheme {
export interface Config { export interface Config {
@ -273,22 +272,6 @@ export namespace DefaultTheme {
target?: string target?: string
} }
/**
* ReturnType of `useSidebar`
*/
export interface DocSidebar {
isOpen: Ref<boolean>
sidebar: ComputedRef<SidebarItem[]>
sidebarGroups: ComputedRef<SidebarItem[]>
hasSidebar: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
open: () => void
close: () => void
toggle: () => void
}
// edit link ----------------------------------------------------------------- // edit link -----------------------------------------------------------------
export interface EditLink { export interface EditLink {
@ -357,25 +340,6 @@ export namespace DefaultTheme {
actionText?: string actionText?: string
} }
// local nav -----------------------------------------------------------------
/**
* ReturnType of `useLocalNav`.
*/
export interface DocLocalNav {
/**
* The outline headers of the current page.
*/
headers: ShallowRef<any>
/**
* Whether the current page has a local nav. Local nav is shown when the
* "outline" is present in the page. However, note that the actual
* local nav visibility depends on the screen width as well.
*/
hasLocalNav: ComputedRef<boolean>
}
// outline ------------------------------------------------------------------- // outline -------------------------------------------------------------------
export interface Outline { export interface Outline {
@ -383,6 +347,11 @@ export namespace DefaultTheme {
label?: string label?: string
} }
export type OutlineItem = Omit<Header, 'slug' | 'children'> & {
element: HTMLHeadElement
children?: OutlineItem[]
}
// local search -------------------------------------------------------------- // local search --------------------------------------------------------------
export interface LocalSearchOptions { export interface LocalSearchOptions {

Loading…
Cancel
Save