mirror of https://github.com/vuejs/vitepress
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
|
# VitePress Contributing Guide
|
||||||
|
|
||||||
This is a guide to help those who are interested in contributing to VitePress!
|
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:
|
||||||
|
|
||||||
## Prerequisites
|
- [Code of Conduct](https://github.com/vuejs/vue/blob/dev/.github/CODE_OF_CONDUCT.md)
|
||||||
|
- [Pull Request Guidelines](#pull-request-guidelines)
|
||||||
|
|
||||||
- [yarn](https://classic.yarnpkg.com/en/docs/cli/install/)
|
## Pull Request Guidelines
|
||||||
|
|
||||||
## Instructions
|
- Checkout a topic branch from the relevant branch, e.g. `master`, and merge back against that branch.
|
||||||
|
|
||||||
### Setup VitePress dev environment
|
- 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:
|
||||||
|
|
||||||
1. Clone the VitePress repo
|
|
||||||
2. Install dependencies
|
|
||||||
```
|
|
||||||
yarn
|
|
||||||
```
|
|
||||||
3. Create symlink to allow projects to link to local VitePress dev environment
|
|
||||||
```bash
|
```bash
|
||||||
yarn link
|
$ yarn # install the dependencies of the project
|
||||||
```
|
|
||||||
- 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
|
|
||||||
|
### Setup VitePress Dev Environment
|
||||||
|
|
||||||
|
You may start VitePress local dev environment by running `yarn dev`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn dev
|
$ yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setup local VitePress project
|
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.
|
||||||
|
|
||||||
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
|
```bash
|
||||||
yarn link vitepress
|
$ yarn docs
|
||||||
```
|
|
||||||
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! 🎉
|
After executing the above command, visit http://localhost:3000 and try modifying the source code. You'll get live update.
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
node_modules
|
/coverage
|
||||||
dist
|
|
||||||
/src/client/shared
|
/src/client/shared
|
||||||
/src/node/shared
|
/src/node/shared
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vite_opt_cache
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
TODOs.md
|
TODOs.md
|
||||||
|
@ -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/')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -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...
|
|
@ -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.
|
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 223 KiB |
@ -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/']
|
||||||
|
}
|
@ -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>
|
<template>
|
||||||
<div class="debug" :class="{ open }" @click="open = !open">
|
<div class="debug" :class="{ open }" ref="el" @click="open = !open">
|
||||||
<pre>debug</pre>
|
<p class="title">Debug</p>
|
||||||
<pre>$page {{ $page }}</pre>
|
|
||||||
<pre>$siteByRoute {{ $siteByRoute }}</pre>
|
<pre class="block">$page {{ $page }}</pre>
|
||||||
<pre>$site {{ $site }}</pre>
|
<pre class="block">$siteByRoute {{ $siteByRoute }}</pre>
|
||||||
|
<pre class="block">$site {{ $site }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { ref } from 'vue'
|
import { defineComponent, ref, watch } from 'vue'
|
||||||
|
|
||||||
export default {
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
|
const el = ref<HTMLElement | null>(null)
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
|
|
||||||
|
watch(open, (value) => {
|
||||||
|
if (value === false) {
|
||||||
|
el.value!.scrollTop = 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
el,
|
||||||
open
|
open
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
.debug {
|
.debug {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 999;
|
right: 8px;
|
||||||
cursor: pointer;
|
bottom: 8px;
|
||||||
bottom: 0;
|
z-index: 9999;
|
||||||
right: 0;
|
border-radius: 4px;
|
||||||
width: 80px;
|
width: 74px;
|
||||||
height: 30px;
|
height: 32px;
|
||||||
padding: 5px;
|
|
||||||
overflow: hidden;
|
|
||||||
color: #eeeeee;
|
color: #eeeeee;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: rgba(0, 0, 0, .85);
|
||||||
transition: all .15s ease;
|
transition: all .15s ease;
|
||||||
background-color: rgba(0,0,0,0.85);
|
}
|
||||||
|
|
||||||
|
.debug:hover {
|
||||||
|
background-color: rgba(0, 0, 0, .75);
|
||||||
}
|
}
|
||||||
|
|
||||||
.debug.open {
|
.debug.open {
|
||||||
width: 500px;
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
border-radius: 0;
|
||||||
padding: 0 0;
|
padding: 0 0;
|
||||||
overflow: scroll;
|
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-family: Hack, monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
margin: 0;
|
}
|
||||||
padding: 5px 10px;
|
|
||||||
border-bottom: 1px solid #eee;
|
.block +.block {
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -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>
|
<template>
|
||||||
<a
|
<NavBarTitle />
|
||||||
class="title"
|
|
||||||
:aria-label="$site.title + ', back to home'"
|
<div class="flex-grow" />
|
||||||
:href="$site.base"
|
|
||||||
>
|
<div class="nav">
|
||||||
<img
|
<NavBarLinks />
|
||||||
class="logo"
|
</div>
|
||||||
v-if="$theme.logo"
|
|
||||||
:src="withBase($theme.logo)"
|
|
||||||
alt="logo"
|
|
||||||
/>
|
|
||||||
<span>{{ $site.title }}</span>
|
|
||||||
</a>
|
|
||||||
<div class="flex-grow"></div>
|
|
||||||
<NavBarLinks class="hide-mobile" />
|
|
||||||
<slot name="search" />
|
<slot name="search" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { withBase } from '../utils'
|
import NavBarTitle from './NavBarTitle.vue'
|
||||||
import NavBarLinks from './NavBarLinks.vue'
|
import NavBarLinks from './NavBarLinks.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
.title {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-grow {
|
.flex-grow {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.nav {
|
||||||
margin-right: 0.75rem;
|
display: none;
|
||||||
height: 1.3rem;
|
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 719px) {
|
@media (min-width: 720px) {
|
||||||
.hide-mobile {
|
.nav {
|
||||||
display: none;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
@ -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>
|
<template>
|
||||||
|
<aside class="sidebar" :class="{ open }">
|
||||||
|
<div class="nav">
|
||||||
<NavBarLinks class="show-mobile" />
|
<NavBarLinks class="show-mobile" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<slot name="top" />
|
<slot name="sidebar-top" />
|
||||||
|
|
||||||
<ul class="sidebar">
|
<SideBarLinks />
|
||||||
<SideBarItem v-for="item of items" :item="item" />
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<slot name="bottom" />
|
<slot name="sidebar-bottom" />
|
||||||
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute, useSiteDataByRoute } from 'vitepress'
|
import { defineProps } from 'vue'
|
||||||
import { computed } from 'vue'
|
|
||||||
import { getPathDirName } from '../utils'
|
|
||||||
import { useActiveSidebarLinks } from '../composables/activeSidebarLink'
|
|
||||||
import NavBarLinks from './NavBarLinks.vue'
|
import NavBarLinks from './NavBarLinks.vue'
|
||||||
import { SideBarItem } from './SideBarItem'
|
import SideBarLinks from './SideBarLinks.vue'
|
||||||
import type { DefaultTheme } from '../config'
|
|
||||||
import type { Header } from '../../../../types/shared'
|
|
||||||
import type { ResolvedSidebarItem } from './SideBarItem'
|
|
||||||
|
|
||||||
type ResolvedSidebar = ResolvedSidebarItem[]
|
defineProps({
|
||||||
|
open: { type: Boolean, required: true }
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
.show-mobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 719px) {
|
|
||||||
.show-mobile {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar,
|
|
||||||
.sidebar-items {
|
|
||||||
list-style-type: none;
|
|
||||||
line-height: 2;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
padding: 1.5rem 0;
|
position: fixed;
|
||||||
}
|
top: var(--header-height);
|
||||||
|
bottom: 0;
|
||||||
.sidebar-data {
|
left: 0;
|
||||||
padding: 1.5rem 0;
|
z-index: var(--z-index-sidebar);
|
||||||
}
|
border-right: 1px solid var(--c-divider);
|
||||||
|
width: 16.4rem;
|
||||||
@media screen and (max-width: 719px) {
|
background-color: var(--c-bg);
|
||||||
.sidebar-data {
|
overflow-y: auto;
|
||||||
padding: 1rem;
|
transform: translateX(-100%);
|
||||||
}
|
transition: transform 0.25s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-items .sidebar-items {
|
@media (min-width: 720px) {
|
||||||
padding-left: 1rem;
|
.sidebar {
|
||||||
}
|
transform: translateX(0);
|
||||||
|
|
||||||
.sidebar-items .sidebar-items .sidebar-link {
|
|
||||||
border-left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-items .sidebar-items .sidebar-link.active {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-items .sidebar-link {
|
@media (min-width: 960px) {
|
||||||
padding: 0.35rem 1rem 0.35rem 2rem;
|
.sidebar {
|
||||||
line-height: 1.4;
|
width: 20rem;
|
||||||
font-size: 0.95em;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-item + .sidebar-item {
|
|
||||||
padding-top: 0.75rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-items > .sidebar-item + .sidebar-item {
|
.sidebar.open {
|
||||||
padding-top: 0;
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-link {
|
.nav {
|
||||||
display: block;
|
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 {
|
@media (min-width: 720px) {
|
||||||
text-decoration: none;
|
.nav {
|
||||||
color: var(--accent-color);
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.sidebar-link.active {
|
|
||||||
border-left-color: var(--accent-color);
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -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>
|
@ -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
|
||||||
|
}
|
@ -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 {
|
/** Base Styles */
|
||||||
--border-color: rgb(226, 232, 240);
|
: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;
|
--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;
|
--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
|
||||||
|
}, [])
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
export * from './server'
|
export * from './server'
|
||||||
export * from './build/build'
|
export * from './build/build'
|
||||||
|
export * from './serve/serve'
|
||||||
export * from './config'
|
export * from './config'
|
||||||
|
@ -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}`)
|
||||||
|
}
|
@ -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__"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in new issue