diff options
| author | Li Jin <dragon-fly@qq.com> | 2021-04-19 23:41:33 +0800 |
|---|---|---|
| committer | Li Jin <dragon-fly@qq.com> | 2021-04-19 23:41:33 +0800 |
| commit | 5d3b07801456d16dcc2c75dcccd48d508a6b60cc (patch) | |
| tree | 2df1a154bf58d93f2475df02afbd15f1a8ba2963 /doc/docs/.vuepress/theme/components | |
| parent | ea82666506b57d6e905b7f2e5fe78498fe5a7abd (diff) | |
| download | yuescript-5d3b07801456d16dcc2c75dcccd48d508a6b60cc.tar.gz yuescript-5d3b07801456d16dcc2c75dcccd48d508a6b60cc.tar.bz2 yuescript-5d3b07801456d16dcc2c75dcccd48d508a6b60cc.zip | |
first commit for Yuescript document site.
Diffstat (limited to 'doc/docs/.vuepress/theme/components')
| -rw-r--r-- | doc/docs/.vuepress/theme/components/AlgoliaSearchBox.vue | 172 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/DropdownLink.vue | 252 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/DropdownTransition.vue | 33 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/Home.vue | 175 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/NavLink.vue | 90 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/NavLinks.vue | 156 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/Navbar.vue | 140 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/Page.vue | 31 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/PageEdit.vue | 155 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/PageNav.vue | 163 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/Sidebar.vue | 64 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/SidebarButton.vue | 40 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/SidebarGroup.vue | 141 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/SidebarLink.vue | 133 | ||||
| -rw-r--r-- | doc/docs/.vuepress/theme/components/SidebarLinks.vue | 106 |
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> | ||
| 16 | export 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> | ||
| 76 | import NavLink from '@theme/components/NavLink.vue' | ||
| 77 | import DropdownTransition from '@theme/components/DropdownTransition.vue' | ||
| 78 | import last from 'lodash/last' | ||
| 79 | |||
| 80 | export 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> | ||
| 13 | export 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> | ||
| 64 | import NavLink from '@theme/components/NavLink.vue' | ||
| 65 | |||
| 66 | export 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> | ||
| 25 | import { isExternal, isMailto, isTel, ensureExt } from '../util' | ||
| 26 | |||
| 27 | export 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> | ||
| 37 | import DropdownLink from '@theme/components/DropdownLink.vue' | ||
| 38 | import { resolveNavLinkItem } from '../util' | ||
| 39 | import NavLink from '@theme/components/NavLink.vue' | ||
| 40 | |||
| 41 | export 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> | ||
| 40 | import AlgoliaSearchBox from '@AlgoliaSearchBox' | ||
| 41 | import SearchBox from '@SearchBox' | ||
| 42 | import SidebarButton from '@theme/components/SidebarButton.vue' | ||
| 43 | import NavLinks from '@theme/components/NavLinks.vue' | ||
| 44 | |||
| 45 | export 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 | |||
| 87 | function 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> | ||
| 15 | import PageEdit from '@theme/components/PageEdit.vue' | ||
| 16 | import PageNav from '@theme/components/PageNav.vue' | ||
| 17 | |||
| 18 | export 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> | ||
| 26 | import isNil from 'lodash/isNil' | ||
| 27 | import { endingSlashRE, outboundRE } from '../util' | ||
| 28 | |||
| 29 | export 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> | ||
| 61 | import { resolvePage } from '../util' | ||
| 62 | import isString from 'lodash/isString' | ||
| 63 | import isNil from 'lodash/isNil' | ||
| 64 | |||
| 65 | export 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 | |||
| 81 | function resolvePrev (page, items) { | ||
| 82 | return find(page, items, -1) | ||
| 83 | } | ||
| 84 | |||
| 85 | function resolveNext (page, items) { | ||
| 86 | return find(page, items, 1) | ||
| 87 | } | ||
| 88 | |||
| 89 | const 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 | |||
| 102 | function 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 | |||
| 126 | function 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 | |||
| 137 | function 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> | ||
| 16 | import SidebarLinks from '@theme/components/SidebarLinks.vue' | ||
| 17 | import NavLinks from '@theme/components/NavLinks.vue' | ||
| 18 | |||
| 19 | export 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> | ||
| 58 | import { isActive } from '../util' | ||
| 59 | import DropdownTransition from '@theme/components/DropdownTransition.vue' | ||
| 60 | |||
| 61 | export 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> | ||
| 2 | import { isActive, hashRE, groupHeaders } from '../util' | ||
| 3 | |||
| 4 | export 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 | |||
| 57 | function 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 | |||
| 79 | function 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 | |||
| 90 | function 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 | |||
| 109 | a.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> | ||
| 28 | import SidebarGroup from '@theme/components/SidebarGroup.vue' | ||
| 29 | import SidebarLink from '@theme/components/SidebarLink.vue' | ||
| 30 | import { isActive } from '../util' | ||
| 31 | |||
| 32 | export 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 | |||
| 81 | function 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 | |||
| 91 | function 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> | ||
