Format files, fix 404 page

This commit is contained in:
Dane Everitt 2018-07-28 13:47:12 -07:00
parent d174526e67
commit 9d79ca61ac
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
16 changed files with 698 additions and 1124 deletions

View File

@ -1,155 +1,51 @@
<template>
<div class="dropdown-wrapper" :class="{ open }">
<a class="dropdown-title" @click="toggle">
<span class="title">{{ item.text }}</span>
<span class="arrow" :class="open ? 'down' : 'right'"></span>
</a>
<DropdownTransition>
<ul class="nav-dropdown" v-show="open">
<li
class="dropdown-item"
v-for="(subItem, index) in item.items"
:key="subItem.link || index">
<h4 v-if="subItem.type === 'links'">{{ subItem.text }}</h4>
<ul class="dropdown-subitem-wrapper" v-if="subItem.type === 'links'">
<li
class="dropdown-subitem"
v-for="childSubItem in subItem.items"
:key="childSubItem.link">
<NavLink :item="childSubItem"/>
</li>
</ul>
<NavLink v-else :item="subItem"/>
</li>
</ul>
</DropdownTransition>
</div>
<div class="dropdown-wrapper" :class="{ open }">
<a class="dropdown-title" @click="toggle">
<span class="title">{{ item.text }}</span>
<span class="arrow" :class="open ? 'down' : 'right'"></span>
</a>
<DropdownTransition>
<ul class="nav-dropdown" v-show="open">
<li
class="dropdown-item"
v-for="(subItem, index) in item.items"
:key="subItem.link || index">
<h4 v-if="subItem.type === 'links'">{{ subItem.text }}</h4>
<ul class="dropdown-subitem-wrapper" v-if="subItem.type === 'links'">
<li
class="dropdown-subitem"
v-for="childSubItem in subItem.items"
:key="childSubItem.link">
<NavLink :item="childSubItem"/>
</li>
</ul>
<NavLink v-else :item="subItem"/>
</li>
</ul>
</DropdownTransition>
</div>
</template>
<script>
import NavLink from './NavLink.vue'
import DropdownTransition from './DropdownTransition.vue'
import NavLink from './NavLink.vue';
import DropdownTransition from './DropdownTransition.vue';
export default {
components: { NavLink, DropdownTransition },
data () {
return {
open: false
}
},
props: {
item: {
required: true
}
},
methods: {
toggle () {
this.open = !this.open
}
}
}
export default {
components: { NavLink, DropdownTransition },
data() {
return {
open: false
};
},
props: {
item: {
required: true
}
},
methods: {
toggle() {
this.open = ! this.open;
}
}
};
</script>
<!--<style lang="stylus">
@import './styles/config.styl'
.dropdown-wrapper
cursor pointer
.dropdown-title
display block
&: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
.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
// override the inline style.
display block !important
.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>-->

View File

@ -1,30 +1,29 @@
<template>
<transition name="dropdown"
@enter="setHeight"
@after-enter="unsetHeight"
@before-leave="setHeight">
<slot></slot>
</transition>
<transition name="dropdown"
@enter="setHeight"
@after-enter="unsetHeight"
@before-leave="setHeight">
<slot></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 = ''
}
}
}
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>
.dropdown-enter, .dropdown-leave-to {
height: 0 !important;
}
</style>
.dropdown-enter, .dropdown-leave-to {
height: 0 !important;
}
</style>

View File

