refactor: migrate default theme to use script-setup

pull/137/head
Evan You 5 years ago
parent e435eec94a
commit ce783e456e

@ -47,7 +47,7 @@
<Debug /> <Debug />
</template> </template>
<script> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import NavBar from './components/NavBar.vue' import NavBar from './components/NavBar.vue'
import Home from './components/Home.vue' import Home from './components/Home.vue'
@ -56,76 +56,56 @@ import SideBar from './components/SideBar.vue'
import Page from './components/Page.vue' import Page from './components/Page.vue'
import { useRoute, useSiteData, useSiteDataByRoute } from 'vitepress' import { useRoute, useSiteData, useSiteDataByRoute } from 'vitepress'
export default { const route = useRoute()
components: { const siteData = useSiteData()
Home, const siteRouteData = useSiteDataByRoute()
NavBar,
ToggleSideBarButton,
SideBar,
Page
},
setup() { const openSideBar = ref(false)
const route = useRoute() const enableHome = computed(() => !!route.data.frontmatter.home)
const siteData = useSiteData()
const siteRouteData = useSiteDataByRoute()
const openSideBar = ref(false) const showNavbar = computed(() => {
const enableHome = computed(() => !!route.data.frontmatter.home) const { themeConfig } = siteRouteData.value
const { frontmatter } = route.data
const showNavbar = computed(() => { if (frontmatter.navbar === false || themeConfig.navbar === false) {
const { themeConfig } = siteRouteData.value return false
const { frontmatter } = route.data }
if ( return (
frontmatter.navbar === false siteData.value.title ||
|| themeConfig.navbar === false) { themeConfig.logo ||
return false themeConfig.repo ||
} themeConfig.nav
return ( )
siteData.value.title })
|| themeConfig.logo
|| themeConfig.repo
|| themeConfig.nav
)
})
const showSidebar = computed(() => {
const { frontmatter } = route.data
const { themeConfig } = siteRouteData.value
return (
!frontmatter.home
&& frontmatter.sidebar !== false
&& ((typeof themeConfig.sidebar === 'object') && (Object.keys(themeConfig.sidebar).length != 0)
|| (Array.isArray(themeConfig.sidebar) && themeConfig.sidebar.length != 0))
)
})
const pageClasses = computed(() => { const showSidebar = computed(() => {
return [{ const { frontmatter } = route.data
'no-navbar': !showNavbar.value, const { themeConfig } = siteRouteData.value
'sidebar-open': openSideBar.value, return (
'no-sidebar': !showSidebar.value !frontmatter.home &&
}] frontmatter.sidebar !== false &&
}) ((typeof themeConfig.sidebar === 'object' &&
Object.keys(themeConfig.sidebar).length != 0) ||
(Array.isArray(themeConfig.sidebar) && themeConfig.sidebar.length != 0))
)
})
const toggleSidebar = (to) => { const pageClasses = computed(() => {
openSideBar.value = typeof to === 'boolean' ? to : !openSideBar.value return [
{
'no-navbar': !showNavbar.value,
'sidebar-open': openSideBar.value,
'no-sidebar': !showSidebar.value
} }
]
})
const hideSidebar = toggleSidebar.bind(null, false) const toggleSidebar = (to) => {
// close the sidebar when navigating to a different location openSideBar.value = typeof to === 'boolean' ? to : !openSideBar.value
watch(route, hideSidebar)
// TODO: route only changes when the pathname changes
// listening to hashchange does nothing because it's prevented in router
return {
showNavbar,
showSidebar,
openSideBar,
pageClasses,
enableHome,
toggleSidebar
}
}
} }
const hideSidebar = toggleSidebar.bind(null, false)
// close the sidebar when navigating to a different location
watch(route, hideSidebar)
// TODO: route only changes when the pathname changes
// listening to hashchange does nothing because it's prevented in router
</script> </script>

@ -2,15 +2,11 @@
<div class="theme"> <div class="theme">
<h1>404</h1> <h1>404</h1>
<blockquote>{{ getMsg() }}</blockquote> <blockquote>{{ getMsg() }}</blockquote>
<a :href="$site.base" aria-label="go to home"> <a :href="$site.base" aria-label="go to home"> Take me home. </a>
Take me home.
</a>
</div> </div>
</template> </template>
<script lang="ts"> <script setup>
import { defineComponent } from 'vue'
const msgs = [ const msgs = [
`There's nothing here.`, `There's nothing here.`,
`How did we get here?`, `How did we get here?`,
@ -18,11 +14,7 @@ const msgs = [
`Looks like we've got some broken links.` `Looks like we've got some broken links.`
] ]
export default defineComponent({ function getMsg() {
setup: () => ({ return msgs[Math.floor(Math.random() * msgs.length)]
getMsg() { }
return msgs[Math.floor(Math.random() * msgs.length)]
}
})
})
</script> </script>

@ -6,25 +6,11 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'
import { useEditLink } from '../composables/editLink' import { useEditLink } from '../composables/editLink'
import OutboundLink from './icons/OutboundLink.vue' import OutboundLink from './icons/OutboundLink.vue'
export default defineComponent({ const { url, text } = useEditLink()
components: {
OutboundLink
},
setup() {
const { url, text } = useEditLink()
return {
url,
text
}
}
})
</script> </script>
<style scoped> <style scoped>

@ -4,88 +4,53 @@
v-if="data.heroImage" v-if="data.heroImage"
:src="heroImageSrc" :src="heroImageSrc"
:alt="data.heroAlt || 'hero'" :alt="data.heroAlt || 'hero'"
> />
<h1 <h1 v-if="data.heroText !== null" id="main-title">
v-if="data.heroText !== null"
id="main-title"
>
{{ data.heroText || siteTitle || 'Hello' }} {{ data.heroText || siteTitle || 'Hello' }}
</h1> </h1>
<p <p v-if="data.tagline !== null" class="description">
v-if="data.tagline !== null"
class="description"
>
{{ data.tagline || siteDescription || 'Welcome to your VitePress site' }} {{ data.tagline || siteDescription || 'Welcome to your VitePress site' }}
</p> </p>
<p <p v-if="data.actionText && data.actionLink" class="action">
v-if="data.actionText && data.actionLink"
class="action"
>
<NavBarLink :item="actionLink" /> <NavBarLink :item="actionLink" />
</p> </p>
<slot name="hero" /> <slot name="hero" />
</header> </header>
<div <div v-if="data.features && data.features.length" class="features">
v-if="data.features && data.features.length" <div v-for="(feature, index) in data.features" :key="index" class="feature">
class="features"
>
<div
v-for="(feature, index) in data.features"
:key="index"
class="feature"
>
<h2>{{ feature.title }}</h2> <h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p> <p>{{ feature.details }}</p>
</div> </div>
<slot name="features" /> <slot name="features" />
</div> </div>
<div <div v-if="data.footer" class="footer">
v-if="data.footer"
class="footer"
>
{{ data.footer }} {{ data.footer }}
<slot name="footer" /> <slot name="footer" />
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent, computed } from 'vue' import { computed } from 'vue'
import NavBarLink from './NavBarLink.vue' import NavBarLink from './NavBarLink.vue'
import { withBase } from '../utils' import { withBase } from '../utils'
import { useRoute, useSiteData } from 'vitepress' import { useRoute, useSiteData } from 'vitepress'
export default defineComponent({ const route = useRoute()
components: { const siteData = useSiteData()
NavBarLink
}, const data = computed(() => route.data.frontmatter)
const actionLink = computed(() => ({
setup() { link: data.value.actionLink,
const route = useRoute() text: data.value.actionText
const siteData = useSiteData() }))
const heroImageSrc = computed(() => withBase(data.value.heroImage))
const data = computed(() => route.data.frontmatter) const siteTitle = computed(() => siteData.value.title)
const actionLink = computed(() => ({ const siteDescription = computed(() => siteData.value.description)
link: data.value.actionLink,
text: data.value.actionText
}))
const heroImageSrc = computed(() => withBase(data.value.heroImage))
const siteTitle = computed(() => siteData.value.title)
const siteDescription = computed(() => siteData.value.description)
return {
data,
actionLink,
heroImageSrc,
siteTitle,
siteDescription
}
}
})
</script> </script>
<style scoped> <style scoped>
@ -126,7 +91,7 @@ export default defineComponent({
margin-left: 0; margin-left: 0;
padding: 0.8rem 1.6rem; padding: 0.8rem 1.6rem;
border-radius: 4px; border-radius: 4px;
transition: background-color .1s ease; transition: background-color 0.1s ease;
box-sizing: border-box; box-sizing: border-box;
/* TODO: calculating darken 10% color with using style vars from `--accent-color` */ /* TODO: calculating darken 10% color with using style vars from `--accent-color` */
border-bottom: 1px solid #389d70; border-bottom: 1px solid #389d70;

@ -1,12 +0,0 @@
import { withBase } from '../utils'
import NavBarLinks from './NavBarLinks.vue'
export default {
components: {
NavBarLinks
},
setup() {
return { withBase }
}
}

@ -17,7 +17,10 @@
<slot name="search" /> <slot name="search" />
</template> </template>
<script src="./NavBar"></script> <script setup lang="ts">
import { withBase } from '../utils'
import NavBarLinks from './NavBarLinks.vue'
</script>
<style> <style>
.title { .title {

@ -1,77 +0,0 @@
import { defineComponent, computed, PropType } from 'vue'
import { useRoute } from 'vitepress'
import { withBase, isExternal } from '../utils'
import { DefaultTheme } from '../config'
import OutboundLink from './icons/OutboundLink.vue'
const normalizePath = (path: string): string => {
path = path
.replace(/#.*$/, '')
.replace(/\?.*$/, '')
.replace(/\.html$/, '')
if (path.endsWith('/')) {
path += 'index'
}
return path
}
export default defineComponent({
components: {
OutboundLink
},
props: {
item: {
type: Object as PropType<DefaultTheme.NavItemWithLink>,
required: true
}
},
setup(props) {
const item = props.item
const route = useRoute()
const classes = computed(() => ({
active: isActiveLink.value,
external: isExternalLink.value
}))
const isActiveLink = computed(() => {
return normalizePath(withBase(item.link)) === normalizePath(route.path)
})
const isExternalLink = computed(() => {
return isExternal(item.link)
})
const href = computed(() => {
return isExternalLink.value ? item.link : withBase(item.link)
})
const target = computed(() => {
if (item.target) {
return item.target
}
return isExternalLink.value ? '_blank' : ''
})
const rel = computed(() => {
if (item.rel) {
return item.rel
}
return isExternalLink.value ? 'noopener noreferrer' : ''
})
return {
classes,
isActiveLink,
isExternalLink,
href,
target,
rel
}
}
})

@ -14,7 +14,67 @@
</div> </div>
</template> </template>
<script src="./NavBarLink"></script> <script setup lang="ts">
import { computed, defineOptions } from 'vue'
import { useRoute } from 'vitepress'
import { withBase, isExternal } from '../utils'
import type { DefaultTheme } from '../config'
import OutboundLink from './icons/OutboundLink.vue'
const normalizePath = (path: string): string => {
path = path
.replace(/#.*$/, '')
.replace(/\?.*$/, '')
.replace(/\.html$/, '')
if (path.endsWith('/')) {
path += 'index'
}
return path
}
const { props } = defineOptions<{
props: {
item: DefaultTheme.NavItemWithLink
}
}>()
const item = props.item
const route = useRoute()
const classes = computed(() => ({
active: isActiveLink.value,
external: isExternalLink.value
}))
const isActiveLink = computed(() => {
return normalizePath(withBase(item.link)) === normalizePath(route.path)
})
const isExternalLink = computed(() => {
return isExternal(item.link)
})
const href = computed(() => {
return isExternalLink.value ? item.link : withBase(item.link)
})
const target = computed(() => {
if (item.target) {
return item.target
}
return isExternalLink.value ? '_blank' : ''
})
const rel = computed(() => {
if (item.rel) {
return item.rel
}
return isExternalLink.value ? 'noopener noreferrer' : ''
})
</script>
<style> <style>
.nav-item { .nav-item {

@ -1,101 +0,0 @@
import { computed } from 'vue'
import { useSiteData, useSiteDataByRoute, useRoute } from 'vitepress'
import { inBrowser } from '/@app/utils'
import NavBarLink from './NavBarLink.vue'
import NavDropdownLink from './NavDropdownLink.vue'
import { DefaultTheme } from '../config'
const platforms = ['GitHub', 'GitLab', 'Bitbucket'].map(
(platform) => [platform, new RegExp(platform, 'i')] as const
)
export default {
components: {
NavBarLink,
NavDropdownLink
},
setup() {
const siteDataByRoute = useSiteDataByRoute()
const siteData = useSiteData()
const route = useRoute()
const repoInfo = computed(() => {
const theme = siteData.value.themeConfig as DefaultTheme.Config
const repo = theme.docsRepo || theme.repo
let text: string | undefined = theme.repoLabel
if (repo) {
const link = /^https?:/.test(repo) ? repo : `https://github.com/${repo}`
if (!text) {
// if no label is provided, deduce it from the repo url
const repoHosts = link.match(/^https?:\/\/[^/]+/)
if (repoHosts) {
const repoHost = repoHosts[0]
const foundPlatform = platforms.find(([_platform, re]) =>
re.test(repoHost)
)
text = foundPlatform && foundPlatform[0]
}
}
return { link, text: text || 'Source' }
}
return null
})
const localeCandidates = computed(() => {
const locales = siteData.value.themeConfig.locales
if (!locales) {
return null
}
const localeKeys = Object.keys(locales)
if (localeKeys.length <= 1) {
return null
}
// handle site base
const siteBase = inBrowser ? siteData.value.base : '/'
const siteBaseWithoutSuffix = siteBase.endsWith('/')
? siteBase.slice(0, -1)
: siteBase
// remove site base in browser env
const routerPath = route.path.slice(siteBaseWithoutSuffix.length)
const currentLangBase = localeKeys.find((v) => {
if (v === '/') {
return false
}
return routerPath.startsWith(v)
})
const currentContentPath = currentLangBase
? routerPath.substring(currentLangBase.length - 1)
: routerPath
const candidates = localeKeys.map((v) => {
const localePath = v.endsWith('/') ? v.slice(0, -1) : v
return {
text: locales[v].label || locales[v].lang,
link: `${localePath}${currentContentPath}`
}
})
const currentLangKey = currentLangBase ? currentLangBase : '/'
const selectText = locales[currentLangKey].selectText
? locales[currentLangKey].selectText
: 'Languages'
return {
text: selectText,
items: candidates
}
})
const navData = computed(() => {
return siteDataByRoute.value.themeConfig.nav
})
return {
navData,
repoInfo,
localeCandidates
}
}
}

@ -11,7 +11,94 @@
</nav> </nav>
</template> </template>
<script src="./NavBarLinks"></script> <script setup lang="ts">
import { computed } from 'vue'
import { useSiteData, useSiteDataByRoute, useRoute } from 'vitepress'
import { inBrowser } from '/@app/utils'
import NavBarLink from './NavBarLink.vue'
import NavDropdownLink from './NavDropdownLink.vue'
import type { DefaultTheme } from '../config'
const platforms = ['GitHub', 'GitLab', 'Bitbucket'].map(
(platform) => [platform, new RegExp(platform, 'i')] as const
)
const siteDataByRoute = useSiteDataByRoute()
const siteData = useSiteData()
const route = useRoute()
const repoInfo = computed(() => {
const theme = siteData.value.themeConfig as DefaultTheme.Config
const repo = theme.docsRepo || theme.repo
let text: string | undefined = theme.repoLabel
if (repo) {
const link = /^https?:/.test(repo) ? repo : `https://github.com/${repo}`
if (!text) {
// if no label is provided, deduce it from the repo url
const repoHosts = link.match(/^https?:\/\/[^/]+/)
if (repoHosts) {
const repoHost = repoHosts[0]
const foundPlatform = platforms.find(([_platform, re]) =>
re.test(repoHost)
)
text = foundPlatform && foundPlatform[0]
}
}
return { link, text: text || 'Source' }
}
return null
})
const localeCandidates = computed(() => {
const locales = siteData.value.themeConfig.locales
if (!locales) {
return null
}
const localeKeys = Object.keys(locales)
if (localeKeys.length <= 1) {
return null
}
// handle site base
const siteBase = inBrowser ? siteData.value.base : '/'
const siteBaseWithoutSuffix = siteBase.endsWith('/')
? siteBase.slice(0, -1)
: siteBase
// remove site base in browser env
const routerPath = route.path.slice(siteBaseWithoutSuffix.length)
const currentLangBase = localeKeys.find((v) => {
if (v === '/') {
return false
}
return routerPath.startsWith(v)
})
const currentContentPath = currentLangBase
? routerPath.substring(currentLangBase.length - 1)
: routerPath
const candidates = localeKeys.map((v) => {
const localePath = v.endsWith('/') ? v.slice(0, -1) : v
return {
text: locales[v].label || locales[v].lang,
link: `${localePath}${currentContentPath}`
}
})
const currentLangKey = currentLangBase ? currentLangBase : '/'
const selectText = locales[currentLangKey].selectText
? locales[currentLangKey].selectText
: 'Languages'
return {
text: selectText,
items: candidates
}
})
const navData = computed(() => {
return siteDataByRoute.value.themeConfig.nav
})
</script>
<style> <style>
.nav-links { .nav-links {

@ -1,42 +0,0 @@
import NavBarLink from './NavBarLink.vue'
import { defineComponent, ref, watch, PropType } from 'vue'
import { useRoute } from 'vitepress'
import { DefaultTheme } from '../config'
export default defineComponent({
name: 'DropdownLink',
components: {
NavBarLink
},
props: {
item: {
type: Object as PropType<DefaultTheme.NavItemWithChildren>,
required: true
}
},
setup(props) {
const open = ref(false)
const route = useRoute()
watch(
() => route.path,
() => {
open.value = false
}
)
const setOpen = (value: boolean) => {
open.value = value
}
const isLastItemOfArray = <T>(item: T, array: T[]) => {
return array.length && array.indexOf(item) === array.length - 1
}
return {
open,
setOpen,
isLastItemOfArray
}
}
})

@ -11,7 +11,11 @@
</button> </button>
<ul class="nav-dropdown"> <ul class="nav-dropdown">
<li v-for="(subItem, index) in item.items" :key="subItem.link || index" class="dropdown-item"> <li
v-for="(subItem, index) in item.items"
:key="subItem.link || index"
class="dropdown-item"
>
<h4 v-if="subItem.items">{{ subItem.text }}</h4> <h4 v-if="subItem.items">{{ subItem.text }}</h4>
<ul v-if="subItem.items" class="dropdown-subitem-wrapper"> <ul v-if="subItem.items" class="dropdown-subitem-wrapper">
<li <li
@ -22,10 +26,10 @@
<NavBarLink <NavBarLink
:item="childSubItem" :item="childSubItem"
@focusout=" @focusout="
isLastItemOfArray(childSubItem, subItem.items) && isLastItemOfArray(childSubItem, subItem.items) &&
isLastItemOfArray(subItem, item.items) && isLastItemOfArray(subItem, item.items) &&
setOpen(false) setOpen(false)
" "
/> />
</li> </li>
</ul> </ul>
@ -40,7 +44,36 @@
</div> </div>
</template> </template>
<script src="./NavDropdownLink"></script> <script setup lang="ts">
import NavBarLink from './NavBarLink.vue'
import { ref, watch, defineOptions } from 'vue'
import { useRoute } from 'vitepress'
import type { DefaultTheme } from '../config'
defineOptions<{
props: {
item: DefaultTheme.NavItemWithChildren
}
}>()
const open = ref(false)
const route = useRoute()
watch(
() => route.path,
() => {
open.value = false
}
)
const setOpen = (value: boolean) => {
open.value = value
}
const isLastItemOfArray = <T>(item: T, array: T[]) => {
return array.length && array.indexOf(item) === array.length - 1
}
</script>
<style> <style>
.dropdown-wrapper { .dropdown-wrapper {

@ -1,61 +0,0 @@
import { computed } from 'vue'
import { useRoute, useSiteData } from 'vitepress'
import { DefaultTheme } from '../config'
export default {
setup() {
const route = useRoute()
// TODO: could this be useSiteData<DefaultTheme.Config> or is the siteData
// resolved and has a different structure?
const siteData = useSiteData()
const resolveLink = (targetLink: string) => {
let target: DefaultTheme.SideBarLink | undefined
Object.keys(siteData.value.themeConfig.sidebar).some((k) => {
return siteData.value.themeConfig.sidebar[k].some(
(v: { children: any }) => {
if (Array.isArray(v.children)) {
target = v.children.find((value: any) => {
return value.link === targetLink
})
}
return !!target
}
)
})
return target
}
const next = computed(() => {
const pageData = route.data
if (pageData.frontmatter.next === false) {
return undefined
}
if (typeof pageData.frontmatter.next === 'string') {
return resolveLink(pageData.frontmatter.next)
}
return pageData.next
})
const prev = computed(() => {
const pageData = route.data
if (pageData.frontmatter.prev === false) {
return undefined
}
if (typeof pageData.frontmatter.prev === 'string') {
return resolveLink(pageData.frontmatter.prev)
}
return pageData.prev
})
const hasLinks = computed(() => {
return !!next || !!prev
})
return {
next,
prev,
hasLinks
}
}
}

@ -15,28 +15,12 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'
import { useNextAndPrevLinks } from '../composables/nextAndPrevLinks'
import ArrowLeft from './icons/ArrowLeft.vue' import ArrowLeft from './icons/ArrowLeft.vue'
import ArrowRight from './icons/ArrowRight.vue' import ArrowRight from './icons/ArrowRight.vue'
import { useNextAndPrevLinks } from '../composables/nextAndPrevLinks'
export default defineComponent({ const { hasLinks, prev, next } = useNextAndPrevLinks()
components: {
ArrowLeft,
ArrowRight
},
setup () {
const { hasLinks, prev, next } = useNextAndPrevLinks()
return {
hasLinks,
prev,
next
}
}
})
</script> </script>
<style scoped> <style scoped>

@ -13,17 +13,9 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'
import EditLink from './EditLink.vue' import EditLink from './EditLink.vue'
import NextAndPrevLinks from './NextAndPrevLinks.vue' import NextAndPrevLinks from './NextAndPrevLinks.vue'
export default defineComponent({
components: {
EditLink,
NextAndPrevLinks
}
})
</script> </script>
<style scoped> <style scoped>

@ -1,123 +0,0 @@
import { useRoute, useSiteDataByRoute } from 'vitepress'
import { computed } from 'vue'
import { Header } from '../../../../types/shared'
import { getPathDirName } from '../utils'
import { DefaultTheme } from '../config'
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'
import NavBarLinks from './NavBarLinks.vue'
import { SideBarItem } from './SideBarItem'
export interface ResolvedSidebarItem {
text: string
link?: string
isGroup?: boolean
children?: ResolvedSidebarItem[]
}
type ResolvedSidebar = ResolvedSidebarItem[]
export default {
components: {
NavBarLinks,
SideBarItem
},
setup() {
const route = useRoute()
const siteData = useSiteDataByRoute()
useActiveSidebarLinks()
return {
items: computed(() => {
const {
headers,
frontmatter: { sidebar, sidebarDepth = 2 }
} = route.data
if (sidebar === 'auto') {
// auto, render headers of current page
return resolveAutoSidebar(headers, sidebarDepth)
} else if (Array.isArray(sidebar)) {
// in-page array config
return resolveArraySidebar(sidebar, sidebarDepth)
} else if (sidebar === false) {
return []
} else {
// no explicit page sidebar config
// check global theme config
const { sidebar: themeSidebar } = siteData.value.themeConfig
if (themeSidebar === 'auto') {
return resolveAutoSidebar(headers, sidebarDepth)
} else if (Array.isArray(themeSidebar)) {
return resolveArraySidebar(themeSidebar, sidebarDepth)
} else if (themeSidebar === false) {
return []
} else if (typeof themeSidebar === 'object') {
return resolveMultiSidebar(
themeSidebar,
route.path,
headers,
sidebarDepth
)
}
}
})
}
}
}
function resolveAutoSidebar(headers: Header[], depth: number): ResolvedSidebar {
const ret: ResolvedSidebar = []
if (headers === undefined) {
return []
}
let lastH2: ResolvedSidebarItem | undefined = undefined
headers.forEach(({ level, title, slug }) => {
if (level - 1 > depth) {
return
}
const item: ResolvedSidebarItem = {
text: title,
link: `#${slug}`
}
if (level === 2) {
lastH2 = item
ret.push(item)
} else if (lastH2) {
;(lastH2.children || (lastH2.children = [])).push(item)
}
})
return ret
}
function resolveArraySidebar(
config: DefaultTheme.SideBarItem[],
depth: number
): ResolvedSidebar {
return config
}
function resolveMultiSidebar(
config: DefaultTheme.MultiSideBarConfig,
path: string,
headers: Header[],
depth: number
): ResolvedSidebar {
const paths = [path, Object.keys(config)[0]]
const item = paths.map((x) => config[getPathDirName(x)]).find(Boolean)
if (Array.isArray(item)) {
return resolveArraySidebar(item, depth)
}
if (item === 'auto') {
return resolveAutoSidebar(headers, depth)
}
return []
}

@ -10,7 +10,114 @@
<slot name="bottom" /> <slot name="bottom" />
</template> </template>
<script src="./SideBar"></script> <script setup lang="ts">
import { useRoute, useSiteDataByRoute } from 'vitepress'
import { computed } from 'vue'
import { getPathDirName } from '../utils'
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'
import NavBarLinks from './NavBarLinks.vue'
import { SideBarItem } from './SideBarItem'
import type { DefaultTheme } from '../config'
import type { Header } from '../../../../types/shared'
import type { ResolvedSidebarItem } from './SideBarItem'
type ResolvedSidebar = ResolvedSidebarItem[]
const route = useRoute()
const siteData = useSiteDataByRoute()
useActiveSidebarLinks()
const items = computed(() => {
const {
headers,
frontmatter: { sidebar, sidebarDepth = 2 }
} = route.data
if (sidebar === 'auto') {
// auto, render headers of current page
return resolveAutoSidebar(headers, sidebarDepth)
} else if (Array.isArray(sidebar)) {
// in-page array config
return resolveArraySidebar(sidebar, sidebarDepth)
} else if (sidebar === false) {
return []
} else {
// no explicit page sidebar config
// check global theme config
const { sidebar: themeSidebar } = siteData.value.themeConfig
if (themeSidebar === 'auto') {
return resolveAutoSidebar(headers, sidebarDepth)
} else if (Array.isArray(themeSidebar)) {
return resolveArraySidebar(themeSidebar, sidebarDepth)
} else if (themeSidebar === false) {
return []
} else if (typeof themeSidebar === 'object') {
return resolveMultiSidebar(
themeSidebar,
route.path,
headers,
sidebarDepth
)
}
}
})
function resolveAutoSidebar(headers: Header[], depth: number): ResolvedSidebar {
const ret: ResolvedSidebar = []
if (headers === undefined) {
return []
}
let lastH2: ResolvedSidebarItem | undefined = undefined
headers.forEach(({ level, title, slug }) => {
if (level - 1 > depth) {
return
}
const item: ResolvedSidebarItem = {
text: title,
link: `#${slug}`
}
if (level === 2) {
lastH2 = item
ret.push(item)
} else if (lastH2) {
;(lastH2.children || (lastH2.children = [])).push(item)
}
})
return ret
}
function resolveArraySidebar(
config: DefaultTheme.SideBarItem[],
depth: number
): ResolvedSidebar {
return config
}
function resolveMultiSidebar(
config: DefaultTheme.MultiSideBarConfig,
path: string,
headers: Header[],
depth: number
): ResolvedSidebar {
const paths = [path, Object.keys(config)[0]]
const item = paths.map((x) => config[getPathDirName(x)]).find(Boolean)
if (Array.isArray(item)) {
return resolveArraySidebar(item, depth)
}
if (item === 'auto') {
return resolveAutoSidebar(headers, depth)
}
return []
}
</script>
<style> <style>
.show-mobile { .show-mobile {

@ -2,7 +2,13 @@ import { useRoute, useSiteData } from 'vitepress'
import { FunctionalComponent, h, VNode } from 'vue' import { FunctionalComponent, h, VNode } from 'vue'
import { Header } from '../../../../types/shared' import { Header } from '../../../../types/shared'
import { joinUrl, isActive } from '../utils' import { joinUrl, isActive } from '../utils'
import { ResolvedSidebarItem } from './SideBar'
export interface ResolvedSidebarItem {
text: string
link?: string
isGroup?: boolean
children?: ResolvedSidebarItem[]
}
interface HeaderWithChildren extends Header { interface HeaderWithChildren extends Header {
children?: Header[] children?: Header[]

Loading…
Cancel
Save