Skip to content

Commit 24fea8a

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 1e28e92 commit 24fea8a

File tree

7 files changed

+116
-75
lines changed

7 files changed

+116
-75
lines changed

Diff for: packages/@vuepress/theme-default/components/Page.vue

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

Diff for: packages/@vuepress/theme-default/components/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.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
27+
props: ['items']
7828
}
7929
</script>
8030

Diff for: packages/@vuepress/theme-default/components/SidebarGroup.vue

+67-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="handleClick"
1010
>
1111
<span>{{ item.title }}</span>
1212
<span
@@ -23,21 +23,83 @@
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 bus = new Vue()
3643
3744
export default {
3845
name: 'SidebarGroup',
39-
props: ['item', 'first', 'open', 'collapsable'],
40-
components: { SidebarLink, DropdownTransition }
46+
47+
components: { SidebarLink, DropdownTransition },
48+
49+
props: [
50+
'item',
51+
'first',
52+
'collapsable'
53+
],
54+
55+
data () {
56+
return {
57+
open: this.item.isolated && this.item.initialIsolatedOpen || false
58+
}
59+
},
60+
61+
created () {
62+
this.initBusEvent()
63+
this.refreshOpen()
64+
},
65+
66+
watch: {
67+
'$route' () {
68+
this.refreshOpen()
69+
}
70+
},
71+
72+
methods: {
73+
initBusEvent () {
74+
const onRequestClose = this.onRequestClose.bind(this)
75+
bus.$on('requestClose', onRequestClose)
76+
this.$on('hook:destroyed', () => {
77+
bus.$off('requestClose', onRequestClose)
78+
})
79+
},
80+
refreshOpen () {
81+
const arr = this.item.descendants || []
82+
if (arr.some(c => isActive(this.$route, c.path))) {
83+
this.open = true
84+
}
85+
},
86+
handleClick () {
87+
this.open = !this.open
88+
if (this.open && !this.item.isolated) {
89+
bus.$emit('requestClose', { groupDepth: this.item.groupDepth, target: this })
90+
}
91+
},
92+
onRequestClose ({ groupDepth, target }) {
93+
if (
94+
target.$parent === this.$parent &&
95+
groupDepth === this.item.groupDepth &&
96+
!this.item.isolated &&
97+
target !== this
98+
) {
99+
this.open = false
100+
}
101+
}
102+
}
41103
}
42104
</script>
43105

@@ -48,7 +110,7 @@ export default {
48110
.sidebar-group
49111
padding-left 0.5em
50112
&:not(.collapsable)
51-
.sidebar-heading
113+
> .sidebar-heading
52114
cursor auto
53115
color inherit
54116

Diff for: packages/@vuepress/theme-default/components/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 }
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 (sidebarDepth != null) {
30+
configDepth = sidebarDepth
31+
} else {
32+
configDepth = $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: packages/@vuepress/theme-default/util/index.js

+15-11
Original file line numberDiff line numberDiff line change
@@ -207,26 +207,30 @@ function ensureEndingSlash (path) {
207207
: path + '/'
208208
}
209209

210-
function resolveItem (item, pages, base, isNested) {
210+
function resolveItem (item, pages, base, groupDepth = 1) {
211211
if (typeof item === 'string') {
212212
return resolvePage(pages, item, base)
213213
} else if (Array.isArray(item)) {
214214
return Object.assign(resolvePage(pages, item[0], base), {
215215
title: item[1]
216216
})
217-
} else {
218-
if (isNested) {
219-
console.error(
220-
'[vuepress] Nested sidebar groups are not supported. ' +
221-
'Consider using navbar + categories instead.'
222-
)
223-
}
224-
const children = item.children || []
217+
} else if (Array.isArray(item.children)) {
218+
const descendants = []
219+
const children = item.children.map(child => {
220+
const resolvedItem = resolveItem(child, pages, base, groupDepth + 1)
221+
;[].push.apply(descendants, resolvedItem.descendants || [resolvedItem])
222+
return resolvedItem
223+
})
225224
return {
226225
type: 'group',
227226
title: item.title,
228-
children: children.map(child => resolveItem(child, pages, base, true)),
229-
collapsable: item.collapsable !== false
227+
children,
228+
descendants,
229+
collapsable: item.collapsable !== false,
230+
isolated: item.isolated === true,
231+
initialIsolatedOpen: item.initialIsolatedOpen === true,
232+
sidebarDepth: item.sidebarDepth,
233+
groupDepth
230234
}
231235
}
232236
}

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

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

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

198+
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`.
199+
200+
You can config a group's sidebarDepth with `sidebarDepth: 2`.
201+
202+
You can nest sidebar groups.
203+
198204
### Multiple Sidebars
199205

200206
If you wish to display different sidebars for different sections of content, first organize your pages into directories for each desired section:
@@ -329,7 +335,7 @@ module.exports = {
329335
```
330336

331337
::: warning Note
332-
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.
338+
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.
333339
:::
334340

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

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

+6
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,12 @@ module.exports = {
192192

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

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

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

0 commit comments

Comments
 (0)