@ -1,134 +1,43 @@
<template>
<div class="home">
<div class="hero">
<img v-if="data.heroImage" :src="$withBase(data.heroImage)" alt="hero">
<h1>{{ data.heroText || $title || 'Hello' }}</h1>
<p class="description">
{{ data.tagline || $description || 'Welcome to your VuePress site' }}
</p>
<p class="action" v-if="data.actionText && data.actionLink">
<NavLink class="action-button" :item="actionLink"/>
</p>
<div class="home">
<div class="hero">
<img v-if="data.heroImage" :src="$withBase(data.heroImage)" alt="hero">
<h1>{{ data.heroText || $title || 'Hello' }}</h1>
<p class="description">
{{ data.tagline || $description || 'Welcome to your VuePress site' }}
</p>
<p class="action" v-if="data.actionText && data.actionLink">
<NavLink class="action-button" :item="actionLink"/>
</p>
</div>
<div class="features" v-if="data.features && data.features.length">
<div class="feature" v-for="feature in data.features">
<h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p>
</div>
</div>
<Content custom/>
<div class="footer" v-if="data.footer">
{{ data.footer }}
</div>
</div>
<div class="features" v-if="data.features && data.features.length">
<div class="feature" v-for="feature in data.features">
<h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p>
</div>
</div>
<Content custom/>
<div class="footer" v-if="data.footer">
{{ data.footer }}
</div>
</div>
</template>
<script>
import NavLink from './NavLink.vue'
import NavLink from './NavLink.vue';
export default {
components: { NavLink },
computed: {
data () {
return this.$page.frontmatter
},
actionLink () {
return {
link: this.data.actionLink,
text: this.data.actionText
}
}
}
}
export default {
components: { NavLink },
computed: {
data() {
return this.$page.frontmatter;
},
actionLink() {
return {
link: this.data.actionLink,
text: this.data.actionText
};
}
}
};
</script>
<!--<style lang="stylus">
@import './styles/config.styl'
.home-gtfo
padding $navbarHeight 2rem 0
max-width 960px
margin 0px auto
.hero
text-align center
img
max-height 280px
display block
margin 3rem auto 1.5rem
h1
font-size 3rem
h1, .description, .action
margin 1.8rem auto
.description
max-width 35rem
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
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>-->

View File

@ -1,133 +1,133 @@
<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)"></div>
<Sidebar :items="sidebarItems" @toggle-sidebar="toggleSidebar">
<slot name="sidebar-top" slot="top"/>
<slot name="sidebar-bottom" slot="bottom"/>
</Sidebar>
<div class="custom-layout" v-if="$page.frontmatter.layout">
<component :is="$page.frontmatter.layout"/>
<div class="theme-container"
:class="pageClasses"
@touchstart="onTouchStart"
@touchend="onTouchEnd">
<Navbar v-if="shouldShowNavbar" @toggle-sidebar="toggleSidebar"/>
<div class="sidebar-mask" @click="toggleSidebar(false)"></div>
<Sidebar :items="sidebarItems" @toggle-sidebar="toggleSidebar">
<slot name="sidebar-top" slot="top"/>
<slot name="sidebar-bottom" slot="bottom"/>
</Sidebar>
<div class="custom-layout" v-if="$page.frontmatter.layout">
<component :is="$page.frontmatter.layout"/>
</div>
<Home v-else-if="$page.frontmatter.home"/>
<Page v-else :sidebar-items="sidebarItems">
<slot name="page-top" slot="top"/>
<slot name="page-bottom" slot="bottom"/>
</Page>
</div>
<Home v-else-if="$page.frontmatter.home"/>
<Page v-else :sidebar-items="sidebarItems">
<slot name="page-top" slot="top"/>
<slot name="page-bottom" slot="bottom"/>
</Page>
</div>
</template>
<script>
import Vue from 'vue'
import nprogress from 'nprogress'
import Home from './Home.vue'
import Navbar from './Navbar.vue'
import Page from './Page.vue'
import Sidebar from './Sidebar.vue'
import { resolveSidebarItems } from './util'
import Vue from 'vue';
import nprogress from 'nprogress';
import Home from './Home.vue';
import Navbar from './Navbar.vue';
import Page from './Page.vue';
import Sidebar from './Sidebar.vue';
import { resolveSidebarItems } from './util';
export default {
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.layout &&
!frontmatter.home &&
frontmatter.sidebar !== false &&
this.sidebarItems.length
)
},
sidebarItems () {
return resolveSidebarItems(
this.$page,
this.$route,
this.$site,
this.$localePath
)
},
pageClasses () {
const userPageClass = this.$page.frontmatter.pageClass
return [
{
'no-navbar': !this.shouldShowNavbar,
'sidebar-open': this.isSidebarOpen,
'no-sidebar': !this.shouldShowSidebar
export default {
components: { Home, Page, Sidebar, Navbar },
data() {
return {
isSidebarOpen: false
};
},
userPageClass
]
}
},
mounted () {
window.addEventListener('scroll', this.onScroll)
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.layout &&
! frontmatter.home &&
frontmatter.sidebar !== false &&
this.sidebarItems.length
);
},
sidebarItems() {
return resolveSidebarItems(
this.$page,
this.$route,
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
];
}
},
// configure progress bar
nprogress.configure({ showSpinner: false })
mounted() {
window.addEventListener('scroll', this.onScroll);
this.$router.beforeEach((to, from, next) => {
if (to.path !== from.path && !Vue.component(to.name)) {
nprogress.start()
}
next()
})
// configure progress bar
nprogress.configure({ showSpinner: false });
this.$router.afterEach(() => {
nprogress.done()
this.isSidebarOpen = false
})
},
this.$router.beforeEach((to, from, next) => {
if (to.path !== from.path && ! Vue.component(to.name)) {
nprogress.start();
}
next();
});
methods: {
toggleSidebar (to) {
this.isSidebarOpen = typeof to === 'boolean' ? to : !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)
this.$router.afterEach(() => {
nprogress.done();
this.isSidebarOpen = false;
});
},
methods: {
toggleSidebar(to) {
this.isSidebarOpen = typeof to === 'boolean' ? to : ! 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>
<style src="prismjs/themes/prism-tomorrow.css"></style>
<style src="./styles/main.css" lang="postcss"></style>
<style src="./styles/main.css" lang="postcss"></style>

View File

@ -1,46 +1,47 @@
<template>
<router-link
class="nav-link"
:to="link"
v-if="!isExternal(link)"
:exact="exact"
>{{ item.text }}</router-link>
<a
v-else
:href="link"
class="nav-link external"
:target="isMailto(link) || isTel(link) ? null : '_blank'"
:rel="isMailto(link) || isTel(link) ? null : 'noopener noreferrer'"
>
{{ item.text }}
<OutboundLink/>
</a>
<router-link
class="nav-link"
:to="link"
v-if="!isExternal(link)"
:exact="exact"
>{{ item.text }}
</router-link>
<a
v-else
:href="link"
class="nav-link external"
:target="isMailto(link) || isTel(link) ? null : '_blank'"
:rel="isMailto(link) || isTel(link) ? null : 'noopener noreferrer'"
>
{{ item.text }}
<OutboundLink/>
</a>
</template>
<script>
import { isExternal, isMailto, isTel, ensureExt } from './util'
import { isExternal, isMailto, isTel, ensureExt } from './util';
export default {
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 === '/'
}
},
methods: {
isExternal,
isMailto,
isTel
}
}
export default {
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 === '/';
}
},
methods: {
isExternal,
isMailto,
isTel
}
};
</script>

View File

@ -1,134 +1,100 @@
<template>
<nav class="nav-links flex-no-shrink" v-if="userLinks.length || repoLink">
<!-- user links -->
<div
class="nav-item"
v-for="item in userLinks"
:key="item.link">
<DropdownLink v-if="item.type === 'links'" :item="item"/>
<NavLink v-else :item="item"/>
</div>
<!-- repo link -->
<div v-if="repoLink"
class="nav-item">
<a :href="repoLink"
class="nav-link"
target="_blank"
rel="noopener noreferrer">
{{ repoLabel }}
<OutboundLink/>
</a>
</div>
</nav>
<nav class="nav-links flex-no-shrink" v-if="userLinks.length || repoLink">
<!-- user links -->
<div
class="nav-item"
v-for="item in userLinks"
:key="item.link">
<DropdownLink v-if="item.type === 'links'" :item="item"/>
<NavLink v-else :item="item"/>
</div>
<!-- repo link -->
<div v-if="repoLink"
class="nav-item">
<a :href="repoLink"
class="nav-link"
target="_blank"
rel="noopener noreferrer">
{{ repoLabel }}
<OutboundLink/>
</a>
</div>
</nav>
</template>
<script>
import DropdownLink from './DropdownLink.vue'
import { resolveNavLinkItem } from './util'
import NavLink from './NavLink.vue'
import DropdownLink from './DropdownLink.vue';
import { resolveNavLinkItem } from './util';
import NavLink from './NavLink.vue';
export default {
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',
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
}
export default {
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',
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}`;
}
},
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';
}
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}`
}
},
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">
@import './styles/config.styl'
.nav-links-away
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
.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>
-->

