Skip to content

Commit f1e0ec5

Browse files
committed
feat: refine group
- isolate a group's open status with `isolated: true` - config a isolated group's initial open status with `initialIsolatedOpen: true` - config a group's sidebarDepth with `sidebarDepth: 2` - nested sidebar groups
1 parent f4f9e6b commit f1e0ec5

File tree

7 files changed

+111
-75
lines changed

7 files changed

+111
-75
lines changed

Diff for: docs/default-theme-config/README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ module.exports = {
199199

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

202+
When a group is open, other groups at the same level will be automatically closed. You can isolate a group's open status with `isolated: true`. And then you can config its initial open status with `initialIsolatedOpen: true`.
203+
204+
You can config a group's sidebarDepth with `sidebarDepth: 2`.
205+
206+
You can nest sidebar groups.
207+
202208
### Multiple Sidebars
203209

204210
If you wish to display different sidebars for different sections of content, first organize your pages into directories for each desired section:
@@ -333,7 +339,7 @@ module.exports = {
333339
```
334340

335341
::: warning Note
336-
Unlike the [built-in search](#built-in-search) engine which works out of the box, [Algolia DocSearch](https://community.algolia.com/docsearch/) requires you to submit your site to them for indexing before it starts working.
342+
Unlike the [built-in search](#built-in-search) engine which works out of the box, [Algolia DocSearch](https://community.algolia.com/docsearch/) requires you to submit your site to them for indexing before it starts working.
337343
:::
338344

339345
For more options, refer to [Algolia DocSearch's documentation](https://github.com/algolia/docsearch#docsearch-options).

Diff for: docs/zh/default-theme-config/README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ module.exports = {
164164
}
165165
```
166166

167-
::: tip
167+
::: tip
168168
值得一提的是,当你禁用此选项时,此功能的相应脚本将不会被加载,这是我们性能优化的一个小点。
169169
:::
170170

@@ -196,6 +196,12 @@ module.exports = {
196196

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

199+
当一个子组被展开时,跟它同级的其他子组会被折叠。你可以设置 `isolated: true` 来隔离一个组的展开状态。然后你可以设置 `initialIsolatedOpen: true` 来让一个被隔离的组在一开始就处于展开状态。
200+
201+
你可以通过 `sidebarDepth: 2` 来设置一个组的 sidebarDepth。
202+
203+
你可以嵌套组。
204+
199205
### 多个侧边栏
200206

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

Diff for: lib/default-theme/Page.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ function find (page, items, offset) {
180180
const res = []
181181
items.forEach(item => {
182182
if (item.type === 'group') {
183-
res.push(...item.children || [])
183+
res.push(...(item.descendants || item.children))
184184
} else {
185185
res.push(item)
186186
}

Diff for: lib/default-theme/Sidebar.vue

+2-52
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88
v-if="item.type === 'group'"
99
:item="item"
1010
:first="i === 0"
11-
:open="i === openGroupIndex"
12-
:collapsable="item.collapsable || item.collapsible"
13-
@toggle="toggleGroup(i)"
14-
/>
11+
:collapsable="item.collapsable || item.collapsible"/>
1512
<SidebarLink v-else :item="item"/>
1613
</li>
1714
</ul>
@@ -23,58 +20,11 @@
2320
import SidebarGroup from './SidebarGroup.vue'
2421
import SidebarLink from './SidebarLink.vue'
2522
import NavLinks from './NavLinks.vue'
26-
import { isActive } from './util'
2723
2824
export default {
2925
components: { SidebarGroup, SidebarLink, NavLinks },
3026
31-
props: ['items'],
32-
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.path)
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
27+
props: ['items']
7828
}
7929
</script>
8030

Diff for: lib/default-theme/SidebarGroup.vue

+61-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<p
77
class="sidebar-heading"
88
:class="{ open }"
9-
@click="$emit('toggle')"
9+
@click="handleClickToggle"
1010
>
1111
<span>{{ item.title }}</span>
1212
<span
@@ -23,21 +23,77 @@
2323
v-if="open || !collapsable"
2424
>
2525
<li v-for="child in item.children">
26-
<SidebarLink :item="child"/>
26+
<SidebarGroup v-if="child.type === 'group'"
27+
:item="child"
28+
:collapsable="child.collapsable"/>
29+
<SidebarLink v-else :item="child" :sidebarDepth="item.sidebarDepth"/>
2730
</li>
2831
</ul>
2932
</DropdownTransition>
3033
</div>
3134
</template>
3235

3336
<script>
37+
import Vue from 'vue'
3438
import SidebarLink from './SidebarLink.vue'
3539
import DropdownTransition from './DropdownTransition.vue'
40+
import { isActive } from './util'
41+
42+
const hub = new Vue()
3643
3744
export default {
3845
name: 'SidebarGroup',
39-
props: ['item', 'first', 'open', 'collapsable'],
40-
components: { SidebarLink, DropdownTransition }
46+
components: { SidebarLink, DropdownTransition },
47+
props: [
48+
'item',
49+
'first',
50+
'collapsable'
51+
],
52+
data () {
53+
return {
54+
open: this.item.isolated && this.item.initialIsolatedOpen || false
55+
}
56+
},
57+
created () {
58+
this.initToggleEvent()
59+
this.refreshOpen()
60+
},
61+
watch: {
62+
'$route' () {
63+
this.refreshOpen()
64+
}
65+
},
66+
methods: {
67+
initToggleEvent () {
68+
const onToggle = this.onToggle.bind(this)
69+
hub.$on('toggle', onToggle)
70+
this.$on('hook:destroyed', () => {
71+
hub.$off('toggle', onToggle)
72+
})
73+
},
74+
refreshOpen () {
75+
const arr = this.item.descendants || this.item.children
76+
if (arr.some(c => isActive(this.$route, c.path))) {
77+
this.open = true
78+
}
79+
},
80+
handleClickToggle () {
81+
this.open = !this.open
82+
if (this.open && !this.item.isolated) {
83+
hub.$emit('toggle', { depth: this.item.depth, target: this })
84+
}
85+
},
86+
onToggle ({ depth, target }) {
87+
if (
88+
target.$parent === this.$parent &&
89+
depth === this.item.depth &&
90+
!this.item.isolated &&
91+
target !== this
92+
) {
93+
this.open = false
94+
}
95+
}
96+
}
4197
}
4298
</script>
4399

@@ -48,7 +104,7 @@ export default {
48104
.sidebar-group
49105
padding-left 0.5em
50106
&:not(.collapsable)
51-
.sidebar-heading
107+
> .sidebar-heading
52108
cursor auto
53109
color inherit
54110

Diff for: lib/default-theme/SidebarLink.vue

+18-5
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ 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 (
10+
h,
11+
{
12+
parent: { $page, $site, $route },
13+
props: { item, sidebarDepth: groupSidebarDepth }
14+
}
15+
) {
1016
// use custom active class matching logic
1117
// due to edge case of paths ending with / + hash
1218
const selfActive = isActive($route, item.path)
@@ -16,10 +22,17 @@ export default {
1622
? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug))
1723
: selfActive
1824
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
25+
26+
let configDepth
27+
if ($page.frontmatter.sidebarDepth != null) {
28+
configDepth = $page.frontmatter.sidebarDepth
29+
} else if (groupSidebarDepth != null) {
30+
configDepth = groupSidebarDepth
31+
} else {
32+
$site.themeConfig.sidebarDepth
33+
}
2234
const maxDepth = configDepth == null ? 1 : configDepth
35+
2336
const displayAllHeaders = !!$site.themeConfig.displayAllHeaders
2437
if (item.type === 'auto') {
2538
return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)]

Diff for: lib/default-theme/util.js

+15-10
Original file line numberDiff line numberDiff line change
@@ -191,26 +191,31 @@ function ensureEndingSlash (path) {
191191
: path + '/'
192192
}
193193

194-
function resolveItem (item, pages, base, isNested) {
194+
function resolveItem (item, pages, base, depth = 0) {
195195
if (typeof item === 'string') {
196196
return resolvePage(pages, item, base)
197197
} else if (Array.isArray(item)) {
198198
return Object.assign(resolvePage(pages, item[0], base), {
199199
title: item[1]
200200
})
201201
} else {
202-
if (isNested) {
203-
console.error(
204-
'[vuepress] Nested sidebar groups are not supported. ' +
205-
'Consider using navbar + categories instead.'
206-
)
207-
}
208-
const children = item.children || []
202+
depth++
203+
const descendants = item.descendants || []
204+
const children = (item.children || []).map(child => {
205+
const resolvedItem = resolveItem(child, pages, base, depth)
206+
;[].push.apply(descendants, resolvedItem.descendants || [resolvedItem])
207+
return resolvedItem
208+
})
209209
return {
210210
type: 'group',
211211
title: item.title,
212-
children: children.map(child => resolveItem(child, pages, base, true)),
213-
collapsable: item.collapsable !== false
212+
children,
213+
descendants,
214+
collapsable: item.collapsable !== false,
215+
isolated: item.isolated === true,
216+
initialIsolatedOpen: item.initialIsolatedOpen === true,
217+
sidebarDepth: item.sidebarDepth,
218+
depth
214219
}
215220
}
216221
}

0 commit comments

Comments
 (0)