Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 01dd45b

Browse files
authoredFeb 3, 2019
feat($theme-default): refine sidebar groups (#1257)
Close: #814 Close: #783 Close: #287
1 parent caca1db commit 01dd45b

File tree

10 files changed

+233
-107
lines changed

10 files changed

+233
-107
lines changed
 

‎packages/@vuepress/theme-default/components/Page.vue

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,20 +177,25 @@ function resolveNext (page, items) {
177177
178178
function find (page, items, offset) {
179179
const res = []
180-
items.forEach(item => {
181-
if (item.type === 'group') {
182-
res.push(...item.children || [])
183-
} else {
184-
res.push(item)
185-
}
186-
})
180+
flattern(items, res)
187181
for (let i = 0; i < res.length; i++) {
188182
const cur = res[i]
189183
if (cur.type === 'page' && cur.path === decodeURIComponent(page.path)) {
190184
return res[i + offset]
191185
}
192186
}
193187
}
188+
189+
function flattern (items, res) {
190+
for (let i = 0, l = items.length; i < l; i++) {
191+
if (items[i].type === 'group') {
192+
flattern(items[i].children || [], res)
193+
} else {
194+
res.push(items[i])
195+
}
196+
}
197+
}
198+
194199
</script>
195200

196201
<style lang="stylus">

‎packages/@vuepress/theme-default/components/Sidebar.vue

Lines changed: 9 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,21 @@
22
<aside class="sidebar">
33
<NavLinks/>
44
<slot name="top"/>
5-
<ul class="sidebar-links" v-if="items.length">
6-
<li v-for="(item, i) in items" :key="i">
7-
<SidebarGroup
8-
v-if="item.type === 'group'"
9-
:item="item"
10-
:first="i === 0"
11-
:open="i === openGroupIndex"
12-
:collapsable="item.collapsable || item.collapsible"
13-
@toggle="toggleGroup(i)"
14-
/>
15-
<SidebarLink v-else :item="item"/>
16-
</li>
17-
</ul>
5+
<SidebarLinks :depth="0" :items="items"/>
186
<slot name="bottom"/>
197
</aside>
208
</template>
219

2210
<script>
23-
import SidebarGroup from './SidebarGroup.vue'
24-
import SidebarLink from './SidebarLink.vue'
11+
import SidebarLinks from './SidebarLinks.vue'
2512
import NavLinks from './NavLinks.vue'
26-
import { isActive } from '../util'
2713
2814
export default {
29-
components: { SidebarGroup, SidebarLink, NavLinks },
15+
name: 'Sidebar',
3016
31-
props: ['items'],
17+
components: { SidebarLinks, NavLinks },
3218
33-
data () {
34-
return {
35-
openGroupIndex: 0
36-
}
37-
},
38-
39-
created () {
40-
this.refreshIndex()
41-
},
42-
43-
watch: {
44-
'$route' () {
45-
this.refreshIndex()
46-
}
47-
},
48-
49-
methods: {
50-
refreshIndex () {
51-
const index = resolveOpenGroupIndex(
52-
this.$route,
53-
this.items
54-
)
55-
if (index > -1) {
56-
this.openGroupIndex = index
57-
}
58-
},
59-
60-
toggleGroup (index) {
61-
this.openGroupIndex = index === this.openGroupIndex ? -1 : index
62-
},
63-
64-
isActive (page) {
65-
return isActive(this.$route, page.regularPath)
66-
}
67-
}
68-
}
69-
70-
function resolveOpenGroupIndex (route, items) {
71-
for (let i = 0; i < items.length; i++) {
72-
const item = items[i]
73-
if (item.type === 'group' && item.children.some(c => isActive(route, c.path))) {
74-
return i
75-
}
76-
}
77-
return -1
19+
props: ['items']
7820
}
7921
</script>
8022

@@ -97,15 +39,17 @@ function resolveOpenGroupIndex (route, items) {
9739
line-height 1.25rem
9840
font-size 1.1em
9941
padding 0.5rem 0 0.5rem 1.5rem
100-
.sidebar-links
42+
& > .sidebar-links
10143
padding 1.5rem 0
44+
& > li:not(:first-child)
45+
margin-top .75rem
10246
10347
@media (max-width: $MQMobile)
10448
.sidebar
10549
.nav-links
10650
display block
10751
.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after
10852
top calc(1rem - 2px)
109-
.sidebar-links
53+
& > .sidebar-links
11054
padding 1rem 0
11155
</style>
Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,34 @@
11
<template>
22
<section
33
class="sidebar-group"
4-
:class="{ first, collapsable }"
4+
:class="[
5+
{
6+
collapsable,
7+
'is-sub-group': depth !== 0
8+
},
9+
`depth-${depth}`
10+
]"
511
>
12+
<router-link
13+
v-if="item.path"
14+
class="sidebar-heading clickable"
15+
:class="{
16+
open,
17+
'active': isActive($route, item.path)
18+
}"
19+
:to="item.path"
20+
@click.native="$emit('toggle')"
21+
>
22+
<span>{{ item.title }}</span>
23+
<span
24+
class="arrow"
25+
v-if="collapsable"
26+
:class="open ? 'down' : 'right'">
27+
</span>
28+
</router-link>
29+
630
<p
31+
v-else
732
class="sidebar-heading"
833
:class="{ open }"
934
@click="$emit('toggle')"
@@ -17,40 +42,59 @@
1742
</p>
1843

1944
<DropdownTransition>
20-
<ul
21-
ref="items"
45+
<SidebarLinks
2246
class="sidebar-group-items"
47+
:items="item.children"
2348
v-if="open || !collapsable"
24-
>
25-
<li v-for="child in item.children">
26-
<SidebarLink :item="child"/>
27-
</li>
28-
</ul>
49+
:sidebarDepth="item.sidebarDepth"
50+
:depth="depth + 1"
51+
/>
2952
</DropdownTransition>
3053
</section>
3154
</template>
3255

3356
<script>
34-
import SidebarLink from './SidebarLink.vue'
57+
import { isActive } from '../util'
3558
import DropdownTransition from './DropdownTransition.vue'
3659
3760
export default {
3861
name: 'SidebarGroup',
39-
props: ['item', 'first', 'open', 'collapsable'],
40-
components: { SidebarLink, DropdownTransition }
62+
props: ['item', 'open', 'collapsable', 'depth'],
63+
components: { DropdownTransition },
64+
// ref: https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
65+
beforeCreate () {
66+
this.$options.components.SidebarLinks = require('./SidebarLinks.vue').default
67+
},
68+
methods: { isActive }
4169
}
4270
</script>
4371

4472
<style lang="stylus">
4573
.sidebar-group
46-
&:not(.first)
47-
margin-top 1em
4874
.sidebar-group
4975
padding-left 0.5em
5076
&:not(.collapsable)
51-
.sidebar-heading
77+
.sidebar-heading:not(.clickable)
5278
cursor auto
5379
color inherit
80+
// refine styles of nested sidebar groups
81+
&.is-sub-group
82+
padding-left 0
83+
& > .sidebar-heading
84+
font-size 0.95em
85+
line-height 1.4
86+
font-weight normal
87+
padding-left 2rem
88+
&:not(.clickable)
89+
opacity 0.5
90+
& > .sidebar-group-items
91+
padding-left 1rem
92+
& > li > .sidebar-link
93+
font-size: 0.95em;
94+
border-left none
95+
&.depth-2
96+
& > .sidebar-heading
97+
border-left none
5498
5599
.sidebar-heading
56100
color #999
@@ -59,19 +103,27 @@ export default {
59103
font-size 1.1em
60104
font-weight bold
61105
// text-transform uppercase
62-
padding 0 1.5rem
63-
margin-top 0
64-
margin-bottom 0.5rem
106+
padding 0.35rem 1.5rem 0.35rem 1.25rem
107+
width 100%
108+
box-sizing border-box
109+
margin 0
110+
border-left 0.25rem solid transparent
65111
&.open, &:hover
66112
color inherit
67113
.arrow
68114
position relative
69115
top -0.12em
70116
left 0.5em
71-
&:.open .arrow
72-
top -0.18em
117+
&.clickable
118+
&.active
119+
font-weight 600
120+
color $accentColor
121+
border-left-color $accentColor
122+
&:hover
123+
color $accentColor
73124
74125
.sidebar-group-items
75126
transition height .1s ease-out
127+
font-size 0.95em
76128
overflow hidden
77129
</style>

‎packages/@vuepress/theme-default/components/SidebarLink.vue

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,22 @@ import { isActive, hashRE, groupHeaders } from '../util'
44
export default {
55
functional: true,
66
7-
props: ['item'],
7+
props: ['item', 'sidebarDepth'],
88
9-
render (h, { parent: { $page, $site, $route }, props: { item }}) {
9+
render (h,
10+
{
11+
parent: {
12+
$page,
13+
$site,
14+
$route,
15+
$themeConfig,
16+
$themeLocaleConfig
17+
},
18+
props: {
19+
item,
20+
sidebarDepth
21+
}
22+
}) {
1023
// use custom active class matching logic
1124
// due to edge case of paths ending with / + hash
1225
const selfActive = isActive($route, item.path)
@@ -16,11 +29,17 @@ export default {
1629
? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug))
1730
: selfActive
1831
const link = renderLink(h, item.path, item.title || item.path, active)
19-
const configDepth = $page.frontmatter.sidebarDepth != null
20-
? $page.frontmatter.sidebarDepth
21-
: $site.themeConfig.sidebarDepth
32+
33+
const configDepth = $page.frontmatter.sidebarDepth ||
34+
sidebarDepth ||
35+
$themeLocaleConfig.sidebarDepth ||
36+
$themeConfig.sidebarDepth
37+
2238
const maxDepth = configDepth == null ? 1 : configDepth
23-
const displayAllHeaders = !!$site.themeConfig.displayAllHeaders
39+
40+
const displayAllHeaders = $themeLocaleConfig.displayAllHeaders ||
41+
$themeConfig.displayAllHeaders
42+
2443
if (item.type === 'auto') {
2544
return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)]
2645
} else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) {
@@ -64,6 +83,7 @@ function renderChildren (h, children, path, route, maxDepth, depth = 1) {
6483
font-size 0.95em
6584
6685
a.sidebar-link
86+
font-size 1em
6787
font-weight 400
6888
display inline-block
6989
color $textColor
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<template>
2+
<ul
3+
class="sidebar-links"
4+
v-if="items.length"
5+
>
6+
<li v-for="(item, i) in items" :key="i">
7+
<SidebarGroup
8+
v-if="item.type === 'group'"
9+
:item="item"
10+
:open="i === openGroupIndex"
11+
:collapsable="item.collapsable || item.collapsible"
12+
:depth="depth"
13+
@toggle="toggleGroup(i)"
14+
/>
15+
<SidebarLink
16+
v-else
17+
:sidebarDepth="sidebarDepth"
18+
:item="item"
19+
/>
20+
</li>
21+
</ul>
22+
</template>
23+
24+
<script>
25+
import SidebarGroup from './SidebarGroup.vue'
26+
import SidebarLink from './SidebarLink.vue'
27+
import { isActive } from '../util'
28+
29+
export default {
30+
name: 'SidebarLinks',
31+
32+
components: { SidebarGroup, SidebarLink },
33+
34+
props: [
35+
'items',
36+
'depth', // depth of current sidebar links
37+
'sidebarDepth' // depth of headers to be extracted
38+
],
39+
40+
data () {
41+
return {
42+
openGroupIndex: 0
43+
}
44+
},
45+
46+
created () {
47+
this.refreshIndex()
48+
},
49+
50+
watch: {
51+
'$route' () {
52+
this.refreshIndex()
53+
}
54+
},
55+
56+
methods: {
57+
refreshIndex () {
58+
const index = resolveOpenGroupIndex(
59+
this.$route,
60+
this.items
61+
)
62+
if (index > -1) {
63+
this.openGroupIndex = index
64+
}
65+
},
66+
67+
toggleGroup (index) {
68+
this.openGroupIndex = index === this.openGroupIndex ? -1 : index
69+
},
70+
71+
isActive (page) {
72+
return isActive(this.$route, page.regularPath)
73+
}
74+
}
75+
}
76+
77+
function resolveOpenGroupIndex (route, items) {
78+
for (let i = 0; i < items.length; i++) {
79+
const item = items[i]
80+
if (item.type === 'group' && item.children.some(c => c.type === 'page' && isActive(route, c.path))) {
81+
return i
82+
}
83+
}
84+
return -1
85+
}
86+
</script>

‎packages/@vuepress/theme-default/styles/theme.styl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ body
4040
display none
4141

4242
.sidebar
43-
font-size 15px
43+
font-size 16px
4444
background-color #fff
4545
width $sidebarWidth
4646
position fixed

‎packages/@vuepress/theme-default/util/index.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ function resolveHeaders (page) {
148148
type: 'group',
149149
collapsable: false,
150150
title: page.title,
151+
path: null,
151152
children: headers.map(h => ({
152153
type: 'auto',
153154
title: h.title,
@@ -207,25 +208,26 @@ function ensureEndingSlash (path) {
207208
: path + '/'
208209
}
209210

210-
function resolveItem (item, pages, base, isNested) {
211+
function resolveItem (item, pages, base, groupDepth = 1) {
211212
if (typeof item === 'string') {
212213
return resolvePage(pages, item, base)
213214
} else if (Array.isArray(item)) {
214215
return Object.assign(resolvePage(pages, item[0], base), {
215216
title: item[1]
216217
})
217218
} else {
218-
if (isNested) {
219+
if (groupDepth > 3) {
219220
console.error(
220-
'[vuepress] Nested sidebar groups are not supported. ' +
221-
'Consider using navbar + categories instead.'
221+
'[vuepress] detected a too deep nested sidebar group.'
222222
)
223223
}
224224
const children = item.children || []
225225
return {
226226
type: 'group',
227+
path: item.path,
227228
title: item.title,
228-
children: children.map(child => resolveItem(child, pages, base, true)),
229+
sidebarDepth: item.sidebarDepth,
230+
children: children.map(child => resolveItem(child, pages, base, groupDepth + 1)),
229231
collapsable: item.collapsable !== false
230232
}
231233
}

‎packages/docs/docs/.vuepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ function getThemeSidebar (groupA, introductionA) {
155155
{
156156
title: groupA,
157157
collapsable: false,
158+
sidebarDepth: 2,
158159
children: [
159160
['', introductionA],
160161
'using-a-theme',

‎packages/docs/docs/theme/default-theme-config.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,10 @@ module.exports = {
180180
themeConfig: {
181181
sidebar: [
182182
{
183-
title: 'Group 1',
184-
collapsable: false,
183+
title: 'Group 1', // required
184+
path: '/foo/' // optional, which should be a absolute path.
185+
collapsable: false, // optional, defaults to true
186+
sidebarDepth: 1, // optional, defaults to 1
185187
children: [
186188
'/'
187189
]
@@ -197,6 +199,12 @@ module.exports = {
197199

198200
Sidebar groups are collapsable by default. You can force a group to be always open with `collapsable: false`.
199201

202+
A sidebar group config also supports [sidebarDepth](#nested-header-links) field to override the default sidebar depth (`1`).
203+
204+
::: tip
205+
   From `1.0.0-alpha-36` on, nested sidebar group <Badge text="beta"/> is also supported, but the nesting depth should be less than 3, otherwise the console will receive a warning.
206+
:::
207+
200208
### Multiple Sidebars
201209

202210
If you wish to display different sidebars for different sections of content, first organize your pages into directories for each desired section:

‎packages/docs/docs/zh/theme/default-theme-config.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,10 @@ module.exports = {
177177
themeConfig: {
178178
sidebar: [
179179
{
180-
title: 'Group 1',
181-
collapsable: false,
180+
title: 'Group 1', // 必要的
181+
path: '/foo/' // 可选的, 应该是一个绝对路径
182+
collapsable: false, // 可选的, 默认值是 true,
183+
sidebarDepth: 1, // 可选的, 默认值是 1
182184
children: [
183185
'/'
184186
]
@@ -194,6 +196,12 @@ module.exports = {
194196

195197
侧边栏的每个子组默认是可折叠的,你可以设置 `collapsable: false` 来让一个组永远都是展开状态。
196198

199+
一个侧边栏的子组配置同时支持 [sidebarDepth](#nested-header-links) 字段用于重写默认显示的侧边栏深度(`1`)。
200+
201+
::: tip
202+
`1.0.0-alpha-36` 开始,嵌套的侧边栏分组 <Badge text="beta"/> 也是支持的,但嵌套深度应小于 3,否则在控制台会收到警告。
203+
:::
204+
197205
### 多个侧边栏
198206

199207
如果你想为不同的页面组来显示不同的侧边栏,首先,将你的页面文件组织成下述的目录结构:

0 commit comments

Comments
 (0)
Please sign in to comment.