View File

@ -1,74 +1,42 @@
<template>
<header class="nav">
<SidebarButton class="block md:hidden flex-no-shrink" @toggle-sidebar="$emit('toggle-sidebar')"/>
<div class="logo-container">
<router-link :to="$localePath" class="home-link">
<img class="logo"
v-if="$site.themeConfig.logo"
:src="$withBase($site.themeConfig.logo)">
<span class="site-name hidden md:inline"
v-if="$siteTitle"
:class="{ 'can-hide': $site.themeConfig.logo }">
<header class="nav">
<SidebarButton class="block md:hidden flex-no-shrink" @toggle-sidebar="$emit('toggle-sidebar')"/>
<div class="logo-container">
<router-link :to="$localePath" class="home-link">
<img class="logo"
v-if="$site.themeConfig.logo"
:src="$withBase($site.themeConfig.logo)">
<span class="site-name hidden md:inline"
v-if="$siteTitle"
:class="{ 'can-hide': $site.themeConfig.logo }">
{{ $siteTitle }}
</span>
</router-link>
</div>
<div class="w-full">
<div class="flex">
<SearchBox/>
<NavLinks class="hidden md:flex"/>
</div>
</div>
</header>
</router-link>
</div>
<div class="w-full">
<div class="flex">
<SearchBox/>
<NavLinks class="hidden md:flex"/>
</div>
</div>
</header>
</template>
<script>
import SidebarButton from './SidebarButton.vue'
import AlgoliaSearchBox from '@AlgoliaSearchBox'
import SearchBox from './SearchBox.vue'
import NavLinks from './NavLinks.vue'
import SidebarButton from './SidebarButton.vue';
import AlgoliaSearchBox from '@AlgoliaSearchBox';
import SearchBox from './SearchBox.vue';
import NavLinks from './NavLinks.vue';
export default {
components: { SidebarButton, NavLinks, SearchBox, AlgoliaSearchBox },
computed: {
algolia () {
return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}
},
isAlgoliaSearch () {
return this.algolia && this.algolia.apiKey && this.algolia.indexName
}
}
}
export default {
components: { SidebarButton, NavLinks, SearchBox, AlgoliaSearchBox },
computed: {
algolia() {
return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {};
},
isAlgoliaSearch() {
return this.algolia && this.algolia.apiKey && this.algolia.indexName;
}
}
};
</script>
<!--<style lang="stylus">
@import './styles/config.styl'
.navbar
padding 0.7rem 1.5rem
line-height $navbarHeight - 1.4rem
position relative
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
font-size 0.9rem
position absolute
right 1.5rem
top 0.7rem
@media (max-width: $MQMobile)
.navbar
padding-left 4rem
.can-hide
display none
</style>-->

View File

@ -1,26 +1,30 @@
<template>
<div class="theme-container">
<div class="content">
<h1>404</h1>
<blockquote>{{ getMsg() }}</blockquote>
<router-link to="/">Take me home.</router-link>
<div class="theme-container">
<div class="content error-page">
<h1>404</h1>
<blockquote>{{ getMsg() }}</blockquote>
<router-link to="/">Take me home.</router-link>
</div>
</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.`
]
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.`,
`Who let Dogmeat mess with the documentation again?`,
`Someone unplugged the ethernet cable... again.`,
`Couldn't locate that cap'n, should we start over?`,
`I don't like this game of hide'n'seek anymore.`
];
export default {
methods: {
getMsg () {
return msgs[Math.floor(Math.random() * msgs.length)]
}
}
}
export default {
methods: {
getMsg() {
return msgs[Math.floor(Math.random() * msgs.length)];
}
}
};
</script>

