mirror of https://github.com/vuejs/vitepress
commit
baf8083e66
@ -0,0 +1,7 @@
|
|||||||
|
/docs
|
||||||
|
/examples
|
||||||
|
*.css
|
||||||
|
*.md
|
||||||
|
*.vue
|
||||||
|
dist
|
||||||
|
pnpm-lock.yaml
|
@ -1,134 +0,0 @@
|
|||||||
import {
|
|
||||||
isSideBarEmpty,
|
|
||||||
getSideBarConfig,
|
|
||||||
getFlatSideBarLinks
|
|
||||||
} from 'client/theme-default/support/sideBar'
|
|
||||||
|
|
||||||
describe('client/theme-default/support/sideBar', () => {
|
|
||||||
it('checks if the given sidebar is empty', () => {
|
|
||||||
expect(isSideBarEmpty(undefined)).toBe(true)
|
|
||||||
expect(isSideBarEmpty(false)).toBe(true)
|
|
||||||
expect(isSideBarEmpty([])).toBe(true)
|
|
||||||
|
|
||||||
expect(isSideBarEmpty('auto')).toBe(false)
|
|
||||||
expect(isSideBarEmpty([{ text: 'a', link: '/a' }])).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 = {
|
|
||||||
'/guide/': [{ text: 'G', link: 'g' }],
|
|
||||||
'/': [{ text: 'R', link: 'r' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
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/'])
|
|
||||||
// no ending slash should not match
|
|
||||||
expect(getSideBarConfig(s, '/guide')).not.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)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,41 +0,0 @@
|
|||||||
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/')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,10 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
node: path.resolve(__dirname, '../src/node'),
|
|
||||||
client: path.resolve(__dirname, '../src/client')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,17 @@
|
|||||||
|
import { dirname, resolve } from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
const dir = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
node: resolve(dir, '../src/node'),
|
||||||
|
client: resolve(dir, '../src/client')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
globals: true
|
||||||
|
}
|
||||||
|
})
|
@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
|
||||||
|
|
||||||
"projectFolder": "./src/client",
|
|
||||||
|
|
||||||
"mainEntryPointFilePath": "./dist/temp/index.d.ts",
|
|
||||||
|
|
||||||
"dtsRollup": {
|
|
||||||
"enabled": true,
|
|
||||||
"publicTrimmedFilePath": "./dist/client/index.d.ts"
|
|
||||||
},
|
|
||||||
|
|
||||||
"apiReport": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"docModel": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"tsdocMetadata": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"messages": {
|
|
||||||
"compilerMessageReporting": {
|
|
||||||
"default": {
|
|
||||||
"logLevel": "warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"extractorMessageReporting": {
|
|
||||||
"default": {
|
|
||||||
"logLevel": "warning",
|
|
||||||
"addToApiReportFile": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"ae-missing-release-tag": {
|
|
||||||
"logLevel": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"tsdocMessageReporting": {
|
|
||||||
"default": {
|
|
||||||
"logLevel": "warning"
|
|
||||||
},
|
|
||||||
|
|
||||||
"tsdoc-undefined-tag": {
|
|
||||||
"logLevel": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
|
||||||
|
|
||||||
"projectFolder": "./src/node",
|
|
||||||
|
|
||||||
"mainEntryPointFilePath": "./dist/temp/index.d.ts",
|
|
||||||
|
|
||||||
"dtsRollup": {
|
|
||||||
"enabled": true,
|
|
||||||
"publicTrimmedFilePath": "./dist/node/index.d.ts"
|
|
||||||
},
|
|
||||||
|
|
||||||
"apiReport": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"docModel": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"tsdocMetadata": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"messages": {
|
|
||||||
"compilerMessageReporting": {
|
|
||||||
"default": {
|
|
||||||
"logLevel": "warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"extractorMessageReporting": {
|
|
||||||
"default": {
|
|
||||||
"logLevel": "warning",
|
|
||||||
"addToApiReportFile": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"ae-missing-release-tag": {
|
|
||||||
"logLevel": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"tsdocMessageReporting": {
|
|
||||||
"default": {
|
|
||||||
"logLevel": "warning"
|
|
||||||
},
|
|
||||||
|
|
||||||
"tsdoc-undefined-tag": {
|
|
||||||
"logLevel": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +1,2 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
require('../dist/node/cli')
|
import('../dist/node/cli.js')
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
// re-export vite client types
|
// re-export vite client types. with strict installers like pnpm, user won't
|
||||||
// with strict installers like pnpm, user won't be able to reference vite/client
|
// be able to reference vite/client in project root.
|
||||||
// in project root
|
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
# Theme Config: Algolia Search
|
|
||||||
|
|
||||||
The `themeConfig.algolia` option allows you to use [Algolia DocSearch](https://docsearch.algolia.com). To enable it, you need to provide at least appId, apiKey and indexName:
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = {
|
|
||||||
themeConfig: {
|
|
||||||
algolia: {
|
|
||||||
appId: 'your_app_id',
|
|
||||||
apiKey: 'your_api_key',
|
|
||||||
indexName: 'index_name'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For more options, check out [Algolia DocSearch's documentation](https://docsearch.algolia.com/docs/api/). You can pass any extra option alongside other options, e.g. passing `searchParameters`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = {
|
|
||||||
themeConfig: {
|
|
||||||
algolia: {
|
|
||||||
appId: 'your_app_id',
|
|
||||||
apiKey: 'your_api_key',
|
|
||||||
indexName: 'index_name',
|
|
||||||
searchParameters: {
|
|
||||||
facetFilters: ['tags:guide,api']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Internationalization (i18n)
|
|
||||||
|
|
||||||
If you have multiple locales in your documentation and you have defined a `locales` object in your `themeConfig`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = {
|
|
||||||
themeConfig: {
|
|
||||||
locales: {
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
algolia: {
|
|
||||||
appId: 'your_app_id',
|
|
||||||
apiKey: 'your_api_key',
|
|
||||||
indexName: 'index_name'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
VitePress will automatically add a `lang` _facetFilter_ to the `searchParameters.facetFilter` array with the correct language value. Algolia automatically adds the correct facet filter based on the `lang` attribute on the `<html>` tag. This will match search results with the currently viewed language of the page.
|
|
@ -0,0 +1,161 @@
|
|||||||
|
# App Configs
|
||||||
|
|
||||||
|
App configs are where you can define the global settings of the site. App configs define fundamental settings that are not only limited to the theme configs such as configuration for "base directory", or the "title" of the site.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
// These are app level configs.
|
||||||
|
lang: 'en-US',
|
||||||
|
title: 'VitePress',
|
||||||
|
description: 'Vite & Vue powered static site generator.',
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
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.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
lang: 'en-US'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## title
|
||||||
|
|
||||||
|
- Type: `string`
|
||||||
|
- Default: `VitePress`
|
||||||
|
|
||||||
|
Title for the site. This will be displayed in the nav bar. Also used as the suffix for all page titles unless `titleTemplate` is defined.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
title: 'VitePress'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## titleTemplate
|
||||||
|
|
||||||
|
- Type: `string | boolean`
|
||||||
|
|
||||||
|
The suffix for the title. For example, if you set `title` as `VitePress` and set `titleTemplate` as `My Site`, the html title becomes `VitePress | My Site`.
|
||||||
|
|
||||||
|
Set `false` to disable the feature. If the option is `undefined`, then the value of `title` option will be used.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
title: 'VitePress',
|
||||||
|
titleTemplate: 'Vite & Vue powered static site generator'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## description
|
||||||
|
|
||||||
|
- Type: `string`
|
||||||
|
- Default: `A VitePress site`
|
||||||
|
|
||||||
|
Description for the site. This will render as a `<meta>` tag in the page HTML.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
description: 'A VitePress site'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## markdown
|
||||||
|
|
||||||
|
- Type: `MarkdownOption`
|
||||||
|
|
||||||
|
Configre Markdown parser options. VitePress uses [Markdown-it](https://github.com/markdown-it/markdown-it) as the parser, and [Shiki](https://shiki.matsu.io/) to highlight language syntax. Inside this option, you may pass various Markdown related options to fit your needs.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
markdown: {
|
||||||
|
theme: 'material-palenight',
|
||||||
|
lineNumbers: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Below shows the the full option you may define within this object.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface MarkdownOptions extends MarkdownIt.Options {
|
||||||
|
// Syntax highlight theme for Shiki.
|
||||||
|
// See: https://github.com/shikijs/shiki/blob/main/docs/themes.md#all-themes
|
||||||
|
theme?: Shiki.Theme | { light: Shiki.Theme, dark: Shiki.Theme }
|
||||||
|
|
||||||
|
// Enable line numbers in code block.
|
||||||
|
lineNumbers?: boolean
|
||||||
|
|
||||||
|
// markdown-it-anchor plugin options.
|
||||||
|
// See: https://github.com/valeriangalliat/markdown-it-anchor
|
||||||
|
anchor?: {
|
||||||
|
permalink?: anchor.AnchorOptions['permalink']
|
||||||
|
}
|
||||||
|
|
||||||
|
// markdown-it-attrs plugin options.
|
||||||
|
// See: https://github.com/arve0/markdown-it-attrs
|
||||||
|
attrs?: {
|
||||||
|
leftDelimiter?: string
|
||||||
|
rightDelimiter?: string
|
||||||
|
allowedAttributes?: string[]
|
||||||
|
disable?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// markdown-it-toc-done-right plugin options
|
||||||
|
// https://github.com/nagaozen/markdown-it-toc-done-right
|
||||||
|
toc?: any
|
||||||
|
|
||||||
|
// Configure the Markdown-it instance to fully customize
|
||||||
|
// how it works.
|
||||||
|
config?: (md: MarkdownIt) => void
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## appearance
|
||||||
|
|
||||||
|
- Type: `boolean`
|
||||||
|
- Default: `true`
|
||||||
|
|
||||||
|
Whether to enable "Dark Mode" or not. If the option is set to `true`, it adds `.dark` class to the `<html>` tag depending on the users preference.
|
||||||
|
|
||||||
|
It also injects inline script that tries to read users settings from local storage by `vitepress-theme-appearance` key and restores users preferred color mode.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
appearance: true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## lastUpdated
|
||||||
|
|
||||||
|
- Type: `boolean`
|
||||||
|
- Default: `false`
|
||||||
|
|
||||||
|
Use git commit to get the timestamp. This option enables the default theme to display the page's last updated time. You can customize the text via [`themeConfig.lastUpdatedText`](theme-configs#lastupdatedtext) option.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
lastUpdated: true
|
||||||
|
}
|
||||||
|
```
|
@ -1,59 +0,0 @@
|
|||||||
# App Config: Basics
|
|
||||||
|
|
||||||
::: tip
|
|
||||||
The config reference is incomplete since the config format may still receive further changes. For a complete reference of the current available options, refer to [config.ts](https://github.com/vuejs/vitepress/blob/45b65ce8b63bd54f345bfc3383eb2416b6769dc9/src/node/config.ts#L30-L65).
|
|
||||||
:::
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = {
|
|
||||||
lang: 'en-US'
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## title
|
|
||||||
|
|
||||||
- Type: `string`
|
|
||||||
- Default: `VitePress`
|
|
||||||
|
|
||||||
Title for the site. This will be the suffix 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 = {
|
|
||||||
description: 'A VitePress site'
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,15 +0,0 @@
|
|||||||
# Theme Config: Carbon Ads
|
|
||||||
|
|
||||||
VitePress has built in native support for [Carbon Ads](https://www.carbonads.net). By defining the Carbon Ads credentials in config, VitePress will display ads on the page.
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = {
|
|
||||||
themeConfig: {
|
|
||||||
carbonAds: {
|
|
||||||
carbon: 'your-carbon-key',
|
|
||||||
custom: 'your-carbon-custom',
|
|
||||||
placement: 'your-carbon-placement'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,23 +0,0 @@
|
|||||||
# Theme Config: Homepage
|
|
||||||
|
|
||||||
VitePress provides a homepage layout. To use it, specify `home: true` plus some other metadata in your root `index.md`'s [YAML frontmatter](../guide/frontmatter). This is an example of how it works:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
---
|
|
||||||
home: true
|
|
||||||
heroImage: /logo.png
|
|
||||||
heroAlt: Logo image
|
|
||||||
heroText: Hero Title
|
|
||||||
tagline: Hero subtitle
|
|
||||||
actionText: Get Started
|
|
||||||
actionLink: /guide/
|
|
||||||
features:
|
|
||||||
- title: Simplicity First
|
|
||||||
details: Minimal setup with markdown-centered project structure helps you focus on writing.
|
|
||||||
- title: Vue-Powered
|
|
||||||
details: Enjoy the dev experience of Vue + webpack, use Vue components in markdown, and develop custom themes with Vue.
|
|
||||||
- title: Performant
|
|
||||||
details: VitePress generates pre-rendered static HTML for each page, and runs as an SPA once a page is loaded.
|
|
||||||
footer: MIT Licensed | Copyright © 2019-present Evan You
|
|
||||||
---
|
|
||||||
```
|
|
@ -0,0 +1,72 @@
|
|||||||
|
# Introduction
|
||||||
|
|
||||||
|
Place your configuration file at `.vitepress/config.js`. This is where all VitePress-specific files will be placed.
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├─ docs
|
||||||
|
│ ├─ .vitepress
|
||||||
|
│ │ └─ config.js
|
||||||
|
│ └─ index.md
|
||||||
|
└─ package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
VitePress comes with 2 types of configs. One is the [App Configs](./app-configs) which configures the site's fundamental features such as setting title of the site, or customize how markdown parser works. Second is the [Theme Config](./theme-configs) which configures the theme of the site, for example, adding a sidebar, or add features such as "Edit this page on GitHub" link.
|
||||||
|
|
||||||
|
There's also another configuration you may do in [Frontmatter](./frontmatter-configs). Frontmatter configs can override global configs defined on App Configs or Theme Configs for that specific page. However, there're several options that are only available at frontmatter as well.
|
||||||
|
|
||||||
|
Please refer to the corresponding configs page to learn more.
|
||||||
|
|
||||||
|
## Config Intellisense
|
||||||
|
|
||||||
|
Since VitePress ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints:
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @type {import('vitepress').UserConfig}
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can use the `defineConfig` helper at which should provide intellisense without the need for jsdoc annotations:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
VitePress also directly supports TS config files. You can use `.vitepress/config.ts` with the `defineConfig` helper as well.
|
||||||
|
|
||||||
|
## Typed Theme Config
|
||||||
|
|
||||||
|
By default, `defineConfig` helper leverages the theme config type from default theme:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
themeConfig: {
|
||||||
|
// Type is `DefaultTheme.Config`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use a custom theme and want type checks for the theme config, you'll need to use `defineConfigWithTheme` instead, and pass the config type for your custom theme via a generic argument:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfigWithTheme } from 'vitepress'
|
||||||
|
import { ThemeConfig } from 'your-theme'
|
||||||
|
|
||||||
|
export default defineConfigWithTheme<ThemeConfig>({
|
||||||
|
themeConfig: {
|
||||||
|
// Type is `ThemeConfig`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
@ -0,0 +1,230 @@
|
|||||||
|
# Theme Configs
|
||||||
|
|
||||||
|
Theme configs let you customize your theme. You can define theme configs by adding `themeConfig` key to the config file.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
lang: 'en-US',
|
||||||
|
title: 'VitePress',
|
||||||
|
description: 'Vite & Vue powered static site generator.',
|
||||||
|
|
||||||
|
// Theme related configurations.
|
||||||
|
themeConfig: {
|
||||||
|
logo: '/logo.svg',
|
||||||
|
nav: [...],
|
||||||
|
sidebar: { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here it describes the settings for the VitePress default theme. If you're using a custom theme created by others, these settings may not have any effect, or might behave differently.
|
||||||
|
|
||||||
|
## logo
|
||||||
|
|
||||||
|
- Type: `string`
|
||||||
|
|
||||||
|
Logo file to display in nav bar, right before the site title.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
logo: '/logo.svg'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## siteTitle
|
||||||
|
|
||||||
|
- Type: `string | false`
|
||||||
|
|
||||||
|
You can customize this item to replace the default site title (`title` in app config) in nav. When set to `false`, title in nav will be disabled. Useful when you have `logo` that already contains the site title text.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
siteTitle: 'Hello World'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## nav
|
||||||
|
|
||||||
|
- Type: `NavItem`
|
||||||
|
|
||||||
|
The configuration for the nav menu item. You may learn more details at [Theme: Nav](../guide/theme-nav#navigation-links).
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
nav: [
|
||||||
|
{ text: 'Guide', link: '/guide' },
|
||||||
|
{
|
||||||
|
text: 'Dropdown Menu',
|
||||||
|
items: [
|
||||||
|
{ text: 'Item A', link: '/item-1' },
|
||||||
|
{ text: 'Item B', link: '/item-2' },
|
||||||
|
{ text: 'Item C', link: '/item-3' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type NavItem = NavItemWithLink | NavItemWithChildren
|
||||||
|
|
||||||
|
type NavItemWithLink = {
|
||||||
|
text: string
|
||||||
|
link: string
|
||||||
|
activeMatch?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NavItemWithChildren {
|
||||||
|
text?: string
|
||||||
|
items: NavItemWithLink[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## sidebar
|
||||||
|
|
||||||
|
- Type: `Sidebar`
|
||||||
|
|
||||||
|
The configuration for the sidebar menu item. You may learn more details at [Theme: Nav](../guide/theme-sidebar).
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
text: 'Guide',
|
||||||
|
items: [
|
||||||
|
{ text: 'Introduction', link: '/introduction' },
|
||||||
|
{ text: 'Getting Started', link: '/getting-started' },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Sidebar = SidebarGroup[] | SidebarMulti
|
||||||
|
|
||||||
|
interface SidebarMulti {
|
||||||
|
[path: string]: SidebarGroup[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SidebarGroup {
|
||||||
|
text: string
|
||||||
|
items: SidebarItem[]
|
||||||
|
collapsible?: boolean
|
||||||
|
collapsed?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SidebarItem {
|
||||||
|
text: string
|
||||||
|
link: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## socialLinks
|
||||||
|
|
||||||
|
- Type: `SocialLink`
|
||||||
|
|
||||||
|
You may define this option to show your social account links with icons in nav.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
socialLinks: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' },
|
||||||
|
{ icon: 'twitter', link: '...' },
|
||||||
|
{ icon: 'discord', link: '...' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface SocialLink {
|
||||||
|
icon: SocialLinkIcon
|
||||||
|
link: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SocialLinkIcon =
|
||||||
|
| 'discord'
|
||||||
|
| 'facebook'
|
||||||
|
| 'github'
|
||||||
|
| 'instagram'
|
||||||
|
| 'linkedin'
|
||||||
|
| 'slack'
|
||||||
|
| 'twitter'
|
||||||
|
| 'youtube'
|
||||||
|
```
|
||||||
|
|
||||||
|
## footer
|
||||||
|
|
||||||
|
- Type: `Footer`
|
||||||
|
|
||||||
|
Footer configuration. You can add a message and copyright. The footer will displayed only when the page doesn't contain sidebar due to design reason.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the MIT License.',
|
||||||
|
copyright: 'Copyright © 2019-present Evan You'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface Footer {
|
||||||
|
message?: string
|
||||||
|
copyright?: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## lastUpdatedText
|
||||||
|
|
||||||
|
- Type: `string`
|
||||||
|
- Default: `Last updated`
|
||||||
|
|
||||||
|
The prefix text showing right before the last updated time.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
lastUpdatedText: 'Updated Date'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## carbonAds
|
||||||
|
|
||||||
|
- Type: `CarbonAds`
|
||||||
|
|
||||||
|
A option to display [Carbon Ads](https://www.carbonads.net/).
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
carbonAds: {
|
||||||
|
code: 'your-carbon-code',
|
||||||
|
placement: 'your-carbon-placement'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface CarbonAds {
|
||||||
|
code: string,
|
||||||
|
placement: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Learn more in [Theme: Carbon Ads](../guide/theme-carbon-ads)
|
@ -1,119 +0,0 @@
|
|||||||
---
|
|
||||||
sidebarDepth: 2
|
|
||||||
---
|
|
||||||
|
|
||||||
# Differences from VuePress
|
|
||||||
|
|
||||||
VitePress and VuePress have different [design goals](../index.md). Both projects share similar config naming conventions. VitePress aims to have the bare minimum features needed for authoring docs. Other features are pushed to Themes. On the other hand, VuePress has more features out-of-the-box or enabled by its ecosystem of plugins.
|
|
||||||
|
|
||||||
::: tip
|
|
||||||
If you are using VuePress, there is no need to migrate to VitePress. Both projects are going to continue to co-exist for the foreseeable future.
|
|
||||||
:::
|
|
||||||
|
|
||||||
::: warning
|
|
||||||
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.
|
|
||||||
:::
|
|
||||||
|
|
||||||
In case you decide to move your project to VitePress, this is a list of differences from [VuePress v1.7.1](https://github.com/vuejs/vuepress/releases/tag/v1.7.1) that you need to take into account.
|
|
||||||
|
|
||||||
## General
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- YAML and TOML are not supported formats for site config. Only javascript is supported for `.vitepress/config.js`
|
|
||||||
- [Plugins](https://vuepress.vuejs.org/plugin/) support, features are implemented in themes
|
|
||||||
- [permalink support](https://vuepress.vuejs.org/guide/permalinks.html)
|
|
||||||
- `.vitepress/templates`
|
|
||||||
- Components in `.vitepress/components` [are not auto registered as global components](https://vuepress.vuejs.org)
|
|
||||||
- Differences
|
|
||||||
- [Public files](https://vuepress.vuejs.org/guide/assets.html#public-files) that are directly copied to dist root moved from `.vitepress/public/` is `public/`
|
|
||||||
- [styling](https://vuepress.vuejs.org/config/#styling) `.vitepress/styles/index.styl` and `.vitepress/styles/palette.styl` is not supported. See [Customizing CSS](./theming#customizing-css).
|
|
||||||
- [App Level Enhancements](https://vuepress.vuejs.org/guide/basic-config.html#app-level-enhancements) API, app enhancements `.vitepress/enhanceApp.js` is now done in `.vitepress/theme/index.js`. See [Extending the Default Theme](./theming#extending-the-default-theme).
|
|
||||||
|
|
||||||
## Markdown
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- Support for [toml in frontmatter](https://vuepress.vuejs.org/guide/frontmatter.html#alternative-frontmatter-formats)
|
|
||||||
- [details block](https://vuepress.vuejs.org/guide/markdown.html#custom-containers)
|
|
||||||
- [markdown slots](https://vuepress.vuejs.org/guide/markdown-slot.html)
|
|
||||||
- `~` prefix to explicitly specify a url is a [webpack module request](https://vuepress.vuejs.org/guide/assets.html#relative-urls)
|
|
||||||
|
|
||||||
## Site Config
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- `temp`
|
|
||||||
- `dest`
|
|
||||||
- [`theme` from a dependency](https://vuepress.vuejs.org/theme/using-a-theme.html#using-a-theme-from-a-dependency)
|
|
||||||
- `permalink`
|
|
||||||
- [`port`](https://vuepress.vuejs.org/config/#port)
|
|
||||||
- [`shouldPrefetch`](https://vuepress.vuejs.org/config/#shouldprefetch)
|
|
||||||
- [`cache`](https://vuepress.vuejs.org/config/#cache)
|
|
||||||
- [`extraWatchFiles`](https://vuepress.vuejs.org/config/#extrawatchfiles)
|
|
||||||
- [`patterns`](https://vuepress.vuejs.org/config/#patterns)
|
|
||||||
- [`plugins`](https://vuepress.vuejs.org/config/#pluggable)
|
|
||||||
- [`markdown.pageSuffix`](https://vuepress.vuejs.org/config/#markdown-pagesuffix)
|
|
||||||
- [`markdown.slugify`](https://vuepress.vuejs.org/config/#markdown-slugify)
|
|
||||||
- [`markdown.plugins`](https://vuepress.vuejs.org/config/#markdown-plugins)
|
|
||||||
- [`markdown.extractHeaders`](https://vuepress.vuejs.org/config/#markdown-extractheaders)
|
|
||||||
- `markdown.extendMarkdown` to `markdown.config`
|
|
||||||
- `configureWebpack`, `chainWebpack`, `postcss`, `Stylus`, `scss`, `Sass`, `less` configs
|
|
||||||
- [`evergreen`](https://vuepress.vuejs.org/config/#evergreen)
|
|
||||||
|
|
||||||
## Default Theme Config
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- [`smoothScroll`](https://vuepress.vuejs.org/theme/default-theme-config.html#smooth-scrolling)
|
|
||||||
- [`displayAllHeaders`](https://vuepress.vuejs.org/theme/default-theme-config.html#displaying-header-links-of-all-pages)
|
|
||||||
- [`activeHeaderLinks`](https://vuepress.vuejs.org/theme/default-theme-config.html#active-header-links)
|
|
||||||
- `sidebarDepth` and `initialOpenGroupIndex` for [sidebar groups](https://vuepress.vuejs.org/theme/default-theme-config.html#sidebar-groups)
|
|
||||||
- Differences
|
|
||||||
- `searchMaxSuggestions` is `search.maxSuggestions`
|
|
||||||
- `algolia` is `search.algolia`
|
|
||||||
- `searchPlaceholder` is `search.placeholder`
|
|
||||||
|
|
||||||
## Default Theme
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- [`<code-group>` and `<code-block>`](https://vuepress.vuejs.org/theme/default-theme-config.html#code-groups-and-code-blocks)
|
|
||||||
|
|
||||||
## Computed Globals
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- `$lang`
|
|
||||||
- `$localePath`
|
|
||||||
|
|
||||||
## Frontmatter Predefined Variables
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- `description`
|
|
||||||
- [`meta`](https://vuepress.vuejs.org/guide/frontmatter.html#meta)
|
|
||||||
- [`metaTitle`](https://vuepress.vuejs.org/guide/frontmatter.html#predefined-variables)
|
|
||||||
- `lang`
|
|
||||||
- [`layout`](https://vuepress.vuejs.org/guide/frontmatter.html#layout)
|
|
||||||
- [`permalink`](https://vuepress.vuejs.org/guide/frontmatter.html#predefined-variables)
|
|
||||||
- [`canonicalUrl`](https://vuepress.vuejs.org/guide/frontmatter.html#predefined-variables)
|
|
||||||
|
|
||||||
## Frontmatter Default Theme Variables
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- `prev`, `next`
|
|
||||||
- [`search`](https://vuepress.vuejs.org/guide/frontmatter.html#search)
|
|
||||||
- [`tags`](https://vuepress.vuejs.org/guide/frontmatter.html#tags)
|
|
||||||
- [`pageClass`](https://vuepress.vuejs.org/theme/default-theme-config.html#custom-page-class)
|
|
||||||
- [`layout`](https://vuepress.vuejs.org/theme/default-theme-config.html#custom-layout-for-specific-pages)
|
|
||||||
|
|
||||||
## siteData
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- [`pages`](https://vuepress.vuejs.org/theme/writing-a-theme.html#site-and-page-metadata)
|
|
||||||
|
|
||||||
## pageData
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- `key`
|
|
||||||
- `path`
|
|
||||||
- `regularPath`
|
|
||||||
|
|
||||||
## Global Components
|
|
||||||
|
|
||||||
- Missing
|
|
||||||
- [`<Badge>`](https://vuepress.vuejs.org/guide/using-vue.html#badge)
|
|
@ -1,51 +1,87 @@
|
|||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
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.
|
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 2.
|
||||||
|
|
||||||
- **Step. 1:** Create and change into a new directory.
|
::: warning
|
||||||
|
VitePress is currently in `alpha` status. It is already suitable for out-of-the-box documentation use, but the config and theming API may still change between minor releases.
|
||||||
|
:::
|
||||||
|
|
||||||
```bash
|
## Step. 1: Create a new project
|
||||||
$ mkdir vitepress-starter && cd vitepress-starter
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Step. 2:** Initialize with your preferred package manager.
|
Create and change into a new directory.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ yarn init
|
$ mkdir vitepress-starter && cd vitepress-starter
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Step. 3:** Install VitePress locally.
|
Then, initialize with your preferred package manager.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ yarn add --dev vitepress
|
$ yarn init
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Step. 4:** Create your first document.
|
## Step. 2: Install VitePress
|
||||||
|
|
||||||
```bash
|
Add VitePress as a dependency for the project.
|
||||||
$ mkdir docs && echo '# Hello VitePress' > docs/index.md
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Step. 5:** Add some scripts to `package.json`.
|
```bash
|
||||||
|
$ yarn add --dev vitepress
|
||||||
|
```
|
||||||
|
|
||||||
```json
|
Create your first document.
|
||||||
{
|
|
||||||
"scripts": {
|
|
||||||
"docs:dev": "vitepress dev docs",
|
|
||||||
"docs:build": "vitepress build docs",
|
|
||||||
"docs:serve": "vitepress serve docs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Step. 6:** Serve the documentation site in the local server.
|
```bash
|
||||||
|
$ mkdir docs && echo '# Hello VitePress' > docs/index.md
|
||||||
|
```
|
||||||
|
|
||||||
```bash
|
## Step. 3: Boot up dev environment
|
||||||
$ yarn docs:dev
|
|
||||||
```
|
|
||||||
|
|
||||||
VitePress will start a hot-reloading development server at `http://localhost:3000`.
|
Add some scripts to `package.json`.
|
||||||
|
|
||||||
By now, you should have a basic but functional VitePress documentation site.
|
```json
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"scripts": {
|
||||||
|
"docs:dev": "vitepress dev docs",
|
||||||
|
"docs:build": "vitepress build docs",
|
||||||
|
"docs:serve": "vitepress serve docs"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
When your documentation site starts to take shape, be sure to read the [deployment guide](./deploy).
|
Serve the documentation site in the local server.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ yarn docs:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
VitePress will start a hot-reloading development server at `http://localhost:3000`.
|
||||||
|
|
||||||
|
## Step. 4: Add more pages
|
||||||
|
|
||||||
|
Let's add another page to the site. Create a file name `getting-started.md` along with `index.md` you've created in Step. 2. Now your directory structure should look like this.
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├─ docs
|
||||||
|
│ ├─ getting-started.md
|
||||||
|
│ └─ index.md
|
||||||
|
└─ package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, try to access `http://localhost:3000/getting-started` and you should see the content of `getting-started` is shown.
|
||||||
|
|
||||||
|
This is how VitePress works basically. The directory structure corresponds with the URL path. You add files, and just try to access it.
|
||||||
|
|
||||||
|
## What's next?
|
||||||
|
|
||||||
|
By now, you should have a basic but functional VitePress documentation site. But currently, the user has no way to navigate around the site because it's missing for example sidebar menu we have on this site.
|
||||||
|
|
||||||
|
To enable those navigations, we must add some configurations to the site. Head to [configuration guide](./configuration) to learn how to configure VitePress.
|
||||||
|
|
||||||
|
If you would like to know more about what you can do within the page, for example, writing markdown contents, or using Vue Component, check out the "Writing" section of the docs. [Markdown guide](./markdown) would be a greate starting point.
|
||||||
|
|
||||||
|
If you want to know how to customize how the site looks (Theme), and find out the features VitePress's default theme provides, visit [Theme: Introduction](./theme-introduction).
|
||||||
|
|
||||||
|
When your documentation site starts to take shape, be sure to read the [deployment guide](./deploying).
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
# Global Component
|
|
||||||
|
|
||||||
VitePress comes with few built-in component that can be used globally. You may use these components in your markdown or your custom theme configuration.
|
|
||||||
|
|
||||||
## Content
|
|
||||||
|
|
||||||
The `Content` component displays the rendered markdown contents. Useful [when creating your own theme](./theming).
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<h1>Custom Layout!</h1>
|
|
||||||
<Content />
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## ClientOnly
|
|
||||||
|
|
||||||
The `ClientOnly` component renderes its slot only at client side.
|
|
||||||
|
|
||||||
Because VitePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the universal code requirements. In short, make sure to only access Browser / DOM APIs in beforeMount or mounted hooks.
|
|
||||||
|
|
||||||
If you are using or demoing components that are not SSR-friendly (for example, contain custom directives), you can wrap them inside the `ClientOnly` component.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<ClientOnly>
|
|
||||||
<NonSSRFriendlyComponent />
|
|
||||||
</ClientOnly>
|
|
||||||
```
|
|
||||||
|
|
||||||
## OutboundLink
|
|
||||||
|
|
||||||
The indicator `OutboundLink` is used to denote external links. In VitePress, this component has been followed by every external link.
|
|
@ -0,0 +1,23 @@
|
|||||||
|
# Migration from VitePress 0.x
|
||||||
|
|
||||||
|
If you're coming from VitePress 0.x version, there're several breaking changes due to new features and enhancement. Please follow this guide to see how to migrate your app over to the latest VitePress.
|
||||||
|
|
||||||
|
## App Config
|
||||||
|
|
||||||
|
- The internationalization feature is not yet implemented.
|
||||||
|
|
||||||
|
## Theme Config
|
||||||
|
|
||||||
|
- `sidebar` option has changed its structure.
|
||||||
|
- `children` key is now named `items`.
|
||||||
|
- Top level item may not contain `link` at the moment. We're planning to bring it back.
|
||||||
|
- `repo`, `repoLabel`, `docsDir`, `docsBranch`, `editLinks`, `editLinkText` are removed in favor of more flexible api.
|
||||||
|
- For adding GitHub link with icon to the nav, use [Social Links](./theme-nav.html#navigation-links) feature.
|
||||||
|
- For adding "Edit this page" feature, use [Edit Link](./theme-edit-link) feature.
|
||||||
|
- `lastUpdated` option is now split into `config.lastUpdated` and `themeConfig.lastUpdatedText`.
|
||||||
|
- `carbonAds.carbon` is changed to `carbonAds.code`.
|
||||||
|
|
||||||
|
## Frontmatter Config
|
||||||
|
|
||||||
|
- `home: true` option has changed to `layout: home`. Also, many Homepage related settings have been modified to provide additional features. See [Homepage guide](./theme-homepage) for details.
|
||||||
|
- `footer` option is moved to [`themeConfig.footer`](../config/theme-configs#footer).
|
@ -0,0 +1,3 @@
|
|||||||
|
# Migration from VuePress
|
||||||
|
|
||||||
|
Coming soon...
|
@ -0,0 +1,22 @@
|
|||||||
|
# Carbon Ads
|
||||||
|
|
||||||
|
VitePress has built in native support for [Carbon Ads](https://www.carbonads.net/). By defining the Carbon Ads credentials in config, VitePress will display ads on the page.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
carbonAds: {
|
||||||
|
code: 'your-carbon-code',
|
||||||
|
placement: 'your-carbon-placement'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These values are used to call carbon CDN script as shown below.
|
||||||
|
|
||||||
|
```js
|
||||||
|
`//cdn.carbonads.com/carbon.js?serve=${code}&placement=${placement}`
|
||||||
|
```
|
||||||
|
|
||||||
|
To learn more about Carbon Ads configuration, please visit [Carbon Ads website](https://www.carbonads.net/).
|
@ -0,0 +1,3 @@
|
|||||||
|
# Edit Link
|
||||||
|
|
||||||
|
Documentation coming soon...
|
@ -0,0 +1,26 @@
|
|||||||
|
# Footer
|
||||||
|
|
||||||
|
When the [page layout](./theme-layout) is set to either `home` or `page`, VitePress will display global footer at the bottom of the page. Set `themeConfig.footer` to configure footer content.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the MIT License.',
|
||||||
|
copyright: 'Copyright © 2019-present Evan You'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface Footer {
|
||||||
|
// The message shown rigth before copyright.
|
||||||
|
message?: string
|
||||||
|
|
||||||
|
// The actual copyright text.
|
||||||
|
copyright?: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that footer will not be displayed when the page layout is set to `doc`.
|
@ -0,0 +1,116 @@
|
|||||||
|
# Homepage
|
||||||
|
|
||||||
|
VitePress default theme provides a homepage layout, which you can also see used on [the homepage of this site](../). You may use it on any of your pages by specifying `layout: home` in the [frontmatter](./frontmatter).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
layout: home
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
However, this option alone wouldn't do much. You can add several different pre templated "sections" to the homepage by setting additional other options such as `hero` and `feaures`.
|
||||||
|
|
||||||
|
## Hero Section
|
||||||
|
|
||||||
|
The Hero section comes at the top of the homepage. Here's how you can configure the Hero section.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
hero:
|
||||||
|
name: VuePress
|
||||||
|
text: Vite & Vue powered static site generator.
|
||||||
|
tagline: Lorem ipsum...
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: Get Started
|
||||||
|
link: /guide/what-is-vitepress
|
||||||
|
- theme: alt
|
||||||
|
text: View on GitHub
|
||||||
|
link: https://github.com/vuejs/vitepress
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Hero {
|
||||||
|
// The string shown top of `text`. Comes with brand color
|
||||||
|
// and expected to be short, such as product name.
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
// The main text for the hero section. This will be defined
|
||||||
|
// as `h1` tag.
|
||||||
|
text: string
|
||||||
|
|
||||||
|
// Tagline displayed below `text`.
|
||||||
|
tagline?: string
|
||||||
|
|
||||||
|
// Action buttons to display in home hero section.
|
||||||
|
actions?: HeroAction[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HeroAction {
|
||||||
|
// Color theme of the button. Defaults to `brand`.
|
||||||
|
theme?: 'brand' | 'alt'
|
||||||
|
|
||||||
|
// Label of the button.
|
||||||
|
text: string
|
||||||
|
|
||||||
|
// Destination link of the button.
|
||||||
|
link: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customizing the name color
|
||||||
|
|
||||||
|
VitePress uses the brand color (`--vp-c-brand`) for the `name`. However, you may customize this color by overriding `--vp-home-hero-name-color` variable.
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--vp-home-hero-name-color: blue;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also you may customize it further by combining `--vp-home-hero-name-background` to give the `name` gradient color.
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--vp-home-hero-name-color: transparent;
|
||||||
|
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe, #41d1ff);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features Section
|
||||||
|
|
||||||
|
In Features section, you can list any number of features you would like to show right after the Hero section. To cinfugure it, pass `features` option to the frontmatter.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
features:
|
||||||
|
- icon: ⚡️
|
||||||
|
title: Vite, The DX that can't be beat
|
||||||
|
details: Lorem ipsum...
|
||||||
|
- icon: 🖖
|
||||||
|
title: Power of Vue meets Markdown
|
||||||
|
details: Lorem ipsum...
|
||||||
|
- icon: 🛠️
|
||||||
|
title: Simple and minimal, always
|
||||||
|
details: Lorem ipsum...
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Feature {
|
||||||
|
// Show icon on each feature box. Currently, only emojis
|
||||||
|
// are supported.
|
||||||
|
icon?: string
|
||||||
|
|
||||||
|
// Title of the feature.
|
||||||
|
title: string
|
||||||
|
|
||||||
|
// Details of the feature.
|
||||||
|
details: string
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,3 @@
|
|||||||
|
# Last Updated
|
||||||
|
|
||||||
|
Documentation coming soon...
|
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
layout: doc
|
||||||
|
---
|
||||||
|
|
||||||
|
# Layout
|
||||||
|
|
||||||
|
You may choose the page layout by setting `layout` option to the page [frontmatter](./frontmatter). There are 3 layout options, `doc`, `page`, and `home`. If nothing is specified, then the page is treated as `doc` page.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
layout: doc
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
## Doc Layout
|
||||||
|
|
||||||
|
Option `doc` is the default layout and it styles the whole Markdown content into "documentation" look. It works by wrapping whole content within `vp-doc` css class, and applying styles to elements underneath it.
|
||||||
|
|
||||||
|
Almost all generic elements such as `p`, or `h2` get special styling. Therefore, keep in mind that if you add any custom HTML inside a Markdown content, those will get affected by those styles as well.
|
||||||
|
|
||||||
|
It also provides documentation specific features listed below. These features are only enabled in this layout.
|
||||||
|
|
||||||
|
- Edit Link
|
||||||
|
- Prev Next Link
|
||||||
|
- Outline
|
||||||
|
- [Carbon Ads](./theme-carbon-ads)
|
||||||
|
|
||||||
|
## Page Layout
|
||||||
|
|
||||||
|
Option `page` is treated as "blank page". The Markdown will still be parsed, and all of the [Markdown Extensions](./markdown) work as same as `doc` layout, but it wouldn't get any default stylings.
|
||||||
|
|
||||||
|
The page layout will let you style everything by you without VitePress theme affecting the markup. This is useful when you want to create your own custom page.
|
||||||
|
|
||||||
|
Note that even in this layout, sidebar will still show up if the page has a matching sidebar config.
|
||||||
|
|
||||||
|
## Home Layout
|
||||||
|
|
||||||
|
Option `home` will generate templated "Homepage". In this layout, you can set extra options such as `hero` and `features` to customize the content further. Please visit [Theme: Home Page](./theme-homepage) for more details.
|
@ -0,0 +1,164 @@
|
|||||||
|
# Nav
|
||||||
|
|
||||||
|
The Nav is the navigation bar displayed on top of the page. It contains the site title, global menu links, etc.
|
||||||
|
|
||||||
|
## Site Title and Logo
|
||||||
|
|
||||||
|
By default, nav shows the title of the site refferencing [`config.title`](../config/app-configs.html#title) value. If you would like to change what's displayed on nav, you may define custom text in `themeConfig.siteTitle` option.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
siteTitle: 'My Custom Title'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have a logo for your site, you can display it by passing in the path to the image. You should place the logo within `public` directly, and define the absolute path to it.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
logo: '/my-logo.svg'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When adding a logo, it gets displayed along with the site title. If your logo is all you need and if you would like to hide the site title text, set `false` to the `siteTitle` option.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
logo: '/my-logo.svg',
|
||||||
|
siteTitle: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Navigation Links
|
||||||
|
|
||||||
|
You may define `themeConfig.nav` option to add links to your nav.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
nav: [
|
||||||
|
{ text: 'Guide', link: '/guide' },
|
||||||
|
{ text: 'Configs', link: '/configs' },
|
||||||
|
{ text: 'Changelog', link: 'https://github.com/...' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `text` is the actual text displayed in nav, and the `link` is the link that will be navigated to when the text is clicked. For the link, set path to the actual file without `.md` prefix, and alsways start with `/`.
|
||||||
|
|
||||||
|
Nav links can also be dropdown menus. To do this, set `items` key on link option.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
nav: [
|
||||||
|
{ text: 'Guide', link: '/guide' },
|
||||||
|
{
|
||||||
|
text: 'Dropdown Menu',
|
||||||
|
items: [
|
||||||
|
{ text: 'Item A', link: '/item-1' },
|
||||||
|
{ text: 'Item B', link: '/item-2' },
|
||||||
|
{ text: 'Item C', link: '/item-3' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that dropdown menu title (`Dropdown Menu` in the above example) can not have `link` property since it becomes a button to open dropdown dialog.
|
||||||
|
|
||||||
|
You may further add "sections" to the dropdown menu items as well by passing in more nested items.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
nav: [
|
||||||
|
{ text: 'Guide', link: '/guide' },
|
||||||
|
{
|
||||||
|
text: 'Dropdown Menu',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
// Title for the section.
|
||||||
|
text: 'Section A Title',
|
||||||
|
items: [
|
||||||
|
{ text: 'Section A Item A', link: '...' },
|
||||||
|
{ text: 'Section B Item B', link: '...' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Dropdown Menu',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
// You may also omit the title.
|
||||||
|
items: [
|
||||||
|
{ text: 'Section A Item A', link: '...' },
|
||||||
|
{ text: 'Section B Item B', link: '...' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize link's "active" state
|
||||||
|
|
||||||
|
Nav menu items will be highlighted when the current page is under the matching path. if you would like to customize the path to be mathced, define `activeMatch` property and regex as a string value.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
nav: [
|
||||||
|
// This link gets active state when the user is
|
||||||
|
// on `/config/` path.
|
||||||
|
{
|
||||||
|
text: 'Guide',
|
||||||
|
link: '/guide',
|
||||||
|
activeMatch: '/config/'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
`activeMatch` is expected to be a regex string, but you must define it as a string. We can't use actual RegExp object here because it isn't serializable during the build time.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Social Links
|
||||||
|
|
||||||
|
You may define `socialLinks` option to show your social account links with icons.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
socialLinks: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' },
|
||||||
|
{ icon: 'twitter', link: '...' },
|
||||||
|
{ icon: 'discord', link: '...' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The list of supported icons are shown below.
|
||||||
|
|
||||||
|
- `discord`
|
||||||
|
- `facebook`
|
||||||
|
- `github`
|
||||||
|
- `instagram`
|
||||||
|
- `linkedin`
|
||||||
|
- `slack`
|
||||||
|
- `twitter`
|
||||||
|
- `youtube`
|
@ -0,0 +1,3 @@
|
|||||||
|
# Prev Next Link
|
||||||
|
|
||||||
|
Documentation coming soon...
|
@ -0,0 +1,3 @@
|
|||||||
|
# Search
|
||||||
|
|
||||||
|
Documentation coming soon...
|
@ -0,0 +1,159 @@
|
|||||||
|
# Sidebar
|
||||||
|
|
||||||
|
The sidebar is the main navigation block for your documentation. You can configure the sidebar menu in `themeConfig.sidebar`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
text: 'Guide',
|
||||||
|
items: [
|
||||||
|
{ text: 'Introduction', link: '/introduction' },
|
||||||
|
{ text: 'Getting Started', link: '/getting-started' },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## The Basics
|
||||||
|
|
||||||
|
The simplest form of the sidebar menu is passing in a single array of links. The first level item defines the "section" for the sidebar. It should contain `text`, which is the title of the section, and `items` which are the actual navigation links.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
text: 'Section Title A',
|
||||||
|
items: [
|
||||||
|
{ text: 'Item A', link: '/item-a' },
|
||||||
|
{ text: 'Item B', link: '/item-b' },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Section Title B',
|
||||||
|
items: [
|
||||||
|
{ text: 'Item C', link: '/item-c' },
|
||||||
|
{ text: 'Item D', link: '/item-d' },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each `link` should specify the path to the actual file starting with `/`. If you add trailing slash to the end of link, it will show `index.md` of the corresponding directory.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
text: 'Guide',
|
||||||
|
items: [
|
||||||
|
// This shows `/guide/index.md` page.
|
||||||
|
{ text: 'Introduction', link: '/guide/' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multiple Sidebars
|
||||||
|
|
||||||
|
You may show different sidebar depending on the page path. For example, as shown on this site, you might want to create a separate sections of content in your documentation like "Guide" page and `Config` page.
|
||||||
|
|
||||||
|
To do so, first organize your pages into directories for each desired section:
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├─ guide/
|
||||||
|
│ ├─ index.md
|
||||||
|
│ ├─ one.md
|
||||||
|
│ └─ two.md
|
||||||
|
└─ config/
|
||||||
|
├─ index.md
|
||||||
|
├─ three.md
|
||||||
|
└─ four.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, update your configuration to define your sidebar for each section. This time, you should pass an object instead of an array.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
sidebar: {
|
||||||
|
// This sidebar gets displayed when user is
|
||||||
|
// under `guide` directory.
|
||||||
|
'/guide/': {
|
||||||
|
text: 'Guide',
|
||||||
|
items: [
|
||||||
|
// This shows `/guide/index.md` page.
|
||||||
|
{ text: 'Index', link: '/guide/' }, // /guide/index.md
|
||||||
|
{ text: 'One', link: '/guide/one' }, // /guide/one.md
|
||||||
|
{ text: 'Two', link: '/guide/two' } // /guide/two.md
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// This sidebar gets displayed when user is
|
||||||
|
// under `config` directory.
|
||||||
|
'/config/': {
|
||||||
|
text: 'Config',
|
||||||
|
items: [
|
||||||
|
// This shows `/guide/index.md` page.
|
||||||
|
{ text: 'Index', link: '/config/' }, // /config/index.mdasdfasdfasdfasdfaf
|
||||||
|
{ text: 'Three', link: '/config/three' }, // /config/three.md
|
||||||
|
{ text: 'Four', link: '/config/four' } // /config/four.md
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Collapsible Sidebar Groups
|
||||||
|
|
||||||
|
By adding `collapsible` option to the sidebar group, it shows a toggle button to hide/show each section.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
text: 'Section Title A',
|
||||||
|
collapsible: true,
|
||||||
|
items: [...]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Section Title B',
|
||||||
|
collapsible: true,
|
||||||
|
items: [...]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All sections are "open" by default. If you would like them to be "closed" on intial page load, set `collapsed` option to `true`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
text: 'Section Title A',
|
||||||
|
collapsible: true,
|
||||||
|
collapsed: true,
|
||||||
|
items: [...]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -1,71 +1,21 @@
|
|||||||
# Hello, World!
|
# Lorem Ipsum
|
||||||
|
|
||||||
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.
|
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
|
## What is Lorem Ipsum?
|
||||||
|
|
||||||
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.
|
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
|
||||||
|
|
||||||
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.
|
## Where does it come from?
|
||||||
|
|
||||||
## Lists
|
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
|
||||||
|
|
||||||
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.
|
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
|
||||||
|
|
||||||
- List item 1
|
## Why do we use it?
|
||||||
- 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.
|
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).
|
||||||
|
|
||||||
1. Ordered item 1
|
## Where can I get some?
|
||||||
1. Nested ordered item A
|
|
||||||
1. Nested ordered item A
|
|
||||||
1. Ordered item 2
|
|
||||||
1. Ordered item 3
|
|
||||||
|
|
||||||
## Code Block and Block Quote
|
There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.
|
||||||
|
|
||||||
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.
|
|
||||||
:::
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,109 @@
|
|||||||
|
import { promises as fs } from 'fs'
|
||||||
|
import { resolve } from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import { RollupOptions, defineConfig } from 'rollup'
|
||||||
|
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
||||||
|
import commonjs from '@rollup/plugin-commonjs'
|
||||||
|
import esbuild from 'rollup-plugin-esbuild'
|
||||||
|
import json from '@rollup/plugin-json'
|
||||||
|
import replace from '@rollup/plugin-replace'
|
||||||
|
import alias from '@rollup/plugin-alias'
|
||||||
|
import dts from 'rollup-plugin-dts'
|
||||||
|
import pkg from './package.json'
|
||||||
|
|
||||||
|
const DEV = !!process.env.DEV
|
||||||
|
const PROD = !DEV
|
||||||
|
|
||||||
|
const ROOT = fileURLToPath(import.meta.url)
|
||||||
|
const r = (p: string) => resolve(ROOT, '..', p)
|
||||||
|
|
||||||
|
const external = [...Object.keys(pkg.dependencies), 'buffer', 'punycode']
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
alias({
|
||||||
|
entries: {
|
||||||
|
'readable-stream': 'stream'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
replace({
|
||||||
|
// polyfill broken browser check from bundled deps
|
||||||
|
'navigator.userAgentData': 'undefined',
|
||||||
|
'navigator.userAgent': 'undefined',
|
||||||
|
preventAssignment: true
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
nodeResolve(),
|
||||||
|
esbuild({ target: 'node14' }),
|
||||||
|
json()
|
||||||
|
]
|
||||||
|
|
||||||
|
const esmBuild: RollupOptions = {
|
||||||
|
input: [r('src/node/index.ts'), r('src/node/cli.ts')],
|
||||||
|
output: {
|
||||||
|
format: 'esm',
|
||||||
|
entryFileNames: `[name].js`,
|
||||||
|
chunkFileNames: 'serve-[hash].js',
|
||||||
|
dir: r('dist/node')
|
||||||
|
},
|
||||||
|
external,
|
||||||
|
plugins,
|
||||||
|
onwarn(warning, warn) {
|
||||||
|
if (warning.code !== 'EVAL') warn(warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cjsBuild: RollupOptions = {
|
||||||
|
input: [r('src/node/index.ts'), r('src/node/cli.ts')],
|
||||||
|
output: {
|
||||||
|
format: 'cjs',
|
||||||
|
dir: r('dist/node-cjs'),
|
||||||
|
entryFileNames: `[name].cjs`,
|
||||||
|
chunkFileNames: 'serve-[hash].cjs'
|
||||||
|
},
|
||||||
|
external,
|
||||||
|
plugins,
|
||||||
|
onwarn(warning, warn) {
|
||||||
|
if (warning.code !== 'EVAL') warn(warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeTypes: RollupOptions = {
|
||||||
|
input: r('src/node/index.ts'),
|
||||||
|
output: {
|
||||||
|
format: 'esm',
|
||||||
|
file: 'dist/node/index.d.ts'
|
||||||
|
},
|
||||||
|
plugins: [dts()]
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientTypes: RollupOptions = {
|
||||||
|
input: r('dist/client-types/index.d.ts'),
|
||||||
|
output: {
|
||||||
|
format: 'esm',
|
||||||
|
file: 'dist/client/index.d.ts'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
dts(),
|
||||||
|
{
|
||||||
|
name: 'cleanup',
|
||||||
|
async closeBundle() {
|
||||||
|
if (PROD) {
|
||||||
|
await fs.rm(r('dist/client-types'), { recursive: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = defineConfig([])
|
||||||
|
|
||||||
|
config.push(esmBuild)
|
||||||
|
|
||||||
|
if (PROD) {
|
||||||
|
config.push(cjsBuild)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.push(nodeTypes)
|
||||||
|
config.push(clientTypes)
|
||||||
|
|
||||||
|
export default config
|
@ -1,11 +1,11 @@
|
|||||||
const fs = require('fs-extra')
|
import { copy } from 'fs-extra'
|
||||||
const glob = require('globby')
|
import fg from 'fast-glob'
|
||||||
|
|
||||||
function toDest(file) {
|
function toDest(file) {
|
||||||
return file.replace(/^src\//, 'dist/')
|
return file.replace(/^src\//, 'dist/')
|
||||||
}
|
}
|
||||||
|
|
||||||
glob.sync('src/client/**').forEach((file) => {
|
fg.sync('src/client/**').forEach((file) => {
|
||||||
if (/(\.ts|tsconfig\.json)$/.test(file)) return
|
if (/(\.ts|tsconfig\.json)$/.test(file)) return
|
||||||
fs.copy(file, toDest(file))
|
copy(file, toDest(file))
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const fs = require('fs-extra')
|
import { copy } from 'fs-extra'
|
||||||
const glob = require('globby')
|
import fg from 'fast-glob'
|
||||||
|
|
||||||
glob.sync('src/shared/**/*.ts').forEach((file) => {
|
fg.sync('src/shared/**/*.ts').map(async (file) => {
|
||||||
fs.copy(file, file.replace(/^src\/shared\//, 'src/node/'))
|
await copy(file, file.replace(/^src\/shared\//, 'src/node/'))
|
||||||
fs.copy(file, file.replace(/^src\/shared\//, 'src/client/'))
|
await copy(file, file.replace(/^src\/shared\//, 'src/client/'))
|
||||||
})
|
})
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
import { defineConfig } from 'rollup'
|
|
||||||
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
|
||||||
import commonjs from '@rollup/plugin-commonjs'
|
|
||||||
import esbuild from 'rollup-plugin-esbuild'
|
|
||||||
import json from '@rollup/plugin-json'
|
|
||||||
import alias from '@rollup/plugin-alias'
|
|
||||||
import { resolve } from 'path'
|
|
||||||
|
|
||||||
const r = (p) => resolve(__dirname, '../', p)
|
|
||||||
const pkg = require('../package.json')
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
input: [r('src/node/index.ts'), r('src/node/cli.ts')],
|
|
||||||
output: {
|
|
||||||
format: 'cjs',
|
|
||||||
dir: r('dist/node')
|
|
||||||
},
|
|
||||||
external: [...Object.keys(pkg.dependencies), 'buffer', 'punycode'],
|
|
||||||
plugins: [
|
|
||||||
alias({
|
|
||||||
entries: {
|
|
||||||
'readable-stream': 'stream'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
nodeResolve(),
|
|
||||||
esbuild({
|
|
||||||
target: 'node12'
|
|
||||||
}),
|
|
||||||
json()
|
|
||||||
],
|
|
||||||
onwarn(warning, warn) {
|
|
||||||
if (warning.code !== 'EVAL') warn(warning)
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,11 +1,13 @@
|
|||||||
import { ref, onMounted, defineComponent } from 'vue'
|
import { defineComponent, ref, onMounted } from 'vue'
|
||||||
|
|
||||||
export const ClientOnly = defineComponent({
|
export const ClientOnly = defineComponent({
|
||||||
setup(_, { slots }) {
|
setup(_, { slots }) {
|
||||||
const show = ref(false)
|
const show = ref(false)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
show.value = true
|
show.value = true
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => (show.value && slots.default ? slots.default() : null)
|
return () => (show.value && slots.default ? slots.default() : null)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,189 +1,55 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, defineAsyncComponent } from 'vue'
|
import { provide } from 'vue'
|
||||||
import { useRoute, useData } from 'vitepress'
|
import { useSidebar, useCloseSidebarOnEscape } from './composables/sidebar'
|
||||||
import { isSideBarEmpty, getSideBarConfig } from './support/sideBar'
|
import VPSkipLink from './components/VPSkipLink.vue'
|
||||||
|
import VPBackdrop from './components/VPBackdrop.vue'
|
||||||
// components
|
import VPNav from './components/VPNav.vue'
|
||||||
import Home from './components/Home.vue'
|
import VPLocalNav from './components/VPLocalNav.vue'
|
||||||
import NavBar from './components/NavBar.vue'
|
import VPSidebar from './components/VPSidebar.vue'
|
||||||
import SideBar from './components/SideBar.vue'
|
import VPContent from './components/VPContent.vue'
|
||||||
import Page from './components/Page.vue'
|
import VPFooter from './components/VPFooter.vue'
|
||||||
|
|
||||||
const NoopComponent = () => null
|
const {
|
||||||
|
isOpen: isSidebarOpen,
|
||||||
const CarbonAds = __CARBON__
|
open: openSidebar,
|
||||||
? defineAsyncComponent(() => import('./components/CarbonAds.vue'))
|
close: closeSidebar
|
||||||
: NoopComponent
|
} = useSidebar()
|
||||||
const BuySellAds = __BSA__
|
|
||||||
? defineAsyncComponent(() => import('./components/BuySellAds.vue'))
|
useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
|
||||||
: NoopComponent
|
|
||||||
const AlgoliaSearchBox = __ALGOLIA__
|
provide('close-sidebar', closeSidebar)
|
||||||
? defineAsyncComponent(() => import('./components/AlgoliaSearchBox.vue'))
|
|
||||||
: NoopComponent
|
|
||||||
|
|
||||||
// generic state
|
|
||||||
const route = useRoute()
|
|
||||||
const { site, page, theme, frontmatter } = useData()
|
|
||||||
|
|
||||||
// custom layout
|
|
||||||
const isCustomLayout = computed(() => !!frontmatter.value.customLayout)
|
|
||||||
// home
|
|
||||||
const enableHome = computed(() => !!frontmatter.value.home)
|
|
||||||
|
|
||||||
// automatic multilang check for AlgoliaSearchBox
|
|
||||||
const isMultiLang = computed(() => Object.keys(site.value.langs).length > 1)
|
|
||||||
|
|
||||||
// navbar
|
|
||||||
const showNavbar = computed(() => {
|
|
||||||
const themeConfig = theme.value
|
|
||||||
if (frontmatter.value.navbar === false || themeConfig.navbar === false) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
site.value.title || themeConfig.logo || themeConfig.repo || themeConfig.nav
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
// sidebar
|
|
||||||
const openSideBar = ref(false)
|
|
||||||
|
|
||||||
const showSidebar = computed(() => {
|
|
||||||
if (frontmatter.value.home || frontmatter.value.sidebar === false) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return !isSideBarEmpty(
|
|
||||||
getSideBarConfig(theme.value.sidebar, route.data.relativePath)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const toggleSidebar = (to?: boolean) => {
|
|
||||||
openSideBar.value = typeof to === 'boolean' ? to : !openSideBar.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const hideSidebar = toggleSidebar.bind(null, false)
|
|
||||||
// close the sidebar when navigating to a different location
|
|
||||||
watch(route, hideSidebar)
|
|
||||||
// TODO: route only changes when the pathname changes
|
|
||||||
// listening to hashchange does nothing because it's prevented in router
|
|
||||||
|
|
||||||
// page classes
|
|
||||||
const pageClasses = computed(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'no-navbar': !showNavbar.value,
|
|
||||||
'sidebar-open': openSideBar.value,
|
|
||||||
'no-sidebar': !showSidebar.value
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="theme" :class="pageClasses">
|
<div class="Layout">
|
||||||
<NavBar v-if="showNavbar" @toggle="toggleSidebar">
|
<VPSkipLink />
|
||||||
<template #search>
|
<VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" />
|
||||||
<slot name="navbar-search">
|
<VPNav />
|
||||||
<AlgoliaSearchBox
|
<VPLocalNav :open="isSidebarOpen" @open-menu="openSidebar" />
|
||||||
v-if="theme.algolia"
|
<VPSidebar :open="isSidebarOpen" />
|
||||||
:options="theme.algolia"
|
|
||||||
:multilang="isMultiLang"
|
<VPContent>
|
||||||
/>
|
<template #home-hero-before><slot name="home-hero-before" /></template>
|
||||||
</slot>
|
<template #home-hero-after><slot name="home-hero-after" /></template>
|
||||||
</template>
|
<template #home-features-before><slot name="home-features-before" /></template>
|
||||||
</NavBar>
|
<template #home-features-after><slot name="home-features-after" /></template>
|
||||||
|
|
||||||
<SideBar :open="openSideBar">
|
<template #aside-top><slot name="aside-top" /></template>
|
||||||
<template #sidebar-top>
|
<template #aside-bottom><slot name="aside-bottom" /></template>
|
||||||
<slot name="sidebar-top" />
|
<template #aside-outline-before><slot name="aside-outline-before" /></template>
|
||||||
</template>
|
<template #aside-outline-after><slot name="aside-outline-after" /></template>
|
||||||
<template #sidebar-bottom>
|
<template #aside-ads-before><slot name="aside-ads-before" /></template>
|
||||||
<slot name="sidebar-bottom" />
|
<template #aside-ads-after><slot name="aside-ads-after" /></template>
|
||||||
</template>
|
</VPContent>
|
||||||
</SideBar>
|
|
||||||
<!-- TODO: make this button accessible -->
|
<VPFooter />
|
||||||
<div class="sidebar-mask" @click="toggleSidebar(false)" />
|
|
||||||
|
|
||||||
<Content v-if="isCustomLayout" />
|
|
||||||
|
|
||||||
<template v-else-if="enableHome">
|
|
||||||
<!-- A slot for customizing the entire homepage easily -->
|
|
||||||
<slot name="home">
|
|
||||||
<Home>
|
|
||||||
<template #hero>
|
|
||||||
<slot name="home-hero" />
|
|
||||||
</template>
|
|
||||||
<template #features>
|
|
||||||
<slot name="home-features" />
|
|
||||||
</template>
|
|
||||||
<template #footer>
|
|
||||||
<slot name="home-footer" />
|
|
||||||
</template>
|
|
||||||
</Home>
|
|
||||||
</slot>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<Page v-else>
|
|
||||||
<template #top>
|
|
||||||
<slot name="page-top-ads">
|
|
||||||
<div
|
|
||||||
id="ads-container"
|
|
||||||
v-if="theme.carbonAds && theme.carbonAds.carbon"
|
|
||||||
>
|
|
||||||
<CarbonAds
|
|
||||||
:key="'carbon' + page.relativePath"
|
|
||||||
:code="theme.carbonAds.carbon"
|
|
||||||
:placement="theme.carbonAds.placement"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</slot>
|
|
||||||
<slot name="page-top" />
|
|
||||||
</template>
|
|
||||||
<template #bottom>
|
|
||||||
<slot name="page-bottom" />
|
|
||||||
<slot name="page-bottom-ads">
|
|
||||||
<BuySellAds
|
|
||||||
v-if="theme.carbonAds && theme.carbonAds.custom"
|
|
||||||
:key="'custom' + page.relativePath"
|
|
||||||
:code="theme.carbonAds.custom"
|
|
||||||
:placement="theme.carbonAds.placement"
|
|
||||||
/>
|
|
||||||
</slot>
|
|
||||||
</template>
|
|
||||||
</Page>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Debug />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
#ads-container {
|
.Layout {
|
||||||
margin: 0 auto;
|
display: flex;
|
||||||
}
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
@media (min-width: 420px) {
|
|
||||||
#ads-container {
|
|
||||||
position: relative;
|
|
||||||
right: 0;
|
|
||||||
float: right;
|
|
||||||
margin: -8px -8px 24px 24px;
|
|
||||||
width: 146px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 420px) {
|
|
||||||
#ads-container {
|
|
||||||
/* Avoid layout shift */
|
|
||||||
height: 105px;
|
|
||||||
margin: 1.75rem 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1400px) {
|
|
||||||
#ads-container {
|
|
||||||
position: fixed;
|
|
||||||
right: 8px;
|
|
||||||
bottom: 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,23 +1,84 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
|
||||||
|
const { site } = useData()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="theme">
|
<div class="NotFound">
|
||||||
<h1>404</h1>
|
<p class="code">404</p>
|
||||||
<blockquote>{{ getMsg() }}</blockquote>
|
<h1 class="title">PAGE NOT FOUND</h1>
|
||||||
<a :href="site.base" aria-label="go to home">Take me home.</a>
|
<div class="divider" />
|
||||||
|
<blockquote class="quote">
|
||||||
|
But if you don't change your direction, and if you keep looking, you may end up where you are heading.
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<div class="action">
|
||||||
|
<a class="link" :href="site.base" aria-label="go to home">
|
||||||
|
Take me home
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<style scoped>
|
||||||
import { useData } from 'vitepress'
|
.NotFound {
|
||||||
|
padding: 64px 24px 96px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
const { site } = useData()
|
@media (min-width: 768px) {
|
||||||
const msgs = [
|
.NotFound {
|
||||||
`There's nothing here.`,
|
padding: 96px 32px 168px;
|
||||||
`How did we get here?`,
|
}
|
||||||
`That's a Four-Oh-Four.`,
|
}
|
||||||
`Looks like we've got some broken links.`
|
|
||||||
]
|
|
||||||
|
|
||||||
function getMsg() {
|
.code {
|
||||||
return msgs[Math.floor(Math.random() * msgs.length)]
|
line-height: 64px;
|
||||||
|
font-size: 64px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
.title {
|
||||||
|
padding-top: 12px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
margin: 24px auto 18px;
|
||||||
|
width: 64px;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--vp-c-divider)
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 256px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid var(--vp-c-brand);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 3px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
transition: border-color 0.25s, color .25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
border-color: var(--vp-c-brand-dark);
|
||||||
|
color: var(--vp-c-brand-dark);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,188 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import '@docsearch/css'
|
|
||||||
import docsearch from '@docsearch/js'
|
|
||||||
import { useRoute, useRouter, useData } from 'vitepress'
|
|
||||||
import { getCurrentInstance, onMounted, watch } from 'vue'
|
|
||||||
import type { DefaultTheme } from '../config'
|
|
||||||
import type { DocSearchHit } from '@docsearch/react/dist/esm/types'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
options: DefaultTheme.AlgoliaSearchOptions
|
|
||||||
multilang?: boolean
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const vm = getCurrentInstance()
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.options,
|
|
||||||
(value) => {
|
|
||||||
update(value)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
initialize(props.options)
|
|
||||||
})
|
|
||||||
|
|
||||||
function isSpecialClick(event: MouseEvent) {
|
|
||||||
return (
|
|
||||||
event.button === 1 ||
|
|
||||||
event.altKey ||
|
|
||||||
event.ctrlKey ||
|
|
||||||
event.metaKey ||
|
|
||||||
event.shiftKey
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRelativePath(absoluteUrl: string) {
|
|
||||||
const { pathname, hash } = new URL(absoluteUrl)
|
|
||||||
|
|
||||||
return pathname + hash
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(options: any) {
|
|
||||||
if (vm && vm.vnode.el) {
|
|
||||||
vm.vnode.el.innerHTML =
|
|
||||||
'<div class="algolia-search-box" id="docsearch"></div>'
|
|
||||||
initialize(options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { lang } = useData()
|
|
||||||
|
|
||||||
// if the user has multiple locales, the search results should be filtered
|
|
||||||
// based on the language
|
|
||||||
const facetFilters: string[] = props.multilang ? ['lang:' + lang.value] : []
|
|
||||||
|
|
||||||
if (props.options.searchParameters?.facetFilters) {
|
|
||||||
facetFilters.push(...props.options.searchParameters.facetFilters)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(lang, (newLang, oldLang) => {
|
|
||||||
const index = facetFilters.findIndex((filter) => filter === 'lang:' + oldLang)
|
|
||||||
if (index > -1) {
|
|
||||||
facetFilters.splice(index, 1, 'lang:' + newLang)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function initialize(userOptions: any) {
|
|
||||||
docsearch(
|
|
||||||
Object.assign({}, userOptions, {
|
|
||||||
container: '#docsearch',
|
|
||||||
|
|
||||||
searchParameters: Object.assign({}, userOptions.searchParameters, {
|
|
||||||
// pass a custom lang facetFilter to allow multiple language search
|
|
||||||
// https://github.com/algolia/docsearch-configs/pull/3942
|
|
||||||
facetFilters
|
|
||||||
}),
|
|
||||||
|
|
||||||
navigator: {
|
|
||||||
navigate: ({ itemUrl }: { itemUrl: string }) => {
|
|
||||||
const { pathname: hitPathname } = new URL(
|
|
||||||
window.location.origin + itemUrl
|
|
||||||
)
|
|
||||||
|
|
||||||
// Router doesn't handle same-page navigation so we use the native
|
|
||||||
// browser location API for anchor navigation
|
|
||||||
if (route.path === hitPathname) {
|
|
||||||
window.location.assign(window.location.origin + itemUrl)
|
|
||||||
} else {
|
|
||||||
router.go(itemUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
transformItems: (items: DocSearchHit[]) => {
|
|
||||||
return items.map((item) => {
|
|
||||||
return Object.assign({}, item, {
|
|
||||||
url: getRelativePath(item.url)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
hitComponent: ({
|
|
||||||
hit,
|
|
||||||
children
|
|
||||||
}: {
|
|
||||||
hit: DocSearchHit
|
|
||||||
children: any
|
|
||||||
}) => {
|
|
||||||
const relativeHit = hit.url.startsWith('http')
|
|
||||||
? getRelativePath(hit.url as string)
|
|
||||||
: hit.url
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'a',
|
|
||||||
ref: undefined,
|
|
||||||
constructor: undefined,
|
|
||||||
key: undefined,
|
|
||||||
props: {
|
|
||||||
href: hit.url,
|
|
||||||
onClick: (event: MouseEvent) => {
|
|
||||||
if (isSpecialClick(event)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// we rely on the native link scrolling when user is already on
|
|
||||||
// the right anchor because Router doesn't support duplicated
|
|
||||||
// history entries
|
|
||||||
if (route.path === relativeHit) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the hits goes to another page, we prevent the native link
|
|
||||||
// behavior to leverage the Router loading feature
|
|
||||||
if (route.path !== relativeHit) {
|
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
router.go(relativeHit)
|
|
||||||
},
|
|
||||||
children
|
|
||||||
},
|
|
||||||
__v: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="algolia-search-box" id="docsearch" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.algolia-search-box {
|
|
||||||
padding-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.algolia-search-box {
|
|
||||||
padding-left: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 751px) {
|
|
||||||
.algolia-search-box {
|
|
||||||
min-width: 176.3px; /* avoid layout shift */
|
|
||||||
}
|
|
||||||
|
|
||||||
.algolia-search-box .DocSearch-Button-Placeholder {
|
|
||||||
padding-left: 8px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.DocSearch {
|
|
||||||
--docsearch-primary-color: var(--c-brand);
|
|
||||||
--docsearch-highlight-color: var(--docsearch-primary-color);
|
|
||||||
--docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color);
|
|
||||||
--docsearch-text-color: var(--c-text-light);
|
|
||||||
--docsearch-muted-color: var(--c-text-lighter);
|
|
||||||
--docsearch-searchbox-background: #f2f2f2;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,152 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { onMounted } from 'vue'
|
|
||||||
|
|
||||||
// global _bsa
|
|
||||||
const ID = 'bsa-cpc-script'
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
var _bsa: BSA | undefined
|
|
||||||
|
|
||||||
interface BSA {
|
|
||||||
init(
|
|
||||||
name: string,
|
|
||||||
code: string,
|
|
||||||
placement: string,
|
|
||||||
options: {
|
|
||||||
target: string
|
|
||||||
align: string
|
|
||||||
disable_css?: 'true' | 'false'
|
|
||||||
}
|
|
||||||
): void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { code, placement } = defineProps<{
|
|
||||||
code: string
|
|
||||||
placement: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (!document.getElementById(ID)) {
|
|
||||||
const s = document.createElement('script')
|
|
||||||
|
|
||||||
s.id = ID
|
|
||||||
s.src = '//m.servedby-buysellads.com/monetization.js'
|
|
||||||
|
|
||||||
document.head.appendChild(s)
|
|
||||||
|
|
||||||
s.onload = () => {
|
|
||||||
load()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
load()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function load() {
|
|
||||||
if (typeof _bsa !== 'undefined' && _bsa) {
|
|
||||||
const parent = document.querySelector('.bsa-cpc')!
|
|
||||||
// cleanup any existing ad to avoid them stacking
|
|
||||||
parent.innerHTML = ''
|
|
||||||
|
|
||||||
_bsa.init('default', code, `placement:${placement}`, {
|
|
||||||
target: '.bsa-cpc',
|
|
||||||
align: 'horizontal',
|
|
||||||
disable_css: 'true'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="buy-sell-ads">
|
|
||||||
<div class="bsa-cpc" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.buy-sell-ads {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding-top: 2rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bsa-cpc {
|
|
||||||
border-radius: 6px;
|
|
||||||
background-color: var(--c-bg-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bsa-cpc ::v-deep(a._default_) {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 12px;
|
|
||||||
text-decoration: none;
|
|
||||||
line-height: 1.4;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--c-text-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 512px) {
|
|
||||||
.bsa-cpc ::v-deep(a._default_) {
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bsa-cpc ::v-deep(.default-ad) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bsa-cpc ::v-deep(a._default_ .default-image) {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: 12px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bsa-cpc ::v-deep(a._default_ .default-image img) {
|
|
||||||
border-radius: 4px;
|
|
||||||
height: 24px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bsa-cpc ::v-deep(._default_::after) {
|
|
||||||
border: 1px solid #1c90f3;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-top: 8px;
|
|
||||||
margin-left: 36px;
|
|
||||||
padding: 0 8px;
|
|
||||||
line-height: 22px;
|
|
||||||
font-size: 0.85em;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1c90f3;
|
|
||||||
content: 'Sponsored';
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 512px) {
|
|
||||||
.bsa-cpc ::v-deep(._default_::after) {
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bsa-cpc ::v-deep(.default-text) {
|
|
||||||
flex-grow: 1;
|
|
||||||
align-self: center;
|
|
||||||
width: calc(100% - 36px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 512px) {
|
|
||||||
.bsa-cpc ::v-deep(.default-text) {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bsa-cpc ::v-deep(.default-title) {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bsa-cpc ::v-deep(.default-description) {
|
|
||||||
padding-left: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,99 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
|
|
||||||
const { code, placement } = defineProps<{
|
|
||||||
code: string
|
|
||||||
placement: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const el = ref()
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const s = document.createElement('script')
|
|
||||||
s.id = '_carbonads_js'
|
|
||||||
s.src = `//cdn.carbonads.com/carbon.js?serve=${code}&placement=${placement}`
|
|
||||||
el.value.appendChild(s)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="carbon-ads" ref="el" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.carbon-ads {
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 8px;
|
|
||||||
max-width: 280px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
background-color: var(--c-bg-accent);
|
|
||||||
min-height: 105.38px; /* avoid layout shift on mobile */
|
|
||||||
}
|
|
||||||
|
|
||||||
.carbon-ads::after {
|
|
||||||
clear: both;
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.carbon-ads {
|
|
||||||
z-index: 1;
|
|
||||||
float: right;
|
|
||||||
margin: -8px -8px 24px 24px;
|
|
||||||
width: 146px;
|
|
||||||
max-width: 100%;
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1400px) {
|
|
||||||
.carbon-ads {
|
|
||||||
right: 8px;
|
|
||||||
float: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.carbon-ads :deep(.carbon-img) {
|
|
||||||
float: left;
|
|
||||||
margin-right: 0.75rem;
|
|
||||||
max-width: 110px;
|
|
||||||
border: 1px solid var(--c-divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.carbon-ads :deep(.carbon-img) {
|
|
||||||
float: none;
|
|
||||||
display: block;
|
|
||||||
margin-right: 0;
|
|
||||||
max-width: 130px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.carbon-ads :deep(.carbon-img img) {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.carbon-ads :deep(.carbon-text) {
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.carbon-ads :deep(.carbon-text) {
|
|
||||||
display: block;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--c-text-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.carbon-ads :deep(.carbon-poweredby) {
|
|
||||||
display: block;
|
|
||||||
padding-top: 2px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--c-text-lighter);
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,38 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useEditLink } from '../composables/editLink'
|
|
||||||
import OutboundLink from './icons/OutboundLink.vue'
|
|
||||||
|
|
||||||
const { url, text } = useEditLink()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="edit-link">
|
|
||||||
<a
|
|
||||||
v-if="url"
|
|
||||||
class="link"
|
|
||||||
:href="url"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{{ text }} <OutboundLink class="icon" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.link {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--c-text-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.link:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--c-brand);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,31 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import HomeHero from './HomeHero.vue'
|
|
||||||
import HomeFeatures from './HomeFeatures.vue'
|
|
||||||
import HomeFooter from './HomeFooter.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main class="home" aria-labelledby="main-title">
|
|
||||||
<HomeHero />
|
|
||||||
<slot name="hero" />
|
|
||||||
<HomeFeatures />
|
|
||||||
<div class="home-content">
|
|
||||||
<Content />
|
|
||||||
</div>
|
|
||||||
<slot name="features" />
|
|
||||||
<HomeFooter />
|
|
||||||
<slot name="footer" />
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.home {
|
|
||||||
padding-top: var(--header-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-content {
|
|
||||||
max-width: 960px;
|
|
||||||
margin: 0px auto;
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,143 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useData } from 'vitepress'
|
|
||||||
|
|
||||||
const { frontmatter } = useData()
|
|
||||||
|
|
||||||
const hasFeatures = computed(() => {
|
|
||||||
return frontmatter.value.features && frontmatter.value.features.length > 0
|
|
||||||
})
|
|
||||||
|
|
||||||
interface Feature {
|
|
||||||
title?: string
|
|
||||||
details?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const features = computed<Feature[]>(() => {
|
|
||||||
return frontmatter.value.features ? frontmatter.value.features : []
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="hasFeatures" class="home-features">
|
|
||||||
<div class="wrapper">
|
|
||||||
<div class="container">
|
|
||||||
<div class="features">
|
|
||||||
<section
|
|
||||||
v-for="(feature, index) in features"
|
|
||||||
:key="index"
|
|
||||||
class="feature"
|
|
||||||
>
|
|
||||||
<h2 class="title" v-if="feature.title">{{ feature.title }}</h2>
|
|
||||||
<p class="details" v-if="feature.details">{{ feature.details }}</p>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.home-features {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2.5rem 0 2.75rem;
|
|
||||||
max-width: 960px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-hero + .home-features {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.home-features {
|
|
||||||
padding: 3.25rem 0 3.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-hero + .home-features {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.home-features {
|
|
||||||
padding-right: 1.5rem;
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-hero + .home-features .wrapper {
|
|
||||||
border-top: 1px solid var(--c-divider);
|
|
||||||
padding-top: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.home-hero + .home-features .wrapper {
|
|
||||||
padding-top: 3.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.wrapper {
|
|
||||||
padding-right: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 392px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.container {
|
|
||||||
max-width: 960px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.features {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: -20px -24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature {
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 20px 24px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.feature {
|
|
||||||
width: calc(100% / 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin: 0;
|
|
||||||
border-bottom: 0;
|
|
||||||
line-height: 1.4;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.title {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
margin: 0;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-size: 1rem;
|
|
||||||
color: var(--c-text-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title + .details {
|
|
||||||
padding-top: 0.25rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,50 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useData } from 'vitepress'
|
|
||||||
|
|
||||||
const { frontmatter } = useData()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<footer v-if="frontmatter.footer" class="footer">
|
|
||||||
<div class="container">
|
|
||||||
<p class="text">{{ frontmatter.footer }}</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.footer {
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 960px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.footer {
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 2rem 1.5rem 2.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-hero + .footer .container,
|
|
||||||
.home-features + .footer .container,
|
|
||||||
.home-content + .footer .container {
|
|
||||||
border-top: 1px solid var(--c-divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.container {
|
|
||||||
padding: 3rem 1.5rem 3.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.4;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--c-text-light);
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,161 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useData, withBase } from 'vitepress'
|
|
||||||
import NavLink from './NavLink.vue'
|
|
||||||
|
|
||||||
const { site, frontmatter } = useData()
|
|
||||||
|
|
||||||
const showHero = computed(() => {
|
|
||||||
const { heroImage, heroText, tagline, actionLink, actionText } =
|
|
||||||
frontmatter.value
|
|
||||||
return heroImage || heroText || tagline || (actionLink && actionText)
|
|
||||||
})
|
|
||||||
|
|
||||||
const heroText = computed(() => frontmatter.value.heroText || site.value.title)
|
|
||||||
const tagline = computed(
|
|
||||||
() => frontmatter.value.tagline || site.value.description
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<header v-if="showHero" class="home-hero">
|
|
||||||
<figure v-if="frontmatter.heroImage" class="figure">
|
|
||||||
<img
|
|
||||||
class="image"
|
|
||||||
:src="withBase(frontmatter.heroImage)"
|
|
||||||
:alt="frontmatter.heroAlt"
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
<h1 v-if="heroText" id="main-title" class="title">{{ heroText }}</h1>
|
|
||||||
<p v-if="tagline" class="tagline">{{ tagline }}</p>
|
|
||||||
|
|
||||||
<NavLink
|
|
||||||
v-if="frontmatter.actionLink && frontmatter.actionText"
|
|
||||||
:item="{ link: frontmatter.actionLink, text: frontmatter.actionText }"
|
|
||||||
class="action"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<NavLink
|
|
||||||
v-if="frontmatter.altActionLink && frontmatter.altActionText"
|
|
||||||
:item="{
|
|
||||||
link: frontmatter.altActionLink,
|
|
||||||
text: frontmatter.altActionText
|
|
||||||
}"
|
|
||||||
class="action alt"
|
|
||||||
/>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.home-hero {
|
|
||||||
margin: 2.5rem 0 2.75rem;
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.home-hero {
|
|
||||||
margin: 3.5rem 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.home-hero {
|
|
||||||
margin: 4rem 0 4.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.figure {
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 280px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.title {
|
|
||||||
font-size: 3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.title {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tagline {
|
|
||||||
margin: 0;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
line-height: 1.3;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: var(--c-text-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.tagline {
|
|
||||||
line-height: 1.2;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action.alt {
|
|
||||||
margin-left: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.action {
|
|
||||||
margin-top: 2rem;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action :deep(.item) {
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0 20px;
|
|
||||||
line-height: 44px;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--c-bg);
|
|
||||||
background-color: var(--c-brand);
|
|
||||||
border: 2px solid var(--c-brand);
|
|
||||||
transition: background-color 0.1s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action.alt :deep(.item) {
|
|
||||||
background-color: var(--c-bg);
|
|
||||||
color: var(--c-brand);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action :deep(.item:hover) {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--c-bg);
|
|
||||||
background-color: var(--c-brand-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 420px) {
|
|
||||||
.action :deep(.item) {
|
|
||||||
padding: 0 24px;
|
|
||||||
line-height: 52px;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,60 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, onMounted, watchEffect } from 'vue'
|
|
||||||
import { useData } from 'vitepress'
|
|
||||||
|
|
||||||
const { theme, page } = useData()
|
|
||||||
|
|
||||||
const hasLastUpdated = computed(() => {
|
|
||||||
const lu = theme.value.lastUpdated
|
|
||||||
|
|
||||||
return lu !== undefined && lu !== false && page.value.lastUpdated !== 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const prefix = computed(() => {
|
|
||||||
const p = theme.value.lastUpdated
|
|
||||||
return p === true ? 'Last Updated' : p
|
|
||||||
})
|
|
||||||
|
|
||||||
const datetime = ref('')
|
|
||||||
onMounted(() => {
|
|
||||||
watchEffect(() => {
|
|
||||||
// locale string might be different based on end user
|
|
||||||
// and will lead to potential hydration mismatch if calculated at build time
|
|
||||||
datetime.value = new Date(page.value.lastUpdated!).toLocaleString('en-US')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<p v-if="hasLastUpdated" class="last-updated">
|
|
||||||
<span class="prefix">{{ prefix }}:</span>
|
|
||||||
<span class="datetime">{{ datetime }}</span>
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.last-updated {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
line-height: 1.4;
|
|
||||||
font-size: 0.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,60 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import NavBarTitle from './NavBarTitle.vue'
|
|
||||||
import NavLinks from './NavLinks.vue'
|
|
||||||
import ToggleSideBarButton from './ToggleSideBarButton.vue'
|
|
||||||
|
|
||||||
defineEmits(['toggle'])
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<header class="nav-bar">
|
|
||||||
<ToggleSideBarButton @toggle="$emit('toggle')" />
|
|
||||||
|
|
||||||
<NavBarTitle />
|
|
||||||
|
|
||||||
<div class="flex-grow" />
|
|
||||||
|
|
||||||
<div class="nav">
|
|
||||||
<NavLinks />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<slot name="search" />
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.nav-bar {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: var(--z-index-navbar);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid var(--c-divider);
|
|
||||||
padding: 0.7rem 1.5rem 0.7rem 4rem;
|
|
||||||
height: var(--header-height);
|
|
||||||
background-color: var(--c-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.nav-bar {
|
|
||||||
padding: 0.7rem 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-grow {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.nav {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,41 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { withBase, useData } from 'vitepress'
|
|
||||||
const { site, theme, localePath } = useData()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<a
|
|
||||||
class="nav-bar-title"
|
|
||||||
:href="localePath"
|
|
||||||
:aria-label="`${site.title}, back to home`"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
v-if="theme.logo"
|
|
||||||
class="logo"
|
|
||||||
:src="withBase(theme.logo)"
|
|
||||||
alt="Logo"
|
|
||||||
/>
|
|
||||||
{{ site.title }}
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.nav-bar-title {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--c-text);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-bar-title:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
margin-right: 0.75rem;
|
|
||||||
height: 1.3rem;
|
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,135 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch } from 'vue'
|
|
||||||
import { useRoute } from 'vitepress'
|
|
||||||
import type { DefaultTheme } from '../config'
|
|
||||||
import NavDropdownLinkItem from './NavDropdownLinkItem.vue'
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
item: DefaultTheme.NavItemWithChildren
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const open = ref(false)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => route.path,
|
|
||||||
() => {
|
|
||||||
open.value = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function toggle() {
|
|
||||||
open.value = !open.value
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="nav-dropdown-link" :class="{ open }">
|
|
||||||
<button class="button" :aria-label="item.ariaLabel" @click="toggle">
|
|
||||||
<span class="button-text">{{ item.text }}</span>
|
|
||||||
<span class="button-arrow" :class="open ? 'down' : 'right'" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ul class="dialog">
|
|
||||||
<li v-for="item in item.items" :key="item.text" class="dialog-item">
|
|
||||||
<NavDropdownLinkItem :item="item" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.nav-dropdown-link {
|
|
||||||
position: relative;
|
|
||||||
height: 36px;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.nav-dropdown-link {
|
|
||||||
height: auto;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-dropdown-link:hover .dialog {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-dropdown-link.open {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
display: block;
|
|
||||||
border: 0;
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
line-height: 36px;
|
|
||||||
font-family: var(--font-family-base);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--c-text);
|
|
||||||
white-space: nowrap;
|
|
||||||
background-color: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.button {
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 24px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-arrow {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: -1px;
|
|
||||||
margin-left: 8px;
|
|
||||||
border-top: 6px solid #ccc;
|
|
||||||
border-right: 4px solid transparent;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-left: 4px solid transparent;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-arrow.right {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.button-arrow.right {
|
|
||||||
transform: rotate(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.dialog {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 26px;
|
|
||||||
right: -8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 12px 0;
|
|
||||||
min-width: 128px;
|
|
||||||
background-color: var(--c-bg);
|
|
||||||
box-shadow: var(--shadow-3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,76 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { toRefs } from 'vue'
|
|
||||||
import type { DefaultTheme } from '../config'
|
|
||||||
import { useNavLink } from '../composables/navLink'
|
|
||||||
import OutboundLink from './icons/OutboundLink.vue'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
item: DefaultTheme.NavItemWithLink
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const propsRefs = toRefs(props)
|
|
||||||
|
|
||||||
const { props: linkProps, isExternal } = useNavLink(propsRefs.item)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="nav-dropdown-link-item">
|
|
||||||
<a class="item" v-bind="linkProps">
|
|
||||||
<span class="arrow" />
|
|
||||||
<span class="text">{{ item.text }}</span>
|
|
||||||
<span class="icon"><OutboundLink v-if="isExternal" /></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.item {
|
|
||||||
display: block;
|
|
||||||
padding: 0 1.5rem 0 2.5rem;
|
|
||||||
line-height: 32px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--c-text);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.item {
|
|
||||||
padding: 0 24px 0 12px;
|
|
||||||
line-height: 32px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--c-text);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.active .arrow {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:hover,
|
|
||||||
.item.active {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--c-brand);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.external:hover {
|
|
||||||
border-bottom-color: transparent;
|
|
||||||
color: var(--c-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.arrow {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 8px;
|
|
||||||
border-top: 6px solid #ccc;
|
|
||||||
border-right: 4px solid transparent;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-left: 4px solid transparent;
|
|
||||||
vertical-align: middle;
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-2px) rotate(-90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,61 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { toRefs } from 'vue'
|
|
||||||
import type { DefaultTheme } from '../config'
|
|
||||||
import { useNavLink } from '../composables/navLink'
|
|
||||||
import OutboundLink from './icons/OutboundLink.vue'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
item: DefaultTheme.NavItemWithLink
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const propsRefs = toRefs(props)
|
|
||||||
|
|
||||||
const { props: linkProps, isExternal } = useNavLink(propsRefs.item)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="nav-link">
|
|
||||||
<a class="item" v-bind="linkProps">
|
|
||||||
{{ item.text }} <OutboundLink v-if="isExternal" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.item {
|
|
||||||
display: block;
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
line-height: 36px;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--c-text);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:hover,
|
|
||||||
.item.active {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--c-brand);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item.external:hover {
|
|
||||||
border-bottom-color: transparent;
|
|
||||||
color: var(--c-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.item {
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 24px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:hover,
|
|
||||||
.item.active {
|
|
||||||
border-bottom-color: var(--c-brand);
|
|
||||||
color: var(--c-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,52 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useData } from 'vitepress'
|
|
||||||
import { useLanguageLinks } from '../composables/nav'
|
|
||||||
import { useRepo } from '../composables/repo'
|
|
||||||
import NavLink from './NavLink.vue'
|
|
||||||
import NavDropdownLink from './NavDropdownLink.vue'
|
|
||||||
|
|
||||||
const { theme } = useData()
|
|
||||||
const localeLinks = useLanguageLinks()
|
|
||||||
const repo = useRepo()
|
|
||||||
const show = computed(() => theme.value.nav || repo.value || localeLinks.value)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<nav v-if="show" class="nav-links">
|
|
||||||
<template v-if="theme.nav">
|
|
||||||
<div v-for="item in theme.nav" :key="item.text" class="item">
|
|
||||||
<NavDropdownLink v-if="item.items" :item="item" />
|
|
||||||
<NavLink v-else :item="item" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-if="localeLinks" class="item">
|
|
||||||
<NavDropdownLink :item="localeLinks" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="repo" class="item">
|
|
||||||
<NavLink :item="repo" />
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.nav-links {
|
|
||||||
padding: 0.75rem 0;
|
|
||||||
border-bottom: 1px solid var(--c-divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.nav-links {
|
|
||||||
display: flex;
|
|
||||||
padding: 6px 0 0;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item + .item {
|
|
||||||
padding-left: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,88 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { withBase } from 'vitepress'
|
|
||||||
import { useNextAndPrevLinks } from '../composables/nextAndPrevLinks'
|
|
||||||
import ArrowLeft from './icons/ArrowLeft.vue'
|
|
||||||
import ArrowRight from './icons/ArrowRight.vue'
|
|
||||||
|
|
||||||
const { hasLinks, prev, next } = useNextAndPrevLinks()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="hasLinks" class="next-and-prev-link">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.next-and-prev-link {
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
border-top: 1px solid var(--c-divider);
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prev,
|
|
||||||
.next {
|
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prev {
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.next {
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
max-width: 100%;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
display: block;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: block;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
fill: var(--c-text);
|
|
||||||
transform: translateY(1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-prev {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
.icon-next {
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,53 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import PageFooter from './PageFooter.vue'
|
|
||||||
import NextAndPrevLinks from './NextAndPrevLinks.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main class="page">
|
|
||||||
<div class="container">
|
|
||||||
<slot name="top" />
|
|
||||||
|
|
||||||
<Content class="content" />
|
|
||||||
<PageFooter />
|
|
||||||
<NextAndPrevLinks />
|
|
||||||
|
|
||||||
<slot name="bottom" />
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.page {
|
|
||||||
padding-top: var(--header-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.page {
|
|
||||||
margin-left: 16.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 960px) {
|
|
||||||
.page {
|
|
||||||
margin-left: 20rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 1.5rem 4rem;
|
|
||||||
max-width: 48rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 420px) {
|
|
||||||
.content {
|
|
||||||
/* fix carbon ads display */
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,44 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import EditLink from './EditLink.vue'
|
|
||||||
import LastUpdated from './LastUpdated.vue'
|
|
||||||
import { useData } from 'vitepress'
|
|
||||||
|
|
||||||
const { page } = useData()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<footer class="page-footer">
|
|
||||||
<div class="edit">
|
|
||||||
<EditLink />
|
|
||||||
</div>
|
|
||||||
<div class="updated">
|
|
||||||
<LastUpdated v-if="page.lastUpdated" />
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<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,60 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import NavLinks from './NavLinks.vue'
|
|
||||||
import SideBarLinks from './SideBarLinks.vue'
|
|
||||||
|
|
||||||
defineProps<{ open: boolean }>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<aside class="sidebar" :class="{ open }">
|
|
||||||
<NavLinks class="nav" />
|
|
||||||
|
|
||||||
<slot name="sidebar-top" />
|
|
||||||
|
|
||||||
<SideBarLinks />
|
|
||||||
|
|
||||||
<slot name="sidebar-bottom" />
|
|
||||||
</aside>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.sidebar {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 960px) {
|
|
||||||
.sidebar {
|
|
||||||
width: 20rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar.open {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 720px) {
|
|
||||||
.nav {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,100 +0,0 @@
|
|||||||
import { FunctionalComponent, h, VNode } from 'vue'
|
|
||||||
import { useRoute, useData } from 'vitepress'
|
|
||||||
import { Header } from '../../shared'
|
|
||||||
import { DefaultTheme } from '../config'
|
|
||||||
import { joinUrl, isActive } from '../utils'
|
|
||||||
|
|
||||||
interface HeaderWithChildren extends Header {
|
|
||||||
children?: Header[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SideBarLink: FunctionalComponent<{
|
|
||||||
item: DefaultTheme.SideBarItem
|
|
||||||
depth?: number
|
|
||||||
}> = (props) => {
|
|
||||||
const route = useRoute()
|
|
||||||
const { site, frontmatter } = useData()
|
|
||||||
const depth = props.depth || 1
|
|
||||||
const maxDepth = frontmatter.value.sidebarDepth || Infinity
|
|
||||||
|
|
||||||
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, props.item.link)
|
|
||||||
const childItems =
|
|
||||||
depth < maxDepth
|
|
||||||
? createChildren(active, children, headers, depth + 1)
|
|
||||||
: null
|
|
||||||
|
|
||||||
return h('li', { class: 'sidebar-link' }, [
|
|
||||||
h(
|
|
||||||
link ? 'a' : 'p',
|
|
||||||
{
|
|
||||||
class: { 'sidebar-link-item': true, active },
|
|
||||||
href: link
|
|
||||||
},
|
|
||||||
text
|
|
||||||
),
|
|
||||||
childItems
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
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?: DefaultTheme.SideBarItem[],
|
|
||||||
headers?: Header[],
|
|
||||||
depth = 1
|
|
||||||
): VNode | null {
|
|
||||||
if (children && children.length > 0) {
|
|
||||||
return h(
|
|
||||||
'ul',
|
|
||||||
{ class: 'sidebar-links' },
|
|
||||||
children.map((c) => {
|
|
||||||
return h(SideBarLink, { item: c, depth })
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return active && headers
|
|
||||||
? createChildren(false, resolveHeaders(headers), undefined, depth)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveHeaders(headers: Header[]): DefaultTheme.SideBarItem[] {
|
|
||||||
return mapHeaders(groupHeaders(headers))
|
|
||||||
}
|
|
||||||
|
|
||||||
function groupHeaders(headers: Header[]): HeaderWithChildren[] {
|
|
||||||
headers = headers.map((h) => Object.assign({}, h))
|
|
||||||
let lastH2: HeaderWithChildren
|
|
||||||
headers.forEach((h) => {
|
|
||||||
if (h.level === 2) {
|
|
||||||
lastH2 = h
|
|
||||||
} else if (lastH2) {
|
|
||||||
;(lastH2.children || (lastH2.children = [])).push(h)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return headers.filter((h) => h.level === 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapHeaders(headers: HeaderWithChildren[]): DefaultTheme.SideBarItem[] {
|
|
||||||
return headers.map((header) => ({
|
|
||||||
text: header.title,
|
|
||||||
link: `#${header.slug}`,
|
|
||||||
children: header.children ? mapHeaders(header.children) : undefined
|
|
||||||
}))
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useSideBar } from '../composables/sideBar'
|
|
||||||
import { SideBarLink } from './SideBarLink'
|
|
||||||
|
|
||||||
const items = useSideBar()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ul v-if="items.length > 0" class="sidebar-links">
|
|
||||||
<SideBarLink v-for="item of items" :item="item" />
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
@ -1,46 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
emits: ['toggle']
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="sidebar-button" @click="$emit('toggle')">
|
|
||||||
<svg
|
|
||||||
class="icon"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
aria-hidden="true"
|
|
||||||
role="img"
|
|
||||||
viewBox="0 0 448 512"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"
|
|
||||||
class
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.sidebar-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.6rem;
|
|
||||||
left: 1rem;
|
|
||||||
display: none;
|
|
||||||
padding: 0.6rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-button .icon {
|
|
||||||
display: block;
|
|
||||||
width: 1.25rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 719px) {
|
|
||||||
.sidebar-button {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -0,0 +1,127 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import docsearch from '@docsearch/js'
|
||||||
|
import { DocSearchHit } from '@docsearch/react/dist/esm/types'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useRouter, useRoute, useData } from 'vitepress'
|
||||||
|
import { DefaultTheme } from '../config'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const { theme } = useData()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initialize(theme.value.algolia)
|
||||||
|
setTimeout(poll, 16)
|
||||||
|
})
|
||||||
|
|
||||||
|
function poll() {
|
||||||
|
// programmatically open the search box after initialize
|
||||||
|
const e = new Event('keydown') as any
|
||||||
|
|
||||||
|
e.key = 'k'
|
||||||
|
e.metaKey = true
|
||||||
|
|
||||||
|
window.dispatchEvent(e)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!document.querySelector('.DocSearch-Modal')) {
|
||||||
|
poll()
|
||||||
|
}
|
||||||
|
}, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) {
|
||||||
|
// note: multi-lang search support is removed since the theme
|
||||||
|
// doesn't support multiple locales as of now.
|
||||||
|
const options = Object.assign({}, userOptions, {
|
||||||
|
container: '#docsearch',
|
||||||
|
|
||||||
|
navigator: {
|
||||||
|
navigate({ itemUrl }: { itemUrl: string }) {
|
||||||
|
const { pathname: hitPathname } = new URL(
|
||||||
|
window.location.origin + itemUrl
|
||||||
|
)
|
||||||
|
|
||||||
|
// router doesn't handle same-page navigation so we use the native
|
||||||
|
// browser location API for anchor navigation
|
||||||
|
if (route.path === hitPathname) {
|
||||||
|
window.location.assign(window.location.origin + itemUrl)
|
||||||
|
} else {
|
||||||
|
router.go(itemUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
transformItems(items: DocSearchHit[]) {
|
||||||
|
return items.map((item) => {
|
||||||
|
return Object.assign({}, item, {
|
||||||
|
url: getRelativePath(item.url)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
hitComponent({ hit, children }: { hit: DocSearchHit, children: any }) {
|
||||||
|
const relativeHit = hit.url.startsWith('http')
|
||||||
|
? getRelativePath(hit.url as string)
|
||||||
|
: hit.url
|
||||||
|
|
||||||
|
return {
|
||||||
|
__v: null,
|
||||||
|
type: 'a',
|
||||||
|
ref: undefined,
|
||||||
|
constructor: undefined,
|
||||||
|
key: undefined,
|
||||||
|
|
||||||
|
props: {
|
||||||
|
href: hit.url,
|
||||||
|
|
||||||
|
onClick(event: MouseEvent) {
|
||||||
|
if (isSpecialClick(event)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// we rely on the native link scrolling when user is already on
|
||||||
|
// the right anchor because Router doesn't support duplicated
|
||||||
|
// history entries.
|
||||||
|
if (route.path === relativeHit) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the hits goes to another page, we prevent the native link
|
||||||
|
// behavior to leverage the Router loading feature.
|
||||||
|
if (route.path !== relativeHit) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
router.go(relativeHit)
|
||||||
|
},
|
||||||
|
|
||||||
|
children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
docsearch(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSpecialClick(event: MouseEvent) {
|
||||||
|
return (
|
||||||
|
event.button === 1 ||
|
||||||
|
event.altKey ||
|
||||||
|
event.ctrlKey ||
|
||||||
|
event.metaKey ||
|
||||||
|
event.shiftKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRelativePath(absoluteUrl: string) {
|
||||||
|
const { pathname, hash } = new URL(absoluteUrl)
|
||||||
|
|
||||||
|
return pathname + hash
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="docsearch" />
|
||||||
|
</template>
|
@ -0,0 +1,39 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{
|
||||||
|
show: boolean
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="show" class="VPBackdrop" />
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPBackdrop {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: var(--vp-z-index-backdrop);
|
||||||
|
background: rgba(0, 0, 0, .6);
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPBackdrop.fade-enter-from,
|
||||||
|
.VPBackdrop.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPBackdrop.fade-leave-active {
|
||||||
|
transition-duration: .25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.VPBackdrop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
icon?: string
|
||||||
|
title: string
|
||||||
|
details: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<article class="VPBox">
|
||||||
|
<div v-if="icon" class="icon">{{ icon }}</div>
|
||||||
|
<h1 class="title">{{ title }}</h1>
|
||||||
|
<p class="details">{{ details }}</p>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPBox {
|
||||||
|
border: 1px solid var(--vp-c-divider-light);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--vp-c-bg-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--vp-c-gray-light-4);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .icon {
|
||||||
|
background-color: var(--vp-c-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
padding-top: 8px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,122 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
tag?: string
|
||||||
|
size?: 'medium' | 'big'
|
||||||
|
theme?: 'brand' | 'alt' | 'sponsor'
|
||||||
|
text: string
|
||||||
|
href?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const classes = computed(() => [
|
||||||
|
props.size ?? 'medium',
|
||||||
|
props.theme ?? 'brand'
|
||||||
|
])
|
||||||
|
|
||||||
|
const isExternal = computed(() => props.href && /^[a-z]+:/i.test(props.href))
|
||||||
|
|
||||||
|
const component = computed(() => {
|
||||||
|
if (props.tag) {
|
||||||
|
return props.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.href ? 'a' : 'button'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="component"
|
||||||
|
class="VPButton"
|
||||||
|
:class="classes"
|
||||||
|
:href="href"
|
||||||
|
:target="isExternal ? '_blank' : undefined"
|
||||||
|
:rel="isExternal ? 'noopener noreferrer' : undefined"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPButton {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton:active {
|
||||||
|
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.medium {
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0 20px;
|
||||||
|
line-height: 38px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.big {
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 0 24px;
|
||||||
|
line-height: 46px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.brand {
|
||||||
|
border-color: var(--vp-button-brand-border);
|
||||||
|
color: var(--vp-button-brand-text);
|
||||||
|
background-color: var(--vp-button-brand-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.brand:hover {
|
||||||
|
border-color: var(--vp-button-brand-hover-border);
|
||||||
|
color: var(--vp-button-brand-hover-text);
|
||||||
|
background-color: var(--vp-button-brand-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.brand:active {
|
||||||
|
border-color: var(--vp-button-brand-active-border);
|
||||||
|
color: var(--vp-button-brand-active-text);
|
||||||
|
background-color: var(--vp-button-brand-active-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.alt {
|
||||||
|
border-color: var(--vp-button-alt-border);
|
||||||
|
color: var(--vp-button-alt-text);
|
||||||
|
background-color: var(--vp-button-alt-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.alt:hover {
|
||||||
|
border-color: var(--vp-button-alt-hover-border);
|
||||||
|
color: var(--vp-button-alt-hover-text);
|
||||||
|
background-color: var(--vp-button-alt-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.alt:active {
|
||||||
|
border-color: var(--vp-button-alt-active-border);
|
||||||
|
color: var(--vp-button-alt-active-text);
|
||||||
|
background-color: var(--vp-button-alt-active-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.sponsor {
|
||||||
|
border-color: var(--vp-button-sponsor-border);
|
||||||
|
color: var(--vp-button-sponsor-text);
|
||||||
|
background-color: var(--vp-button-sponsor-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.sponsor:hover {
|
||||||
|
border-color: var(--vp-button-sponsor-hover-border);
|
||||||
|
color: var(--vp-button-sponsor-hover-text);
|
||||||
|
background-color: var(--vp-button-sponsor-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPButton.sponsor:active {
|
||||||
|
border-color: var(--vp-button-sponsor-active-border);
|
||||||
|
color: var(--vp-button-sponsor-active-text);
|
||||||
|
background-color: var(--vp-button-sponsor-active-bg);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,90 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onMounted } from 'vue'
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import { useAside } from '../composables/aside'
|
||||||
|
|
||||||
|
const { theme } = useData()
|
||||||
|
const carbonOptions = theme.value.carbonAds
|
||||||
|
const { isAsideEnabled } = useAside()
|
||||||
|
const container = ref()
|
||||||
|
|
||||||
|
let hasInitalized = false
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if (!hasInitalized) {
|
||||||
|
hasInitalized = true
|
||||||
|
const s = document.createElement('script')
|
||||||
|
s.id = '_carbonads_js'
|
||||||
|
s.src = `//cdn.carbonads.com/carbon.js?serve=${carbonOptions.code}&placement=${carbonOptions.placement}`
|
||||||
|
s.async = true
|
||||||
|
container.value.appendChild(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no need to account for option changes during dev, we can just
|
||||||
|
// refresh the page
|
||||||
|
if (carbonOptions) {
|
||||||
|
onMounted(() => {
|
||||||
|
// if the page is loaded when aside is active, load carbon directly.
|
||||||
|
// otherwise, only load it if the page resizes to wide enough. this avoids
|
||||||
|
// loading carbon at all on mobile where it's never shown
|
||||||
|
if (isAsideEnabled.value) {
|
||||||
|
init()
|
||||||
|
} else {
|
||||||
|
watch(isAsideEnabled, (wide) => wide && init())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="VPCarbonAds" ref="container" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.VPCarbonAds {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
min-height: 240px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
background-color: var(--vp-c-bg-soft);
|
||||||
|
transition: color 0.5s, background-color 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPCarbonAds img {
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPCarbonAds .carbon-text {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-top: 12px;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
transition: color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPCarbonAds .carbon-text:hover {
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPCarbonAds .carbon-poweredby {
|
||||||
|
display: block;
|
||||||
|
padding-top: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPCarbonAds .carbon-poweredby:hover {
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,78 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute, useData } from 'vitepress'
|
||||||
|
import { useCopyCode } from '../composables/copy-code'
|
||||||
|
import { useSidebar } from '../composables/sidebar'
|
||||||
|
import NotFound from '../NotFound.vue'
|
||||||
|
import VPPage from './VPPage.vue'
|
||||||
|
import VPHome from './VPHome.vue'
|
||||||
|
import VPDoc from './VPDoc.vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const { frontmatter } = useData()
|
||||||
|
const { hasSidebar } = useSidebar()
|
||||||
|
|
||||||
|
useCopyCode()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="VPContent"
|
||||||
|
id="VPContent"
|
||||||
|
:class="{
|
||||||
|
'has-sidebar': hasSidebar,
|
||||||
|
'is-home': frontmatter.layout === 'home'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NotFound v-if="route.component === NotFound" />
|
||||||
|
|
||||||
|
<VPPage v-else-if="frontmatter.layout === 'page'" />
|
||||||
|
|
||||||
|
<VPHome v-else-if="frontmatter.layout === 'home'">
|
||||||
|
<template #home-hero-before><slot name="home-hero-before" /></template>
|
||||||
|
<template #home-hero-after><slot name="home-hero-after" /></template>
|
||||||
|
<template #home-features-before><slot name="home-features-before" /></template>
|
||||||
|
<template #home-features-after><slot name="home-features-after" /></template>
|
||||||
|
</VPHome>
|
||||||
|
|
||||||
|
<VPDoc v-else>
|
||||||
|
<template #aside-top><slot name="aside-top" /></template>
|
||||||
|
<template #aside-outline-before><slot name="aside-outline-before" /></template>
|
||||||
|
<template #aside-outline-after><slot name="aside-outline-after" /></template>
|
||||||
|
<template #aside-ads-before><slot name="aside-ads-before" /></template>
|
||||||
|
<template #aside-ads-after><slot name="aside-ads-after" /></template>
|
||||||
|
<template #aside-bottom><slot name="aside-bottom" /></template>
|
||||||
|
</VPDoc>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPContent {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPContent.is-home {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.VPContent {
|
||||||
|
padding-top: var(--vp-nav-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPContent.has-sidebar {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: var(--vp-sidebar-width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1440px) {
|
||||||
|
.VPContent.has-sidebar {
|
||||||
|
padding-right: calc((100vw - var(--vp-layout-max-width)) / 2);
|
||||||
|
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,191 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import { useSidebar } from '../composables/sidebar'
|
||||||
|
import VPDocAside from './VPDocAside.vue'
|
||||||
|
import VPDocFooter from './VPDocFooter.vue'
|
||||||
|
|
||||||
|
const { page } = useData()
|
||||||
|
const { hasSidebar } = useSidebar()
|
||||||
|
|
||||||
|
const pageName = computed(() => {
|
||||||
|
return page.value.relativePath.slice(0, page.value.relativePath.indexOf('/'))
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="VPDoc" :class="{ 'has-sidebar': hasSidebar }">
|
||||||
|
<div class="container">
|
||||||
|
<div class="aside">
|
||||||
|
<div class="aside-curtain" />
|
||||||
|
<div class="aside-container">
|
||||||
|
<div class="aside-content">
|
||||||
|
<VPDocAside>
|
||||||
|
<template #aside-top><slot name="aside-top" /></template>
|
||||||
|
<template #aside-bottom><slot name="aside-bottom" /></template>
|
||||||
|
<template #aside-outline-before><slot name="aside-outline-before" /></template>
|
||||||
|
<template #aside-outline-after><slot name="aside-outline-after" /></template>
|
||||||
|
<template #aside-ads-before><slot name="aside-ads-before" /></template>
|
||||||
|
<template #aside-ads-after><slot name="aside-ads-after" /></template>
|
||||||
|
</VPDocAside>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="content-container">
|
||||||
|
<main class="main">
|
||||||
|
<Content class="vp-doc" :class="pageName" />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<VPDocFooter />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPDoc {
|
||||||
|
padding: 32px 24px 96px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.VPDoc {
|
||||||
|
padding: 48px 32px 128px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.VPDoc {
|
||||||
|
padding: 32px 32px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDoc:not(.has-sidebar) .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 992px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDoc:not(.has-sidebar) .aside {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDoc:not(.has-sidebar) .content {
|
||||||
|
max-width: 752px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.VPDoc .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDoc .aside {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1440px) {
|
||||||
|
.VPDoc:not(.has-sidebar) .content {
|
||||||
|
max-width: 784px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDoc:not(.has-sidebar) .container {
|
||||||
|
max-width: 1104px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside {
|
||||||
|
position: relative;
|
||||||
|
display: none;
|
||||||
|
order: 2;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-left: 32px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 256px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-container {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
margin-top: calc(var(--vp-nav-height-desktop) * -1 - 32px);
|
||||||
|
padding-top: calc(var(--vp-nav-height-desktop) + 32px);
|
||||||
|
height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-curtain {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
width: 224px;
|
||||||
|
height: 32px;
|
||||||
|
background: linear-gradient(transparent, var(--vp-c-bg) 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: calc(100vh - (var(--vp-nav-height-desktop) + 32px));
|
||||||
|
padding-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.content {
|
||||||
|
padding: 0 32px 128px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.content {
|
||||||
|
order: 1;
|
||||||
|
margin: 0;
|
||||||
|
min-width: 640px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 688px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-link {
|
||||||
|
margin: 0 0 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-link .vt-link {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--vt-c-brand);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vt-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
color: var(--vt-c-brand);
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,46 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import VPDocAsideOutline from './VPDocAsideOutline.vue'
|
||||||
|
import VPDocAsideCarbonAds from './VPDocAsideCarbonAds.vue'
|
||||||
|
|
||||||
|
const { page, theme } = useData()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="VPDocAside">
|
||||||
|
<slot name="aside-top" />
|
||||||
|
|
||||||
|
<slot name="aside-outline-before" />
|
||||||
|
<VPDocAsideOutline v-if="page.headers.length" />
|
||||||
|
<slot name="aside-outline-after" />
|
||||||
|
|
||||||
|
<div class="spacer" />
|
||||||
|
|
||||||
|
<slot name="aside-ads-before" />
|
||||||
|
<VPDocAsideCarbonAds v-if="theme.carbonAds" />
|
||||||
|
<slot name="aside-ads-after" />
|
||||||
|
|
||||||
|
<slot name="aside-bottom" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPDocAside {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDocAside :deep(.spacer + .VPDocAsideSponsors),
|
||||||
|
.VPDocAside :deep(.spacer + .VPDocAsideCarbonAds) {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDocAside :deep(.VPDocAsideSponsors + .VPDocAsideCarbonAds) {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue