chore: resolve conflicts

pull/137/head
Evan You 5 years ago
commit ac43f05188

@ -0,0 +1,3 @@
github: [yyx990803, kiaking, posva, antfu, pikax]
open_collective: vuejs
patreon: evanyou

@ -0,0 +1,91 @@
## Git Commit Message Convention
> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
#### TL;DR:
Messages must be matched by the following regex:
``` js
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,50}/
```
#### Examples
Appears under "Features" header, `theme` subheader:
```
feat(theme): add home page feature
```
Appears under "Bug Fixes" header, `theme` subheader, with a link to issue #28:
```
fix(theme): remove underline on sidebar hover style
close #28
```
Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation:
```
perf: improve store getters performance by removing 'foo' option
BREAKING CHANGE: The 'foo' option has been removed.
```
The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header.
```
revert: feat(theme): add home page feature
This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
```
### Full Message Format
A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
The **header** is mandatory and the **scope** of the header is optional.
### Revert
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### Type
If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However, if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.
Other prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.
### Scope
The scope could be anything specifying the place of the commit change. For example `theme`, `compiler`, `ssr`, etc...
### Subject
The subject contains a succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize the first letter
* no dot (.) at the end
### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.
### Footer
The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.

@ -1,65 +1,48 @@
# VitePress Contributing Guide
This is a guide to help those who are interested in contributing to VitePress!
## Prerequisites
- [yarn](https://classic.yarnpkg.com/en/docs/cli/install/)
## Instructions
### Setup VitePress dev environment
1. Clone the VitePress repo
2. Install dependencies
```
yarn
```
3. Create symlink to allow projects to link to local VitePress dev environment
```bash
yarn link
```
- If it's successful, you should see the following message:
```
success Registered "vitepress".
info You can now run `yarn link "vitepress"` in the projects where you want to use this package and it will be used instead.
✨ Done in 0.05s.
```
4. Start VitePress local dev environment
```bash
yarn dev
```
### Setup local VitePress project
1. Open up terminal
1. Create a new folder
1. Initialize with `npm init`
1. Create a `docs` directory
1. Create an `index.md` file with some content inside of `/docs`
1. Add dependency to local VitePress dev environment
```bash
yarn link vitepress
```
1. Add script to run VitePress in `package.json`
- The following sample uses the command `dev` and assumes your VitePress site will live in the folder `docs`
```json
{
"name": "vitepress-project",
"dependencies": {},
"devDependencies": {},
"scripts": {
"dev": "vitepress dev docs",
"test": "echo \"Error: no test specified\" && exit 1"
}
}
```
- If successful, you should see a similar message to the following;
```
$ vitepress dev docs
vitepress v0.3.1
vite v0.20.2
listening at http://localhost:3000
```
And with that, you are now ready to contribute to the VitePress project! 🎉
Hi! We're really excited that you are interested in contributing to VitePress. Before submitting your contribution, please make sure to take a moment and read through the following guidelines:
- [Code of Conduct](https://github.com/vuejs/vue/blob/dev/.github/CODE_OF_CONDUCT.md)
- [Pull Request Guidelines](#pull-request-guidelines)
## Pull Request Guidelines
- Checkout a topic branch from the relevant branch, e.g. `master`, and merge back against that branch.
- If adding a new feature:
- Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
- If fixing bug:
- Provide a detailed description of the bug in the PR. Live demo preferred.
- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.
- Commit messages must follow the [commit message convention](./commit-convention.md) so that changelogs can be automatically generated.
## Development Setup
You will need [Yarn](https://classic.yarnpkg.com/en/docs/cli/install/)/
After cloning the repo, run:
```bash
$ yarn # install the dependencies of the project
```
### Setup VitePress Dev Environment
You may start VitePress local dev environment by running `yarn dev`.
```bash
$ yarn dev
```
The easiest way to start testing out VitePress is to tweak the VitePress docs. You may run `yarn docs` folder to boot up VitePress documentation site locally, with live reloading of the source code.
```bash
$ yarn docs
```
After executing the above command, visit http://localhost:3000 and try modifying the source code. You'll get live update.

6
.gitignore vendored

@ -1,7 +1,9 @@
node_modules
dist
/coverage
/src/client/shared
/src/node/shared
*.log
.DS_Store
.vite_opt_cache
dist
node_modules
TODOs.md

@ -1,3 +1,40 @@
# [0.9.0](https://github.com/vuejs/vitepress/compare/v0.8.1...v0.9.0) (2020-11-24)
### Bug Fixes
* avoid 300ms click delay on touch devices ([621ca3e](https://github.com/vuejs/vitepress/commit/621ca3e26f65e13e504724aef76aba3f3361ce81))
* fix nested list having too much margin ([b0cf2be](https://github.com/vuejs/vitepress/commit/b0cf2be5614505731a3b6dcebeab949c3639c2b2))
* fix sidebar active status not working as expected ([#140](https://github.com/vuejs/vitepress/issues/140)) ([#149](https://github.com/vuejs/vitepress/issues/149)) ([0b181e7](https://github.com/vuejs/vitepress/commit/0b181e7582ea4be7dca51ec399c697e32b7116f3))
* make code block look prettier ([#146](https://github.com/vuejs/vitepress/issues/146)) ([242fcc1](https://github.com/vuejs/vitepress/commit/242fcc1098f606f13e7d8e123c081e73a3d89366))
* some color in code block not working as expected ([#143](https://github.com/vuejs/vitepress/issues/143)) ([da09266](https://github.com/vuejs/vitepress/commit/da09266f5eede3796bb150ccd9d6a173e90354a4))
### Features
* add "last updated" feature ([40d204b](https://github.com/vuejs/vitepress/commit/40d204b2f68b90bd2c5e9940cd128c4c16cd5274))
## [0.8.1](https://github.com/vuejs/vitepress/compare/v0.8.0...v0.8.1) (2020-11-20)
### Bug Fixes
* fix "next and prev link" not working when `link` has extention ([6dcf6b3](https://github.com/vuejs/vitepress/commit/6dcf6b3796bb3d6e703fddd79c6b0c0a7adfd567))
* fix "next and prev links" not working when the `base` option is set ([#139](https://github.com/vuejs/vitepress/issues/139)) ([018a9b4](https://github.com/vuejs/vitepress/commit/018a9b46d924d0d08f7ff67f18a813348c84ab0a))
# [0.8.0](https://github.com/vuejs/vitepress/compare/v0.7.4...v0.8.0) (2020-11-20)
### Bug Fixes
* exit process with non-zero code on error ([fb09f8e](https://github.com/vuejs/vitepress/commit/fb09f8e638c06aec32494f731554fb1b989daaf0))
* fix edit link and prev and next links display ([#97](https://github.com/vuejs/vitepress/issues/97)) ([c3b7172](https://github.com/vuejs/vitepress/commit/c3b71729513592112e233165782e60c9c5b425c4))
* fix next and prev links not working ([#130](https://github.com/vuejs/vitepress/issues/130)) ([fdd498b](https://github.com/vuejs/vitepress/commit/fdd498be70cc09a4331dadd17c4a5339318f21bf))
* display header-anchor links when using keyboard navigation ([ddc3640](https://github.com/vuejs/vitepress/commit/ddc3640ce66f606894b31e1b7ebeacaaf7b0f1b5))
* show top part of scrollbar in sidebar ([#129](https://github.com/vuejs/vitepress/issues/129)) ([1ba209a](https://github.com/vuejs/vitepress/commit/1ba209a4d2b606bee1abb7ec1d383467d98cf198))
### Features
* add ability to configure markdown options ([#127](https://github.com/vuejs/vitepress/issues/127)) ([#128](https://github.com/vuejs/vitepress/issues/128)) ([463a03a](https://github.com/vuejs/vitepress/commit/463a03a9815ce8fc9f55293dda07bc211ef4f62b))
* add serve command ([#136](https://github.com/vuejs/vitepress/issues/136)) ([67868bd](https://github.com/vuejs/vitepress/commit/67868bd9281077a4ce708e666bf61a7824afb8b2))
* better build command output ([e435eec](https://github.com/vuejs/vitepress/commit/e435eec94a841ab0e1c14d59bb13608d5ad6a011))
## [0.7.4](https://github.com/vuejs/vitepress/compare/v0.7.3...v0.7.4) (2020-11-11)
### Bug Fixes

@ -2,15 +2,23 @@
[![npm](https://img.shields.io/npm/v/vitepress)](https://www.npmjs.com/package/vitepress)
> [VuePress](http://vuepress.vuejs.org/)' little brother, built on top of [vite](https://github.com/vuejs/vite)
---
**:fire: Note this is early WIP! Currently the focus is on making Vite stable and feature complete first. It is not recommended to use this for anything serious yet.**
---
VitePress is [VuePress](http://vuepress.vuejs.org/)' little brother, built on top of [vite](https://github.com/vuejs/vite).
## Documentation
To check out docs, visit [vitepress.vuejs.org](https://vitepress.vuejs.org).
## Want to contribute?
## Changelog
Detailed changes for each release are documented in the [release notes](https://github.com/vuejs/vitepress/releases).
## Contribution
Please make sure to read the [Contributing Guide](./.github/contributing.md) before making a pull request.

@ -0,0 +1,123 @@
import {
getSideBarConfig,
getFlatSideBarLinks
} from 'client/theme-default/support/sideBar'
describe('client/theme-default/support/sideBar', () => {
it('gets the correct sidebar items', () => {
expect(getSideBarConfig(false, '')).toEqual(false)
expect(getSideBarConfig('auto', '')).toEqual('auto')
const sidebar = [{ text: 'Title 01', link: 'title-01' }]
const expected = [{ text: 'Title 01', link: 'title-01' }]
expect(getSideBarConfig(sidebar, '')).toEqual(expected)
})
it('gets the correct sidebar items from the given path', () => {
const sidebar = {
'/': [{ text: 'R', link: 'r' }],
'/guide/': [{ text: 'G', link: 'g' }]
}
expect(getSideBarConfig(sidebar, '/')).toEqual(sidebar['/'])
expect(getSideBarConfig(sidebar, '/guide/')).toEqual(sidebar['/guide/'])
})
it('gets the correct sidebar items with various combination', () => {
const s = {
'/guide/': [{ text: 'G', link: 'g' }],
api: [{ text: 'A', link: 'a' }]
}
expect(getSideBarConfig(s, '/guide/')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, '/guide')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, 'guide/')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, 'guide/nested')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, '/guide/nested')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, 'guide/nested/')).toEqual(s['/guide/'])
expect(getSideBarConfig(s, '/api/')).toEqual(s['api'])
expect(getSideBarConfig(s, '/api')).toEqual(s['api'])
expect(getSideBarConfig(s, 'api/')).toEqual(s['api'])
expect(getSideBarConfig(s, 'api/nested')).toEqual(s['api'])
expect(getSideBarConfig(s, '/api/nested')).toEqual(s['api'])
expect(getSideBarConfig(s, 'api/nested/')).toEqual(s['api'])
expect(getSideBarConfig(s, '/')).toEqual('auto')
})
it('creates flat sidebar links', () => {
const sidebar = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]
const expected = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]
expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})
it('creates flat sidebar links with mixed sidebar group', () => {
const sidebar = [
{
text: 'Title 01',
link: '/title-01',
children: [
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' }
]
},
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]
const expected = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' },
{ text: 'Title 02', link: '/title-02' },
{ text: 'Title 03', link: '/title-03' }
]
expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})
it('ignores any items with no `link` property', () => {
const sidebar = [
{
text: 'Title 01',
children: [
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' }
]
},
{ text: 'Title 02', link: '/title-02' }
]
const expected = [
{ text: 'Children 01', link: '/children-01' },
{ text: 'Children 02', link: '/children-02' },
{ text: 'Title 02', link: '/title-02' }
]
expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})
it('removes `.md` or `.html` extention', () => {
const sidebar = [
{ text: 'Title 01', link: '/title-01.md' },
{ text: 'Title 02', link: '/title-02.html' }
]
const expected = [
{ text: 'Title 01', link: '/title-01' },
{ text: 'Title 02', link: '/title-02' }
]
expect(getFlatSideBarLinks(sidebar)).toEqual(expected)
})
})

@ -0,0 +1,41 @@
import * as Utils from 'client/theme-default/utils'
describe('client/theme-default/utils', () => {
describe('ensureStartingSlash', () => {
it('should add slash to the beginning of the given path', () => {
expect(Utils.ensureStartingSlash('path')).toBe('/path')
expect(Utils.ensureStartingSlash('path/nested')).toBe('/path/nested')
expect(Utils.ensureStartingSlash('/path')).toBe('/path')
expect(Utils.ensureStartingSlash('/path/nested')).toBe('/path/nested')
})
})
describe('ensureEndingSlash', () => {
it('should add slash to the end of the given path', () => {
expect(Utils.ensureEndingSlash('path')).toBe('path/')
expect(Utils.ensureEndingSlash('path/nested')).toBe('path/nested/')
expect(Utils.ensureEndingSlash('path/')).toBe('path/')
expect(Utils.ensureEndingSlash('path/nested/')).toBe('path/nested/')
expect(Utils.ensureEndingSlash('path/page.html')).toBe('path/page.html')
})
})
describe('removeExtention', () => {
it('removes `.md` or `.html` extention from the path', () => {
expect(Utils.removeExtention('/')).toBe('/')
expect(Utils.removeExtention('index')).toBe('/')
expect(Utils.removeExtention('index.md')).toBe('/')
expect(Utils.removeExtention('index.html')).toBe('/')
expect(Utils.removeExtention('/index')).toBe('/')
expect(Utils.removeExtention('/index.md')).toBe('/')
expect(Utils.removeExtention('/index.html')).toBe('/')
expect(Utils.removeExtention('path')).toBe('path')
expect(Utils.removeExtention('path.md')).toBe('path')
expect(Utils.removeExtention('path.html')).toBe('path')
expect(Utils.removeExtention('path/')).toBe('path/')
expect(Utils.removeExtention('path/nested.md')).toBe('path/nested')
expect(Utils.removeExtention('path/nested.html')).toBe('path/nested')
expect(Utils.removeExtention('path/nested/index')).toBe('path/nested/')
})
})
})

@ -22,13 +22,23 @@ if (!command || command === 'dev') {
})
.catch((err) => {
console.error(chalk.red(`failed to start server. error:\n`), err)
process.exit(1)
})
} else if (command === 'build') {
require('../dist/node')
.build(argv)
.catch((err) => {
console.error(chalk.red(`build error:\n`), err)
process.exit(1)
})
} else if (command === 'serve') {
require('../dist/node')
.serve(argv)
.catch((err) => {
console.error(chalk.red(`failed to start server. error:\n`), err)
process.exit(1)
})
} else {
console.log(chalk.red(`unknown command "${command}".`))
process.exit(1)
}

@ -6,11 +6,14 @@ module.exports = {
themeConfig: {
repo: 'vuejs/vitepress',
docsDir: 'docs',
editLinks: true,
editLinkText: 'Edit this page on GitHub',
lastUpdated: 'Last Updated',
nav: [
{ text: 'Guide', link: '/' },
{ text: 'Config Reference', link: '/config/' },
{ text: 'Config Reference', link: '/config/basics' },
{
text: 'Release Notes',
link: 'https://github.com/vuejs/vitepress/releases'
@ -18,18 +21,40 @@ module.exports = {
],
sidebar: {
'/guide/': [
{
text: 'Introduction',
children: [
{ text: 'What is VitePress?', link: '/' },
{ text: 'Getting Started', link: '/guide/getting-started' },
{ text: 'Configuration', link: '/guide/configuration' },
{ text: 'Customization', link: '/guide/customization' }
]
}
],
'/config/': [{ text: 'Config Reference', link: '/config/' }]
'/': getGuideSidebar(),
'/guide/': getGuideSidebar(),
'/config/': getConfigSidebar()
}
}
}
function getGuideSidebar() {
return [
{
text: 'Introduction',
children: [
{ text: 'What is VitePress?', link: '/' },
{ text: 'Getting Started', link: '/guide/getting-started' },
{ text: 'Configuration', link: '/guide/configuration' },
{ text: 'Markdown Extensions', link: '/guide/markdown' },
{ text: 'Deploying', link: '/guide/deploy' }
]
},
{
text: 'Advanced',
children: [
{ text: 'Frontmatter', link: '/guide/frontmatter' },
{ text: 'Customization', link: '/guide/customization' }
]
}
]
}
function getConfigSidebar() {
return [
{
text: 'App Config',
children: [{ text: 'Basics', link: '/config/basics' }]
}
]
}

@ -0,0 +1,57 @@
# App Config: Basics
## base
- Type: `string`
- Default: `/`
The base URL the site will be deployed at. You will need to set this if you plan to deploy your site under a sub path, for example, GitHub pages. If you plan to deploy your site to `https://foo.github.io/bar/`, then you should set base to `'/bar/'`. It should always start and end with a slash.
The `base` is automatically prepended to all the URLs that start with `/` in other options, so you only need to specify it once.
```js
module.exports = {
base: '/base/'
}
```
## lang
- Type: `string`
- Default: `en-US`
The `lang` attribute for the site. This will render as a `<html lang="en-US">` tag in the page HTML.
Note that the `lang` attribute will only be added when building the site via `vitepress build`. You will not see this rendered during `vitepress dev`.
```js
module.exports = {
lang: 'en-US'
}
```
## title
- Type: `string`
- Default: `VitePress`
Title for the site. This will be the prefix for all page titles, and displayed in the navbar.
```js
module.exports = {
title: 'VitePress'
}
```
## Description
- Type: `string`
- Default: `A VitePress site`
Description for the site. This will render as a `<meta>` tag in the page HTML.
```js
module.exports = {
title: 'A VitePress site'
}
```

@ -1,3 +0,0 @@
# Config Reference
Coming soon...

@ -11,7 +11,7 @@ Without any configuration, the page is pretty minimal, and the user has no way t
└─ package.json
````
The essential file for configuring a VitePress site is `.vuepress/config.js`, which should export a JavaScript object:
The essential file for configuring a VitePress site is `.vitepress/config.js`, which should export a JavaScript object:
```js
module.exports = {

@ -0,0 +1,49 @@
# Deploying
The following guides are based on some shared assumptions:
- You are placing your docs inside the `docs` directory of your project;
- You are using the default build output location (`.vitepress/dist`);
- VitePress is installed as a local dependency in your project, and you have setup the following npm scripts:
```json
{
"scripts": {
"docs:build": "vitepress build docs",
"docs:serve": "vitepress serve docs"
}
}
```
## Building The Docs
You may run `yarn docs:build` command to build the docs.
```bash
$ yarn docs:build
```
By default, the build output will be placed at `.vitepress/dist`. You may deploy this `dist` folder to any of your preferred platforms.
### Testing The Docs Locally
Once you've built the docs, you may test them locally by running `yarn docs:serve` command.
```bash
$ yarn docs:build
$ yarn docs:serve
```
The `serve` command will boot up local static web server that serves the files from `.vitepress/dist` at http://localhost:3000. It's an easy way to check if the production build looks OK in your local environment.
You may configure the port of the server py passing `--port` flag as an argument.
```json
{
"scripts": {
"docs:serve": "vitepress serve docs --port 8080"
}
}
```
Now the `docs:serve` method will launch the server at http://localhost:8080.

@ -0,0 +1,87 @@
# Frontmatter
Any Markdown file that contains a YAML frontmatter block will be processed by [gray-matter](https://github.com/jonschlinkert/gray-matter). The frontmatter must be at the top of the Markdown file, and must take the form of valid YAML set between triple-dashed lines. Example:
```md
---
title: Docs with VitePress
editLink: true
---
```
Between the triple-dashed lines, you can set [predefined variables](#predefined-variables), or even create custom ones of your own. These variables can be used via the <code>\$page.frontmatter</code> variable.
Heres an example of how you could use it in your Markdown file:
```md
---
title: Docs with VitePress
editLink: true
---
# {{ $page.frontmatter.title }}
Guide content
```
## Alternative frontmatter Formats
VitePress also supports JSON frontmatter syntax, starting and ending in curly braces:
```json
---
{
"title": "Blogging Like a Hacker",
"editLink": true
}
---
```
## Predefined Variables
### title
- Type: `string`
- Default: `h1_title || siteData.title`
Title of the current page.
### head
- Type: `array`
- Default: `undefined`
Specify extra head tags to be injected:
```yaml
---
head:
- - meta
- name: description
content: hello
- - meta
- name: keywords
content: super duper SEO
---
```
### navbar
- Type: `boolean`
- Default: `undefined`
You can disable the navbar on a specific page with `navbar: false`
### sidebar
- Type: `boolean|'auto'`
- Default: `undefined`
You can decide to show the sidebar on a specific page with `sidebar: auto` or disable it with `sidebar: false`
### editLink
- Type: `boolean`
- Default: `undefined`
Define if this page should include an edit link.

@ -1,6 +1,6 @@
# Getting Started
This section will help you build a basic VuePress documentation site from ground up. If you already have an existing project and would like to keep documentation inside the project, start from Step 3.
This section will help you build a basic VitePress documentation site from ground up. If you already have an existing project and would like to keep documentation inside the project, start from Step 3.
- **Step. 1:** Create and change into a new directory.
@ -14,7 +14,7 @@ This section will help you build a basic VuePress documentation site from ground
$ yarn init
```
- **Step. 3:** Install VuePress locally.
- **Step. 3:** Install VitePress locally.
```bash
$ yarn add --dev vitepress
@ -32,7 +32,8 @@ This section will help you build a basic VuePress documentation site from ground
{
"scripts": {
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs"
"docs:build": "vitepress build docs",
"docs:serve": "vitepress serve docs"
}
}
```
@ -44,3 +45,7 @@ This section will help you build a basic VuePress documentation site from ground
```
VitePress will start a hot-reloading development server at http://localhost:3000.
By now, you should have a basic but functional VitePress documentation site.
When your documentation site starts to take shape, be sure to read the [deployment guide](./deploy).

@ -0,0 +1,346 @@
# Markdown Extensions
## Header Anchors
Headers automatically get anchor links applied. Rendering of anchors can be configured using the `markdown.anchor` option.
## Links
### Internal Links
Internal links are converted to router link for SPA navigation. Also, every `index.md` contained in each sub-directory will automatically be converted to `index.html`, with corresponding URL `/`.
For example, given the following directory structure:
```
.
├─ index.md
├─ foo
│ ├─ index.md
│ ├─ one.md
│ └─ two.md
└─ bar
├─ index.md
├─ three.md
└─ four.md
```
And providing you are in `foo/one.md`:
```md
[Home](/) <!-- sends the user to the root README.md -->
[foo](/foo/) <!-- sends the user to index.html of directory foo -->
[foo heading](./#heading) <!-- anchors user to a heading in the foo README file -->
[bar - three](../bar/three) <!-- you can omit extention -->
[bar - three](../bar/three.md) <!-- you can append .md -->
[bar - four](../bar/four.html) <!-- or you can append .html -->
```
### Page Suffix
Pages and internal links get generated with the `.html` suffix by default.
### External Links
Outbound links automatically get `target="_blank" rel="noopener noreferrer"`:
- [vuejs.org](https://vuejs.org)
- [VitePress on GitHub](https://github.com/vuejs/vitepress)
## Frontmatter
[YAML frontmatter](https://jekyllrb.com/docs/frontmatter/) is supported out of the box:
```yaml
---
title: Blogging Like a Hacker
lang: en-US
---
```
This data will be available to the rest of the page, along with all custom and theming components.
For more details, see [Frontmatter](./frontmatter.md).
## GitHub-Style Tables
**Input**
```
| Tables | Are | Cool |
| ------------- |:-------------:| -----:|
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |
```
**Output**
| Tables | Are | Cool |
| ------------- | :-----------: | -----: |
| col 3 is | right-aligned | \$1600 |
| col 2 is | centered | \$12 |
| zebra stripes | are neat | \$1 |
## Emoji :tada:
**Input**
```
:tada: :100:
```
**Output**
:tada: :100:
A [list of all emojis](https://github.com/markdown-it/markdown-it-emoji/blob/master/lib/data/full.json) is available.
## Table of Contents
**Input**
```
[[toc]]
```
**Output**
[[toc]]
Rendering of the TOC can be configured using the `markdown.toc` option.
## Custom Containers
Custom containers can be defined by their types, titles, and contents.
### Default Title
**Input**
```md
::: tip
This is a tip
:::
::: warning
This is a warning
:::
::: danger
This is a dangerous warning
:::
```
**Output**
::: tip
This is a tip
:::
::: warning
This is a warning
:::
::: danger
This is a dangerous warning
:::
### Custom Title
**Input**
```md
::: danger STOP
Danger zone, do not proceed
:::
```
**Output**
::: danger STOP
Danger zone, do not proceed
:::
## Syntax Highlighting in Code Blocks
VitePress uses [Prism](https://prismjs.com/) to highlight language syntax in Markdown code blocks, using coloured text. Prism supports a wide variety of programming languages. All you need to do is append a valid language alias to the beginning backticks for the code block:
**Input**
````
```js
export default {
name: 'MyComponent',
// ...
}
```
````
**Output**
```js
export default {
name: 'MyComponent'
// ...
}
```
**Input**
````
```html
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
```
````
**Output**
```html
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
```
A [list of valid languages](https://prismjs.com/#languages-list) is available on Prisms site.
## Line Highlighting in Code Blocks
**Input**
````
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
````
**Output**
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
In addition to a single line, you can also specify multiple single lines, ranges, or both:
- Line ranges: for example `{5-8}`, `{3-10}`, `{10-17}`
- Multiple single lines: for example `{4,7,9}`
- Line ranges and single lines: for example `{4,7-13,16,23-27,40}`
**Input**
````
```js{1,4,6-7}
export default { // Highlighted
data () {
return {
msg: `Highlighted!
This line isn't highlighted,
but this and the next 2 are.`,
motd: 'VitePress is awesome',
lorem: 'ipsum',
}
}
}
```
````
**Output**
```js{1,4,6-8}
export default { // Highlighted
data () {
return {
msg: `Highlighted!
This line isn't highlighted,
but this and the next 2 are.`,
motd: 'VitePress is awesome',
lorem: 'ipsum',
}
}
}
```
## Line Numbers
You can enable line numbers for each code blocks via config:
```js
module.exports = {
markdown: {
lineNumbers: true
}
}
```
- Demo:
<picture>
<source srcset="../images/line-numbers-mobile.gif" media="(max-width: 719px)">
<img class="line-numbers-mobile-snap" src="../images/line-numbers-mobile.gif" alt="Image">
</picture>
<picture>
<source srcset="../images/line-numbers-desktop.png" media="(min-width: 720px)">
<img class="line-numbers-desktop-snap" src="../images/line-numbers-desktop.png" alt="Image">
</picture>
<style>
.line-numbers-mobile-snap {
margin: 0 -1.5rem;
width: 100vw;
max-width: none !important;
}
.line-numbers-desktop-snap {
display: none;
}
@media (min-width: 720px) {
.line-numbers-mobile-snap {
display: none;
}
.line-numbers-desktop-snap {
display: block;
}
}
</style>
## Advanced Configuration
VitePress uses [markdown-it](https://github.com/markdown-it/markdown-it) as the Markdown renderer. A lot of the extensions above are implemented via custom plugins. You can further customize the `markdown-it` instance using the `markdown` option in `.vitepress/config.js`:
```js
module.exports = {
markdown: {
// options for markdown-it-anchor
anchor: { permalink: false },
// options for markdown-it-toc
toc: { includeLevel: [1, 2] },
config: (md) => {
// use more markdown-it plugins!
md.use(require('markdown-it-xxx'))
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

@ -47,6 +47,6 @@ VitePress is more opinionated and less configurable: VitePress aims to scale bac
VitePress is future oriented: VitePress only targets browsers that support native ES module imports. It encourages the use of native JavaScript without transpilation, and CSS variables for theming.
## Will this become the next VuePress in the future?
## Will This Become The Next VuePress in The Future?
Probably not. It's currently under a different name so that we don't over commit to the compatibility with the current VuePress ecosystem (mostly themes and plugins). We'll see how close we can get without compromising the design goals listed above. But the overall idea is that VitePress will have a drastically more minimal theming API (preferring JavaScript APIs instead of file layout conventions) and likely no plugins (all customization is done in themes).

@ -1,4 +1,4 @@
{
"name": "vitepress-docs",
"private": true
"private": true,
"name": "vitepress-docs"
}

@ -0,0 +1,71 @@
# Hello, World!
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
## General Markdown
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Ut enim ad minim veniam, [link to Vue.js](https://vuejs.org) quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Ut enim ad minim veniam, **strong opinion** quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetur *and emphasize* adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
## Lists
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
- List item 1
- Nested item A
- Nested item B
- List item 2
- List item 3
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
1. Ordered item 1
1. Nested ordered item A
2. Nested ordered item A
2. Ordered item 2
3. Ordered item 3
## Code Block and Block Quote
Lorem ipsum dolor sit amet, consectetur adipisicing elit, `exampleCodeBlock` sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
```js
function exampleCodeBlock() {
console.log('It is Working.')
}
```
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
> The best is yet to come.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
## Custom Alerts
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
### Tips
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
::: tip TIP
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
:::
### Warning
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
::: warning WARNING
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
:::
### Danger
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
::: danger DANGER
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
:::

@ -0,0 +1,9 @@
{
"private": true,
"name": "vitepress-example-minimal",
"scripts": {
"dev": "node ../../bin/vitepress dev",
"build": "node ../../bin/vitepress build",
"serve": "node ../../bin/vitepress serve"
}
}

@ -0,0 +1,13 @@
module.exports = {
preset: 'ts-jest',
rootDir: __dirname,
moduleNameMapper: {
'^src/(.*)$': '<rootDir>/src/$1',
'^client/(.*)$': '<rootDir>/src/client/$1',
'^node/(.*)$': '<rootDir>/src/node/$1',
'^shared/(.*)$': '<rootDir>/src/shared/$1',
'^tests/(.*)$': '<rootDir>/__tests__/$1'
},
testMatch: ['<rootDir>/__tests__/**/*.spec.ts'],
testPathIgnorePatterns: ['/node_modules/']
}

@ -1,6 +1,6 @@
{
"name": "vitepress",
"version": "0.7.4",
"version": "0.9.0",
"description": "Vite & Vue powered static site generator",
"main": "dist/node/index.js",
"typings": "types/index.d.ts",
@ -22,13 +22,15 @@
"dev-watch": "node scripts/watchAndCopy",
"build": "rimraf -rf dist && node scripts/copyShared && tsc -p src/client && tsc -p src/node && node scripts/copyClient",
"lint": "yarn lint:js && yarn lint:ts",
"lint:js": "prettier --check --write \"{bin,scripts,src}/**/*.js\"",
"lint:ts": "prettier --check --write --parser typescript \"{src,types}/**/*.ts\"",
"lint:js": "prettier --check --write \"{bin,docs,scripts,src}/**/*.js\"",
"lint:ts": "prettier --check --write --parser typescript \"{__tests__,src,docs,types}/**/*.ts\"",
"test": "jest",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"release": "bash scripts/release.sh",
"release": "node scripts/release.js",
"docs": "run-p dev docs-dev",
"docs-dev": "node ./bin/vitepress dev docs",
"docs-build": "yarn build && node ./bin/vitepress build docs"
"docs-build": "yarn build && node ./bin/vitepress build docs",
"docs-serve": "yarn docs-build && node ./bin/vitepress serve --root docs"
},
"engines": {
"node": ">=10.0.0"
@ -62,6 +64,7 @@
"dependencies": {
"@vue/compiler-sfc": "^3.0.3",
"@vue/server-renderer": "^3.0.3",
"chalk": "^4.1.0",
"debug": "^4.1.1",
"diacritics": "^1.3.0",
"escape-html": "^1.0.3",
@ -77,22 +80,33 @@
"minimist": "^1.2.5",
"ora": "^5.1.0",
"prismjs": "^1.20.0",
"rollup": "^2.33.3",
"slash": "^3.0.0",
"vite": "^1.0.0-rc.13",
"vue": "^3.0.3"
},
"devDependencies": {
"@types/fs-extra": "^9.0.1",
"@types/jest": "^26.0.15",
"@types/koa": "^2.11.6",
"@types/koa-static": "^4.0.1",
"@types/lru-cache": "^5.1.0",
"@types/markdown-it": "^10.0.2",
"@types/node": "^13.13.4",
"@types/postcss-load-config": "^2.0.1",
"chokidar": "^3.4.2",
"conventional-changelog-cli": "^2.1.0",
"enquirer": "^2.3.6",
"execa": "^4.1.0",
"jest": "^26.6.3",
"koa": "^2.13.0",
"koa-static": "^5.0.0",
"lint-staged": "^10.3.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"rimraf": "^3.0.2",
"semver": "^7.3.2",
"ts-jest": "^26.4.4",
"typescript": "^3.8.3",
"yorkie": "^2.0.0"
}

@ -0,0 +1,120 @@
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const semver = require('semver')
const { prompt } = require('enquirer')
const execa = require('execa')
const currentVersion = require('../package.json').version
const versionIncrements = ['patch', 'minor', 'major']
const tags = ['latest', 'next']
const inc = (i) => semver.inc(currentVersion, i)
const bin = (name) => path.resolve(__dirname, `../node_modules/.bin/${name}`)
const run = (bin, args, opts = {}) =>
execa(bin, args, { stdio: 'inherit', ...opts })
const step = (msg) => console.log(chalk.cyan(msg))
async function main() {
let targetVersion
const { release } = await prompt({
type: 'select',
name: 'release',
message: 'Select release type',
choices: versionIncrements.map((i) => `${i} (${inc(i)})`).concat(['custom'])
})
if (release === 'custom') {
targetVersion = (
await prompt({
type: 'input',
name: 'version',
message: 'Input custom version',
initial: currentVersion
})
).version
} else {
targetVersion = release.match(/\((.*)\)/)[1]
}
if (!semver.valid(targetVersion)) {
throw new Error(`Invalid target version: ${targetVersion}`)
}
const { tag } = await prompt({
type: 'select',
name: 'tag',
message: 'Select tag type',
choices: tags
})
console.log(tag)
const { yes: tagOk } = await prompt({
type: 'confirm',
name: 'yes',
message: `Releasing v${targetVersion} with the "${tag}" tag. Confirm?`
})
if (!tagOk) {
return
}
// Update the package version.
step('\nUpdating the package version...')
updatePackage(targetVersion)
// Build the package.
step('\nBuilding the package...')
await run('yarn', ['build'])
// Generate the changelog.
step('\nGenerating the changelog...')
await run('yarn', ['changelog'])
const { yes: changelogOk } = await prompt({
type: 'confirm',
name: 'yes',
message: `Changelog generated. Does it look good?`
})
if (!changelogOk) {
return
}
// Commit changes to the Git.
step('\nCommitting changes...')
await run('git', ['add', '-A'])
await run('git', ['commit', '-m', `release: v${targetVersion}`])
// Publish the package.
step('\nPublishing the package...')
await run('yarn', [
'publish',
'--tag',
tag,
'--new-version',
targetVersion,
'--no-commit-hooks',
'--no-git-tag-version'
])
// Push to GitHub.
step('\nPushing to GitHub...')
await run('git', ['tag', `v${targetVersion}`])
await run('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
await run('git', ['push'])
}
function updatePackage(version) {
const pkgPath = path.resolve(path.resolve(__dirname, '..'), 'package.json')
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
pkg.version = version
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
}
main().catch((err) => console.error(err))

@ -1,34 +0,0 @@
set -e
echo "Current version:" $(grep version package.json | sed -E 's/^.*"([0-9][^"]+)".*$/\1/')
echo "Enter the new version you want to publish e.g., 0.0.2: "
read VERSION
read -p "Releasing v$VERSION - are you sure? (y/n)" -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
echo "Releasing v$VERSION ..."
# generate the version so that the changelog can be generated too
yarn version --no-git-tag-version --no-commit-hooks --new-version $VERSION
yarn run build
# changelog
yarn run changelog
yarn prettier --write CHANGELOG.md
echo "Please check the git history and the changelog and press enter"
read OKAY
# commit and tag
git add CHANGELOG.md package.json
git commit -m "release: v$VERSION"
git tag "v$VERSION"
# commit
yarn publish --new-version "$VERSION" --no-commit-hooks --no-git-tag-version
# publish
git push origin refs/tags/v$VERSION
git push
fi

@ -1,55 +1,93 @@
<template>
<div class="debug" :class="{ open }" @click="open = !open">
<pre>debug</pre>
<pre>$page {{ $page }}</pre>
<pre>$siteByRoute {{ $siteByRoute }}</pre>
<pre>$site {{ $site }}</pre>
<div class="debug" :class="{ open }" ref="el" @click="open = !open">
<p class="title">Debug</p>
<pre class="block">$page {{ $page }}</pre>
<pre class="block">$siteByRoute {{ $siteByRoute }}</pre>
<pre class="block">$site {{ $site }}</pre>
</div>
</template>
<script>
import { ref } from 'vue'
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
export default {
export default defineComponent({
setup() {
const el = ref<HTMLElement | null>(null)
const open = ref(false)
watch(open, (value) => {
if (value === false) {
el.value!.scrollTop = 0
}
})
return {
el,
open
}
}
}
})
</script>
<style>
<style scoped>
.debug {
box-sizing: border-box;
position: fixed;
z-index: 999;
cursor: pointer;
bottom: 0;
right: 0;
width: 80px;
height: 30px;
padding: 5px;
overflow: hidden;
right: 8px;
bottom: 8px;
z-index: 9999;
border-radius: 4px;
width: 74px;
height: 32px;
color: #eeeeee;
overflow: hidden;
cursor: pointer;
background-color: rgba(0, 0, 0, .85);
transition: all .15s ease;
background-color: rgba(0,0,0,0.85);
}
.debug:hover {
background-color: rgba(0, 0, 0, .75);
}
.debug.open {
width: 500px;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
margin-top: 0;
border-radius: 0;
padding: 0 0;
overflow: scroll;
}
.debug pre {
@media (min-width: 512px) {
.debug.open {
width: 512px;
}
}
.debug.open:hover {
background-color: rgba(0, 0, 0, .85);
}
.title {
margin: 0;
padding: 6px 16px 6px;
line-height: 20px;
font-size: 13px;
}
.block {
margin: 2px 0 0;
border-top: 1px solid rgba(255, 255, 255, .16);
padding: 8px 16px;
font-family: Hack, monospace;
font-size: 13px;
margin: 0;
padding: 5px 10px;
border-bottom: 1px solid #eee;
}
.block +.block {
margin-top: 8px;
}
</style>

@ -8,16 +8,14 @@
</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>
<SideBar :open="openSideBar">
<template #sidebar-top>
<slot name="sidebar-top" />
</template>
<template #sidebar-bottom>
<slot name="sidebar-bottom" />
</template>
</SideBar>
<!-- TODO: make this button accessible -->
<div class="sidebar-mask" @click="toggleSidebar(false)" />
<main class="home" aria-labelledby="main-title" v-if="enableHome">

@ -14,26 +14,19 @@ const { url, text } = useEditLink()
</script>
<style scoped>
.edit-link {
padding-top: 1rem;
padding-bottom: 1rem;
overflow: auto;
}
.link {
display: inline-block;
font-size: 1rem;
font-weight: 500;
color: var(--text-color-light);
color: var(--c-text-light);
}
.link:hover {
text-decoration: none;
color: var(--accent-color);
color: var(--c-brand);
}
.icon {
display: inline-block;
margin-left: 4px;
width: 1rem;
height: 1rem;
}
</style>

@ -15,7 +15,9 @@
</p>
<p v-if="data.actionText && data.actionLink" class="action">
<NavBarLink :item="actionLink" />
<a class="action-link" :href="actionLink.link">
{{ actionLink.text }}
</a>
</p>
<slot name="hero" />
</header>
@ -36,13 +38,11 @@
<script setup lang="ts">
import { computed } from 'vue'
import NavBarLink from './NavBarLink.vue'
import { withBase } from '../utils'
import { useRoute, useSiteData } from 'vitepress'
import { withBase } from '../utils'
const route = useRoute()
const siteData = useSiteData()
const data = computed(() => route.data.frontmatter)
const actionLink = computed(() => ({
link: data.value.actionLink,
@ -79,31 +79,38 @@ const siteDescription = computed(() => siteData.value.description)
max-width: 35rem;
font-size: 1.6rem;
line-height: 1.3;
/* TODO: calculating lighten 40% color with using style :vars from `--text-color` */
/* TODO: calculating lighten 40% color with using style :vars from `--c-text` */
color: #6a8bad;
}
::v-deep(.nav-link) {
.action-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 0.1s ease;
box-sizing: border-box;
/* TODO: calculating darken 10% color with using style vars from `--accent-color` */
border-bottom: 1px solid #389d70;
padding: 0 20px;
line-height: 48px;
font-size: 1rem;
font-weight: 500;
color: #ffffff;
background-color: var(--c-brand);
transition: background-color .1s ease;
}
::v-deep(.nav-link:hover) {
/* TODO: calculating lighten 10% color with using style vars from `--accent-color` */
background-color: #4abf8a;
.action-link:hover {
text-decoration: none;
background-color: var(--c-brand-light);
}
@media (min-width: 420px) {
.action-link {
padding: 0 24px;
line-height: 56px;
font-size: 1.2rem;
font-weight: 500;
}
}
.features {
border-top: 1px solid var(--border-color);
border-top: 1px solid var(--c-divider);
padding: 1.2rem 0;
margin-top: 2.5rem;
display: flex;
@ -124,20 +131,20 @@ const siteDescription = computed(() => siteData.value.description)
font-weight: 500;
border-bottom: none;
padding-bottom: 0;
/* TODO: calculating lighten 10% color with using style :vars from `--text-color` */
/* TODO: calculating lighten 10% color with using style :vars from `--c-text` */
color: #3a5169;
}
.feature p {
/* TODO: calculating lighten 25% color with using style :vars from `--text-color` */
/* TODO: calculating lighten 25% color with using style :vars from `--c-text` */
color: #4e6e8e;
}
.footer {
padding: 2.5rem;
border-top: 1px solid var(--border-color);
border-top: 1px solid var(--c-divider);
text-align: center;
/* TODO: calculating lighten 25% color with using style :vars from `--text-color` */
/* TODO: calculating lighten 25% color with using style :vars from `--c-text` */
color: #4e6e8e;
}
@ -163,8 +170,7 @@ const siteDescription = computed(() => siteData.value.description)
}
.hero h1,
.hero .description,
.hero .action {
.hero .description {
margin: 1.2rem auto;
}
@ -172,11 +178,6 @@ const siteDescription = computed(() => siteData.value.description)
font-size: 1.2rem;
}
.hero .action-button {
font-size: 1rem;
padding: 0.6rem 1.2rem;
}
.feature h2 {
font-size: 1.25rem;
}

@ -0,0 +1,69 @@
<template>
<p v-if="hasLastUpdated" class="last-updated">
<span class="prefix">{{ prefix }}:</span>
<span class="datetime">{{ datetime }}</span>
</p>
</template>
<script lang="ts">
import { defineComponent, ref, computed, onMounted } from 'vue'
import { useSiteDataByRoute, usePageData } from 'vitepress'
export default defineComponent({
setup() {
const site = useSiteDataByRoute()
const page = usePageData()
const datetime = ref('')
const hasLastUpdated = computed(() => {
const lu = site.value.themeConfig.lastUpdated
return lu !== undefined && lu !== false
})
const prefix = computed(() => {
const p = site.value.themeConfig.lastUpdated
return p === true ? 'Last Updated' : p
})
onMounted(() => {
datetime.value = new Date(page.value.lastUpdated).toLocaleString('en-US')
})
return {
hasLastUpdated,
prefix,
datetime
}
}
})
</script>
<style scoped>
.last-updated {
display: inline-block;
margin: 0;
line-height: 1.4;
font-size: .9rem;
color: var(--c-text-light);
}
@media (min-width: 960px) {
.last-updated {
font-size: 1rem;
}
}
.prefix {
display: inline-block;
font-weight: 500;
}
.datetime {
display: inline-block;
margin-left: 6px;
font-weight: 400;
}
</style>

@ -1,51 +1,32 @@
<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" />
<NavBarTitle />
<div class="flex-grow" />
<div class="nav">
<NavBarLinks />
</div>
<slot name="search" />
</template>
<script setup lang="ts">
import { withBase } from '../utils'
import NavBarTitle from './NavBarTitle.vue'
import NavBarLinks from './NavBarLinks.vue'
</script>
<style>
.title {
font-size: 1.3rem;
font-weight: 600;
color: var(--text-color);
}
.title:hover {
text-decoration: none;
}
<style scoped>
.flex-grow {
flex-grow: 1;
}
.logo {
margin-right: 0.75rem;
height: 1.3rem;
vertical-align: bottom;
.nav {
display: none;
}
@media screen and (max-width: 719px) {
.hide-mobile {
display: none;
@media (min-width: 720px) {
.nav {
display: block;
}
}
</style>

@ -1,15 +1,14 @@
<template>
<div class="nav-item">
<div class="navbar-link">
<a
class="nav-link"
class="item"
:class="classes"
:href="href"
:target="target"
:rel="rel"
:aria-label="item.ariaLabel"
>
{{ item.text }}
<OutboundLink v-if="isExternalLink" />
{{ item.text }} <OutboundLink v-if="isExternalLink" />
</a>
</div>
</template>
@ -72,55 +71,55 @@ const rel = computed(() => {
})
</script>
<style>
.nav-item {
<style scoped>
.navbar-link {
position: relative;
display: inline-block;
margin-left: 1.5rem;
line-height: 2rem;
padding: 0 1.5rem;
}
@media screen and (max-width: 719px) {
.nav-item {
display: block;
margin-left: 0;
padding: 0.3rem 1.5rem;
@media (min-width: 720px) {
.navbar-link {
padding: 0;
}
.navbar-link + .navbar-link,
.dropdown-wrapper + .navbar-link {
padding-left: 1.5rem;
}
}
.nav-link {
.item {
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);
line-height: 40px;
font-size: 1rem;
font-weight: 600;
color: var(--c-text);
white-space: nowrap;
}
.nav-link:hover,
.nav-link.active {
border-bottom-color: var(--accent-color);
.item:hover,
.item.active {
text-decoration: none;
color: var(--c-brand);
}
.nav-link.external:hover {
border-bottom-color: transparent;
}
@media (min-width: 720px) {
.item {
border-bottom: 2px solid transparent;
line-height: 1.5rem;
font-size: .9rem;
font-weight: 500;
}
@media screen and (max-width: 719px) {
.nav-link {
line-height: 1.7;
font-size: 1em;
font-weight: 600;
border-bottom: none;
margin-bottom: 0;
.item:hover,
.item.active {
color: var(--c-text);
border-bottom-color: var(--c-brand);
}
.nav-link:hover,
.nav-link.active {
color: var(--accent-color);
.item.external:hover {
border-bottom-color: transparent;
}
}
</style>

@ -1,5 +1,5 @@
<template>
<nav class="nav-links" v-if="navData || repoInfo">
<nav v-if="navData || repoInfo" class="navbar-links">
<template v-if="navData">
<template v-for="item of navData">
<NavDropdownLink v-if="item.items" :item="item" />
@ -100,21 +100,17 @@ const navData = computed(() => {
})
</script>
<style>
.nav-links {
display: flex;
align-items: center;
height: 35px;
list-style-type: none;
transform: translateY(1px);
<style scoped>
.navbar-links {
padding: 1rem 0;
border-bottom: 1px solid var(--c-divider);
}
@media screen and (max-width: 719px) {
.nav-links {
display: block;
height: auto;
padding: 0.5rem 0 1rem;
border-bottom: 1px solid var(--border-color);
@media (min-width: 720px) {
.navbar-links {
display: flex;
align-items: center;
border-bottom: 0;
}
}
</style>

@ -0,0 +1,44 @@
<template>
<a
class="nav-bar-title"
:href="$site.base"
:aria-label="`${$site.title}, back to home`"
>
<img
v-if="$theme.logo"
class="logo"
:src="withBase($theme.logo)"
alt="Logo"
/>
{{ $site.title }}
</a>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { withBase } from '../utils'
export default defineComponent({
setup() {
return { withBase }
}
})
</script>
<style scoped>
.nav-bar-title {
font-size: 1.3rem;
font-weight: 600;
color: var(--c-text);
}
.nav-bar-title:hover {
text-decoration: none;
}
.logo {
margin-right: .75rem;
height: 1.3rem;
vertical-align: bottom;
}
</style>

@ -83,7 +83,7 @@ const isLastItemOfArray = <T>(item: T, array: T[]) => {
.dropdown-wrapper .dropdown-title {
font: inherit;
color: var(--text-color);
color: var(--c-text);
font-size: 0.9rem;
font-weight: 500;
display: inline-block;
@ -145,18 +145,18 @@ const isLastItemOfArray = <T>(item: T, array: T[]) => {
}
.dropdown-wrapper .nav-dropdown .dropdown-item a:hover {
color: var(--accent-color);
color: var(--c-brand);
}
.dropdown-wrapper .nav-dropdown .dropdown-item a.active {
color: var(--accent-color);
color: var(--c-brand);
}
.dropdown-wrapper .nav-dropdown .dropdown-item a.active::after {
content: '';
width: 0;
height: 0;
border-left: 5px solid var(--accent-color);
border-left: 5px solid var(--c-brand);
border-top: 3px solid transparent;
border-bottom: 3px solid transparent;
position: absolute;

@ -1,33 +1,40 @@
<template>
<div v-if="hasLinks" class="next-and-prev-link">
<div class="prev">
<a v-if="prev" class="link" :href="prev.link">
<ArrowLeft class="icon icon-prev" />
<span class="text">{{ prev.text }}</span>
</a>
</div>
<div class="next">
<a v-if="next" class="link" :href="next.link">
<span class="text">{{ next.text }}</span>
<ArrowRight class="icon icon-next" />
</a>
<div class="container">
<div class="prev">
<a v-if="prev" class="link" :href="withBase(prev.link)">
<ArrowLeft class="icon icon-prev" />
<span class="text">{{ prev.text }}</span>
</a>
</div>
<div class="next">
<a v-if="next" class="link" :href="withBase(next.link)">
<span class="text">{{ next.text }}</span>
<ArrowRight class="icon icon-next" />
</a>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useNextAndPrevLinks } from '../composables/nextAndPrevLinks'
import { withBase } from '../utils'
import ArrowLeft from './icons/ArrowLeft.vue'
import ArrowRight from './icons/ArrowRight.vue'
import { useNextAndPrevLinks } from '../composables/nextAndPrevLinks'
const { hasLinks, prev, next } = useNextAndPrevLinks()
</script>
<style scoped>
.next-and-prev-link {
padding-top: 1rem;
}
.container {
display: flex;
justify-content: space-between;
border-top: 1px solid var(--border-color);
border-top: 1px solid var(--c-divider);
padding-top: 1rem;
}
@ -66,11 +73,16 @@ const { hasLinks, prev, next } = useNextAndPrevLinks()
.icon {
display: block;
flex-shrink: 0;
width: 1rem;
height: 1rem;
fill: var(--text-color);
width: 16px;
height: 16px;
fill: var(--c-text);
transform: translateY(1px);
}
.icon-prev { margin-right: 8px; }
.icon-next { margin-left: 8px; }
.icon-prev {
margin-right: 8px;
}
.icon-next {
margin-left: 8px;
}
</style>

@ -6,7 +6,8 @@
<Content />
</div>
<EditLink />
<PageFooter />
<NextAndPrevLinks />
<slot name="bottom" />
@ -14,7 +15,7 @@
</template>
<script setup lang="ts">
import EditLink from './EditLink.vue'
import PageFooter from './PageFooter.vue'
import NextAndPrevLinks from './NextAndPrevLinks.vue'
</script>
@ -22,7 +23,7 @@ import NextAndPrevLinks from './NextAndPrevLinks.vue'
.page {
margin: 0 auto;
padding: 0 1.5rem 4rem;
max-width: 50rem;
max-width: 48rem;
}
.content {

@ -0,0 +1,49 @@
<template>
<footer class="page-footer">
<div class="edit">
<EditLink />
</div>
<div class="updated">
<LastUpdated />
</div>
</footer>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import EditLink from './EditLink.vue'
import LastUpdated from './LastUpdated.vue'
export default defineComponent({
components: {
EditLink,
LastUpdated
}
})
</script>
<style scoped>
.page-footer {
padding-top: 1rem;
padding-bottom: 1rem;
overflow: auto;
}
@media (min-width: 960px) {
.page-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
}
.updated {
padding-top: 4px;
}
@media (min-width: 960px) {
.updated {
padding-top: 0;
}
}
</style>

@ -1,203 +1,65 @@
<template>
<NavBarLinks class="show-mobile" />
<aside class="sidebar" :class="{ open }">
<div class="nav">
<NavBarLinks class="show-mobile" />
</div>
<slot name="top" />
<slot name="sidebar-top" />
<ul class="sidebar">
<SideBarItem v-for="item of items" :item="item" />
</ul>
<SideBarLinks />
<slot name="bottom" />
<slot name="sidebar-bottom" />
</aside>
</template>
<script setup lang="ts">
import { useRoute, useSiteDataByRoute } from 'vitepress'
import { computed } from 'vue'
import { getPathDirName } from '../utils'
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'
import { defineProps } from 'vue'
import NavBarLinks from './NavBarLinks.vue'
import { SideBarItem } from './SideBarItem'
import type { DefaultTheme } from '../config'
import type { Header } from '../../../../types/shared'
import type { ResolvedSidebarItem } from './SideBarItem'
import SideBarLinks from './SideBarLinks.vue'
type ResolvedSidebar = ResolvedSidebarItem[]
const route = useRoute()
const siteData = useSiteDataByRoute()
useActiveSidebarLinks()
const 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
)
}
}
defineProps({
open: { type: Boolean, required: true }
})
function resolveAutoSidebar(headers: Header[], depth: number): ResolvedSidebar {
const ret: ResolvedSidebar = []
if (headers === undefined) {
return []
}
let lastH2: ResolvedSidebarItem | undefined = undefined
headers.forEach(({ level, title, slug }) => {
if (level - 1 > depth) {
return
}
const item: ResolvedSidebarItem = {
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: DefaultTheme.SideBarItem[],
depth: number
): ResolvedSidebar {
return config
}
function resolveMultiSidebar(
config: DefaultTheme.MultiSideBarConfig,
path: string,
headers: Header[],
depth: number
): ResolvedSidebar {
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 []
}
</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;
}
<style scoped>
.sidebar {
padding: 1.5rem 0;
}
.sidebar-data {
padding: 1.5rem 0;
}
@media screen and (max-width: 719px) {
.sidebar-data {
padding: 1rem;
position: fixed;
top: var(--header-height);
bottom: 0;
left: 0;
z-index: var(--z-index-sidebar);
border-right: 1px solid var(--c-divider);
width: 16.4rem;
background-color: var(--c-bg);
overflow-y: auto;
transform: translateX(-100%);
transition: transform 0.25s ease;
}
@media (min-width: 720px) {
.sidebar {
transform: translateX(0);
}
}
.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;
@media (min-width: 960px) {
.sidebar {
width: 20rem;
}
}
.sidebar-items > .sidebar-item + .sidebar-item {
padding-top: 0;
.sidebar.open {
transform: translateX(0);
}
.sidebar-link {
.nav {
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:hover {
text-decoration: none;
color: var(--accent-color);
}
a.sidebar-link.active {
border-left-color: var(--accent-color);
font-weight: 600;
color: var(--accent-color);
@media (min-width: 720px) {
.nav {
display: none;
}
}
</style>

@ -1,39 +1,31 @@
import { useRoute, useSiteData } from 'vitepress'
import { FunctionalComponent, h, VNode } from 'vue'
import { Header } from '../../../../types/shared'
import { useRoute, useSiteData } from 'vitepress'
import { Header } from '/@types/shared'
import { DefaultTheme } from '../config'
import { joinUrl, isActive } from '../utils'
export interface ResolvedSidebarItem {
text: string
link?: string
isGroup?: boolean
children?: ResolvedSidebarItem[]
}
interface HeaderWithChildren extends Header {
children?: Header[]
}
export const SideBarItem: FunctionalComponent<{
item: ResolvedSidebarItem
export const SideBarLink: FunctionalComponent<{
item: DefaultTheme.SideBarItem
}> = (props) => {
const {
item: { link: relLink, text, children }
} = props
const route = useRoute()
const siteData = useSiteData()
const site = useSiteData()
const link = resolveLink(siteData.value.base, relLink || '')
const active = isActive(route, link)
const headers = route.data.headers
const text = props.item.text
const link = resolveLink(site.value.base, props.item.link)
const children = (props.item as DefaultTheme.SideBarGroup).children
const active = isActive(route, link)
const childItems = createChildren(active, children, headers)
return h('li', { class: 'sidebar-item' }, [
return h('li', { class: 'sidebar-link' }, [
h(
link ? 'a' : 'p',
{
class: { 'sidebar-link': true, active },
class: { 'sidebar-link-item': true, active },
href: link
},
text
@ -42,26 +34,30 @@ export const SideBarItem: FunctionalComponent<{
])
}
function resolveLink(base: string, path: string): string | undefined {
return path
? // keep relative hash to the same page
path.startsWith('#')
? path
: joinUrl(base, path)
: undefined
function resolveLink(base: string, path?: string): string | undefined {
if (path === undefined) {
return path
}
// keep relative hash to the same page
if (path.startsWith('#')) {
return path
}
return joinUrl(base, path)
}
function createChildren(
active: boolean,
children?: ResolvedSidebarItem[],
children?: DefaultTheme.SideBarItem[],
headers?: Header[]
): VNode | null {
if (children && children.length > 0) {
return h(
'ul',
{ class: 'sidebar-items' },
{ class: 'sidebar-links' },
children.map((c) => {
return h(SideBarItem, { item: c })
return h(SideBarLink, { item: c })
})
)
}
@ -71,7 +67,7 @@ function createChildren(
: null
}
function resolveHeaders(headers: Header[]): ResolvedSidebarItem[] {
function resolveHeaders(headers: Header[]): DefaultTheme.SideBarItem[] {
return mapHeaders(groupHeaders(headers))
}
@ -88,7 +84,7 @@ function groupHeaders(headers: Header[]): HeaderWithChildren[] {
return headers.filter((h) => h.level === 2)
}
function mapHeaders(headers: HeaderWithChildren[]): ResolvedSidebarItem[] {
function mapHeaders(headers: HeaderWithChildren[]): DefaultTheme.SideBarItem[] {
return headers.map((header) => ({
text: header.title,
link: `#${header.slug}`,

@ -0,0 +1,12 @@
<template>
<ul v-if="items.length > 0" class="sidebar-links">
<SideBarLink v-for="item of items" :key="item.text" :item="item" />
</ul>
</template>
<script setup lang="ts">
import { useSideBar } from '../composables/sideBar'
import { SideBarLink } from './SideBarLink'
const items = useSideBar()
</script>

@ -1,13 +1,13 @@
<template functional>
<svg
class="icon outbound"
class="icon"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
x="0px"
y="0px"
viewBox="0 0 100 100"
width="15"
height="15"
width="16"
height="16"
>
<path
fill="currentColor"
@ -21,7 +21,7 @@
</template>
<style>
.icon.outbound {
.icon {
position: relative;
top: -1px;
display: inline-block;

@ -3,66 +3,54 @@ import { onMounted, onUnmounted, onUpdated } from 'vue'
export function useActiveSidebarLinks() {
let rootActiveLink: HTMLAnchorElement | null = null
let activeLink: HTMLAnchorElement | null = null
const decode = decodeURIComponent
const deactiveLink = (link: HTMLAnchorElement | null) =>
link && link.classList.remove('active')
const activateLink = (hash: string) => {
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')
) as HTMLAnchorElement[]
const anchors = [].slice
.call(document.querySelectorAll('.header-anchor'))
.filter((anchor: HTMLAnchorElement) =>
sidebarLinks.some((sidebarLink) => sidebarLink.hash === anchor.hash)
) as HTMLAnchorElement[]
const pageOffset = (document.querySelector('.navbar') as HTMLElement)
.offsetHeight
const scrollTop = window.scrollY
const onScroll = throttleAndDebounce(setActiveLink, 300)
const getAnchorTop = (anchor: HTMLAnchorElement): number =>
anchor.parentElement!.offsetTop - pageOffset - 15
function setActiveLink(): void {
const sidebarLinks = getSidebarLinks()
const anchors = getAnchors(sidebarLinks)
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
const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor)
if (isActive) {
const targetHash = decode(anchor.hash)
history.replaceState(null, document.title, targetHash)
activateLink(targetHash)
history.replaceState(null, document.title, hash ? hash : ' ')
activateLink(hash)
return
}
}
}
const onScroll = throttleAndDebounce(setActiveLink, 300)
function activateLink(hash: string | null): void {
deactiveLink(activeLink)
deactiveLink(rootActiveLink)
activeLink = document.querySelector(`.sidebar a[href="${hash}"]`)
if (!activeLink) {
return
}
activeLink.classList.add('active')
// also add active class to parent h2 anchors
const rootLi = activeLink.closest('.sidebar-links > ul > li')
if (rootLi && rootLi !== activeLink.parentElement) {
rootActiveLink = rootLi.querySelector('a')
rootActiveLink && rootActiveLink.classList.add('active')
} else {
rootActiveLink = null
}
}
function deactiveLink(link: HTMLAnchorElement | null): void {
link && link.classList.remove('active')
}
onMounted(() => {
setActiveLink()
window.addEventListener('scroll', onScroll)
@ -70,7 +58,7 @@ export function useActiveSidebarLinks() {
onUpdated(() => {
// sidebar update means a route change
activateLink(decode(location.hash))
activateLink(decodeURIComponent(location.hash))
})
onUnmounted(() => {
@ -78,11 +66,61 @@ export function useActiveSidebarLinks() {
})
}
function getSidebarLinks(): HTMLAnchorElement[] {
return [].slice.call(
document.querySelectorAll('.sidebar a.sidebar-link-item')
)
}
function getAnchors(sidebarLinks: HTMLAnchorElement[]): HTMLAnchorElement[] {
return [].slice
.call(document.querySelectorAll('.header-anchor'))
.filter((anchor: HTMLAnchorElement) =>
sidebarLinks.some((sidebarLink) => sidebarLink.hash === anchor.hash)
) as HTMLAnchorElement[]
}
function getPageOffset(): number {
return (document.querySelector('.navbar') as HTMLElement).offsetHeight
}
function getAnchorTop(anchor: HTMLAnchorElement): number {
const pageOffset = getPageOffset()
return anchor.parentElement!.offsetTop - pageOffset - 15
}
function isAnchorActive(
index: number,
anchor: HTMLAnchorElement,
nextAnchor: HTMLAnchorElement
): [boolean, string | null] {
const scrollTop = window.scrollY
if (index === 0 && scrollTop === 0) {
return [true, null]
}
if (scrollTop < getAnchorTop(anchor)) {
return [false, null]
}
if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
return [true, decodeURIComponent(anchor.hash)]
}
return [false, null]
}
function throttleAndDebounce(fn: () => void, delay: number): () => void {
let timeout: NodeJS.Timeout
let called = false
return () => {
if (timeout) clearTimeout(timeout)
if (timeout) {
clearTimeout(timeout)
}
if (!called) {
fn()
called = true

@ -1,55 +1,45 @@
import { computed } from 'vue'
import { useRoute, useSiteData } from 'vitepress'
import { DefaultTheme } from '../config'
import { useSiteDataByRoute, usePageData } from 'vitepress'
import { isArray, ensureStartingSlash, removeExtention } from '../utils'
import { getSideBarConfig, getFlatSideBarLinks } from '../support/sideBar'
export function useNextAndPrevLinks() {
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: string) => {
let target: DefaultTheme.SideBarLink | undefined
Object.keys(siteData.value.themeConfig.sidebar).some((k) => {
return siteData.value.themeConfig.sidebar[k].some(
(v: { children: any }) => {
if (Array.isArray(v.children)) {
target = v.children.find((value: any) => {
return value.link === targetLink
})
}
return !!target
}
)
const site = useSiteDataByRoute()
const page = usePageData()
const path = computed(() => {
return removeExtention(ensureStartingSlash(page.value.relativePath))
})
const candidates = computed(() => {
const config = getSideBarConfig(site.value.themeConfig.sidebar, path.value)
return isArray(config) ? getFlatSideBarLinks(config) : []
})
const index = computed(() => {
return candidates.value.findIndex((item) => {
return item.link === path.value
})
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)
if (
site.value.themeConfig.nextLinks !== false &&
index.value > -1 &&
index.value < candidates.value.length - 1
) {
return candidates.value[index.value + 1]
}
return pageData.next
})
const prev = computed(() => {
const pageData = route.data
if (pageData.frontmatter.prev === false) {
return undefined
if (site.value.themeConfig.prevLinks !== false && index.value > 0) {
return candidates.value[index.value - 1]
}
if (typeof pageData.frontmatter.prev === 'string') {
return resolveLink(pageData.frontmatter.prev)
}
return pageData.prev
})
const hasLinks = computed(() => {
return !!next.value || !!prev.value
})
const hasLinks = computed(() => !!next.value || !!prev.value)
return {
next,

@ -0,0 +1,77 @@
import { computed } from 'vue'
import { useRoute, useSiteDataByRoute } from 'vitepress'
import { Header } from '/@types/shared'
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'
import { getSideBarConfig } from '../support/sideBar'
import { DefaultTheme } from '../config'
export function useSideBar() {
const route = useRoute()
const site = useSiteDataByRoute()
useActiveSidebarLinks()
return computed(() => {
// at first, we'll check if we can find the sidebar setting in frontmatter.
const headers = route.data.headers
const frontSidebar = route.data.frontmatter.sidebar
const sidebarDepth = route.data.frontmatter.sidebarDepth
// if it's `false`, we'll just return an empty array here.
if (frontSidebar === false) {
return []
}
// if it's `atuo`, render headers of the current page
if (frontSidebar === 'auto') {
return resolveAutoSidebar(headers, sidebarDepth)
}
// now, there's no sidebar setting at frontmatter; let's see the configs
const themeSidebar = getSideBarConfig(
site.value.themeConfig.sidebar,
route.path
)
if (themeSidebar === false) {
return []
}
if (themeSidebar === 'auto') {
return resolveAutoSidebar(headers, sidebarDepth)
}
return themeSidebar
})
}
function resolveAutoSidebar(
headers: Header[],
depth: number
): DefaultTheme.SideBarItem[] {
const ret: DefaultTheme.SideBarItem[] = []
if (headers === undefined) {
return []
}
let lastH2: DefaultTheme.SideBarItem | undefined = undefined
headers.forEach(({ level, title, slug }) => {
if (level - 1 > depth) {
return
}
const item: DefaultTheme.SideBarItem = {
text: title,
link: `#${slug}`
}
if (level === 2) {
lastH2 = item
ret.push(item)
} else if (lastH2) {
;((lastH2 as any).children || ((lastH2 as any).children = [])).push(item)
}
})
return ret
}

@ -51,7 +51,13 @@ export namespace DefaultTheme {
*/
editLinkText?: string
/**
* Show last updated time at the bottom of the page. Defaults to `false`.
* If given a string, it will be displayed as a prefix (default value:
* "Last Updated").
*/
lastUpdated?: string | boolean
prevLink?: boolean
nextLink?: boolean
}

@ -2,6 +2,7 @@ import './styles/vars.css'
import './styles/layout.css'
import './styles/code.css'
import './styles/custom-blocks.css'
import './styles/sidebar-links.css'
import { Theme } from 'vitepress'
import Layout from './Layout.vue'

@ -1,11 +1,11 @@
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;
border-radius: 3px;
padding: .25rem .5rem;
font-family: var(--code-font-family);
font-size: .85em;
color: var(--c-text-light);
background-color: var(--code-inline-bg-color);
}
code .token.deleted {
@ -13,94 +13,113 @@ code .token.deleted {
}
code .token.inserted {
color: var(--accent-color);
color: var(--c-brand);
}
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;
margin: 1rem -1.5rem;
background-color: var(--code-bg-color);
}
[class*='language-'] pre {
background: transparent;
position: relative;
z-index: 1;
li > div[class*='language-'] {
border-radius: 6px 0 0 6px;
margin: 1rem -1.5rem 1rem -1.25rem;
}
[class*='language-'] code {
color: #eee;
padding: 0;
@media (min-width: 420px) {
div[class*='language-'] {
margin: 1rem 0;
border-radius: 6px;
}
li > div[class*='language-'] {
margin: 1rem 0 1rem 0rem;
border-radius: 6px;
}
}
[class*='language-'] code,
[class*='language-'] pre {
[class*='language-'] pre,
[class*='language-'] code {
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;
}
[class*='language-'] pre {
position: relative;
z-index: 1;
margin: 0;
padding: 1.25rem 1.5rem;
background: transparent;
overflow-x: auto;
}
[class*='language-'] code {
padding: 0;
line-height: var(--code-line-height);
font-size: var(--code-font-size);
color: #eee;
}
/* Line highlighting */
.highlight-lines {
font-size: 0.9em;
user-select: none;
padding-top: 1.3rem;
position: absolute;
top: 0;
bottom: 0;
left: 0;
padding: 1.25rem 0;
width: 100%;
line-height: 1.5;
line-height: var(--code-line-height);
font-family: var(--code-font-family);
font-size: var(--code-font-size);
user-select: none;
}
.highlight-lines .highlighted {
background-color: rgba(0, 0, 0, 66%);
background-color: rgba(0, 0, 0, .66);
}
/* Line numbers mode */
div[class*='language-'].line-numbers-mode {
padding-left: 5rem;
padding-left: 3.5rem;
}
.line-numbers-wrapper {
position: absolute;
top: 0;
bottom: 0;
left: 0;
z-index: 3;
border-right: 1px solid rgba(0, 0, 0, .5);
padding: 1.25rem 0;
width: 3.5rem;
text-align: center;
line-height: var(--code-line-height);
font-family: var(--code-font-family);
font-size: var(--code-font-size);
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;
top: .6em;
right: 1em;
font-size: 0.8rem;
z-index: 2;
font-size: .8rem;
color: #888;
}
@ -192,8 +211,9 @@ div[class*='language-'].line-numbers-mode {
}
/**
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML.
* Based on https://github.com/chriskempson/tomorrow-theme
*
* @author Rose Pritchard
*/
.token.comment,
@ -258,6 +278,7 @@ div[class*='language-'].line-numbers-mode {
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}

@ -1,15 +1,9 @@
.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;
border-left: .5rem solid;
padding: .1rem 1.5rem;
}
.custom-block.tip {
@ -18,9 +12,9 @@
}
.custom-block.warning {
background-color: rgba(255, 229, 100, 0.3);
border-color: #e7c000;
color: #6b5900;
background-color: rgba(255, 229, 100, .3);
}
.custom-block.warning .custom-block-title {
@ -28,13 +22,13 @@
}
.custom-block.warning a {
color: var(--text-color);
color: var(--c-text);
}
.custom-block.danger {
background-color: #ffe6e6;
border-color: #c00;
color: #4d0000;
background-color: #ffe6e6;
}
.custom-block.danger .custom-block-title {
@ -42,12 +36,12 @@
}
.custom-block.danger a {
color: var(--text-color);
color: var(--c-text);
}
.custom-block.details {
display: block;
position: relative;
display: block;
border-radius: 2px;
margin: 1.6em 0;
padding: 1.6em;
@ -68,3 +62,8 @@
outline: none;
cursor: pointer;
}
.custom-block-title {
margin-bottom: -.4rem;
font-weight: 600;
}

@ -5,7 +5,8 @@
}
html {
line-height: 1.15;
line-height: 1.4;
font-size: 16px;
-webkit-text-size-adjust: 100%;
}
@ -15,11 +16,11 @@ body {
min-width: 320px;
min-height: 100vh;
line-height: 1.4;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
font-family: var(--font-family-base);
font-size: 16px;
font-weight: 400;
color: var(--text-color);
background-color: #ffffff;
color: var(--c-text);
background-color: var(--c-bg);
direction: ltr;
font-synthesis: none;
text-rendering: optimizeLegibility;
@ -31,130 +32,14 @@ main {
display: block;
}
.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: var(--header-height);
bottom: 0;
left: 0;
width: var(--sidebar-width);
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;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
line-height: 1.25;
}
h1,
@ -166,7 +51,6 @@ h6,
strong,
b {
font-weight: 600;
line-height: 1.6;
}
h1:hover .header-anchor,
@ -185,6 +69,7 @@ h6:focus .header-anchor {
}
h1 {
margin-top: 1.5rem;
font-size: 1.9rem;
}
@ -195,12 +80,20 @@ h1 {
}
h2 {
font-size: 1.65rem;
margin-top: 2.25rem;
margin-bottom: 1.25rem;
border-bottom: 1px solid var(--c-divider);
padding-bottom: .3rem;
border-bottom: 1px solid var(--border-color);
line-height: 1.25;
font-size: 1.65rem;
}
h2 + h3 {
margin-top: 1.5rem;
}
h3 {
margin-top: 2rem;
font-size: 1.35rem;
}
@ -211,12 +104,25 @@ h4 {
p,
ol,
ul {
margin: 1rem 0;
line-height: 1.7;
}
a,
area,
button,
[role="button"],
input,
label,
select,
summary,
textarea {
touch-action: manipulation;
}
a {
text-decoration: none;
color: var(--accent-color);
color: var(--c-brand);
}
a:hover {
@ -246,6 +152,11 @@ ol {
padding-left: 1.25em;
}
li > ul,
li > ol {
margin: 0;
}
table {
display: block;
border-collapse: collapse;
@ -278,3 +189,106 @@ blockquote {
blockquote > p {
margin: 0;
}
.theme {
font-size: 16px;
color: var(--c-text);
}
.theme .navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--header-height);
background-color: #fff;
border-bottom: 1px solid var(--c-divider);
z-index: var(--z-index-navbar);
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.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);
}
}
.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: 20rem;
}
}
@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: 16.4rem;
}
}
@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;
}
}

@ -0,0 +1,85 @@
.sidebar-links {
margin: 0;
padding: 0;
list-style: none;
}
.sidebar-link-item {
display: block;
margin: 0;
border-left: .25rem solid transparent;
color: var(--c-text);
}
a.sidebar-link-item:hover {
text-decoration: none;
color: var(--c-brand);
}
a.sidebar-link-item.active {
color: var(--c-brand);
}
.sidebar > .sidebar-links {
padding: 1.5rem 0;
}
.sidebar > .sidebar-links > .sidebar-link + .sidebar-link {
padding-top: 1rem;
}
.sidebar > .sidebar-links > .sidebar-link > .sidebar-link-item {
padding: .35rem 1.5rem .35rem 1.25rem;
font-size: 1.1rem;
font-weight: 700;
}
.sidebar > .sidebar-links > .sidebar-link > a.sidebar-link-item.active {
border-left-color: var(--c-brand);
font-weight: 600;
}
.sidebar > .sidebar-links > .sidebar-link > .sidebar-links > .sidebar-link > .sidebar-link-item {
display: block;
padding: .35rem 1.5rem .35rem 2rem;
line-height: 1.4;
font-size: 1rem;
font-weight: 400;
}
.sidebar > .sidebar-links > .sidebar-link > .sidebar-links > .sidebar-link > a.sidebar-link-item.active {
border-left-color: var(--c-brand);
font-weight: 600;
}
.sidebar >
.sidebar-links >
.sidebar-link >
.sidebar-links >
.sidebar-link >
.sidebar-links >
.sidebar-link >
.sidebar-link-item {
display: block;
padding: .3rem 1.5rem .3rem 3rem;
line-height: 1.4;
font-size: .9rem;
font-weight: 400;
}
.sidebar >
.sidebar-links >
.sidebar-link >
.sidebar-links >
.sidebar-link >
.sidebar-links >
.sidebar-link >
.sidebar-links >
.sidebar-link >
.sidebar-link-item {
display: block;
padding: .3rem 1.5rem .3rem 4rem;
line-height: 1.4;
font-size: .9rem;
font-weight: 400;
}

@ -1,14 +1,57 @@
.theme {
--border-color: rgb(226, 232, 240);
/** Base Styles */
:root {
/**
* Colors
* --------------------------------------------------------------------- */
--c-white: #ffffff;
--c-black: #000000;
--c-divider-light: rgba(60, 60, 67, .12);
--c-divider-dark: rgba(84, 84, 88, .48);
--c-text-light-1: #2c3e50;
--c-text-light-2: #476582;
--c-brand: #3eaf7c;
--c-brand-light: #4abf8a;
/**
* Typography
* --------------------------------------------------------------------- */
--font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
--font-family-mono: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
/**
* Z Indexes
* --------------------------------------------------------------------- */
--z-index-navbar: 1100;
--z-index-sidebar: 1000;
/**
* Sizes
* --------------------------------------------------------------------- */
--header-height: 3.6rem;
--sidebar-width: 16.4rem;
--text-color: #2c3e50;
--text-color-light: #476582;
}
/** Fallback Styles */
:root {
--c-divider: var(--c-divider-light);
--c-text: var(--c-text-light-1);
--c-text-light: var(--c-text-light-2);
--c-bg: var(--c-white);
--code-line-height: 24px;
--code-font-family: var(--font-family-mono);
--code-font-size: 14px;
--code-inline-bg-color: rgba(27, 31, 35, .05);
--code-bg-color: #282c34;
--accent-color: #3eaf7c;
/* responsive breakpoints */
/* --mq-narrow: 959px; */
/* --mq-mobile: 719px; */
/* --mq-mobile-narrow: 419px; */
}

@ -0,0 +1,71 @@
import { DefaultTheme } from '../config'
import {
isArray,
ensureSlash,
ensureStartingSlash,
removeExtention
} from '../utils'
export function isSideBarConfig(
sidebar: DefaultTheme.SideBarConfig | DefaultTheme.MultiSideBarConfig
): sidebar is DefaultTheme.SideBarConfig {
return sidebar === false || sidebar === 'auto' || isArray(sidebar)
}
export function isSideBarGroup(
item: DefaultTheme.SideBarItem
): item is DefaultTheme.SideBarGroup {
return (item as DefaultTheme.SideBarGroup).children !== undefined
}
/**
* Get the `SideBarConfig` from sidebar option. This method will ensure to get
* correct sidebar config from `MultiSideBarConfig` with various path
* combinations such as matching `guide/` and `/guide/`. If no matching config
* was found, it will return `auto` as a fallback.
*/
export function getSideBarConfig(
sidebar: DefaultTheme.SideBarConfig | DefaultTheme.MultiSideBarConfig,
path: string
): DefaultTheme.SideBarConfig {
if (isSideBarConfig(sidebar)) {
return sidebar
}
// get the very first segment of the path to compare with nulti sidebar keys
// and make sure it's surrounded by slash
path = removeExtention(path)
path = ensureStartingSlash(path).split('/')[1] || '/'
path = ensureSlash(path)
for (const dir in sidebar) {
// make sure the multi sidebar key is surrounded by slash too
if (path === ensureSlash(dir)) {
return sidebar[dir]
}
}
return 'auto'
}
/**
* Get flat sidebar links from the sidebar items. This method is useful for
* creating the "next and prev link" feature. It will ignore any items that
* don't have `link` property and removes `.md` or `.html` extension if a
* link contains it.
*/
export function getFlatSideBarLinks(
sidebar: DefaultTheme.SideBarItem[]
): DefaultTheme.SideBarLink[] {
return sidebar.reduce<DefaultTheme.SideBarLink[]>((links, item) => {
if (item.link) {
links.push({ text: item.text, link: removeExtention(item.link) })
}
if (isSideBarGroup(item)) {
links = [...links, ...getFlatSideBarLinks(item.children)]
}
return links
}, [])
}

@ -9,6 +9,10 @@ export function isNullish(value: any): value is null | undefined {
return value === null || value === undefined
}
export function isArray(value: any): value is any[] {
return Array.isArray(value)
}
export function withBase(path: string) {
return (useSiteData().value.base + path).replace(/\/+/g, '/')
}
@ -62,6 +66,22 @@ export function getPathDirName(path: string): string {
return ensureEndingSlash(segments.join('/'))
}
export function ensureSlash(path: string): string {
return ensureEndingSlash(ensureStartingSlash(path))
}
export function ensureStartingSlash(path: string): string {
return /^\//.test(path) ? path : `/${path}`
}
export function ensureEndingSlash(path: string): string {
return /(\.html|\/)$/.test(path) ? path : `${path}/`
}
/**
* Remove `.md` or `.html` extention from the given path. It also converts
* `index` to slush.
*/
export function removeExtention(path: string): string {
return path.replace(/(index)?(\.(md|html))?$/, '') || '/'
}

@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "../../dist/client",

@ -67,5 +67,5 @@ export async function build(buildOptions: BuildOptions = {}) {
} finally {
await fs.remove(siteConfig.tempDir)
}
console.log(`✨ done in ${((Date.now() - start) / 1000).toFixed(2)}s.`)
console.log(`build complete in ${((Date.now() - start) / 1000).toFixed(2)}s.`)
}

@ -1,3 +1,4 @@
export * from './server'
export * from './build/build'
export * from './serve/serve'
export * from './config'

@ -1,6 +1,6 @@
// markdown-it plugin for wrapping <pre> ... </pre>.
//
// If your plugin was chained before preWrapper, you can add additional eleemnt directly.
// If your plugin was chained before preWrapper, you can add additional element directly.
// If your plugin was chained after preWrapper, you can use these slots:
// 1. <!--beforebegin-->
// 2. <!--afterbegin-->

@ -0,0 +1,21 @@
import Koa from 'koa'
import koaServe from 'koa-static'
import { resolveConfig } from '../config'
export interface ServeOptions {
root?: string
port?: number
}
export async function serve(options: ServeOptions = {}) {
const port = options.port !== undefined ? options.port : 3000
const site = await resolveConfig(options.root)
const app = new Koa()
app.use(koaServe(site.outDir))
app.listen(port)
console.log(`listening at http://localhost:${port}`)
}

@ -105,18 +105,13 @@ function createVitePressPlugin({
ctx.body = vueSrc
debug(ctx.url, ctx.status)
const pageDataWithLinks = {
...pageData,
// TODO: this doesn't work with locales
...getNextAndPrev(siteData.themeConfig, ctx.path)
}
await next()
// make sure this is the main <script> block
if (!ctx.query.type) {
// inject pageData to generated script
ctx.body += `\nexport const __pageData = ${JSON.stringify(
JSON.stringify(pageDataWithLinks)
JSON.stringify(pageData)
)}`
}
return
@ -133,56 +128,6 @@ function createVitePressPlugin({
}
}
// TODO: share types from SideBarLink, SideBarGroup, etc. We are also assuming
// all themes follow this structure, in which case, we should expose the type
// instead of having any for themeConfig or not nest `sidebar` inside
// `themeConfig`, specially given it must be specified inside `locales` if there
// are any
interface SideBarLink {
text: string
link: string
}
function getNextAndPrev(themeConfig: any, pagePath: string) {
if (!themeConfig.sidebar) {
return
}
const sidebar = themeConfig.sidebar
let candidates: SideBarLink[] = []
Object.keys(sidebar).forEach((k) => {
if (!pagePath.startsWith(k)) {
return
}
sidebar[k].forEach((sidebarItem: { children?: SideBarLink[] }) => {
if (!sidebarItem.children) {
return
}
sidebarItem.children.forEach((candidate) => {
candidates.push(candidate)
})
})
})
const path = pagePath.replace(/\.(md|html)$/, '')
const currentLinkIndex = candidates.findIndex((v) => v.link === path)
const nextAndPrev: { prev?: SideBarLink; next?: SideBarLink } = {}
if (
themeConfig.nextLinks !== false &&
currentLinkIndex > -1 &&
currentLinkIndex < candidates.length - 1
) {
nextAndPrev.next = candidates[currentLinkIndex + 1]
}
if (themeConfig.prevLinks !== false && currentLinkIndex > 0) {
nextAndPrev.next = candidates[currentLinkIndex - 1]
}
return nextAndPrev
}
export async function createServer(options: ServerConfig = {}) {
const config = await resolveConfig(options.root)

@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "../../dist/node",

@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {

@ -1,10 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"moduleResolution": "node",
"strict": true,
"declaration": true,
"noUnusedLocals": true,
"esModuleInterop": true
}
}

@ -0,0 +1,30 @@
{
"compilerOptions": {
"baseUrl": ".",
"module": "esnext",
"target": "esnext",
"moduleResolution": "node",
"strict": true,
"declaration": true,
"noUnusedLocals": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"types": ["node", "jest"],
"paths": {
"src/*": ["src/*"],
"client/*": ["src/client/*"],
"node/*": ["src/node/*"],
"shared/*": ["src/shared/*"],
"tests/*": ["__tests__/*"],
"/@app/*": ["src/client/app/*"],
"/@theme/*": ["src/client/theme-default/*"],
"/@shared/*": ["src/client/shared/*"],
"/@types/*": ["types/*"],
"vitepress": ["src/client/app/exports.ts"]
}
},
"include": [
"src",
"__tests__"
]
}

2
types/shared.d.ts vendored

@ -29,8 +29,6 @@ export interface PageData {
headers: Header[]
relativePath: string
lastUpdated: number
next?: { text: string; link: string }
prev?: { text: string; link: string }
}
export interface Header {

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save