View File

@ -1,221 +1,137 @@
<template>
<div class="search-box">
<input
@input="query = $event.target.value"
aria-label="Search"
:value="query"
placeholder="Search"
autocomplete="off"
spellcheck="false"
@focus="focused = true"
@blur="focused = false"
@keyup.enter="go(focusIndex)"
@keyup.up="onUp"
@keyup.down="onDown">
<div class="suggestion-container" v-if="showSuggestions" @mouseleave="unfocus">
<div class="suggestion-padding"></div>
<ul class="suggestions" :class="{ 'align-right': alignRight }">
<li class="suggestion" v-for="(s, i) in suggestions"
:class="{ focused: i === focusIndex }"
@mousedown="go(i)"
@mouseenter="focus(i)">
<a :href="s.path" @click.prevent>
<span class="page-title">{{ s.title || s.path }}</span>
<span v-if="s.header" class="header">&gt; {{ s.header.title }}</span>
</a>
</li>
</ul>
<div class="search-box">
<input
@input="query = $event.target.value"
aria-label="Search"
:value="query"
placeholder="Search"
autocomplete="off"
spellcheck="false"
@focus="focused = true"
@blur="focused = false"
@keyup.enter="go(focusIndex)"
@keyup.up="onUp"
@keyup.down="onDown">
<div class="suggestion-container" v-if="showSuggestions" @mouseleave="unfocus">
<div class="suggestion-padding"></div>
<ul class="suggestions" :class="{ 'align-right': alignRight }">
<li class="suggestion" v-for="(s, i) in suggestions"
:class="{ focused: i === focusIndex }"
@mousedown="go(i)"
@mouseenter="focus(i)">
<a :href="s.path" @click.prevent>
<span class="page-title">{{ s.title || s.path }}</span>
<span v-if="s.header" class="header">&gt; {{ s.header.title }}</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
query: '',
focused: false,
focusIndex: 0
}
},
computed: {
showSuggestions () {
return (
this.focused &&
this.suggestions &&
this.suggestions.length
)
},
suggestions () {
const query = this.query.trim().toLowerCase()
if (!query) {
return
}
export default {
data() {
return {
query: '',
focused: false,
focusIndex: 0
};
},
computed: {
showSuggestions() {
return (
this.focused &&
this.suggestions &&
this.suggestions.length
);
},
suggestions() {
const query = this.query.trim().toLowerCase();
if (! query) {
return;
}
const { pages, themeConfig } = this.$site
const max = themeConfig.searchMaxSuggestions || 5
const localePath = this.$localePath
const matches = item => (
item.title &&
item.title.toLowerCase().indexOf(query) > -1
)
const res = []
for (let i = 0; i < pages.length; i++) {
if (res.length >= max) break
const p = pages[i]
// filter out results that do not match current locale
if (this.getPageLocalePath(p) !== localePath) {
continue
}
if (matches(p)) {
res.push(p)
} else if (p.headers) {
for (let j = 0; j < p.headers.length; j++) {
if (res.length >= max) break
const h = p.headers[j]
if (matches(h)) {
res.push(Object.assign({}, p, {
path: p.path + '#' + h.slug,
header: h
}))
const { pages, themeConfig } = this.$site;
const max = themeConfig.searchMaxSuggestions || 5;
const localePath = this.$localePath;
const matches = item => (
item.title &&
item.title.toLowerCase().indexOf(query) > - 1
);
const res = [];
for (let i = 0; i < pages.length; i ++) {
if (res.length >= max) break;
const p = pages[i];
// filter out results that do not match current locale
if (this.getPageLocalePath(p) !== localePath) {
continue;
}
if (matches(p)) {
res.push(p);
} else if (p.headers) {
for (let j = 0; j < p.headers.length; j ++) {
if (res.length >= max) break;
const h = p.headers[j];
if (matches(h)) {
res.push(Object.assign({}, p, {
path: p.path + '#' + h.slug,
header: h
}));
}
}
}
}
return res;
},
// make suggestions align right when there are not enough items
alignRight() {
const navCount = (this.$site.themeConfig.nav || []).length;
const repo = this.$site.repo ? 1 : 0;
return navCount + repo <= 2;
}
},
methods: {
getPageLocalePath(page) {
for (const localePath in this.$site.locales || {}) {
if (localePath !== '/' && page.path.indexOf(localePath) === 0) {
return localePath;
}
}
return '/';
},
onUp() {
if (this.showSuggestions) {
if (this.focusIndex > 0) {
this.focusIndex --;
} else {
this.focusIndex = this.suggestions.length - 1;
}
}
},
onDown() {
if (this.showSuggestions) {
if (this.focusIndex < this.suggestions.length - 1) {
this.focusIndex ++;
} else {
this.focusIndex = 0;
}
}
},
go(i) {
if (! this.showSuggestions) {
return;
}
this.$router.push(this.suggestions[i].path);
this.query = '';
this.focusIndex = 0;
},
focus(i) {
this.focusIndex = i;
},
unfocus() {
this.focusIndex = - 1;
}
}
}
}
return res
},
// make suggestions align right when there are not enough items
alignRight () {
const navCount = (this.$site.themeConfig.nav || []).length
const repo = this.$site.repo ? 1 : 0
return navCount + repo <= 2
}
},
methods: {
getPageLocalePath (page) {
for (const localePath in this.$site.locales || {}) {
if (localePath !== '/' && page.path.indexOf(localePath) === 0) {
return localePath
}
}
return '/'
},
onUp () {
if (this.showSuggestions) {
if (this.focusIndex > 0) {
this.focusIndex--
} else {
this.focusIndex = this.suggestions.length - 1
}
}
},
onDown () {
if (this.showSuggestions) {
if (this.focusIndex < this.suggestions.length - 1) {
this.focusIndex++
} else {
this.focusIndex = 0
}
}
},
go (i) {
if (!this.showSuggestions) {
return
}
this.$router.push(this.suggestions[i].path)
this.query = ''
this.focusIndex = 0
},
focus (i) {
this.focusIndex = i
},
unfocus () {
this.focusIndex = -1
}
}
}
};
</script>
<!--<style lang="stylus">
@import './styles/config.styl'
.search-box-no
display inline-block
position relative
margin-right 0.5rem
input
cursor text
width 10rem
color lighten($textColor, 25%)
display inline-block
border 1px solid darken($borderColor, 10%)
border-radius 2rem
font-size 0.9rem
line-height 2rem
padding 0 0.5rem 0 2rem
outline none
transition all .2s ease
background #fff url(./search.svg) 0.6rem 0.5rem no-repeat
background-size 1rem
&:focus
cursor auto
border-color $accentColor
.suggestions
background #fff
width 20rem
position absolute
top 1.5rem
border 1px solid darken($borderColor, 10%)
border-radius 6px
padding 0.4rem
list-style-type none
&.align-right
right 0
.suggestion
line-height 1.4
padding 0.4rem 0.6rem
border-radius 4px
cursor pointer
a
color lighten($textColor, 35%)
.page-title
font-weight 600
.header
font-size 0.9em
margin-left 0.25em
&.focused
background-color #f3f4f5
a
color $accentColor
@media (max-width: $MQNarrow)
.search-box
input
cursor pointer
width 0
border-color transparent
position relative
left 1rem
&:focus
cursor text
left 0
width 10rem
@media (max-width: $MQNarrow) and (min-width: $MQMobile)
.search-box
.suggestions
left 0
@media (max-width: $MQMobile)
.search-box
margin-right 0
.suggestions
right 0
@media (max-width: $MQMobileNarrow)
.search-box
.suggestions
width calc(100vw - 4rem)
input:focus
width 8rem
</style>-->

View File

@ -1,105 +1,70 @@
<template>
<div class="sidebar">
<NavLinks class="block md:hidden" />
<slot name="top"/>
<ul class="sidebar-links" v-if="items.length">
<li v-for="(item, i) in items" :key="i">
<SidebarGroup v-if="item.type === 'group'"
:item="item"
:first="i === 0"
:open="i === openGroupIndex"
:collapsable="item.collapsable"
@toggle="toggleGroup(i)"/>
<SidebarLink v-else :item="item"/>
</li>
</ul>
<slot name="bottom"/>
</div>
<div class="sidebar">
<NavLinks class="block md:hidden"/>
<slot name="top"/>
<ul class="sidebar-links" v-if="items.length">
<li v-for="(item, i) in items" :key="i">
<SidebarGroup v-if="item.type === 'group'"
:item="item"
:first="i === 0"
:open="i === openGroupIndex"
:collapsable="item.collapsable"
@toggle="toggleGroup(i)"/>
<SidebarLink v-else :item="item"/>
</li>
</ul>
<slot name="bottom"/>
</div>
</template>
<script>
import SidebarGroup from './SidebarGroup.vue'
import SidebarLink from './SidebarLink.vue'
import NavLinks from './NavLinks.vue'
import { isActive } from './util'
import SidebarGroup from './SidebarGroup.vue';
import SidebarLink from './SidebarLink.vue';
import NavLinks from './NavLinks.vue';
import { isActive } from './util';
export default {
components: { SidebarGroup, SidebarLink, NavLinks },
props: ['items'],
data () {
return {
openGroupIndex: 0
}
},
created () {
this.refreshIndex()
},
watch: {
'$route' () {
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.path)
}
}
}
export default {
components: { SidebarGroup, SidebarLink, NavLinks },
props: ['items'],
data() {
return {
openGroupIndex: 0
};
},
created() {
this.refreshIndex();
},
watch: {
'$route'() {
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.path);
}
}
};
function resolveOpenGroupIndex (route, items) {
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.type === 'group' && item.children.some(c => isActive(route, c.path))) {
return i
function resolveOpenGroupIndex(route, items) {
for (let i = 0; i < items.length; i ++) {
const item = items[i];
if (item.type === 'group' && item.children.some(c => isActive(route, c.path))) {
return i;
}
}
return - 1;
}
}
return -1
}
</script>
<!--<style lang="stylus">
@import './styles/config.styl'
.sidebar-no
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
@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>
-->

