docs:更新文档

This commit is contained in:
张益铭
2021-03-01 15:06:11 +08:00
parent 1542135ab0
commit 9064b372e8
5835 changed files with 904126 additions and 161722 deletions

View File

@@ -0,0 +1,131 @@
<template>
<div class="theme" :class="pageClasses">
<header class="navbar" v-if="showNavbar">
<NavBar>
<template #search>
<slot name="navbar-search" />
</template>
</NavBar>
<ToggleSideBarButton @toggle="toggleSidebar" />
</header>
<aside :class="{ open: openSideBar }">
<SideBar>
<template #top>
<slot name="sidebar-top" />
</template>
<template #bottom>
<slot name="sidebar-bottom" />
</template>
</SideBar>
</aside>
<!-- TODO: make this button accessible -->
<div class="sidebar-mask" @click="toggleSidebar(false)" />
<main class="home" aria-labelledby="main-title" v-if="enableHome">
<Home>
<template #hero>
<slot name="home-hero" />
</template>
<template #features>
<slot name="home-features" />
</template>
<template #footer>
<slot name="home-footer" />
</template>
</Home>
</main>
<main v-else>
<Page>
<template #top>
<slot name="page-top" />
</template>
<template #bottom>
<slot name="page-bottom" />
</template>
</Page>
</main>
</div>
<Debug />
</template>
<script>
import { ref, computed, watch } from 'vue'
import NavBar from './components/NavBar.vue'
import Home from './components/Home.vue'
import ToggleSideBarButton from './components/ToggleSideBarButton.vue'
import SideBar from './components/SideBar.vue'
import Page from './components/Page.vue'
import { useRoute, useSiteData, useSiteDataByRoute } from 'vitepress'
export default {
components: {
Home,
NavBar,
ToggleSideBarButton,
SideBar,
Page
},
setup() {
const route = useRoute()
const siteData = useSiteData()
const siteRouteData = useSiteDataByRoute()
const openSideBar = ref(false)
const enableHome = computed(() => !!route.data.frontmatter.home)
const showNavbar = computed(() => {
const { themeConfig } = siteRouteData.value
const { frontmatter } = route.data
if (
frontmatter.navbar === false
|| themeConfig.navbar === false) {
return false
}
return (
siteData.value.title
|| themeConfig.logo
|| themeConfig.repo
|| themeConfig.nav
)
})
const showSidebar = computed(() => {
const { frontmatter } = route.data
const { themeConfig } = siteRouteData.value
return (
!frontmatter.home
&& frontmatter.sidebar !== false
&& ((typeof themeConfig.sidebar === 'object') && (Object.keys(themeConfig.sidebar).length != 0)
|| (Array.isArray(themeConfig.sidebar) && themeConfig.sidebar.length != 0))
)
})
const pageClasses = computed(() => {
return [{
'no-navbar': !showNavbar.value,
'sidebar-open': openSideBar.value,
'no-sidebar': !showSidebar.value
}]
})
const toggleSidebar = (to) => {
openSideBar.value = typeof to === 'boolean' ? to : !openSideBar.value
}
const hideSidebar = toggleSidebar.bind(null, false)
// close the sidebar when navigating to a different location
watch(route, hideSidebar)
// TODO: route only changes when the pathname changes
// listening to hashchange does nothing because it's prevented in router
return {
showNavbar,
showSidebar,
openSideBar,
pageClasses,
enableHome,
toggleSidebar
}
}
}
</script>

View File

@@ -0,0 +1,26 @@
<template>
<div class="theme">
<h1>404</h1>
<blockquote>{{ getMsg() }}</blockquote>
<a :href="$site.base" aria-label="go to home">
Take me home.
</a>
</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 {
setup: () => ({
getMsg() {
return msgs[Math.floor(Math.random() * msgs.length)]
}
})
}
</script>

View File

@@ -0,0 +1,219 @@
<template>
<header class="hero">
<img
v-if="data.heroImage"
:src="heroImageSrc"
:alt="data.heroAlt || 'hero'"
>
<h1
v-if="data.heroText !== null"
id="main-title"
>
{{ data.heroText || siteTitle || 'Hello' }}
</h1>
<p
v-if="data.tagline !== null"
class="description"
>
{{ data.tagline || siteDescription || 'Welcome to your VitePress site' }}
</p>
<p
v-if="data.actionText && data.actionLink"
class="action"
>
<NavBarLink :item="actionLink" />
</p>
<slot name="hero" />
</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>
<slot name="features" />
</div>
<div
v-if="data.footer"
class="footer"
>
{{ data.footer }}
<slot name="footer" />
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import NavBarLink from './NavBarLink.vue'
import { withBase } from '../utils'
import { useRoute, useSiteData } from 'vitepress'
export default defineComponent({
components: {
NavBarLink
},
setup() {
const route = useRoute()
const siteData = useSiteData()
const data = computed(() => route.data.frontmatter)
const actionLink = computed(() => ({
link: data.value.actionLink,
text: data.value.actionText
}))
const heroImageSrc = computed(() => withBase(data.value.heroImage))
const siteTitle = computed(() => siteData.value.title)
const siteDescription = computed(() => siteData.value.description)
return {
data,
actionLink,
heroImageSrc,
siteTitle,
siteDescription
}
}
})
</script>
<style scoped>
.hero {
text-align: center;
}
.hero img {
max-width: 100%;
max-height: 280px;
display: block;
margin: 3rem auto 1.5rem;
}
.hero h1 {
font-size: 3rem;
}
.hero h1,
.hero .description,
.hero .action {
margin: 1.8rem auto;
}
.hero .description {
max-width: 35rem;
font-size: 1.6rem;
line-height: 1.3;
/* TODO: calculating lighten 40% color with using style :vars from `--text-color` */
color: #6a8bad;
}
::v-deep(.nav-link) {
display: inline-block;
font-size: 1.2rem;
color: #fff;
background-color: var(--accent-color);
margin-left: 0;
padding: 0.8rem 1.6rem;
border-radius: 4px;
transition: background-color .1s ease;
box-sizing: border-box;
/* TODO: calculating darken 10% color with using style vars from `--accent-color` */
border-bottom: 1px solid #389d70;
}
::v-deep(.nav-link:hover) {
/* TODO: calculating lighten 10% color with using style vars from `--accent-color` */
background-color: #4abf8a;
}
.features {
border-top: 1px solid var(--border-color);
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%;
}
.feature h2 {
font-size: 1.4rem;
font-weight: 500;
border-bottom: none;
padding-bottom: 0;
/* TODO: calculating lighten 10% color with using style :vars from `--text-color` */
color: #3a5169;
}
.feature p {
/* TODO: calculating lighten 25% color with using style :vars from `--text-color` */
color: #4e6e8e;
}
.footer {
padding: 2.5rem;
border-top: 1px solid var(--border-color);
text-align: center;
/* TODO: calculating lighten 25% color with using style :vars from `--text-color` */
color: #4e6e8e;
}
@media screen and (max-width: 719px) {
.features {
flex-direction: column;
}
.feature {
max-width: 100%;
padding: 0 2.5rem;
}
}
@media screen and (max-width: 429px) {
.hero img {
max-height: 210px;
margin: 2rem auto 1.2rem;
}
.hero h1 {
font-size: 2rem;
}
.hero h1,
.hero .description,
.hero .action {
margin: 1.2rem auto;
}
.hero .description {
font-size: 1.2rem;
}
.hero .action-button {
font-size: 1rem;
padding: 0.6rem 1.2rem;
}
.feature h2 {
font-size: 1.25rem;
}
}
</style>

