teaclave-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ms...@apache.org
Subject [incubator-teaclave-website] 01/04: Add more docs
Date Fri, 15 May 2020 00:34:12 GMT
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 <bob@mssun.me>
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: commits-unsubscribe@teaclave.apache.org
For additional commands, e-mail: commits-help@teaclave.apache.org


Mime
View raw message