diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b02838e5c6..0021518955 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -11,13 +11,7 @@ on: jobs: check: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - node: ['18', '20'] - - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -40,6 +34,11 @@ jobs: - name: Linter Test run: pnpm lint + - name: Docs Test + run: | + pnpm --filter @vuepress/ecosystem-docs docs:build + pnpm --filter @vuepress/ecosystem-docs docs:build-webpack + - name: Unit Test run: pnpm test:unit diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..823eb47aae --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,44 @@ +name: Deploy Ecosystem Docs + +on: + push: + branches: + - main + +jobs: + deploy-github-docs: + name: Deploy ecosystem docs to Github Pages + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@v2 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install deps + run: pnpm install --frozen-lockfile + + - name: Build Project + run: pnpm build + + - name: Docs build + env: + BASE: /ecosystem/ + NODE_OPTIONS: --max_old_space_size=8192 + run: pnpm --filter @vuepress/ecosystem-docs docs:build + + - name: Deploy docs + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: gh-pages + folder: docs/.vuepress/dist + single-commit: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b0574a9b72..4c6e450fbe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,3 +25,39 @@ jobs: tag_name: ${{ github.ref }} body: | Please refer to [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for details. + + deploy-docs: + name: Deploy ecosystem docs to Netlify + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@v2 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install deps + run: pnpm install --frozen-lockfile + + - name: Build Project + run: pnpm build + + - name: Docs build + env: + NODE_OPTIONS: --max_old_space_size=8192 + run: pnpm --filter @vuepress/ecosystem-docs docs:build + + - name: Deploy docs + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: netlify + folder: docs/.vuepress/dist + single-commit: true diff --git a/docs/.vuepress/components/NpmBadge.vue b/docs/.vuepress/components/NpmBadge.vue new file mode 100644 index 0000000000..c9ff7be250 --- /dev/null +++ b/docs/.vuepress/components/NpmBadge.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts new file mode 100644 index 0000000000..1a7b841c47 --- /dev/null +++ b/docs/.vuepress/config.ts @@ -0,0 +1,131 @@ +import { createRequire } from 'node:module' +import process from 'node:process' +import { viteBundler } from '@vuepress/bundler-vite' +import { webpackBundler } from '@vuepress/bundler-webpack' +// import { docsearchPlugin } from '@vuepress/plugin-docsearch' +import { registerComponentsPlugin } from '@vuepress/plugin-register-components' +import { shikiPlugin } from '@vuepress/plugin-shiki' +import { defineUserConfig } from 'vuepress' +import type { UserConfig } from 'vuepress' +import { getDirname, path } from 'vuepress/utils' +import { head } from './configs/index.js' +import theme from './theme.js' + +const __dirname = getDirname(import.meta.url) +const require = createRequire(import.meta.url) + +const isProd = process.env.NODE_ENV === 'production' + +export default defineUserConfig({ + // set site base to default value + base: (process.env.BASE as `/${string}/` | '/') || '/', + + // extra tags in `` + head, + + // site-level locales config + locales: { + '/': { + lang: 'en-US', + title: 'VuePress Ecosystem', + description: 'VuePress official themes plugins', + }, + '/zh/': { + lang: 'zh-CN', + title: 'VuePress 生态系统', + description: 'VuePress 官方主题和插件', + }, + }, + + // specify bundler via environment variable + bundler: + process.env.DOCS_BUNDLER === 'webpack' ? webpackBundler() : viteBundler(), + + // configure markdown + markdown: { + importCode: { + handleImportPath: (importPath) => { + // handle @vuepress packages import path + if (importPath.startsWith('@vuepress/')) { + const packageName = importPath.match(/^(@vuepress\/[^/]*)/)![1] + return importPath + .replace( + packageName, + path.dirname(require.resolve(`${packageName}/package.json`)), + ) + .replace('/src/', '/lib/') + .replace(/hotKey\.ts$/, 'hotKey.d.ts') + } + return importPath + }, + }, + }, + + // configure default theme + theme, + + // use plugins + plugins: [ + // docsearchPlugin({ + // appId: '34YFD9IUQ2', + // apiKey: '9a9058b8655746634e01071411c366b8', + // indexName: 'vuepress', + // searchParameters: { + // facetFilters: ['tags:v2'], + // }, + // locales: { + // '/zh/': { + // placeholder: '搜索文档', + // translations: { + // button: { + // buttonText: '搜索文档', + // buttonAriaLabel: '搜索文档', + // }, + // modal: { + // searchBox: { + // resetButtonTitle: '清除查询条件', + // resetButtonAriaLabel: '清除查询条件', + // cancelButtonText: '取消', + // cancelButtonAriaLabel: '取消', + // }, + // startScreen: { + // recentSearchesTitle: '搜索历史', + // noRecentSearchesText: '没有搜索历史', + // saveRecentSearchButtonTitle: '保存至搜索历史', + // removeRecentSearchButtonTitle: '从搜索历史中移除', + // favoriteSearchesTitle: '收藏', + // removeFavoriteSearchButtonTitle: '从收藏中移除', + // }, + // errorScreen: { + // titleText: '无法获取结果', + // helpText: '你可能需要检查你的网络连接', + // }, + // footer: { + // selectText: '选择', + // navigateText: '切换', + // closeText: '关闭', + // searchByText: '搜索提供者', + // }, + // noResultsScreen: { + // noResultsText: '无法找到相关结果', + // suggestedQueryText: '你可以尝试查询', + // reportMissingResultsText: '你认为该查询应该有结果?', + // reportMissingResultsLinkText: '点击反馈', + // }, + // }, + // }, + // }, + // }, + // }), + registerComponentsPlugin({ + componentsDir: path.resolve(__dirname, './components'), + }), + // only enable shiki plugin in production mode + isProd + ? shikiPlugin({ + langs: ['bash', 'diff', 'json', 'md', 'ts', 'vue'], + theme: 'dark-plus', + }) + : [], + ], +}) as UserConfig diff --git a/docs/.vuepress/configs/head.ts b/docs/.vuepress/configs/head.ts new file mode 100644 index 0000000000..a869976d67 --- /dev/null +++ b/docs/.vuepress/configs/head.ts @@ -0,0 +1,40 @@ +import type { HeadConfig } from 'vuepress/core' + +export const head: HeadConfig[] = [ + [ + 'link', + { + rel: 'icon', + type: 'image/png', + sizes: '16x16', + href: `/images/icons/favicon-16x16.png`, + }, + ], + [ + 'link', + { + rel: 'icon', + type: 'image/png', + sizes: '32x32', + href: `/images/icons/favicon-32x32.png`, + }, + ], + ['link', { rel: 'manifest', href: '/manifest.webmanifest' }], + ['meta', { name: 'application-name', content: 'VuePress' }], + ['meta', { name: 'apple-mobile-web-app-title', content: 'VuePress' }], + ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }], + [ + 'link', + { rel: 'apple-touch-icon', href: `/images/icons/apple-touch-icon.png` }, + ], + [ + 'link', + { + rel: 'mask-icon', + href: '/images/icons/safari-pinned-tab.svg', + color: '#3eaf7c', + }, + ], + ['meta', { name: 'msapplication-TileColor', content: '#3eaf7c' }], + ['meta', { name: 'theme-color', content: '#3eaf7c' }], +] diff --git a/docs/.vuepress/configs/index.ts b/docs/.vuepress/configs/index.ts new file mode 100644 index 0000000000..b23dc923d2 --- /dev/null +++ b/docs/.vuepress/configs/index.ts @@ -0,0 +1,3 @@ +export * from './head.js' +export * from './navbar/index.js' +export * from './sidebar/index.js' diff --git a/docs/.vuepress/configs/navbar/en.ts b/docs/.vuepress/configs/navbar/en.ts new file mode 100644 index 0000000000..3c237a7afb --- /dev/null +++ b/docs/.vuepress/configs/navbar/en.ts @@ -0,0 +1,52 @@ +import type { NavbarConfig } from '@vuepress/theme-default' + +export const navbarEn: NavbarConfig = [ + { + text: 'Themes', + children: [ + { + text: 'Default Theme', + link: '/themes/default/', + }, + ], + }, + { + text: 'Plugins', + children: [ + { + text: 'Common Features', + children: [ + '/plugins/back-to-top', + '/plugins/container', + '/plugins/external-link-icon', + '/plugins/google-analytics', + '/plugins/medium-zoom', + '/plugins/nprogress', + '/plugins/register-components', + ], + }, + { + text: 'Content Search', + children: ['/plugins/docsearch', '/plugins/search'], + }, + { + text: 'PWA', + children: ['/plugins/pwa', '/plugins/pwa-popup'], + }, + { + text: 'Syntax Highlighting', + children: ['/plugins/prismjs', '/plugins/shiki'], + }, + { + text: 'Theme Development', + children: [ + '/plugins/active-header-links', + '/plugins/git', + '/plugins/palette', + '/plugins/theme-data', + '/plugins/toc', + ], + }, + ], + }, +] diff --git a/docs/.vuepress/configs/navbar/index.ts b/docs/.vuepress/configs/navbar/index.ts new file mode 100644 index 0000000000..7183393c31 --- /dev/null +++ b/docs/.vuepress/configs/navbar/index.ts @@ -0,0 +1,2 @@ +export * from './en.js' +export * from './zh.js' diff --git a/docs/.vuepress/configs/navbar/zh.ts b/docs/.vuepress/configs/navbar/zh.ts new file mode 100644 index 0000000000..8ed14f4129 --- /dev/null +++ b/docs/.vuepress/configs/navbar/zh.ts @@ -0,0 +1,52 @@ +import type { NavbarConfig } from '@vuepress/theme-default' + +export const navbarZh: NavbarConfig = [ + { + text: '主题', + children: [ + { + text: '默认主题', + link: '/zh/themes/default/', + }, + ], + }, + { + text: '插件', + children: [ + { + text: '常用功能', + children: [ + '/zh/plugins/back-to-top', + '/zh/plugins/container', + '/zh/plugins/external-link-icon', + '/zh/plugins/google-analytics', + '/zh/plugins/medium-zoom', + '/zh/plugins/nprogress', + '/zh/plugins/register-components', + ], + }, + { + text: '内容搜索', + children: ['/zh/plugins/docsearch', '/zh/plugins/search'], + }, + { + text: 'PWA', + children: ['/zh/plugins/pwa', '/zh/plugins/pwa-popup'], + }, + { + text: '语法高亮', + children: ['/zh/plugins/prismjs', '/zh/plugins/shiki'], + }, + { + text: '主题开发', + children: [ + '/zh/plugins/active-header-links', + '/zh/plugins/git', + '/zh/plugins/palette', + '/zh/plugins/theme-data', + '/zh/plugins/toc', + ], + }, + ], + }, +] diff --git a/docs/.vuepress/configs/sidebar/en.ts b/docs/.vuepress/configs/sidebar/en.ts new file mode 100644 index 0000000000..89a9ac0f8e --- /dev/null +++ b/docs/.vuepress/configs/sidebar/en.ts @@ -0,0 +1,54 @@ +import type { SidebarConfig } from '@vuepress/theme-default' + +export const sidebarEn: SidebarConfig = { + '/plugins/': [ + { + text: 'Common Features', + children: [ + '/plugins/back-to-top', + '/plugins/container', + '/plugins/external-link-icon', + '/plugins/google-analytics', + '/plugins/medium-zoom', + '/plugins/nprogress', + '/plugins/register-components', + ], + }, + { + text: 'Content Search', + children: ['/plugins/docsearch', '/plugins/search'], + }, + { + text: 'PWA', + children: ['/plugins/pwa', '/plugins/pwa-popup'], + }, + { + text: 'Syntax Highlighting', + children: ['/plugins/prismjs', '/plugins/shiki'], + }, + { + text: 'Theme Development', + children: [ + '/plugins/active-header-links', + '/plugins/git', + '/plugins/palette', + '/plugins/theme-data', + '/plugins/toc', + ], + }, + ], + '/themes/': [ + { + text: 'Default Theme', + children: [ + '/themes/default/', + '/themes/default/config', + '/themes/default/frontmatter', + '/themes/default/components', + '/themes/default/markdown', + '/themes/default/styles', + '/themes/default/extending', + ], + }, + ], +} diff --git a/docs/.vuepress/configs/sidebar/index.ts b/docs/.vuepress/configs/sidebar/index.ts new file mode 100644 index 0000000000..7183393c31 --- /dev/null +++ b/docs/.vuepress/configs/sidebar/index.ts @@ -0,0 +1,2 @@ +export * from './en.js' +export * from './zh.js' diff --git a/docs/.vuepress/configs/sidebar/zh.ts b/docs/.vuepress/configs/sidebar/zh.ts new file mode 100644 index 0000000000..a3405a7282 --- /dev/null +++ b/docs/.vuepress/configs/sidebar/zh.ts @@ -0,0 +1,54 @@ +import type { SidebarConfig } from '@vuepress/theme-default' + +export const sidebarZh: SidebarConfig = { + '/zh/plugins/': [ + { + text: '常用功能', + children: [ + '/zh/plugins/back-to-top', + '/zh/plugins/container', + '/zh/plugins/external-link-icon', + '/zh/plugins/google-analytics', + '/zh/plugins/medium-zoom', + '/zh/plugins/nprogress', + '/zh/plugins/register-components', + ], + }, + { + text: '内容搜索', + children: ['/zh/plugins/docsearch', '/zh/plugins/search'], + }, + { + text: 'PWA', + children: ['/zh/plugins/pwa', '/zh/plugins/pwa-popup'], + }, + { + text: '语法高亮', + children: ['/zh/plugins/prismjs', '/zh/plugins/shiki'], + }, + { + text: '主题开发', + children: [ + '/zh/plugins/active-header-links', + '/zh/plugins/git', + '/zh/plugins/palette', + '/zh/plugins/theme-data', + '/zh/plugins/toc', + ], + }, + ], + '/zh/themes/': [ + { + text: '默认主题', + children: [ + '/zh/themes/default/', + '/zh/themes/default/config', + '/zh/themes/default/frontmatter', + '/zh/themes/default/components', + '/zh/themes/default/markdown', + '/zh/themes/default/styles', + '/zh/themes/default/extending', + ], + }, + ], +} diff --git a/docs/.vuepress/public/favicon.ico b/docs/.vuepress/public/favicon.ico new file mode 100644 index 0000000000..e481e5dda7 Binary files /dev/null and b/docs/.vuepress/public/favicon.ico differ diff --git a/docs/.vuepress/public/images/cookbook/extending-a-theme-01.png b/docs/.vuepress/public/images/cookbook/extending-a-theme-01.png new file mode 100644 index 0000000000..9ba6d7e812 Binary files /dev/null and b/docs/.vuepress/public/images/cookbook/extending-a-theme-01.png differ diff --git a/docs/.vuepress/public/images/hero.png b/docs/.vuepress/public/images/hero.png new file mode 100644 index 0000000000..ac6beaff06 Binary files /dev/null and b/docs/.vuepress/public/images/hero.png differ diff --git a/docs/.vuepress/public/images/icons/android-chrome-192x192.png b/docs/.vuepress/public/images/icons/android-chrome-192x192.png new file mode 100644 index 0000000000..ddd043910e Binary files /dev/null and b/docs/.vuepress/public/images/icons/android-chrome-192x192.png differ diff --git a/docs/.vuepress/public/images/icons/android-chrome-384x384.png b/docs/.vuepress/public/images/icons/android-chrome-384x384.png new file mode 100644 index 0000000000..86e1fd58b3 Binary files /dev/null and b/docs/.vuepress/public/images/icons/android-chrome-384x384.png differ diff --git a/docs/.vuepress/public/images/icons/apple-touch-icon.png b/docs/.vuepress/public/images/icons/apple-touch-icon.png new file mode 100644 index 0000000000..208915f1de Binary files /dev/null and b/docs/.vuepress/public/images/icons/apple-touch-icon.png differ diff --git a/docs/.vuepress/public/images/icons/favicon-16x16.png b/docs/.vuepress/public/images/icons/favicon-16x16.png new file mode 100644 index 0000000000..ca5047e7b8 Binary files /dev/null and b/docs/.vuepress/public/images/icons/favicon-16x16.png differ diff --git a/docs/.vuepress/public/images/icons/favicon-32x32.png b/docs/.vuepress/public/images/icons/favicon-32x32.png new file mode 100644 index 0000000000..e275ce9ba1 Binary files /dev/null and b/docs/.vuepress/public/images/icons/favicon-32x32.png differ diff --git a/docs/.vuepress/public/images/icons/mstile-150x150.png b/docs/.vuepress/public/images/icons/mstile-150x150.png new file mode 100644 index 0000000000..d0b1439483 Binary files /dev/null and b/docs/.vuepress/public/images/icons/mstile-150x150.png differ diff --git a/docs/.vuepress/public/images/icons/safari-pinned-tab.svg b/docs/.vuepress/public/images/icons/safari-pinned-tab.svg new file mode 100644 index 0000000000..dc0b992c04 --- /dev/null +++ b/docs/.vuepress/public/images/icons/safari-pinned-tab.svg @@ -0,0 +1,23 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/docs/.vuepress/public/images/logo.png b/docs/.vuepress/public/images/logo.png new file mode 100644 index 0000000000..60e17006ad Binary files /dev/null and b/docs/.vuepress/public/images/logo.png differ diff --git a/docs/.vuepress/public/manifest.webmanifest b/docs/.vuepress/public/manifest.webmanifest new file mode 100644 index 0000000000..d2e935f1ae --- /dev/null +++ b/docs/.vuepress/public/manifest.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "VuePress", + "short_name": "VuePress", + "description": "Vue-powered Static Site Generator", + "start_url": "/index.html", + "display": "standalone", + "background_color": "#fff", + "theme_color": "#3eaf7c", + "icons": [ + { + "src": "/images/icons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/images/icons/android-chrome-384x384.png", + "sizes": "384x384", + "type": "image/png" + } + ] +} diff --git a/docs/.vuepress/theme.ts b/docs/.vuepress/theme.ts new file mode 100644 index 0000000000..907c3aebe8 --- /dev/null +++ b/docs/.vuepress/theme.ts @@ -0,0 +1,68 @@ +import { defaultTheme } from '@vuepress/theme-default' +import { navbarEn, navbarZh, sidebarEn, sidebarZh } from './configs' + +const isProd = process.env.NODE_ENV === 'production' + +export default defaultTheme({ + logo: '/images/hero.png', + repo: 'vuepress/docs', + docsDir: 'docs', + + // theme-level locales config + locales: { + /** + * English locale config + * + * As the default locale of @vuepress/theme-default is English, + * we don't need to set all of the locale fields + */ + '/': { + // navbar + navbar: navbarEn, + // sidebar + sidebar: sidebarEn, + // page meta + editLinkText: 'Edit this page on GitHub', + }, + + /** + * Chinese locale config + */ + '/zh/': { + // navbar + navbar: navbarZh, + selectLanguageName: '简体中文', + selectLanguageText: '选择语言', + selectLanguageAriaLabel: '选择语言', + // sidebar + sidebar: sidebarZh, + // page meta + editLinkText: '在 GitHub 上编辑此页', + lastUpdatedText: '上次更新', + contributorsText: '贡献者', + // custom containers + tip: '提示', + warning: '注意', + danger: '警告', + // 404 page + notFound: [ + '这里什么都没有', + '我们怎么到这来了?', + '这是一个 404 页面', + '看起来我们进入了错误的链接', + ], + backToHome: '返回首页', + // a11y + openInNewWindow: '在新窗口打开', + toggleColorMode: '切换颜色模式', + toggleSidebar: '切换侧边栏', + }, + }, + + themePlugins: { + // only enable git plugin in production mode + git: isProd, + // use shiki plugin in production mode instead + prismjs: !isProd, + }, +}) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..337defa917 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,15 @@ +--- +home: true +title: Home +heroImage: /images/hero.png +# actions: +# - text: Themes +# link: ./themes/ +# type: primary + +# - text: Plugins +# link: ./plugins/ +# type: primary + +footer: MIT Licensed | Copyright © 2018-present VuePress Community +--- diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000000..5ea86d30c6 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,30 @@ +{ + "name": "@vuepress/ecosystem-docs", + "private": true, + "scripts": { + "docs:build": "vuepress build . --clean-cache --clean-temp", + "docs:build-webpack": "DOCS_BUNDLER=webpack pnpm docs:build", + "docs:dev": "vuepress dev . --clean-cache --clean-temp", + "docs:dev-webpack": "DOCS_BUNDLER=webpack pnpm docs:dev", + "docs:serve": "anywhere -s -h localhost -d .vuepress/dist" + }, + "dependencies": { + "@vuepress/bundler-vite": "2.0.0-rc.2", + "@vuepress/bundler-webpack": "2.0.0-rc.2", + "@vuepress/plugin-back-to-top": "workspace:*", + "@vuepress/plugin-docsearch": "workspace:*", + "@vuepress/plugin-external-link-icon": "workspace:*", + "@vuepress/plugin-google-analytics": "workspace:*", + "@vuepress/plugin-medium-zoom": "workspace:*", + "@vuepress/plugin-nprogress": "workspace:*", + "@vuepress/plugin-pwa-popup": "workspace:*", + "@vuepress/plugin-register-components": "workspace:*", + "@vuepress/plugin-search": "workspace:*", + "@vuepress/plugin-shiki": "workspace:*", + "@vuepress/theme-default": "workspace:*", + "anywhere": "^1.6.0", + "sass-loader": "^14.0.0", + "vue": "^3.4.15", + "vuepress": "2.0.0-rc.2" + } +} diff --git a/docs/plugins/active-header-links.md b/docs/plugins/active-header-links.md new file mode 100644 index 0000000000..a4b0ca0b00 --- /dev/null +++ b/docs/plugins/active-header-links.md @@ -0,0 +1,74 @@ +# active-header-links + + + +This plugin will listen to page scroll event. When the page scrolls to a certain _header anchor_, this plugin will change the route hash to that _header anchor_ if there is a corresponding _header link_. + +This plugin is mainly used to develop themes, and has been integrated into the default theme. You won't need to use it directly in most cases. + +## Usage + +```bash +npm i -D @vuepress/plugin-active-header-links@next +``` + +```ts +import { activeHeaderLinksPlugin } from '@vuepress/plugin-active-header-links' + +export default { + plugins: [ + activeHeaderLinksPlugin({ + // options + }), + ], +} +``` + +## Options + +### headerLinkSelector + +- Type: `string` + +- Default: `'a.sidebar-item'` + +- Details: + + Selector of _header link_. + + If a _header anchor_ does not have a corresponding _header link_, this plugin won't change the route hash to that anchor when scrolling to it. + +### headerAnchorSelector + +- Type: `string` + +- Default: `'.header-anchor'` + +- Details: + + Selector of _header anchor_. + + You don't need to specify this option unless you have changed the `permalinkClass` option of [markdown-it-anchor](https://github.com/valeriangalliat/markdown-it-anchor#readme) via [markdown.anchor](../config.md#markdown-anchor). + +- Also see: + - [Guide > Markdown > Syntax Extensions > Header Anchors](../../guide/markdown.md#header-anchors) + +### delay + +- Type: `number` + +- Default: `200` + +- Details: + + The delay of the debounced scroll event listener. + +### offset + +- Type: `number` + +- Default: `5` + +- Details: + + Even if you click the link of the _header anchor_ directly, the `scrollTop` might not be exactly equal to `offsetTop` of the _header anchor_, so we add an offset to avoid the error. diff --git a/docs/plugins/back-to-top.md b/docs/plugins/back-to-top.md new file mode 100644 index 0000000000..6e9596481a --- /dev/null +++ b/docs/plugins/back-to-top.md @@ -0,0 +1,27 @@ +# back-to-top + + + +This plugin will add a _back to top_ button to your site. The button will be displayed in the bottom right corner of the page when scrolling down. By clicking the button, the page will scroll to the top. + +This plugin has been integrated into the default theme. + +## Usage + +```bash +npm i -D @vuepress/plugin-back-to-top@next +``` + +```ts +import { backToTopPlugin } from '@vuepress/plugin-back-to-top' + +export default { + plugins: [backToTopPlugin()], +} +``` + +## Styles + +You can customize the style of the _back to top_ button via CSS variables: + +@[code css](@vuepress/plugin-back-to-top/src/client/styles/vars.css) diff --git a/docs/plugins/container.md b/docs/plugins/container.md new file mode 100644 index 0000000000..db3912e3a5 --- /dev/null +++ b/docs/plugins/container.md @@ -0,0 +1,165 @@ +# container + + + +Register markdown custom containers in your VuePress site. + +This plugin simplifies the use of [markdown-it-container](https://github.com/markdown-it/markdown-it-container), but also retains its original capabilities. + +The [Custom Containers](../default-theme/markdown.md#custom-containers) of default theme is powered by this plugin. + +## Usage + +```bash +npm i -D @vuepress/plugin-container@next +``` + +```ts +import { containerPlugin } from '@vuepress/plugin-container' + +export default { + plugins: [ + containerPlugin({ + // options + }), + ], +} +``` + +## Container Syntax + +```md +::: [info] +[content] +::: +``` + +- The `type` is required and should be specified via [type](#type) option. +- The `info` is optional, and the default value can be specified via `defaultInfo` in [locales](#locales) option. +- The `content` can be any valid markdown content. + +::: tip +This plugin can be used multiple times to support different types of containers. +::: + +## Options + +### type + +- Type: `string` + +- Details: + + The type of the container. + + It will be used as the `name` param of [markdown-it-container](https://github.com/markdown-it/markdown-it-container#api). + +### locales + +- Type: `Record` + +- Details: + + The default `info` of the container in different locales. + + If this option is not specified, the default `info` will fallback to the uppercase of the [type](#type) option. + +- Example: + +```ts +export default { + plugins: [ + containerPlugin({ + type: 'tip', + locales: { + '/': { + defaultInfo: 'TIP', + }, + '/zh/': { + defaultInfo: '提示', + }, + }, + }), + ], +} +``` + +- Also see: + - [Guide > I18n](../../guide/i18n.md) + +### before + +- Type: `(info: string) => string` + +- Default: + + +```ts +(info: string): string => + `
${info ? `

${info}

` : ''}\n` +``` + + +- Details: + + A function to render the starting tag of the container. + + The first param is the `info` part of [container syntax](#container-syntax). + + This option will not take effect if you don't specify the [after](#after) option. + +### after + +- Type: `(info: string) => string` + +- Default: + + +```ts +(): string => '
\n' +``` + + +- Details: + + A function to render the ending tag of the container. + + The first param is the `info` part of [container syntax](#container-syntax). + + This option will not take effect if you don't specify the [before](#before) option. + +### render + +- Type: + +```ts +type MarkdownItContainerRenderFunction = ( + tokens: Token[], + index: number, + options: any, + env: MarkdownEnv, + self: Renderer, +) => string +``` + +- Details: + + The `render` option of [markdown-it-container](https://github.com/markdown-it/markdown-it-container#api). + + This plugin uses a default `render` function. If you specify this option, the default `render` function will be replaced, and the [locales](#locales), [before](#before) and [after](#after) options will be ignored. + +### validate + +- Type: `(params: string) => boolean` + +- Details: + + The `validate` option of [markdown-it-container](https://github.com/markdown-it/markdown-it-container#api). + +### marker + +- Type: `string` + +- Details: + + The `marker` option of [markdown-it-container](https://github.com/markdown-it/markdown-it-container#api). diff --git a/docs/plugins/docsearch.md b/docs/plugins/docsearch.md new file mode 100644 index 0000000000..0b43755326 --- /dev/null +++ b/docs/plugins/docsearch.md @@ -0,0 +1,411 @@ +# docsearch + + + +Integrate [Algolia DocSearch](https://docsearch.algolia.com/) into VuePress, which can provide search to your documentation site. + +::: tip +Default theme will add DocSearch to the navbar once you configure this plugin correctly. + +This plugin may not be used directly in other themes, so you'd better refer to the documentation of your theme for more details. +::: + +## Usage + +```bash +npm i -D @vuepress/plugin-docsearch@next +``` + +```ts +import { docsearchPlugin } from '@vuepress/plugin-docsearch' + +export default { + plugins: [ + docsearchPlugin({ + // options + }), + ], +} +``` + +## Get Search Index + +You need to [submit the URL of your site](https://docsearch.algolia.com/apply/) to join the DocSearch program. The DocSearch team will send [apiKey](#apikey) and [indexName](#indexname) to your email once the index is generated. Then you can configure this plugin to enable DocSearch in VuePress. + +Alternatively, you can [run your own crawler](https://docsearch.algolia.com/docs/run-your-own/) to generate the index, and then use your own [appId](#appId), [apiKey](#apikey) and [indexName](#indexname) to configure this plugin. + +::: details Official crawler config + +```js{35-50,59} +new Crawler({ + appId: 'YOUR_APP_ID', + apiKey: 'YOUR_API_KEY', + rateLimit: 8, + startUrls: [ + // These are urls which algolia start to craw + // If your site is divided in to mutiple parts, + // you may want to set mutiple entry links + 'https://YOUR_WEBSITE_URL/', + ], + sitemaps: [ + // if you are using sitemap plugins (e.g.: vuepress-plugin-sitemap2), you may provide one + 'https://YOUR_WEBSITE_URL/sitemap.xml', + ], + ignoreCanonicalTo: false, + exclusionPatterns: [ + // You can use this to stop algolia crawing some paths + ], + discoveryPatterns: [ + // These are urls which algolia looking for, + 'https://YOUR_WEBSITE_URL/**', + ], + // Crawler schedule, set it according to your docs update frequency + schedule: 'at 02:00 every 1 day', + actions: [ + // you may have mutiple actions, especially when you are deploying mutiple docs under one domain + { + // name the index with name you like + indexName: 'YOUR_INDEX_NAME', + // paths where the index take effect + pathsToMatch: ['https://YOUR_WEBSITE_URL/**'], + // controls how algolia extracts records from your site + recordExtractor: ({ $, helpers }) => { + // options for @vuepress/theme-default + return helpers.docsearch({ + recordProps: { + lvl0: { + selectors: '.sidebar-heading.active', + defaultValue: 'Documentation', + }, + lvl1: '.theme-default-content h1', + lvl2: '.theme-default-content h2', + lvl3: '.theme-default-content h3', + lvl4: '.theme-default-content h4', + lvl5: '.theme-default-content h5', + lvl6: '.theme-default-content h6', + content: '.theme-default-content p, .theme-default-content li', + }, + indexHeadings: true, + }) + }, + }, + ], + initialIndexSettings: { + // controls how index are initialized + // only has effects before index are initialize + // you may need to delete your index and recraw after modification + YOUR_INDEX_NAME: { + 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: '', + highlightPostTag: '', + minWordSizefor1Typo: 3, + minWordSizefor2Typos: 7, + allowTyposOnNumericTokens: false, + minProximity: 1, + ignorePlurals: true, + advancedSyntax: true, + attributeCriteriaComputedByMinProximity: true, + removeWordsIfNoResults: 'allOptional', + }, + }, +}) +``` + +The above `recordProps` is the configuration used for the default theme. You can modify them according to the theme you are using. + +Notice that the `initialIndexSettings.YOUR_INDEX_NAME.attributesForFaceting` fields must include `'lang'` to make this plugin work properly. +::: + +::: tip +If you are not using default theme, or you meet any problems when using docsearch, you can also check the above example crawler config, and ahead to [Algolia Crawler](https://crawler.algolia.com/admin/crawlers/), and edit your config with 'Editor' panel in project sidebar. +::: + +## Options + +### apiKey + +- Type: `string` + +- Required: `true` + +- Details: + + The `apiKey` that you received from the DocSearch team, or generated by yourself. + +- Also see: + - [DocSearch > Options > apiKey](https://docsearch.algolia.com/docs/api#apikey) + +### indexName + +- Type: `string` + +- Required: `true` + +- Details: + + The `indexName` that you received from the DocSearch team, or generated by yourself. + +- Also see: + - [DocSearch > Options > indexName](https://docsearch.algolia.com/docs/api#indexname) + +### appId + +- Type: `string` + +- Required: `true` + +- Details: + + It defines your own application ID. + +- Also see: + - [DocSearch > Options > appId](https://docsearch.algolia.com/docs/api#appid) + +### searchParameters + +- Type: `SearchParameters` + +- Details: + + Parameters of Algolia Search API. + +- Also see: + - [DocSearch > Options > searchParameters](https://docsearch.algolia.com/docs/api/#searchparameters) + - [Algolia > Search API Parameters](https://www.algolia.com/doc/api-reference/search-api-parameters/) + +### placeholder + +- Type: `string` + +- Default: `'Search docs'` + +- Details: + + The placeholder attribute of the search input. + +- Also see: + - [DocSearch > Options > placeholder](https://docsearch.algolia.com/docs/api/#placeholder) + +### disableUserPersonalization + +- Type: `boolean` + +- Default: `false` + +- Details: + + Whether to disable all personalized features: recent searches, favorite searches, etc. + +- Also see: + - [DocSearch > Options > disableUserPersonalization](https://docsearch.algolia.com/docs/api/#disableuserpersonalization) + +### initialQuery + +- Type: `string` + +- Details: + + The initial query when the modal opens. + +- Also see: + - [DocSearch > Options > initialQuery](https://docsearch.algolia.com/docs/api/#initialquery) + +### translations + +- Type: `Partial` + +- Details: + + Allow replacing the default text in the DocSearch button or modal. + +- Also see: + - [DocSearch > Options > translations](https://docsearch.algolia.com/docs/api/#translations) + +### locales + +- Type: `Record` + +- Details: + + Options of this plugin in different locales. + + All other options of this plugin are acceptable in locale config. + +- Example: + +```ts +export default { + plugins: [ + docsearchPlugin({ + appId: '', + apiKey: '', + indexName: '', + locales: { + '/': { + placeholder: 'Search Documentation', + translations: { + button: { + buttonText: 'Search Documentation', + }, + }, + }, + '/zh/': { + placeholder: '搜索文档', + translations: { + button: { + buttonText: '搜索文档', + }, + }, + }, + }, + }), + ], +} +``` + +- Also see: + - [Guide > I18n](../../guide/i18n.md) + +### indexBase + +- Type: `string` + +- Default: [base](../config.md#base) + +- Details: + + The base path of the search index. + + If you are deploying your site to multiple domains, you don't need to submit all of them to DocSearch and generate search index separately. You could choose one of the domains as the _index domain_, and only submit the _index domain_ to Docsearch for crawling search index. Then, you could reuse the search index across all deployments. + + However, if the [base](../config.md#base) of your deployments are different for different domains, you need to set the option to the [base](../config.md#base) of your _index domain_, so that other deployments could reuse the search index correctly. + +### injectStyles + +- Type: `boolean` + +- Default: `true` + +- Details: + + Whether to inject the default styles of DocSearch or not. + + If you think the default styles of DocSearch is not compatible with your site, you can try to override the default styles, or set this option to `false` to totally exclude the default styles. + + When this option is disabled, you need to import your own styles for DocSearch. Also notice that all styles customization in [Styles](#styles) section would be unavailable. + +## Styles + +You can customize styles via CSS variables that provided by [@docsearch/css](https://docsearch.algolia.com/docs/styling): + +```css +:root { + --docsearch-primary-color: rgb(84, 104, 255); + --docsearch-text-color: rgb(28, 30, 33); + --docsearch-spacing: 12px; + --docsearch-icon-stroke-width: 1.4; + --docsearch-highlight-color: var(--docsearch-primary-color); + --docsearch-muted-color: rgb(150, 159, 175); + --docsearch-container-background: rgba(101, 108, 133, 0.8); + --docsearch-logo-color: rgba(84, 104, 255); + + /* modal */ + --docsearch-modal-width: 560px; + --docsearch-modal-height: 600px; + --docsearch-modal-background: rgb(245, 246, 247); + --docsearch-modal-shadow: inset 1px 1px 0 0 rgba(255, 255, 255, 0.5), 0 3px + 8px 0 rgba(85, 90, 100, 1); + + /* searchbox */ + --docsearch-searchbox-height: 56px; + --docsearch-searchbox-background: rgb(235, 237, 240); + --docsearch-searchbox-focus-background: #fff; + --docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color); + + /* hit */ + --docsearch-hit-height: 56px; + --docsearch-hit-color: rgb(68, 73, 80); + --docsearch-hit-active-color: #fff; + --docsearch-hit-background: #fff; + --docsearch-hit-shadow: 0 1px 3px 0 rgb(212, 217, 225); + + /* key */ + --docsearch-key-gradient: linear-gradient( + -225deg, + rgb(213, 219, 228) 0%, + rgb(248, 248, 248) 100% + ); + --docsearch-key-shadow: inset 0 -2px 0 0 rgb(205, 205, 230), inset 0 0 1px 1px + #fff, 0 1px 2px 1px rgba(30, 35, 90, 0.4); + + /* footer */ + --docsearch-footer-height: 44px; + --docsearch-footer-background: #fff; + --docsearch-footer-shadow: 0 -1px 0 0 rgb(224, 227, 232), 0 -3px 6px 0 rgba(69, 98, 155, 0.12); +} +``` + +## Components + +### Docsearch + +- Details: + + This plugin will register a `` component globally, and you can use it without any props. + + Put this component to where you want to place the docsearch button. For example, default theme puts this component to the end of the navbar. + +::: tip +This component is mainly used for theme development. You don't need to use it directly in most cases. +::: diff --git a/docs/plugins/external-link-icon.md b/docs/plugins/external-link-icon.md new file mode 100644 index 0000000000..c57f43749f --- /dev/null +++ b/docs/plugins/external-link-icon.md @@ -0,0 +1,87 @@ +# external-link-icon + + + +This plugin will add an icon to the external link in your markdown content, i.e. + +This plugin has been integrated into the default theme. + +## Usage + +```bash +npm i -D @vuepress/plugin-external-link-icon@next +``` + +```ts +import { externalLinkIconPlugin } from '@vuepress/plugin-external-link-icon' + +export default { + plugins: [ + externalLinkIconPlugin({ + // options + }), + ], +} +``` + +## Options + +### locales + +- Type: `Record` + +- Details: + + The a11y text of the external link icon in different locales. + + If this option is not specified, it will fallback to default text. + +- Example: + +```ts +export default { + plugins: [ + externalLinkIconPlugin({ + locales: { + '/': { + openInNewWindow: 'open in new window', + }, + '/zh/': { + openInNewWindow: '在新窗口打开', + }, + }, + }), + ], +} +``` + +- Also see: + - [Guide > I18n](../../guide/i18n.md) + +## Frontmatter + +### externalLinkIcon + +- Type: `boolean` + +- Details: + + Whether to append an external link icon to external links in current page. + +## Styles + +You can customize the style of the external link icon via CSS variables: + +@[code css](@vuepress/plugin-external-link-icon/src/client/styles/vars.css) + +## Components + +### ExternalLinkIcon + +- Details: + + This plugin will register a `` component globally, and you can use it without any props. + +::: tip +This component is mainly used for theme development. You don't need to use it directly in most cases. +::: diff --git a/docs/plugins/git.md b/docs/plugins/git.md new file mode 100644 index 0000000000..059c8ab17f --- /dev/null +++ b/docs/plugins/git.md @@ -0,0 +1,145 @@ +# git + + + +This plugin will collect git information of your pages, including the created and updated time, the contributors, etc. + +The [lastUpdated](../default-theme/config.md#lastupdated) and [contributors](../default-theme/config.md#contributors) of default theme is powered by this plugin. + +This plugin is mainly used to develop themes. You won't need to use it directly in most cases. + +## Usage + +```bash +npm i -D @vuepress/plugin-git@next +``` + +```ts +import { gitPlugin } from '@vuepress/plugin-git' + +export default { + plugins: [ + gitPlugin({ + // options + }), + ], +} +``` + +## Git Repository + +This plugin requires your project to be inside a [Git Repository](https://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository), so that it can collect information from the commit history. + +You should ensure all commits are available when building your site. For example, CI workflows usually clone your repository with [--depth 1](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---depthltdepthgt) to avoid fetching all commits, so you should disable the behavior to make this plugin work properly in CI. + +::: warning +This plugin will significantly slow down the speed of data preparation, especially when you have a lot of pages. You can consider disabling this plugin in `dev` mode to get better development experience. +::: + +## Options + +### createdTime + +- Type: `boolean` + +- Default: `true` + +- Details: + + Whether to collect page created time or not. + +### updatedTime + +- Type: `boolean` + +- Default: `true` + +- Details: + + Whether to collect page updated time or not. + +### contributors + +- Type: `boolean` + +- Default: `true` + +- Details: + + Whether to collect page contributors or not. + +## Frontmatter + +### gitInclude + +- Type: `string[]` + +- Details: + + An array of relative paths to be included when calculating page data. + +- Example: + +```md +--- +gitInclude: + - relative/path/to/file1 + - relative/path/to/file2 +--- +``` + +## Page Data + +This plugin will add a `git` field to page data. + +After using this plugin, you can get the collected git information in page data: + +```ts +import type { GitPluginPageData } from '@vuepress/plugin-git' +import { usePageData } from 'vuepress/client' + +export default { + setup() { + const page = usePageData() + console.log(page.value.git) + }, +} +``` + +### git.createdTime + +- Type: `number` + +- Details: + + Unix timestamp in milliseconds of the first commit of the page. + + This attribute would take the minimum of the first commit timestamps of the current page and the files listed in [gitInclude](#gitinclude). + +### git.updatedTime + +- Type: `number` + +- Details: + + Unix timestamp in milliseconds of the last commit of the page. + + This attribute would take the maximum of the last commit timestamps of the current page and the files listed in [gitInclude](#gitinclude). + +### git.contributors + +- Type: `GitContributor[]` + +```ts +interface GitContributor { + name: string + email: string + commits: number +} +``` + +- Details: + + The contributors information of the page. + + This attribute would also include contributors to the files listed in [gitInclude](#gitinclude). diff --git a/docs/plugins/google-analytics.md b/docs/plugins/google-analytics.md new file mode 100644 index 0000000000..c35b267ddd --- /dev/null +++ b/docs/plugins/google-analytics.md @@ -0,0 +1,78 @@ +# google-analytics + + + +Integrate [Google Analytics](https://analytics.google.com/) into VuePress. + +This plugin will import [gtag.js](https://developers.google.com/analytics/devguides/collection/gtagjs) for [Google Analytics 4](https://support.google.com/analytics/answer/10089681). + +## Usage + +```bash +npm i -D @vuepress/plugin-google-analytics@next +``` + +```ts +import { googleAnalyticsPlugin } from '@vuepress/plugin-google-analytics' + +export default { + plugins: [ + googleAnalyticsPlugin({ + // options + }), + ], +} +``` + +## Reporting Events + +Google Analytics will [automatically collect some events](https://support.google.com/analytics/answer/9234069), such as `page_view`, `first_visit`, etc. + +So if you only want to collect some basic data of your site, you don't need to do anything else except setting the [Measurement ID](#id) correctly. + +After using this plugin, the global `gtag()` function is available on the `window` object, and you can use it for [custom events reporting](https://developers.google.com/analytics/devguides/collection/ga4/events). + +## Options + +### id + +- Type: `string` + +- Details: + + The Measurement ID of Google Analytics 4, which should start with `'G-'`. + + You can follow the instructions [here](https://support.google.com/analytics/answer/9539598) to find your Measurement ID. Notice the difference between Google Analytics 4 Measurement ID (i.e. "G-" ID) and Universal Analytics Tracking ID (i.e. "UA-" ID). + +- Example: + +```ts +export default { + plugins: [ + googleAnalyticsPlugin({ + id: 'G-XXXXXXXXXX', + }), + ], +} +``` + +### debug + +- Type: `boolean` + +- Details: + + Set to `true` to enable sending events to DebugView. [See more information on DebugView](https://support.google.com/analytics/answer/7201382). + +- Example: + +```ts +export default { + plugins: [ + googleAnalyticsPlugin({ + id: 'G-XXXXXXXXXX', + debug: true, + }), + ], +} +``` diff --git a/docs/plugins/medium-zoom.md b/docs/plugins/medium-zoom.md new file mode 100644 index 0000000000..362dce2544 --- /dev/null +++ b/docs/plugins/medium-zoom.md @@ -0,0 +1,100 @@ +# medium-zoom + + + +Integrate [medium-zoom](https://github.com/francoischalifour/medium-zoom#readme) into VuePress, which can provide the ability to zoom images. + +This plugin has been integrated into the default theme. + +## Usage + +```bash +npm i -D @vuepress/plugin-medium-zoom@next +``` + +```ts +import { mediumZoomPlugin } from '@vuepress/plugin-medium-zoom' + +export default { + plugins: [ + mediumZoomPlugin({ + // options + }), + ], +} +``` + +## Options + +### selector + +- Type: `string` + +- Default: `':not(a) > img'` + +- Details: + + Selector of zoomable images. + + By default this plugin will make all images zoomable except those inside `` tags. + +### delay + +- Type: `number` + +- Default: `500` + +- Details: + + Delay in milliseconds. + + After navigating to a new page, this plugin will make images zoomable with a delay. + +### zoomOptions + +- Type: `Object` + +- Details: + + Options for medium-zoom. + +- Also see: + - [medium-zoom > Options](https://github.com/francoischalifour/medium-zoom#options) + +## Styles + +You can customize most of the zoom styles via [zoomOptions](#zoomoptions), while this plugin also provides some CSS variables for additional customization: + +@[code css](@vuepress/plugin-medium-zoom/src/client/styles/vars.css) + +## Composition API + +### useMediumZoom + +- Details: + + Returns the `Zoom` instance that used by this plugin, so that you can use the instance [methods](https://github.com/francoischalifour/medium-zoom#methods) directly. + + This plugin will make images zoomable after navigating to current page. But if you are going to add new images dynamically, you may need this method to make those new images zoomable, too. + + This plugin adds an extra `refresh` method on the `Zoom` instance, which will call `zoom.detach()` then `zoom.attach()` with the [selector](#selector) as the default parameter. It will help you to refresh the zoomable images for current page. + +- Example: + +```ts +import { nextTick } from 'vue' +import { useMediumZoom } from '@vuepress/plugin-medium-zoom/client' + +export default { + setup() { + const zoom = useMediumZoom() + + // ... do something to add new images in current page + + // then you may need to call `refresh` manually to make those new images zoomable + nextTick(() => { + zoom.refresh() + }) + }, +} +``` diff --git a/docs/plugins/nprogress.md b/docs/plugins/nprogress.md new file mode 100644 index 0000000000..2f501345a9 --- /dev/null +++ b/docs/plugins/nprogress.md @@ -0,0 +1,35 @@ +--- +title: nprogress +--- + + + + + +# nprogress Plugin + + + +Integrate [nprogress](https://github.com/rstacruz/nprogress) into VuePress, which can provide a progress bar when navigating to another page. + +This plugin has been integrated into the default theme. + +## Usage + +```bash +npm i -D @vuepress/plugin-nprogress@next +``` + +```ts +import { nprogressPlugin } from '@vuepress/plugin-nprogress' + +export default { + plugins: [nprogressPlugin()], +} +``` + +## Styles + +You can customize the style of the progress bar via CSS variables: + +@[code css](@vuepress/plugin-nprogress/src/client/styles/vars.css) diff --git a/docs/plugins/palette.md b/docs/plugins/palette.md new file mode 100644 index 0000000000..a5a5ecb842 --- /dev/null +++ b/docs/plugins/palette.md @@ -0,0 +1,203 @@ +# palette + + + +Provide palette support for your theme. + +This plugin is mainly used to develop themes, and has been integrated into the default theme. You won't need to use it directly in most cases. + +For theme authors, this plugin will help you to provide styles customization for users. + +## Usage + +```bash +npm i -D @vuepress/plugin-palette@next +``` + +```ts +import { palettePlugin } from '@vuepress/plugin-palette' + +export default { + plugins: [ + palettePlugin({ + // options + }), + ], +} +``` + +## Palette and Style + +This plugin will provide a `@vuepress/plugin-palette/palette` (palette file) and a `@vuepress/plugin-palette/style` (style file) to be imported in your theme styles. + +The palette file is used for defining style variables, so it's likely to be imported at the beginning of your theme styles. For example, users can define [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties), [SASS variables](https://sass-lang.com/documentation/variables), [LESS variables](http://lesscss.org/features/#variables-feature) or [Stylus variables](https://stylus-lang.com/docs/variables.html) in the palette, and then you can use those variables in your theme styles. + +The style file is used for overriding the default styles or adding extra styles, so it's likely to be imported at the end of your theme styles. + +## Usage + +Use this plugin in your theme, assuming you are using SASS: + +```ts +export default { + // ... + plugins: [palettePlugin({ preset: 'sass' })], +} +``` + +### Usage of Palette + +Import the plugin's palette file where your theme needs to use the corresponding variables, such as in the `Layout.vue` file: + +```vue + + + +``` + +Then users can customize variables in `.vuepress/styles/palette.scss`: + +```scss +$color: green; +``` + +### Usage of Style + +Import the plugin's style file after your theme's styles, for example, in the `clientConfigFile`: + +```ts +// import your theme's style file +import 'path/to/your/theme/style' +// import the plugin's style file +import '@vuepress/plugin-palette/style' +``` + +Then users can add extra styles in `.vuepress/styles/index.scss` and override the default styles of your theme: + +```scss +h1 { + font-size: 2.5rem; +} +``` + +## Options + +### preset + +- Type: `'css' | 'sass' | 'less' | 'stylus'` + +- Default: `'css'` + +- Details: + + Set preset for other options. + + If you don't need advanced customization of the plugin, it's recommended to only set this option and omit other options. + +### userPaletteFile + +- Type: `string` + +- Default: + + - css: `'.vuepress/styles/palette.css'` + - sass: `'.vuepress/styles/palette.scss'` + - less: `'.vuepress/styles/palette.less'` + - stylus: `'.vuepress/styles/palette.styl'` + +- Details: + + File path of the user palette file, relative to source directory. + + The default value depends on the [preset](#preset) option. + + The file is where users define style variables, and it's recommended to keep the default file path as a convention. + +### tempPaletteFile + +- Type: `string` + +- Default: + + - css: `'styles/palette.css'` + - sass: `'styles/palette.scss'` + - less: `'styles/palette.less'` + - stylus: `'styles/palette.styl'` + +- Details: + + File path of the generated palette temp file, relative to temp directory. + + The default value depends on the [preset](#preset) option. + + You should import the palette file via `'@vuepress/plugin-palette/palette'` alias, so you don't need to change this option in most cases. + +### userStyleFile + +- Type: `string` + +- Default: + + - css: `'.vuepress/styles/index.css'` + - sass: `'.vuepress/styles/index.scss'` + - less: `'.vuepress/styles/index.less'` + - stylus: `'.vuepress/styles/index.styl'` + +- Details: + + File path of the user style file, relative to source directory. + + The default value depends on the [preset](#preset) option. + + The file is where users override default styles or add extra styles, and it's recommended to keep the default file path as a convention. + +### tempStyleFile + +- Type: `string` + +- Default: + + - css: `'styles/index.css'` + - sass: `'styles/index.scss'` + - less: `'styles/index.less'` + - stylus: `'styles/index.styl'` + +- Details: + + File path of the generated style temp file, relative to temp directory. + + The default value depends on the [preset](#preset) option. + + You should import the style file via `'@vuepress/plugin-palette/style'` alias, so you don't need to change this option in most cases. + +### importCode + +- Type: `(filePath: string) => string` + +- Default: + + - css: `` (filePath) => `@import '${filePath}';\n` `` + - sass: `` (filePath) => `@forward 'file:///${filePath}';\n` `` + - less: `` (filePath) => `@import '${filePath}';\n` `` + - stylus: `` (filePath) => `@require '${filePath}';\n` `` + +- Details: + + Function to generate import code. + + The default value depends on the [preset](#preset) option. + + This option is used for generating [tempPaletteFile](#temppalettefile) and [tempStyleFile](#tempstylefile), and you don't need to change this option in most cases. diff --git a/docs/plugins/prismjs.md b/docs/plugins/prismjs.md new file mode 100644 index 0000000000..d72625f222 --- /dev/null +++ b/docs/plugins/prismjs.md @@ -0,0 +1,43 @@ +# prismjs + + + +This plugin will enable syntax highlighting for markdown code fence with [Prism.js](https://prismjs.com/). + +This plugin has been integrated into the default theme. + +Notice that this plugin would only tokenize the code fence without adding styles. When using it with a custom theme, you may need to choose and import Prism.js style theme yourself. + +## Usage + +```bash +npm i -D @vuepress/plugin-prismjs@next +``` + +```ts +import { prismjsPlugin } from '@vuepress/plugin-prismjs' + +export default { + plugins: [ + prismjsPlugin({ + // options + }), + ], +} +``` + +## Options + +### preloadLanguages + +- Type: `string[]` + +- Default: `['markdown', 'jsdoc', 'yaml']` + +- Details: + + Languages to preload. + + By default, languages will be loaded on demand when parsing markdown files. + + However, Prism.js has [some potential issues](https://github.com/PrismJS/prism/issues/2716) about loading languages dynamically. To avoid them, you can preload languages via this option. diff --git a/docs/plugins/pwa-popup.md b/docs/plugins/pwa-popup.md new file mode 100644 index 0000000000..f305e526f6 --- /dev/null +++ b/docs/plugins/pwa-popup.md @@ -0,0 +1,72 @@ +# pwa-popup + + + +Provide a popup component for users to activate the new PWA service worker manually. + +This plugin must be used together with [pwa plugin](./pwa.md), and the `skipWaiting` option must not be set to `true`. + +When the new service worker is ready, a popup will appear in the right bottom of the page to ask users to activate the waiting service worker. + +## Usage + +```bash +npm i -D @vuepress/plugin-pwa-popup@next +``` + +```ts +import { pwaPlugin } from '@vuepress/plugin-pwa' +import { pwaPopupPlugin } from '@vuepress/plugin-pwa-popup' + +export default { + plugins: [ + pwaPlugin(), + pwaPopupPlugin({ + // options + }), + ], +} +``` + +## Options + +### locales + +- Type: `Record` + +- Details: + + The messages of the popup in different locales. + + If this option is not specified, it will fallback to default messages. + +- Example: + +```ts +export default { + plugins: [ + pwaPlugin(), + pwaPopupPlugin({ + locales: { + '/': { + message: 'New content is available.', + buttonText: 'Refresh', + }, + '/zh/': { + message: '发现新内容可用', + buttonText: '刷新', + }, + }, + }), + ], +} +``` + +- Also see: + - [Guide > I18n](../../guide/i18n.md) + +## Styles + +You can customize the style of the popup via CSS variables: + +@[code css](@vuepress/plugin-pwa-popup/src/client/styles/vars.css) diff --git a/docs/plugins/pwa.md b/docs/plugins/pwa.md new file mode 100644 index 0000000000..e94f76971c --- /dev/null +++ b/docs/plugins/pwa.md @@ -0,0 +1,173 @@ +# pwa + + + +Make your VuePress site a [Progressive Web Application (PWA)](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps). + +This plugin uses [workbox-build](https://developers.google.com/web/tools/workbox/modules/workbox-build) to generate service worker file, and uses [register-service-worker](https://github.com/yyx990803/register-service-worker) to register service worker. + +## Usage + +```bash +npm i -D @vuepress/plugin-pwa@next +``` + +```ts +import { pwaPlugin } from '@vuepress/plugin-pwa' + +export default { + plugins: [ + pwaPlugin({ + // options + }), + ], +} +``` + +## Web App Manifests + +To make your website fully compliant with PWA, you need to create a [Web app manifests](https://developer.mozilla.org/en-US/docs/Web/Manifest) file and set the icons, colors, etc. for your PWA. + +You need to put your manifest file and icons into the [public files directory](../../guide/assets.md#public-files). In the following example, we assume that you are using the default public directory `.vuepress/public`. + +1. Create manifest file + +Typically `.vuepress/public/manifest.webmanifest`: + +```json +{ + "name": "VuePress", + "short_name": "VuePress", + "description": "Vue-powered Static Site Generator", + "start_url": "/index.html", + "display": "standalone", + "background_color": "#fff", + "theme_color": "#3eaf7c", + "icons": [ + { + "src": "/images/icons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/images/icons/android-chrome-384x384.png", + "sizes": "384x384", + "type": "image/png" + } + ] +} +``` + +2. Generate PWA icons + +To make your PWA more accessible, you need to generate some icons, and put them inside the public directory. + +Make sure the path of icons matches the `icons` field in your manifest file: + +- `.vuepress/public/images/icons/android-chrome-192x192.png` +- `.vuepress/public/images/icons/android-chrome-384x384.png` + +::: tip +Some tools can help to do that. For example, [Favicon Generator](https://realfavicongenerator.net/) would help you to generate icons together with a sample manifest file. +::: + +3. Set tags in head + +You also need to set some tags via [head](../config.md#head) option to [deploy the manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest#deploying_a_manifest_with_the_link_tag): + +```ts +export default { + head: [ + ['link', { rel: 'manifest', href: '/manifest.webmanifest' }], + ['meta', { name: 'theme-color', content: '#3eaf7c' }], + // ...other tags + ], +} +``` + +## Options + +This plugin accepts all parameters of workbox-build's [generateSW method](https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-build#.generateSW) in its options, except `globDirectory` and `swDest`. + +For example, you can set `skipWaiting: true` to auto activate the new service worker once it is ready: + +```ts +export default { + plugins: [ + pwaPlugin({ + skipWaiting: true, + }), + ], +} +``` + +But if you omit `skipWaiting` or set it to `false`, you have to activate the new service worker manually: + +- For users, you can use our [pwa-popup](./pwa-popup.md) plugin together. +- For developers, you can use our [composition API](#composition-api) to take control of the service worker behavior. + +### serviceWorkerFilename + +- Type: `string` + +- Default: `'service-worker.js'` + +- Details: + + File path of the generated service worker file, which is relative to the [dest](../config.md#dest) directory. + + The service worker file will only be generated in `build` mode. + +## Composition API + +### usePwaEvent + +- Details: + + Returns the event emitter of this plugin. + + You can add listener function to events that provided by [register-service-worker](https://github.com/yyx990803/register-service-worker). + +- Example: + +```ts +import { usePwaEvent } from '@vuepress/plugin-pwa/client' + +export default { + setup() { + const event = usePwaEvent() + event.on('ready', (registration) => { + console.log('Service worker is active.') + }) + }, +} +``` + +### useSkipWaiting + +- Parameters: + +| Parameter | Type | Description | +| ------------ | --------------------------- | -------------------------------------------------------- | +| registration | `ServiceWorkerRegistration` | The registration of the service worker you want activate | + +- Details: + + Call [skipWaiting()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting) to activate the waiting service worker. + +- Example: + +```ts +import { usePwaEvent, useSkipWaiting } from '@vuepress/plugin-pwa/client' + +export default { + setup() { + const event = usePwaEvent() + event.on('updated', (registration) => { + console.log('The waiting service worker is available.') + // activate the waiting service worker + useSkipWaiting(registration) + }) + }, +} +``` diff --git a/docs/plugins/register-components.md b/docs/plugins/register-components.md new file mode 100644 index 0000000000..4515860809 --- /dev/null +++ b/docs/plugins/register-components.md @@ -0,0 +1,135 @@ +# register-components + + + +Register Vue components from component files or directory automatically. + +## Usage + +```bash +npm i -D @vuepress/plugin-register-components@next +``` + +```ts +import { registerComponentsPlugin } from '@vuepress/plugin-register-components' + +export default { + plugins: [ + registerComponentsPlugin({ + // options + }), + ], +} +``` + +## Options + +### components + +- Type: `Record` + +- Default: `{}` + +- Details: + + An object that defines name of components and their corresponding file path. + + The key will be used as the component name, and the value is an absolute path of the component file. + + If the component name from this option conflicts with [componentsDir](#componentsdir) option, this option will have a higher priority. + +- Example: + +```ts +import { getDirname, path } from 'vuepress/utils' + +const __dirname = getDirname(import.meta.url) + +export default { + plugins: [ + registerComponentsPlugin({ + components: { + FooBar: path.resolve(__dirname, './components/FooBar.vue'), + }, + }), + ], +} +``` + +### componentsDir + +- Type: `string | null` + +- Default: `null` + +- Details: + + Absolute path to the components directory. + + Files in this directory which are matched with [componentsPatterns](#componentspatterns) will be registered as Vue components automatically. + +- Example: + +```ts +import { getDirname, path } from 'vuepress/utils' + +const __dirname = getDirname(import.meta.url) + +export default { + plugins: [ + registerComponentsPlugin({ + componentsDir: path.resolve(__dirname, './components'), + }), + ], +} +``` + +Components directory: + +```bash +components +├─ FooBar.vue +└─ Baz.vue +``` + +Components will be registered like this: + +```ts +import { defineAsyncComponent } from 'vue' + +app.component( + 'FooBar', + defineAsyncComponent(() => import('/path/to/components/FooBar.vue')), +) + +app.component( + 'Baz', + defineAsyncComponent(() => import('/path/to/components/Baz.vue')), +) +``` + +### componentsPatterns + +- Type: `string[]` + +- Default: `['**/*.vue']` + +- Details: + + Patterns to match component files using [globby](https://github.com/sindresorhus/globby). + + The patterns are relative to [componentsDir](#componentsdir). + +### getComponentName + +- Type: `(filename: string) => string` + +- Default: `(filename) => path.trimExt(filename.replace(/\/|\\/g, '-'))` + +- Details: + + A function to get component name from the filename. + + It will only take effect on the files in the [componentsDir](#componentsdir) which are matched with the [componentsPatterns](#componentspatterns). + + Notice that the `filename` is a filepath relative to [componentsDir](#componentsdir). diff --git a/docs/plugins/search.md b/docs/plugins/search.md new file mode 100644 index 0000000000..f563b8a757 --- /dev/null +++ b/docs/plugins/search.md @@ -0,0 +1,166 @@ +# search + + + +Provide local search to your documentation site. + +::: tip +Default theme will add search box to the navbar once you configure this plugin correctly. + +This plugin may not be used directly in other themes, so you'd better refer to the documentation of your theme for more details. +::: + +## Usage + +```bash +npm i -D @vuepress/plugin-search@next +``` + +```ts +import { searchPlugin } from '@vuepress/plugin-search' + +export default { + plugins: [ + searchPlugin({ + // options + }), + ], +} +``` + +## Local Search Index + +This plugin will generate search index from your pages locally, and load the search index file when users enter your site. In other words, this is a lightweight built-in search which does not require any external requests. + +However, when your site has a large number of pages, the size of search index file would be very large, which could slow down the page loading speed. In this case, we recommend you to use a more professional solution - [docsearch](./docsearch.md). + +## Options + +### locales + +- Type: `Record` + +- Details: + + The text of the search box in different locales. + + If this option is not specified, it will fallback to default text. + +- Example: + +```ts +export default { + plugins: [ + searchPlugin({ + locales: { + '/': { + placeholder: 'Search', + }, + '/zh/': { + placeholder: '搜索', + }, + }, + }), + ], +} +``` + +- Also see: + - [Guide > I18n](../../guide/i18n.md) + +### hotKeys + +- Type: `(string | HotKeyOptions)[]` + +@[code ts](@vuepress/plugin-search/src/shared/hotKey.ts) + +- Default: `['s', '/']` + +- Details: + + Specify the [event.key](http://keycode.info/) of the hotkeys. + + When hotkeys are pressed, the search box input will be focused. + + Set to an empty array to disable hotkeys. + +### maxSuggestions + +- Type: `number` + +- Default: `5` + +- Details: + + Specify the maximum number of search results. + +### isSearchable + +- Type: `(page: Page) => boolean` + +- Default: `() => true` + +- Details: + + A function to determine whether a page should be included in the search index. + + - Return `true` to include the page. + - Return `false` to exclude the page. + +- Example: + +```ts +export default { + plugins: [ + searchPlugin({ + // exclude the homepage + isSearchable: (page) => page.path !== '/', + }), + ], +} +``` + +### getExtraFields + +- Type: `(page: Page) => string[]` + +- Default: `() => []` + +- Details: + + A function to add extra fields to the search index of a page. + + By default, this plugin will use page title and headers as the search index. This option could help you to add more searchable fields. + +- Example: + +```ts +export default { + plugins: [ + searchPlugin({ + // allow searching the `tags` frontmatter + getExtraFields: (page) => page.frontmatter.tags ?? [], + }), + ], +} +``` + +## Styles + +You can customize the style of the search box via CSS variables: + +@[code css](@vuepress/plugin-search/src/client/styles/vars.css) + +## Components + +### SearchBox + +- Details: + + This plugin will register a `` component globally, and you can use it without any props. + + Put this component to where you want to place the search box. For example, default theme puts this component to the end of the navbar. + +::: tip +This component is mainly used for theme development. You don't need to use it directly in most cases. +::: diff --git a/docs/plugins/shiki.md b/docs/plugins/shiki.md new file mode 100644 index 0000000000..46dabd2977 --- /dev/null +++ b/docs/plugins/shiki.md @@ -0,0 +1,75 @@ +# shiki + + + +This plugin will enable syntax highlighting for markdown code fence with [Shiki](https://shiki.matsu.io/) ([Shikiji](https://shikiji.netlify.app/)). + +::: tip +[Shiki](https://shiki.matsu.io/) is the syntax highlighter being used by VSCode. It has higher fidelity, but it could be slower than [Prism.js](https://prismjs.com/), especially when you have a lot of code blocks. + +You could consider disabling this plugin in `dev` mode to get better development experience. +::: + +## Usage + +```bash +npm i -D @vuepress/plugin-shiki@next +``` + +```ts +import { shikiPlugin } from '@vuepress/plugin-shiki' + +export default { + plugins: [ + shikiPlugin({ + // options + langs: ['ts', 'json', 'vue', 'md', 'bash', 'diff'], + }), + ], +} +``` + +## Options + +### langs + +- Type: `ShikiLang[]` + +- Details: + + Languages of code blocks to be parsed by shikiji. + + This option will be forwarded to `getHighlighter()` method of shikiji. + + You need to provide the languages list you are using explicitly, otherwise shikiji won't load any languages. + +- Also see: + - [shikiji > Languages](https://shikiji.netlify.app/languages) + +### theme + +- Type: `ShikiTheme` + +- Default: `'nord'` + +- Details: + + Theme of shikiji. + + This option will be forwarded to `codeToHtml()` method of shikiji. + +- Also see: + - [shikiji > Themes](https://shikiji.netlify.app/themes) + +### themes + +- Type: `Record<'dark' | 'light', ShikiTheme>` + +- Details: + + Dark / Light Dual themes of shikiji. + + This option will be forwarded to `codeToHtml()` method of shikiji. + +- Also see: + - [shikiji > Dual Themes](https://shikiji.netlify.app/guide/dual-themes) diff --git a/docs/plugins/theme-data.md b/docs/plugins/theme-data.md new file mode 100644 index 0000000000..ecf2ea5a20 --- /dev/null +++ b/docs/plugins/theme-data.md @@ -0,0 +1,118 @@ +# theme-data + + + +Provide client data for your theme, with VuePress [i18n](../../guide/i18n.md) support. + +This plugin is mainly used to develop themes, and has been integrated into the default theme. You won't need to use it directly in most cases. + +For theme authors, this plugin will help you to use the same i18n mechanism as VuePress and the default theme. But if you don't want to provide i18n support, or you want to implement in your own way, you don't need this plugin. + +## Usage + +```bash +npm i -D @vuepress/plugin-theme-data@next +``` + +```ts +import { themeDataPlugin } from '@vuepress/plugin-theme-data' + +export default { + plugins: [ + themeDataPlugin({ + // options + }), + ], +} +``` + +## Options + +### themeData + +- Type: `ThemeData` + +- Default: `{}` + +- Details: + + The theme data object that you want to use in client side. + + You can provide theme data in Node side via this option, and use it in client side via [useThemeData](#useThemeData) and [useThemeLocaleData](#useThemeLocaleData). + +- Example: + +```ts +export default { + plugins: [ + themeDataPlugin({ + themeData: { + foo: 'foo', + locales: { + '/zh/': { + foo: 'zh-foo', + }, + }, + }, + }), + ], +} +``` + +::: warning +The theme data object will be processed by `JSON.stringify()` before forwarding to client side, so you should ensure that you are providing a JSON-friendly object. +::: + +## Composition API + +### useThemeData + +- Details: + + Returns the theme data ref object. + + The value is provided by [themeData](#themeData) option. + +- Example: + +```ts +import { useThemeData } from '@vuepress/plugin-theme-data/client' +import type { ThemeData } from '@vuepress/plugin-theme-data/client' + +type MyThemeData = ThemeData<{ + foo: string +}> + +export default { + setup() { + const themeData = useThemeData() + console.log(themeData.value) + }, +} +``` + +### useThemeLocaleData + +- Details: + + Returns the theme data ref object in current locale. + + The properties of current locale has been merged into the root-level properties. + +- Example: + +```ts +import { useThemeLocaleData } from '@vuepress/plugin-theme-data/client' +import type { ThemeData } from '@vuepress/plugin-theme-data/client' + +type MyThemeData = ThemeData<{ + foo: string +}> + +export default { + setup() { + const themeLocaleData = useThemeLocaleData() + console.log(themeLocaleData.value) + }, +} +``` diff --git a/docs/plugins/toc.md b/docs/plugins/toc.md new file mode 100644 index 0000000000..82ffdf85f0 --- /dev/null +++ b/docs/plugins/toc.md @@ -0,0 +1,172 @@ +# toc + + + +This plugin will provide a table-of-contents (TOC) component. + +## Usage + +```bash +npm i -D @vuepress/plugin-toc@next +``` + +```ts +import { tocPlugin } from '@vuepress/plugin-toc' + +export default { + plugins: [ + tocPlugin({ + // options + }), + ], +} +``` + +## Differences with Markdown TOC Syntax + +Similar to the [Table of Contents Markdown Syntax](../../guide/markdown.md#table-of-contents), the TOC component that provided by this plugin could be used in your markdown content directly: + +```md + + +[[toc]] + + + +``` + +Both of them can be pre-rendered correctly in build mode. However, there are some differences between them. + +The markdown syntax `[[toc]]` could only be used in markdown files. It is parsed by markdown-it, and the generated TOC is static content. + +The component `` could be used in both markdown files and vue files. It is loaded by vue, and the generated TOC is a vue component. + +This plugin could work together with [@vuepress/plugin-active-header-links](./active-header-links.md) by setting the [headerLinkSelector](./active-header-links.md#headerlinkselector) to match the `linkClass` option. When the page scroll to a certain header anchor, this corresponding link will be added `linkActiveClass` class name. + +Therefore, this plugin is more useful for theme developers. + +## Options + +### componentName + +- Type: `string` + +- Default: `'Toc'` + +- Details: + + Specify the name of the TOC component. + +### defaultPropsOptions + +- Type: `Partial` + +- Default: `{}` + +- Details: + + Override the default values of the component [options](#options-1) prop. + +## Component Props + +The TOC component also accepts props for customization. + +```vue + +``` + +### headers + +- Type: `PageHeader[]` + +```ts +interface PageHeader { + level: number + title: string + slug: string + children: PageHeader[] +} +``` + +- Details: + + Specify the headers array to render. + + If this prop is not specified, the headers of current page will be used. + +### options + +- Type: `Partial` + +```ts +interface TocPropsOptions { + containerTag: string + containerClass: string + listClass: string + itemClass: string + linkTag: 'a' | 'RouterLink' + linkClass: string + linkActiveClass: string + linkChildrenActiveClass: string +} +``` + +- Default: + + Following default values can be overridden by [defaultPropsOptions](#defaultpropsoptions). + +```ts +const defaultOptions = { + containerTag: 'nav', + containerClass: 'vuepress-toc', + listClass: 'vuepress-toc-list', + itemClass: 'vuepress-toc-item', + linkTag: 'RouterLink', + linkClass: 'vuepress-toc-link', + linkActiveClass: 'active', + linkChildrenActiveClass: 'active', +} +``` + +- Details: + + Customize the TOC component. + + If the `containerTag` is set to an empty string `''`, the `