View File

@@ -0,0 +1,10 @@
import { withBase } from '../utils';
declare const _default: {
components: {
NavBarLinks: import("vue").ComponentOptions<{}, any, any, any, any, any, any, any>;
};
setup(): {
withBase: typeof withBase;
};
};
export default _default;

View File

@@ -0,0 +1,10 @@
import { withBase } from '../utils';
import NavBarLinks from './NavBarLinks.vue';
export default {
components: {
NavBarLinks
},
setup() {
return { withBase };
}
};

View File

@@ -0,0 +1,44 @@
<template>
<a
class="title"
:aria-label="$site.title + ', back to home'"
:href="$site.base"
>
<img
class="logo"
v-if="$theme.logo"
:src="withBase($theme.logo)"
alt="logo"
/>
<span>{{ $site.title }}</span>
</a>
<div class="flex-grow"></div>
<NavBarLinks class="hide-mobile" />
<slot name="search" />
</template>
<script src="./NavBar"></script>
<style>
.title {
font-size: 1.3rem;
font-weight: 600;
color: var(--text-color);
}
.flex-grow {
flex-grow: 1;
}
.logo {
margin-right: 0.75rem;
height: 1.3rem;
vertical-align: bottom;
}
@media screen and (max-width: 719px) {
.hide-mobile {
display: none;
}
}
</style>

View File

