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
type="radio"
:id="option.key"
:name="name"
:name
:value="option.value"
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`
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.
<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.
<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).
@ -107,9 +107,7 @@ const members = [
team, some of whom have chosen to be featured below.
</template>
</VPTeamPageTitle>
<VPTeamMembers
:members="members"
/>
<VPTeamMembers :members />
</VPTeamPage>
```

@ -225,3 +225,16 @@ Then you can customize styles of this specific page in `.vitepress/theme/custom.
/* 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.
<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.
<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).
@ -107,9 +107,7 @@ const members = [
Algunos de los miembros han elegido aparecer a continuación.
</template>
</VPTeamPageTitle>
<VPTeamMembers
:members="members"
/>
<VPTeamMembers :members />
</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) مراجعه کنید.
@ -106,9 +106,7 @@ const members = [
توسعه ویت‌پرس توسط تیمی بین‌المللی راهنمایی می‌شود، برخی از اعضا که انتخاب کرده‌اند تا در زیر نمایش داده شوند.
</template>
</VPTeamPageTitle>
<VPTeamMembers
:members="members"
/>
<VPTeamMembers :members />
</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.
<VPTeamMembers size="small" :members="members" />
<VPTeamMembers size="small" :members />
```
위 코드는 카드 형태의 엘리먼트로 팀 구성원을 표시합니다. 아래와 비슷한 형태로 표시됩니다.
<VPTeamMembers size="small" :members="members" />
<VPTeamMembers size="small" :members />
`<VPTeamMembers>` 컴포넌트는 `small``medium` 두 가지 크기로 제공됩니다. 개인의 선호도에 따라 선택할 수 있지만, 일반적으로 `small` 사이즈가 문서 페이지에 더 적합합니다. 또한, 각 구성원에 "설명"이나 "후원" 버튼과 같은 프로퍼티를 추가할 수도 있습니다. 자세한 내용은 [`<VPTeamMembers>`](#vpteammembers)에서 확인할 수 있습니다.
@ -107,9 +107,7 @@ const members = [
team, some of whom have chosen to be featured below.
</template>
</VPTeamPageTitle>
<VPTeamMembers
:members="members"
/>
<VPTeamMembers :members />
</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.
<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.
<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).
@ -107,9 +107,7 @@ const members = [
alguns dos membros escolheram ser apresentados abaixo.
</template>
</VPTeamPageTitle>
<VPTeamMembers
:members="members"
/>
<VPTeamMembers :members />
</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).
@ -104,7 +104,7 @@ layout: page
которой представлены ниже.
</template>
</VPTeamPageTitle>
<VPTeamMembers :members="members" />
<VPTeamMembers :members />
</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.
<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) 中了解更多信息。
@ -107,9 +107,7 @@ const members = [
team, some of whom have chosen to be featured below.
</template>
</VPTeamPageTitle>
<VPTeamMembers
:members="members"
/>
<VPTeamMembers :members />
</VPTeamPage>
```

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

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

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

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

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

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

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { MenuItem } from '../composables/outline'
import type { DefaultTheme } from 'vitepress/theme'
defineProps<{
headers: MenuItem[]
headers: DefaultTheme.OutlineItem[]
root?: boolean
}>()
@ -16,7 +16,9 @@ function onClick({ target: el }: Event) {
<template>
<ul class="VPDocOutlineItem" :class="root ? 'root' : 'nested'">
<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">
<VPDocOutlineItem :headers="children" />
</template>

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

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

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

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

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

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

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

@ -11,7 +11,7 @@ defineProps<{
<div class="VPMenu">
<div v-if="items" class="items">
<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
v-else-if="'component' in item"
:is="item.component"

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

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

@ -16,13 +16,13 @@ const { theme } = useData()
Main Navigation
</span>
<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
v-else-if="'component' in item"
:is="item.component"
v-bind="item.props"
/>
<VPNavBarMenuGroup v-else :item="item" />
<VPNavBarMenuGroup v-else :item />
</template>
</nav>
</template>

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

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

@ -9,7 +9,7 @@ const { theme } = useData()
<template>
<nav v-if="theme.nav" class="VPNavScreenMenu">
<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
v-else-if="'component' in item"
:is="item.component"

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

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

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

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

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

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

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

@ -1,9 +1,9 @@
import { useMediaQuery } from '@vueuse/core'
import { computed } from 'vue'
import { useSidebar } from './sidebar'
import { useLayout } from './layout'
export function useAside() {
const { hasSidebar } = useSidebar()
const { hasSidebar } = useLayout()
const is960 = useMediaQuery('(min-width: 960px)')
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 type { DefaultTheme } from 'vitepress/theme'
import { onMounted, onUnmounted, onUpdated, type Ref } from 'vue'
import type { Header } from '../../shared'
import { throttleAndDebounce } from '../support/utils'
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
const resolvedHeaders: { element: HTMLHeadElement; link: string }[] = []
export type MenuItem = Omit<Header, 'slug' | 'children'> & {
element: HTMLHeadElement
children?: MenuItem[]
}
export function resolveTitle(theme: DefaultTheme.Config): string {
return (
(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 = [
...document.querySelectorAll('.VPDoc :where(h1,h2,h3,h4,h5,h6)')
]
@ -57,9 +53,9 @@ function serializeHeader(h: Element): string {
}
export function resolveHeaders(
headers: MenuItem[],
headers: DefaultTheme.OutlineItem[],
range?: DefaultTheme.Config['outline']
): MenuItem[] {
): DefaultTheme.OutlineItem[] {
if (range === false) {
return []
}
@ -193,11 +189,18 @@ function getAbsoluteTop(element: HTMLElement): number {
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
const result: MenuItem[] = []
const stack: (MenuItem | { level: number; shouldIgnore: true })[] = []
const result: DefaultTheme.OutlineItem[] = []
const stack: (
| DefaultTheme.OutlineItem
| { level: number; shouldIgnore: true }
)[] = []
data.forEach((item) => {
const node = { ...item, children: [] }

@ -1,4 +1,3 @@
import { useMediaQuery } from '@vueuse/core'
import type { DefaultTheme } from 'vitepress/theme'
import {
computed,
@ -8,108 +7,19 @@ import {
watch,
watchEffect,
watchPostEffect,
type ComputedRef,
type Ref
type ComputedRef
} from 'vue'
import { isActive } from '../../shared'
import {
hasActiveLink as containsActiveLink,
getSidebar,
getSidebarGroups
} from '../support/sidebar'
import { hasActiveLink as containsActiveLink } from '../support/sidebar'
import { useData } from './data'
export interface SidebarControl {
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
}
}
const isOpen = ref(false)
/**
* a11y: cache the element that opened the Sidebar (the menu button) then
* focus that button again when Menu is closed with Escape key.
*/
export function useCloseSidebarOnEscape(
isOpen: Ref<boolean>,
close: () => void
) {
export function useCloseSidebarOnEscape(close: () => void) {
let triggerElement: HTMLButtonElement | undefined
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>
): SidebarControl {
) {
const { page, hash } = useData()
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 VPTeamPageTitle } from './components/VPTeamPageTitle.vue'
export { useLocalNav } from './composables/local-nav'
export { useSidebar } from './composables/sidebar'
export { useLayout } from './composables/layout'
const theme: Theme = {
Layout,

27
theme.d.ts vendored

@ -1,6 +1,6 @@
// 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 { DefaultTheme } from './types/default-theme.js'
@ -12,8 +12,29 @@ declare const 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
export declare const VPBadge: DefineComponent

@ -1,8 +1,7 @@
import type { Options as _MiniSearchOptions } from 'minisearch'
import type { ComputedRef, Ref, ShallowRef } from 'vue'
import type { DocSearchProps } from './docsearch.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 interface Config {
@ -273,22 +272,6 @@ export namespace DefaultTheme {
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 -----------------------------------------------------------------
export interface EditLink {
@ -357,25 +340,6 @@ export namespace DefaultTheme {
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 -------------------------------------------------------------------
export interface Outline {
@ -383,6 +347,11 @@ export namespace DefaultTheme {
label?: string
}
export type OutlineItem = Omit<Header, 'slug' | 'children'> & {
element: HTMLHeadElement
children?: OutlineItem[]
}
// local search --------------------------------------------------------------
export interface LocalSearchOptions {

Loading…
Cancel
Save