View File

@ -1,7 +1,9 @@
<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=""></path>
</svg>
</div>
<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=""></path>
</svg>
</div>
</template>

View File

@ -1,64 +1,28 @@
<template>
<div class="sidebar-group" :class="{ first, collapsable }">
<p class="sidebar-heading" :class="{ open }" @click="$emit('toggle')">
<span>{{ item.title }}</span>
<span class="arrow"
v-if="collapsable"
:class="open ? 'down' : 'right'"></span>
</p>
<DropdownTransition>
<ul class="sidebar-group-items" ref="items" v-if="open || !collapsable">
<li v-for="child in item.children">
<SidebarLink :item="child"/>
</li>
</ul>
</DropdownTransition>
</div>
<div class="sidebar-group" :class="{ first, collapsable }">
<p class="sidebar-heading" :class="{ open }" @click="$emit('toggle')">
<span>{{ item.title }}</span>
<span class="arrow"
v-if="collapsable"
:class="open ? 'down' : 'right'"></span>
</p>
<DropdownTransition>
<ul class="sidebar-group-items" ref="items" v-if="open || !collapsable">
<li v-for="child in item.children">
<SidebarLink :item="child"/>
</li>
</ul>
</DropdownTransition>
</div>
</template>
<script>
import SidebarLink from './SidebarLink.vue'
import DropdownTransition from './DropdownTransition.vue'
import SidebarLink from './SidebarLink.vue';
import DropdownTransition from './DropdownTransition.vue';
export default {
name: 'SidebarGroup',
props: ['item', 'first', 'open', 'collapsable'],
components: { SidebarLink, DropdownTransition }
}
export default {
name: 'SidebarGroup',
props: ['item', 'first', 'open', 'collapsable'],
components: { SidebarLink, DropdownTransition }
};
</script>
<!--<style lang="stylus">
.sidebar-group-away
&:not(.first)
margin-top 1em
.sidebar-group
padding-left 0.5em
&:not(.collapsable)
.sidebar-heading
cursor auto
color inherit
.sidebar-heading-no
color #999
transition color .15s ease
cursor pointer
font-size 1.1em
font-weight bold
// text-transform uppercase
padding 0 1.5rem
margin-top 0
margin-bottom 0.5rem
&.open, &:hover
color inherit
.arrow
position relative
top -0.12em
left 0.5em
&:.open .arrow
top -0.18em
.sidebar-group-items
transition height .1s ease-out
overflow hidden
</style>
-->

