feat: more efficient `useData()` method that exposes all data

BREAKING CHANGE:

    - Individual `useX()` data methods are removed.

        ```js
        // before
        import { useSiteDataByRoute, usePageData } from 'vitepress'
        const site = useSiteDataByRoute()
        const page = usePageData()
        const theme = computed(() => site.value.themeConfig)

        // after
        import { useData } from 'vitepress'
        const { site, page, theme } = useData()
        ```

      All destructured values are computed refs injected from app root
      so they are created only once globally.

    - Global mixin properties (e.g. `$site`) are removed. Always use
      `useData()` to retrieve VitePress data.
pull/314/head
Evan You 3 years ago
parent e10fdbcf34
commit 0661063d29

@ -1,16 +1,8 @@
<template>
<div class="debug" :class="{ open }" ref="el" @click="open = !open">
<p class="title">Debug</p>
<pre class="block">$page {{ $page }}</pre>
<pre class="block">$siteByRoute {{ $siteByRoute }}</pre>
<pre class="block">$site {{ $site }}</pre>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useData } from '../data'
const data = useData()
const el = ref<HTMLElement | null>(null)
const open = ref(false)
@ -21,6 +13,13 @@ watch(open, (value) => {
})
</script>
<template>
<div class="debug" :class="{ open }" ref="el" @click="open = !open">
<p class="title">Debug</p>
<pre class="block">{{ data }}</pre>
</div>
</template>
<style scoped>
.debug {
box-sizing: border-box;

@ -1,9 +0,0 @@
import { Ref, computed } from 'vue'
import { usePageData } from './pageData'
export type FrontmatterRef = Ref<Record<string, any>>
export function useFrontmatter() {
const pageData = usePageData()
return computed(() => pageData.value.frontmatter)
}

@ -1,11 +0,0 @@
import { Ref, computed } from 'vue'
import { PageData } from '/@types/shared'
import { Route, useRoute } from '../router'
export type PageDataRef = Ref<PageData>
export function usePageData(route?: Route) {
const r = route || useRoute()
return computed(() => r.data)
}

@ -1,22 +0,0 @@
import serialized from '@siteData'
import { SiteData } from '/@types/shared'
import { Ref, ref, readonly } from 'vue'
export type SiteDataRef<T = any> = Ref<SiteData<T>>
export const siteDataRef: Ref<SiteData> = ref(parse(serialized))
export function useSiteData<T = any>() {
return siteDataRef as Ref<SiteData<T>>
}
function parse(data: string): SiteData {
return readonly(JSON.parse(data)) as SiteData
}
// hmr
if (import.meta.hot) {
import.meta.hot!.accept('/@siteData', (m) => {
siteDataRef.value = parse(m.default)
})
}

@ -1,12 +0,0 @@
import { computed } from 'vue'
import { resolveSiteDataByRoute } from '/@shared/config'
import { siteDataRef } from './siteData'
import { Route, useRoute } from '../router'
export function useSiteDataByRoute(route?: Route) {
const r = route || useRoute()
return computed(() => {
return resolveSiteDataByRoute(siteDataRef.value, r.path)
})
}

@ -0,0 +1,70 @@
import { InjectionKey, Ref, ref, readonly, computed, inject } from 'vue'
import { Route } from './router'
import { PageData, SiteData } from '/@types/shared'
import serializedSiteData from '@siteData'
import { resolveSiteDataByRoute } from '../shared/config'
export const dataSymbol: InjectionKey<VitePressData> = Symbol()
export interface VitePressData {
site: Ref<SiteData>
theme: Ref<any>
page: Ref<PageData>
frontmatter: Ref<any>
lang: Ref<string>
localePath: Ref<string>
title: Ref<string>
description: Ref<string>
}
// site data is a singleton
export type SiteDataRef<T = any> = Ref<SiteData<T>>
export const siteDataRef: Ref<SiteData> = ref(parse(serializedSiteData))
function parse(data: string): SiteData {
return readonly(JSON.parse(data)) as SiteData
}
// hmr
if (import.meta.hot) {
import.meta.hot!.accept('/@siteData', (m) => {
siteDataRef.value = parse(m.default)
})
}
// per-app data
export function initData(route: Route): VitePressData {
const site = computed(() =>
resolveSiteDataByRoute(siteDataRef.value, route.path)
)
return {
site,
theme: computed(() => site.value.themeConfig),
page: computed(() => route.data),
frontmatter: computed(() => route.data.frontmatter),
lang: computed(() => site.value.lang),
localePath: computed(() => {
const { locales, lang } = site.value
const path = Object.keys(locales).find((lp) => locales[lp].lang === lang)
return (locales && path) || '/'
}),
title: computed(() => {
return route.data.title
? route.data.title + ' | ' + site.value.title
: site.value.title
}),
description: computed(() => {
return route.data.description || site.value.description
})
}
}
export function useData(): VitePressData {
const data = inject(dataSymbol)
if (!data) {
throw new Error('vitepress data not properly injected in app')
}
return data
}

@ -2,31 +2,32 @@ import {
App,
createApp as createClientApp,
createSSRApp,
defineAsyncComponent,
h,
onMounted,
watch
} from 'vue'
import { inBrowser, pathToFile } from './utils'
import { Router, RouterSymbol, createRouter } from './router'
import { mixinGlobalComputed, mixinGlobalComponents } from './mixin'
import { siteDataRef } from './composables/siteData'
import { useSiteDataByRoute } from './composables/siteDataByRoute'
import { usePageData } from './composables/pageData'
import { siteDataRef, useData } from './data'
import { useUpdateHead } from './composables/head'
import Theme from '/@theme/index'
import { usePrefetch } from './composables/preFetch'
import { dataSymbol, initData } from './data'
import { Content } from './components/Content'
import { ClientOnly } from './components/ClientOnly'
const NotFound = Theme.NotFound || (() => '404 Not Found')
const VitePressApp = {
name: 'VitePressApp',
setup() {
const siteData = useSiteDataByRoute()
const { site } = useData()
// change the language on the HTML element based on the current lang
onMounted(() => {
watch(
() => siteData.value.lang,
() => site.value.lang,
(lang: string) => {
document.documentElement.lang = lang
},
@ -51,16 +52,23 @@ export function createApp() {
app.provide(RouterSymbol, router)
const siteDataByRouteRef = useSiteDataByRoute(router.route)
const pageDataRef = usePageData(router.route)
const data = initData(router.route)
app.provide(dataSymbol, data)
if (inBrowser) {
// dynamically update head tags
useUpdateHead(router.route, siteDataByRouteRef)
useUpdateHead(router.route, data.site)
}
mixinGlobalComputed(app, siteDataRef, siteDataByRouteRef, pageDataRef)
mixinGlobalComponents(app)
// install global components
app.component('Content', Content)
app.component('ClientOnly', ClientOnly)
app.component(
'Debug',
import.meta.env.PROD
? () => null
: defineAsyncComponent(() => import('./components/Debug.vue'))
)
if (Theme.enhanceApp) {
Theme.enhanceApp({

@ -1,95 +0,0 @@
import { App, defineAsyncComponent } from 'vue'
import { joinPath } from './utils'
import { SiteDataRef } from './composables/siteData'
import { PageDataRef } from './composables/pageData'
import { Content } from './components/Content'
import { ClientOnly } from './components/ClientOnly'
export function mixinGlobalComputed(
app: App,
site: SiteDataRef,
siteByRoute: SiteDataRef,
page: PageDataRef
): void {
Object.defineProperties(app.config.globalProperties, {
$site: {
get() {
return site.value
}
},
$siteByRoute: {
get() {
return siteByRoute.value
}
},
$themeConfig: {
get() {
return siteByRoute.value.themeConfig
}
},
$page: {
get() {
return page.value
}
},
$frontmatter: {
get() {
return page.value.frontmatter
}
},
$lang: {
get() {
return siteByRoute.value.lang
}
},
$localePath: {
get() {
const { locales } = site.value
const { lang } = siteByRoute.value
const path = Object.keys(locales).find(
(lp) => locales[lp].lang === lang
)
return (locales && path) || '/'
}
},
$title: {
get() {
return page.value.title
? page.value.title + ' | ' + siteByRoute.value.title
: siteByRoute.value.title
}
},
$description: {
get() {
return page.value.description || siteByRoute.value.description
}
},
$withBase: {
value(path: string) {
return joinPath(site.value.base, path)
}
}
})
}
export function mixinGlobalComponents(app: App) {
app.component('Content', Content)
app.component('ClientOnly', ClientOnly)
app.component(
'Debug',
import.meta.env.PROD
? () => null
: defineAsyncComponent(() => import('./components/Debug.vue'))
)
}

@ -1,3 +1,4 @@
import { siteDataRef } from './data'
import { inBrowser } from '/@shared/config'
export { inBrowser }
@ -9,6 +10,10 @@ export function joinPath(base: string, path: string): string {
return `${base}${path}`.replace(/\/+/g, '/')
}
export function withBase(path: string) {
return joinPath(siteDataRef.value.base, path)
}
/**
* Converts a url path to the corresponding js chunk filename.
*/

@ -8,14 +8,11 @@ export type { Router, Route } from './app/router'
export type { Theme, EnhanceAppContext } from './app/theme'
// composables
export { useData } from './app/data'
export { useRouter, useRoute } from './app/router'
export { useSiteData } from './app/composables/siteData'
export { useSiteDataByRoute } from './app/composables/siteDataByRoute'
export { usePageData } from './app/composables/pageData'
export { useFrontmatter } from './app/composables/frontmatter'
// utilities
export { inBrowser, joinPath } from './app/utils'
export { inBrowser, joinPath, withBase } from './app/utils'
// components
export { Content } from './app/components/Content'

@ -1,86 +1,7 @@
<template>
<div class="theme" :class="pageClasses">
<NavBar v-if="showNavbar" @toggle="toggleSidebar">
<template #search>
<slot name="navbar-search">
<AlgoliaSearchBox
v-if="theme.algolia"
:options="theme.algolia"
:multilang="!!theme.locales"
:key="siteRouteData.lang"
/>
</slot>
</template>
</NavBar>
<SideBar :open="openSideBar">
<template #sidebar-top>
<slot name="sidebar-top" />
</template>
<template #sidebar-bottom>
<slot name="sidebar-bottom" />
</template>
</SideBar>
<!-- TODO: make this button accessible -->
<div class="sidebar-mask" @click="toggleSidebar(false)" />
<Content v-if="isCustomLayout" />
<Home v-else-if="enableHome">
<template #hero>
<slot name="home-hero" />
</template>
<template #features>
<slot name="home-features" />
</template>
<template #footer>
<slot name="home-footer" />
</template>
</Home>
<Page v-else>
<template #top>
<slot name="page-top-ads">
<div
id="ads-container"
v-if="theme.carbonAds && theme.carbonAds.carbon"
>
<CarbonAds
:key="'carbon' + page.relativePath"
:code="theme.carbonAds.carbon"
:placement="theme.carbonAds.placement"
/>
</div>
</slot>
<slot name="page-top" />
</template>
<template #bottom>
<slot name="page-bottom" />
<slot name="page-bottom-ads">
<BuySellAds
v-if="theme.carbonAds && theme.carbonAds.custom"
:key="'custom' + page.relativePath"
:code="theme.carbonAds.custom"
:placement="theme.carbonAds.placement"
/>
</slot>
</template>
</Page>
</div>
<Debug />
</template>
<script setup lang="ts">
import { ref, computed, watch, defineAsyncComponent } from 'vue'
import {
useRoute,
useSiteData,
usePageData,
useSiteDataByRoute
} from 'vitepress'
import { useRoute, useData } from 'vitepress'
import { isSideBarEmpty, getSideBarConfig } from './support/sideBar'
import type { DefaultTheme } from './config'
// components
import NavBar from './components/NavBar.vue'
@ -103,10 +24,7 @@ const AlgoliaSearchBox = __ALGOLIA__
// generic state
const route = useRoute()
const siteData = useSiteData<DefaultTheme.Config>()
const siteRouteData = useSiteDataByRoute()
const theme = computed(() => siteData.value.themeConfig)
const page = usePageData()
const { site, page, theme, frontmatter } = useData()
// custom layout
const isCustomLayout = computed(() => !!route.data.frontmatter.customLayout)
@ -115,16 +33,12 @@ const enableHome = computed(() => !!route.data.frontmatter.home)
// navbar
const showNavbar = computed(() => {
const { themeConfig } = siteRouteData.value
const { frontmatter } = route.data
if (frontmatter.navbar === false || themeConfig.navbar === false) {
const themeConfig = theme.value
if (frontmatter.value.navbar === false || themeConfig.navbar === false) {
return false
}
return (
siteData.value.title ||
themeConfig.logo ||
themeConfig.repo ||
themeConfig.nav
site.value.title || themeConfig.logo || themeConfig.repo || themeConfig.nav
)
})
@ -132,16 +46,12 @@ const showNavbar = computed(() => {
const openSideBar = ref(false)
const showSidebar = computed(() => {
const { frontmatter } = route.data
if (frontmatter.home || frontmatter.sidebar === false) {
if (frontmatter.value.home || frontmatter.value.sidebar === false) {
return false
}
const { themeConfig } = siteRouteData.value
return !isSideBarEmpty(
getSideBarConfig(themeConfig.sidebar, route.data.relativePath)
getSideBarConfig(theme.value.sidebar, route.data.relativePath)
)
})
@ -167,6 +77,79 @@ const pageClasses = computed(() => {
})
</script>
<template>
<div class="theme" :class="pageClasses">
<NavBar v-if="showNavbar" @toggle="toggleSidebar">
<template #search>
<slot name="navbar-search">
<AlgoliaSearchBox
v-if="theme.algolia"
:options="theme.algolia"
:multilang="!!theme.locales"
:key="site.lang"
/>
</slot>
</template>
</NavBar>
<SideBar :open="openSideBar">
<template #sidebar-top>
<slot name="sidebar-top" />
</template>
<template #sidebar-bottom>
<slot name="sidebar-bottom" />
</template>
</SideBar>
<!-- TODO: make this button accessible -->
<div class="sidebar-mask" @click="toggleSidebar(false)" />
<Content v-if="isCustomLayout" />
<Home v-else-if="enableHome">
<template #hero>
<slot name="home-hero" />
</template>
<template #features>
<slot name="home-features" />
</template>
<template #footer>
<slot name="home-footer" />
</template>
</Home>
<Page v-else>
<template #top>
<slot name="page-top-ads">
<div
id="ads-container"
v-if="theme.carbonAds && theme.carbonAds.carbon"
>
<CarbonAds
:key="'carbon' + page.relativePath"
:code="theme.carbonAds.carbon"
:placement="theme.carbonAds.placement"
/>
</div>
</slot>
<slot name="page-top" />
</template>
<template #bottom>
<slot name="page-bottom" />
<slot name="page-bottom-ads">
<BuySellAds
v-if="theme.carbonAds && theme.carbonAds.custom"
:key="'custom' + page.relativePath"
:code="theme.carbonAds.custom"
:placement="theme.carbonAds.placement"
/>
</slot>
</template>
</Page>
</div>
<Debug />
</template>
<style>
#ads-container {
margin: 0 auto;

@ -1,17 +1,10 @@
<template>
<div class="algolia-search-box" id="docsearch" />
</template>
<script setup lang="ts">
import '@docsearch/css'
import { useRoute, useRouter } from 'vitepress'
import { defineProps, getCurrentInstance, onMounted, watch } from 'vue'
import docsearch from '@docsearch/js'
import { useRoute, useRouter, useData } from 'vitepress'
import { defineProps, getCurrentInstance, onMounted, watch } from 'vue'
import type { DefaultTheme } from '../config'
import type { DocSearchHit } from '@docsearch/react/dist/esm/types'
import { useSiteDataByRoute } from 'vitepress'
const siteData = useSiteDataByRoute()
const props = defineProps<{
options: DefaultTheme.AlgoliaSearchOptions
@ -57,12 +50,12 @@ function update(options: any) {
}
}
const { lang } = useData()
function initialize(userOptions: any) {
// if the user has multiple locales, the search results should be filtered
// based on the language
const facetFilters = props.multilang
? ['language:' + siteData.value.lang]
: []
const facetFilters = props.multilang ? ['language:' + lang.value] : []
docsearch(
Object.assign({}, userOptions, {
@ -147,6 +140,10 @@ function initialize(userOptions: any) {
}
</script>
<template>
<div class="algolia-search-box" id="docsearch" />
</template>
<style>
.algolia-search-box {
padding-top: 1px;

@ -1,9 +1,3 @@
<template>
<div class="buy-sell-ads">
<div class="bsa-cpc" />
</div>
</template>
<script setup lang="ts">
import { defineProps, onMounted } from 'vue'
@ -60,6 +54,12 @@ function load() {
}
</script>
<template>
<div class="buy-sell-ads">
<div class="bsa-cpc" />
</div>
</template>
<style scoped>
.buy-sell-ads {
margin: 0 auto;

@ -1,7 +1,3 @@
<template>
<div class="carbon-ads" ref="el" />
</template>
<script setup lang="ts">
import { defineProps, ref, onMounted } from 'vue'
@ -20,6 +16,10 @@ onMounted(() => {
})
</script>
<template>
<div class="carbon-ads" ref="el" />
</template>
<style scoped>
.carbon-ads {
border-radius: 4px;

@ -1,3 +1,10 @@
<script setup lang="ts">
import { useEditLink } from '../composables/editLink'
import OutboundLink from './icons/OutboundLink.vue'
const { url, text } = useEditLink()
</script>
<template>
<div class="edit-link">
<a
@ -12,13 +19,6 @@
</div>
</template>
<script setup lang="ts">
import { useEditLink } from '../composables/editLink'
import OutboundLink from './icons/OutboundLink.vue'
const { url, text } = useEditLink()
</script>
<style scoped>
.link {
display: inline-block;

@ -1,3 +1,9 @@
<script setup lang="ts">
import HomeHero from './HomeHero.vue'
import HomeFeatures from './HomeFeatures.vue'
import HomeFooter from './HomeFooter.vue'
</script>
<template>
<main class="home" aria-labelledby="main-title">
<HomeHero />
@ -12,12 +18,6 @@
</main>
</template>
<script setup lang="ts">
import HomeHero from './HomeHero.vue'
import HomeFeatures from './HomeFeatures.vue'
import HomeFooter from './HomeFooter.vue'
</script>
<style scoped>
.home {
padding-top: var(--header-height);

@ -1,3 +1,23 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useData } from 'vitepress'
const { frontmatter } = useData()
const hasFeatures = computed(() => {
return frontmatter.value.features && frontmatter.value.features.length > 0
})
interface Feature {
title?: string
details?: string
}
const features = computed<Feature[]>(() => {
return frontmatter.value.features ? frontmatter.value.features : []
})
</script>
<template>
<div v-if="hasFeatures" class="home-features">
<div class="wrapper">
@ -17,21 +37,6 @@
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useSiteDataByRoute, useFrontmatter } from 'vitepress'
const data = useFrontmatter()
const hasFeatures = computed(() => {
return data.value.features && data.value.features.length > 0
})
const features = computed(() => {
return data.value.features ? data.value.features : []
})
</script>
<style scoped>
.home-features {
margin: 0 auto;

@ -1,7 +1,13 @@
<script setup lang="ts">
import { useData } from 'vitepress'
const { frontmatter } = useData()
</script>
<template>
<footer v-if="$frontmatter.footer" class="footer">
<footer v-if="frontmatter.footer" class="footer">
<div class="container">
<p class="text">{{ $frontmatter.footer }}</p>
<p class="text">{{ frontmatter.footer }}</p>
</div>
</footer>
</template>

@ -1,59 +1,56 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useData, withBase } from 'vitepress'
import NavLink from './NavLink.vue'
const { site, frontmatter } = useData()
const showHero = computed(() => {
const {
heroImage,
heroText,
tagline,
actionLink,
actionText
} = frontmatter.value
return heroImage || heroText || tagline || (actionLink && actionText)
})
const heroText = computed(() => frontmatter.value.heroText || site.value.title)
</script>
<template>
<header v-if="showHero" class="home-hero">
<figure v-if="$frontmatter.heroImage" class="figure">
<figure v-if="frontmatter.heroImage" class="figure">
<img
class="image"
:src="$withBase($frontmatter.heroImage)"
:alt="$frontmatter.heroAlt"
:src="withBase(frontmatter.heroImage)"
:alt="frontmatter.heroAlt"
/>
</figure>
<h1 v-if="hasHeroText" id="main-title" class="title">{{ heroText }}</h1>
<p v-if="hasTagline" class="description">{{ tagline }}</p>
<h1 v-if="heroText" id="main-title" class="title">{{ heroText }}</h1>
<p v-if="frontmatter.tagline" class="description">
{{ frontmatter.tagline }}
</p>
<NavLink
v-if="hasAction"
:item="{ link: data.actionLink, text: data.actionText }"
v-if="frontmatter.actionLink && frontmatter.actionText"
:item="{ link: frontmatter.actionLink, text: frontmatter.actionText }"
class="action"
/>
<NavLink
v-if="hasAltAction"
:item="{ link: data.altActionLink, text: data.altActionText }"
v-if="frontmatter.altActionLink && frontmatter.altActionText"
:item="{
link: frontmatter.altActionLink,
text: frontmatter.altActionText
}"
class="action alt"
/>
</header>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useSiteDataByRoute, useFrontmatter } from 'vitepress'
import NavLink from './NavLink.vue'
const site = useSiteDataByRoute()
const data = useFrontmatter()
const showHero = computed(() => {
return (
data.value.heroImage ||
hasHeroText.value ||
hasTagline.value ||
hasAction.value
)
})
const hasHeroText = computed(() => data.value.heroText !== null)
const heroText = computed(() => data.value.heroText || site.value.title)
const hasTagline = computed(() => data.value.tagline !== null)
const tagline = computed(() => data.value.tagline || site.value.description)
const hasAction = computed(() => data.value.actionLink && data.value.actionText)
const hasAltAction = computed(
() => data.value.altActionLink && data.value.altActionText
)
</script>
<style scoped>
.home-hero {
margin: 2.5rem 0 2.75rem;

@ -1,25 +1,17 @@
<template>
<p v-if="hasLastUpdated" class="last-updated">
<span class="prefix">{{ prefix }}:</span>
<span class="datetime">{{ datetime }}</span>
</p>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useSiteDataByRoute, usePageData } from 'vitepress'
import { useData } from 'vitepress'
const site = useSiteDataByRoute()
const page = usePageData()
const { theme, page } = useData()
const hasLastUpdated = computed(() => {
const lu = site.value.themeConfig.lastUpdated
const lu = theme.value.lastUpdated
return lu !== undefined && lu !== false
})
const prefix = computed(() => {
const p = site.value.themeConfig.lastUpdated
const p = theme.value.lastUpdated
return p === true ? 'Last Updated' : p
})
@ -31,6 +23,13 @@ onMounted(() => {
})
</script>
<template>
<p v-if="hasLastUpdated" class="last-updated">
<span class="prefix">{{ prefix }}:</span>
<span class="datetime">{{ datetime }}</span>
</p>
</template>
<style scoped>
.last-updated {
display: inline-block;

@ -1,3 +1,12 @@
<script setup lang="ts">
import { defineEmit } from 'vue'
import NavBarTitle from './NavBarTitle.vue'
import NavLinks from './NavLinks.vue'
import ToggleSideBarButton from './ToggleSideBarButton.vue'
defineEmit(['toggle'])
</script>
<template>
<header class="nav-bar">
<ToggleSideBarButton @toggle="$emit('toggle')" />
@ -14,15 +23,6 @@
</header>
</template>
<script setup lang="ts">
import { defineEmit } from 'vue'
import NavBarTitle from './NavBarTitle.vue'
import NavLinks from './NavLinks.vue'
import ToggleSideBarButton from './ToggleSideBarButton.vue'
defineEmit(['toggle'])
</script>
<style scoped>
.nav-bar {
position: fixed;

@ -1,16 +1,21 @@
<script setup lang="ts">
import { withBase, useData } from 'vitepress'
const { site, theme, localePath } = useData()
</script>
<template>
<a
class="nav-bar-title"
:href="$withBase($localePath)"
:aria-label="`${$siteByRoute.title}, back to home`"
:href="withBase(localePath)"
:aria-label="`${site.title}, back to home`"
>
<img
v-if="$themeConfig.logo"
v-if="theme.logo"
class="logo"
:src="$withBase($themeConfig.logo)"
:src="withBase(theme.logo)"
alt="Logo"
/>
{{ $site.title }}
{{ site.title }}
</a>
</template>

@ -1,18 +1,3 @@
<template>
<div class="nav-dropdown-link" :class="{ open }">
<button class="button" :aria-label="item.ariaLabel" @click="toggle">
<span class="button-text">{{ item.text }}</span>
<span class="button-arrow" :class="open ? 'down' : 'right'" />
</button>
<ul class="dialog">
<li v-for="item in item.items" :key="item.text" class="dialog-item">
<NavDropdownLinkItem :item="item" />
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { defineProps, ref, watch } from 'vue'
import { useRoute } from 'vitepress'
@ -39,6 +24,21 @@ function toggle() {
}
</script>
<template>
<div class="nav-dropdown-link" :class="{ open }">
<button class="button" :aria-label="item.ariaLabel" @click="toggle">
<span class="button-text">{{ item.text }}</span>
<span class="button-arrow" :class="open ? 'down' : 'right'" />
</button>
<ul class="dialog">
<li v-for="item in item.items" :key="item.text" class="dialog-item">
<NavDropdownLinkItem :item="item" />
</li>
</ul>
</div>
</template>
<style scoped>
.nav-dropdown-link {
position: relative;

@ -1,13 +1,3 @@
<template>
<div class="nav-dropdown-link-item">
<a class="item" v-bind="linkProps">
<span class="arrow" />
<span class="text">{{ item.text }}</span>
<span class="icon"><OutboundLink v-if="isExternal" /></span>
</a>
</div>
</template>
<script setup lang="ts">
import { defineProps, toRefs } from 'vue'
import type { DefaultTheme } from '../config'
@ -23,6 +13,16 @@ const propsRefs = toRefs(props)
const { props: linkProps, isExternal } = useNavLink(propsRefs.item)
</script>
<template>
<div class="nav-dropdown-link-item">
<a class="item" v-bind="linkProps">
<span class="arrow" />
<span class="text">{{ item.text }}</span>
<span class="icon"><OutboundLink v-if="isExternal" /></span>
</a>
</div>
</template>
<style scoped>
.item {
display: block;

@ -1,11 +1,3 @@
<template>
<div class="nav-link">
<a class="item" v-bind="linkProps">
{{ item.text }} <OutboundLink v-if="isExternal" />
</a>
</div>
</template>
<script setup lang="ts">
import { defineProps, toRefs } from 'vue'
import type { DefaultTheme } from '../config'
@ -21,6 +13,14 @@ const propsRefs = toRefs(props)
const { props: linkProps, isExternal } = useNavLink(propsRefs.item)
</script>
<template>
<div class="nav-link">
<a class="item" v-bind="linkProps">
{{ item.text }} <OutboundLink v-if="isExternal" />
</a>
</div>
</template>
<style scoped>
.item {
display: block;

@ -1,7 +1,21 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useData } from 'vitepress'
import { useLocaleLinks } from '../composables/nav'
import { useRepo } from '../composables/repo'
import NavLink from './NavLink.vue'
import NavDropdownLink from './NavDropdownLink.vue'
const { theme } = useData()
const localeLinks = useLocaleLinks()
const repo = useRepo()
const show = computed(() => theme.value.value || repo.value)
</script>
<template>
<nav v-if="show" class="nav-links">
<template v-if="links">
<div v-for="item in links" :key="item.text" class="item">
<template v-if="theme.nav">
<div v-for="item in theme.nav" :key="item.text" class="item">
<NavDropdownLink v-if="item.items" :item="item" />
<NavLink v-else :item="item" />
</div>
@ -17,23 +31,6 @@
</nav>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useSiteDataByRoute } from 'vitepress'
import { useLocaleLinks } from '../composables/nav'
import { useRepo } from '../composables/repo'
import NavLink from './NavLink.vue'
import NavDropdownLink from './NavDropdownLink.vue'
const site = useSiteDataByRoute()
const localeLinks = useLocaleLinks()
const repo = useRepo()
const show = computed(() => links.value || repo.value)
const links = computed(() => site.value.themeConfig.nav)
</script>
<style scoped>
.nav-links {
padding: 0.75rem 0;

@ -1,14 +1,23 @@
<script setup lang="ts">
import { withBase } from 'vitepress'
import { useNextAndPrevLinks } from '../composables/nextAndPrevLinks'
import ArrowLeft from './icons/ArrowLeft.vue'
import ArrowRight from './icons/ArrowRight.vue'
const { hasLinks, prev, next } = useNextAndPrevLinks()
</script>
<template>
<div v-if="hasLinks" class="next-and-prev-link">
<div class="container">
<div class="prev">
<a v-if="prev" class="link" :href="$withBase(prev.link)">
<a v-if="prev" class="link" :href="withBase(prev.link)">
<ArrowLeft class="icon icon-prev" />
<span class="text">{{ prev.text }}</span>
</a>
</div>
<div class="next">
<a v-if="next" class="link" :href="$withBase(next.link)">
<a v-if="next" class="link" :href="withBase(next.link)">
<span class="text">{{ next.text }}</span>
<ArrowRight class="icon icon-next" />
</a>
@ -17,14 +26,6 @@
</div>
</template>
<script setup lang="ts">
import { useNextAndPrevLinks } from '../composables/nextAndPrevLinks'
import ArrowLeft from './icons/ArrowLeft.vue'
import ArrowRight from './icons/ArrowRight.vue'
const { hasLinks, prev, next } = useNextAndPrevLinks()
</script>
<style scoped>
.next-and-prev-link {
padding-top: 1rem;

@ -1,3 +1,8 @@
<script setup lang="ts">
import PageFooter from './PageFooter.vue'
import NextAndPrevLinks from './NextAndPrevLinks.vue'
</script>
<template>
<main class="page">
<div class="container">
@ -16,11 +21,6 @@
</main>
</template>
<script setup lang="ts">
import PageFooter from './PageFooter.vue'
import NextAndPrevLinks from './NextAndPrevLinks.vue'
</script>
<style scoped>
.page {
padding-top: var(--header-height);

@ -1,3 +1,8 @@
<script setup lang="ts">
import EditLink from './EditLink.vue'
import LastUpdated from './LastUpdated.vue'
</script>
<template>
<footer class="page-footer">
<div class="edit">
@ -9,11 +14,6 @@
</footer>
</template>
<script setup lang="ts">
import EditLink from './EditLink.vue'
import LastUpdated from './LastUpdated.vue'
</script>
<style scoped>
.page-footer {
padding-top: 1rem;

@ -1,3 +1,13 @@
<script setup lang="ts">
import { defineProps } from 'vue'
import NavLinks from './NavLinks.vue'
import SideBarLinks from './SideBarLinks.vue'
defineProps({
open: { type: Boolean, required: true }
})
</script>
<template>
<aside class="sidebar" :class="{ open }">
<NavLinks class="nav" />
@ -10,16 +20,6 @@
</aside>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
import NavLinks from './NavLinks.vue'
import SideBarLinks from './SideBarLinks.vue'
defineProps({
open: { type: Boolean, required: true }
})
</script>
<style scoped>
.sidebar {
position: fixed;

@ -1,5 +1,5 @@
import { FunctionalComponent, h, VNode } from 'vue'
import { useRoute, useSiteData } from 'vitepress'
import { useRoute, useData } from 'vitepress'
import { Header } from '/@types/shared'
import { DefaultTheme } from '../config'
import { joinUrl, isActive } from '../utils'
@ -12,7 +12,7 @@ export const SideBarLink: FunctionalComponent<{
item: DefaultTheme.SideBarItem
}> = (props) => {
const route = useRoute()
const site = useSiteData()
const { site } = useData()
const headers = route.data.headers
const text = props.item.text

@ -1,12 +1,12 @@
<template>
<ul v-if="items.length > 0" class="sidebar-links">
<SideBarLink v-for="item of items" :key="item.text" :item="item" />
</ul>
</template>
<script setup lang="ts">
import { useSideBar } from '../composables/sideBar'
import { SideBarLink } from './SideBarLink'
const items = useSideBar()
</script>
<template>
<ul v-if="items.length > 0" class="sidebar-links">
<SideBarLink v-for="item of items" :item="item" />
</ul>
</template>

@ -1,3 +1,9 @@
<script lang="ts">
export default {
emits: ['toggle']
}
</script>
<template>
<div class="sidebar-button" @click="$emit('toggle')">
<svg
@ -16,12 +22,6 @@
</div>
</template>
<script>
export default {
emits: ['toggle']
}
</script>
<style>
.sidebar-button {
position: absolute;

@ -1,25 +1,22 @@
import { computed } from 'vue'
import { useSiteDataByRoute, usePageData } from 'vitepress'
import { endingSlashRE, isNullish, isExternal } from '../utils'
import { useData } from 'vitepress'
import { endingSlashRE, isExternal } from '../utils'
const bitbucketRE = /bitbucket.org/
export function useEditLink() {
const site = useSiteDataByRoute()
const page = usePageData()
const { page, theme, frontmatter } = useData()
const url = computed(() => {
const showEditLink = isNullish(page.value.frontmatter.editLink)
? site.value.themeConfig.editLinks
: page.value.frontmatter.editLink
const {
repo,
docsDir = '',
docsBranch = 'master',
docsRepo = repo
} = site.value.themeConfig
docsRepo = repo,
editLinks
} = theme.value
const showEditLink = frontmatter.value.editLink || editLinks
const { relativePath } = page.value
if (!showEditLink || !relativePath || !repo) {
@ -30,7 +27,7 @@ export function useEditLink() {
})
const text = computed(() => {
return site.value.themeConfig.editLinkText || 'Edit this page'
return theme.value.editLinkText || 'Edit this page'
})
return {

@ -1,10 +1,10 @@
import { computed } from 'vue'
import { useRoute, useSiteData, inBrowser } from 'vitepress'
import { useRoute, useData, inBrowser } from 'vitepress'
import type { DefaultTheme } from '../config'
export function useLocaleLinks() {
const route = useRoute()
const site = useSiteData()
const { site } = useData()
return computed(() => {
const theme = site.value.themeConfig as DefaultTheme.Config

@ -1,12 +1,10 @@
import { computed, Ref } from 'vue'
import { useRoute } from 'vitepress'
import type { DefaultTheme } from '../config'
import { useRoute, withBase } from 'vitepress'
import { isExternal as isExternalCheck } from '../utils'
import { useUrl } from '../composables/url'
import type { DefaultTheme } from '../config'
export function useNavLink(item: Ref<DefaultTheme.NavItemWithLink>) {
const route = useRoute()
const { withBase } = useUrl()
const isExternal = isExternalCheck(item.value.link)

@ -1,18 +1,17 @@
import { computed } from 'vue'
import { useSiteDataByRoute, usePageData } from 'vitepress'
import { useData } from 'vitepress'
import { isArray, ensureStartingSlash, removeExtention } from '../utils'
import { getSideBarConfig, getFlatSideBarLinks } from '../support/sideBar'
export function useNextAndPrevLinks() {
const site = useSiteDataByRoute()
const page = usePageData()
const { page, theme } = useData()
const path = computed(() => {
return removeExtention(ensureStartingSlash(page.value.relativePath))
})
const candidates = computed(() => {
const config = getSideBarConfig(site.value.themeConfig.sidebar, path.value)
const config = getSideBarConfig(theme.value.sidebar, path.value)
return isArray(config) ? getFlatSideBarLinks(config) : []
})
@ -25,7 +24,7 @@ export function useNextAndPrevLinks() {
const next = computed(() => {
if (
site.value.themeConfig.nextLinks !== false &&
theme.value.nextLinks !== false &&
index.value > -1 &&
index.value < candidates.value.length - 1
) {
@ -34,7 +33,7 @@ export function useNextAndPrevLinks() {
})
const prev = computed(() => {
if (site.value.themeConfig.prevLinks !== false && index.value > 0) {
if (theme.value.prevLinks !== false && index.value > 0) {
return candidates.value[index.value - 1]
}
})

@ -1,5 +1,5 @@
import { computed } from 'vue'
import { useSiteDataByRoute } from 'vitepress'
import { useData } from 'vitepress'
import type { DefaultTheme } from '../config'
export const platforms = ['GitHub', 'GitLab', 'Bitbucket'].map((platform) => {
@ -7,7 +7,7 @@ export const platforms = ['GitHub', 'GitLab', 'Bitbucket'].map((platform) => {
})
export function useRepo() {
const site = useSiteDataByRoute()
const { site } = useData()
return computed(() => {
const theme = site.value.themeConfig as DefaultTheme.Config

@ -1,5 +1,5 @@
import { computed } from 'vue'
import { useRoute, useSiteDataByRoute } from 'vitepress'
import { useRoute, useData } from 'vitepress'
import { Header } from '/@types/shared'
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'
import { getSideBarConfig } from '../support/sideBar'
@ -7,7 +7,7 @@ import { DefaultTheme } from '../config'
export function useSideBar() {
const route = useRoute()
const site = useSiteDataByRoute()
const { site } = useData()
useActiveSidebarLinks()

@ -1,13 +0,0 @@
import { useSiteData, joinPath } from 'vitepress'
export function useUrl() {
const site = useSiteData()
function withBase(path: string): string {
return joinPath(site.value.base, path)
}
return {
withBase
}
}

@ -28,7 +28,10 @@ function resolveLocales<T>(
}
// this merges the locales data to the main data by the route
export function resolveSiteDataByRoute(siteData: SiteData, route: string) {
export function resolveSiteDataByRoute(
siteData: SiteData,
route: string
): SiteData {
route = cleanRoute(siteData, route)
const localeData = resolveLocales(siteData.locales || {}, route) || {}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save