This is an automated email from the ASF dual-hosted git repository. mssun pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-teaclave-website.git
commit 1a56b1d56ea1174db7cfe8823a52ee1aa8a20f46 Author: Mingshen Sun <[email protected]> AuthorDate: Thu May 14 17:02:55 2020 -0700 Add more docs --- .vuepress/config.js | 31 ++- .vuepress/styles/index.styl | 3 - .vuepress/theme/LICENSE | 21 ++ .vuepress/theme/components/AlgoliaSearchBox.vue | 170 +++++++++++++++ .vuepress/theme/components/DropdownLink.vue | 230 +++++++++++++++++++++ .vuepress/theme/components/DropdownTransition.vue | 33 +++ .vuepress/theme/components/Home.vue | 184 +++++++++++++++++ .vuepress/theme/components/NavLink.vue | 87 ++++++++ .vuepress/theme/components/NavLinks.vue | 156 ++++++++++++++ .vuepress/theme/components/Navbar.vue | 140 +++++++++++++ .vuepress/theme/components/Page.vue | 31 +++ .vuepress/theme/components/PageEdit.vue | 143 +++++++++++++ .vuepress/theme/components/PageNav.vue | 163 +++++++++++++++ .vuepress/theme/components/Sidebar.vue | 64 ++++++ .vuepress/theme/components/SidebarButton.vue | 40 ++++ .vuepress/theme/components/SidebarGroup.vue | 140 +++++++++++++ .vuepress/theme/components/SidebarLink.vue | 133 ++++++++++++ .vuepress/theme/components/SidebarLinks.vue | 102 +++++++++ .vuepress/theme/global-components/Badge.vue | 44 ++++ .vuepress/theme/index.js | 59 ++++++ .vuepress/theme/layouts/404.vue | 30 +++ .vuepress/theme/layouts/Layout.vue | 151 ++++++++++++++ .vuepress/theme/noopModule.js | 1 + .vuepress/theme/styles/arrow.styl | 22 ++ .vuepress/theme/styles/code.styl | 137 ++++++++++++ .vuepress/theme/styles/config.styl | 1 + .vuepress/theme/styles/custom-blocks.styl | 44 ++++ .vuepress/theme/styles/index.styl | 201 ++++++++++++++++++ .vuepress/theme/styles/mobile.styl | 37 ++++ .vuepress/theme/styles/toc.styl | 3 + .vuepress/theme/styles/wrapper.styl | 9 + .vuepress/theme/util/index.js | 240 ++++++++++++++++++++++ Makefile | 3 + index.md | 3 - 34 files changed, 2848 insertions(+), 8 deletions(-) diff --git a/.vuepress/config.js b/.vuepress/config.js index 0a48a78..9c4dd7c 100644 --- a/.vuepress/config.js +++ b/.vuepress/config.js @@ -1,10 +1,37 @@ module.exports = { - title: 'Apache Teaclave', - description: 'Teaclave is an open source universal secure computing platform, making computation on privacy-sensitive data safe and simple.', + title: 'Apache Teaclave (incubating)', + description: 'Apache Teaclave (incubating) is an open source universal secure computing platform, making computation on privacy-sensitive data safe and simple.', + base: '/', themeConfig: { search: false, nav: [ + { text: 'Documentation', link: '/docs/' }, { text: 'GitHub', link: 'https://github.com/apache/incubator-teaclave' } + ], + sidebar: [ + { + title: 'Documentation', + path: '/docs/my-first-function/', + collapsable: false, + children: [ + '/teaclave/docs/my-first-function', + '/teaclave/docs/threat-model', + '/teaclave/docs/rust-guideline', + '/teaclave/docs/mutual-attestation', + ], + }, + { + title: 'Codebase', + path: '/services/', + collapsable: false, + children: [ + '/teaclave/services/', + '/teaclave/config/', + '/teaclave/dcap/', + '/teaclave/keys/', + '/teaclave/docker/', + ], + }, ] }, } diff --git a/.vuepress/styles/index.styl b/.vuepress/styles/index.styl deleted file mode 100644 index e1ab8a3..0000000 --- a/.vuepress/styles/index.styl +++ /dev/null @@ -1,3 +0,0 @@ -.footer { - font-size: 12px; -} diff --git a/.vuepress/theme/LICENSE b/.vuepress/theme/LICENSE new file mode 100644 index 0000000..15f1f7e --- /dev/null +++ b/.vuepress/theme/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/.vuepress/theme/components/AlgoliaSearchBox.vue b/.vuepress/theme/components/AlgoliaSearchBox.vue new file mode 100644 index 0000000..4d51a71 --- /dev/null +++ b/.vuepress/theme/components/AlgoliaSearchBox.vue @@ -0,0 +1,170 @@ +<template> + <form + id="search-form" + class="algolia-search-wrapper search-box" + role="search" + > + <input + id="algolia-search-input" + class="search-query" + :placeholder="placeholder" + > + </form> +</template> + +<script> +export default { + name: 'AlgoliaSearchBox', + + props: ['options'], + + data () { + return { + placeholder: undefined + } + }, + + watch: { + $lang (newValue) { + this.update(this.options, newValue) + }, + + options (newValue) { + this.update(newValue, this.$lang) + } + }, + + mounted () { + this.initialize(this.options, this.$lang) + this.placeholder = this.$site.themeConfig.searchPlaceholder || '' + }, + + methods: { + initialize (userOptions, lang) { + Promise.all([ + import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.js'), + import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.css') + ]).then(([docsearch]) => { + docsearch = docsearch.default + const { algoliaOptions = {}} = userOptions + docsearch(Object.assign( + {}, + userOptions, + { + inputSelector: '#algolia-search-input', + // #697 Make docsearch work well at i18n mode. + algoliaOptions: Object.assign({ + 'facetFilters': [`lang:${lang}`].concat(algoliaOptions.facetFilters || []) + }, algoliaOptions), + handleSelected: (input, event, suggestion) => { + const { pathname, hash } = new URL(suggestion.url) + const routepath = pathname.replace(this.$site.base, '/') + this.$router.push(`${routepath}${hash}`) + } + } + )) + }) + }, + + update (options, lang) { + this.$el.innerHTML = '<input id="algolia-search-input" class="search-query">' + this.initialize(options, lang) + } + } +} +</script> + +<style lang="stylus"> +.algolia-search-wrapper + & > span + vertical-align middle + .algolia-autocomplete + line-height normal + .ds-dropdown-menu + background-color #fff + border 1px solid #999 + border-radius 4px + font-size 16px + margin 6px 0 0 + padding 4px + text-align left + &:before + border-color #999 + [class*=ds-dataset-] + border none + padding 0 + .ds-suggestions + margin-top 0 + .ds-suggestion + border-bottom 1px solid $borderColor + .algolia-docsearch-suggestion--highlight + color #2c815b + .algolia-docsearch-suggestion + border-color $borderColor + padding 0 + .algolia-docsearch-suggestion--category-header + padding 5px 10px + margin-top 0 + background $accentColor + color #fff + font-weight 600 + .algolia-docsearch-suggestion--highlight + background rgba(255, 255, 255, 0.6) + .algolia-docsearch-suggestion--wrapper + padding 0 + .algolia-docsearch-suggestion--title + font-weight 600 + margin-bottom 0 + color $textColor + .algolia-docsearch-suggestion--subcategory-column + vertical-align top + padding 5px 7px 5px 5px + border-color $borderColor + background #f1f3f5 + &:after + display none + .algolia-docsearch-suggestion--subcategory-column-text + color #555 + .algolia-docsearch-footer + border-color $borderColor + .ds-cursor .algolia-docsearch-suggestion--content + background-color #e7edf3 !important + color $textColor + +@media (min-width: $MQMobile) + .algolia-search-wrapper + .algolia-autocomplete + .algolia-docsearch-suggestion + .algolia-docsearch-suggestion--subcategory-column + float none + width 150px + min-width 150px + display table-cell + .algolia-docsearch-suggestion--content + float none + display table-cell + width 100% + vertical-align top + .ds-dropdown-menu + min-width 515px !important + +@media (max-width: $MQMobile) + .algolia-search-wrapper + .ds-dropdown-menu + min-width calc(100vw - 4rem) !important + max-width calc(100vw - 4rem) !important + .algolia-docsearch-suggestion--wrapper + padding 5px 7px 5px 5px !important + .algolia-docsearch-suggestion--subcategory-column + padding 0 !important + background white !important + .algolia-docsearch-suggestion--subcategory-column-text:after + content " > " + font-size 10px + line-height 14.4px + display inline-block + width 5px + margin -3px 3px 0 + vertical-align middle + +</style> diff --git a/.vuepress/theme/components/DropdownLink.vue b/.vuepress/theme/components/DropdownLink.vue new file mode 100644 index 0000000..0ca7137 --- /dev/null +++ b/.vuepress/theme/components/DropdownLink.vue @@ -0,0 +1,230 @@ +<template> + <div + class="dropdown-wrapper" + :class="{ open }" + > + <button + class="dropdown-title" + type="button" + :aria-label="dropdownAriaLabel" + @click="setOpen(!open)" + > + <span class="title">{{ item.text }}</span> + <span + class="arrow" + :class="open ? 'down' : 'right'" + /> + </button> + + <DropdownTransition> + <ul + v-show="open" + class="nav-dropdown" + > + <li + v-for="(subItem, index) in item.items" + :key="subItem.link || index" + class="dropdown-item" + > + <h4 v-if="subItem.type === 'links'"> + {{ subItem.text }} + </h4> + + <ul + v-if="subItem.type === 'links'" + class="dropdown-subitem-wrapper" + > + <li + v-for="childSubItem in subItem.items" + :key="childSubItem.link" + class="dropdown-subitem" + > + <NavLink + :item="childSubItem" + @focusout=" + isLastItemOfArray(childSubItem, subItem.items) && + isLastItemOfArray(subItem, item.items) && + setOpen(false) + " + /> + </li> + </ul> + + <NavLink + v-else + :item="subItem" + @focusout="isLastItemOfArray(subItem, item.items) && setOpen(false)" + /> + </li> + </ul> + </DropdownTransition> + </div> +</template> + +<script> +import NavLink from '@theme/components/NavLink.vue' +import DropdownTransition from '@theme/components/DropdownTransition.vue' +import last from 'lodash/last' + +export default { + name: 'DropdownLink', + + components: { + NavLink, + DropdownTransition + }, + + props: { + item: { + required: true + } + }, + + data () { + return { + open: false + } + }, + + computed: { + dropdownAriaLabel () { + return this.item.ariaLabel || this.item.text + } + }, + + watch: { + $route () { + this.open = false + } + }, + + methods: { + setOpen (value) { + this.open = value + }, + + isLastItemOfArray (item, array) { + return last(array) === item + } + } +} +</script> + +<style lang="stylus"> +.dropdown-wrapper + cursor pointer + .dropdown-title + display block + font-size 0.9rem + font-family inherit + cursor inherit + padding inherit + line-height 1.4rem + background transparent + border none + font-weight 500 + color $textColor + &:hover + border-color transparent + .arrow + vertical-align middle + margin-top -1px + margin-left 0.4rem + .nav-dropdown + .dropdown-item + color inherit + line-height 1.7rem + h4 + margin 0.45rem 0 0 + border-top 1px solid #eee + padding 0.45rem 1.5rem 0 1.25rem + .dropdown-subitem-wrapper + padding 0 + list-style none + .dropdown-subitem + font-size 0.9em + a + display block + line-height 1.7rem + position relative + border-bottom none + font-weight 400 + margin-bottom 0 + padding 0 1.5rem 0 1.25rem + &:hover + color $accentColor + &.router-link-active + color $accentColor + &::after + content "" + width 0 + height 0 + border-left 5px solid $accentColor + border-top 3px solid transparent + border-bottom 3px solid transparent + position absolute + top calc(50% - 2px) + left 9px + &:first-child h4 + margin-top 0 + padding-top 0 + border-top 0 + +@media (max-width: $MQMobile) + .dropdown-wrapper + &.open .dropdown-title + margin-bottom 0.5rem + .dropdown-title + font-weight 600 + font-size inherit + &:hover + color $accentColor + .nav-dropdown + transition height .1s ease-out + overflow hidden + .dropdown-item + h4 + border-top 0 + margin-top 0 + padding-top 0 + h4, & > a + font-size 15px + line-height 2rem + .dropdown-subitem + font-size 14px + padding-left 1rem + +@media (min-width: $MQMobile) + .dropdown-wrapper + height 1.8rem + &:hover .nav-dropdown, + &.open .nav-dropdown + // override the inline style. + display block !important + &.open:blur + display none + .dropdown-title .arrow + // make the arrow always down at desktop + border-left 4px solid transparent + border-right 4px solid transparent + border-top 6px solid $arrowBgColor + border-bottom 0 + .nav-dropdown + display none + // Avoid height shaked by clicking + height auto !important + box-sizing border-box; + max-height calc(100vh - 2.7rem) + overflow-y auto + position absolute + top 100% + right 0 + background-color #fff + padding 0.6rem 0 + border 1px solid #ddd + border-bottom-color #ccc + text-align left + border-radius 0.25rem + white-space nowrap + margin 0 +</style> diff --git a/.vuepress/theme/components/DropdownTransition.vue b/.vuepress/theme/components/DropdownTransition.vue new file mode 100644 index 0000000..eeaf12b --- /dev/null +++ b/.vuepress/theme/components/DropdownTransition.vue @@ -0,0 +1,33 @@ +<template> + <transition + name="dropdown" + @enter="setHeight" + @after-enter="unsetHeight" + @before-leave="setHeight" + > + <slot /> + </transition> +</template> + +<script> +export default { + name: 'DropdownTransition', + + methods: { + setHeight (items) { + // explicitly set height so that it can be transitioned + items.style.height = items.scrollHeight + 'px' + }, + + unsetHeight (items) { + items.style.height = '' + } + } +} +</script> + +<style lang="stylus"> +.dropdown-enter, .dropdown-leave-to + height 0 !important + +</style> diff --git a/.vuepress/theme/components/Home.vue b/.vuepress/theme/components/Home.vue new file mode 100644 index 0000000..c11cba7 --- /dev/null +++ b/.vuepress/theme/components/Home.vue @@ -0,0 +1,184 @@ +<template> + <main + class="home" + aria-labelledby="main-title" + > + <header class="hero"> + <img + v-if="data.heroImage" + :src="$withBase(data.heroImage)" + :alt="data.heroAlt || 'hero'" + > + + <h1 + v-if="data.heroText !== null" + id="main-title" + > + {{ data.heroText || $title || 'Hello' }} + </h1> + + <p + v-if="data.tagline !== null" + class="description" + > + {{ data.tagline || $description || 'Welcome to your VuePress site' }} + </p> + + <p + v-if="data.actionText && data.actionLink" + class="action" + > + <NavLink + class="action-button" + :item="actionLink" + /> + </p> + </header> + + <div + v-if="data.features && data.features.length" + class="features" + > + <div + v-for="(feature, index) in data.features" + :key="index" + class="feature" + > + <h2>{{ feature.title }}</h2> + <p>{{ feature.details }}</p> + </div> + </div> + + <Content class="theme-default-content custom" /> + + <div class="footer"> + Apache Teaclave is an effort undergoing incubation at The Apache Software + Foundation (ASF), sponsored by the Apache Incubator. Incubation is required + of all newly accepted projects until a further review indicates that the + infrastructure, communications, and decision making process have stabilized + in a manner consistent with other successful ASF projects. While incubation + status is not necessarily a reflection of the completeness or stability of + the code, it does indicate that the project has yet to be fully endorsed by + the ASF. Copyright © 2020 The Apache Software Foundation. Apache Teaclave, + Apache, the Apache feather, and the Apache Teaclave project logo are either + trademarks or registered trademarks of the Apache Software Foundation. See + also other useful ASF links: Apache Homepage, License Sponsorship, Security + Thanks, Current Event + </div> + </main> +</template> + +<script> +import NavLink from '@theme/components/NavLink.vue' + +export default { + name: 'Home', + + components: { NavLink }, + + computed: { + data () { + return this.$page.frontmatter + }, + + actionLink () { + return { + link: this.data.actionLink, + text: this.data.actionText + } + } + } +} +</script> + +<style lang="stylus"> +.home + padding $navbarHeight 2rem 0 + max-width $homePageWidth + margin 0px auto + display block + .hero + text-align center + img + max-width: 100% + max-height 280px + display block + margin 3rem auto 1.5rem + h1 + font-size 3rem + h1, .description, .action + margin 1.8rem auto + .description + max-width 45rem + font-size 1.6rem + line-height 1.3 + color lighten($textColor, 40%) + .action-button + display inline-block + font-size 1.2rem + color #fff + background-color $accentColor + padding 0.8rem 1.6rem + border-radius 4px + transition background-color .1s ease + box-sizing border-box + border-bottom 1px solid darken($accentColor, 10%) + &:hover + background-color lighten($accentColor, 10%) + .features + border-top 1px solid $borderColor + padding 1.2rem 0 + margin-top 2.5rem + display flex + flex-wrap wrap + align-items flex-start + align-content stretch + justify-content space-between + .feature + flex-grow 1 + flex-basis 30% + max-width 30% + h2 + font-size 1.4rem + font-weight 500 + border-bottom none + padding-bottom 0 + color lighten($textColor, 10%) + p + color lighten($textColor, 25%) + .footer + font-size 0.7rem + padding 2.5rem + border-top 1px solid $borderColor + text-align center + color lighten($textColor, 25%) + +@media (max-width: $MQMobile) + .home + .features + flex-direction column + .feature + max-width 100% + padding 0 2.5rem + +@media (max-width: $MQMobileNarrow) + .home + padding-left 1.5rem + padding-right 1.5rem + .hero + img + max-height 210px + margin 2rem auto 1.2rem + h1 + font-size 2rem + h1, .description, .action + margin 1.2rem auto + .description + font-size 1.2rem + .action-button + font-size 1rem + padding 0.6rem 1.2rem + .feature + h2 + font-size 1.25rem +</style> diff --git a/.vuepress/theme/components/NavLink.vue b/.vuepress/theme/components/NavLink.vue new file mode 100644 index 0000000..4ccd13d --- /dev/null +++ b/.vuepress/theme/components/NavLink.vue @@ -0,0 +1,87 @@ +<template> + <RouterLink + v-if="isInternal" + class="nav-link" + :to="link" + :exact="exact" + @focusout.native="focusoutAction" + > + {{ item.text }} + </RouterLink> + <a + v-else + :href="link" + class="nav-link external" + :target="target" + :rel="rel" + @focusout="focusoutAction" + > + {{ item.text }} + <OutboundLink v-if="isBlankTarget" /> + </a> +</template> + +<script> +import { isExternal, isMailto, isTel, ensureExt } from '../util' + +export default { + name: 'NavLink', + + props: { + item: { + required: true + } + }, + + computed: { + link () { + return ensureExt(this.item.link) + }, + + exact () { + if (this.$site.locales) { + return Object.keys(this.$site.locales).some(rootLink => rootLink === this.link) + } + return this.link === '/' + }, + + isNonHttpURI () { + return isMailto(this.link) || isTel(this.link) + }, + + isBlankTarget () { + return this.target === '_blank' + }, + + isInternal () { + return !isExternal(this.link) && !this.isBlankTarget + }, + + target () { + if (this.isNonHttpURI) { + return null + } + if (this.item.target) { + return this.item.target + } + return isExternal(this.link) ? '_blank' : '' + }, + + rel () { + if (this.isNonHttpURI) { + return null + } + if (this.item.rel) { + return this.item.rel + } + return this.isBlankTarget ? 'noopener noreferrer' : '' + } + }, + + methods: { + focusoutAction () { + this.$emit('focusout') + } + } +} +</script> diff --git a/.vuepress/theme/components/NavLinks.vue b/.vuepress/theme/components/NavLinks.vue new file mode 100644 index 0000000..2656ae2 --- /dev/null +++ b/.vuepress/theme/components/NavLinks.vue @@ -0,0 +1,156 @@ +<template> + <nav + v-if="userLinks.length || repoLink" + class="nav-links" + > + <!-- user links --> + <div + v-for="item in userLinks" + :key="item.link" + class="nav-item" + > + <DropdownLink + v-if="item.type === 'links'" + :item="item" + /> + <NavLink + v-else + :item="item" + /> + </div> + + <!-- repo link --> + <a + v-if="repoLink" + :href="repoLink" + class="repo-link" + target="_blank" + rel="noopener noreferrer" + > + {{ repoLabel }} + <OutboundLink /> + </a> + </nav> +</template> + +<script> +import DropdownLink from '@theme/components/DropdownLink.vue' +import { resolveNavLinkItem } from '../util' +import NavLink from '@theme/components/NavLink.vue' + +export default { + name: 'NavLinks', + + components: { + NavLink, + DropdownLink + }, + + computed: { + userNav () { + return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || [] + }, + + nav () { + const { locales } = this.$site + if (locales && Object.keys(locales).length > 1) { + const currentLink = this.$page.path + const routes = this.$router.options.routes + const themeLocales = this.$site.themeConfig.locales || {} + const languageDropdown = { + text: this.$themeLocaleConfig.selectText || 'Languages', + ariaLabel: this.$themeLocaleConfig.ariaLabel || 'Select language', + items: Object.keys(locales).map(path => { + const locale = locales[path] + const text = themeLocales[path] && themeLocales[path].label || locale.lang + let link + // Stay on the current page + if (locale.lang === this.$lang) { + link = currentLink + } else { + // Try to stay on the same page + link = currentLink.replace(this.$localeConfig.path, path) + // fallback to homepage + if (!routes.some(route => route.path === link)) { + link = path + } + } + return { text, link } + }) + } + return [...this.userNav, languageDropdown] + } + return this.userNav + }, + + userLinks () { + return (this.nav || []).map(link => { + return Object.assign(resolveNavLinkItem(link), { + items: (link.items || []).map(resolveNavLinkItem) + }) + }) + }, + + repoLink () { + const { repo } = this.$site.themeConfig + if (repo) { + return /^https?:/.test(repo) + ? repo + : `https://github.com/${repo}` + } + return null + }, + + repoLabel () { + if (!this.repoLink) return + if (this.$site.themeConfig.repoLabel) { + return this.$site.themeConfig.repoLabel + } + + const repoHost = this.repoLink.match(/^https?:\/\/[^/]+/)[0] + const platforms = ['GitHub', 'GitLab', 'Bitbucket'] + for (let i = 0; i < platforms.length; i++) { + const platform = platforms[i] + if (new RegExp(platform, 'i').test(repoHost)) { + return platform + } + } + + return 'Source' + } + } +} +</script> + +<style lang="stylus"> +.nav-links + display inline-block + a + line-height 1.4rem + color inherit + &:hover, &.router-link-active + color $accentColor + .nav-item + position relative + display inline-block + margin-left 1.5rem + line-height 2rem + &:first-child + margin-left 0 + .repo-link + margin-left 1.5rem + +@media (max-width: $MQMobile) + .nav-links + .nav-item, .repo-link + margin-left 0 + +@media (min-width: $MQMobile) + .nav-links a + &:hover, &.router-link-active + color $textColor + .nav-item > a:not(.external) + &:hover, &.router-link-active + margin-bottom -2px + border-bottom 2px solid lighten($accentColor, 8%) +</style> diff --git a/.vuepress/theme/components/Navbar.vue b/.vuepress/theme/components/Navbar.vue new file mode 100644 index 0000000..f8dd49c --- /dev/null +++ b/.vuepress/theme/components/Navbar.vue @@ -0,0 +1,140 @@ +<template> + <header class="navbar"> + <SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" /> + + <RouterLink + :to="$localePath" + class="home-link" + > + <img + v-if="$site.themeConfig.logo" + class="logo" + :src="$withBase($site.themeConfig.logo)" + :alt="$siteTitle" + > + <span + v-if="$siteTitle" + ref="siteName" + class="site-name" + :class="{ 'can-hide': $site.themeConfig.logo }" + >{{ $siteTitle }}</span> + </RouterLink> + + <div + class="links" + :style="linksWrapMaxWidth ? { + 'max-width': linksWrapMaxWidth + 'px' + } : {}" + > + <AlgoliaSearchBox + v-if="isAlgoliaSearch" + :options="algolia" + /> + <SearchBox v-else-if="$site.themeConfig.search !== false && $page.frontmatter.search !== false" /> + <NavLinks class="can-hide" /> + </div> + </header> +</template> + +<script> +import AlgoliaSearchBox from '@AlgoliaSearchBox' +import SearchBox from '@SearchBox' +import SidebarButton from '@theme/components/SidebarButton.vue' +import NavLinks from '@theme/components/NavLinks.vue' + +export default { + name: 'Navbar', + + components: { + SidebarButton, + NavLinks, + SearchBox, + AlgoliaSearchBox + }, + + data () { + return { + linksWrapMaxWidth: null + } + }, + + computed: { + algolia () { + return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {} + }, + + isAlgoliaSearch () { + return this.algolia && this.algolia.apiKey && this.algolia.indexName + } + }, + + mounted () { + const MOBILE_DESKTOP_BREAKPOINT = 719 // refer to config.styl + const NAVBAR_VERTICAL_PADDING = parseInt(css(this.$el, 'paddingLeft')) + parseInt(css(this.$el, 'paddingRight')) + const handleLinksWrapWidth = () => { + if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) { + this.linksWrapMaxWidth = null + } else { + this.linksWrapMaxWidth = this.$el.offsetWidth - NAVBAR_VERTICAL_PADDING + - (this.$refs.siteName && this.$refs.siteName.offsetWidth || 0) + } + } + handleLinksWrapWidth() + window.addEventListener('resize', handleLinksWrapWidth, false) + } +} + +function css (el, property) { + // NOTE: Known bug, will return 'auto' if style value is 'auto' + const win = el.ownerDocument.defaultView + // null means not to return pseudo styles + return win.getComputedStyle(el, null)[property] +} +</script> + +<style lang="stylus"> +$navbar-vertical-padding = 0.7rem +$navbar-horizontal-padding = 1.5rem + +.navbar + padding $navbar-vertical-padding $navbar-horizontal-padding + line-height $navbarHeight - 1.4rem + a, span, img + display inline-block + .logo + height $navbarHeight - 1.4rem + min-width $navbarHeight - 1.4rem + margin-right 0.8rem + vertical-align top + .site-name + font-size 1.3rem + font-weight 600 + color $textColor + position relative + .links + padding-left 1.5rem + box-sizing border-box + background-color white + white-space nowrap + font-size 0.9rem + position absolute + right $navbar-horizontal-padding + top $navbar-vertical-padding + display flex + .search-box + flex: 0 0 auto + vertical-align top + +@media (max-width: $MQMobile) + .navbar + padding-left 4rem + .can-hide + display none + .links + padding-left 1.5rem + .site-name + width calc(100vw - 9.4rem) + overflow hidden + white-space nowrap + text-overflow ellipsis +</style> diff --git a/.vuepress/theme/components/Page.vue b/.vuepress/theme/components/Page.vue new file mode 100644 index 0000000..04ec7cb --- /dev/null +++ b/.vuepress/theme/components/Page.vue @@ -0,0 +1,31 @@ +<template> + <main class="page"> + <slot name="top" /> + + <Content class="theme-default-content" /> + <PageEdit /> + + <PageNav v-bind="{ sidebarItems }" /> + + <slot name="bottom" /> + </main> +</template> + +<script> +import PageEdit from '@theme/components/PageEdit.vue' +import PageNav from '@theme/components/PageNav.vue' + +export default { + components: { PageEdit, PageNav }, + props: ['sidebarItems'] +} +</script> + +<style lang="stylus"> +@require '../styles/wrapper.styl' + +.page + padding-bottom 2rem + display block + +</style> diff --git a/.vuepress/theme/components/PageEdit.vue b/.vuepress/theme/components/PageEdit.vue new file mode 100644 index 0000000..e1ac3ca --- /dev/null +++ b/.vuepress/theme/components/PageEdit.vue @@ -0,0 +1,143 @@ +<template> + <footer class="page-edit"> + <div + v-if="editLink" + class="edit-link" + > + <a + :href="editLink" + target="_blank" + rel="noopener noreferrer" + >{{ editLinkText }}</a> + <OutboundLink /> + </div> + + <div + v-if="lastUpdated" + class="last-updated" + > + <span class="prefix">{{ lastUpdatedText }}:</span> + <span class="time">{{ lastUpdated }}</span> + </div> + </footer> +</template> + +<script> +import isNil from 'lodash/isNil' +import { endingSlashRE, outboundRE } from '../util' + +export default { + name: 'PageEdit', + + computed: { + lastUpdated () { + return this.$page.lastUpdated + }, + + lastUpdatedText () { + if (typeof this.$themeLocaleConfig.lastUpdated === 'string') { + return this.$themeLocaleConfig.lastUpdated + } + if (typeof this.$site.themeConfig.lastUpdated === 'string') { + return this.$site.themeConfig.lastUpdated + } + return 'Last Updated' + }, + + editLink () { + const showEditLink = isNil(this.$page.frontmatter.editLink) + ? this.$site.themeConfig.editLinks + : this.$page.frontmatter.editLink + + const { + repo, + docsDir = '', + docsBranch = 'master', + docsRepo = repo + } = this.$site.themeConfig + + if (showEditLink && docsRepo && this.$page.relativePath) { + return this.createEditLink( + repo, + docsRepo, + docsDir, + docsBranch, + this.$page.relativePath + ) + } + return null + }, + + editLinkText () { + return ( + this.$themeLocaleConfig.editLinkText + || this.$site.themeConfig.editLinkText + || `Edit this page` + ) + } + }, + + methods: { + createEditLink (repo, docsRepo, docsDir, docsBranch, path) { + const bitbucket = /bitbucket.org/ + if (bitbucket.test(repo)) { + const base = outboundRE.test(docsRepo) ? docsRepo : repo + return ( + base.replace(endingSlashRE, '') + + `/src` + + `/${docsBranch}/` + + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '') + + path + + `?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default` + ) + } + + const base = outboundRE.test(docsRepo) + ? docsRepo + : `https://github.com/${docsRepo}` + return ( + base.replace(endingSlashRE, '') + + `/edit` + + `/${docsBranch}/` + + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '') + + path + ) + } + } +} +</script> + +<style lang="stylus"> +@require '../styles/wrapper.styl' + +.page-edit + @extend $wrapper + padding-top 1rem + padding-bottom 1rem + overflow auto + + .edit-link + display inline-block + a + color lighten($textColor, 25%) + margin-right 0.25rem + .last-updated + float right + font-size 0.9em + .prefix + font-weight 500 + color lighten($textColor, 25%) + .time + font-weight 400 + color #aaa + +@media (max-width: $MQMobile) + .page-edit + .edit-link + margin-bottom 0.5rem + .last-updated + font-size 0.8em + float none + text-align left + +</style> diff --git a/.vuepress/theme/components/PageNav.vue b/.vuepress/theme/components/PageNav.vue new file mode 100644 index 0000000..4c19aae --- /dev/null +++ b/.vuepress/theme/components/PageNav.vue @@ -0,0 +1,163 @@ +<template> + <div + v-if="prev || next" + class="page-nav" + > + <p class="inner"> + <span + v-if="prev" + class="prev" + > + ← + <a + v-if="prev.type === 'external'" + class="prev" + :href="prev.path" + target="_blank" + rel="noopener noreferrer" + > + {{ prev.title || prev.path }} + + <OutboundLink /> + </a> + + <RouterLink + v-else + class="prev" + :to="prev.path" + > + {{ prev.title || prev.path }} + </RouterLink> + </span> + + <span + v-if="next" + class="next" + > + <a + v-if="next.type === 'external'" + :href="next.path" + target="_blank" + rel="noopener noreferrer" + > + {{ next.title || next.path }} + + <OutboundLink /> + </a> + + <RouterLink + v-else + :to="next.path" + > + {{ next.title || next.path }} + </RouterLink> + → + </span> + </p> + </div> +</template> + +<script> +import { resolvePage } from '../util' +import isString from 'lodash/isString' +import isNil from 'lodash/isNil' + +export default { + name: 'PageNav', + + props: ['sidebarItems'], + + computed: { + prev () { + return resolvePageLink(LINK_TYPES.PREV, this) + }, + + next () { + return resolvePageLink(LINK_TYPES.NEXT, this) + } + } +} + +function resolvePrev (page, items) { + return find(page, items, -1) +} + +function resolveNext (page, items) { + return find(page, items, 1) +} + +const LINK_TYPES = { + NEXT: { + resolveLink: resolveNext, + getThemeLinkConfig: ({ nextLinks }) => nextLinks, + getPageLinkConfig: ({ frontmatter }) => frontmatter.next + }, + PREV: { + resolveLink: resolvePrev, + getThemeLinkConfig: ({ prevLinks }) => prevLinks, + getPageLinkConfig: ({ frontmatter }) => frontmatter.prev + } +} + +function resolvePageLink ( + linkType, + { $themeConfig, $page, $route, $site, sidebarItems } +) { + const { resolveLink, getThemeLinkConfig, getPageLinkConfig } = linkType + + // Get link config from theme + const themeLinkConfig = getThemeLinkConfig($themeConfig) + + // Get link config from current page + const pageLinkConfig = getPageLinkConfig($page) + + // Page link config will overwrite global theme link config if defined + const link = isNil(pageLinkConfig) ? themeLinkConfig : pageLinkConfig + + if (link === false) { + return + } else if (isString(link)) { + return resolvePage($site.pages, link, $route.path) + } else { + return resolveLink($page, sidebarItems) + } +} + +function find (page, items, offset) { + const res = [] + flatten(items, res) + for (let i = 0; i < res.length; i++) { + const cur = res[i] + if (cur.type === 'page' && cur.path === decodeURIComponent(page.path)) { + return res[i + offset] + } + } +} + +function flatten (items, res) { + for (let i = 0, l = items.length; i < l; i++) { + if (items[i].type === 'group') { + flatten(items[i].children || [], res) + } else { + res.push(items[i]) + } + } +} +</script> + +<style lang="stylus"> +@require '../styles/wrapper.styl' + +.page-nav + @extend $wrapper + padding-top 1rem + padding-bottom 0 + .inner + min-height 2rem + margin-top 0 + border-top 1px solid $borderColor + padding-top 1rem + overflow auto // clear float + .next + float right +</style> diff --git a/.vuepress/theme/components/Sidebar.vue b/.vuepress/theme/components/Sidebar.vue new file mode 100644 index 0000000..e70e333 --- /dev/null +++ b/.vuepress/theme/components/Sidebar.vue @@ -0,0 +1,64 @@ +<template> + <aside class="sidebar"> + <NavLinks /> + + <slot name="top" /> + + <SidebarLinks + :depth="0" + :items="items" + /> + <slot name="bottom" /> + </aside> +</template> + +<script> +import SidebarLinks from '@theme/components/SidebarLinks.vue' +import NavLinks from '@theme/components/NavLinks.vue' + +export default { + name: 'Sidebar', + + components: { SidebarLinks, NavLinks }, + + props: ['items'] +} +</script> + +<style lang="stylus"> +.sidebar + ul + padding 0 + margin 0 + list-style-type none + a + display inline-block + .nav-links + display none + border-bottom 1px solid $borderColor + padding 0.5rem 0 0.75rem 0 + a + font-weight 600 + .nav-item, .repo-link + display block + line-height 1.25rem + font-size 1.1em + padding 0.5rem 0 0.5rem 1.5rem + & > .sidebar-links + padding 1.5rem 0 + & > li > a.sidebar-link + font-size 1.1em + line-height 1.7 + font-weight bold + & > li:not(:first-child) + margin-top .75rem + +@media (max-width: $MQMobile) + .sidebar + .nav-links + display block + .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after + top calc(1rem - 2px) + & > .sidebar-links + padding 1rem 0 +</style> diff --git a/.vuepress/theme/components/SidebarButton.vue b/.vuepress/theme/components/SidebarButton.vue new file mode 100644 index 0000000..3f54afd --- /dev/null +++ b/.vuepress/theme/components/SidebarButton.vue @@ -0,0 +1,40 @@ +<template> + <div + class="sidebar-button" + @click="$emit('toggle-sidebar')" + > + <svg + class="icon" + xmlns="http://www.w3.org/2000/svg" + aria-hidden="true" + role="img" + viewBox="0 0 448 512" + > + <path + fill="currentColor" + 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" + class="" + /> + </svg> + </div> +</template> + +<style lang="stylus"> +.sidebar-button + cursor pointer + display none + width 1.25rem + height 1.25rem + position absolute + padding 0.6rem + top 0.6rem + left 1rem + .icon + display block + width 1.25rem + height 1.25rem + +@media (max-width: $MQMobile) + .sidebar-button + display block +</style> diff --git a/.vuepress/theme/components/SidebarGroup.vue b/.vuepress/theme/components/SidebarGroup.vue new file mode 100644 index 0000000..23f8a61 --- /dev/null +++ b/.vuepress/theme/components/SidebarGroup.vue @@ -0,0 +1,140 @@ +<template> + <section + class="sidebar-group" + :class="[ + { + collapsable, + 'is-sub-group': depth !== 0 + }, + `depth-${depth}` + ]" + > + <RouterLink + v-if="item.path" + class="sidebar-heading clickable" + :class="{ + open, + 'active': isActive($route, item.path) + }" + :to="item.path" + @click.native="$emit('toggle')" + > + <span>{{ item.title }}</span> + <span + v-if="collapsable" + class="arrow" + :class="open ? 'down' : 'right'" + /> + </RouterLink> + + <p + v-else + class="sidebar-heading" + :class="{ open }" + @click="$emit('toggle')" + > + <span>{{ item.title }}</span> + <span + v-if="collapsable" + class="arrow" + :class="open ? 'down' : 'right'" + /> + </p> + + <DropdownTransition> + <SidebarLinks + v-if="open || !collapsable" + class="sidebar-group-items" + :items="item.children" + :sidebar-depth="item.sidebarDepth" + :depth="depth + 1" + /> + </DropdownTransition> + </section> +</template> + +<script> +import { isActive } from '../util' +import DropdownTransition from '@theme/components/DropdownTransition.vue' + +export default { + name: 'SidebarGroup', + + components: { + DropdownTransition + }, + + props: [ + 'item', + 'open', + 'collapsable', + 'depth' + ], + + // ref: https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components + beforeCreate () { + this.$options.components.SidebarLinks = require('@theme/components/SidebarLinks.vue').default + }, + + methods: { isActive } +} +</script> + +<style lang="stylus"> +.sidebar-group + .sidebar-group + padding-left 0.5em + &:not(.collapsable) + .sidebar-heading:not(.clickable) + cursor auto + color inherit + // refine styles of nested sidebar groups + &.is-sub-group + padding-left 0 + & > .sidebar-heading + font-size 0.95em + line-height 1.4 + font-weight normal + padding-left 2rem + &:not(.clickable) + opacity 0.5 + & > .sidebar-group-items + padding-left 1rem + & > li > .sidebar-link + font-size: 0.95em; + border-left none + &.depth-2 + & > .sidebar-heading + border-left none + +.sidebar-heading + color $textColor + transition color .15s ease + cursor pointer + font-size 1.1em + font-weight bold + // text-transform uppercase + padding 0.35rem 1.5rem 0.35rem 1.25rem + width 100% + box-sizing border-box + margin 0 + border-left 0.25rem solid transparent + &.open, &:hover + color inherit + .arrow + position relative + top -0.12em + left 0.5em + &.clickable + &.active + font-weight 600 + color $accentColor + border-left-color $accentColor + &:hover + color $accentColor + +.sidebar-group-items + transition height .1s ease-out + font-size 0.95em + overflow hidden +</style> diff --git a/.vuepress/theme/components/SidebarLink.vue b/.vuepress/theme/components/SidebarLink.vue new file mode 100644 index 0000000..4cd7665 --- /dev/null +++ b/.vuepress/theme/components/SidebarLink.vue @@ -0,0 +1,133 @@ +<script> +import { isActive, hashRE, groupHeaders } from '../util' + +export default { + functional: true, + + props: ['item', 'sidebarDepth'], + + render (h, + { + parent: { + $page, + $site, + $route, + $themeConfig, + $themeLocaleConfig + }, + props: { + item, + sidebarDepth + } + }) { + // use custom active class matching logic + // due to edge case of paths ending with / + hash + const selfActive = isActive($route, item.path) + // for sidebar: auto pages, a hash link should be active if one of its child + // matches + const active = item.type === 'auto' + ? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug)) + : selfActive + const link = item.type === 'external' + ? renderExternal(h, item.path, item.title || item.path) + : renderLink(h, item.path, item.title || item.path, active) + + const maxDepth = [ + $page.frontmatter.sidebarDepth, + sidebarDepth, + $themeLocaleConfig.sidebarDepth, + $themeConfig.sidebarDepth, + 1 + ].find(depth => depth !== undefined) + + const displayAllHeaders = $themeLocaleConfig.displayAllHeaders + || $themeConfig.displayAllHeaders + + if (item.type === 'auto') { + return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)] + } else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) { + const children = groupHeaders(item.headers) + return [link, renderChildren(h, children, item.path, $route, maxDepth)] + } else { + return link + } + } +} + +function renderLink (h, to, text, active, level) { + const component = { + props: { + to, + activeClass: '', + exactActiveClass: '' + }, + class: { + active, + 'sidebar-link': true + } + } + + if (level > 2) { + component.style = { + 'padding-left': level + 'rem' + } + } + + return h('RouterLink', component, text) +} + +function renderChildren (h, children, path, route, maxDepth, depth = 1) { + if (!children || depth > maxDepth) return null + return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => { + const active = isActive(route, path + '#' + c.slug) + return h('li', { class: 'sidebar-sub-header' }, [ + renderLink(h, path + '#' + c.slug, c.title, active, c.level - 1), + renderChildren(h, c.children, path, route, maxDepth, depth + 1) + ]) + })) +} + +function renderExternal (h, to, text) { + return h('a', { + attrs: { + href: to, + target: '_blank', + rel: 'noopener noreferrer' + }, + class: { + 'sidebar-link': true + } + }, [text, h('OutboundLink')]) +} +</script> + +<style lang="stylus"> +.sidebar .sidebar-sub-headers + padding-left 1rem + font-size 0.95em + +a.sidebar-link + font-size 1em + font-weight 400 + display inline-block + color $textColor + border-left 0.25rem solid transparent + padding 0.35rem 1rem 0.35rem 1.25rem + line-height 1.4 + width: 100% + box-sizing: border-box + &:hover + color $accentColor + &.active + font-weight 600 + color $accentColor + border-left-color $accentColor + .sidebar-group & + padding-left 2rem + .sidebar-sub-headers & + padding-top 0.25rem + padding-bottom 0.25rem + border-left none + &.active + font-weight 500 +</style> diff --git a/.vuepress/theme/components/SidebarLinks.vue b/.vuepress/theme/components/SidebarLinks.vue new file mode 100644 index 0000000..7adf461 --- /dev/null +++ b/.vuepress/theme/components/SidebarLinks.vue @@ -0,0 +1,102 @@ +<template> + <ul + v-if="items.length" + class="sidebar-links" + > + <li + v-for="(item, i) in items" + :key="i" + > + <SidebarGroup + v-if="item.type === 'group'" + :item="item" + :open="i === openGroupIndex" + :collapsable="item.collapsable || item.collapsible" + :depth="depth" + @toggle="toggleGroup(i)" + /> + <SidebarLink + v-else + :sidebar-depth="sidebarDepth" + :item="item" + /> + </li> + </ul> +</template> + +<script> +import SidebarGroup from '@theme/components/SidebarGroup.vue' +import SidebarLink from '@theme/components/SidebarLink.vue' +import { isActive } from '../util' + +export default { + name: 'SidebarLinks', + + components: { SidebarGroup, SidebarLink }, + + props: [ + 'items', + 'depth', // depth of current sidebar links + 'sidebarDepth' // depth of headers to be extracted + ], + + data () { + return { + openGroupIndex: 0 + } + }, + + watch: { + '$route' () { + this.refreshIndex() + } + }, + + created () { + this.refreshIndex() + }, + + methods: { + refreshIndex () { + const index = resolveOpenGroupIndex( + this.$route, + this.items + ) + if (index > -1) { + this.openGroupIndex = index + } + }, + + toggleGroup (index) { + this.openGroupIndex = index === this.openGroupIndex ? -1 : index + }, + + isActive (page) { + return isActive(this.$route, page.regularPath) + } + } +} + +function resolveOpenGroupIndex (route, items) { + for (let i = 0; i < items.length; i++) { + const item = items[i] + if (descendantIsActive(route, item)) { + return i + } + } + return -1 +} + +function descendantIsActive (route, item) { + if (item.type === 'group') { + return item.children.some(child => { + if (child.type === 'group') { + return descendantIsActive(route, child) + } else { + return child.type === 'page' && isActive(route, child.path) + } + }) + } + return false +} +</script> diff --git a/.vuepress/theme/global-components/Badge.vue b/.vuepress/theme/global-components/Badge.vue new file mode 100644 index 0000000..53951f9 --- /dev/null +++ b/.vuepress/theme/global-components/Badge.vue @@ -0,0 +1,44 @@ +<script> +export default { + functional: true, + props: { + type: { + type: String, + default: 'tip' + }, + text: String, + vertical: { + type: String, + default: 'top' + } + }, + render (h, { props, slots }) { + return h('span', { + class: ['badge', props.type], + style: { + verticalAlign: props.vertical + } + }, props.text || slots().default) + } +} +</script> + +<style lang="stylus" scoped> +.badge + display inline-block + font-size 14px + height 18px + line-height 18px + border-radius 3px + padding 0 6px + color white + background-color #42b983 + &.tip, &.green + background-color $badgeTipColor + &.error + background-color $badgeErrorColor + &.warning, &.warn, &.yellow + background-color $badgeWarningColor + & + & + margin-left 5px +</style> diff --git a/.vuepress/theme/index.js b/.vuepress/theme/index.js new file mode 100644 index 0000000..baaf102 --- /dev/null +++ b/.vuepress/theme/index.js @@ -0,0 +1,59 @@ +const path = require('path') + +// Theme API. +module.exports = (options, ctx) => { + const { themeConfig, siteConfig } = ctx + + // resolve algolia + const isAlgoliaSearch = ( + themeConfig.algolia + || Object + .keys(siteConfig.locales && themeConfig.locales || {}) + .some(base => themeConfig.locales[base].algolia) + ) + + const enableSmoothScroll = themeConfig.smoothScroll === true + + return { + alias () { + return { + '@AlgoliaSearchBox': isAlgoliaSearch + ? path.resolve(__dirname, 'components/AlgoliaSearchBox.vue') + : path.resolve(__dirname, 'noopModule.js') + } + }, + + plugins: [ + ['@vuepress/active-header-links', options.activeHeaderLinks], + '@vuepress/search', + '@vuepress/plugin-nprogress', + ['container', { + type: 'tip', + defaultTitle: { + '/': 'TIP', + '/zh/': '提示' + } + }], + ['container', { + type: 'warning', + defaultTitle: { + '/': 'WARNING', + '/zh/': '注意' + } + }], + ['container', { + type: 'danger', + defaultTitle: { + '/': 'WARNING', + '/zh/': '警告' + } + }], + ['container', { + type: 'details', + before: info => `<details class="custom-block details">${info ? `<summary>${info}</summary>` : ''}\n`, + after: () => '</details>\n' + }], + ['smooth-scroll', enableSmoothScroll] + ] + } +} diff --git a/.vuepress/theme/layouts/404.vue b/.vuepress/theme/layouts/404.vue new file mode 100644 index 0000000..2cbfa0f --- /dev/null +++ b/.vuepress/theme/layouts/404.vue @@ -0,0 +1,30 @@ +<template> + <div class="theme-container"> + <div class="theme-default-content"> + <h1>404</h1> + + <blockquote>{{ getMsg() }}</blockquote> + + <RouterLink to="/"> + Take me home. + </RouterLink> + </div> + </div> +</template> + +<script> +const msgs = [ + `There's nothing here.`, + `How did we get here?`, + `That's a Four-Oh-Four.`, + `Looks like we've got some broken links.` +] + +export default { + methods: { + getMsg () { + return msgs[Math.floor(Math.random() * msgs.length)] + } + } +} +</script> diff --git a/.vuepress/theme/layouts/Layout.vue b/.vuepress/theme/layouts/Layout.vue new file mode 100644 index 0000000..3298070 --- /dev/null +++ b/.vuepress/theme/layouts/Layout.vue @@ -0,0 +1,151 @@ +<template> + <div + class="theme-container" + :class="pageClasses" + @touchstart="onTouchStart" + @touchend="onTouchEnd" + > + <Navbar + v-if="shouldShowNavbar" + @toggle-sidebar="toggleSidebar" + /> + + <div + class="sidebar-mask" + @click="toggleSidebar(false)" + /> + + <Sidebar + :items="sidebarItems" + @toggle-sidebar="toggleSidebar" + > + <template #top> + <slot name="sidebar-top" /> + </template> + <template #bottom> + <slot name="sidebar-bottom" /> + </template> + </Sidebar> + + <Home v-if="$page.frontmatter.home" /> + + <Page + v-else + :sidebar-items="sidebarItems" + > + <template #top> + <slot name="page-top" /> + </template> + <template #bottom> + <slot name="page-bottom" /> + </template> + </Page> + </div> +</template> + +<script> +import Home from '@theme/components/Home.vue' +import Navbar from '@theme/components/Navbar.vue' +import Page from '@theme/components/Page.vue' +import Sidebar from '@theme/components/Sidebar.vue' +import { resolveSidebarItems } from '../util' + +export default { + name: 'Layout', + + components: { + Home, + Page, + Sidebar, + Navbar + }, + + data () { + return { + isSidebarOpen: false + } + }, + + computed: { + shouldShowNavbar () { + const { themeConfig } = this.$site + const { frontmatter } = this.$page + if ( + frontmatter.navbar === false + || themeConfig.navbar === false) { + return false + } + return ( + this.$title + || themeConfig.logo + || themeConfig.repo + || themeConfig.nav + || this.$themeLocaleConfig.nav + ) + }, + + shouldShowSidebar () { + const { frontmatter } = this.$page + return ( + !frontmatter.home + && frontmatter.sidebar !== false + && this.sidebarItems.length + ) + }, + + sidebarItems () { + return resolveSidebarItems( + this.$page, + this.$page.regularPath, + this.$site, + this.$localePath + ) + }, + + pageClasses () { + const userPageClass = this.$page.frontmatter.pageClass + return [ + { + 'no-navbar': !this.shouldShowNavbar, + 'sidebar-open': this.isSidebarOpen, + 'no-sidebar': !this.shouldShowSidebar + }, + userPageClass + ] + } + }, + + mounted () { + this.$router.afterEach(() => { + this.isSidebarOpen = false + }) + }, + + methods: { + toggleSidebar (to) { + this.isSidebarOpen = typeof to === 'boolean' ? to : !this.isSidebarOpen + this.$emit('toggle-sidebar', this.isSidebarOpen) + }, + + // side swipe + onTouchStart (e) { + this.touchStart = { + x: e.changedTouches[0].clientX, + y: e.changedTouches[0].clientY + } + }, + + onTouchEnd (e) { + const dx = e.changedTouches[0].clientX - this.touchStart.x + const dy = e.changedTouches[0].clientY - this.touchStart.y + if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) { + if (dx > 0 && this.touchStart.x <= 80) { + this.toggleSidebar(true) + } else { + this.toggleSidebar(false) + } + } + } + } +} +</script> diff --git a/.vuepress/theme/noopModule.js b/.vuepress/theme/noopModule.js new file mode 100644 index 0000000..b1c6ea4 --- /dev/null +++ b/.vuepress/theme/noopModule.js @@ -0,0 +1 @@ +export default {} diff --git a/.vuepress/theme/styles/arrow.styl b/.vuepress/theme/styles/arrow.styl new file mode 100644 index 0000000..20bffc0 --- /dev/null +++ b/.vuepress/theme/styles/arrow.styl @@ -0,0 +1,22 @@ +@require './config' + +.arrow + display inline-block + width 0 + height 0 + &.up + border-left 4px solid transparent + border-right 4px solid transparent + border-bottom 6px solid $arrowBgColor + &.down + border-left 4px solid transparent + border-right 4px solid transparent + border-top 6px solid $arrowBgColor + &.right + border-top 4px solid transparent + border-bottom 4px solid transparent + border-left 6px solid $arrowBgColor + &.left + border-top 4px solid transparent + border-bottom 4px solid transparent + border-right 6px solid $arrowBgColor diff --git a/.vuepress/theme/styles/code.styl b/.vuepress/theme/styles/code.styl new file mode 100644 index 0000000..9d3aa9a --- /dev/null +++ b/.vuepress/theme/styles/code.styl @@ -0,0 +1,137 @@ +{$contentClass} + code + color lighten($textColor, 20%) + padding 0.25rem 0.5rem + margin 0 + font-size 0.85em + background-color rgba(27,31,35,0.05) + border-radius 3px + .token + &.deleted + color #EC5975 + &.inserted + color $accentColor + +{$contentClass} + pre, pre[class*="language-"] + line-height 1.4 + padding 1.25rem 1.5rem + margin 0.85rem 0 + background-color $codeBgColor + border-radius 6px + overflow auto + code + color #fff + padding 0 + background-color transparent + border-radius 0 + +div[class*="language-"] + position relative + background-color $codeBgColor + border-radius 6px + .highlight-lines + user-select none + padding-top 1.3rem + position absolute + top 0 + left 0 + width 100% + line-height 1.4 + .highlighted + background-color rgba(0, 0, 0, 66%) + pre, pre[class*="language-"] + background transparent + position relative + z-index 1 + &::before + position absolute + z-index 3 + top 0.8em + right 1em + font-size 0.75rem + color rgba(255, 255, 255, 0.4) + &:not(.line-numbers-mode) + .line-numbers-wrapper + display none + &.line-numbers-mode + .highlight-lines .highlighted + position relative + &:before + content ' ' + position absolute + z-index 3 + left 0 + top 0 + display block + width $lineNumbersWrapperWidth + height 100% + background-color rgba(0, 0, 0, 66%) + pre + padding-left $lineNumbersWrapperWidth + 1 rem + vertical-align middle + .line-numbers-wrapper + position absolute + top 0 + width $lineNumbersWrapperWidth + text-align center + color rgba(255, 255, 255, 0.3) + padding 1.25rem 0 + line-height 1.4 + br + user-select none + .line-number + position relative + z-index 4 + user-select none + font-size 0.85em + &::after + content '' + position absolute + z-index 2 + top 0 + left 0 + width $lineNumbersWrapperWidth + height 100% + border-radius 6px 0 0 6px + border-right 1px solid rgba(0, 0, 0, 66%) + background-color $codeBgColor + + +for lang in $codeLang + div{'[class~="language-' + lang + '"]'} + &:before + content ('' + lang) + +div[class~="language-javascript"] + &:before + content "js" + +div[class~="language-typescript"] + &:before + content "ts" + +div[class~="language-markup"] + &:before + content "html" + +div[class~="language-markdown"] + &:before + content "md" + +div[class~="language-json"]:before + content "json" + +div[class~="language-ruby"]:before + content "rb" + +div[class~="language-python"]:before + content "py" + +div[class~="language-bash"]:before + content "sh" + +div[class~="language-php"]:before + content "php" + +@import '~prismjs/themes/prism-tomorrow.css' diff --git a/.vuepress/theme/styles/config.styl b/.vuepress/theme/styles/config.styl new file mode 100644 index 0000000..9e40321 --- /dev/null +++ b/.vuepress/theme/styles/config.styl @@ -0,0 +1 @@ +$contentClass = '.theme-default-content' diff --git a/.vuepress/theme/styles/custom-blocks.styl b/.vuepress/theme/styles/custom-blocks.styl new file mode 100644 index 0000000..5b86816 --- /dev/null +++ b/.vuepress/theme/styles/custom-blocks.styl @@ -0,0 +1,44 @@ +.custom-block + .custom-block-title + font-weight 600 + margin-bottom -0.4rem + &.tip, &.warning, &.danger + padding .1rem 1.5rem + border-left-width .5rem + border-left-style solid + margin 1rem 0 + &.tip + background-color #f3f5f7 + border-color #42b983 + &.warning + background-color rgba(255,229,100,.3) + border-color darken(#ffe564, 35%) + color darken(#ffe564, 70%) + .custom-block-title + color darken(#ffe564, 50%) + a + color $textColor + &.danger + background-color #ffe6e6 + border-color darken(red, 20%) + color darken(red, 70%) + .custom-block-title + color darken(red, 40%) + a + color $textColor + &.details + display block + position relative + border-radius 2px + margin 1.6em 0 + padding 1.6em + background-color #eee + h4 + margin-top 0 + figure, p + &:last-child + margin-bottom 0 + padding-bottom 0 + summary + outline none + cursor pointer diff --git a/.vuepress/theme/styles/index.styl b/.vuepress/theme/styles/index.styl new file mode 100644 index 0000000..976bfb0 --- /dev/null +++ b/.vuepress/theme/styles/index.styl @@ -0,0 +1,201 @@ +@require './config' +@require './code' +@require './custom-blocks' +@require './arrow' +@require './wrapper' +@require './toc' + +html, body + padding 0 + margin 0 + background-color #fff + +body + font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif + -webkit-font-smoothing antialiased + -moz-osx-font-smoothing grayscale + font-size 16px + color $textColor + +.page + padding-left $sidebarWidth + +.navbar + position fixed + z-index 20 + top 0 + left 0 + right 0 + height $navbarHeight + background-color #fff + box-sizing border-box + border-bottom 1px solid $borderColor + +.sidebar-mask + position fixed + z-index 9 + top 0 + left 0 + width 100vw + height 100vh + display none + +.sidebar + font-size 16px + background-color #fff + width $sidebarWidth + position fixed + z-index 10 + margin 0 + top $navbarHeight + left 0 + bottom 0 + box-sizing border-box + border-right 1px solid $borderColor + overflow-y auto + +{$contentClass}:not(.custom) + @extend $wrapper + > *:first-child + margin-top $navbarHeight + + a:hover + text-decoration underline + + p.demo + padding 1rem 1.5rem + border 1px solid #ddd + border-radius 4px + + img + max-width 100% + +{$contentClass}.custom + padding 0 + margin 0 + + img + max-width 100% + +a + font-weight 500 + color $accentColor + text-decoration none + +p a code + font-weight 400 + color $accentColor + +kbd + background #eee + border solid 0.15rem #ddd + border-bottom solid 0.25rem #ddd + border-radius 0.15rem + padding 0 0.15em + +blockquote + font-size 1rem + color #999; + border-left .2rem solid #dfe2e5 + margin 1rem 0 + padding .25rem 0 .25rem 1rem + + & > p + margin 0 + +ul, ol + padding-left 1.2em + +strong + font-weight 600 + +h1, h2, h3, h4, h5, h6 + font-weight 600 + line-height 1.25 + + {$contentClass}:not(.custom) > & + margin-top (0.5rem - $navbarHeight) + padding-top ($navbarHeight + 1rem) + margin-bottom 0 + + &:first-child + margin-top -1.5rem + margin-bottom 1rem + + + p, + pre, + .custom-block + margin-top 2rem + + &:hover .header-anchor + opacity: 1 + +h1 + font-size 2.2rem + +h2 + font-size 1.65rem + padding-bottom .3rem + border-bottom 1px solid $borderColor + +h3 + font-size 1.35rem + +a.header-anchor + font-size 0.85em + float left + margin-left -0.87em + padding-right 0.23em + margin-top 0.125em + opacity 0 + + &:hover + text-decoration none + +code, kbd, .line-number + font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace + +p, ul, ol + line-height 1.7 + +hr + border 0 + border-top 1px solid $borderColor + +table + border-collapse collapse + margin 1rem 0 + display: block + overflow-x: auto + +tr + border-top 1px solid #dfe2e5 + + &:nth-child(2n) + background-color #f6f8fa + +th, td + border 1px solid #dfe2e5 + padding .6em 1em + +.theme-container + &.sidebar-open + .sidebar-mask + display: block + + &.no-navbar + {$contentClass}:not(.custom) > h1, h2, h3, h4, h5, h6 + margin-top 1.5rem + padding-top 0 + + .sidebar + top 0 + + +@media (min-width: ($MQMobile + 1px)) + .theme-container.no-sidebar + .sidebar + display none + + .page + padding-left 0 + +@require 'mobile.styl' diff --git a/.vuepress/theme/styles/mobile.styl b/.vuepress/theme/styles/mobile.styl new file mode 100644 index 0000000..f5bd327 --- /dev/null +++ b/.vuepress/theme/styles/mobile.styl @@ -0,0 +1,37 @@ +@require './config' + +$mobileSidebarWidth = $sidebarWidth * 0.82 + +// narrow desktop / iPad +@media (max-width: $MQNarrow) + .sidebar + font-size 15px + width $mobileSidebarWidth + .page + padding-left $mobileSidebarWidth + +// wide mobile +@media (max-width: $MQMobile) + .sidebar + top 0 + padding-top $navbarHeight + transform translateX(-100%) + transition transform .2s ease + .page + padding-left 0 + .theme-container + &.sidebar-open + .sidebar + transform translateX(0) + &.no-navbar + .sidebar + padding-top: 0 + +// narrow mobile +@media (max-width: $MQMobileNarrow) + h1 + font-size 1.9rem + {$contentClass} + div[class*="language-"] + margin 0.85rem -1.5rem + border-radius 0 diff --git a/.vuepress/theme/styles/toc.styl b/.vuepress/theme/styles/toc.styl new file mode 100644 index 0000000..d3e7106 --- /dev/null +++ b/.vuepress/theme/styles/toc.styl @@ -0,0 +1,3 @@ +.table-of-contents + .badge + vertical-align middle diff --git a/.vuepress/theme/styles/wrapper.styl b/.vuepress/theme/styles/wrapper.styl new file mode 100644 index 0000000..a99262c --- /dev/null +++ b/.vuepress/theme/styles/wrapper.styl @@ -0,0 +1,9 @@ +$wrapper + max-width $contentWidth + margin 0 auto + padding 2rem 2.5rem + @media (max-width: $MQNarrow) + padding 2rem + @media (max-width: $MQMobileNarrow) + padding 1.5rem + diff --git a/.vuepress/theme/util/index.js b/.vuepress/theme/util/index.js new file mode 100644 index 0000000..23e78f8 --- /dev/null +++ b/.vuepress/theme/util/index.js @@ -0,0 +1,240 @@ +export const hashRE = /#.*$/ +export const extRE = /\.(md|html)$/ +export const endingSlashRE = /\/$/ +export const outboundRE = /^[a-z]+:/i + +export function normalize (path) { + return decodeURI(path) + .replace(hashRE, '') + .replace(extRE, '') +} + +export function getHash (path) { + const match = path.match(hashRE) + if (match) { + return match[0] + } +} + +export function isExternal (path) { + return outboundRE.test(path) +} + +export function isMailto (path) { + return /^mailto:/.test(path) +} + +export function isTel (path) { + return /^tel:/.test(path) +} + +export function ensureExt (path) { + if (isExternal(path)) { + return path + } + const hashMatch = path.match(hashRE) + const hash = hashMatch ? hashMatch[0] : '' + const normalized = normalize(path) + + if (endingSlashRE.test(normalized)) { + return path + } + return normalized + '.html' + hash +} + +export function isActive (route, path) { + const routeHash = decodeURIComponent(route.hash) + const linkHash = getHash(path) + if (linkHash && routeHash !== linkHash) { + return false + } + const routePath = normalize(route.path) + const pagePath = normalize(path) + return routePath === pagePath +} + +export function resolvePage (pages, rawPath, base) { + if (isExternal(rawPath)) { + return { + type: 'external', + path: rawPath + } + } + if (base) { + rawPath = resolvePath(rawPath, base) + } + const path = normalize(rawPath) + for (let i = 0; i < pages.length; i++) { + if (normalize(pages[i].regularPath) === path) { + return Object.assign({}, pages[i], { + type: 'page', + path: ensureExt(pages[i].path) + }) + } + } + console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`) + return {} +} + +function resolvePath (relative, base, append) { + const firstChar = relative.charAt(0) + if (firstChar === '/') { + return relative + } + + if (firstChar === '?' || firstChar === '#') { + return base + relative + } + + const stack = base.split('/') + + // remove trailing segment if: + // - not appending + // - appending to trailing slash (last segment is empty) + if (!append || !stack[stack.length - 1]) { + stack.pop() + } + + // resolve relative path + const segments = relative.replace(/^\//, '').split('/') + for (let i = 0; i < segments.length; i++) { + const segment = segments[i] + if (segment === '..') { + stack.pop() + } else if (segment !== '.') { + stack.push(segment) + } + } + + // ensure leading slash + if (stack[0] !== '') { + stack.unshift('') + } + + return stack.join('/') +} + +/** + * @param { Page } page + * @param { string } regularPath + * @param { SiteData } site + * @param { string } localePath + * @returns { SidebarGroup } + */ +export function resolveSidebarItems (page, regularPath, site, localePath) { + const { pages, themeConfig } = site + + const localeConfig = localePath && themeConfig.locales + ? themeConfig.locales[localePath] || themeConfig + : themeConfig + + const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar + if (pageSidebarConfig === 'auto') { + return resolveHeaders(page) + } + + const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar + if (!sidebarConfig) { + return [] + } else { + const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig) + return config + ? config.map(item => resolveItem(item, pages, base)) + : [] + } +} + +/** + * @param { Page } page + * @returns { SidebarGroup } + */ +function resolveHeaders (page) { + const headers = groupHeaders(page.headers || []) + return [{ + type: 'group', + collapsable: false, + title: page.title, + path: null, + children: headers.map(h => ({ + type: 'auto', + title: h.title, + basePath: page.path, + path: page.path + '#' + h.slug, + children: h.children || [] + })) + }] +} + +export function groupHeaders (headers) { + // group h3s under h2 + headers = headers.map(h => Object.assign({}, h)) + let lastH2 + headers.forEach(h => { + if (h.level === 2) { + lastH2 = h + } else if (lastH2) { + (lastH2.children || (lastH2.children = [])).push(h) + } + }) + return headers.filter(h => h.level === 2) +} + +export function resolveNavLinkItem (linkItem) { + return Object.assign(linkItem, { + type: linkItem.items && linkItem.items.length ? 'links' : 'link' + }) +} + +/** + * @param { Route } route + * @param { Array<string|string[]> | Array<SidebarGroup> | [link: string]: SidebarConfig } config + * @returns { base: string, config: SidebarConfig } + */ +export function resolveMatchingConfig (regularPath, config) { + if (Array.isArray(config)) { + return { + base: '/', + config: config + } + } + for (const base in config) { + if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) { + return { + base, + config: config[base] + } + } + } + return {} +} + +function ensureEndingSlash (path) { + return /(\.html|\/)$/.test(path) + ? path + : path + '/' +} + +function resolveItem (item, pages, base, groupDepth = 1) { + if (typeof item === 'string') { + return resolvePage(pages, item, base) + } else if (Array.isArray(item)) { + return Object.assign(resolvePage(pages, item[0], base), { + title: item[1] + }) + } else { + const children = item.children || [] + if (children.length === 0 && item.path) { + return Object.assign(resolvePage(pages, item.path, base), { + title: item.title + }) + } + return { + type: 'group', + path: item.path, + title: item.title, + sidebarDepth: item.sidebarDepth, + children: children.map(child => resolveItem(child, pages, base, groupDepth + 1)), + collapsable: item.collapsable !== false + } + } +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..13a01d6 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +all: + git clone https://github.com/apache/incubator-teaclave.git teaclave || cd teaclave && git pull + vuepress build -d dist diff --git a/index.md b/index.md index 93e3eac..8e60ba9 100644 --- a/index.md +++ b/index.md @@ -2,11 +2,8 @@ home: true heroText: Apache Teaclave (incubating) tagline: an open source universal secure computing platform, making computation on privacy-sensitive data safe and simple -footer: "Apache Teaclave is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Apache Incubator. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that t [...] --- -Teaclave is an open source universal secure computing platform, making computation on privacy-sensitive data safe and simple. - ## Highlights - **Security**: --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