@@ -0,0 +1,21 @@
import { PropType } from 'vue';
import { DefaultTheme } from '../config';
declare const _default: import("vue").DefineComponent<{
item: {
type: PropType<DefaultTheme.NavItemWithLink>;
required: true;
};
}, {
classes: import("vue").ComputedRef<{
active: boolean;
external: boolean;
}>;
isActiveLink: import("vue").ComputedRef<boolean>;
isExternalLink: import("vue").ComputedRef<boolean>;
href: import("vue").ComputedRef<string>;
target: import("vue").ComputedRef<string>;
rel: import("vue").ComputedRef<string>;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, Record<string, any>, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<{
item: DefaultTheme.NavItemWithLink;
} & {}>, {}>;
export default _default;

View File

@@ -0,0 +1,62 @@
import { defineComponent, computed } from 'vue';
import { useRoute } from 'vitepress';
import { withBase, isExternal } from '../utils';
import OutboundLink from './icons/OutboundLink.vue';
const normalizePath = (path) => {
path = path
.replace(/#.*$/, '')
.replace(/\?.*$/, '')
.replace(/\.html$/, '');
if (path.endsWith('/')) {
path += 'index';
}
return path;
};
export default defineComponent({
components: {
OutboundLink
},
props: {
item: {
type: Object,
required: true
}
},
setup(props) {
const item = props.item;
const route = useRoute();
const classes = computed(() => ({
active: isActiveLink.value,
external: isExternalLink.value
}));
const isActiveLink = computed(() => {
return normalizePath(withBase(item.link)) === normalizePath(route.path);
});
const isExternalLink = computed(() => {
return isExternal(item.link);
});
const href = computed(() => {
return isExternalLink.value ? item.link : withBase(item.link);
});
const target = computed(() => {
if (item.target) {
return item.target;
}
return isExternalLink.value ? '_blank' : '';
});
const rel = computed(() => {
if (item.rel) {
return item.rel;
}
return isExternalLink.value ? 'noopener noreferrer' : '';
});
return {
classes,
isActiveLink,
isExternalLink,
href,
target,
rel
};
}
});

View File

@@ -0,0 +1,69 @@
<template>
<div class="nav-item">
<a
class="nav-link"
:class="classes"
:href="href"
:target="target"
:rel="rel"
:aria-label="item.ariaLabel"
>
{{ item.text }}
<OutboundLink v-if="isExternalLink" />
</a>
</div>
</template>
<script src="./NavBarLink"></script>
<style>
.nav-item {
position: relative;
display: inline-block;
margin-left: 1.5rem;
line-height: 2rem;
}
@media screen and (max-width: 719px) {
.nav-item {
display: block;
margin-left: 0;
padding: 0.3rem 1.5rem;
}
}
.nav-link {
display: block;
margin-bottom: -2px;
border-bottom: 2px solid transparent;
font-size: 0.9rem;
font-weight: 500;
line-height: 1.4rem;
color: var(--text-color);
white-space: nowrap;
}
.nav-link:hover,
.nav-link.active {
border-bottom-color: var(--accent-color);
}
.nav-link.external:hover {
border-bottom-color: transparent;
}
@media screen and (max-width: 719px) {
.nav-link {
line-height: 1.7;
font-size: 1em;
font-weight: 600;
border-bottom: none;
margin-bottom: 0;
}
.nav-link:hover,
.nav-link.active {
color: var(--accent-color);
}
}
</style>

View File

@@ -0,0 +1,21 @@
declare const _default: {
components: {
NavBarLink: import("vue").ComponentOptions<{}, any, any, any, any, any, any, any>;
NavDropdownLink: import("vue").ComponentOptions<{}, any, any, any, any, any, any, any>;
};
setup(): {
navData: import("vue").ComputedRef<any>;
repoInfo: import("vue").ComputedRef<{
link: string;
text: string;
} | null>;
localeCandidates: import("vue").ComputedRef<{
text: any;
items: {
text: any;
link: string;
}[];
} | null>;
};
};
export default _default;

View File

@@ -0,0 +1,85 @@
import { computed } from 'vue';
import { useSiteData, useSiteDataByRoute, useRoute } from 'vitepress';
import { inBrowser } from '/@app/utils';
import NavBarLink from './NavBarLink.vue';
import NavDropdownLink from './NavDropdownLink.vue';
const platforms = ['GitHub', 'GitLab', 'Bitbucket'].map((platform) => [platform, new RegExp(platform, 'i')]);
export default {
components: {
NavBarLink,
NavDropdownLink
},
setup() {
const siteDataByRoute = useSiteDataByRoute();
const siteData = useSiteData();
const route = useRoute();
const repoInfo = computed(() => {
const theme = siteData.value.themeConfig;
const repo = theme.docsRepo || theme.repo;
let text = theme.repoLabel;
if (repo) {
const link = /^https?:/.test(repo) ? repo : `https://github.com/${repo}`;
if (!text) {
// if no label is provided, deduce it from the repo url
const repoHosts = link.match(/^https?:\/\/[^/]+/);
if (repoHosts) {
const repoHost = repoHosts[0];
const foundPlatform = platforms.find(([_platform, re]) => re.test(repoHost));
text = foundPlatform && foundPlatform[0];
}
}
return { link, text: text || 'Source' };
}
return null;
});
const localeCandidates = computed(() => {
const locales = siteData.value.themeConfig.locales;
if (!locales) {
return null;
}
const localeKeys = Object.keys(locales);
if (localeKeys.length <= 1) {
return null;
}
// handle site base
const siteBase = inBrowser ? siteData.value.base : '/';
const siteBaseWithoutSuffix = siteBase.endsWith('/')
? siteBase.slice(0, -1)
: siteBase;
// remove site base in browser env
const routerPath = route.path.slice(siteBaseWithoutSuffix.length);
const currentLangBase = localeKeys.find((v) => {
if (v === '/') {
return false;
}
return routerPath.startsWith(v);
});
const currentContentPath = currentLangBase
? routerPath.substring(currentLangBase.length - 1)
: routerPath;
const candidates = localeKeys.map((v) => {
const localePath = v.endsWith('/') ? v.slice(0, -1) : v;
return {
text: locales[v].label || locales[v].lang,
link: `${localePath}${currentContentPath}`
};
});
const currentLangKey = currentLangBase ? currentLangBase : '/';
const selectText = locales[currentLangKey].selectText
? locales[currentLangKey].selectText
: 'Languages';
return {
text: selectText,
items: candidates
};
});
const navData = computed(() => {
return siteDataByRoute.value.themeConfig.nav;
});
return {
navData,
repoInfo,
localeCandidates
};
}
};

View File

@@ -0,0 +1,33 @@
<template>
<nav class="nav-links" v-if="navData || repoInfo">
<template v-if="navData">
<template v-for="item of navData">
<NavDropdownLink v-if="item.items" :item="item" />
<NavBarLink v-else :item="item" />
</template>
</template>
<NavDropdownLink v-if="localeCandidates" :item="localeCandidates" />
<NavBarLink v-if="repoInfo" :item="repoInfo" />
</nav>
</template>
<script src="./NavBarLinks"></script>
<style>
.nav-links {
display: flex;
align-items: center;
height: 35px;
list-style-type: none;
transform: translateY(1px);
}
@media screen and (max-width: 719px) {
.nav-links {
display: block;
height: auto;
padding: 0.5rem 0 1rem;
border-bottom: 1px solid var(--border-color);
}
}
</style>

View File

@@ -0,0 +1,15 @@
import { PropType } from 'vue';
import { DefaultTheme } from '../config';
declare const _default: import("vue").DefineComponent<{
item: {
type: PropType<DefaultTheme.NavItemWithChildren>;
required: true;
};
}, {
open: import("vue").Ref<boolean>;
setOpen: (value: boolean) => void;
isLastItemOfArray: <T>(item: T, array: T[]) => boolean | 0;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, Record<string, any>, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<{
item: DefaultTheme.NavItemWithChildren;
} & {}>, {}>;
export default _default;

View File

@@ -0,0 +1,33 @@
import NavBarLink from './NavBarLink.vue';
import { defineComponent, ref, watch } from 'vue';
import { useRoute } from 'vitepress';
export default defineComponent({
name: 'DropdownLink',
components: {
NavBarLink
},
props: {
item: {
type: Object,
required: true
}
},
setup(props) {
const open = ref(false);
const route = useRoute();
watch(() => route.path, () => {
open.value = false;
});
const setOpen = (value) => {
open.value = value;
};
const isLastItemOfArray = (item, array) => {
return array.length && array.indexOf(item) === array.length - 1;
};
return {
open,
setOpen,
isLastItemOfArray
};
}
});

View File

@@ -0,0 +1,218 @@
<template>
<div class="dropdown-wrapper" :class="{ open }">
<button
class="dropdown-title"
type="button"
:aria-label="item.ariaLabel"
@click="setOpen(!open)"
>
<span>{{ item.text }}</span>
<span class="arrow" :class="open ? 'down' : 'right'" />
</button>
<ul class="nav-dropdown">
<li v-for="(subItem, index) in item.items" :key="subItem.link || index" class="dropdown-item">
<h4 v-if="subItem.items">{{ subItem.text }}</h4>
<ul v-if="subItem.items" class="dropdown-subitem-wrapper">
<li
v-for="childSubItem in subItem.items"
:key="childSubItem.link"
class="dropdown-subitem"
>
<NavBarLink
:item="childSubItem"
@focusout="
isLastItemOfArray(childSubItem, subItem.items) &&
isLastItemOfArray(subItem, item.items) &&
setOpen(false)
"
/>
</li>
</ul>
<NavBarLink
v-else
:item="subItem"
@focusout="isLastItemOfArray(subItem, item.items) && setOpen(false)"
/>
</li>
</ul>
</div>
</template>
<script src="./NavDropdownLink"></script>
<style>
.dropdown-wrapper {
position: relative;
cursor: pointer;
display: block;
margin-left: 1.5rem;
}
.dropdown-wrapper .dropdown-title {
font: inherit;
color: var(--text-color);
font-size: 0.9rem;
font-weight: 500;
display: inline-block;
height: 1.75rem;
line-height: 1.75rem;
padding: inherit;
background: transparent;
border: none;
}
.dropdown-wrapper .dropdown-title:hover {
border-color: transparent;
}
.dropdown-wrapper .dropdown-title .arrow {
display: inline-block;
vertical-align: middle;
margin-top: -1px;
margin-left: 0.4rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item {
color: inherit;
line-height: 1.7rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item h4 {
margin: 0.45rem 0 0;
border-top: 1px solid #eee;
padding: 0.45rem 1.5rem 0 1.25rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item .nav-item {
margin-left: 0.5rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper {
padding: 0;
list-style: none;
}
.dropdown-wrapper
.nav-dropdown
.dropdown-item
.dropdown-subitem-wrapper
.dropdown-subitem {
font-size: 0.9em;
}
.dropdown-wrapper .nav-dropdown .dropdown-item a {
display: block;
line-height: 1.7rem;
position: relative;
border-bottom: none;
font-weight: 400;
margin-bottom: 0;
margin-left: 0;
padding: 0 1.5rem 0 1.25rem;
}
.dropdown-wrapper .nav-dropdown .dropdown-item a:hover {
color: var(--accent-color);
}
.dropdown-wrapper .nav-dropdown .dropdown-item a.active {
color: var(--accent-color);
}
.dropdown-wrapper .nav-dropdown .dropdown-item a.active::after {
content: '';
width: 0;
height: 0;
border-left: 5px solid var(--accent-color);
border-top: 3px solid transparent;
border-bottom: 3px solid transparent;
position: absolute;
top: calc(50% - 1.5px);
left: 8px;
}
.dropdown-wrapper .nav-dropdown .dropdown-item:first-child h4 {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dropdown-wrapper {
height: 1.8rem;
}
.dropdown-wrapper:hover .nav-dropdown,
.dropdown-wrapper.open .nav-dropdown {
display: block;
}
.dropdown-wrapper.open:blur {
display: none;
}
.dropdown-wrapper .dropdown-title .arrow {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 6px solid #aaa;
border-bottom: 0;
}
.dropdown-wrapper .nav-dropdown {
display: none;
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;
}
@media screen and (max-width: 719px) {
.dropdown-wrapper {
height: auto;
margin-left: 1.5rem;
}
.dropdown-wrapper .dropdown-title {
font-size: 1rem;
font-weight: 600;
}
.dropdown-wrapper .nav-dropdown {
position: relative;
top: none;
right: none;
border: none;
padding: 4px 0;
background-color: transparent;
}
.dropdown-wrapper:hover .nav-dropdown {
display: none;
}
.dropdown-wrapper.open .nav-dropdown {
display: block;
}
.dropdown-wrapper .nav-dropdown .dropdown-item .nav-item {
margin: 0;
padding: 0;
}
.dropdown-wrapper .nav-dropdown .dropdown-item .nav-link {
font-size: 0.9rem;
}
}
</style>

View File

@@ -0,0 +1,14 @@
declare const _default: {
setup(): {
next: import("vue").ComputedRef<{
text: string;
link: string;
} | undefined>;
prev: import("vue").ComputedRef<{
text: string;
link: string;
} | undefined>;
hasLinks: import("vue").ComputedRef<boolean>;
};
};
export default _default;

View File

@@ -0,0 +1,52 @@
import { computed } from 'vue';
import { useRoute, useSiteData } from 'vitepress';
export default {
setup() {
const route = useRoute();
// TODO: could this be useSiteData<DefaultTheme.Config> or is the siteData
// resolved and has a different structure?
const siteData = useSiteData();
const resolveLink = (targetLink) => {
let target;
Object.keys(siteData.value.themeConfig.sidebar).some((k) => {
return siteData.value.themeConfig.sidebar[k].some((v) => {
if (Array.isArray(v.children)) {
target = v.children.find((value) => {
return value.link === targetLink;
});
}
return !!target;
});
});
return target;
};
const next = computed(() => {
const pageData = route.data;
if (pageData.frontmatter.next === false) {
return undefined;
}
if (typeof pageData.frontmatter.next === 'string') {
return resolveLink(pageData.frontmatter.next);
}
return pageData.next;
});
const prev = computed(() => {
const pageData = route.data;
if (pageData.frontmatter.prev === false) {
return undefined;
}
if (typeof pageData.frontmatter.prev === 'string') {
return resolveLink(pageData.frontmatter.prev);
}
return pageData.prev;
});
const hasLinks = computed(() => {
return !!next || !!prev;
});
return {
next,
prev,
hasLinks
};
}
};

View File

@@ -0,0 +1,35 @@
<template>
<div v-if="hasLinks" class="links-wrapper">
<div class="prev-link">
<div v-if="prev">
<a :href="prev.link">{{ prev.text }}</a>
</div>
</div>
<div class="next-link">
<div v-if="next">
<a :href="next.link">{{ next.text }}</a>
</div>
</div>
</div>
</template>
<script src="./NextAndPrevLinks"></script>
<style>
.links-wrapper {
display: flex;
justify-content: space-between;
margin-top: 4rem;
border-top: 1px solid var(--border-color);
padding-top: 1rem;
padding-bottom: 2rem;
}
.links-wrapper a {
font-weight: 500;
}
.links-wrapper a:hover {
text-decoration: none !important;
}
</style>

View File

@@ -0,0 +1,42 @@
<template>
<div class="content">
<slot name="top" />
<Content />
<NextAndPrevLinks />
<PageEdit />
<slot name="bottom" />
</div>
</template>
<script>
import NextAndPrevLinks from './NextAndPrevLinks.vue'
import PageEdit from './PageEdit.vue'
export default {
components: { NextAndPrevLinks, PageEdit }
}
</script>
<style>
.content {
margin: 0 auto;
padding: 0.025rem 2.5rem 2rem;
/* if this is moved to a variable, add it to BuySellAds.vue */
max-width: 50rem;
}
.content a {
color: var(--accent-color);
}
.content a:hover {
text-decoration: underline;
}
.content img {
max-width: 100%;
}
/*
.content div > h1:first-child, .content div > h2:first-child {
margin-top: 0;
} */
</style>

View File

@@ -0,0 +1,10 @@
declare const _default: {
components: {
OutboundLink: import("vue").ComponentOptions<{}, any, any, any, any, any, any, any>;
};
setup(): {
editLink: import("vue").ComputedRef<string | null>;
editLinkText: import("vue").ComputedRef<string>;
};
};
export default _default;

View File

@@ -0,0 +1,50 @@
import { computed } from 'vue';
import OutboundLink from './icons/OutboundLink.vue';
import { endingSlashRE, isExternal } from '../utils';
import { useRoute, useSiteData } from 'vitepress';
function createEditLink(repo, docsRepo, docsDir, docsBranch, path) {
const bitbucket = /bitbucket.org/;
if (bitbucket.test(repo)) {
const base = isExternal(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 = isExternal(docsRepo)
? docsRepo
: `https://github.com/${docsRepo}`;
return (base.replace(endingSlashRE, '') +
`/edit` +
`/${docsBranch}/` +
(docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '') +
path);
}
export default {
components: {
OutboundLink
},
setup() {
const route = useRoute();
const siteData = useSiteData();
const editLink = computed(() => {
const pageData = route.data;
const showEditLink = pageData.frontmatter.editLink == null
? siteData.value.themeConfig.editLinks
: pageData.frontmatter.editLink;
const { repo, docsDir = '', docsBranch = 'master', docsRepo = repo } = siteData.value.themeConfig;
const { relativePath } = pageData;
if (showEditLink && relativePath && repo) {
return createEditLink(repo, docsRepo || repo, docsDir, docsBranch, relativePath);
}
return null;
});
const editLinkText = computed(() => siteData.value.themeConfig.editLinkText || 'Edit this page');
return {
editLink,
editLinkText
};
}
};

View File

@@ -0,0 +1,28 @@
<template>
<footer class="page-edit">
<a
v-if="editLink"
:href="editLink"
target="_blank"
rel="noopener noreferrer"
>
{{ editLinkText }}
<OutboundLink />
</a>
</footer>
</template>
<script src="./PageEdit"></script>
<style>
.page-edit {
padding-top: 1rem;
padding-bottom: 1rem;
overflow: auto;
}
.page-edit a {
color: var(--text-color);
margin-right: 0.25rem;
}
</style>

View File

@@ -0,0 +1,288 @@
<template>
<div class="search-box">
<input
ref="input"
aria-label="Search"
:value="query"
:class="{ 'focused': focused }"
:placeholder="placeholder"
autocomplete="off"
spellcheck="false"
@input="query = $event.target.value"
@focus="focused = true"
@blur="focused = false"
@keyup.enter="go(focusIndex)"
@keyup.up="onUp"
@keyup.down="onDown"
>
<ul
v-if="showSuggestions"
class="suggestions"
:class="{ 'align-right': alignRight }"
@mouseleave="unfocus"
>
<li
v-for="(s, i) in suggestions"
:key="i"
class="suggestion"
: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>
</template>
<script>
import matchQuery from './match-query'
/* global SEARCH_MAX_SUGGESTIONS, SEARCH_PATHS, SEARCH_HOTKEYS */
export default {
name: 'SearchBox',
data () {
return {
query: '',
focused: false,
focusIndex: 0,
placeholder: undefined
}
},
computed: {
showSuggestions () {
return (
this.focused
&& this.suggestions
&& this.suggestions.length
)
},
suggestions () {
const query = this.query.trim().toLowerCase()
if (!query) {
return
}
const { pages } = this.$site
const max = this.$site.themeConfig.searchMaxSuggestions || SEARCH_MAX_SUGGESTIONS
const localePath = this.$localePath
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
}
// filter out results that do not match searchable paths
if (!this.isSearchable(p)) {
continue
}
if (matchQuery(query, 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 (h.title && matchQuery(query, p, h.title)) {
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
}
},
mounted () {
this.placeholder = this.$site.themeConfig.searchPlaceholder || ''
document.addEventListener('keydown', this.onHotkey)
},
beforeDestroy () {
document.removeEventListener('keydown', this.onHotkey)
},
methods: {
getPageLocalePath (page) {
for (const localePath in this.$site.locales || {}) {
if (localePath !== '/' && page.path.indexOf(localePath) === 0) {
return localePath
}
}
return '/'
},
isSearchable (page) {
let searchPaths = SEARCH_PATHS
// all paths searchables
if (searchPaths === null) { return true }
searchPaths = Array.isArray(searchPaths) ? searchPaths : new Array(searchPaths)
return searchPaths.filter(path => {
return page.path.match(path)
}).length > 0
},
onHotkey (event) {
if (event.srcElement === document.body && SEARCH_HOTKEYS.includes(event.key)) {
this.$refs.input.focus()
event.preventDefault()
}
},
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">
.search-box
display inline-block
position relative
margin-right 1rem
input
cursor text
width 10rem
height: 2rem
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 2 rem
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
white-space normal
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
&:focus
cursor text
left 0
width 10rem
// Match IE11
@media all and (-ms-high-contrast: none)
.search-box input
height 2rem
@media (max-width: $MQNarrow) and (min-width: $MQMobile)
.search-box
.suggestions
left 0
@media (max-width: $MQMobile)
.search-box
margin-right 0
input
left 1rem
.suggestions
right 0
@media (max-width: $MQMobileNarrow)
.search-box
.suggestions
width calc(100vw - 4rem)
input:focus
width 8rem
</style>

View File

@@ -0,0 +1,19 @@
export interface ResolvedSidebarItem {
text: string;
link?: string;
isGroup?: boolean;
children?: ResolvedSidebarItem[];
}
declare type ResolvedSidebar = ResolvedSidebarItem[];
declare const _default: {
components: {
NavBarLinks: import("vue").ComponentOptions<{}, any, any, any, any, any, any, any>;
SideBarItem: import("vue").FunctionalComponent<{
item: ResolvedSidebarItem;
}, {}>;
};
setup(): {
items: import("vue").ComputedRef<ResolvedSidebar | undefined>;
};
};
export default _default;

View File

@@ -0,0 +1,89 @@
import { useRoute, useSiteDataByRoute } from 'vitepress';
import { computed } from 'vue';
import { getPathDirName } from '../utils';
import { useActiveSidebarLinks } from '../composables/activeSidebarLink';
import NavBarLinks from './NavBarLinks.vue';
import { SideBarItem } from './SideBarItem';
export default {
components: {
NavBarLinks,
SideBarItem
},
setup() {
const route = useRoute();
const siteData = useSiteDataByRoute();
useActiveSidebarLinks();
return {
items: computed(() => {
const { headers, frontmatter: { sidebar, sidebarDepth = 2 } } = route.data;
if (sidebar === 'auto') {
// auto, render headers of current page
return resolveAutoSidebar(headers, sidebarDepth);
}
else if (Array.isArray(sidebar)) {
// in-page array config
return resolveArraySidebar(sidebar, sidebarDepth);
}
else if (sidebar === false) {
return [];
}
else {
// no explicit page sidebar config
// check global theme config
const { sidebar: themeSidebar } = siteData.value.themeConfig;
if (themeSidebar === 'auto') {
return resolveAutoSidebar(headers, sidebarDepth);
}
else if (Array.isArray(themeSidebar)) {
return resolveArraySidebar(themeSidebar, sidebarDepth);
}
else if (themeSidebar === false) {
return [];
}
else if (typeof themeSidebar === 'object') {
return resolveMultiSidebar(themeSidebar, route.path, headers, sidebarDepth);
}
}
})
};
}
};
function resolveAutoSidebar(headers, depth) {
const ret = [];
if (headers === undefined) {
return [];
}
let lastH2 = undefined;
headers.forEach(({ level, title, slug }) => {
if (level - 1 > depth) {
return;
}
const item = {
text: title,
link: `#${slug}`
};
if (level === 2) {
lastH2 = item;
ret.push(item);
}
else if (lastH2) {
;
(lastH2.children || (lastH2.children = [])).push(item);
}
});
return ret;
}
function resolveArraySidebar(config, depth) {
return config;
}
function resolveMultiSidebar(config, path, headers, depth) {
const paths = [path, Object.keys(config)[0]];
const item = paths.map((x) => config[getPathDirName(x)]).find(Boolean);
if (Array.isArray(item)) {
return resolveArraySidebar(item, depth);
}
if (item === 'auto') {
return resolveAutoSidebar(headers, depth);
}
return [];
}

View File

@@ -0,0 +1,99 @@
<template>
<NavBarLinks class="show-mobile" />
<slot name="top" />
<ul class="sidebar">
<SideBarItem v-for="item of items" :item="item" />
</ul>
<slot name="bottom" />
</template>
<script src="./SideBar"></script>
<style>
.show-mobile {
display: none;
}
@media screen and (max-width: 719px) {
.show-mobile {
display: block;
}
}
.sidebar,
.sidebar-items {
list-style-type: none;
line-height: 2;
padding: 0;
margin: 0;
}
.sidebar {
padding: 1.5rem 0;
}
.sidebar-data {
padding: 1.5rem 0;
}
@media screen and (max-width: 719px) {
.sidebar-data {
padding: 1rem;
}
}
.sidebar-items .sidebar-items {
padding-left: 1rem;
}
.sidebar-items .sidebar-items .sidebar-link {
border-left: 0;
}
.sidebar-items .sidebar-items .sidebar-link.active {
font-weight: 500;
}
.sidebar-items .sidebar-link {
padding: 0.35rem 1rem 0.35rem 2rem;
line-height: 1.4;
font-size: 0.95em;
font-weight: 400;
}
.sidebar-item + .sidebar-item {
padding-top: 0.75rem;
}
.sidebar-items > .sidebar-item + .sidebar-item {
padding-top: 0;
}
.sidebar-link {
display: block;
margin: 0;
border-left: 0.25rem solid transparent;
padding: 0.35rem 1.5rem 0.35rem 1.25rem;
line-height: 1.7;
font-size: 1.05em;
font-weight: 700;
color: var(--text-color);
}
a.sidebar-link {
transition: color 0.15s ease;
}
a.sidebar-link:hover {
color: var(--accent-color);
}
a.sidebar-link.active {
border-left-color: var(--accent-color);
font-weight: 600;
color: var(--accent-color);
}
</style>

View File

@@ -0,0 +1,5 @@
import { FunctionalComponent } from 'vue';
import { ResolvedSidebarItem } from './SideBar';
export declare const SideBarItem: FunctionalComponent<{
item: ResolvedSidebarItem;
}>;

View File

@@ -0,0 +1,61 @@
import { useRoute, useSiteData } from 'vitepress';
import { h } from 'vue';
import { joinUrl, isActive } from '../utils';
export const SideBarItem = (props) => {
const { item: { link: relLink, text, children } } = props;
const route = useRoute();
const siteData = useSiteData();
const link = resolveLink(siteData.value.base, relLink || '');
const active = isActive(route, link);
const headers = route.data.headers;
const childItems = createChildren(active, children, headers);
return h('li', { class: 'sidebar-item' }, [
h(link ? 'a' : 'p', {
class: { 'sidebar-link': true, active },
href: link
}, text),
childItems
]);
};
function resolveLink(base, path) {
return path
? // keep relative hash to the same page
path.startsWith('#')
? path
: joinUrl(base, path)
: undefined;
}
function createChildren(active, children, headers) {
if (children && children.length > 0) {
return h('ul', { class: 'sidebar-items' }, children.map((c) => {
return h(SideBarItem, { item: c });
}));
}
return active && headers
? createChildren(false, resolveHeaders(headers))
: null;
}
function resolveHeaders(headers) {
return mapHeaders(groupHeaders(headers));
}
function groupHeaders(headers) {
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);
}
function mapHeaders(headers) {
return headers.map((header) => ({
text: header.title,
link: `#${header.slug}`,
children: header.children ? mapHeaders(header.children) : undefined
}));
}

View File

@@ -0,0 +1,46 @@
<template>
<div class="sidebar-button" @click="$emit('toggle')">
<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>
<script>
export default {
emits: ['toggle']
}
</script>
<style>
.sidebar-button {
position: absolute;
top: 0.6rem;
left: 1rem;
display: none;
padding: 0.6rem;
cursor: pointer;
}
.sidebar-button .icon {
display: block;
width: 1.25rem;
height: 1.25rem;
}
@media screen and (max-width: 719px) {
.sidebar-button {
display: block;
}
}
</style>

View File

@@ -0,0 +1,31 @@
<template functional>
<svg
class="icon outbound"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
x="0px"
y="0px"
viewBox="0 0 100 100"
width="15"
height="15"
>
<path
fill="currentColor"
d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"
/>
<polygon
fill="currentColor"
points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"
/>
</svg>
</template>
<style>
.icon.outbound {
color: #aaa;
display: inline-block;
vertical-align: middle;
position: relative;
top: -1px;
}
</style>

View File

@@ -0,0 +1,50 @@
import get from 'lodash.get'
export default (query, page, additionalStr = null) => {
let domain = get(page, 'title', '')
if (get(page, 'frontmatter.tags')) {
domain += ` ${page.frontmatter.tags.join(' ')}`
}
if (additionalStr) {
domain += ` ${additionalStr}`
}
return matchTest(query, domain)
}
const matchTest = (query, domain) => {
const escapeRegExp = (str) => str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
// eslint-disable-next-line no-control-regex
const nonASCIIRegExp = new RegExp('[^\x00-\x7F]')
const words = query
.split(/\s+/g)
.map((str) => str.trim())
.filter((str) => !!str)
if (!nonASCIIRegExp.test(query)) {
// if the query only has ASCII chars, treat as English
const hasTrailingSpace = query.endsWith(' ')
const searchRegex = new RegExp(
words
.map((word, index) => {
if (words.length === index + 1 && !hasTrailingSpace) {
// The last word - ok with the word being "startswith"-like
return `(?=.*\\b${escapeRegExp(word)})`
} else {
// Not the last word - expect the whole word exactly
return `(?=.*\\b${escapeRegExp(word)}\\b)`
}
})
.join('') + '.+',
'gi'
)
return searchRegex.test(domain)
} else {
// if the query has non-ASCII chars, treat as other languages
return words.some((word) => domain.toLowerCase().indexOf(word) > -1)
}
}

View File

@@ -0,0 +1 @@
export declare function useActiveSidebarLinks(): void;

View File

@@ -0,0 +1,78 @@
import { onMounted, onUnmounted, onUpdated } from 'vue';
export function useActiveSidebarLinks() {
let rootActiveLink = null;
let activeLink = null;
const decode = decodeURIComponent;
const deactiveLink = (link) => link && link.classList.remove('active');
const activateLink = (hash) => {
deactiveLink(activeLink);
deactiveLink(rootActiveLink);
activeLink = document.querySelector(`.sidebar a[href="${hash}"]`);
if (activeLink) {
activeLink.classList.add('active');
// also add active class to parent h2 anchors
const rootLi = activeLink.closest('.sidebar > ul > li');
if (rootLi && rootLi !== activeLink.parentElement) {
rootActiveLink = rootLi.querySelector('a');
rootActiveLink && rootActiveLink.classList.add('active');
}
else {
rootActiveLink = null;
}
}
};
const setActiveLink = () => {
const sidebarLinks = [].slice.call(document.querySelectorAll('.sidebar a'));
const anchors = [].slice
.call(document.querySelectorAll('.header-anchor'))
.filter((anchor) => sidebarLinks.some((sidebarLink) => sidebarLink.hash === anchor.hash));
const pageOffset = document.querySelector('.navbar')
.offsetHeight;
const scrollTop = window.scrollY;
const getAnchorTop = (anchor) => anchor.parentElement.offsetTop - pageOffset - 15;
for (let i = 0; i < anchors.length; i++) {
const anchor = anchors[i];
const nextAnchor = anchors[i + 1];
const isActive = (i === 0 && scrollTop === 0) ||
(scrollTop >= getAnchorTop(anchor) &&
(!nextAnchor || scrollTop < getAnchorTop(nextAnchor)));
// TODO: fix case when at page bottom
if (isActive) {
const targetHash = decode(anchor.hash);
history.replaceState(null, document.title, targetHash);
activateLink(targetHash);
return;
}
}
};
const onScroll = throttleAndDebounce(setActiveLink, 300);
onMounted(() => {
setActiveLink();
window.addEventListener('scroll', onScroll);
});
onUpdated(() => {
// sidebar update means a route change
activateLink(decode(location.hash));
});
onUnmounted(() => {
window.removeEventListener('scroll', onScroll);
});
}
function throttleAndDebounce(fn, delay) {
let timeout;
let called = false;
return () => {
if (timeout)
clearTimeout(timeout);
if (!called) {
fn();
called = true;
setTimeout(() => {
called = false;
}, delay);
}
else {
timeout = setTimeout(fn, delay);
}
};
}

View File

@@ -0,0 +1,94 @@
export declare namespace DefaultTheme {
interface Config {
logo?: string;
nav?: NavItem[] | false;
sidebar?: SideBarConfig | MultiSideBarConfig;
search?: SearchConfig | false;
/**
* GitHub repository following the format <user>/<project>.
*
* @example vuejs/vue-next
*/
repo?: string;
/**
* Customize the header label. Defaults to GitHub/Gitlab/Bitbucket depending
* on the provided repo
*
* @exampe `"Contribute!"`
*/
repoLabel?: string;
/**
* If your docs are in a different repository from your main project
*
* @example `"vuejs/docs-next"`
*/
docsRepo?: string;
/**
* If your docs are not at the root of the repo.
*
* @example `"docs"`
*/
docsDir?: string;
/**
* If your docs are in a different branch. Defaults to `master`
* @example `"next"`
*/
docsBranch?: string;
/**
* Enable links to edit pages at the bottom of the page
*/
editLinks?: boolean;
/**
* Custom text for edit link. Defaults to "Edit this page"
*/
editLinkText?: string;
lastUpdated?: string | boolean;
prevLink?: boolean;
nextLink?: boolean;
}
type NavItem = NavItemWithLink | NavItemWithChildren;
interface NavItemWithLink extends NavItemBase {
link: string;
}
interface NavItemWithChildren extends NavItemBase {
items: NavItem[];
}
interface NavItemBase {
text: string;
target?: string;
rel?: string;
ariaLabel?: string;
}
type SideBarConfig = SideBarItem[] | 'auto' | false;
interface MultiSideBarConfig {
[path: string]: SideBarConfig;
}
type SideBarItem = SideBarLink | SideBarGroup;
interface SideBarLink {
text: string;
link: string;
}
interface SideBarGroup {
text: string;
link?: string;
/**
* @default false
*/
collapsable?: boolean;
children: SideBarItem[];
}
interface SearchConfig {
/**
* @default 5
*/
maxSuggestions?: number;
/**
* @default ''
*/
placeholder?: string;
algolia?: {
apiKey: string;
indexName: string;
};
}
}

View File

View File

@@ -0,0 +1,7 @@
import './styles/vars.css';
import './styles/layout.css';
import './styles/code.css';
import './styles/custom-blocks.css';
import { Theme } from '../app/theme';
declare const theme: Theme;
export default theme;

View File

@@ -0,0 +1,11 @@
import './styles/vars.css';
import './styles/layout.css';
import './styles/code.css';
import './styles/custom-blocks.css';
import Layout from './Layout.vue';
import NotFound from './NotFound.vue';
const theme = {
Layout,
NotFound
};
export default theme;

View File

@@ -0,0 +1,271 @@
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, Courier New, monospace;
font-size: 0.85em;
color: var(--text-color-light);
background-color: rgba(27, 31, 35, 0.05);
padding: 0.25rem 0.5rem;
border-radius: 3px;
margin: 0;
}
code .token.deleted {
color: #ec5975;
}
code .token.inserted {
color: var(--accent-color);
}
div[class*='language-'] {
line-height: 1.5;
padding: 0.5rem 1.5rem;
background-color: var(--code-bg-color);
border-radius: 6px;
overflow-x: auto;
position: relative;
margin: 0.85rem 0;
}
[class*='language-'] pre {
background: transparent;
position: relative;
z-index: 1;
}
[class*='language-'] code {
color: #eee;
padding: 0;
}
[class*='language-'] code,
[class*='language-'] pre {
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Line highlighting */
.highlight-lines {
font-size: 0.9em;
user-select: none;
padding-top: 1.3rem;
position: absolute;
top: 0;
left: 0;
width: 100%;
line-height: 1.5;
}
.highlight-lines .highlighted {
background-color: rgba(0, 0, 0, 66%);
}
/* Line numbers mode */
div[class*='language-'].line-numbers-mode {
padding-left: 5rem;
}
.line-numbers-wrapper {
position: absolute;
top: 0;
left: 0;
width: 3.5rem;
text-align: center;
color: #888;
line-height: 1.5;
font-size: 0.9em;
padding: 1.3rem 0;
border-right: 1px solid rgba(0,0,0,50%);
z-index: 4;
}
/* Language marker */
[class*='language-']:before {
position: absolute;
z-index: 3;
top: 0.6em;
right: 1em;
font-size: 0.8rem;
color: #888;
}
[class~='language-html']:before,
[class~='language-markup']:before {
content: 'html';
}
[class~='language-md']:before,
[class~='language-markdown']:before {
content: 'md';
}
[class~='language-css']:before {
content: 'css';
}
[class~='language-sass']:before {
content: 'sass';
}
[class~='language-scss']:before {
content: 'scss';
}
[class~='language-less']:before {
content: 'less';
}
[class~='language-stylus']:before {
content: 'styl';
}
[class~='language-js']:before,
[class~='language-typescript']:before {
content: 'js';
}
[class~='language-ts']:before,
[class~='language-typescript']:before {
content: 'ts';
}
[class~='language-json']:before {
content: 'json';
}
[class~='language-rb']:before,
[class~='language-ruby']:before {
content: 'rb';
}
[class~='language-py']:before,
[class~='language-python']:before {
content: 'py';
}
[class~='language-sh']:before,
[class~='language-bash']:before {
content: 'sh';
}
[class~='language-php']:before {
content: 'php';
}
[class~='language-go']:before {
content: 'go';
}
[class~='language-rust']:before {
content: 'rust';
}
[class~='language-java']:before {
content: 'java';
}
[class~='language-c']:before {
content: 'c';
}
[class~='language-yaml']:before {
content: 'yaml';
}
[class~='language-dockerfile']:before {
content: 'dockerfile';
}
/**
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
* Based on https://github.com/chriskempson/tomorrow-theme
* @author Rose Pritchard
*/
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #999;
}
.token.punctuation {
color: #ccc;
}
.token.tag,
.token.attr-name,
.token.namespace,
.token.deleted {
color: #e2777a;
}
.token.function-name {
color: #6196cc;
}
.token.boolean,
.token.number,
.token.function {
color: #f08d49;
}
.token.property,
.token.class-name,
.token.constant,
.token.symbol {
color: #f8c555;
}
.token.selector,
.token.important,
.token.atrule,
.token.keyword,
.token.builtin {
color: #cc99cd;
}
.token.string,
.token.char,
.token.attr-value,
.token.regex,
.token.variable {
color: #7ec699;
}
.token.operator,
.token.entity,
.token.url {
color: #67cdcc;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.inserted {
color: green;
}

View File

@@ -0,0 +1,70 @@
.custom-block .custom-block-title {
font-weight: 600;
margin-bottom: -0.4rem;
}
.custom-block.tip,
.custom-block.warning,
.custom-block.danger {
padding: 0.1rem 1.5rem;
border-left-width: 0.5rem;
border-left-style: solid;
margin: 1rem 0;
}
.custom-block.tip {
background-color: #f3f5f7;
border-color: #42b983;
}
.custom-block.warning {
background-color: rgba(255, 229, 100, 0.3);
border-color: #e7c000;
color: #6b5900;
}
.custom-block.warning .custom-block-title {
color: #b29400;
}
.custom-block.warning a {
color: var(--text-color);
}
.custom-block.danger {
background-color: #ffe6e6;
border-color: #c00;
color: #4d0000;
}
.custom-block.danger .custom-block-title {
color: #900;
}
.custom-block.danger a {
color: var(--text-color);
}
.custom-block.details {
display: block;
position: relative;
border-radius: 2px;
margin: 1.6em 0;
padding: 1.6em;
background-color: #eee;
}
.custom-block.details h4 {
margin-top: 0;
}
.custom-block.details figure:last-child,
.custom-block.details p:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
.custom-block.details summary {
outline: none;
cursor: pointer;
}

View File

@@ -0,0 +1,240 @@
body {
padding: 0;
margin: 0;
background-color: #fff;
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;
}
* {
box-sizing: border-box;
}
.theme {
font-size: 16px;
color: var(--text-color);
}
.theme .navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--header-height);
background-color: #fff;
border-bottom: 1px solid var(--border-color);
z-index: 4;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.7rem 1.5rem;
}
@media screen and (max-width: 719px) {
.theme .navbar {
padding-left: 4rem;
}
}
.theme aside {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: var(--sidebar-width);
padding: var(--header-height) 0 1.5rem 0;
border-right: 1px solid var(--border-color);
background-color: #fff;
z-index: 3;
overflow-y: auto;
}
.theme.sidebar-open .sidebar-mask {
display: block;
}
.theme.no-navbar > h1,
.theme.no-navbar > h2,
.theme.no-navbar > h3,
.theme.no-navbar > h4,
.theme.no-navbar > h5,
.theme.no-navbar > h6 {
margin-top: 1.5rem;
padding-top: 0;
}
.theme.no-navbar aside {
top: 0;
}
@media screen and (max-width: 959px) {
.theme aside {
width: var(--sidebar-width);
}
}
@media screen and (max-width: 719px) {
.theme aside {
transition: transform 0.2s ease;
transform: translateX(-100%);
}
.theme aside.open {
transform: translateX(0);
}
}
.sidebar-mask {
z-index: 2;
position: fixed;
width: 100vw;
height: 100vh;
display: none;
}
.theme main {
padding-top: var(--header-height);
}
@media screen and (min-width: 960px) {
.theme main {
padding-left: var(--sidebar-width);
}
}
@media screen and (min-width: 720px) {
.theme.no-sidebar aside {
display: none;
}
.theme.no-sidebar main {
margin-left: 0;
}
}
@media screen and (max-width: 959px) {
.theme main {
margin-left: var(--sidebar-width);
}
}
@media screen and (max-width: 719px) {
.theme main {
margin-left: 0;
}
}
.theme main.home {
padding: var(--header-height) 2rem 0;
max-width: 960px;
margin: 0px auto;
display: block;
}
@media screen and (max-width: 429px) {
.theme main.home {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
}
a {
text-decoration: none;
}
h1,
h2,
h3,
h4,
h5,
h6,
strong,
b {
font-weight: 600;
line-height: 1.6;
}
h1 {
font-size: 2.2rem;
}
h2 {
font-size: 1.65rem;
padding-bottom: 0.3rem;
border-bottom: 1px solid var(--border-color);
}
h3 {
font-size: 1.35rem;
}
h4 {
font-size: 1.15rem;
}
a.header-anchor {
font-size: 0.85em;
float: left;
margin-left: -0.87em;
padding-right: 0.23em;
margin-top: 0.125em;
opacity: 0;
}
a.header-anchor:hover {
text-decoration: none;
}
h1:hover .header-anchor,
h2:hover .header-anchor,
h3:hover .header-anchor,
h4:hover .header-anchor,
h5:hover .header-anchor,
h6:hover .header-anchor {
opacity: 1;
}
p,
ol,
ul {
line-height: 1.6;
}
ul,
ol {
padding-left: 1.25em;
}
table {
border-collapse: collapse;
margin: 1rem 0;
display: block;
overflow-x: auto;
}
tr {
border-top: 1px solid #dfe2e5;
}
tr:nth-child(2n) {
background-color: #f6f8fa;
}
th,
td {
border: 1px solid #dfe2e5;
padding: 0.6em 1em;
}
blockquote {
margin: 1rem 0;
border-left: 0.2rem solid #dfe2e5;
padding: 0.25rem 0 0.25rem 1rem;
font-size: 1rem;
color: #999;
}
blockquote > p {
margin: 0;
}

View File

@@ -0,0 +1,14 @@
.theme {
--border-color: rgb(226, 232, 240);
--header-height: 3.6rem;
--sidebar-width: 16.4rem;
--text-color: #2c3e50;
--text-color-light: #476582;
--code-bg-color: #282c34;
--accent-color: #3eaf7c;
/* responsive breakpoints */
/* --mq-narrow: 959px; */
/* --mq-mobile: 719px; */
/* --mq-mobile-narrow: 419px; */
}

View File

@@ -0,0 +1,17 @@
import { Route } from 'vitepress';
export declare const hashRE: RegExp;
export declare const extRE: RegExp;
export declare const endingSlashRE: RegExp;
export declare const outboundRE: RegExp;
export declare function withBase(path: string): string;
export declare function isExternal(path: string): boolean;
export declare function isActive(route: Route, path?: string): boolean;
export declare function normalize(path: string): string;
export declare function joinUrl(base: string, path: string): string;
/**
* get the path without filename (the last segment). for example, if the given
* path is `/guide/getting-started.html`, this method will return `/guide/`.
* Always with a trailing slash.
*/
export declare function getPathDirName(path: string): string;
export declare function ensureEndingSlash(path: string): string;

View File

@@ -0,0 +1,48 @@
import { useSiteData } from 'vitepress';
export const hashRE = /#.*$/;
export const extRE = /(index)?\.(md|html)$/;
export const endingSlashRE = /\/$/;
export const outboundRE = /^[a-z]+:/i;
export function withBase(path) {
return (useSiteData().value.base + path).replace(/\/+/g, '/');
}
export function isExternal(path) {
return outboundRE.test(path);
}
export function isActive(route, path) {
if (path === undefined) {
return false;
}
const routePath = normalize(route.path);
const pagePath = normalize(path);
return routePath === pagePath;
}
export function normalize(path) {
return decodeURI(path).replace(hashRE, '').replace(extRE, '');
}
export function joinUrl(base, path) {
const baseEndsWithSlash = base.endsWith('/');
const pathStartsWithSlash = path.startsWith('/');
if (baseEndsWithSlash && pathStartsWithSlash) {
return base.slice(0, -1) + path;
}
if (!baseEndsWithSlash && !pathStartsWithSlash) {
return `${base}/${path}`;
}
return base + path;
}
/**
* get the path without filename (the last segment). for example, if the given
* path is `/guide/getting-started.html`, this method will return `/guide/`.
* Always with a trailing slash.
*/
export function getPathDirName(path) {
const segments = path.split('/');
if (segments[segments.length - 1]) {
segments.pop();
}
return ensureEndingSlash(segments.join('/'));
}
export function ensureEndingSlash(path) {
return /(\.html|\/)$/.test(path) ? path : `${path}/`;
}