Merge branch 'main' into patch-1

pull/807/head
Divyansh Singh 3 years ago committed by GitHub
commit c9fbc792da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,3 +1,30 @@
## [1.0.0-alpha.4](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.3...v1.0.0-alpha.4) (2022-06-22)
### Bug Fixes
* **theme:** home image style is broken in big view port ([2bd960d](https://github.com/vuejs/vitepress/commit/2bd960d5f5a84df614035a4fb941331fdf9d84f2))
## [1.0.0-alpha.3](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.2...v1.0.0-alpha.3) (2022-06-22)
### Bug Fixes
* **theme:** italic fonts are missing ([#759](https://github.com/vuejs/vitepress/issues/759)) ([#777](https://github.com/vuejs/vitepress/issues/777)) ([fa00c83](https://github.com/vuejs/vitepress/commit/fa00c83af4aa5fa619cf2e3da8d5aab77984ba7c))
* **theme:** copy code in non-secure contexts ([#792](https://github.com/vuejs/vitepress/issues/792)) ([2935ed2](https://github.com/vuejs/vitepress/commit/2935ed22954010fa0d48d0625e5f2b0136991e0b))
* **theme:** copy code button has wrong tag closing syntax ([#816](https://github.com/vuejs/vitepress/issues/816)) ([75ca9e4](https://github.com/vuejs/vitepress/commit/75ca9e4302c65e3bcc9518f7df928318380f6cf6))
* **theme:** edit link gets hidden when a page don't have siblings ([#751](https://github.com/vuejs/vitepress/issues/751)) ([9bc4330](https://github.com/vuejs/vitepress/commit/9bc43306a1fe7bfd54b738642fd1737917a3af41))
* **theme:** nav close icon not working correctly ([#744](https://github.com/vuejs/vitepress/issues/744)) ([75c9d80](https://github.com/vuejs/vitepress/commit/75c9d809d21c0484c0ae8ce691d598cf229c9525))
* **theme:** sidebar is shown on home layout ([#825](https://github.com/vuejs/vitepress/issues/825)) ([#829](https://github.com/vuejs/vitepress/issues/829)) ([42cbd31](https://github.com/vuejs/vitepress/commit/42cbd31327b789ff9525919afb39b3092f1d445b))
* **theme:** sidebar collapsed option not working on layout change ([#809](https://github.com/vuejs/vitepress/issues/809)) ([#811](https://github.com/vuejs/vitepress/issues/811)) ([7737699](https://github.com/vuejs/vitepress/commit/773769926b74cabfbb3577d6c6e654fe976c0b76))
* **theme:** `DefaultTheme` type causes error in some cases ([#804](https://github.com/vuejs/vitepress/issues/804)) ([107724a](https://github.com/vuejs/vitepress/commit/107724ac6f24e5272964d3bdbff54169fa4d91ae))
### Features
* **build:** allow setting `base` from command line ([2952638](https://github.com/vuejs/vitepress/commit/295263807df5a0cdff3b04d5131a3cebc76ec491))
* **theme:** add active status to nav menu group ([#820](https://github.com/vuejs/vitepress/issues/820)) ([fdb5720](https://github.com/vuejs/vitepress/commit/fdb5720acda9f8f2dd1e4f33d0810a6e9ca9e7de))
* **theme:** add global layout slots ([#760](https://github.com/vuejs/vitepress/issues/760)) ([#812](https://github.com/vuejs/vitepress/issues/812)) ([1f1e298](https://github.com/vuejs/vitepress/commit/1f1e298864f7b8af9672b55251958ba766678e0b))
* **theme:** support themeable images for logo and hero ([#745](https://github.com/vuejs/vitepress/issues/745)) ([42813ce](https://github.com/vuejs/vitepress/commit/42813ce936d9fb141241969651cb0e3a02345442))
* **theme:** add team page feature ([#828](https://github.com/vuejs/vitepress/issues/828)) ([7cfe0f0](https://github.com/vuejs/vitepress/commit/7cfe0f05ab013904c66c48d8529d2ba4747869cb))
## [1.0.0-alpha.2](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2022-06-14) ## [1.0.0-alpha.2](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2022-06-14)
### Bug Fixes ### Bug Fixes

@ -1,5 +1,7 @@
import { defineConfig } from '../../src/node' import { defineConfig } from '../../src/node'
import { version } from '../../package.json'
export default defineConfig({ export default defineConfig({
lang: 'en-US', lang: 'en-US',
title: 'VitePress', title: 'VitePress',
@ -47,9 +49,18 @@ function nav() {
{ text: 'Guide', link: '/guide/what-is-vitepress', activeMatch: '/guide/' }, { text: 'Guide', link: '/guide/what-is-vitepress', activeMatch: '/guide/' },
{ text: 'Configs', link: '/config/introduction', activeMatch: '/config/' }, { text: 'Configs', link: '/config/introduction', activeMatch: '/config/' },
{ {
text: 'Changelog', text: version,
link: 'https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md' items: [
} {
text: 'Changelog',
link: 'https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md'
},
{
text: 'Contributing',
link: 'https://github.com/vuejs/vitepress/blob/main/.github/contributing.md'
},
],
},
] ]
} }
@ -87,7 +98,8 @@ function sidebarGuide() {
{ text: 'Edit Link', link: '/guide/theme-edit-link' }, { text: 'Edit Link', link: '/guide/theme-edit-link' },
{ text: 'Last Updated', link: '/guide/theme-last-updated' }, { text: 'Last Updated', link: '/guide/theme-last-updated' },
{ text: 'Layout', link: '/guide/theme-layout' }, { text: 'Layout', link: '/guide/theme-layout' },
{ text: 'Homepage', link: '/guide/theme-homepage' }, { text: 'Home Page', link: '/guide/theme-home-page' },
{ text: 'Team Page', link: '/guide/theme-team-page' },
{ text: 'Footer', link: '/guide/theme-footer' }, { text: 'Footer', link: '/guide/theme-footer' },
{ text: 'Search', link: '/guide/theme-search' }, { text: 'Search', link: '/guide/theme-search' },
{ text: 'Carbon Ads', link: '/guide/theme-carbon-ads' } { text: 'Carbon Ads', link: '/guide/theme-carbon-ads' }

@ -12,6 +12,21 @@ export default {
} }
``` ```
## 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
}
```
## base ## base
- Type: `string` - Type: `string`
@ -27,57 +42,55 @@ export default {
} }
``` ```
## lang ## description
- Type: `string` - Type: `string`
- Default: `en-US` - Default: `A VitePress site`
The lang attribute for the site. This will render as a `<html lang="en-US">` tag in the page HTML. Description for the site. This will render as a `<meta>` tag in the page HTML.
```ts ```ts
export default { export default {
lang: 'en-US' description: 'A VitePress site'
} }
``` ```
## title ## ignoreDeadLinks
- Type: `string` - Type: `boolean`
- Default: `VitePress` - Default: `false`
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. When set to `true`, VitePress will not fail builds due to dead links.
```ts ```ts
export default { export default {
title: 'VitePress' ignoreDeadLinks: true
} }
``` ```
## titleTemplate ## lang
- 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`. - Type: `string`
- Default: `en-US`
Set `false` to disable the feature. If the option is `undefined`, then the value of `title` option will be used. The lang attribute for the site. This will render as a `<html lang="en-US">` tag in the page HTML.
```ts ```ts
export default { export default {
title: 'VitePress', lang: 'en-US'
titleTemplate: 'Vite & Vue powered static site generator'
} }
``` ```
## description ## lastUpdated
- Type: `string` - Type: `boolean`
- Default: `A VitePress site` - Default: `false`
Description for the site. This will render as a `<meta>` tag in the page HTML. 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 ```ts
export default { export default {
description: 'A VitePress site' lastUpdated: true
} }
``` ```
@ -132,30 +145,31 @@ interface MarkdownOptions extends MarkdownIt.Options {
} }
``` ```
## appearance ## title
- 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. - Type: `string`
- Default: `VitePress`
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. 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 ```ts
export default { export default {
appearance: true title: 'VitePress'
} }
``` ```
## lastUpdated ## titleTemplate
- Type: `boolean` - Type: `string | 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. 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 ```ts
export default { export default {
lastUpdated: true title: 'VitePress',
titleTemplate: 'Vite & Vue powered static site generator'
} }
``` ```

@ -83,6 +83,7 @@ type NavItemWithLink = {
interface NavItemWithChildren { interface NavItemWithChildren {
text?: string text?: string
items: NavItemWithLink[] items: NavItemWithLink[]
activeMatch?: string
} }
``` ```
@ -129,6 +130,22 @@ interface SidebarItem {
} }
``` ```
## outlineTitle
- Type: `string`
- Default: `On this page`
Can be used to customize the title of the right sidebar (on the top of outline links). This is useful when writing documentation in another language.
```js
export default {
themeConfig: {
outlineTitle: 'In hac pagina'
}
}
```
## socialLinks ## socialLinks
- Type: `SocialLink` - Type: `SocialLink`
@ -246,9 +263,33 @@ export default {
```ts ```ts
export interface CarbonAds { export interface CarbonAds {
code: string, code: string
placement: string placement: string
} }
``` ```
Learn more in [Theme: Carbon Ads](../guide/theme-carbon-ads) Learn more in [Theme: Carbon Ads](../guide/theme-carbon-ads)
## docFooter
- Type: `DocFooter`
Can be used to customize text appearing above previous and next links. Helpful if not writing docs in English.
```js
export default {
themeConfig: {
docFooter: {
prev: 'Pagina prior',
next: 'Proxima pagina'
}
}
}
```
```ts
export interface DocFooter {
prev?: string
next?: string
}
```

@ -28,6 +28,39 @@ Add VitePress and Vue as dev dependencies for the project.
$ yarn add --dev vitepress vue $ yarn add --dev vitepress vue
``` ```
::: details Getting missing peer deps warnings?
`@docsearch/js` has certain issues with its peer dependencies. If you see some commands failing due to them, you can try this workaround for now:
On Yarn v2/v3, add this inside your rc file (`.yarnrc.yml` by default):
```yaml
packageExtensions:
'@docsearch/react@*':
peerDependenciesMeta:
'@types/react':
optional: true
'react':
optional: true
'react-dom':
optional: true
```
On PNPM, add this in your `package.json`:
```json
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@types/react",
"react",
"react-dom"
]
}
}
```
:::
Create your first document. Create your first document.
```bash ```bash

@ -19,5 +19,5 @@ If you're coming from VitePress 0.x version, there're several breaking changes d
## Frontmatter Config ## 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. - `home: true` option has changed to `layout: home`. Also, many Homepage related settings have been modified to provide additional features. See [Home Page guide](./theme-home-page) for details.
- `footer` option is moved to [`themeConfig.footer`](../config/theme-configs#footer). - `footer` option is moved to [`themeConfig.footer`](../config/theme-configs#footer).

@ -1,4 +1,4 @@
# Homepage # Home Page
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). 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).

@ -8,16 +8,17 @@ VitePress comes with its default theme providing many features out of the box. L
- [Edit Link](./theme-edit-link) - [Edit Link](./theme-edit-link)
- [Last Updated](./theme-last-updated) - [Last Updated](./theme-last-updated)
- [Layout](./theme-layout) - [Layout](./theme-layout)
- [Homepage](./theme-homepage) - [Home Page](./theme-home-page)
- [Team Page](./theme-team-page)
- [Footer](./theme-footer) - [Footer](./theme-footer)
- [Search](./theme-search) - [Search](./theme-search)
- [Carbon Ads](./theme-carbon-ads) - [Carbon Ads](./theme-carbon-ads)
If you don't find the features you're looking for, or you would rather create your own theme, you may customize VitePress to fit your requirements. If you don't find the features you're looking for, or you would rather create your own theme, you may customize VitePress to fit your requirements. In the following sections, we'll go through each way of customizing the VitePress theme.
## Using a Custom Theme ## Using a Custom Theme
You can enable a custom theme by adding the `.vitepress/theme/index.js` file (the "theme entry file"). You can enable a custom theme by adding the `.vitepress/theme/index.js` or `.vitepress/theme/index.ts` file (the "theme entry file").
``` ```
. .
@ -53,6 +54,7 @@ The theme entry file should export the theme as its default export:
import Layout from './Layout.vue' import Layout from './Layout.vue'
export default { export default {
// root component to wrap each page
Layout, Layout,
// this is a Vue 3 functional component // this is a Vue 3 functional component

@ -35,4 +35,4 @@ Note that even in this layout, sidebar will still show up if the page has a matc
## Home Layout ## 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. 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-home-page) for more details.

@ -51,7 +51,7 @@ export default {
} }
``` ```
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 `/`. 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 always start with `/`.
Nav links can also be dropdown menus. To do this, set `items` key on link option. Nav links can also be dropdown menus. To do this, set `items` key on link option.

@ -1,3 +1,29 @@
# Prev Next Link # Prev Next Link
Documentation coming soon... You can customize the text of previous and next links. This is helpful if you want to show different text on previous/next links than what you have on your sidebar.
## prev
- Type: `string`
- Details:
Specify the text to show on the link to the previous page.
If you don't set this in frontmatter, the text will be inferred from the sidebar config.
- Example:
```yaml
---
prev: 'Get Started | Markdown'
---
```
## next
- Type: `string`
- Details:
Same as `prev` but for the next page.

@ -110,7 +110,7 @@ export default {
{ {
text: 'Config', text: 'Config',
items: [ items: [
// This shows `/guide/index.md` page. // This shows `/config/index.md` page.
{ text: 'Index', link: '/config/' }, // /config/index.md { text: 'Index', link: '/config/' }, // /config/index.md
{ text: 'Three', link: '/config/three' }, // /config/three.md { text: 'Three', link: '/config/three' }, // /config/three.md
{ text: 'Four', link: '/config/four' } // /config/four.md { text: 'Four', link: '/config/four' } // /config/four.md

@ -0,0 +1,255 @@
<script setup>
import { VPTeamMembers } from 'vitepress/theme'
const members = [
{
avatar: 'https://github.com/yyx990803.png',
name: 'Evan You',
title: 'Creator',
links: [
{ icon: 'github', link: 'https://github.com/yyx990803' },
{ icon: 'twitter', link: 'https://twitter.com/youyuxi' }
]
},
{
avatar: 'https://github.com/kiaking.png',
name: 'Kia King Ishii',
title: 'Developer',
links: [
{ icon: 'github', link: 'https://github.com/kiaking' },
{ icon: 'twitter', link: 'https://twitter.com/KiaKing85' }
]
}
]
</script>
# Team Page
If you would like to introduce your team, you may use Team components to construct the Team Page. There're 2 ways of using these components. One is to embbed it in doc page, and another is to create a full Team Page.
## Show team members in a page
You may use `<VPTeamMembers>` component exposed from `vitepress/theme` to display a list of team members on any page.
```html
<script setup>
import { VPTeamMembers } from 'vitepress/theme'
const members = [
{
avatar: 'https://www.github.com/yyx990803.png',
name: 'Evan You',
title: 'Creator',
links: [
{ icon: 'github', link: 'https://github.com/yyx990803' },
{ icon: 'twitter', link: 'https://twitter.com/youyuxi' }
]
},
...
]
</script>
# Our Team
Say hello to our awesome team.
<VPTeamMembers size="small" :members="members" />
```
The above will display a team member in card looking element. It should display something similar to below.
<VPTeamMembers size="small" :members="members" />
`<VPTeamMembers>` component comes in 2 different sizes, `small` and `medium`. While it boiles down to your preference, usually `small` size should fit better when used in doc page. Also, you may add more properties to each member such as adding "description" or "sponsor" button. Learn more about it in [`<VPTeamMembers>`](#vpteammembers).
Embbeding team members in doc page is good for small size team where having dedicated full team page might be too much, or introducing partial members as a refference to documenation context.
If you have large number of members, or simply would like to have more space to show team members, consider [creating a full team page](#create-a-full-team-page).
## Create a full Team Page
Instead of adding team members to doc page, you may also create a full Team Page, similar to how you can create a custom [Home Page](./theme-home-page).
To create a team page, first, create a new md file. The file name doesn't matter, but here lets call it `team.md`. In this file, set frontmatter option `layout: page`, and then you may compose your page structure using `TeamPage` components.
```html
---
layout: page
---
<script setup>
import {
VPTeamPage,
VPTeamPageTitle,
VPTeamMembers
} from 'vitepress/theme'
const members = [
{
avatar: 'https://www.github.com/yyx990803.png',
name: 'Evan You',
title: 'Creator',
links: [
{ icon: 'github', link: 'https://github.com/yyx990803' },
{ icon: 'twitter', link: 'https://twitter.com/youyuxi' }
]
},
...
]
</script>
<VPTeamPage>
<VPTeamPageTitle>
<template #title>
Our Team
</template>
<template #lead>
The development of VitePress is guided by an international
team, some of whom have chosen to be featured below.
</template>
</VPTeamPageTitle>
<VPTeamMembers
:members="members"
/>
</VPTeamPage>
```
When creating a full team page, remember to wrap all components with `<VPTeamPage>` component. This component will ensure all nested team related components get the proper layout structure like spacings.
`<VPPageTitle>` component adds the page title section. The title being `<h1>` heading. Use `#title` and `#lead` slot to document about your team.
`<VPMembers>` works as same as when used in a doc page. It will display list of members.
### Add sections to divide team members
You may add "sections" to the team page. For example, you may have different types of team members such as Core Team Members and Community Partners. You can devide these members into sections to better explain the roles of each group.
To do so, add `<VPTeamPageSection>` component to the `team.md` file we created previously.
```html
---
layout: page
---
<script setup>
import {
VPTeamPage,
VPTeamPageTitle,
VPTeamMembers,
VPTeamPageSection
} from 'vitepress/theme'
const coreMembers = [...]
const partners = [...]
</script>
<VPTeamPage>
<VPTeamPageTitle>
<template #title>Our Team</template>
<template #lead>...</template>
</VPTeamPageTitle>
<VPTeamMembers size="medium" :members="coreMembers" />
<VPTeamPageSection>
<template #title>Partners</template>
<template #lead>...</template>
<template #members>
<VPTeamMembers size="small" :members="partners" />
</template>
</VPTeamPageSection>
</VPTeamPage>
```
The `<VPTeamPageSection>` component can have `#title` and `#lead` slot similar to `VPTeamPageTitle` component, and also `#members` slot for displaying team members.
Remember to put in `<VPTeamMembers>` component within `#members` slot.
## `<VPTeamMembers>`
The `<VPTeamMembers>` component displays a given list of members.
```html
<VPTeamMembers
size="medium"
:members="[
{ avatar: '...', name: '...' },
{ avatar: '...', name: '...' },
...
]"
/>
```
```ts
interface Props {
// Size of each members. Defaults to `medium`.
size?: 'small' | 'meidum'
// List of members to display.
members: TeamMember[]
}
interface TeamMember {
// Avatar image for the member.
avatar: string
// Name of the member.
name: string
// Title to be shown below member's name.
// e.g. Developer, Software Engineer, etc.
title?: string
// Organization that the member belongs.
org?: string
// URL for the organization.
orgLink?: string
// Description for the member.
desc?: string
// Social links. e.g. GitHub, Twitter, etc. You may pass in
// the Social Links object here.
// See: https://vitepress.vuejs.org/config/theme-configs.html#sociallinks
links?: SocialLink[]
// URL for the sponsor page for the member.
sponsor?: string
}
```
## `<VPTeamPage>`
The root component when creating a full team page. It only accepts a single slot. It's will style all passed in team related components.
## `<VPTeamPageTitle>`
Adds "title" section of the page. Best use at the very beginning under `<VPTeamPage>`. It accepts `#title` and `#lead` slot.
```html
<VPTeamPage>
<VPTeamPageTitle>
<template #title>
Our Team
</template>
<template #lead>
The development of VitePress is guided by an international
team, some of whom have chosen to be featured below.
</template>
</VPTeamPageTitle>
</VPTeamPage>
```
## `<VPTeamPageSection>`
Creates a "section" with in team page. It accepts `#title`, `#lead`, and `#members` slot. You may add as many sections as you like inside `<VPTeamPage>`.
```html
<VPTeamPage>
...
<VPTeamPageSection>
<template #title>Partners</template>
<template #lead>Lorem ipsum...</template>
<template #members>
<VPTeamMembers :members="data" />
</template>
</VPTeamPageSection>
</VPTeamPage>
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

@ -1,6 +1,6 @@
{ {
"name": "vitepress", "name": "vitepress",
"version": "1.0.0-alpha.2", "version": "1.0.0-alpha.4",
"description": "Vite & Vue powered static site generator", "description": "Vite & Vue powered static site generator",
"type": "module", "type": "module",
"packageManager": "pnpm@7.1.7", "packageManager": "pnpm@7.1.7",

@ -1,7 +1,9 @@
import { copy } from 'fs-extra' import { copy } from 'fs-extra'
import fg from 'fast-glob' import fg from 'fast-glob'
fg.sync('src/shared/**/*.ts').map(async (file) => { fg.sync('src/shared/**/*.ts').forEach(async (file) => {
await copy(file, file.replace(/^src\/shared\//, 'src/node/')) await Promise.all([
await copy(file, file.replace(/^src\/shared\//, 'src/client/')) copy(file, file.replace(/^src\/shared\//, 'src/node/')),
copy(file, file.replace(/^src\/shared\//, 'src/client/'))
])
}) })

@ -31,8 +31,10 @@ export const siteDataRef: Ref<SiteData> = shallowRef(
// hmr // hmr
if (import.meta.hot) { if (import.meta.hot) {
import.meta.hot!.accept('/@siteData', (m) => { import.meta.hot.accept('/@siteData', (m) => {
siteDataRef.value = m.default if (m) {
siteDataRef.value = m.default
}
}) })
} }

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { provide } from 'vue' import { provide, watch } from 'vue'
import { useRoute } from 'vitepress'
import { useSidebar, useCloseSidebarOnEscape } from './composables/sidebar' import { useSidebar, useCloseSidebarOnEscape } from './composables/sidebar'
import VPSkipLink from './components/VPSkipLink.vue' import VPSkipLink from './components/VPSkipLink.vue'
import VPBackdrop from './components/VPBackdrop.vue' import VPBackdrop from './components/VPBackdrop.vue'
@ -15,6 +16,9 @@ const {
close: closeSidebar close: closeSidebar
} = useSidebar() } = useSidebar()
const route = useRoute()
watch(() => route.path, closeSidebar)
useCloseSidebarOnEscape(isSidebarOpen, closeSidebar) useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
provide('close-sidebar', closeSidebar) provide('close-sidebar', closeSidebar)
@ -22,6 +26,7 @@ provide('close-sidebar', closeSidebar)
<template> <template>
<div class="Layout"> <div class="Layout">
<slot name="layout-top" />
<VPSkipLink /> <VPSkipLink />
<VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" /> <VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" />
<VPNav /> <VPNav />
@ -46,6 +51,7 @@ provide('close-sidebar', closeSidebar)
</VPContent> </VPContent>
<VPFooter /> <VPFooter />
<slot name="layout-bottom" />
</div> </div>
</template> </template>

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { withBase } from 'vitepress' import { normalizeLink } from '../support/utils'
const props = defineProps<{ const props = defineProps<{
tag?: string tag?: string
@ -31,7 +31,7 @@ const component = computed(() => {
:is="component" :is="component"
class="VPButton" class="VPButton"
:class="classes" :class="classes"
:href="href ? withBase(href) : undefined" :href="href ? normalizeLink(href) : undefined"
:target="isExternal ? '_blank' : undefined" :target="isExternal ? '_blank' : undefined"
:rel="isExternal ? 'noopener noreferrer' : undefined" :rel="isExternal ? 'noopener noreferrer' : undefined"
> >

@ -1,13 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vitepress' import { useRoute } from 'vitepress'
import { useSidebar } from '../composables/sidebar' import { useSidebar } from '../composables/sidebar'
import VPDocAside from './VPDocAside.vue' import VPDocAside from './VPDocAside.vue'
import VPDocFooter from './VPDocFooter.vue' import VPDocFooter from './VPDocFooter.vue'
const { path } = useRoute() const route = useRoute()
const { hasSidebar } = useSidebar() const { hasSidebar } = useSidebar()
const pageName = path.replace(/[./]+/g, '_').replace(/_html$/, '') const pageName = computed(() =>
route.path.replace(/[./]+/g, '_').replace(/_html$/, '')
)
</script> </script>
<template> <template>
@ -169,24 +172,4 @@ const pageName = path.replace(/[./]+/g, '_').replace(/_html$/, '')
margin: 0 auto; margin: 0 auto;
max-width: 688px; 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> </style>

@ -19,7 +19,7 @@ const hasLastUpdated = computed(() => {
</script> </script>
<template> <template>
<footer v-if="control.prev || control.next" class="VPDocFooter"> <footer class="VPDocFooter">
<div class="edit-info"> <div class="edit-info">
<div v-if="theme.editLink && frontmatter.editLink !== false" class="edit-link"> <div v-if="theme.editLink && frontmatter.editLink !== false" class="edit-link">
<VPLink class="edit-link-button" :href="editLink.url" :no-icon="true"> <VPLink class="edit-link-button" :href="editLink.url" :no-icon="true">
@ -33,16 +33,16 @@ const hasLastUpdated = computed(() => {
</div> </div>
</div> </div>
<div class="prev-next"> <div v-if="control.prev || control.next" class="prev-next">
<div class="pager"> <div class="pager">
<a v-if="control.prev" class="pager-link prev" :href="normalizeLink(control.prev.link)"> <a v-if="control.prev" class="pager-link prev" :href="normalizeLink(control.prev.link)">
<span class="desc">Previous page</span> <span class="desc">{{ theme.docFooter?.prev ?? 'Previous page' }}</span>
<span class="title">{{ control.prev.text }} </span> <span class="title">{{ control.prev.text }} </span>
</a> </a>
</div> </div>
<div class="pager" :class="{ 'has-prev': control.prev }"> <div class="pager" :class="{ 'has-prev': control.prev }">
<a v-if="control.next" class="pager-link next" :href="normalizeLink(control.next.link)"> <a v-if="control.next" class="pager-link next" :href="normalizeLink(control.next.link)">
<span class="desc">Next page</span> <span class="desc">{{ theme.docFooter?.next ?? 'Next page' }}</span>
<span class="title">{{ control.next.text }}</span> <span class="title">{{ control.next.text }}</span>
</a> </a>
</div> </div>

@ -1,11 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watchEffect, onMounted } from 'vue' import { ref, computed, watchEffect, onMounted } from 'vue'
import { useData } from 'vitepress' import { useData } from 'vitepress'
const { theme, page } = useData() const { theme, page } = useData()
const date = new Date(page.value.lastUpdated!) const date = computed(() => new Date(page.value.lastUpdated!))
const isoDatetime = date.toISOString() const isoDatetime = computed(() => date.value.toISOString())
const datetime = ref('') const datetime = ref('')
// set time on mounted hook because the locale string might be different // set time on mounted hook because the locale string might be different
@ -13,7 +13,7 @@ const datetime = ref('')
// calculated at build time // calculated at build time
onMounted(() => { onMounted(() => {
watchEffect(() => { watchEffect(() => {
datetime.value = date.toLocaleString(window.navigator.language) datetime.value = date.value.toLocaleString(window.navigator.language)
}) })
}) })
</script> </script>

@ -72,6 +72,14 @@ function onBlur() {
fill: var(--vp-c-text-2); fill: var(--vp-c-text-2);
} }
.VPFlyout.active .text {
color: var(--vp-c-brand);
}
.VPFlyout.active:hover .text {
color: var(--vp-c-brand-dark);
}
.VPFlyout:hover .menu, .VPFlyout:hover .menu,
.button[aria-expanded="true"] + .menu { .button[aria-expanded="true"] + .menu {
opacity: 1; opacity: 1;

@ -301,13 +301,13 @@ defineProps<{
} }
@media (min-width: 640px) { @media (min-width: 640px) {
.image-src { :deep(.image-src) {
max-width: 256px; max-width: 256px;
} }
} }
@media (min-width: 960px) { @media (min-width: 960px) {
.image-src { :deep(.image-src) {
max-width: 320px; max-width: 320px;
} }
} }

@ -48,7 +48,6 @@ const { hasSidebar } = useSidebar()
border-bottom: 1px solid var(--vp-c-divider-light); border-bottom: 1px solid var(--vp-c-divider-light);
padding: 0 8px 0 24px; padding: 0 8px 0 24px;
height: var(--vp-nav-height-mobile); height: var(--vp-nav-height-mobile);
background-color: var(--vt-c-bg);
transition: border-color 0.5s, background-color 0.5s; transition: border-color 0.5s, background-color 0.5s;
} }

@ -48,18 +48,18 @@ defineEmits<{
overflow: hidden; overflow: hidden;
} }
.container:hover .top { top: 0; left: 0; transform: translateX(4px); } .VPNavBarHamburger:hover .top { top: 0; left: 0; transform: translateX(4px); }
.container:hover .middle { top: 6; left: 0; transform: translateX(0); } .VPNavBarHamburger:hover .middle { top: 6px; left: 0; transform: translateX(0); }
.container:hover .bottom { top: 12px; left: 0; transform: translateX(8px); } .VPNavBarHamburger:hover .bottom { top: 12px; left: 0; transform: translateX(8px); }
.container.active .top { top: 6px; transform: translateX(0) rotate(225deg); } .VPNavBarHamburger.active .top { top: 6px; transform: translateX(0) rotate(225deg); }
.container.active .middle { top: 6px; transform: translateX(16px); } .VPNavBarHamburger.active .middle { top: 6px; transform: translateX(16px); }
.container.active .bottom { top: 6px; transform: translateX(0) rotate(135deg); } .VPNavBarHamburger.active .bottom { top: 6px; transform: translateX(0) rotate(135deg); }
.container.active:hover .top, .VPNavBarHamburger.active:hover .top,
.container.active:hover .middle, .VPNavBarHamburger.active:hover .middle,
.container.active:hover .bottom { .VPNavBarHamburger.active:hover .bottom {
background-color: var(--vt-c-text-2); background-color: var(--vp-c-text-2);
transition: top .25s, background-color .25s, transform .25s; transition: top .25s, background-color .25s, transform .25s;
} }

@ -1,12 +1,27 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useData } from 'vitepress'
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { isActive } from '../support/utils'
import VPFlyout from './VPFlyout.vue' import VPFlyout from './VPFlyout.vue'
defineProps<{ defineProps<{
item: DefaultTheme.NavItemWithChildren item: DefaultTheme.NavItemWithChildren
}>() }>()
const { page } = useData()
</script> </script>
<template> <template>
<VPFlyout :button="item.text" :items="item.items" /> <VPFlyout
:class="{
VPNavBarMenuGroup: true,
active: isActive(
page.relativePath,
item.activeMatch,
!!item.activeMatch
)
}"
:button="item.text"
:items="item.items"
/>
</template> </template>

@ -76,7 +76,7 @@ function load() {
stroke-linejoin="round" stroke-linejoin="round"
/> />
</svg> </svg>
<span class="DocSearch-Button-Placeholder">Search</span> <span class="DocSearch-Button-Placeholder">{{ theme.algolia?.buttonText || 'Search' }}</span>
</span> </span>
<span class="DocSearch-Button-Keys"> <span class="DocSearch-Button-Keys">
<kbd class="DocSearch-Button-Key" ref="metaKey">Meta</kbd> <kbd class="DocSearch-Button-Key" ref="metaKey">Meta</kbd>

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { ref } from 'vue' import { ref, watchEffect } from 'vue'
import VPIconPlusSquare from './icons/VPIconPlusSquare.vue' import VPIconPlusSquare from './icons/VPIconPlusSquare.vue'
import VPIconMinusSquare from './icons/VPIconMinusSquare.vue' import VPIconMinusSquare from './icons/VPIconMinusSquare.vue'
import VPSidebarLink from './VPSidebarLink.vue' import VPSidebarLink from './VPSidebarLink.vue'
@ -12,7 +12,10 @@ const props = defineProps<{
collapsed?: boolean collapsed?: boolean
}>() }>()
const collapsed = ref(props.collapsible && props.collapsed) const collapsed = ref(false)
watchEffect(() => {
collapsed.value = !!(props.collapsible && props.collapsed)
})
function toggle() { function toggle() {
if (props.collapsible) { if (props.collapsible) {

@ -0,0 +1,55 @@
<script setup lang="ts">
import { computed } from 'vue'
import type { DefaultTheme } from '..'
import VPTeamMembersItem from './VPTeamMembersItem.vue'
const props = defineProps<{
size?: 'small' | 'medium'
members: DefaultTheme.TeamMember[]
}>()
const classes = computed(() => [
props.size ?? 'medium',
`count-${props.members.length}`
])
</script>
<template>
<div class="VPTeamMembers" :class="classes">
<div class="container">
<div v-for="member in members" :key="member.name" class="item">
<VPTeamMembersItem :size="size" :member="member" />
</div>
</div>
</div>
</template>
<style scoped>
.VPTeamMembers.small .container {
grid-template-columns: repeat(auto-fit, minmax(224px, 1fr));
}
.VPTeamMembers.small.count-1 .container { max-width: 276px; }
.VPTeamMembers.small.count-2 .container { max-width: calc(276px * 2 + 24px); }
.VPTeamMembers.small.count-3 .container { max-width: calc(276px * 3 + 24px * 2); }
.VPTeamMembers.medium .container {
grid-template-columns: repeat(auto-fit, minmax(256px, 1fr));
}
@media (min-width: 375px) {
.VPTeamMembers.medium .container {
grid-template-columns: repeat(auto-fit, minmax(288px, 1fr));
}
}
.VPTeamMembers.medium.count-1 .container { max-width: 368px; }
.VPTeamMembers.medium.count-2 .container { max-width: calc(368px * 2 + 24px); }
.container {
display: grid;
gap: 24px;
margin: 0 auto;
max-width: 1152px;
}
</style>

@ -0,0 +1,215 @@
<script setup lang="ts">
import type { DefaultTheme } from '..'
import VPIconHeart from './icons/VPIconHeart.vue'
import VPLink from './VPLink.vue'
import VPSocialLinks from './VPSocialLinks.vue'
defineProps<{
size?: 'small' | 'medium'
member: DefaultTheme.TeamMember
}>()
</script>
<template>
<article class="VPTeamMembersItem" :class="[size ?? 'medium']">
<div class="profile">
<figure class="avatar">
<img class="avatar-img" :src="member.avatar" :alt="member.name">
</figure>
<div class="data">
<h1 class="name">
{{ member.name }}
</h1>
<p v-if="member.title || member.org" class="affiliation">
<span v-if="member.title" class="title">
{{ member.title }}
</span>
<span v-if="member.title && member.org" class="at">
@
</span>
<VPLink v-if="member.org" class="org" :class="{ link: member.orgLink }" :href="member.orgLink" no-icon>
{{ member.org }}
</VPLink>
</p>
<p v-if="member.desc" class="desc">
{{ member.desc }}
</p>
<div v-if="member.links" class="links">
<VPSocialLinks :links="member.links" />
</div>
</div>
</div>
<div v-if="member.sponsor" class="sp">
<VPLink class="sp-link" :href="member.sponsor" no-icon>
<VPIconHeart class="sp-icon" /> Sponsor
</VPLink>
</div>
</article>
</template>
<style scoped>
.VPTeamMembersItem {
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 12px;
width: 100%;
height: 100%;
overflow: hidden;
}
.VPTeamMembersItem.small .profile {
padding: 32px;
}
.VPTeamMembersItem.small .data {
padding-top: 20px;
}
.VPTeamMembersItem.small .avatar {
width: 64px;
height: 64px;
}
.VPTeamMembersItem.small .name {
line-height: 24px;
font-size: 16px;
}
.VPTeamMembersItem.small .affiliation {
padding-top: 4px;
line-height: 20px;
font-size: 14px;
}
.VPTeamMembersItem.small .desc {
padding-top: 12px;
line-height: 20px;
font-size: 14px;
}
.VPTeamMembersItem.small .links {
margin: 0 -16px -20px;
padding: 10px 0 0;
}
.VPTeamMembersItem.medium .profile {
padding: 48px 32px;
}
.VPTeamMembersItem.medium .data {
padding-top: 24px;
text-align: center;
}
.VPTeamMembersItem.medium .avatar {
width: 96px;
height: 96px;
}
.VPTeamMembersItem.medium .name {
letter-spacing: 0.15px;
line-height: 28px;
font-size: 20px;
}
.VPTeamMembersItem.medium .affiliation {
padding-top: 4px;
font-size: 16px;
}
.VPTeamMembersItem.medium .desc {
padding-top: 16px;
max-width: 288px;
font-size: 16px;
}
.VPTeamMembersItem.medium .links {
margin: 0 -16px -12px;
padding: 16px 12px 0;
}
.profile {
flex-grow: 1;
background-color: var(--vp-c-bg-soft);
}
.data {
text-align: center;
}
.avatar {
position: relative;
flex-shrink: 0;
margin: 0 auto;
border-radius: 50%;
box-shadow: var(--vp-shadow-3);
}
.avatar-img {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: 50%;
object-fit: cover;
}
.name {
margin: 0;
font-weight: 600;
}
.affiliation {
margin: 0;
font-weight: 500;
color: var(--vp-c-text-2);
}
.org.link {
color: var(--vp-c-text-2);
transition: color 0.25s;
}
.org.link:hover {
color: var(--vp-c-brand);
}
.desc {
margin: 0 auto;
}
.links {
display: flex;
justify-content: center;
height: 56px;
}
.sp-link {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 16px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-sponsor);
background-color: var(--vp-c-bg-soft);
transition: color 0.25s, background-color 0.25s;
}
.sp-link:hover,
.sp-link:focus {
outline: none;
color: var(--vp-c-text-dark-1);
background-color: var(--vp-c-sponsor);
}
.sp-icon {
margin-right: 8px;
width: 16px;
height: 16px;
fill: currentColor;
}
</style>

@ -0,0 +1,53 @@
<template>
<div class="VPTeamPage">
<slot />
</div>
</template>
<style scoped>
.VPTeamPage {
padding-bottom: 96px;
}
@media (min-width: 768px) {
.VPTeamPage {
padding-bottom: 128px;
}
}
:slotted(.VPTeamPageSection + .VPTeamPageSection),
:slotted(.VPTeamMembers + .VPTeamPageSection) {
margin-top: 64px;
}
:slotted(.VPTeamMembers + .VPTeamMembers) {
margin-top: 24px;
}
@media (min-width: 768px) {
:slotted(.VPTeamPageTitle + .VPTeamPageSection) {
margin-top: 16px;
}
:slotted(.VPTeamPageSection + .VPTeamPageSection),
:slotted(.VPTeamMembers + .VPTeamPageSection) {
margin-top: 96px;
}
}
:slotted(.VPTeamMembers) {
padding: 0 24px;
}
@media (min-width: 768px) {
:slotted(.VPTeamMembers) {
padding: 0 48px;
}
}
@media (min-width: 960px) {
:slotted(.VPTeamMembers) {
padding: 0 64px;
}
}
</style>

@ -0,0 +1,77 @@
<template>
<section class="VPTeamPageSection">
<div class="title">
<div class="title-line" />
<h2 v-if="$slots.title" class="title-text">
<slot name="title" />
</h2>
</div>
<p v-if="$slots.lead" class="lead">
<slot name="lead" />
</p>
<div v-if="$slots.members" class="members">
<slot name="members" />
</div>
</section>
</template>
<style scoped>
.VPTeamPageSection {
padding: 0 32px;
}
@media (min-width: 768px) {
.VPTeamPageSection {
padding: 0 48px;
}
}
@media (min-width: 960px) {
.VPTeamPageSection {
padding: 0 64px;
}
}
.title {
position: relative;
margin: 0 auto;
max-width: 1152px;
text-align: center;
color: var(--vp-c-text-2);
}
.title-line {
position: absolute;
top: 16px;
left: 0;
width: 100%;
height: 1px;
background-color: var(--vp-c-divider-light);
}
.title-text {
position: relative;
display: inline-block;
padding: 0 24px;
letter-spacing: 0;
line-height: 32px;
font-size: 20px;
font-weight: 500;
background-color: var(--vp-c-bg);
}
.lead {
margin: 0 auto;
max-width: 480px;
padding-top: 12px;
text-align: center;
line-height: 24px;
font-size: 16px;
font-weight: 500;
color: var(--vp-c-text-2);
}
.members {
padding-top: 40px;
}
</style>

@ -0,0 +1,63 @@
<template>
<div class="VPTeamPageTitle">
<h1 v-if="$slots.title" class="title">
<slot name="title" />
</h1>
<p v-if="$slots.lead" class="lead">
<slot name="lead" />
</p>
</div>
</template>
<style scoped>
.VPTeamPageTitle {
padding: 48px 32px;
text-align: center;
}
@media (min-width: 768px) {
.VPTeamPageTitle {
padding: 64px 48px 48px;
}
}
@media (min-width: 960px) {
.VPTeamPageTitle {
padding: 80px 64px 48px;
}
}
.title {
letter-spacing: 0;
line-height: 44px;
font-size: 36px;
font-weight: 500;
}
@media (min-width: 768px) {
.title {
letter-spacing: -0.5px;
line-height: 56px;
font-size: 48px;
}
}
.lead {
margin: 0 auto;
max-width: 512px;
padding-top: 12px;
line-height: 24px;
font-size: 16px;
font-weight: 500;
color: var(--vp-c-text-2);
}
@media (min-width: 768px) {
.lead {
max-width: 592px;
letter-spacing: 0.15px;
line-height: 28px;
font-size: 20px;
}
}
</style>

@ -1,5 +1,5 @@
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { ref, computed } from 'vue' import { ref, computed, watch } from 'vue'
import { useData, useRoute } from 'vitepress' import { useData, useRoute } from 'vitepress'
export function useNav() { export function useNav() {
@ -26,6 +26,9 @@ export function useNav() {
window.outerWidth >= 768 && closeScreen() window.outerWidth >= 768 && closeScreen()
} }
const route = useRoute()
watch(() => route.path, closeScreen)
return { return {
isScreenOpen, isScreenOpen,
openScreen, openScreen,

@ -4,7 +4,7 @@ import { isActive } from '../support/utils'
import { getSidebar, getFlatSideBarLinks } from '../support/sidebar' import { getSidebar, getFlatSideBarLinks } from '../support/sidebar'
export function usePrevNext() { export function usePrevNext() {
const { page, theme } = useData() const { page, theme, frontmatter } = useData()
return computed(() => { return computed(() => {
const sidebar = getSidebar(theme.value.sidebar, page.value.relativePath) const sidebar = getSidebar(theme.value.sidebar, page.value.relativePath)
@ -15,8 +15,12 @@ export function usePrevNext() {
}) })
return { return {
prev: candidates[index - 1], prev: frontmatter.value.prev
next: candidates[index + 1] ? { ...candidates[index - 1], text: frontmatter.value.prev }
: candidates[index - 1],
next: frontmatter.value.next
? { ...candidates[index + 1], text: frontmatter.value.next }
: candidates[index + 1]
} }
}) })
} }

@ -1,5 +1,5 @@
import { Ref, ref, computed, watchEffect, onMounted, onUnmounted } from 'vue' import { computed, onMounted, onUnmounted, Ref, ref, watchEffect } from 'vue'
import { useRoute, useData } from 'vitepress' import { useData, useRoute } from 'vitepress'
import { getSidebar } from '../support/sidebar' import { getSidebar } from '../support/sidebar'
export function useSidebar() { export function useSidebar() {
@ -10,13 +10,15 @@ export function useSidebar() {
const sidebar = computed(() => { const sidebar = computed(() => {
const sidebarConfig = theme.value.sidebar const sidebarConfig = theme.value.sidebar
const relativePath = route.data.relativePath return sidebarConfig ? getSidebar(sidebarConfig, route.path) : []
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : []
}) })
const hasSidebar = computed(() => { const hasSidebar = computed(() => {
return frontmatter.value.sidebar !== false && sidebar.value.length > 0 return (
frontmatter.value.sidebar !== false &&
sidebar.value.length > 0 &&
frontmatter.value.layout !== 'home'
)
}) })
function open() { function open() {

@ -15,6 +15,10 @@ export { default as VPHomeHero } from './components/VPHomeHero.vue'
export { default as VPHomeFeatures } from './components/VPHomeFeatures.vue' export { default as VPHomeFeatures } from './components/VPHomeFeatures.vue'
export { default as VPHomeSponsors } from './components/VPHomeSponsors.vue' export { default as VPHomeSponsors } from './components/VPHomeSponsors.vue'
export { default as VPDocAsideSponsors } from './components/VPDocAsideSponsors.vue' export { default as VPDocAsideSponsors } from './components/VPDocAsideSponsors.vue'
export { default as VPTeamPage } from './components/VPTeamPage.vue'
export { default as VPTeamPageTitle } from './components/VPTeamPageTitle.vue'
export { default as VPTeamPageSection } from './components/VPTeamPageSection.vue'
export { default as VPTeamMembers } from './components/VPTeamMembers.vue'
const theme: Theme = { const theme: Theme = {
Layout, Layout,

@ -20,7 +20,7 @@
} }
.vp-doc h2 { .vp-doc h2 {
margin: 48px 0 0; margin: 48px 0 16px;
border-top: 1px solid var(--vp-c-divider-light); border-top: 1px solid var(--vp-c-divider-light);
padding-top: 24px; padding-top: 24px;
letter-spacing: -0.02em; letter-spacing: -0.02em;
@ -461,3 +461,26 @@
.vp-doc [class~='language-tsx']:before { content: 'tsx'; } .vp-doc [class~='language-tsx']:before { content: 'tsx'; }
.vp-doc [class~='language-vue']:before { content: 'vue'; } .vp-doc [class~='language-vue']:before { content: 'vue'; }
.vp-doc [class~='language-yaml']:before { content: 'yaml'; } .vp-doc [class~='language-yaml']:before { content: 'yaml'; }
/**
* Component: Team
* -------------------------------------------------------------------------- */
.vp-doc .VPTeamMembers {
margin-top: 24px;
}
.vp-doc .VPTeamMembers.small.count-1 .container {
margin: 0 !important;
max-width: calc((100% - 24px) / 2) !important;
}
.vp-doc .VPTeamMembers.small.count-2 .container,
.vp-doc .VPTeamMembers.small.count-3 .container {
max-width: 100% !important;
}
.vp-doc .VPTeamMembers.medium.count-1 .container {
margin: 0 !important;
max-width: calc((100% - 24px) / 2) !important;
}

@ -9,13 +9,18 @@ import { bundle, okMark, failMark } from './bundle'
export async function build( export async function build(
root: string, root: string,
buildOptions: BuildOptions & { mpa?: string } = {} buildOptions: BuildOptions & { base?: string; mpa?: string } = {}
) { ) {
const start = Date.now() const start = Date.now()
process.env.NODE_ENV = 'production' process.env.NODE_ENV = 'production'
const siteConfig = await resolveConfig(root, 'build', 'production') const siteConfig = await resolveConfig(root, 'build', 'production')
if (buildOptions.base) {
siteConfig.site.base = buildOptions.base
delete buildOptions.base
}
if (buildOptions.mpa) { if (buildOptions.mpa) {
siteConfig.mpa = true siteConfig.mpa = true
delete buildOptions.mpa delete buildOptions.mpa

@ -64,6 +64,13 @@ export interface UserConfig<ThemeConfig = any> {
* @experimental * @experimental
*/ */
mpa?: boolean mpa?: boolean
/**
* Don't fail builds due to dead links.
*
* @default false
*/
ignoreDeadLinks?: boolean
} }
export type RawConfigExports<ThemeConfig = any> = export type RawConfigExports<ThemeConfig = any> =
@ -74,7 +81,13 @@ export type RawConfigExports<ThemeConfig = any> =
export interface SiteConfig<ThemeConfig = any> export interface SiteConfig<ThemeConfig = any>
extends Pick< extends Pick<
UserConfig, UserConfig,
'markdown' | 'vue' | 'vite' | 'shouldPreload' | 'mpa' | 'lastUpdated' | 'markdown'
| 'vue'
| 'vite'
| 'shouldPreload'
| 'mpa'
| 'lastUpdated'
| 'ignoreDeadLinks'
> { > {
root: string root: string
srcDir: string srcDir: string
@ -152,7 +165,8 @@ export async function resolveConfig(
vue: userConfig.vue, vue: userConfig.vue,
vite: userConfig.vite, vite: userConfig.vite,
shouldPreload: userConfig.shouldPreload, shouldPreload: userConfig.shouldPreload,
mpa: !!userConfig.mpa mpa: !!userConfig.mpa,
ignoreDeadLinks: userConfig.ignoreDeadLinks
} }
return config return config
@ -166,14 +180,9 @@ async function resolveUserConfig(
mode: string mode: string
): Promise<[UserConfig, string | undefined]> { ): Promise<[UserConfig, string | undefined]> {
// load user config // load user config
let configPath const configPath = supportedConfigExtensions
for (const ext of supportedConfigExtensions) { .map((ext) => resolve(root, `config.${ext}`))
const p = resolve(root, `config.${ext}`) .find(fs.pathExistsSync)
if (await fs.pathExists(p)) {
configPath = p
break
}
}
const userConfig: RawConfigExports = configPath const userConfig: RawConfigExports = configPath
? (( ? ((

@ -61,7 +61,7 @@ export const createMarkdownRenderer = async (
const md = MarkdownIt({ const md = MarkdownIt({
html: true, html: true,
linkify: true, linkify: true,
highlight: await highlight(options.theme), highlight: options.highlight || (await highlight(options.theme)),
...options ...options
}) as MarkdownRenderer }) as MarkdownRenderer

@ -15,6 +15,6 @@ export const preWrapperPlugin = (md: MarkdownIt) => {
const [tokens, idx] = args const [tokens, idx] = args
const token = tokens[idx] const token = tokens[idx]
const rawCode = fence(...args) const rawCode = fence(...args)
return `<div class="language-${token.info.trim()}"><span class="copy" />${rawCode}</div>` return `<div class="language-${token.info.trim()}"><span class="copy"></span>${rawCode}</div>`
} }
} }

@ -34,14 +34,7 @@ export async function createMarkdownToVueRenderFn(
pages = pages.map((p) => slash(p.replace(/\.md$/, ''))) pages = pages.map((p) => slash(p.replace(/\.md$/, '')))
const userDefineRegex = userDefines const replaceRegex = genReplaceRegexp(userDefines, isBuild)
? new RegExp(
`\\b(${Object.keys(userDefines)
.map((key) => key.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'))
.join('|')})`,
'g'
)
: null
return async ( return async (
src: string, src: string,
@ -74,30 +67,19 @@ export async function createMarkdownToVueRenderFn(
md.__path = file md.__path = file
md.__relativePath = relativePath md.__relativePath = relativePath
let html = md.render(content) const html = md.render(content)
const data = md.__data const data = md.__data
if (isBuild) {
// avoid env variables being replaced by vite
html = html
.replace(/\bimport\.meta/g, 'import.<wbr/>meta')
.replace(/\bprocess\.env/g, 'process.<wbr/>env')
// also avoid replacing vite user defines
if (userDefineRegex) {
html = html.replace(
userDefineRegex,
(_) => `${_[0]}<wbr/>${_.slice(1)}`
)
}
}
// validate data.links // validate data.links
const deadLinks: string[] = [] const deadLinks: string[] = []
const recordDeadLink = (url: string) => { const recordDeadLink = (url: string) => {
console.warn( console.warn(
c.yellow( c.yellow(
`\n(!) Found dead link ${c.cyan(url)} in file ${c.white(c.dim(file))}` `\n(!) Found dead link ${c.cyan(url)} in file ${c.white(
c.dim(file)
)}\nIf it is intended, you can use:\n ${c.cyan(
`<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`
)}`
) )
) )
deadLinks.push(url) deadLinks.push(url)
@ -145,8 +127,14 @@ export async function createMarkdownToVueRenderFn(
} }
const vueSrc = const vueSrc =
genPageDataCode(data.hoistedTags || [], pageData).join('\n') + genPageDataCode(data.hoistedTags || [], pageData, replaceRegex).join(
`\n<template><div>${html}</div></template>` '\n'
) +
`\n<template><div>${replaceConstants(
html,
replaceRegex,
vueTemplateBreaker
)}</div></template>`
debug(`[render] ${file} in ${Date.now() - start}ms.`) debug(`[render] ${file} in ${Date.now() - start}ms.`)
@ -167,10 +155,42 @@ const scriptSetupRE = /<\s*script[^>]*\bsetup\b[^>]*/
const scriptClientRE = /<\s*script[^>]*\bclient\b[^>]*/ const scriptClientRE = /<\s*script[^>]*\bclient\b[^>]*/
const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/ const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)as(\s*)default/ const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)as(\s*)default/
const jsStringBreaker = '\u200b'
const vueTemplateBreaker = '<wbr>'
function genReplaceRegexp(
userDefines: Record<string, any> = {},
isBuild: boolean
): RegExp {
// `process.env` need to be handled in both dev and build
// @see https://github.com/vitejs/vite/blob/cad27ee8c00bbd5aeeb2be9bfb3eb164c1b77885/packages/vite/src/node/plugins/clientInjections.ts#L57-L64
const replacements = ['process.env']
if (isBuild) {
replacements.push('import.meta', ...Object.keys(userDefines))
}
return new RegExp(
`\\b(${replacements
.map((key) => key.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'))
.join('|')})`,
'g'
)
}
/**
* To avoid env variables being replaced by vite:
* - insert `'\u200b'` char into those strings inside js string (page data)
* - insert `<wbr>` tag into those strings inside html string (vue template)
*
* @see https://vitejs.dev/guide/env-and-mode.html#production-replacement
*/
function replaceConstants(str: string, replaceRegex: RegExp, breaker: string) {
return str.replace(replaceRegex, (_) => `${_[0]}${breaker}${_.slice(1)}`)
}
function genPageDataCode(tags: string[], data: PageData) { function genPageDataCode(tags: string[], data: PageData, replaceRegex: RegExp) {
const dataJson = JSON.stringify(data)
const code = `\nexport const __pageData = JSON.parse(${JSON.stringify( const code = `\nexport const __pageData = JSON.parse(${JSON.stringify(
JSON.stringify(data) replaceConstants(dataJson, replaceRegex, jsStringBreaker)
)})` )})`
const existingScriptIndex = tags.findIndex((tag) => { const existingScriptIndex = tags.findIndex((tag) => {

@ -44,7 +44,8 @@ export async function createVitePressPlugin(
site, site,
vue: userVuePluginOptions, vue: userVuePluginOptions,
vite: userViteConfig, vite: userViteConfig,
pages pages,
ignoreDeadLinks
} = siteConfig } = siteConfig
let markdownToVue: Awaited<ReturnType<typeof createMarkdownToVueRenderFn>> let markdownToVue: Awaited<ReturnType<typeof createMarkdownToVueRenderFn>>
@ -153,7 +154,7 @@ export async function createVitePressPlugin(
}, },
renderStart() { renderStart() {
if (hasDeadLinks) { if (hasDeadLinks && !ignoreDeadLinks) {
throw new Error(`One or more pages contain dead links.`) throw new Error(`One or more pages contain dead links.`)
} }
}, },

@ -16,6 +16,7 @@ function trimChar(str: string, char: string) {
} }
export interface ServeOptions { export interface ServeOptions {
base?: string
root?: string root?: string
port?: number port?: number
} }
@ -23,7 +24,7 @@ export interface ServeOptions {
export async function serve(options: ServeOptions = {}) { export async function serve(options: ServeOptions = {}) {
const port = options.port !== undefined ? options.port : 5000 const port = options.port !== undefined ? options.port : 5000
const site = await resolveConfig(options.root, 'serve', 'production') const site = await resolveConfig(options.root, 'serve', 'production')
const base = trimChar(site?.site?.base ?? '', '/') const base = trimChar(options?.base ?? site?.site?.base ?? '', '/')
const compress = compression() const compress = compression()
const serve = sirv(site.outDir, { const serve = sirv(site.outDir, {

@ -8,6 +8,11 @@ export async function createServer(
) { ) {
const config = await resolveConfig(root) const config = await resolveConfig(root)
if (serverOptions.base) {
config.site.base = serverOptions.base
delete serverOptions.base
}
return createViteServer({ return createViteServer({
root: config.srcDir, root: config.srcDir,
base: config.site.base, base: config.site.base,

4
theme.d.ts vendored

@ -5,6 +5,10 @@ export const VPHomeHero = ComponentOptions
export const VPHomeFeatures = ComponentOptions export const VPHomeFeatures = ComponentOptions
export const VPHomeSponsors = ComponentOptions export const VPHomeSponsors = ComponentOptions
export const VPDocAsideSponsors = ComponentOptions export const VPDocAsideSponsors = ComponentOptions
export const VPTeamPage = ComponentOptions
export const VPTeamPageTitle = ComponentOptions
export const VPTeamPageSection = ComponentOptions
export const VPTeamMembers = ComponentOptions
declare const theme: { declare const theme: {
Layout: ComponentOptions Layout: ComponentOptions

@ -43,6 +43,11 @@ export namespace DefaultTheme {
*/ */
lastUpdatedText?: string lastUpdatedText?: string
/**
* Set custom prev/next labels.
*/
docFooter?: DocFooter
/** /**
* The social links to be displayed at the end of the nav bar. Perfect for * The social links to be displayed at the end of the nav bar. Perfect for
* placing links to social services such as GitHub, Twitter, Facebook, etc. * placing links to social services such as GitHub, Twitter, Facebook, etc.
@ -94,6 +99,12 @@ export namespace DefaultTheme {
export interface NavItemWithChildren { export interface NavItemWithChildren {
text?: string text?: string
items: (NavItemChildren | NavItemWithLink)[] items: (NavItemChildren | NavItemWithLink)[]
/**
* `activeMatch` is expected to be a regex string. We can't use actual
* RegExp object here because it isn't serializable
*/
activeMatch?: string
} }
// image ----------------------------------------------------------------------- // image -----------------------------------------------------------------------
@ -151,6 +162,24 @@ export namespace DefaultTheme {
text?: string text?: string
} }
// prev-next -----------------------------------------------------------------
export interface DocFooter {
/**
* Custom label for previous page button.
*
* @default 'Previous page'
*/
prev?: string
/**
* Custom label for next page button.
*
* @default 'Next page'
*/
next?: string
}
// social link --------------------------------------------------------------- // social link ---------------------------------------------------------------
export interface SocialLink { export interface SocialLink {
@ -175,6 +204,19 @@ export namespace DefaultTheme {
copyright?: string copyright?: string
} }
// team ----------------------------------------------------------------------
export interface TeamMember {
avatar: string
name: string
title?: string
org?: string
orgLink?: string
desc?: string
links?: SocialLink[]
sponsor?: string
}
// locales ------------------------------------------------------------------- // locales -------------------------------------------------------------------
export interface LocaleLinks { export interface LocaleLinks {
@ -201,6 +243,7 @@ export namespace DefaultTheme {
searchParameters?: any searchParameters?: any
disableUserPersonalization?: boolean disableUserPersonalization?: boolean
initialQuery?: string initialQuery?: string
buttonText?: string
} }
// carbon ads ---------------------------------------------------------------- // carbon ads ----------------------------------------------------------------

Loading…
Cancel
Save