aboutsummaryrefslogtreecommitdiff
path: root/doc/docs/.vuepress/theme/components
diff options
context:
space:
mode:
Diffstat (limited to 'doc/docs/.vuepress/theme/components')
-rw-r--r--doc/docs/.vuepress/theme/components/AlgoliaSearchBox.vue172
-rw-r--r--doc/docs/.vuepress/theme/components/DropdownLink.vue252
-rw-r--r--doc/docs/.vuepress/theme/components/DropdownTransition.vue33
-rw-r--r--doc/docs/.vuepress/theme/components/Home.vue175
-rw-r--r--doc/docs/.vuepress/theme/components/NavLink.vue90
-rw-r--r--doc/docs/.vuepress/theme/components/NavLinks.vue156
-rw-r--r--doc/docs/.vuepress/theme/components/Navbar.vue140
-rw-r--r--doc/docs/.vuepress/theme/components/Page.vue31
-rw-r--r--doc/docs/.vuepress/theme/components/PageEdit.vue155
-rw-r--r--doc/docs/.vuepress/theme/components/PageNav.vue163
-rw-r--r--doc/docs/.vuepress/theme/components/Sidebar.vue64
-rw-r--r--doc/docs/.vuepress/theme/components/SidebarButton.vue40
-rw-r--r--doc/docs/.vuepress/theme/components/SidebarGroup.vue141
-rw-r--r--doc/docs/.vuepress/theme/components/SidebarLink.vue133
-rw-r--r--doc/docs/.vuepress/theme/components/SidebarLinks.vue106
15 files changed, 1851 insertions, 0 deletions
diff --git a/doc/docs/.vuepress/theme/components/AlgoliaSearchBox.vue b/doc/docs/.vuepress/theme/components/AlgoliaSearchBox.vue
new file mode 100644
index 0000000..7071fb8
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/AlgoliaSearchBox.vue
@@ -0,0 +1,172 @@
1<template>
2 <form
3 id="search-form"
4 class="algolia-search-wrapper search-box"
5 role="search"
6 >
7 <input
8 id="algolia-search-input"
9 class="search-query"
10 :placeholder="placeholder"
11 >
12 </form>
13</template>
14
15<script>
16export default {
17 name: 'AlgoliaSearchBox',
18
19 props: ['options'],
20
21 data () {
22 return {
23 placeholder: undefined
24 }
25 },
26
27 watch: {
28 $lang (newValue) {
29 this.update(this.options, newValue)
30 },
31
32 options (newValue) {
33 this.update(newValue, this.$lang)
34 }
35 },
36
37 mounted () {
38 this.initialize(this.options, this.$lang)
39 this.placeholder = this.$site.themeConfig.searchPlaceholder || ''
40 },
41
42 methods: {
43 initialize (userOptions, lang) {
44 Promise.all([
45 import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.js'),
46 import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.css')
47 ]).then(([docsearch]) => {
48 docsearch = docsearch.default
49 const { algoliaOptions = {}} = userOptions
50 docsearch(Object.assign(
51 {},
52 userOptions,
53 {
54 inputSelector: '#algolia-search-input',
55 // #697 Make docsearch work well at i18n mode.
56 algoliaOptions: {
57 ...algoliaOptions,
58 facetFilters: [`lang:${lang}`].concat(algoliaOptions.facetFilters || [])
59 },
60 handleSelected: (input, event, suggestion) => {
61 const { pathname, hash } = new URL(suggestion.url)
62 const routepath = pathname.replace(this.$site.base, '/')
63 const _hash = decodeURIComponent(hash)
64 this.$router.push(`${routepath}${_hash}`)
65 }
66 }
67 ))
68 })
69 },
70
71 update (options, lang) {
72 this.$el.innerHTML = '<input id="algolia-search-input" class="search-query">'
73 this.initialize(options, lang)
74 }
75 }
76}
77</script>
78
79<style lang="stylus">
80.algolia-search-wrapper
81 & > span
82 vertical-align middle
83 .algolia-autocomplete
84 line-height normal
85 .ds-dropdown-menu
86 background-color #fff
87 border 1px solid #999
88 border-radius 4px
89 font-size 16px
90 margin 6px 0 0
91 padding 4px
92 text-align left
93 &:before
94 border-color #999
95 [class*=ds-dataset-]
96 border none
97 padding 0
98 .ds-suggestions
99 margin-top 0
100 .ds-suggestion
101 border-bottom 1px solid $borderColor
102 .algolia-docsearch-suggestion--highlight
103 color #2c815b
104 .algolia-docsearch-suggestion
105 border-color $borderColor
106 padding 0
107 .algolia-docsearch-suggestion--category-header
108 padding 5px 10px
109 margin-top 0
110 background $accentColor
111 color #fff
112 font-weight 600
113 .algolia-docsearch-suggestion--highlight
114 background rgba(255, 255, 255, 0.6)
115 .algolia-docsearch-suggestion--wrapper
116 padding 0
117 .algolia-docsearch-suggestion--title
118 font-weight 600
119 margin-bottom 0
120 color $textColor
121 .algolia-docsearch-suggestion--subcategory-column
122 vertical-align top
123 padding 5px 7px 5px 5px
124 border-color $borderColor
125 background #f1f3f5
126 &:after
127 display none
128 .algolia-docsearch-suggestion--subcategory-column-text
129 color #555
130 .algolia-docsearch-footer
131 border-color $borderColor
132 .ds-cursor .algolia-docsearch-suggestion--content
133 background-color #e7edf3 !important
134 color $textColor
135
136@media (min-width: $MQMobile)
137 .algolia-search-wrapper
138 .algolia-autocomplete
139 .algolia-docsearch-suggestion
140 .algolia-docsearch-suggestion--subcategory-column
141 float none
142 width 150px
143 min-width 150px
144 display table-cell
145 .algolia-docsearch-suggestion--content
146 float none
147 display table-cell
148 width 100%
149 vertical-align top
150 .ds-dropdown-menu
151 min-width 515px !important
152
153@media (max-width: $MQMobile)
154 .algolia-search-wrapper
155 .ds-dropdown-menu
156 min-width calc(100vw - 4rem) !important
157 max-width calc(100vw - 4rem) !important
158 .algolia-docsearch-suggestion--wrapper
159 padding 5px 7px 5px 5px !important
160 .algolia-docsearch-suggestion--subcategory-column
161 padding 0 !important
162 background white !important
163 .algolia-docsearch-suggestion--subcategory-column-text:after
164 content " > "
165 font-size 10px
166 line-height 14.4px
167 display inline-block
168 width 5px
169 margin -3px 3px 0
170 vertical-align middle
171
172</style>
diff --git a/doc/docs/.vuepress/theme/components/DropdownLink.vue b/doc/docs/.vuepress/theme/components/DropdownLink.vue
new file mode 100644
index 0000000..be6563f
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/DropdownLink.vue
@@ -0,0 +1,252 @@
1<template>
2 <div
3 class="dropdown-wrapper"
4 :class="{ open }"
5 >
6 <button
7 class="dropdown-title"
8 type="button"
9 :aria-label="dropdownAriaLabel"
10 @click="handleDropdown"
11 >
12 <span class="title">{{ item.text }}</span>
13 <span
14 class="arrow down"
15 />
16 </button>
17 <button
18 class="mobile-dropdown-title"
19 type="button"
20 :aria-label="dropdownAriaLabel"
21 @click="setOpen(!open)"
22 >
23 <span class="title">{{ item.text }}</span>
24 <span
25 class="arrow"
26 :class="open ? 'down' : 'right'"
27 />
28 </button>
29
30 <DropdownTransition>
31 <ul
32 v-show="open"
33 class="nav-dropdown"
34 >
35 <li
36 v-for="(subItem, index) in item.items"
37 :key="subItem.link || index"
38 class="dropdown-item"
39 >
40 <h4 v-if="subItem.type === 'links'">
41 {{ subItem.text }}
42 </h4>
43
44 <ul
45 v-if="subItem.type === 'links'"
46 class="dropdown-subitem-wrapper"
47 >
48 <li
49 v-for="childSubItem in subItem.items"
50 :key="childSubItem.link"
51 class="dropdown-subitem"
52 >
53 <NavLink
54 :item="childSubItem"
55 @focusout="
56 isLastItemOfArray(childSubItem, subItem.items) &&
57 isLastItemOfArray(subItem, item.items) &&
58 setOpen(false)
59 "
60 />
61 </li>
62 </ul>
63
64 <NavLink
65 v-else
66 :item="subItem"
67 @focusout="isLastItemOfArray(subItem, item.items) && setOpen(false)"
68 />
69 </li>
70 </ul>
71 </DropdownTransition>
72 </div>
73</template>
74
75<script>
76import NavLink from '@theme/components/NavLink.vue'
77import DropdownTransition from '@theme/components/DropdownTransition.vue'
78import last from 'lodash/last'
79
80export default {
81 name: 'DropdownLink',
82
83 components: {
84 NavLink,
85 DropdownTransition
86 },
87
88 props: {
89 item: {
90 required: true
91 }
92 },
93
94 data () {
95 return {
96 open: false
97 }
98 },
99
100 computed: {
101 dropdownAriaLabel () {
102 return this.item.ariaLabel || this.item.text
103 }
104 },
105
106 watch: {
107 $route () {
108 this.open = false
109 }
110 },
111
112 methods: {
113 setOpen (value) {
114 this.open = value
115 },
116
117 isLastItemOfArray (item, array) {
118 return last(array) === item
119 },
120
121 /**
122 * Open the dropdown when user tab and click from keyboard.
123 *
124 * Use event.detail to detect tab and click from keyboard. Ref: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail
125 * The Tab + Click is UIEvent > KeyboardEvent, so the detail is 0.
126 */
127 handleDropdown () {
128 const isTriggerByTab = event.detail === 0
129 if (isTriggerByTab) this.setOpen(!this.open)
130 }
131 }
132}
133</script>
134
135<style lang="stylus">
136.dropdown-wrapper
137 cursor pointer
138 .dropdown-title
139 display block
140 font-size 0.9rem
141 font-family inherit
142 cursor inherit
143 padding inherit
144 line-height 1.4rem
145 background transparent
146 border none
147 font-weight 500
148 color $textColor
149 &:hover
150 border-color transparent
151 .arrow
152 vertical-align middle
153 margin-top -1px
154 margin-left 0.4rem
155 .mobile-dropdown-title
156 @extends .dropdown-title
157 display none
158 font-weight 600
159 font-size inherit
160 &:hover
161 color $accentColor
162 .nav-dropdown
163 .dropdown-item
164 color inherit
165 line-height 1.7rem
166 h4
167 margin 0.45rem 0 0
168 border-top 1px solid #eee
169 padding 1rem 1.5rem 0.45rem 1.25rem
170 .dropdown-subitem-wrapper
171 padding 0
172 list-style none
173 .dropdown-subitem
174 font-size 0.9em
175 a
176 display block
177 line-height 1.7rem
178 position relative
179 border-bottom none
180 font-weight 400
181 margin-bottom 0
182 padding 0 1.5rem 0 1.25rem
183 &:hover
184 color $accentColor
185 &.router-link-active
186 color $accentColor
187 &::after
188 content ""
189 width 0
190 height 0
191 border-left 5px solid $accentColor
192 border-top 3px solid transparent
193 border-bottom 3px solid transparent
194 position absolute
195 top calc(50% - 2px)
196 left 9px
197 &:first-child h4
198 margin-top 0
199 padding-top 0
200 border-top 0
201
202@media (max-width: $MQMobile)
203 .dropdown-wrapper
204 &.open .dropdown-title
205 margin-bottom 0.5rem
206 .dropdown-title
207 display: none
208 .mobile-dropdown-title
209 display: block
210 .nav-dropdown
211 transition height .1s ease-out
212 overflow hidden
213 .dropdown-item
214 h4
215 border-top 0
216 margin-top 0
217 padding-top 0
218 h4, & > a
219 font-size 15px
220 line-height 2rem
221 .dropdown-subitem
222 font-size 14px
223 padding-left 1rem
224
225@media (min-width: $MQMobile)
226 .dropdown-wrapper
227 height 1.8rem
228 &:hover .nav-dropdown,
229 &.open .nav-dropdown
230 // override the inline style.
231 display block !important
232 &.open:blur
233 display none
234 .nav-dropdown
235 display none
236 // Avoid height shaked by clicking
237 height auto !important
238 box-sizing border-box;
239 max-height calc(100vh - 2.7rem)
240 overflow-y auto
241 position absolute
242 top 100%
243 right 0
244 background-color #fff
245 padding 0.6rem 0
246 border 1px solid #ddd
247 border-bottom-color #ccc
248 text-align left
249 border-radius 0.25rem
250 white-space nowrap
251 margin 0
252</style>
diff --git a/doc/docs/.vuepress/theme/components/DropdownTransition.vue b/doc/docs/.vuepress/theme/components/DropdownTransition.vue
new file mode 100644
index 0000000..eeaf12b
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/DropdownTransition.vue
@@ -0,0 +1,33 @@
1<template>
2 <transition
3 name="dropdown"
4 @enter="setHeight"
5 @after-enter="unsetHeight"
6 @before-leave="setHeight"
7 >
8 <slot />
9 </transition>
10</template>
11
12<script>
13export default {
14 name: 'DropdownTransition',
15
16 methods: {
17 setHeight (items) {
18 // explicitly set height so that it can be transitioned
19 items.style.height = items.scrollHeight + 'px'
20 },
21
22 unsetHeight (items) {
23 items.style.height = ''
24 }
25 }
26}
27</script>
28
29<style lang="stylus">
30.dropdown-enter, .dropdown-leave-to
31 height 0 !important
32
33</style>
diff --git a/doc/docs/.vuepress/theme/components/Home.vue b/doc/docs/.vuepress/theme/components/Home.vue
new file mode 100644
index 0000000..acd8744
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/Home.vue
@@ -0,0 +1,175 @@
1<template>
2 <main
3 class="home"
4 :aria-labelledby="data.heroText !== null ? 'main-title' : null"
5 >
6 <header class="hero">
7 <img
8 v-if="data.heroImage"
9 :src="$withBase(data.heroImage)"
10 :alt="data.heroAlt || 'hero'"
11 >
12
13 <h1
14 v-if="data.heroText !== null"
15 id="main-title"
16 >
17 {{ data.heroText || $title || 'Hello' }}
18 </h1>
19
20 <p
21 v-if="data.tagline !== null"
22 class="description"
23 >
24 {{ data.tagline || $description || 'Welcome to your VuePress site' }}
25 </p>
26
27 <p
28 v-if="data.actionText && data.actionLink"
29 class="action"
30 >
31 <NavLink
32 class="action-button"
33 :item="actionLink"
34 />
35 </p>
36 </header>
37
38 <div
39 v-if="data.features && data.features.length"
40 class="features"
41 >
42 <div
43 v-for="(feature, index) in data.features"
44 :key="index"
45 class="feature"
46 >
47 <h2>{{ feature.title }}</h2>
48 <p>{{ feature.details }}</p>
49 </div>
50 </div>
51
52 <Content class="theme-default-content custom" />
53
54 <div
55 v-if="data.footer"
56 class="footer"
57 >
58 {{ data.footer }}
59 </div>
60 </main>
61</template>
62
63<script>
64import NavLink from '@theme/components/NavLink.vue'
65
66export default {
67 name: 'Home',
68
69 components: { NavLink },
70
71 computed: {
72 data () {
73 return this.$page.frontmatter
74 },
75
76 actionLink () {
77 return {
78 link: this.data.actionLink,
79 text: this.data.actionText
80 }
81 }
82 }
83}
84</script>
85
86<style lang="stylus">
87.home
88 padding $navbarHeight 2rem 0
89 max-width $homePageWidth
90 margin 0px auto
91 display block
92 .hero
93 text-align center
94 img
95 max-width: 100%
96 max-height 280px
97 display block
98 margin 3rem auto 1.5rem
99 h1
100 font-size 3rem
101 h1, .description, .action
102 margin 1.8rem auto
103 .description
104 max-width 35rem
105 font-size 1.6rem
106 line-height 1.3
107 color lighten($textColor, 40%)
108 .action-button
109 display inline-block
110 font-size 1.2rem
111 color #fff
112 background-color $accentColor
113 padding 0.8rem 1.6rem
114 border-radius 4px
115 transition background-color .1s ease
116 box-sizing border-box
117 border-bottom 1px solid darken($accentColor, 10%)
118 &:hover
119 background-color lighten($accentColor, 10%)
120 .features
121 border-top 1px solid $borderColor
122 padding 1.2rem 0
123 margin-top 2.5rem
124 display flex
125 flex-wrap wrap
126 align-items flex-start
127 align-content stretch
128 justify-content space-between
129 .feature
130 flex-grow 1
131 flex-basis 30%
132 max-width 30%
133 h2
134 font-size 1.4rem
135 font-weight 500
136 border-bottom none
137 padding-bottom 0
138 color lighten($textColor, 10%)
139 p
140 color lighten($textColor, 25%)
141 .footer
142 padding 2.5rem
143 border-top 1px solid $borderColor
144 text-align center
145 color lighten($textColor, 25%)
146
147@media (max-width: $MQMobile)
148 .home
149 .features
150 flex-direction column
151 .feature
152 max-width 100%
153 padding 0 2.5rem
154
155@media (max-width: $MQMobileNarrow)
156 .home
157 padding-left 1.5rem
158 padding-right 1.5rem
159 .hero
160 img
161 max-height 210px
162 margin 2rem auto 1.2rem
163 h1
164 font-size 2rem
165 h1, .description, .action
166 margin 1.2rem auto
167 .description
168 font-size 1.2rem
169 .action-button
170 font-size 1rem
171 padding 0.6rem 1.2rem
172 .feature
173 h2
174 font-size 1.25rem
175</style>
diff --git a/doc/docs/.vuepress/theme/components/NavLink.vue b/doc/docs/.vuepress/theme/components/NavLink.vue
new file mode 100644
index 0000000..f7e65a4
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/NavLink.vue
@@ -0,0 +1,90 @@
1<template>
2 <RouterLink
3 v-if="isInternal"
4 class="nav-link"
5 :to="link"
6 :exact="exact"
7 @focusout.native="focusoutAction"
8 >
9 {{ item.text }}
10 </RouterLink>
11 <a
12 v-else
13 :href="link"
14 class="nav-link external"
15 :target="target"
16 :rel="rel"
17 @focusout="focusoutAction"
18 >
19 {{ item.text }}
20 <OutboundLink v-if="isBlankTarget" />
21 </a>
22</template>
23
24<script>
25import { isExternal, isMailto, isTel, ensureExt } from '../util'
26
27export default {
28 name: 'NavLink',
29
30 props: {
31 item: {
32 required: true
33 }
34 },
35
36 computed: {
37 link () {
38 return ensureExt(this.item.link)
39 },
40
41 exact () {
42 if (this.$site.locales) {
43 return Object.keys(this.$site.locales).some(rootLink => rootLink === this.link)
44 }
45 return this.link === '/'
46 },
47
48 isNonHttpURI () {
49 return isMailto(this.link) || isTel(this.link)
50 },
51
52 isBlankTarget () {
53 return this.target === '_blank'
54 },
55
56 isInternal () {
57 return !isExternal(this.link) && !this.isBlankTarget
58 },
59
60 target () {
61 if (this.isNonHttpURI) {
62 return null
63 }
64 if (this.item.target) {
65 return this.item.target
66 }
67 return isExternal(this.link) ? '_blank' : ''
68 },
69
70 rel () {
71 if (this.isNonHttpURI) {
72 return null
73 }
74 if (this.item.rel === false) {
75 return null
76 }
77 if (this.item.rel) {
78 return this.item.rel
79 }
80 return this.isBlankTarget ? 'noopener noreferrer' : null
81 }
82 },
83
84 methods: {
85 focusoutAction () {
86 this.$emit('focusout')
87 }
88 }
89}
90</script>
diff --git a/doc/docs/.vuepress/theme/components/NavLinks.vue b/doc/docs/.vuepress/theme/components/NavLinks.vue
new file mode 100644
index 0000000..2656ae2
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/NavLinks.vue
@@ -0,0 +1,156 @@
1<template>
2 <nav
3 v-if="userLinks.length || repoLink"
4 class="nav-links"
5 >
6 <!-- user links -->
7 <div
8 v-for="item in userLinks"
9 :key="item.link"
10 class="nav-item"
11 >
12 <DropdownLink
13 v-if="item.type === 'links'"
14 :item="item"
15 />
16 <NavLink
17 v-else
18 :item="item"
19 />
20 </div>
21
22 <!-- repo link -->
23 <a
24 v-if="repoLink"
25 :href="repoLink"
26 class="repo-link"
27 target="_blank"
28 rel="noopener noreferrer"
29 >
30 {{ repoLabel }}
31 <OutboundLink />
32 </a>
33 </nav>
34</template>
35
36<script>
37import DropdownLink from '@theme/components/DropdownLink.vue'
38import { resolveNavLinkItem } from '../util'
39import NavLink from '@theme/components/NavLink.vue'
40
41export default {
42 name: 'NavLinks',
43
44 components: {
45 NavLink,
46 DropdownLink
47 },
48
49 computed: {
50 userNav () {
51 return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || []
52 },
53
54 nav () {
55 const { locales } = this.$site
56 if (locales && Object.keys(locales).length > 1) {
57 const currentLink = this.$page.path
58 const routes = this.$router.options.routes
59 const themeLocales = this.$site.themeConfig.locales || {}
60 const languageDropdown = {
61 text: this.$themeLocaleConfig.selectText || 'Languages',
62 ariaLabel: this.$themeLocaleConfig.ariaLabel || 'Select language',
63 items: Object.keys(locales).map(path => {
64 const locale = locales[path]
65 const text = themeLocales[path] && themeLocales[path].label || locale.lang
66 let link
67 // Stay on the current page
68 if (locale.lang === this.$lang) {
69 link = currentLink
70 } else {
71 // Try to stay on the same page
72 link = currentLink.replace(this.$localeConfig.path, path)
73 // fallback to homepage
74 if (!routes.some(route => route.path === link)) {
75 link = path
76 }
77 }
78 return { text, link }
79 })
80 }
81 return [...this.userNav, languageDropdown]
82 }
83 return this.userNav
84 },
85
86 userLinks () {
87 return (this.nav || []).map(link => {
88 return Object.assign(resolveNavLinkItem(link), {
89 items: (link.items || []).map(resolveNavLinkItem)
90 })
91 })
92 },
93
94 repoLink () {
95 const { repo } = this.$site.themeConfig
96 if (repo) {
97 return /^https?:/.test(repo)
98 ? repo
99 : `https://github.com/${repo}`
100 }
101 return null
102 },
103
104 repoLabel () {
105 if (!this.repoLink) return
106 if (this.$site.themeConfig.repoLabel) {
107 return this.$site.themeConfig.repoLabel
108 }
109
110 const repoHost = this.repoLink.match(/^https?:\/\/[^/]+/)[0]
111 const platforms = ['GitHub', 'GitLab', 'Bitbucket']
112 for (let i = 0; i < platforms.length; i++) {
113 const platform = platforms[i]
114 if (new RegExp(platform, 'i').test(repoHost)) {
115 return platform
116 }
117 }
118
119 return 'Source'
120 }
121 }
122}
123</script>
124
125<style lang="stylus">
126.nav-links
127 display inline-block
128 a
129 line-height 1.4rem
130 color inherit
131 &:hover, &.router-link-active
132 color $accentColor
133 .nav-item
134 position relative
135 display inline-block
136 margin-left 1.5rem
137 line-height 2rem
138 &:first-child
139 margin-left 0
140 .repo-link
141 margin-left 1.5rem
142
143@media (max-width: $MQMobile)
144 .nav-links
145 .nav-item, .repo-link
146 margin-left 0
147
148@media (min-width: $MQMobile)
149 .nav-links a
150 &:hover, &.router-link-active
151 color $textColor
152 .nav-item > a:not(.external)
153 &:hover, &.router-link-active
154 margin-bottom -2px
155 border-bottom 2px solid lighten($accentColor, 8%)
156</style>
diff --git a/doc/docs/.vuepress/theme/components/Navbar.vue b/doc/docs/.vuepress/theme/components/Navbar.vue
new file mode 100644
index 0000000..f8dd49c
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/Navbar.vue
@@ -0,0 +1,140 @@
1<template>
2 <header class="navbar">
3 <SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" />
4
5 <RouterLink
6 :to="$localePath"
7 class="home-link"
8 >
9 <img
10 v-if="$site.themeConfig.logo"
11 class="logo"
12 :src="$withBase($site.themeConfig.logo)"
13 :alt="$siteTitle"
14 >
15 <span
16 v-if="$siteTitle"
17 ref="siteName"
18 class="site-name"
19 :class="{ 'can-hide': $site.themeConfig.logo }"
20 >{{ $siteTitle }}</span>
21 </RouterLink>
22
23 <div
24 class="links"
25 :style="linksWrapMaxWidth ? {
26 'max-width': linksWrapMaxWidth + 'px'
27 } : {}"
28 >
29 <AlgoliaSearchBox
30 v-if="isAlgoliaSearch"
31 :options="algolia"
32 />
33 <SearchBox v-else-if="$site.themeConfig.search !== false && $page.frontmatter.search !== false" />
34 <NavLinks class="can-hide" />
35 </div>
36 </header>
37</template>
38
39<script>
40import AlgoliaSearchBox from '@AlgoliaSearchBox'
41import SearchBox from '@SearchBox'
42import SidebarButton from '@theme/components/SidebarButton.vue'
43import NavLinks from '@theme/components/NavLinks.vue'
44
45export default {
46 name: 'Navbar',
47
48 components: {
49 SidebarButton,
50 NavLinks,
51 SearchBox,
52 AlgoliaSearchBox
53 },
54
55 data () {
56 return {
57 linksWrapMaxWidth: null
58 }
59 },
60
61 computed: {
62 algolia () {
63 return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}
64 },
65
66 isAlgoliaSearch () {
67 return this.algolia && this.algolia.apiKey && this.algolia.indexName
68 }
69 },
70
71 mounted () {
72 const MOBILE_DESKTOP_BREAKPOINT = 719 // refer to config.styl
73 const NAVBAR_VERTICAL_PADDING = parseInt(css(this.$el, 'paddingLeft')) + parseInt(css(this.$el, 'paddingRight'))
74 const handleLinksWrapWidth = () => {
75 if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {
76 this.linksWrapMaxWidth = null
77 } else {
78 this.linksWrapMaxWidth = this.$el.offsetWidth - NAVBAR_VERTICAL_PADDING
79 - (this.$refs.siteName && this.$refs.siteName.offsetWidth || 0)
80 }
81 }
82 handleLinksWrapWidth()
83 window.addEventListener('resize', handleLinksWrapWidth, false)
84 }
85}
86
87function css (el, property) {
88 // NOTE: Known bug, will return 'auto' if style value is 'auto'
89 const win = el.ownerDocument.defaultView
90 // null means not to return pseudo styles
91 return win.getComputedStyle(el, null)[property]
92}
93</script>
94
95<style lang="stylus">
96$navbar-vertical-padding = 0.7rem
97$navbar-horizontal-padding = 1.5rem
98
99.navbar
100 padding $navbar-vertical-padding $navbar-horizontal-padding
101 line-height $navbarHeight - 1.4rem
102 a, span, img
103 display inline-block
104 .logo
105 height $navbarHeight - 1.4rem
106 min-width $navbarHeight - 1.4rem
107 margin-right 0.8rem
108 vertical-align top
109 .site-name
110 font-size 1.3rem
111 font-weight 600
112 color $textColor
113 position relative
114 .links
115 padding-left 1.5rem
116 box-sizing border-box
117 background-color white
118 white-space nowrap
119 font-size 0.9rem
120 position absolute
121 right $navbar-horizontal-padding
122 top $navbar-vertical-padding
123 display flex
124 .search-box
125 flex: 0 0 auto
126 vertical-align top
127
128@media (max-width: $MQMobile)
129 .navbar
130 padding-left 4rem
131 .can-hide
132 display none
133 .links
134 padding-left 1.5rem
135 .site-name
136 width calc(100vw - 9.4rem)
137 overflow hidden
138 white-space nowrap
139 text-overflow ellipsis
140</style>
diff --git a/doc/docs/.vuepress/theme/components/Page.vue b/doc/docs/.vuepress/theme/components/Page.vue
new file mode 100644
index 0000000..04ec7cb
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/Page.vue
@@ -0,0 +1,31 @@
1<template>
2 <main class="page">
3 <slot name="top" />
4
5 <Content class="theme-default-content" />
6 <PageEdit />
7
8 <PageNav v-bind="{ sidebarItems }" />
9
10 <slot name="bottom" />
11 </main>
12</template>
13
14<script>
15import PageEdit from '@theme/components/PageEdit.vue'
16import PageNav from '@theme/components/PageNav.vue'
17
18export default {
19 components: { PageEdit, PageNav },
20 props: ['sidebarItems']
21}
22</script>
23
24<style lang="stylus">
25@require '../styles/wrapper.styl'
26
27.page
28 padding-bottom 2rem
29 display block
30
31</style>
diff --git a/doc/docs/.vuepress/theme/components/PageEdit.vue b/doc/docs/.vuepress/theme/components/PageEdit.vue
new file mode 100644
index 0000000..cf9b2d2
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/PageEdit.vue
@@ -0,0 +1,155 @@
1<template>
2 <footer class="page-edit">
3 <div
4 v-if="editLink"
5 class="edit-link"
6 >
7 <a
8 :href="editLink"
9 target="_blank"
10 rel="noopener noreferrer"
11 >{{ editLinkText }}</a>
12 <OutboundLink />
13 </div>
14
15 <div
16 v-if="lastUpdated"
17 class="last-updated"
18 >
19 <span class="prefix">{{ lastUpdatedText }}:</span>
20 <span class="time">{{ lastUpdated }}</span>
21 </div>
22 </footer>
23</template>
24
25<script>
26import isNil from 'lodash/isNil'
27import { endingSlashRE, outboundRE } from '../util'
28
29export default {
30 name: 'PageEdit',
31
32 computed: {
33 lastUpdated () {
34 return this.$page.lastUpdated
35 },
36
37 lastUpdatedText () {
38 if (typeof this.$themeLocaleConfig.lastUpdated === 'string') {
39 return this.$themeLocaleConfig.lastUpdated
40 }
41 if (typeof this.$site.themeConfig.lastUpdated === 'string') {
42 return this.$site.themeConfig.lastUpdated
43 }
44 return 'Last Updated'
45 },
46
47 editLink () {
48 const showEditLink = isNil(this.$page.frontmatter.editLink)
49 ? this.$site.themeConfig.editLinks
50 : this.$page.frontmatter.editLink
51
52 const {
53 repo,
54 docsDir = '',
55 docsBranch = 'master',
56 docsRepo = repo
57 } = this.$site.themeConfig
58
59 if (showEditLink && docsRepo && this.$page.relativePath) {
60 return this.createEditLink(
61 repo,
62 docsRepo,
63 docsDir,
64 docsBranch,
65 this.$page.relativePath
66 )
67 }
68 return null
69 },
70
71 editLinkText () {
72 return (
73 this.$themeLocaleConfig.editLinkText
74 || this.$site.themeConfig.editLinkText
75 || `Edit this page`
76 )
77 }
78 },
79
80 methods: {
81 createEditLink (repo, docsRepo, docsDir, docsBranch, path) {
82 const bitbucket = /bitbucket.org/
83 if (bitbucket.test(docsRepo)) {
84 const base = docsRepo
85 return (
86 base.replace(endingSlashRE, '')
87 + `/src`
88 + `/${docsBranch}/`
89 + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')
90 + path
91 + `?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`
92 )
93 }
94
95 const gitlab = /gitlab.com/
96 if (gitlab.test(docsRepo)) {
97 const base = docsRepo
98 return (
99 base.replace(endingSlashRE, '')
100 + `/-/edit`
101 + `/${docsBranch}/`
102 + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')
103 + path
104 )
105 }
106
107 const base = outboundRE.test(docsRepo)
108 ? docsRepo
109 : `https://github.com/${docsRepo}`
110 return (
111 base.replace(endingSlashRE, '')
112 + '/edit'
113 + `/${docsBranch}/`
114 + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')
115 + path
116 )
117 }
118 }
119}
120</script>
121
122<style lang="stylus">
123@require '../styles/wrapper.styl'
124
125.page-edit
126 @extend $wrapper
127 padding-top 1rem
128 padding-bottom 1rem
129 overflow auto
130
131 .edit-link
132 display inline-block
133 a
134 color lighten($textColor, 25%)
135 margin-right 0.25rem
136 .last-updated
137 float right
138 font-size 0.9em
139 .prefix
140 font-weight 500
141 color lighten($textColor, 25%)
142 .time
143 font-weight 400
144 color #767676
145
146@media (max-width: $MQMobile)
147 .page-edit
148 .edit-link
149 margin-bottom 0.5rem
150 .last-updated
151 font-size 0.8em
152 float none
153 text-align left
154
155</style>
diff --git a/doc/docs/.vuepress/theme/components/PageNav.vue b/doc/docs/.vuepress/theme/components/PageNav.vue
new file mode 100644
index 0000000..4c19aae
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/PageNav.vue
@@ -0,0 +1,163 @@
1<template>
2 <div
3 v-if="prev || next"
4 class="page-nav"
5 >
6 <p class="inner">
7 <span
8 v-if="prev"
9 class="prev"
10 >
11
12 <a
13 v-if="prev.type === 'external'"
14 class="prev"
15 :href="prev.path"
16 target="_blank"
17 rel="noopener noreferrer"
18 >
19 {{ prev.title || prev.path }}
20
21 <OutboundLink />
22 </a>
23
24 <RouterLink
25 v-else
26 class="prev"
27 :to="prev.path"
28 >
29 {{ prev.title || prev.path }}
30 </RouterLink>
31 </span>
32
33 <span
34 v-if="next"
35 class="next"
36 >
37 <a
38 v-if="next.type === 'external'"
39 :href="next.path"
40 target="_blank"
41 rel="noopener noreferrer"
42 >
43 {{ next.title || next.path }}
44
45 <OutboundLink />
46 </a>
47
48 <RouterLink
49 v-else
50 :to="next.path"
51 >
52 {{ next.title || next.path }}
53 </RouterLink>
54
55 </span>
56 </p>
57 </div>
58</template>
59
60<script>
61import { resolvePage } from '../util'
62import isString from 'lodash/isString'
63import isNil from 'lodash/isNil'
64
65export default {
66 name: 'PageNav',
67
68 props: ['sidebarItems'],
69
70 computed: {
71 prev () {
72 return resolvePageLink(LINK_TYPES.PREV, this)
73 },
74
75 next () {
76 return resolvePageLink(LINK_TYPES.NEXT, this)
77 }
78 }
79}
80
81function resolvePrev (page, items) {
82 return find(page, items, -1)
83}
84
85function resolveNext (page, items) {
86 return find(page, items, 1)
87}
88
89const LINK_TYPES = {
90 NEXT: {
91 resolveLink: resolveNext,
92 getThemeLinkConfig: ({ nextLinks }) => nextLinks,
93 getPageLinkConfig: ({ frontmatter }) => frontmatter.next
94 },
95 PREV: {
96 resolveLink: resolvePrev,
97 getThemeLinkConfig: ({ prevLinks }) => prevLinks,
98 getPageLinkConfig: ({ frontmatter }) => frontmatter.prev
99 }
100}
101
102function resolvePageLink (
103 linkType,
104 { $themeConfig, $page, $route, $site, sidebarItems }
105) {
106 const { resolveLink, getThemeLinkConfig, getPageLinkConfig } = linkType
107
108 // Get link config from theme
109 const themeLinkConfig = getThemeLinkConfig($themeConfig)
110
111 // Get link config from current page
112 const pageLinkConfig = getPageLinkConfig($page)
113
114 // Page link config will overwrite global theme link config if defined
115 const link = isNil(pageLinkConfig) ? themeLinkConfig : pageLinkConfig
116
117 if (link === false) {
118 return
119 } else if (isString(link)) {
120 return resolvePage($site.pages, link, $route.path)
121 } else {
122 return resolveLink($page, sidebarItems)
123 }
124}
125
126function find (page, items, offset) {
127 const res = []
128 flatten(items, res)
129 for (let i = 0; i < res.length; i++) {
130 const cur = res[i]
131 if (cur.type === 'page' && cur.path === decodeURIComponent(page.path)) {
132 return res[i + offset]
133 }
134 }
135}
136
137function flatten (items, res) {
138 for (let i = 0, l = items.length; i < l; i++) {
139 if (items[i].type === 'group') {
140 flatten(items[i].children || [], res)
141 } else {
142 res.push(items[i])
143 }
144 }
145}
146</script>
147
148<style lang="stylus">
149@require '../styles/wrapper.styl'
150
151.page-nav
152 @extend $wrapper
153 padding-top 1rem
154 padding-bottom 0
155 .inner
156 min-height 2rem
157 margin-top 0
158 border-top 1px solid $borderColor
159 padding-top 1rem
160 overflow auto // clear float
161 .next
162 float right
163</style>
diff --git a/doc/docs/.vuepress/theme/components/Sidebar.vue b/doc/docs/.vuepress/theme/components/Sidebar.vue
new file mode 100644
index 0000000..e70e333
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/Sidebar.vue
@@ -0,0 +1,64 @@
1<template>
2 <aside class="sidebar">
3 <NavLinks />
4
5 <slot name="top" />
6
7 <SidebarLinks
8 :depth="0"
9 :items="items"
10 />
11 <slot name="bottom" />
12 </aside>
13</template>
14
15<script>
16import SidebarLinks from '@theme/components/SidebarLinks.vue'
17import NavLinks from '@theme/components/NavLinks.vue'
18
19export default {
20 name: 'Sidebar',
21
22 components: { SidebarLinks, NavLinks },
23
24 props: ['items']
25}
26</script>
27
28<style lang="stylus">
29.sidebar
30 ul
31 padding 0
32 margin 0
33 list-style-type none
34 a
35 display inline-block
36 .nav-links
37 display none
38 border-bottom 1px solid $borderColor
39 padding 0.5rem 0 0.75rem 0
40 a
41 font-weight 600
42 .nav-item, .repo-link
43 display block
44 line-height 1.25rem
45 font-size 1.1em
46 padding 0.5rem 0 0.5rem 1.5rem
47 & > .sidebar-links
48 padding 1.5rem 0
49 & > li > a.sidebar-link
50 font-size 1.1em
51 line-height 1.7
52 font-weight bold
53 & > li:not(:first-child)
54 margin-top .75rem
55
56@media (max-width: $MQMobile)
57 .sidebar
58 .nav-links
59 display block
60 .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after
61 top calc(1rem - 2px)
62 & > .sidebar-links
63 padding 1rem 0
64</style>
diff --git a/doc/docs/.vuepress/theme/components/SidebarButton.vue b/doc/docs/.vuepress/theme/components/SidebarButton.vue
new file mode 100644
index 0000000..3f54afd
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/SidebarButton.vue
@@ -0,0 +1,40 @@
1<template>
2 <div
3 class="sidebar-button"
4 @click="$emit('toggle-sidebar')"
5 >
6 <svg
7 class="icon"
8 xmlns="http://www.w3.org/2000/svg"
9 aria-hidden="true"
10 role="img"
11 viewBox="0 0 448 512"
12 >
13 <path
14 fill="currentColor"
15 d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"
16 class=""
17 />
18 </svg>
19 </div>
20</template>
21
22<style lang="stylus">
23.sidebar-button
24 cursor pointer
25 display none
26 width 1.25rem
27 height 1.25rem
28 position absolute
29 padding 0.6rem
30 top 0.6rem
31 left 1rem
32 .icon
33 display block
34 width 1.25rem
35 height 1.25rem
36
37@media (max-width: $MQMobile)
38 .sidebar-button
39 display block
40</style>
diff --git a/doc/docs/.vuepress/theme/components/SidebarGroup.vue b/doc/docs/.vuepress/theme/components/SidebarGroup.vue
new file mode 100644
index 0000000..d7f1929
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/SidebarGroup.vue
@@ -0,0 +1,141 @@
1<template>
2 <section
3 class="sidebar-group"
4 :class="[
5 {
6 collapsable,
7 'is-sub-group': depth !== 0
8 },
9 `depth-${depth}`
10 ]"
11 >
12 <RouterLink
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 v-if="collapsable"
25 class="arrow"
26 :class="open ? 'down' : 'right'"
27 />
28 </RouterLink>
29
30 <p
31 v-else
32 class="sidebar-heading"
33 :class="{ open }"
34 @click="$emit('toggle')"
35 >
36 <span>{{ item.title }}</span>
37 <span
38 v-if="collapsable"
39 class="arrow"
40 :class="open ? 'down' : 'right'"
41 />
42 </p>
43
44 <DropdownTransition>
45 <SidebarLinks
46 v-if="open || !collapsable"
47 class="sidebar-group-items"
48 :items="item.children"
49 :sidebar-depth="item.sidebarDepth"
50 :initial-open-group-index="item.initialOpenGroupIndex"
51 :depth="depth + 1"
52 />
53 </DropdownTransition>
54 </section>
55</template>
56
57<script>
58import { isActive } from '../util'
59import DropdownTransition from '@theme/components/DropdownTransition.vue'
60
61export default {
62 name: 'SidebarGroup',
63
64 components: {
65 DropdownTransition
66 },
67
68 props: [
69 'item',
70 'open',
71 'collapsable',
72 'depth'
73 ],
74
75 // ref: https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
76 beforeCreate () {
77 this.$options.components.SidebarLinks = require('@theme/components/SidebarLinks.vue').default
78 },
79
80 methods: { isActive }
81}
82</script>
83
84<style lang="stylus">
85.sidebar-group
86 .sidebar-group
87 padding-left 0.5em
88 &:not(.collapsable)
89 .sidebar-heading:not(.clickable)
90 cursor auto
91 color inherit
92 // refine styles of nested sidebar groups
93 &.is-sub-group
94 padding-left 0
95 & > .sidebar-heading
96 font-size 0.95em
97 line-height 1.4
98 font-weight normal
99 padding-left 2rem
100 &:not(.clickable)
101 opacity 0.5
102 & > .sidebar-group-items
103 padding-left 1rem
104 & > li > .sidebar-link
105 font-size: 0.95em;
106 border-left none
107 &.depth-2
108 & > .sidebar-heading
109 border-left none
110
111.sidebar-heading
112 color $textColor
113 transition color .15s ease
114 cursor pointer
115 font-size 1.1em
116 font-weight bold
117 // text-transform uppercase
118 padding 0.35rem 1.5rem 0.35rem 1.25rem
119 width 100%
120 box-sizing border-box
121 margin 0
122 border-left 0.25rem solid transparent
123 &.open, &:hover
124 color inherit
125 .arrow
126 position relative
127 top -0.12em
128 left 0.5em
129 &.clickable
130 &.active
131 font-weight 600
132 color $accentColor
133 border-left-color $accentColor
134 &:hover
135 color $accentColor
136
137.sidebar-group-items
138 transition height .1s ease-out
139 font-size 0.95em
140 overflow hidden
141</style>
diff --git a/doc/docs/.vuepress/theme/components/SidebarLink.vue b/doc/docs/.vuepress/theme/components/SidebarLink.vue
new file mode 100644
index 0000000..4cd7665
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/SidebarLink.vue
@@ -0,0 +1,133 @@
1<script>
2import { isActive, hashRE, groupHeaders } from '../util'
3
4export default {
5 functional: true,
6
7 props: ['item', 'sidebarDepth'],
8
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 }) {
23 // use custom active class matching logic
24 // due to edge case of paths ending with / + hash
25 const selfActive = isActive($route, item.path)
26 // for sidebar: auto pages, a hash link should be active if one of its child
27 // matches
28 const active = item.type === 'auto'
29 ? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug))
30 : selfActive
31 const link = item.type === 'external'
32 ? renderExternal(h, item.path, item.title || item.path)
33 : renderLink(h, item.path, item.title || item.path, active)
34
35 const maxDepth = [
36 $page.frontmatter.sidebarDepth,
37 sidebarDepth,
38 $themeLocaleConfig.sidebarDepth,
39 $themeConfig.sidebarDepth,
40 1
41 ].find(depth => depth !== undefined)
42
43 const displayAllHeaders = $themeLocaleConfig.displayAllHeaders
44 || $themeConfig.displayAllHeaders
45
46 if (item.type === 'auto') {
47 return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)]
48 } else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) {
49 const children = groupHeaders(item.headers)
50 return [link, renderChildren(h, children, item.path, $route, maxDepth)]
51 } else {
52 return link
53 }
54 }
55}
56
57function renderLink (h, to, text, active, level) {
58 const component = {
59 props: {
60 to,
61 activeClass: '',
62 exactActiveClass: ''
63 },
64 class: {
65 active,
66 'sidebar-link': true
67 }
68 }
69
70 if (level > 2) {
71 component.style = {
72 'padding-left': level + 'rem'
73 }
74 }
75
76 return h('RouterLink', component, text)
77}
78
79function renderChildren (h, children, path, route, maxDepth, depth = 1) {
80 if (!children || depth > maxDepth) return null
81 return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => {
82 const active = isActive(route, path + '#' + c.slug)
83 return h('li', { class: 'sidebar-sub-header' }, [
84 renderLink(h, path + '#' + c.slug, c.title, active, c.level - 1),
85 renderChildren(h, c.children, path, route, maxDepth, depth + 1)
86 ])
87 }))
88}
89
90function renderExternal (h, to, text) {
91 return h('a', {
92 attrs: {
93 href: to,
94 target: '_blank',
95 rel: 'noopener noreferrer'
96 },
97 class: {
98 'sidebar-link': true
99 }
100 }, [text, h('OutboundLink')])
101}
102</script>
103
104<style lang="stylus">
105.sidebar .sidebar-sub-headers
106 padding-left 1rem
107 font-size 0.95em
108
109a.sidebar-link
110 font-size 1em
111 font-weight 400
112 display inline-block
113 color $textColor
114 border-left 0.25rem solid transparent
115 padding 0.35rem 1rem 0.35rem 1.25rem
116 line-height 1.4
117 width: 100%
118 box-sizing: border-box
119 &:hover
120 color $accentColor
121 &.active
122 font-weight 600
123 color $accentColor
124 border-left-color $accentColor
125 .sidebar-group &
126 padding-left 2rem
127 .sidebar-sub-headers &
128 padding-top 0.25rem
129 padding-bottom 0.25rem
130 border-left none
131 &.active
132 font-weight 500
133</style>
diff --git a/doc/docs/.vuepress/theme/components/SidebarLinks.vue b/doc/docs/.vuepress/theme/components/SidebarLinks.vue
new file mode 100644
index 0000000..55e6288
--- /dev/null
+++ b/doc/docs/.vuepress/theme/components/SidebarLinks.vue
@@ -0,0 +1,106 @@
1<template>
2 <ul
3 v-if="items.length"
4 class="sidebar-links"
5 >
6 <li
7 v-for="(item, i) in items"
8 :key="i"
9 >
10 <SidebarGroup
11 v-if="item.type === 'group'"
12 :item="item"
13 :open="i === openGroupIndex"
14 :collapsable="item.collapsable || item.collapsible"
15 :depth="depth"
16 @toggle="toggleGroup(i)"
17 />
18 <SidebarLink
19 v-else
20 :sidebar-depth="sidebarDepth"
21 :item="item"
22 />
23 </li>
24 </ul>
25</template>
26
27<script>
28import SidebarGroup from '@theme/components/SidebarGroup.vue'
29import SidebarLink from '@theme/components/SidebarLink.vue'
30import { isActive } from '../util'
31
32export default {
33 name: 'SidebarLinks',
34
35 components: { SidebarGroup, SidebarLink },
36
37 props: [
38 'items',
39 'depth', // depth of current sidebar links
40 'sidebarDepth', // depth of headers to be extracted
41 'initialOpenGroupIndex'
42 ],
43
44 data () {
45 return {
46 openGroupIndex: this.initialOpenGroupIndex || 0
47 }
48 },
49
50 watch: {
51 '$route' () {
52 this.refreshIndex()
53 }
54 },
55
56 created () {
57 this.refreshIndex()
58 },
59
60 methods: {
61 refreshIndex () {
62 const index = resolveOpenGroupIndex(
63 this.$route,
64 this.items
65 )
66 if (index > -1) {
67 this.openGroupIndex = index
68 }
69 },
70
71 toggleGroup (index) {
72 this.openGroupIndex = index === this.openGroupIndex ? -1 : index
73 },
74
75 isActive (page) {
76 return isActive(this.$route, page.regularPath)
77 }
78 }
79}
80
81function resolveOpenGroupIndex (route, items) {
82 for (let i = 0; i < items.length; i++) {
83 const item = items[i]
84 if (descendantIsActive(route, item)) {
85 return i
86 }
87 }
88 return -1
89}
90
91function descendantIsActive (route, item) {
92 if (item.type === 'group') {
93 const childIsActive = item.path && isActive(route, item.path)
94 const grandChildIsActive = item.children.some(child => {
95 if (child.type === 'group') {
96 return descendantIsActive(route, child)
97 } else {
98 return child.type === 'page' && isActive(route, child.path)
99 }
100 })
101
102 return childIsActive || grandChildIsActive
103 }
104 return false
105}
106</script>