View File

@ -1,89 +1,57 @@
<script>
import { isActive, hashRE, groupHeaders } from './util'
import { isActive, hashRE, groupHeaders } from './util';
export default {
functional: true,
props: ['item'],
render (h, { parent: { $page, $site, $route }, props: { item }}) {
// 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 = renderLink(h, item.path, item.title || item.path, active)
const configDepth = $page.frontmatter.sidebarDepth != null
? $page.frontmatter.sidebarDepth
: $site.themeConfig.sidebarDepth
const maxDepth = configDepth == null ? 1 : configDepth
const displayAllHeaders = !!$site.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
export default {
functional: true,
props: ['item'],
render(h, { parent: { $page, $site, $route }, props: { item } }) {
// 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 = renderLink(h, item.path, item.title || item.path, active);
const configDepth = $page.frontmatter.sidebarDepth != null
? $page.frontmatter.sidebarDepth
: $site.themeConfig.sidebarDepth;
const maxDepth = configDepth == null ? 1 : configDepth;
const displayAllHeaders = ! ! $site.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) {
return h('router-link', {
props: {
to,
activeClass: '',
exactActiveClass: ''
},
class: {
active,
'sidebar-link': true
}
}, text);
}
}
}
function renderLink (h, to, text, active) {
return h('router-link', {
props: {
to,
activeClass: '',
exactActiveClass: ''
},
class: {
active,
'sidebar-link': true
function renderChildren(h, children, path, route, maxDepth, depth = 1) {
if (! children || depth > maxDepth) return null;
return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => {
const active = isActive(route, path + '#' + c.slug);
return h('li', { class: 'sidebar-sub-header' }, [
renderLink(h, path + '#' + c.slug, c.title, active),
renderChildren(h, c.children, path, route, maxDepth, depth + 1)
]);
}));
}
}, text)
}
function renderChildren (h, children, path, route, maxDepth, depth = 1) {
if (!children || depth > maxDepth) return null
return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => {
const active = isActive(route, path + '#' + c.slug)
return h('li', { class: 'sidebar-sub-header' }, [
renderLink(h, path + '#' + c.slug, c.title, active),
renderChildren(h, c.children, path, route, maxDepth, depth + 1)
])
}))
}
</script>
<!--<style lang="stylus">
@import './styles/config.styl'
.sidebar .sidebar-sub-headers
padding-left 1rem
font-size 0.95em
a.sidebar-link
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>-->

View File

@ -1 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="12" height="13"><g stroke-width="2" stroke="#aaa" fill="none"><path d="M11.29 11.71l-4-4"/><circle cx="5" cy="5" r="4"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="13">
<g stroke-width="2" stroke="#aaa" fill="none">
<path d="M11.29 11.71l-4-4"/>
<circle cx="5" cy="5" r="4"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 216 B

After

Width:  |  Height:  |  Size: 246 B

View File

@ -13,6 +13,12 @@
@screen md {
@apply .p-8;
}
&.error-page {
h1 {
@apply .pt-12 !important;
}
}
}
.table-of-contents {
@ -43,7 +49,7 @@
.next, .prev {
@apply .inline-block;
}
.next {
@apply .float-right;
}

View File

@ -46,6 +46,10 @@ a.header-anchor {
}
}
blockquote {
@apply .border-l-4 .border-grey-light .text-grey .p-1 .pl-4 .font-light .my-4 .text-xl;
}
h1, h2, h3, h4, h5, h6 {
@apply .mb-4 .font-semibold;