mirror of https://github.com/vuejs/vitepress
parent
621ca3e26f
commit
1836934f7c
@ -1,70 +1,69 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="nav-item">
|
<div class="navbar-link">
|
||||||
<a
|
<a
|
||||||
class="nav-link"
|
class="item"
|
||||||
:class="classes"
|
:class="classes"
|
||||||
:href="href"
|
:href="href"
|
||||||
:target="target"
|
:target="target"
|
||||||
:rel="rel"
|
:rel="rel"
|
||||||
:aria-label="item.ariaLabel"
|
:aria-label="item.ariaLabel"
|
||||||
>
|
>
|
||||||
{{ item.text }}
|
{{ item.text }} <OutboundLink v-if="isExternalLink" />
|
||||||
<OutboundLink v-if="isExternalLink" />
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./NavBarLink"></script>
|
<script src="./NavBarLink"></script>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
.nav-item {
|
.navbar-link {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
padding: 0 1.5rem;
|
||||||
margin-left: 1.5rem;
|
|
||||||
line-height: 2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 719px) {
|
@media (min-width: 720px) {
|
||||||
.nav-item {
|
.navbar-link {
|
||||||
display: block;
|
padding: 0;
|
||||||
margin-left: 0;
|
}
|
||||||
padding: 0.3rem 1.5rem;
|
|
||||||
|
.navbar-link + .navbar-link,
|
||||||
|
.dropdown-wrapper + .navbar-link {
|
||||||
|
padding-left: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link {
|
.item {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: -2px;
|
margin-bottom: -2px;
|
||||||
border-bottom: 2px solid transparent;
|
line-height: 40px;
|
||||||
font-size: 0.9rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
line-height: 1.4rem;
|
|
||||||
color: var(--c-text);
|
color: var(--c-text);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link:hover,
|
.item:hover,
|
||||||
.nav-link.active {
|
.item.active {
|
||||||
border-bottom-color: var(--c-brand);
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
color: var(--c-brand);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link.external:hover {
|
@media (min-width: 720px) {
|
||||||
border-bottom-color: transparent;
|
.item {
|
||||||
}
|
border-bottom: 2px solid transparent;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
font-size: .9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 719px) {
|
.item:hover,
|
||||||
.nav-link {
|
.item.active {
|
||||||
line-height: 1.7;
|
color: var(--c-text);
|
||||||
font-size: 1em;
|
border-bottom-color: var(--c-brand);
|
||||||
font-weight: 600;
|
|
||||||
border-bottom: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link:hover,
|
.item.external:hover {
|
||||||
.nav-link.active {
|
border-bottom-color: transparent;
|
||||||
color: var(--c-brand);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -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 []
|
|
||||||
}
|
|
@ -1,96 +1,72 @@
|
|||||||
<template>
|
<template>
|
||||||
<NavBarLinks class="show-mobile" />
|
<aside class="sidebar" :class="{ open }">
|
||||||
|
<div class="nav">
|
||||||
|
<NavBarLinks class="show-mobile" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<slot name="top" />
|
<slot name="sidebar-top" />
|
||||||
|
|
||||||
<ul class="sidebar">
|
<SideBarLinks />
|
||||||
<SideBarItem v-for="item of items" :item="item" />
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<slot name="bottom" />
|
<slot name="sidebar-bottom" />
|
||||||
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./SideBar"></script>
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import NavBarLinks from './NavBarLinks.vue'
|
||||||
|
import SideBarLinks from './SideBarLinks.vue'
|
||||||
|
|
||||||
<style>
|
export default defineComponent({
|
||||||
.show-mobile {
|
components: {
|
||||||
display: none;
|
NavBarLinks,
|
||||||
}
|
SideBarLinks
|
||||||
|
},
|
||||||
|
|
||||||
@media screen and (max-width: 719px) {
|
props: {
|
||||||
.show-mobile {
|
open: { type: Boolean, required: true }
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
</script>
|
||||||
.sidebar,
|
|
||||||
.sidebar-items {
|
|
||||||
list-style-type: none;
|
|
||||||
line-height: 2;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
.sidebar {
|
.sidebar {
|
||||||
padding: 1.5rem 0;
|
position: fixed;
|
||||||
|
top: var(--header-height);
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: var(--z-index-sidebar);
|
||||||
|
border-right: 1px solid var(--c-divider);
|
||||||
|
width: 16.4rem;
|
||||||
|
background-color: var(--c-bg);
|
||||||
|
overflow-y: auto;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform .25s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-data {
|
@media (min-width: 720px) {
|
||||||
padding: 1.5rem 0;
|
.sidebar {
|
||||||
}
|
transform: translateX(0);
|
||||||
|
|
||||||
@media screen and (max-width: 719px) {
|
|
||||||
.sidebar-data {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-items .sidebar-items {
|
@media (min-width: 960px) {
|
||||||
padding-left: 1rem;
|
.sidebar {
|
||||||
}
|
width: 20rem;
|
||||||
|
}
|
||||||
.sidebar-items .sidebar-items .sidebar-link {
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-items .sidebar-items .sidebar-link.active {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-items .sidebar-link {
|
|
||||||
padding: 0.35rem 1rem 0.35rem 2rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
font-size: 0.95em;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-item + .sidebar-item {
|
|
||||||
padding-top: 0.75rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-items > .sidebar-item + .sidebar-item {
|
.sidebar.open {
|
||||||
padding-top: 0;
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-link {
|
.nav {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0;
|
|
||||||
border-left: 0.25rem solid transparent;
|
|
||||||
padding: 0.35rem 1.5rem 0.35rem 1.25rem;
|
|
||||||
line-height: 1.7;
|
|
||||||
font-size: 1.05em;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--c-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
a.sidebar-link:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--c-brand);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.sidebar-link.active {
|
@media (min-width: 720px) {
|
||||||
border-left-color: var(--c-brand);
|
.nav {
|
||||||
font-weight: 600;
|
display: none;
|
||||||
color: var(--c-brand);
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<ul v-if="items.length > 0" class="sidebar-links">
|
||||||
|
<SideBarLink
|
||||||
|
v-for="item of items"
|
||||||
|
:key="item.text"
|
||||||
|
:item="item"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { useSideBar } from '../composables/sideBar'
|
||||||
|
import { SideBarLink } from './SideBarLink'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
SideBarLink
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const items = useSideBar()
|
||||||
|
|
||||||
|
return {
|
||||||
|
items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
@ -0,0 +1,77 @@
|
|||||||
|
import { computed } from 'vue'
|
||||||
|
import { useRoute, useSiteDataByRoute } from 'vitepress'
|
||||||
|
import { Header } from '/@types/shared'
|
||||||
|
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'
|
||||||
|
import { getSideBarConfig } from '../support/sideBar'
|
||||||
|
import { DefaultTheme } from '../config'
|
||||||
|
|
||||||
|
export function useSideBar() {
|
||||||
|
const route = useRoute()
|
||||||
|
const site = useSiteDataByRoute()
|
||||||
|
|
||||||
|
useActiveSidebarLinks()
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
// at first, we'll check if we can find the sidebar setting in frontmatter.
|
||||||
|
const headers = route.data.headers
|
||||||
|
const frontSidebar = route.data.frontmatter.sidebar
|
||||||
|
const sidebarDepth = route.data.frontmatter.sidebarDepth
|
||||||
|
|
||||||
|
// if it's `false`, we'll just return an empty array here.
|
||||||
|
if (frontSidebar === false) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it's `atuo`, render headers of the current page
|
||||||
|
if (frontSidebar === 'auto') {
|
||||||
|
return resolveAutoSidebar(headers, sidebarDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now, there's no sidebar setting at frontmatter; let's see the configs
|
||||||
|
const themeSidebar = getSideBarConfig(
|
||||||
|
site.value.themeConfig.sidebar,
|
||||||
|
route.path
|
||||||
|
)
|
||||||
|
|
||||||
|
if (themeSidebar === false) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themeSidebar === 'auto') {
|
||||||
|
return resolveAutoSidebar(headers, sidebarDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeSidebar
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAutoSidebar(
|
||||||
|
headers: Header[],
|
||||||
|
depth: number
|
||||||
|
): DefaultTheme.SideBarItem[] {
|
||||||
|
const ret: DefaultTheme.SideBarItem[] = []
|
||||||
|
|
||||||
|
if (headers === undefined) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastH2: DefaultTheme.SideBarItem | undefined = undefined
|
||||||
|
headers.forEach(({ level, title, slug }) => {
|
||||||
|
if (level - 1 > depth) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const item: DefaultTheme.SideBarItem = {
|
||||||
|
text: title,
|
||||||
|
link: `#${slug}`
|
||||||
|
}
|
||||||
|
if (level === 2) {
|
||||||
|
lastH2 = item
|
||||||
|
ret.push(item)
|
||||||
|
} else if (lastH2) {
|
||||||
|
;((lastH2 as any).children || ((lastH2 as any).children = [])).push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
.sidebar-links {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-link-item {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
border-left: .25rem solid transparent;
|
||||||
|
color: var(--c-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.sidebar-link-item:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--c-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.sidebar-link-item.active {
|
||||||
|
color: var(--c-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar > .sidebar-links {
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar > .sidebar-links > .sidebar-link + .sidebar-link {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar > .sidebar-links > .sidebar-link > .sidebar-link-item {
|
||||||
|
padding: .35rem 1.5rem .35rem 1.25rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar > .sidebar-links > .sidebar-link > a.sidebar-link-item.active {
|
||||||
|
border-left-color: var(--c-brand);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar > .sidebar-links > .sidebar-link > .sidebar-links > .sidebar-link > .sidebar-link-item {
|
||||||
|
display: block;
|
||||||
|
padding: .35rem 1.5rem .35rem 2rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar > .sidebar-links > .sidebar-link > .sidebar-links > .sidebar-link > a.sidebar-link-item.active {
|
||||||
|
border-left-color: var(--c-brand);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar >
|
||||||
|
.sidebar-links >
|
||||||
|
.sidebar-link >
|
||||||
|
.sidebar-links >
|
||||||
|
.sidebar-link >
|
||||||
|
.sidebar-links >
|
||||||
|
.sidebar-link >
|
||||||
|
.sidebar-link-item {
|
||||||
|
display: block;
|
||||||
|
padding: .3rem 1.5rem .3rem 3rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-size: .9rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar >
|
||||||
|
.sidebar-links >
|
||||||
|
.sidebar-link >
|
||||||
|
.sidebar-links >
|
||||||
|
.sidebar-link >
|
||||||
|
.sidebar-links >
|
||||||
|
.sidebar-link >
|
||||||
|
.sidebar-links >
|
||||||
|
.sidebar-link >
|
||||||
|
.sidebar-link-item {
|
||||||
|
display: block;
|
||||||
|
padding: .3rem 1.5rem .3rem 4rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-size: .9rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
Loading…
Reference in new issue