Merge pull request #211 from vuepress-reco/feature/add-sub-sidebar

feat(vuepress-theme-reco): add sub sidebar
This commit is contained in:
reco_luan 2020-07-05 21:14:49 +08:00 committed by GitHub
commit 6ad09c222e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 2698 additions and 153 deletions

View File

@ -7,42 +7,38 @@ module.exports = {
['meta', { name: 'viewport', content: 'width=device-width,initial-scale=1,user-scalable=no' }]
],
// theme: 'reco',
locales: {
'/': {
lang: 'ja-JP',
title: "vuepress-theme-reco",
description: 'A simple and beautiful vuepress blog theme.',
},
},
theme: require.resolve('../../packages/vuepress-theme-reco'),
themeConfig: {
locales: {
'/': {
recoLocales: {
pagation: {
prev: '上壹頁',
next: '下壹頁',
go: '前往',
jump: '跳轉至'
}
}
}
},
nav: [
{ text: 'Home', link: '/', icon: 'reco-home' },
{ text: 'TimeLine', link: '/timeline/', icon: 'reco-date' },
{ text: 'Contact',
icon: 'reco-message',
items: [
{ text: 'NPM', link: 'https://www.npmjs.com/~reco_luan', icon: 'reco-npm' },
{ text: 'GitHub', link: 'https://github.com/recoluan', icon: 'reco-github' },
{ text: '简书', link: 'https://www.jianshu.com/u/cd674a19515e', icon: 'reco-jianshu' },
{ text: 'CSDN', link: 'https://blog.csdn.net/recoluan', icon: 'reco-csdn' },
{ text: '博客圆', link: 'https://www.cnblogs.com/luanhewei/', icon: 'reco-bokeyuan' },
{ text: 'WeChat', link: 'https://mp.weixin.qq.com/s/mXFqeUTegdvPliXknAAG_A', icon: 'reco-wechat' },
]
}
{ text: 'sidebar', link: '/views/sidebar/' },
{ text: 'sidebar', link: '/views/sidebargroup/' }
],
sidebar: {
'/views/sidebar/': [
'',
'bar1',
'bar2'
],
'/views/sidebargroup/': [
{
title: '基础',
collapsable: true,
children: [
'',
'bar1'
]
},
{
title: '进阶',
collapsable: true,
children: [
'bar2'
]
},
]
},
type: 'blog',
// 博客设置
blogConfig: {
@ -55,14 +51,14 @@ module.exports = {
text: 'Tag' // 默认 “标签”
}
},
type: 'blog',
logo: '/head.png',
authorAvatar: '/head.png',
// 搜索设置
search: true,
searchMaxSuggestions: 10,
// 自动形成侧边导航
sidebar: 'auto',
// sidebar: 'auto',
sidebarDepth: 4,
// 最后更新时间
lastUpdated: 'Last Updated',
// 作者

View File

@ -1,2 +1,2 @@
// $accentColor = #424242
// $textColor = #232321
// $textColor = #232321

View File

@ -1,10 +1,69 @@
---
title: second page in category1
title: sidebar test
date: 2019-09-21
sidebarDepth: 5
tags:
- tag2
categories:
- category1
---
second page in category1
## 二级标题1
### 三级标题1-1
#### 四级标题1-1-1
#### 四级标题1-1-2
#### 四级标题1-1-3
### 三体标题1-2
#### 四级标题1-2-1
#### 四级标题1-2-2
#### 四级标题1-2-3
### 三体标题1-3
#### 四级标题1-3-1
#### 四级标题1-3-2
#### 四级标题1-3-3
## 二级标题2
### 三级标题2-1
#### 四级标题2-1-1
#### 四级标题2-1-2
#### 四级标题2-1-3
### 三体标题2-2
#### 四级标题2-2-1
#### 四级标题2-2-2
#### 四级标题2-2-3
### 三体标题2-3
#### 四级标题2-3-1
#### 四级标题2-3-2
#### 四级标题2-3-3
## 二级标题3
### 三级标题3-1
#### 四级标题3-1-1
#### 四级标题3-1-2
#### 四级标题3-1-3
### 三体标题3-2
#### 四级标题3-2-1
#### 四级标题3-2-2
#### 四级标题3-2-3
### 三体标题3-3
#### 四级标题3-3-1
#### 四级标题3-3-2
#### 四级标题3-3-3

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
---
title: 介绍
---
介绍

View File

@ -0,0 +1,5 @@
---
title: bar1
---
bar1

View File

@ -0,0 +1,65 @@
---
title: bar2
---
bar2
## 二级标题1
### 三级标题1-1
#### 四级标题1-1-1
#### 四级标题1-1-2
#### 四级标题1-1-3
### 三体标题1-2
#### 四级标题1-2-1
#### 四级标题1-2-2
#### 四级标题1-2-3
### 三体标题1-3
#### 四级标题1-3-1
#### 四级标题1-3-2
#### 四级标题1-3-3
## 二级标题2
### 三级标题2-1
#### 四级标题2-1-1
#### 四级标题2-1-2
#### 四级标题2-1-3
### 三体标题2-2
#### 四级标题2-2-1
#### 四级标题2-2-2
#### 四级标题2-2-3
### 三体标题2-3
#### 四级标题2-3-1
#### 四级标题2-3-2
#### 四级标题2-3-3
## 二级标题3
### 三级标题3-1
#### 四级标题3-1-1
#### 四级标题3-1-2
#### 四级标题3-1-3
### 三体标题3-2
#### 四级标题3-2-1
#### 四级标题3-2-2
#### 四级标题3-2-3
### 三体标题3-3
#### 四级标题3-3-1
#### 四级标题3-3-2
#### 四级标题3-3-3

View File

@ -0,0 +1,5 @@
---
title: 介绍
---
介绍

View File

@ -0,0 +1,5 @@
---
title: bar1
---
bar1

View File

@ -0,0 +1,65 @@
---
title: bar2
---
bar2
## 二级标题1
### 三级标题1-1
#### 四级标题1-1-1
#### 四级标题1-1-2
#### 四级标题1-1-3
### 三体标题1-2
#### 四级标题1-2-1
#### 四级标题1-2-2
#### 四级标题1-2-3
### 三体标题1-3
#### 四级标题1-3-1
#### 四级标题1-3-2
#### 四级标题1-3-3
## 二级标题2
### 三级标题2-1
#### 四级标题2-1-1
#### 四级标题2-1-2
#### 四级标题2-1-3
### 三体标题2-2
#### 四级标题2-2-1
#### 四级标题2-2-2
#### 四级标题2-2-3
### 三体标题2-3
#### 四级标题2-3-1
#### 四级标题2-3-2
#### 四级标题2-3-3
## 二级标题3
### 三级标题3-1
#### 四级标题3-1-1
#### 四级标题3-1-2
#### 四级标题3-1-3
### 三体标题3-2
#### 四级标题3-2-1
#### 四级标题3-2-2
#### 四级标题3-2-3
### 三体标题3-3
#### 四级标题3-3-1
#### 四级标题3-3-2
#### 四级标题3-3-3

View File

@ -23,9 +23,9 @@
<Sidebar
:items="sidebarItems"
@toggle-sidebar="toggleSidebar">
<slot
name="sidebar-top"
slot="top"/>
<template slot="top">
<PersonalInfo />
</template>
<slot
name="sidebar-bottom"
slot="bottom"/>
@ -53,9 +53,9 @@
<Sidebar
:items="sidebarItems"
@toggle-sidebar="toggleSidebar">
<slot
name="sidebar-top"
slot="top"/>
<template slot="top">
<PersonalInfo />
</template>
<slot
name="sidebar-bottom"
slot="bottom"/>
@ -74,7 +74,7 @@
<script>
import Navbar from '@theme/components/Navbar'
import Sidebar from '@theme/components/Sidebar'
import { resolveSidebarItems } from '@theme/helpers/utils'
import PersonalInfo from '@theme/components/PersonalInfo'
import Password from '@theme/components/Password'
import { setTimeout } from 'timers'
import moduleTransitonMixin from '@theme/mixins/moduleTransiton'
@ -82,12 +82,16 @@ import moduleTransitonMixin from '@theme/mixins/moduleTransiton'
export default {
mixins: [moduleTransitonMixin],
components: { Sidebar, Navbar, Password },
components: { Sidebar, Navbar, Password, PersonalInfo },
props: {
sidebar: {
type: Boolean,
default: true
},
sidebarItems: {
type: Array,
default: () => []
}
},
@ -124,22 +128,14 @@ export default {
},
shouldShowSidebar () {
const { frontmatter } = this.$page
return (
this.sidebar !== false &&
!frontmatter.home &&
frontmatter.sidebar !== false &&
this.sidebarItems.length
)
},
sidebarItems () {
return resolveSidebarItems(
this.$page,
this.$page.regularPath,
this.$site,
this.$localePath
)
// const { frontmatter } = this.$page
// return (
// this.sidebar !== false &&
// !frontmatter.home &&
// frontmatter.sidebar !== false &&
// this.sidebarItems.length
// )
return this.sidebarItems.length > 0
},
pageClasses () {

View File

@ -208,7 +208,7 @@ export default {
display flex
align-items: flex-start;
margin 20px auto 0
max-width 1126px
max-width $homePageWidth
.blog-list {
flex auto
width 0

View File

@ -11,7 +11,7 @@ const modeOptions = {
'--default-color-2': 'rgba(255, 255, 255, .2)',
'--default-color-1': 'rgba(255, 255, 255, .1)',
'--background-color': '#fff',
'--box-shadow': '0 1px 6px 0 rgba(0, 0, 0, 0.2)',
'--box-shadow': '0 1px 8px 0 rgba(0, 0, 0, 0.1)',
'--box-shadow-hover': '0 2px 16px 0 rgba(0, 0, 0, 0.2)',
'--text-color': '#242424',
'--text-color-sub': '#7F7F7F',

View File

@ -1,5 +1,5 @@
<template>
<main class="page">
<main class="page" :style="{ paddingRight: (this.$page.headers || []).length > 0 ? '14rem' : '0' }">
<ModuleTransition>
<div v-show="recoShowModule && $page.title" class="page-title">
<h1>{{$page.title}}</h1>
@ -72,6 +72,10 @@
<ModuleTransition delay="0.32">
<Comments v-if="recoShowModule" :isShowComments="shouldShowComments"/>
</ModuleTransition>
<ModuleTransition delay="0.4">
<SubSidebar class="side-bar" />
</ModuleTransition>
</main>
</template>
@ -80,10 +84,11 @@ import PageInfo from '@theme/components/PageInfo'
import { resolvePage, outboundRE, endingSlashRE } from '@theme/helpers/utils'
import ModuleTransition from '@theme/components/ModuleTransition'
import moduleTransitonMixin from '@theme/mixins/moduleTransiton'
import SubSidebar from '@theme/components/SubSidebar'
export default {
mixins: [moduleTransitonMixin],
components: { PageInfo, ModuleTransition },
components: { PageInfo, ModuleTransition, SubSidebar },
props: ['sidebarItems'],
@ -234,9 +239,19 @@ function flatten (items, res) {
@require '../styles/wrapper.styl'
.page
position relative
padding-top 5rem
padding-bottom 2rem
display block
.side-bar
position fixed
top 10rem
bottom 10rem
right 2rem
overflow-y scroll
&::-webkit-scrollbar
width: 0
height: 0
.page-title
max-width: $contentWidth;
margin: 0 auto;
@ -278,14 +293,18 @@ function flatten (items, res) {
float right
@media (max-width: $MQMobile)
.page-title
padding: 0 1rem;
.page-edit
.edit-link
margin-bottom .5rem
.last-updated
font-size .8em
float none
text-align left
.page
padding-right 0
.side-bar
display none
.page-title
padding: 0 1rem;
.page-edit
.edit-link
margin-bottom .5rem
.last-updated
font-size .8em
float none
text-align left
</style>

View File

@ -1,8 +1,7 @@
<template>
<aside class="sidebar">
<PersonalInfo/>
<NavLinks/>
<slot name="top"/>
<NavLinks/>
<SidebarLinks :depth="0" :items="items"/>
<slot name="bottom"/>
</aside>
@ -10,13 +9,12 @@
<script>
import SidebarLinks from '@theme/components/SidebarLinks'
import PersonalInfo from '@theme/components/PersonalInfo'
import NavLinks from '@theme/components/NavLinks'
export default {
name: 'Sidebar',
components: { SidebarLinks, NavLinks, PersonalInfo },
components: { SidebarLinks, NavLinks },
props: ['items']
}
@ -24,6 +22,9 @@ export default {
<style lang="stylus">
.sidebar
&&::-webkit-scrollbar
width: 0
height: 0
.personal-info-wrapper
display none
ul
@ -46,9 +47,9 @@ export default {
& > .sidebar-links
padding 1.5rem 0
& > li > a.sidebar-link
font-size 1.1em
font-size 1em
line-height 1.7
font-weight bold
font-weight 500
& > li:not(:first-child)
margin-top .75rem

View File

@ -97,23 +97,24 @@ export default {
border-left none
.sidebar-heading
position relative
color var(--text-color)
transition color .15s ease
cursor pointer
font-size 1.1em
font-weight bold
// text-transform uppercase
font-size 1em
font-weight 500
padding 0.35rem 1.5rem 0.35rem 1.25rem
width 100%
box-sizing border-box
margin 0
border-left 0.25rem solid transparent
&.open, &:hover
color $accentColor
.arrow
position relative
top -0.12em
left 0.5em
position absolute
top 0
bottom 0
right 0.5em
margin auto
&.clickable
&.active
font-weight 600

View File

@ -1,5 +1,5 @@
<script>
import { isActive, hashRE, groupHeaders } from '@theme/helpers/utils'
import { isActive } from '@theme/helpers/utils'
export default {
functional: true,
@ -29,25 +29,26 @@ export default {
? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug))
: selfActive
const link = renderLink(h, item.path, item.title || item.path, active)
return link
const configDepth = $page.frontmatter.sidebarDepth ||
sidebarDepth ||
$themeLocaleConfig.sidebarDepth ||
$themeConfig.sidebarDepth
// const configDepth = $page.frontmatter.sidebarDepth ||
// sidebarDepth ||
// $themeLocaleConfig.sidebarDepth ||
// $themeConfig.sidebarDepth
const maxDepth = configDepth == null ? 1 : configDepth
// const maxDepth = configDepth == null ? 1 : configDepth
const displayAllHeaders = $themeLocaleConfig.displayAllHeaders ||
$themeConfig.displayAllHeaders
// const displayAllHeaders = $themeLocaleConfig.displayAllHeaders ||
// $themeConfig.displayAllHeaders
if (item.type === 'auto') {
return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)]
} else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) {
const children = groupHeaders(item.headers)
return [link, renderChildren(h, children, item.path, $route, maxDepth)]
} else {
return link
}
// if (item.type === 'auto') {
// return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)]
// } else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) {
// const children = groupHeaders(item.headers)
// return [link, renderChildren(h, children, item.path, $route, maxDepth)]
// } else {
// return link
// }
}
}
@ -65,16 +66,16 @@ function renderLink (h, to, text, active) {
}, text)
}
function renderChildren (h, children, path, route, maxDepth, depth = 1) {
if (!children || depth > maxDepth) return null
return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => {
const active = isActive(route, path + '#' + c.slug)
return h('li', { class: 'sidebar-sub-header' }, [
renderLink(h, path + '#' + c.slug, c.title, active),
renderChildren(h, c.children, path, route, maxDepth, depth + 1)
])
}))
}
// function renderChildren (h, children, path, route, maxDepth, depth = 1) {
// if (!children || depth > maxDepth) return null
// return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => {
// const active = isActive(route, path + '#' + c.slug)
// return h('li', { class: 'sidebar-sub-header' }, [
// renderLink(h, path + '#' + c.slug, c.title, active),
// renderChildren(h, c.children, path, route, maxDepth, depth + 1)
// ])
// }))
// }
</script>
<style lang="stylus">
@ -90,18 +91,17 @@ a.sidebar-link
font-weight 400
display block!important
color var(--text-color)
padding 0.35rem 1rem 0.35rem .75rem
padding 0.35rem 1rem 0.35rem 2.25rem
line-height 1.4
margin 0 1rem 0 1.5rem
// margin 0 0 0 1.5rem
box-sizing: border-box
border-radius .25rem
&:hover
color $accentColor
&.active
font-weight 600
color #fff
background $accentColor
// border-left-color $accentColor
color $accentColor
background lighten($accentColor, 92%)
border-right 3px solid $accentColor
.sidebar-group &
// padding-left 2rem
.sidebar-sub-headers &

View File

@ -88,24 +88,24 @@ export default {
},
isInViewPortOfOne () {
const siderbarScroll = document.getElementsByClassName('sidebar')[0]
const sidebarScroll = document.getElementsByClassName('sidebar')[0]
let el = document.getElementsByClassName('active sidebar-link')[1]
if (el == null || el == undefined || el.offsetTop == undefined) {
el = document.getElementsByClassName('active sidebar-link')[0]
}
if (el == null || el == undefined || el.offsetTop == undefined) return
const viewPortHeight = siderbarScroll.clientHeight || window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const viewPortHeight = sidebarScroll.clientHeight || window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const offsetTop = el.offsetTop
const offsetBottom = el.offsetTop + el.offsetHeight
const scrollTop = siderbarScroll.scrollTop
const scrollTop = sidebarScroll.scrollTop
const bottomVisible = (offsetBottom <= viewPortHeight + scrollTop)
if (!bottomVisible) {
siderbarScroll.scrollTop = (offsetBottom + 5 - viewPortHeight)
sidebarScroll.scrollTop = (offsetBottom + 5 - viewPortHeight)
}
const topVisible = (offsetTop >= scrollTop)
if (!topVisible) {
siderbarScroll.scrollTop = (offsetTop - 5)
sidebarScroll.scrollTop = (offsetTop - 5)
}
},

View File

@ -0,0 +1,68 @@
<script>
import { isActive } from '@theme/helpers/utils'
export default {
computed: {
headers () {
const headers = (this.$page.headers || []).filter(header => header.level === 2)
return headers
}
},
methods: {
isLinkActive (header) {
return isActive(this.$route, this.$page.path + '#' + header.slug)
}
},
render (h) {
return h('ul', {
class: { 'sub-sidebar-wrapper': true },
style: { width: (this.$page.headers || []).length > 0 ? '12rem' : '0' }
}, [
...(this.$page.headers || []).map(header => {
return h('li', {
class: {
active: this.isLinkActive(header),
[`level-${header.level}`]: true
},
attr: { key: header.title }
}, [
h('router-link', {
class: { 'sidebar-link': true },
props: { to: `${this.$page.path}#${header.slug}` }
}, header.title)
])
})
])
}
}
</script>
<style lang="stylus" scoped>
.sub-sidebar-wrapper
width 12rem
padding-left 0
list-style none
font-size 12px
li
padding .2rem 0
cursor pointer
border-left 1px solid var(--border-color)
a
padding 0.35rem 1rem 0.35rem 0rem
color var(--text-color)
&:hover
a
color $accentColor
&.active
border-left 1px solid $accentColor
a
color $accentColor
&.level-1
padding-left .4rem
&.level-2
padding-left .9rem
&.level-3
padding-left 1.5rem
</style>

View File

@ -122,42 +122,49 @@ export function resolveSidebarItems (page, regularPath, site, localePath) {
? themeConfig.locales[localePath] || themeConfig
: themeConfig
const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar
if (pageSidebarConfig === 'auto') {
return resolveHeaders(page)
}
// 计算页面的菜单层级
// const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar
// if (pageSidebarConfig === 'auto') {
// return resolveHeaders(page)
// }
// const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar
// if (!sidebarConfig) {
// return []
// } else {
// const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig)
// return config
// ? config.map(item => resolveItem(item, pages, base))
// : []
// }
const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar
if (!sidebarConfig) {
return []
} else {
const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig)
return config
? config.map(item => resolveItem(item, pages, base))
: []
}
const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig)
return config
? config.map(item => resolveItem(item, pages, base))
: []
}
/**
* @param { Page } page
* @returns { SidebarGroup }
*/
function resolveHeaders (page) {
const headers = groupHeaders(page.headers || [])
return [{
type: 'group',
collapsable: false,
title: page.title,
path: null,
children: headers.map(h => ({
type: 'auto',
title: h.title,
basePath: page.path,
path: page.path + '#' + h.slug,
children: h.children || []
}))
}]
}
// function resolveHeaders (page) {
// const headers = groupHeaders(page.headers || [])
// return [{
// type: 'group',
// collapsable: false,
// title: page.title,
// path: null,
// children: headers.map(h => ({
// type: 'auto',
// title: h.title,
// basePath: page.path,
// path: page.path + '#' + h.slug,
// children: h.children || []
// }))
// }]
// }
export function groupHeaders (headers) {
// group h3s under h2

View File

@ -1,5 +1,5 @@
<template>
<Common>
<Common :sidebarItems="sidebarItems">
<component v-if="$frontmatter.home" :is="homeCom"/>
<Page v-else :sidebar-items="sidebarItems"/>
<Footer v-if="$frontmatter.home" class="footer" />

View File

@ -27,8 +27,8 @@ $lineNumbersWrapperWidth = 2.5rem
$backgroundColor ?= #fff
$backgroundColorDark ?= #181818
$boxShadow = 0 1px 6px 0 $darkColor2
$boxShadowHover = 0 2px 16px 0 $darkColor2
$boxShadow = 0 1px 8px 0 $darkColor1
$boxShadowHover = 0 2px 16px 0 $darkColor1
$boxShadowDark = 0 1px 6px 0 $darkColor6
$boxShadowHoverDark = 0 2px 16px 0 $darkColor6
@ -45,3 +45,7 @@ $codeColorDark ?= $darkColor3
$maskColor ?= #888
$maskColorDark ?= #000
$homePageWidth = 1126px
$contentWidth = 860px
$sidebarWidth = 18rem