Merge branch 'main' into chore/docs-add-externalIcon

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

@ -14,6 +14,7 @@ concurrency:
jobs:
action:
if: github.repository == 'vuejs/vitepress'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4

@ -7,6 +7,7 @@ on:
jobs:
release:
if: github.repository == 'vuejs/vitepress'
runs-on: ubuntu-latest
steps:

@ -1,3 +1,9 @@
# [1.0.0-beta.5](https://github.com/vuejs/vitepress/compare/v1.0.0-beta.4...v1.0.0-beta.5) (2023-07-03)
### Bug Fixes
- **types:** `Sidebar` was exported multiple times breaking the config ([#2573](https://github.com/vuejs/vitepress/issues/2573)) ([a99dcf9](https://github.com/vuejs/vitepress/commit/a99dcf94436d6cbbd53ef5481a6ec5ffd8d887d2))
# [1.0.0-beta.4](https://github.com/vuejs/vitepress/compare/v1.0.0-beta.3...v1.0.0-beta.4) (2023-07-02)
### Bug Fixes

@ -65,7 +65,7 @@ describe('Table of Contents', () => {
test('render toc', async () => {
const items = page.locator('#table-of-contents + nav ul li')
const count = await items.count()
expect(count).toBe(33)
expect(count).toBe(35)
})
})
@ -242,6 +242,15 @@ describe('Markdown File Inclusion', () => {
expect(await h1.getAttribute('id')).toBe('foo-1')
})
test('render markdown using nested inclusion inside sub folder', async () => {
const h1 = page.locator('#after-foo + h1')
expect(await h1.getAttribute('id')).toBe('inside-sub-folder')
const h2 = page.locator('#after-foo + h1 + h2')
expect(await h2.getAttribute('id')).toBe('sub-sub')
const h3 = page.locator('#after-foo + h1 + h2 + h3')
expect(await h3.getAttribute('id')).toBe('sub-sub-sub')
})
test('support selecting range', async () => {
const h2 = page.locator('#markdown-file-inclusion-with-range + h2')
expect(trim(await h2.textContent())).toBe('Region')

@ -1,3 +1,5 @@
<!--@include: ./foo.md-->
### After Foo
<!--@include: ./subfolder/inside-subfolder.md-->

@ -0,0 +1,3 @@
# Inside sub folder
<!--@include: ./subsub/subsub.md-->

@ -0,0 +1,3 @@
## Sub sub
<!--@include: ./subsubsub/subsubsub.md-->

@ -119,66 +119,87 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
### GitHub Pages
1. In your theme config file, `docs/.vitepress/config.js`, set the `base` property to the name of your GitHub repository. 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.
2. Create a file named `deploy.yml` inside `.github/workflows` directory of your project with the following content:
1. Create a file named `deploy.yml` inside `.github/workflows` directory of your project with some content like this:
```yaml
name: Deploy
# Sample workflow for building and deploying a VitePress site to GitHub Pages
#
name: Deploy VitePress site to Pages
on:
workflow_dispatch: {}
# Runs on pushes targeting the `main` branch. Change this to `master` if you're
# using the `master` branch as the default branch.
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
branches: [main]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: pages
cancel-in-progress: false
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
fetch-depth: 0 # Not needed if lastUpdated is not enabled
# - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
cache: npm
- run: npm ci
- name: Build
run: npm run docs:build
- uses: actions/configure-pages@v2
- uses: actions/upload-pages-artifact@v1
node-version: 18
cache: npm # or pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v3
- name: Install dependencies
run: npm ci # or pnpm install / yarn install
- name: Build with VitePress
run: npm run docs:build # or pnpm docs:build / yarn docs:build
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
path: docs/.vitepress/dist
- name: Deploy
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
name: Deploy
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
uses: actions/deploy-pages@v2
```
::: tip
Please replace the corresponding branch name. For example, if the branch you want to build is `master`, then you should replace `main` with `master` in the above file.
::: warning
Make sure the `base` option in your VitePress is properly configured. See [Setting a Public Base Path](#setting-a-public-base-path) for more details.
:::
3. In your repository's Settings under Pages menu item, select `GitHub Actions` in Build and deployment's Source.
2. In your repository's settings under "Pages" menu item, select "GitHub Actions" in "Build and deployment > Source".
4. Now commit your code and push it to the `main` branch.
5. Wait for actions to complete.
6. In your repository's Settings under Pages menu item, click `Visit site`, then you can see your site. Your docs will automatically deploy each time you push.
3. Push your changes to the `main` branch and wait for the GitHub Actions workflow to complete. You should see your site deployed to `https://<username>.github.io/[repository]/` or `https://<custom-domain>/` depending on your settings. Your site will automatically be deployed on every push to the `main` branch.
### GitLab Pages
1. Set `outDir` in `docs/.vitepress/config.js` to `../public`.
2. Still in your config file, `docs/.vitepress/config.js`, set the `base` property to the name of your GitLab repository. If you plan to deploy your site to `https://foo.gitlab.io/bar/`, then you should set base to `'/bar/'`. It should always start and end with a slash.
1. Set `outDir` in VitePress config to `../public`. Configure `base` option to `'/<repository>/'` if you want to deploy to `https://<username>.gitlab.io/<repository>/`.
3. Create a file called `.gitlab-ci.yml` in the root of your project with the content below. This will build and deploy your site whenever you make changes to your content:
2. Create a file named `.gitlab-ci.yml` in the root of your project with the content below. This will build and deploy your site whenever you make changes to your content:
```yaml
image: node:16
@ -187,25 +208,7 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
paths:
- node_modules/
script:
- npm install
- npm run docs:build
artifacts:
paths:
- public
only:
- main
```
4. Alternatively, if you want to use an _alpine_ version of node, you have to install `git` manually. In that case, the code above modifies to this:
```yaml
image: node:16-alpine
pages:
cache:
paths:
- node_modules/
before_script:
- apk add git
script:
# - apk add git # Uncomment this if you're using small docker images like alpine and have lastUpdated enabled
- npm install
- npm run docs:build
artifacts:

@ -12,7 +12,7 @@ Just want to try it out? Skip to the [Quickstart](./getting-started).
- **Documentation**
VitePress ships with a default theme designed for technical documentation, especially those that need to embed interactive demos. It powers this page you are reading right now, along with the documentation for [Vite](https://vitejs.dev/), [Pinia](https://pinia.vuejs.org/), [VueUse](https://vueuse.org/), [Rollup](https://rollupjs.org/), [Mermaid](https://mermaid.js.org/), [Wikimedia Codex](https://doc.wikimedia.org/codex/latest/), and many more.
VitePress ships with a default theme designed for technical documentation. It powers this page you are reading right now, along with the documentation for [Vite](https://vitejs.dev/), [Rollup](https://rollupjs.org/), [Pinia](https://pinia.vuejs.org/), [VueUse](https://vueuse.org/), [Vitest](https://vitest.dev/), [D3](https://d3js.org/), [UnoCSS](https://unocss.dev/), [Iconify](https://iconify.design/) and [many more](https://www.vuetelescope.com/explore?framework.slug=vitepress).
The [official Vue.js documentation](https://vuejs.org/) is also based on VitePress, but uses a custom theme shared between multiple translations.

@ -11,7 +11,7 @@ hero:
actions:
- theme: brand
text: Get Started
link: /guide/what-is-vitepress
link: /guide/getting-started
- theme: alt
text: View on GitHub
link: https://github.com/vuejs/vitepress
@ -20,10 +20,10 @@ features:
- icon: 📝
title: Focus on Your Content
details: Effortlessly create beautiful documentation sites with just markdown.
- icon: <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><g fill="none"><path fill="url(#a)" d="m29.884 6.146-13.142 23.5a.714.714 0 0 1-1.244.005L2.096 6.148a.714.714 0 0 1 .746-1.057l13.156 2.352a.714.714 0 0 0 .253 0l12.881-2.348a.714.714 0 0 1 .752 1.05z"/><path fill="url(#b)" d="M22.264 2.007 12.54 3.912a.357.357 0 0 0-.288.33l-.598 10.104a.357.357 0 0 0 .437.369l2.707-.625a.357.357 0 0 1 .43.42l-.804 3.939a.357.357 0 0 0 .454.413l1.672-.508a.357.357 0 0 1 .454.414l-1.279 6.187c-.08.387.435.598.65.267l.143-.222 7.925-15.815a.357.357 0 0 0-.387-.51l-2.787.537a.357.357 0 0 1-.41-.45l1.818-6.306a.357.357 0 0 0-.412-.45z"/><defs><linearGradient id="a" x1="6" x2="235" y1="33" y2="344" gradientTransform="translate(1.34 1.894) scale(.07142)" gradientUnits="userSpaceOnUse"><stop stop-color="#41D1FF"/><stop offset="1" stop-color="#BD34FE"/></linearGradient><linearGradient id="b" x1="194.651" x2="236.076" y1="8.818" y2="292.989" gradientTransform="translate(1.34 1.894) scale(.07142)" gradientUnits="userSpaceOnUse"><stop stop-color="#FFEA83"/><stop offset=".083" stop-color="#FFDD35"/><stop offset="1" stop-color="#FFA800"/></linearGradient></defs></g></svg>
- icon: <svg xmlns="http://www.w3.org/2000/svg" width="30" viewBox="0 0 256 256.32"><defs><linearGradient id="a" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"/><stop offset="100%" stop-color="#BD34FE"/></linearGradient><linearGradient id="b" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"/><stop offset="8.333%" stop-color="#FFDD35"/><stop offset="100%" stop-color="#FFA800"/></linearGradient></defs><path fill="url(#a)" d="M255.153 37.938 134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"/><path fill="url(#b)" d="M185.432.063 96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028 72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"/></svg>
title: Enjoy the Vite DX
details: Instant server start, lightning fast hot updates, and leverage Vite ecosystem plugins.
- icon: <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path fill="#41b883" d="M24.4 3.925H30l-14 24.15L2 3.925h10.71l3.29 5.6 3.22-5.6Z"/><path fill="#41b883" d="m2 3.925 14 24.15 14-24.15h-5.6L16 18.415 7.53 3.925Z"/><path fill="#35495e" d="M7.53 3.925 16 18.485l8.4-14.56h-5.18L16 9.525l-3.29-5.6Z"/></svg>
- icon: <svg xmlns="http://www.w3.org/2000/svg" width="30" viewBox="0 0 256 220.8"><path fill="#41B883" d="M204.8 0H256L128 220.8 0 0h97.92L128 51.2 157.44 0h47.36Z"/><path fill="#41B883" d="m0 0 128 220.8L256 0h-51.2L128 132.48 50.56 0H0Z"/><path fill="#35495E" d="M50.56 0 128 133.12 204.8 0h-47.36L128 51.2 97.92 0H50.56Z"/></svg>
title: Customize with Vue
details: Use Vue syntax and components directly in markdown, or build custom themes with Vue.
- icon: 🚀

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<title>Plain HTML page | VitePress</title>

@ -2,6 +2,10 @@
The update time of the last content will be displayed in the lower right corner of the page. To enable it, add `lastUpdated` options to your config.
::: tip
You need to commit the markdown file to see the updated time.
:::
## Site-Level Config
```js

@ -1,3 +1,7 @@
---
outline: deep
---
# Search
## Local Search
@ -58,6 +62,42 @@ export default defineConfig({
})
```
### miniSearch options
You can configure MiniSearch like this:
```ts
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'local',
options: {
miniSearch: {
/**
* @type {Pick<import('minisearch').Options, 'extractField' | 'tokenize' | 'processTerm'>}
*/
options: {
/* ... */
},
/**
* @type {import('minisearch').SearchOptions}
* @default
* { fuzzy: 0.2, prefix: true, boost: { title: 4, text: 2, titles: 1 } }
*/
searchOptions: {
/* ... */
}
}
}
}
}
})
```
Learn more in [MiniSearch docs](https://lucaong.github.io/minisearch/classes/_minisearch_.minisearch.html).
## Algolia Search
VitePress supports searching your docs site using [Algolia DocSearch](https://docsearch.algolia.com/docs/what-is-docsearch). Refer their getting started guide. In your `.vitepress/config.ts` you'll need to provide at least the following to make it work:
@ -145,6 +185,114 @@ export default defineConfig({
[These options](https://github.com/vuejs/vitepress/blob/main/types/docsearch.d.ts) can be overridden. Refer official Algolia docs to learn more about them.
### Crawler Config
Here is an example config based on what this site uses:
```ts
new Crawler({
appId: '...',
apiKey: '...',
rateLimit: 8,
startUrls: ['https://vitepress.dev/'],
renderJavaScript: false,
sitemaps: [],
exclusionPatterns: [],
ignoreCanonicalTo: false,
discoveryPatterns: ['https://vitepress.dev/**'],
schedule: 'at 05:10 on Saturday',
actions: [
{
indexName: 'vitepress',
pathsToMatch: ['https://vitepress.dev/**'],
recordExtractor: ({ $, helpers }) => {
return helpers.docsearch({
recordProps: {
lvl1: '.content h1',
content: '.content p, .content li',
lvl0: {
selectors: '',
defaultValue: 'Documentation'
},
lvl2: '.content h2',
lvl3: '.content h3',
lvl4: '.content h4',
lvl5: '.content h5'
},
indexHeadings: true
})
}
}
],
initialIndexSettings: {
vitepress: {
attributesForFaceting: ['type', 'lang'],
attributesToRetrieve: ['hierarchy', 'content', 'anchor', 'url'],
attributesToHighlight: ['hierarchy', 'hierarchy_camel', 'content'],
attributesToSnippet: ['content:10'],
camelCaseAttributes: ['hierarchy', 'hierarchy_radio', 'content'],
searchableAttributes: [
'unordered(hierarchy_radio_camel.lvl0)',
'unordered(hierarchy_radio.lvl0)',
'unordered(hierarchy_radio_camel.lvl1)',
'unordered(hierarchy_radio.lvl1)',
'unordered(hierarchy_radio_camel.lvl2)',
'unordered(hierarchy_radio.lvl2)',
'unordered(hierarchy_radio_camel.lvl3)',
'unordered(hierarchy_radio.lvl3)',
'unordered(hierarchy_radio_camel.lvl4)',
'unordered(hierarchy_radio.lvl4)',
'unordered(hierarchy_radio_camel.lvl5)',
'unordered(hierarchy_radio.lvl5)',
'unordered(hierarchy_radio_camel.lvl6)',
'unordered(hierarchy_radio.lvl6)',
'unordered(hierarchy_camel.lvl0)',
'unordered(hierarchy.lvl0)',
'unordered(hierarchy_camel.lvl1)',
'unordered(hierarchy.lvl1)',
'unordered(hierarchy_camel.lvl2)',
'unordered(hierarchy.lvl2)',
'unordered(hierarchy_camel.lvl3)',
'unordered(hierarchy.lvl3)',
'unordered(hierarchy_camel.lvl4)',
'unordered(hierarchy.lvl4)',
'unordered(hierarchy_camel.lvl5)',
'unordered(hierarchy.lvl5)',
'unordered(hierarchy_camel.lvl6)',
'unordered(hierarchy.lvl6)',
'content'
],
distinct: true,
attributeForDistinct: 'url',
customRanking: [
'desc(weight.pageRank)',
'desc(weight.level)',
'asc(weight.position)'
],
ranking: [
'words',
'filters',
'typo',
'attribute',
'proximity',
'exact',
'custom'
],
highlightPreTag: '<span class="algolia-docsearch-suggestion--highlight">',
highlightPostTag: '</span>',
minWordSizefor1Typo: 3,
minWordSizefor2Typos: 7,
allowTyposOnNumericTokens: false,
minProximity: 1,
ignorePlurals: true,
advancedSyntax: true,
attributeCriteriaComputedByMinProximity: true,
removeWordsIfNoResults: 'allOptional'
}
}
})
```
<style>
img[src="/search.png"] {
width: 100%;

@ -186,7 +186,7 @@ export default {
Returns sidebar-related data. The returned object has the following type:
```ts
export interface Sidebar {
export interface DocSidebar {
isOpen: Ref<boolean>
sidebar: ComputedRef<DefaultTheme.SidebarItem[]>
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>

@ -290,6 +290,19 @@ export default {
}
```
### assetsDir
- Type: `string`
- Default: `assets`
The directory for assets files. See also: [assetsDir](https://vitejs.dev/config/build-options.html#build-assetsdir).
```ts
export default {
assetsDir: 'static'
}
```
### cacheDir
- Type: `string`

@ -1,9 +1,9 @@
{
"name": "vitepress",
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"description": "Vite & Vue powered static site generator",
"type": "module",
"packageManager": "pnpm@8.6.5",
"packageManager": "pnpm@8.6.9",
"main": "dist/node/index.js",
"types": "types/index.d.ts",
"exports": {
@ -96,11 +96,11 @@
"@vueuse/core": "^10.2.1",
"@vueuse/integrations": "^10.2.1",
"body-scroll-lock": "4.0.0-beta.0",
"focus-trap": "^7.4.3",
"focus-trap": "^7.5.2",
"mark.js": "8.11.1",
"minisearch": "^6.1.0",
"shiki": "^0.14.3",
"vite": "4.4.0-beta.3",
"vite": "^4.4.4",
"vue": "^3.3.4"
},
"devDependencies": {
@ -113,7 +113,7 @@
"@mdit-vue/plugin-toc": "^0.12.0",
"@mdit-vue/shared": "^0.12.0",
"@rollup/plugin-alias": "^5.0.0",
"@rollup/plugin-commonjs": "^25.0.2",
"@rollup/plugin-commonjs": "^25.0.3",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.1.0",
"@rollup/plugin-replace": "^5.0.2",
@ -130,14 +130,15 @@
"@types/markdown-it-emoji": "^2.0.2",
"@types/micromatch": "^4.0.2",
"@types/minimist": "^1.2.2",
"@types/node": "^20.3.3",
"@types/node": "^20.4.2",
"@types/prompts": "^2.4.4",
"@vue/shared": "^3.3.4",
"chokidar": "^3.5.3",
"compression": "^1.7.4",
"conventional-changelog-cli": "^2",
"cross-spawn": "^7.0.3",
"debug": "^4.3.4",
"esbuild": "^0.18.11",
"esbuild": "^0.18.14",
"escape-html": "^1.0.3",
"execa": "^7.1.1",
"fast-glob": "^3.3.0",
@ -160,23 +161,23 @@
"path-to-regexp": "^6.2.1",
"picocolors": "^1.0.0",
"pkg-dir": "^7.0.0",
"playwright-chromium": "^1.35.1",
"playwright-chromium": "^1.36.1",
"polka": "1.0.0-next.22",
"prettier": "^2.8.8",
"prettier": "^3.0.0",
"prompts": "^2.4.2",
"punycode": "^2.3.0",
"rimraf": "^5.0.1",
"rollup": "^3.26.0",
"rollup": "^3.26.3",
"rollup-plugin-dts": "^5.3.0",
"rollup-plugin-esbuild": "^5.0.0",
"semver": "^7.5.3",
"semver": "^7.5.4",
"shiki-processor": "^0.1.3",
"simple-git-hooks": "^2.8.1",
"sirv": "^2.0.3",
"supports-color": "^9.4.0",
"typescript": "^5.1.6",
"vitest": "^0.32.2",
"vue-tsc": "^1.8.3",
"vitest": "^0.33.0",
"vue-tsc": "^1.8.5",
"wait-on": "^7.0.1"
},
"simple-git-hooks": {

File diff suppressed because it is too large Load Diff

@ -37,9 +37,15 @@ export function useUpdateHead(route: Route, siteDataByRouteRef: Ref<SiteData>) {
// update title and description
document.title = createTitle(siteData, pageData)
document
.querySelector(`meta[name=description]`)!
.setAttribute('content', pageDescription || siteData.description)
const description = pageDescription || siteData.description
let metaDescriptionElement = document.querySelector(
`meta[name=description]`
)
if (metaDescriptionElement) {
metaDescriptionElement.setAttribute('content', description)
} else {
createHeadElement(['meta', { name: 'description', content: description }])
}
updateHeadTags(
mergeHead(siteData.head, filterOutHeadDescription(frontmatterHead))

@ -58,7 +58,10 @@ export function pathToFile(path: string) {
pageHash = __VP_HASH_MAP__[pagePath.toLowerCase()]
}
if (!pageHash) return null
pagePath = `${base}assets/${pagePath}.${pageHash}.js`
pagePath = `${base}${__ASSETS_DIR__.replace(
/"(.+)"/,
'$1'
)}/${pagePath}.${pageHash}.js`
} else {
// ssr build uses much simpler name mapping
pagePath = `./${sanitizeFileName(

@ -3,6 +3,7 @@ declare const __VP_LOCAL_SEARCH__: boolean
declare const __ALGOLIA__: boolean
declare const __CARBON__: boolean
declare const __VUE_PROD_DEVTOOLS__: boolean
declare const __ASSETS_DIR__: string
declare module '*.vue' {
import type { DefineComponent } from 'vue'

@ -1,6 +1,5 @@
<script setup lang="ts">
import { useRoute } from 'vitepress'
import { useSidebar } from 'vitepress/theme'
import { computed, provide, useSlots, watch } from 'vue'
import VPBackdrop from './components/VPBackdrop.vue'
import VPContent from './components/VPContent.vue'
@ -10,7 +9,7 @@ import VPNav from './components/VPNav.vue'
import VPSidebar from './components/VPSidebar.vue'
import VPSkipLink from './components/VPSkipLink.vue'
import { useData } from './composables/data'
import { useCloseSidebarOnEscape } from './composables/sidebar'
import { useCloseSidebarOnEscape, useSidebar } from './composables/sidebar'
const {
isOpen: isSidebarOpen,

@ -1,7 +1,7 @@
<script setup lang="ts">
import { useSidebar } from 'vitepress/theme'
import NotFound from '../NotFound.vue'
import { useData } from '../composables/data'
import { useSidebar } from '../composables/sidebar'
import VPDoc from './VPDoc.vue'
import VPHome from './VPHome.vue'
import VPPage from './VPPage.vue'

@ -1,8 +1,8 @@
<script setup lang="ts">
import { useRoute } from 'vitepress'
import { useSidebar } from 'vitepress/theme'
import { computed } from 'vue'
import { useData } from '../composables/data'
import { useSidebar } from '../composables/sidebar'
import VPDocAside from './VPDocAside.vue'
import VPDocFooter from './VPDocFooter.vue'
import VPDocOutlineDropdown from './VPDocOutlineDropdown.vue'
@ -205,5 +205,6 @@ const pageName = computed(() =>
.external-link-icon-enabled :is(.vp-doc a[href*='://'], .vp-doc a[target='_blank'])::after {
content: '';
color: currentColor;
}
</style>

@ -41,20 +41,20 @@ const showFooter = computed(() => {
</div>
</div>
<div v-if="control.prev?.link || control.next?.link" class="prev-next">
<nav v-if="control.prev?.link || control.next?.link" class="prev-next">
<div class="pager">
<a v-if="control.prev?.link" class="pager-link prev" :href="normalizeLink(control.prev.link)">
<span class="desc" v-html="theme.docFooter?.prev || 'Previous page'"></span>
<span class="title" v-html="control.prev.text"></span>
</a>
</div>
<div class="pager" :class="{ 'has-prev': control.prev?.link }">
<div class="pager">
<a v-if="control.next?.link" class="pager-link next" :href="normalizeLink(control.next.link)">
<span class="desc" v-html="theme.docFooter?.next || 'Next page'"></span>
<span class="title" v-html="control.next.text"></span>
</a>
</div>
</div>
</nav>
</footer>
</template>
@ -101,29 +101,14 @@ const showFooter = computed(() => {
.prev-next {
border-top: 1px solid var(--vp-c-divider);
padding-top: 24px;
display: grid;
grid-row-gap: 8px;
}
@media (min-width: 640px) {
.prev-next {
display: flex;
}
}
.pager.has-prev {
padding-top: 8px;
}
@media (min-width: 640px) {
.pager {
display: flex;
flex-direction: column;
flex-shrink: 0;
width: 50%;
}
.pager.has-prev {
padding-top: 0;
padding-left: 16px;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 16px;
}
}

@ -14,7 +14,7 @@ defineProps<{
</script>
<template>
<VPLink class="VPFeature" :href="link" :no-icon="true">
<VPLink class="VPFeature" :href="link" :no-icon="true" :tag="link ? 'a' : 'div'">
<article class="box">
<VPImage
v-if="typeof icon === 'object'"

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useSidebar } from 'vitepress/theme'
import { useData } from '../composables/data'
import { useSidebar } from '../composables/sidebar'
const { theme } = useData()
const { hasSidebar } = useSidebar()

@ -27,10 +27,10 @@ const heroImageSlotExists = inject('hero-image-slot-exists') as Ref<boolean>
<div class="main">
<slot name="home-hero-info">
<h1 v-if="name" class="name">
<span class="clip">{{ name }}</span>
<span v-html="name" class="clip"></span>
</h1>
<p v-if="text" class="text">{{ text }}</p>
<p v-if="tagline" class="tagline">{{ tagline }}</p>
<p v-if="text" v-html="text" class="text"></p>
<p v-if="tagline" v-html="tagline" class="tagline"></p>
</slot>
<div v-if="actions" class="actions">

@ -19,7 +19,11 @@ const isExternal = computed(() => props.href && EXTERNAL_URL_RE.test(props.href)
<component
:is="tag"
class="VPLink"
:class="{ link: href, 'vp-external-link-icon': isExternal && !noIcon }"
:class="{
link: href,
'vp-external-link-icon': isExternal,
'no-icon': noIcon
}"
:href="href ? normalizeLink(href) : undefined"
:target="target || (isExternal ? '_blank' : undefined)"
:rel="rel || (isExternal ? 'noreferrer' : undefined)"

@ -1,9 +1,9 @@
<script lang="ts" setup>
import { useWindowScroll } from '@vueuse/core'
import { onContentUpdated } from 'vitepress'
import { useSidebar } from 'vitepress/theme'
import { computed, shallowRef } from 'vue'
import { useData } from '../composables/data'
import { useSidebar } from '../composables/sidebar'
import { getHeaders, type MenuItem } from '../composables/outline'
import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue'
import VPIconAlignLeft from './icons/VPIconAlignLeft.vue'

@ -80,8 +80,12 @@ const searchIndex = computedAsync(async () =>
searchOptions: {
fuzzy: 0.2,
prefix: true,
boost: { title: 4, text: 2, titles: 1 }
}
boost: { title: 4, text: 2, titles: 1 },
...(theme.value.search?.provider === 'local' &&
theme.value.search.options?.miniSearch?.searchOptions)
},
...(theme.value.search?.provider === 'local' &&
theme.value.search.options?.miniSearch?.options)
}
)
)
@ -396,8 +400,16 @@ function formMarkRegex(terms: Set<string>) {
<div class="backdrop" @click="$emit('close')" />
<div class="shell">
<form class="search-bar" @pointerup="onSearchBarClick($event)" @submit.prevent="">
<label :title="placeholder" id="localsearch-label" for="localsearch-input">
<form
class="search-bar"
@pointerup="onSearchBarClick($event)"
@submit.prevent=""
>
<label
:title="placeholder"
id="localsearch-label"
for="localsearch-input"
>
<svg
class="search-icon"
width="18"
@ -454,7 +466,9 @@ function formMarkRegex(terms: Set<string>) {
class="toggle-layout-button"
:class="{ 'detailed-list': showDetailedList }"
:title="$t('modal.displayDetails')"
@click="selectedIndex > -1 && (showDetailedList = !showDetailedList)"
@click="
selectedIndex > -1 && (showDetailedList = !showDetailedList)
"
>
<svg
width="18"
@ -527,7 +541,11 @@ function formMarkRegex(terms: Set<string>) {
<div>
<div class="titles">
<span class="title-icon">#</span>
<span v-for="(t, index) in p.titles" :key="index" class="title">
<span
v-for="(t, index) in p.titles"
:key="index"
class="title"
>
<span class="text" v-html="t" />
<svg width="18" height="18" viewBox="0 0 24 24">
<path

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { useWindowScroll } from '@vueuse/core'
import { useSidebar } from 'vitepress/theme'
import { computed } from 'vue'
import { useSidebar } from '../composables/sidebar'
import VPNavBarAppearance from './VPNavBarAppearance.vue'
import VPNavBarExtra from './VPNavBarExtra.vue'
import VPNavBarHamburger from './VPNavBarHamburger.vue'

@ -1,7 +1,7 @@
<script setup lang="ts">
import { useSidebar } from 'vitepress/theme'
import { useData } from '../composables/data'
import { useLangs } from '../composables/langs'
import { useSidebar } from '../composables/sidebar'
import { normalizeLink } from '../support/utils'
import VPImage from './VPImage.vue'
@ -47,6 +47,6 @@ const { currentLang } = useLangs()
:deep(.logo) {
margin-right: 8px;
height: 24px;
height: var(--vp-nav-logo-height);
}
</style>

@ -27,7 +27,7 @@ function unlockBodyScroll() {
@enter="lockBodyScroll"
@after-leave="unlockBodyScroll"
>
<div v-if="open" class="VPNavScreen" ref="screen">
<div v-if="open" class="VPNavScreen" ref="screen" id="VPNavScreen">
<div class="container">
<slot name="nav-screen-content-before" />
<VPNavScreenMenu class="menu" />

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { clearAllBodyScrollLocks, disableBodyScroll } from 'body-scroll-lock'
import { useSidebar } from 'vitepress/theme'
import { ref, watchPostEffect } from 'vue'
import { useSidebar } from '../composables/sidebar'
import VPSidebarItem from './VPSidebarItem.vue'
const { sidebarGroups, hasSidebar } = useSidebar()

@ -193,6 +193,7 @@ function onCaretClick() {
color: var(--vp-c-text-3);
cursor: pointer;
transition: color 0.25s;
flex-shrink: 0;
}
.item:hover .caret {

@ -17,7 +17,7 @@ const svg = computed(() => {
<template>
<a
class="VPSocialLink"
class="VPSocialLink no-icon"
:href="link"
:aria-label="ariaLabel ?? (typeof icon === 'string' ? icon : '')"
target="_blank"

@ -1,6 +1,6 @@
import { useMediaQuery } from '@vueuse/core'
import { useSidebar } from 'vitepress/theme'
import { computed } from 'vue'
import { useSidebar } from './sidebar'
export function useAside() {
const { hasSidebar } = useSidebar()

@ -42,7 +42,9 @@
font-weight: 500;
user-select: none;
opacity: 0;
transition: color 0.25s, opacity 0.25s;
transition:
color 0.25s,
opacity 0.25s;
}
.vp-doc .header-anchor:before {
@ -245,7 +247,9 @@
padding: 3px 6px;
color: var(--vp-c-text-code);
background-color: var(--vp-c-mute);
transition: color 0.5s, background-color 0.5s;
transition:
color 0.5s,
background-color 0.5s;
}
.vp-doc h1 > code,
@ -362,12 +366,16 @@
.vp-doc [class*='language-'] .has-focused-lines .line:not(.has-focus) {
filter: blur(0.095rem);
opacity: 0.4;
transition: filter 0.35s, opacity 0.35s;
transition:
filter 0.35s,
opacity 0.35s;
}
.vp-doc [class*='language-'] .has-focused-lines .line:not(.has-focus) {
opacity: 0.7;
transition: filter 0.35s, opacity 0.35s;
transition:
filter 0.35s,
opacity 0.35s;
}
.vp-doc [class*='language-']:hover .has-focused-lines .line:not(.has-focus) {
@ -415,7 +423,9 @@
line-height: var(--vp-code-line-height);
font-size: var(--vp-code-font-size);
color: var(--vp-code-line-number-color);
transition: border-color 0.5s, color 0.5s;
transition:
border-color 0.5s,
color 0.5s;
}
.vp-doc [class*='language-'] > button.copy {
@ -437,7 +447,10 @@
background-position: 50%;
background-size: 20px;
background-repeat: no-repeat;
transition: border-color 0.25s, background-color 0.25s, opacity 0.25s;
transition:
border-color 0.25s,
background-color 0.25s,
opacity 0.25s;
}
.vp-doc [class*='language-']:hover > button.copy,
@ -492,7 +505,9 @@
font-size: 12px;
font-weight: 500;
color: var(--vp-c-code-dimm);
transition: color 0.4s, opacity 0.4s;
transition:
color 0.4s,
opacity 0.4s;
}
.vp-doc [class*='language-']:hover > button.copy + span.lang,
@ -523,17 +538,15 @@
max-width: calc((100% - 24px) / 2) !important;
}
:is(
.vp-external-link-icon,
.vp-doc a[href*='://'],
.vp-doc a[target='_blank']
)::after {
/* prettier-ignore */
:is(.vp-external-link-icon, .vp-doc a[href*='://'], .vp-doc a[target='_blank']):not(.no-icon)::after {
display: inline-block;
margin-top: -1px;
margin-left: 4px;
width: 11px;
height: 11px;
background: currentColor;
color: var(--vp-c-text-3);
flex-shrink: 0;
--icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");
-webkit-mask-image: var(--icon);

@ -361,6 +361,7 @@
--vp-nav-height: 64px;
--vp-nav-bg-color: var(--vp-c-bg);
--vp-nav-screen-bg-color: var(--vp-c-bg);
--vp-nav-logo-height: 24px;
}
/**

@ -1,3 +1,4 @@
import { createHash } from 'crypto'
import fs from 'fs-extra'
import { createRequire } from 'module'
import ora from 'ora'
@ -7,9 +8,9 @@ import { rimraf } from 'rimraf'
import type { OutputAsset, OutputChunk } from 'rollup'
import { pathToFileURL } from 'url'
import type { BuildOptions } from 'vite'
import { resolveConfig } from '../config'
import type { HeadConfig } from '../shared'
import { serializeFunctions } from '../utils/fnSerialize'
import { resolveConfig, type SiteConfig } from '../config'
import { slash, type HeadConfig } from '../shared'
import { deserializeFunctions, serializeFunctions } from '../utils/fnSerialize'
import { bundle, failMark, okMark } from './bundle'
import { renderPage } from './render'
@ -42,7 +43,7 @@ export async function build(
const entryPath = path.join(siteConfig.tempDir, 'app.js')
const { render } = await import(pathToFileURL(entryPath).toString())
const spinner = ora()
const spinner = ora({ discardStdin: false })
spinner.start('rendering pages...')
try {
@ -56,12 +57,12 @@ export async function build(
) as OutputChunk)
const cssChunk = (
siteConfig.mpa ? serverResult : clientResult
siteConfig.mpa ? serverResult : clientResult!
).output.find(
(chunk) => chunk.type === 'asset' && chunk.fileName.endsWith('.css')
) as OutputAsset
const assets = (siteConfig.mpa ? serverResult : clientResult).output
const assets = (siteConfig.mpa ? serverResult : clientResult!).output
.filter(
(chunk) => chunk.type === 'asset' && !chunk.fileName.endsWith('.css')
)
@ -79,6 +80,8 @@ export async function build(
chunk.moduleIds.some((id) => id.includes('client/theme-default'))
)
const metadataScript = generateMetadataScript(pageToHashMap, siteConfig)
if (isDefaultTheme) {
const fontURL = assets.find((file) =>
/inter-roman-latin\.\w+\.woff2/.test(file)
@ -97,15 +100,6 @@ export async function build(
}
}
// We embed the hash map and site config strings into each page directly
// so that it doesn't alter the main chunk's hash on every build.
// It's also embedded as a string and JSON.parsed from the client because
// it's faster than embedding as JS object literal.
const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap))
const siteDataString = JSON.stringify(
JSON.stringify(serializeFunctions({ ...siteConfig.site, head: [] }))
)
await Promise.all(
['404.md', ...siteConfig.pages]
.map((page) => siteConfig.rewrites.map[page] || page)
@ -119,8 +113,7 @@ export async function build(
cssChunk,
assets,
pageToHashMap,
hashMapString,
siteDataString,
metadataScript,
additionalHeadTags
)
)
@ -168,3 +161,51 @@ function linkVue() {
}
return () => {}
}
function generateMetadataScript(
pageToHashMap: Record<string, string>,
config: SiteConfig
) {
if (config.mpa) {
return { html: '', inHead: false }
}
// We embed the hash map and site config strings into each page directly
// so that it doesn't alter the main chunk's hash on every build.
// It's also embedded as a string and JSON.parsed from the client because
// it's faster than embedding as JS object literal.
const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap))
const siteDataString = JSON.stringify(
JSON.stringify(serializeFunctions({ ...config.site, head: [] }))
)
const metadataContent = `window.__VP_HASH_MAP__=JSON.parse(${hashMapString});${
siteDataString.includes('_vp-fn_')
? `${deserializeFunctions.toString()};window.__VP_SITE_DATA__=deserializeFunctions(JSON.parse(${siteDataString}));`
: `window.__VP_SITE_DATA__=JSON.parse(${siteDataString});`
}`
if (!config.metaChunk) {
return { html: `<script>${metadataContent}</script>`, inHead: false }
}
const metadataFile = path.join(
config.assetsDir,
'chunks',
`metadata.${createHash('sha256')
.update(metadataContent)
.digest('hex')
.slice(0, 8)}.js`
)
const resolvedMetadataFile = path.join(config.outDir, metadataFile)
const metadataFileURL = slash(`${config.site.base}${metadataFile}`)
fs.ensureDirSync(path.dirname(resolvedMetadataFile))
fs.writeFileSync(resolvedMetadataFile, metadataContent)
return {
html: `<script type="module" src="${metadataFileURL}"></script>`,
inHead: true
}
}

@ -31,7 +31,7 @@ export async function bundle(
config: SiteConfig,
options: BuildOptions
): Promise<{
clientResult: RollupOutput
clientResult: RollupOutput | null
serverResult: RollupOutput
pageToHashMap: Record<string, string>
}> {
@ -94,19 +94,19 @@ export async function bundle(
output: {
sanitizeFileName,
...rollupOptions?.output,
assetFileNames: 'assets/[name].[hash].[ext]',
assetFileNames: `${config.assetsDir}/[name].[hash].[ext]`,
...(ssr
? {
entryFileNames: '[name].js',
chunkFileNames: '[name].[hash].js'
}
: {
entryFileNames: 'assets/[name].[hash].js',
entryFileNames: `${config.assetsDir}/[name].[hash].js`,
chunkFileNames(chunk) {
// avoid ads chunk being intercepted by adblock
return /(?:Carbon|BuySell)Ads/.test(chunk.name)
? 'assets/chunks/ui-custom.[hash].js'
: 'assets/chunks/[name].[hash].js'
? `${config.assetsDir}/chunks/ui-custom.[hash].js`
: `${config.assetsDir}/chunks/[name].[hash].js`
},
manualChunks(id, ctx) {
if (lazyDefaultThemeComponentsRE.test(id)) {
@ -142,16 +142,16 @@ export async function bundle(
}
})
let clientResult: RollupOutput
let clientResult: RollupOutput | null
let serverResult: RollupOutput
const spinner = ora()
const spinner = ora({ discardStdin: false })
spinner.start('building client + server bundles...')
try {
;[clientResult, serverResult] = await (Promise.all([
config.mpa ? null : build(await resolveViteConfig(false)),
build(await resolveViteConfig(true))
]) as Promise<[RollupOutput, RollupOutput]>)
clientResult = config.mpa
? null
: ((await build(await resolveViteConfig(false))) as RollupOutput)
serverResult = (await build(await resolveViteConfig(true))) as RollupOutput
} catch (e) {
spinner.stopAndPersist({
symbol: failMark

@ -1,3 +1,4 @@
import { isBooleanAttr } from '@vue/shared'
import escape from 'escape-html'
import fs from 'fs-extra'
import path from 'path'
@ -6,8 +7,8 @@ import { pathToFileURL } from 'url'
import { normalizePath, transformWithEsbuild } from 'vite'
import type { SiteConfig } from '../config'
import {
createTitle,
EXTERNAL_URL_RE,
createTitle,
mergeHead,
notFoundPageData,
resolveSiteDataByRoute,
@ -17,19 +18,17 @@ import {
type PageData,
type SSGContext
} from '../shared'
import { deserializeFunctions } from '../utils/fnSerialize'
export async function renderPage(
render: (path: string) => Promise<SSGContext>,
config: SiteConfig,
page: string, // foo.md
result: RollupOutput | null,
appChunk: OutputChunk | undefined,
cssChunk: OutputAsset | undefined,
appChunk: OutputChunk | null,
cssChunk: OutputAsset | null,
assets: string[],
pageToHashMap: Record<string, string>,
hashMapString: string,
siteDataString: string,
metadataScript: { html: string; inHead: boolean },
additionalHeadTags: HeadConfig[]
) {
const routePath = `/${page.replace(/\.md$/, '')}`
@ -45,7 +44,7 @@ export async function renderPage(
// for any initial page load, we only need the lean version of the page js
// since the static content is already on the page!
const pageHash = pageToHashMap[pageName.toLowerCase()]
const pageClientJsFileName = `assets/${pageName}.${pageHash}.lean.js`
const pageClientJsFileName = `${config.assetsDir}/${pageName}.${pageHash}.lean.js`
let pageData: PageData
let hasCustom404 = true
@ -149,15 +148,7 @@ export async function renderPage(
}
}
let metadataScript = `__VP_HASH_MAP__ = JSON.parse(${hashMapString})\n`
if (siteDataString.includes('_vp-fn_')) {
metadataScript += `${deserializeFunctions.toString()}\n__VP_SITE_DATA__ = deserializeFunctions(JSON.parse(${siteDataString}))`
} else {
metadataScript += `__VP_SITE_DATA__ = JSON.parse(${siteDataString})`
}
const html = `
<!DOCTYPE html>
const html = `<!DOCTYPE html>
<html lang="${siteData.lang}" dir="${siteData.dir}">
<head>
<meta charset="utf-8">
@ -165,21 +156,22 @@ export async function renderPage(
<title>${title}</title>
<meta name="description" content="${description}">
${stylesheetLink}
${metadataScript.inHead ? metadataScript.html : ''}
${
appChunk
? `<script type="module" src="${siteData.base}${appChunk.fileName}"></script>`
: ``
: ''
}
${await renderHead(head)}
</head>
<body>${teleports?.body || ''}
<div id="app">${content}</div>
${config.mpa ? '' : `<script>${metadataScript}</script>`}
${metadataScript.inHead ? '' : metadataScript.html}
${inlinedScript}
</body>
</html>`.trim()
const htmlFileName = path.join(config.outDir, page.replace(/\.md$/, '.html'))
</html>`
const htmlFileName = path.join(config.outDir, page.replace(/\.md$/, '.html'))
await fs.ensureDir(path.dirname(htmlFileName))
const transformedHtml = await config.transformHtml?.(html, htmlFileName, {
page,
@ -223,8 +215,8 @@ function resolvePageImports(
]
}
function renderHead(head: HeadConfig[]): Promise<string> {
return Promise.all(
async function renderHead(head: HeadConfig[]): Promise<string> {
const tags = await Promise.all(
head.map(async ([tag, attrs = {}, innerHTML = '']) => {
const openTag = `<${tag}${renderAttrs(attrs)}>`
if (tag !== 'link' && tag !== 'meta') {
@ -243,13 +235,15 @@ function renderHead(head: HeadConfig[]): Promise<string> {
return openTag
}
})
).then((tags) => tags.join('\n '))
)
return tags.join('\n ')
}
function renderAttrs(attrs: Record<string, string>): string {
return Object.keys(attrs)
.map((key) => {
return ` ${key}="${escape(attrs[key])}"`
if (isBooleanAttr(key)) return ` ${key}`
return ` ${key}="${escape(attrs[key] as string)}"`
})
.join('')
}

@ -73,6 +73,9 @@ export async function resolveConfig(
})
const site = await resolveSiteData(root, userConfig)
const srcDir = normalizePath(path.resolve(root, userConfig.srcDir || '.'))
const assetsDir = userConfig.assetsDir
? userConfig.assetsDir.replace(/\//g, '')
: 'assets'
const outDir = userConfig.outDir
? normalizePath(path.resolve(root, userConfig.outDir))
: resolve(root, 'dist')
@ -94,6 +97,7 @@ export async function resolveConfig(
const config: SiteConfig = {
root,
srcDir,
assetsDir,
site,
themeDir,
pages,
@ -111,6 +115,7 @@ export async function resolveConfig(
vite: userConfig.vite,
shouldPreload: userConfig.shouldPreload,
mpa: !!userConfig.mpa,
metaChunk: !!userConfig.metaChunk,
ignoreDeadLinks: userConfig.ignoreDeadLinks,
cleanUrls: !!userConfig.cleanUrls,
useWebFonts:

@ -36,4 +36,5 @@ export interface MarkdownEnv {
relativePath: string
cleanUrls: boolean
links?: string[]
includes?: string[]
}

@ -50,6 +50,7 @@ export interface MarkdownOptions extends MarkdownIt.Options {
languages?: ILanguageRegistration[]
toc?: TocPluginOptions
externalLinks?: Record<string, string>
cache?: boolean
}
export type MarkdownRenderer = MarkdownIt

@ -140,17 +140,26 @@ export const snippetPlugin = (md: MarkdownIt, srcDir: string) => {
const fence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) => {
const [tokens, idx, , { loader }] = args
const [tokens, idx, , { includes }] = args
const token = tokens[idx]
// @ts-ignore
const [src, regionName] = token.src ?? []
if (src) {
if (loader) {
loader.addDependency(src)
if (!src) return fence(...args)
if (includes) {
includes.push(src)
}
const isAFile = fs.lstatSync(src).isFile()
if (fs.existsSync(src) && isAFile) {
if (!fs.existsSync(src) || !isAFile) {
token.content = isAFile
? `Code snippet path not found: ${src}`
: `Invalid code snippet option`
token.info = ''
return fence(...args)
}
let content = fs.readFileSync(src, 'utf8')
if (regionName) {
@ -161,20 +170,13 @@ export const snippetPlugin = (md: MarkdownIt, srcDir: string) => {
content = dedent(
lines
.slice(region.start, region.end)
.filter((line: string) => !region.regexp.test(line.trim()))
.filter((line) => !region.regexp.test(line.trim()))
.join('\n')
)
}
}
token.content = content
} else {
token.content = isAFile
? `Code snippet path not found: ${src}`
: `Invalid code snippet option`
token.info = ''
}
}
return fence(...args)
}

@ -67,11 +67,13 @@ export async function createMarkdownToVueRenderFn(
const relativePath = slash(path.relative(srcDir, file))
const cacheKey = JSON.stringify({ src, file })
if (isBuild || options.cache !== false) {
const cached = cache.get(cacheKey)
if (cached) {
debug(`[cache hit] ${relativePath}`)
return cached
}
}
const start = Date.now()
@ -88,7 +90,7 @@ export async function createMarkdownToVueRenderFn(
// resolve includes
let includes: string[] = []
function processIncludes(src: string): string {
function processIncludes(src: string, file: string): string {
return src.replace(includesRE, (m: string, m1: string) => {
if (!m1.length) return m
@ -98,7 +100,7 @@ export async function createMarkdownToVueRenderFn(
try {
const includePath = atPresent
? path.join(srcDir, m1.slice(m1[1] === '/' ? 2 : 1))
: path.join(path.dirname(fileOrig), m1)
: path.join(path.dirname(file), m1)
let content = fs.readFileSync(includePath, 'utf-8')
if (range) {
const [, startLine, endLine] = range
@ -112,20 +114,21 @@ export async function createMarkdownToVueRenderFn(
}
includes.push(slash(includePath))
// recursively process includes in the content
return processIncludes(content)
return processIncludes(content, includePath)
} catch (error) {
return m // silently ignore error if file is not present
}
})
}
src = processIncludes(src)
src = processIncludes(src, fileOrig)
// reset env before render
const env: MarkdownEnv = {
path: file,
relativePath,
cleanUrls
cleanUrls,
includes
}
const html = md.render(src, env)
const {
@ -243,7 +246,9 @@ export async function createMarkdownToVueRenderFn(
deadLinks,
includes
}
if (isBuild || options.cache !== false) {
cache.set(cacheKey, result)
}
return result
}
}
@ -317,14 +322,16 @@ function injectPageDataCode(
code +
(hasDefaultExport
? ``
: `\nexport default {name:'${data.relativePath}'}`) +
: `\nexport default {name:${JSON.stringify(data.relativePath)}}`) +
`</script>`
)
} else {
tags.unshift(
`<script ${isUsingTS ? 'lang="ts"' : ''}>${code}\nexport default {name:'${
`<script ${
isUsingTS ? 'lang="ts"' : ''
}>${code}\nexport default {name:${JSON.stringify(
data.relativePath
}'}</script>`
)}}</script>`
)
}

@ -129,7 +129,8 @@ export async function createVitePressPlugin(
__ALGOLIA__:
site.themeConfig?.search?.provider === 'algolia' ||
!!site.themeConfig?.algolia, // legacy
__CARBON__: !!site.themeConfig?.carbonAds
__CARBON__: !!site.themeConfig?.carbonAds,
__ASSETS_DIR__: JSON.stringify(siteConfig.assetsDir)
},
optimizeDeps: {
// force include vue to avoid duplicated copies when linked + optimized

@ -1,12 +1,12 @@
import path from 'node:path'
import type { Plugin, ViteDevServer } from 'vite'
import MiniSearch from 'minisearch'
import fs from 'fs-extra'
import _debug from 'debug'
import fs from 'fs-extra'
import MiniSearch from 'minisearch'
import path from 'path'
import type { Plugin, ViteDevServer } from 'vite'
import type { SiteConfig } from '../config'
import type { MarkdownEnv } from '../markdown'
import { createMarkdownRenderer } from '../markdown'
import { resolveSiteDataByRoute, slash } from '../shared'
import { resolveSiteDataByRoute, slash, type DefaultTheme } from '../shared'
const debug = _debug('vitepress:local-search')
@ -21,7 +21,7 @@ interface IndexObject {
}
export async function localSearchPlugin(
siteConfig: SiteConfig
siteConfig: SiteConfig<DefaultTheme.Config>
): Promise<Plugin> {
if (siteConfig.site.themeConfig?.search?.provider !== 'local') {
return {
@ -63,7 +63,9 @@ export async function localSearchPlugin(
if (!index) {
index = new MiniSearch<IndexObject>({
fields: ['title', 'titles', 'text'],
storeFields: ['title', 'titles']
storeFields: ['title', 'titles'],
...(siteConfig.site.themeConfig?.search?.provider === 'local' &&
siteConfig.site.themeConfig.search.options?.miniSearch?.options)
})
indexByLocales.set(locale, index)
}

@ -28,7 +28,8 @@ export async function serve(options: ServeOptions = {}) {
const config = await resolveConfig(options.root, 'serve', 'production')
const base = trimChar(options?.base ?? config?.site?.base ?? '', '/')
const notAnAsset = (pathname: string) => !pathname.includes('/assets/')
const notAnAsset = (pathname: string) =>
!pathname.includes(`/${config.assetsDir}/`)
const notFound = fs.readFileSync(path.resolve(config.outDir, './404.html'))
const onNoMatch: IOptions['onNoMatch'] = (req, res) => {
res.statusCode = 404

@ -59,6 +59,7 @@ export interface UserConfig<ThemeConfig = any>
srcDir?: string
srcExclude?: string[]
outDir?: string
assetsDir?: string
cacheDir?: string
shouldPreload?: (link: string, page: string) => boolean
@ -97,6 +98,12 @@ export interface UserConfig<ThemeConfig = any>
*/
mpa?: boolean
/**
* Extracts metadata to a separate chunk.
* @experimental
*/
metaChunk?: boolean
/**
* Don't fail builds due to dead links.
*
@ -175,6 +182,7 @@ export interface SiteConfig<ThemeConfig = any>
| 'vite'
| 'shouldPreload'
| 'mpa'
| 'metaChunk'
| 'lastUpdated'
| 'ignoreDeadLinks'
| 'cleanUrls'
@ -192,6 +200,7 @@ export interface SiteConfig<ThemeConfig = any>
configDeps: string[]
themeDir: string
outDir: string
assetsDir: string
cacheDir: string
tempDir: string
pages: string[]

@ -5,7 +5,7 @@ export function getGitTimestamp(file: string) {
return new Promise<number>((resolve, reject) => {
const cwd = dirname(file)
const fileName = basename(file)
const child = spawn('git', ['log', '-1', '--pretty="%ci"', fileName], {
const child = spawn('git', ['log', '-1', '--pretty="%ai"', fileName], {
cwd
})
let output = ''

@ -1 +1,2 @@
export * from './theme'
export { default } from './theme'

2
theme.d.ts vendored

@ -20,4 +20,4 @@ declare const theme: {
export default theme
export type { DefaultTheme } from './types/default-theme.js'
export const useSidebar: () => DefaultTheme.SideBar
export const useSidebar: () => DefaultTheme.DocSidebar

@ -1,3 +1,5 @@
import type { Options as MiniSearchOptions } from 'minisearch'
import type { ComputedRef, Ref } from 'vue'
import type { DocSearchProps } from './docsearch.js'
import type { LocalSearchTranslations } from './local-search.js'
import type { PageData } from './shared.js'
@ -222,7 +224,7 @@ export namespace DefaultTheme {
/**
* ReturnType of `useSidebar`
*/
export interface Sidebar {
export interface DocSidebar {
isOpen: Ref<boolean>
sidebar: ComputedRef<SidebarItem[]>
sidebarGroups: ComputedRef<SidebarItem[]>
@ -334,6 +336,20 @@ export namespace DefaultTheme {
translations?: LocalSearchTranslations
locales?: Record<string, Partial<Omit<LocalSearchOptions, 'locales'>>>
miniSearch?: {
/**
* @see https://lucaong.github.io/minisearch/modules/_minisearch_.html#options
*/
options?: Pick<
MiniSearchOptions,
'extractField' | 'tokenize' | 'processTerm'
>
/**
* @see https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchoptions-1
*/
searchOptions?: MiniSearchOptions['searchOptions']
}
}
// algolia -------------------------------------------------------------------

Loading…
Cancel
Save