diff --git a/src/client/app/exports.ts b/src/client/app/exports.ts
index de39bee1c8c5..f5d84533eb09 100644
--- a/src/client/app/exports.ts
+++ b/src/client/app/exports.ts
@@ -7,7 +7,7 @@ export * from './theme'
// composables
export { useSiteData } from './composables/siteData'
export { usePageData } from './composables/pageData'
-export { useRouter, useRoute } from './router'
+export { useRouter, useRoute, Router, Route } from './router'
// components
export { Content } from './components/Content'
diff --git a/src/client/theme-default/components/SideBar.ts b/src/client/theme-default/components/SideBar.ts
index 34af2c2dac69..4edb86f7d585 100644
--- a/src/client/theme-default/components/SideBar.ts
+++ b/src/client/theme-default/components/SideBar.ts
@@ -1,6 +1,7 @@
import { useSiteData, usePageData, useRoute } from 'vitepress'
-import { computed, h, FunctionalComponent } from 'vue'
+import { computed, h, FunctionalComponent, VNode } from 'vue'
import { Header } from '../../../../types/shared'
+import { isActive } from '../utils'
import { DefaultTheme } from '../config'
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'
@@ -10,14 +11,16 @@ const SideBarItem: FunctionalComponent<{
const {
item: { link, text, children }
} = props
- return h('li', [
- h('a', { href: link }, text),
- children
- ? h(
- 'ul',
- children.map((c) => h(SideBarItem, { item: c }))
- )
- : null
+
+ const route = useRoute()
+ const pageData = usePageData()
+
+ const active = isActive(route, link)
+ const headers = pageData.value.headers
+
+ return h('li', { class: 'sidebar-item' }, [
+ createLink(active, text, link),
+ createChildren(active, children, headers)
])
}
@@ -67,6 +70,10 @@ export default {
}
}
+interface HeaderWithChildren extends Header {
+ children?: Header[]
+}
+
type ResolvedSidebar = ResolvedSidebarItem[]
interface ResolvedSidebarItem {
@@ -104,7 +111,7 @@ function resolveArraySidebar(
config: DefaultTheme.SideBarItem[],
depth: number
): ResolvedSidebar {
- return []
+ return config
}
function resolveMultiSidebar(
@@ -114,3 +121,59 @@ function resolveMultiSidebar(
): ResolvedSidebar {
return []
}
+
+function createLink(active: boolean, text: string, link?: string): VNode {
+ const tag = link ? 'a' : 'p'
+
+ const component = {
+ class: { 'sidebar-link': true, active },
+ href: link
+ }
+
+ return h(tag, component, text)
+}
+
+function createChildren(
+ active: boolean,
+ children?: ResolvedSidebarItem[],
+ headers?: Header[]
+): VNode | null {
+ if (children && children.length > 0) {
+ return h(
+ 'ul',
+ { class: 'sidebar-items' },
+ children.map((c) => {
+ return h(SideBarItem, { item: c })
+ })
+ )
+ }
+
+ return active && headers
+ ? createChildren(false, resolveHeaders(headers))
+ : null
+}
+
+function resolveHeaders(headers: Header[]): ResolvedSidebarItem[] {
+ return mapHeaders(groupHeaders(headers))
+}
+
+function groupHeaders(headers: Header[]): HeaderWithChildren[] {
+ headers = headers.map((h) => Object.assign({}, h))
+ let lastH2: HeaderWithChildren
+ headers.forEach((h) => {
+ if (h.level === 2) {
+ lastH2 = h
+ } else if (lastH2) {
+ ;(lastH2.children || (lastH2.children = [])).push(h)
+ }
+ })
+ return headers.filter((h) => h.level === 2)
+}
+
+function mapHeaders(headers: HeaderWithChildren[]): ResolvedSidebarItem[] {
+ return headers.map((header) => ({
+ text: header.title,
+ link: `#${header.slug}`,
+ children: header.children ? mapHeaders(header.children) : undefined
+ }))
+}
diff --git a/src/client/theme-default/components/SideBar.vue b/src/client/theme-default/components/SideBar.vue
index 0e44829a9abb..8ce13fddd396 100644
--- a/src/client/theme-default/components/SideBar.vue
+++ b/src/client/theme-default/components/SideBar.vue
@@ -1,43 +1,61 @@
-
+
diff --git a/src/client/theme-default/utils.ts b/src/client/theme-default/utils.ts
index 4b644c3f9ab7..4c6aee47dca2 100644
--- a/src/client/theme-default/utils.ts
+++ b/src/client/theme-default/utils.ts
@@ -1,5 +1,23 @@
-import { useSiteData } from 'vitepress'
+import { useSiteData, Route } from 'vitepress'
+
+export const hashRE = /#.*$/
+export const extRE = /\.(md|html)$/
export function withBase(path: string) {
return (useSiteData().value.base + path).replace(/\/+/g, '/')
}
+
+export function isActive(route: Route, path?: string): boolean {
+ if (path === undefined) {
+ return false
+ }
+
+ const routePath = normalize(route.path)
+ const pagePath = normalize(path)
+
+ return routePath === pagePath
+}
+
+export function normalize(path: string): string {
+ return decodeURI(path).replace(hashRE, '').replace(extRE, '')
+}
diff --git a/src/node/server.ts b/src/node/server.ts
index 10f6683c54c0..e9724010dee4 100644
--- a/src/node/server.ts
+++ b/src/node/server.ts
@@ -43,8 +43,7 @@ function createVitePressPlugin({
customData: {
path: resolver.fileToRequest(file),
pageData
- },
- timestamp: Date.now()
+ }
})
// reload the content component