├── .stylelintignore ├── .czrc ├── pnpm-workspace.yaml ├── .eslintignore ├── packages ├── components │ ├── index.ts │ ├── npm-badge │ │ ├── examples │ │ │ └── basic.vue │ │ ├── docs │ │ │ ├── index.zh-CN.md │ │ │ └── index.md │ │ ├── index.ts │ │ └── src │ │ │ └── npm-badge.vue │ ├── shim.d.ts │ ├── CHANGELOG.md │ └── package.json ├── plugin-code-block │ ├── src │ │ ├── shared │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── client-iframe │ │ │ ├── components │ │ │ │ ├── index.ts │ │ │ │ └── Vuepress.ts │ │ │ ├── composables │ │ │ │ ├── index.ts │ │ │ │ └── siteData.ts │ │ │ ├── router.ts │ │ │ └── app.ts │ │ ├── node │ │ │ ├── prepare │ │ │ │ ├── index.ts │ │ │ │ ├── prepareClientIframe.ts │ │ │ │ └── prepareVmiComponents.ts │ │ │ ├── plugins │ │ │ │ ├── index.ts │ │ │ │ ├── proxy.ts │ │ │ │ ├── hmr.ts │ │ │ │ └── iframe.ts │ │ │ ├── utils │ │ │ │ ├── index.ts │ │ │ │ ├── cache.ts │ │ │ │ ├── highlight.ts │ │ │ │ ├── resolve.ts │ │ │ │ ├── scan.ts │ │ │ │ └── dep.ts │ │ │ ├── resolve │ │ │ │ ├── index.ts │ │ │ │ ├── resolveScriptSetup.ts │ │ │ │ ├── resolveHtmlBlock.ts │ │ │ │ ├── resolvePageHeaders.ts │ │ │ │ └── resolveOptions.ts │ │ │ ├── parse │ │ │ │ ├── index.ts │ │ │ │ ├── parseInline.ts │ │ │ │ ├── parseRaw.ts │ │ │ │ ├── parseNodeAttrs.ts │ │ │ │ ├── readSource.ts │ │ │ │ ├── parseCodeBlock.ts │ │ │ │ └── parseVue.ts │ │ │ ├── index.ts │ │ │ └── codeBlockPlugin.ts │ │ ├── client │ │ │ ├── shim.d.ts │ │ │ ├── index.ts │ │ │ ├── components │ │ │ │ ├── Previewer.vue │ │ │ │ ├── SourceCodeItem.vue │ │ │ │ └── Example.vue │ │ │ ├── icons │ │ │ │ ├── Open.vue │ │ │ │ ├── CopySuccess.vue │ │ │ │ ├── File.vue │ │ │ │ ├── Copy.vue │ │ │ │ ├── Edit.vue │ │ │ │ ├── Refresh.vue │ │ │ │ ├── UnExpand.vue │ │ │ │ └── Expand.vue │ │ │ ├── clientConfig.ts │ │ │ └── styles │ │ │ │ └── index.scss │ │ └── client-internal │ │ │ ├── siteData.d.ts │ │ │ ├── clientConfigs.d.ts │ │ │ ├── clientIframeConfigs.d.ts │ │ │ ├── pagesVmiComponents.d.ts │ │ │ └── global.d.ts │ ├── tsconfig.build.json │ ├── CHANGELOG.md │ └── package.json ├── plugin-page-map │ ├── src │ │ └── node │ │ │ ├── plugins │ │ │ ├── index.ts │ │ │ └── hmr.ts │ │ │ ├── utils │ │ │ ├── index.ts │ │ │ └── createPage.ts │ │ │ ├── resolve │ │ │ ├── index.ts │ │ │ ├── resolvePages.ts │ │ │ └── resolveOptions.ts │ │ │ ├── index.ts │ │ │ ├── dev │ │ │ ├── index.ts │ │ │ ├── handlePageUnlink.ts │ │ │ ├── handlePageAdd.ts │ │ │ ├── pageDepsHelper.ts │ │ │ ├── handlePageChange.ts │ │ │ └── watchPageFiles.ts │ │ │ └── pageMapPlugin.ts │ ├── tsconfig.build.json │ ├── CHANGELOG.md │ └── package.json ├── plugin-page-missing │ ├── src │ │ └── node │ │ │ ├── resolve │ │ │ ├── index.ts │ │ │ └── resolvePageMissing.ts │ │ │ ├── index.ts │ │ │ └── pageMissingPlugin.ts │ ├── tsconfig.build.json │ ├── CHANGELOG.md │ └── package.json └── theme-vmi │ ├── src │ ├── shared │ │ ├── index.ts │ │ ├── page.ts │ │ └── nav.ts │ ├── node │ │ ├── utils │ │ │ ├── index.ts │ │ │ ├── resolveContainerPluginOptions.ts │ │ │ └── assignDefaultLocaleOptions.ts │ │ └── index.ts │ └── client │ │ ├── index.ts │ │ ├── components │ │ ├── HomeContent.vue │ │ ├── global │ │ │ ├── index.ts │ │ │ ├── Badge.vue │ │ │ └── CodeGroupItem.vue │ │ ├── Sidebar.vue │ │ ├── Home.vue │ │ ├── DropdownTransition.vue │ │ ├── Page.vue │ │ ├── ToggleSidebarButton.vue │ │ ├── HomeFooter.vue │ │ ├── HomeFeatures.vue │ │ ├── NavbarBrand.vue │ │ ├── SidebarItems.vue │ │ ├── ToggleColorModeButton.vue │ │ ├── SidebarItem.vue │ │ ├── Navbar.vue │ │ ├── HomeHero.vue │ │ ├── PageNav.vue │ │ ├── AutoLink.vue │ │ ├── NavbarDropdown.vue │ │ └── PageMeta.vue │ │ ├── utils │ │ ├── index.ts │ │ ├── resolveRepoType.ts │ │ ├── isActiveSidebarItem.ts │ │ └── resolveEditLink.ts │ │ ├── shim.d.ts │ │ ├── composables │ │ ├── index.ts │ │ ├── useThemeData.ts │ │ ├── useScrollPromise.ts │ │ ├── useNavLink.ts │ │ ├── useResolveRouteWithRedirect.ts │ │ └── useDarkMode.ts │ │ ├── styles │ │ ├── index.scss │ │ ├── _variables.scss │ │ ├── _mixins.scss │ │ ├── transitions.scss │ │ ├── badge.scss │ │ ├── toc.scss │ │ ├── arrow.scss │ │ ├── code-dark.scss │ │ ├── vars-dark.scss │ │ ├── code-group.scss │ │ ├── page.scss │ │ ├── custom-container.scss │ │ ├── sidebar.scss │ │ ├── normalize.scss │ │ ├── home.scss │ │ └── navbar.scss │ │ ├── layouts │ │ ├── NotFound.vue │ │ └── Layout.vue │ │ └── config.ts │ ├── tsconfig.build.json │ ├── CHANGELOG.md │ ├── templates │ └── build.html │ └── package.json ├── .prettierignore ├── docs ├── zh │ ├── guide │ │ ├── demos │ │ │ ├── demo-tabs-user.vue │ │ │ ├── demo-file.d.ts │ │ │ ├── demo-file.json │ │ │ ├── demo-transform.vue │ │ │ ├── demo-tabs-task.tsx │ │ │ ├── demo-raw.vue │ │ │ ├── demo-inline.vue │ │ │ ├── demo-iframe.vue │ │ │ ├── demo-basic.vue │ │ │ ├── demo-debug.vue │ │ │ ├── demo-desc.vue │ │ │ ├── demo-title.vue │ │ │ ├── demo-iframe-url.vue │ │ │ └── demo-tabs.vue │ │ ├── README.md │ │ ├── theme-vmi.md │ │ └── page-map.md │ ├── components │ │ └── README.md │ └── README.md ├── .vuepress │ ├── configs │ │ ├── navbar │ │ │ ├── index.ts │ │ │ ├── zh.ts │ │ │ └── en.ts │ │ ├── sidebar │ │ │ ├── index.ts │ │ │ ├── en.ts │ │ │ └── zh.ts │ │ └── index.ts │ ├── public │ │ ├── favicon.ico │ │ ├── demos │ │ │ ├── dark.png │ │ │ └── light.png │ │ └── images │ │ │ ├── hero.png │ │ │ └── logo.png │ ├── client-iframe.ts │ ├── client.ts │ └── config.ts ├── components │ └── README.md ├── package.json ├── README.md └── guide │ ├── page-missing.md │ └── README.md ├── .husky ├── pre-commit └── commit-msg ├── .prettierrc.cjs ├── .stylelintrc.cjs ├── .editorconfig ├── .lintstagedrc.cjs ├── .commitlintrc.cjs ├── .vscode ├── settings.json └── launch.json ├── tsconfig.base.json ├── .changeset ├── config.json └── README.md ├── tsconfig.build.json ├── .eslintrc.cjs ├── tsconfig.json ├── .gitignore ├── README.md ├── LICENSE ├── .github └── workflows │ ├── deploy.yml │ └── release.yml └── package.json /.stylelintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "cz-conventional-changelog" 3 | } 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - docs 3 | - packages/* 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .temp 2 | .cache 3 | 4 | lib 5 | dist 6 | node_modules 7 | -------------------------------------------------------------------------------- /packages/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './npm-badge/index.js' 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .temp 2 | .cache 3 | 4 | lib 5 | dist 6 | node_modules 7 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types.js' 2 | -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hmr.js' 2 | -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './createPage.js' 2 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-tabs-user.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | npx lint-staged 6 | -------------------------------------------------------------------------------- /docs/.vuepress/configs/navbar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './en.js' 2 | export * from './zh.js' 3 | -------------------------------------------------------------------------------- /docs/.vuepress/configs/sidebar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './en.js' 2 | export * from './zh.js' 3 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client-iframe/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Vuepress.js' 2 | -------------------------------------------------------------------------------- /packages/plugin-page-missing/src/node/resolve/index.ts: -------------------------------------------------------------------------------- 1 | export * from './resolvePageMissing.js' 2 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client-iframe/composables/index.ts: -------------------------------------------------------------------------------- 1 | export * from './siteData.js' 2 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-file.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@internal/name' { 2 | export const name: string 3 | } 4 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | npx --no-install commitlint --edit $1 6 | -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfehub/vuepress-plugins/HEAD/docs/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/resolve/index.ts: -------------------------------------------------------------------------------- 1 | export * from './resolveOptions.js' 2 | export * from './resolvePages.js' 3 | -------------------------------------------------------------------------------- /docs/.vuepress/configs/index.ts: -------------------------------------------------------------------------------- 1 | export * as navbar from './navbar/index.js' 2 | export * as sidebar from './sidebar/index.js' 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/demos/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfehub/vuepress-plugins/HEAD/docs/.vuepress/public/demos/dark.png -------------------------------------------------------------------------------- /docs/.vuepress/public/demos/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfehub/vuepress-plugins/HEAD/docs/.vuepress/public/demos/light.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfehub/vuepress-plugins/HEAD/docs/.vuepress/public/images/hero.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfehub/vuepress-plugins/HEAD/docs/.vuepress/public/images/logo.png -------------------------------------------------------------------------------- /packages/theme-vmi/src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nav.js' 2 | export * from './options.js' 3 | export * from './page.js' 4 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | trailingComma: 'es5', 4 | singleQuote: true, 5 | printWidth: 80, 6 | } 7 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "description": "A theme for vuepress" 5 | } 6 | -------------------------------------------------------------------------------- /packages/components/npm-badge/examples/basic.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/prepare/index.ts: -------------------------------------------------------------------------------- 1 | export * from './prepareClientIframe.js' 2 | export * from './prepareVmiComponents.js' 3 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-transform.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hmr.js' 2 | export * from './proxy.js' 3 | export * from './iframe.js' 4 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/node/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './assignDefaultLocaleOptions.js' 2 | export * from './resolveContainerPluginOptions.js' 3 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../shared/index.js' 2 | export * from './composables/index.js' 3 | export * from './utils/index.js' 4 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/HomeContent.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './isActiveSidebarItem.js' 2 | export * from './resolveEditLink.js' 3 | export * from './resolveRepoType.js' 4 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { ComponentOptions } from 'vue' 3 | const comp: ComponentOptions 4 | export default comp 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { ComponentOptions } from 'vue' 3 | const comp: ComponentOptions 4 | export default comp 5 | } 6 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-tabs-task.tsx: -------------------------------------------------------------------------------- 1 | import { h, defineComponent } from 'vue' 2 | 3 | export default defineComponent({ 4 | setup() { 5 | return () => h('div', 'Task') 6 | }, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client-internal/siteData.d.ts: -------------------------------------------------------------------------------- 1 | import type { SiteData } from '@vuepress/shared' 2 | 3 | declare module '@internal/siteData' { 4 | export const siteData: SiteData 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dep.js' 2 | export * from './highlight.js' 3 | export * from './resolve.js' 4 | export * from './scan.js' 5 | export * from './cache.js' 6 | -------------------------------------------------------------------------------- /docs/.vuepress/client-iframe.ts: -------------------------------------------------------------------------------- 1 | import { defineClientConfig } from '@vuepress/client' 2 | 3 | export default defineClientConfig({ 4 | enhance({ app }) { 5 | console.log('This is a iframe.') 6 | }, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/plugin-page-map/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib" 6 | }, 7 | "include": ["./src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/resolve/index.ts: -------------------------------------------------------------------------------- 1 | export * from './resolveOptions.js' 2 | export * from './resolveHtmlBlock.js' 3 | export * from './resolveScriptSetup.js' 4 | export * from './resolvePageHeaders.js' 5 | -------------------------------------------------------------------------------- /packages/plugin-page-missing/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib" 6 | }, 7 | "include": ["./src"] 8 | } 9 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-raw.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /.stylelintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@bfehub/stylelint-config-basic', 4 | rules: { 5 | 'no-descending-specificity': null, 6 | 'selector-pseudo-class-no-unknown': null, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-inline.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client-internal/clientConfigs.d.ts: -------------------------------------------------------------------------------- 1 | import type { ClientConfig } from '@vuepress/client' 2 | 3 | declare module '@internal/clientConfigs' { 4 | export const clientConfigs: ClientConfig[] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-page-missing/src/node/index.ts: -------------------------------------------------------------------------------- 1 | import { pageMissingPlugin } from './pageMissingPlugin.js' 2 | 3 | export * from './pageMissingPlugin.js' 4 | export * from './resolve/index.js' 5 | 6 | export default pageMissingPlugin 7 | -------------------------------------------------------------------------------- /packages/components/npm-badge/docs/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | # NpmBadge 2 | 3 | 参考 `vuepress` 官方的一个本地组件,用于展示 npm 包的徽章。 4 | 5 | ## Basic 6 | 7 | 指定 `package` 包名称 和 `dist-tag` 默认 `latest`。 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/global/index.ts: -------------------------------------------------------------------------------- 1 | import Badge from './Badge.vue' 2 | import { CodeGroup } from './CodeGroup.js' 3 | import CodeGroupItem from './CodeGroupItem.vue' 4 | 5 | export { Badge, CodeGroup, CodeGroupItem } 6 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client-internal/clientIframeConfigs.d.ts: -------------------------------------------------------------------------------- 1 | import type { ClientConfig } from '@vuepress/client' 2 | 3 | declare module '@internal/clientIframeConfigs' { 4 | export const clientIframeConfigs: ClientConfig[] 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client-internal/pagesVmiComponents.d.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentOptions } from 'vue' 2 | 3 | declare module '@internal/pagesVmiComponents' { 4 | export const components: Record 5 | } 6 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/node/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultTheme } from './defaultTheme.js' 2 | 3 | export * from '../shared/index.js' 4 | export * from './defaultTheme.js' 5 | export * from './utils/index.js' 6 | 7 | export default defaultTheme 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /packages/components/shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { App, ComponentOptions } from 'vue' 3 | 4 | const component: ComponentOptions & { 5 | static install(app: App): void 6 | } 7 | 8 | export default component 9 | } 10 | -------------------------------------------------------------------------------- /packages/theme-vmi/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib", 6 | "types": ["@vuepress/client/types"] 7 | }, 8 | "include": ["./src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/parse/index.ts: -------------------------------------------------------------------------------- 1 | export * from './parseCodeBlock.js' 2 | export * from './parseNodeAttrs.js' 3 | export * from './parseInline.js' 4 | export * from './parseRaw.js' 5 | export * from './parseVue.js' 6 | export * from './readSource.js' 7 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/composables/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useDarkMode.js' 2 | export * from './useNavLink.js' 3 | export * from './useResolveRouteWithRedirect.js' 4 | export * from './useScrollPromise.js' 5 | export * from './useSidebarItems.js' 6 | export * from './useThemeData.js' 7 | -------------------------------------------------------------------------------- /.lintstagedrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'], 3 | '*.{css,scss,less,styl}': ['stylelint --fix', 'prettier --write'], 4 | '*.vue': ['eslint --fix', 'stylelint --fix', 'prettier --write'], 5 | 'package.json': ['prettier --write'], 6 | } 7 | -------------------------------------------------------------------------------- /docs/.vuepress/configs/navbar/zh.ts: -------------------------------------------------------------------------------- 1 | import type { NavbarConfig } from '@bfehub/vuepress-theme-vmi' 2 | 3 | export const zh: NavbarConfig = [ 4 | { 5 | text: '指南', 6 | link: '/zh/guide/', 7 | }, 8 | { 9 | text: '组件', 10 | link: '/zh/components/', 11 | }, 12 | ] 13 | -------------------------------------------------------------------------------- /packages/components/npm-badge/docs/index.md: -------------------------------------------------------------------------------- 1 | # NpmBadge 2 | 3 | Refer to `vuepress` official local component for displaying badges of npm packages. 4 | 5 | ## Basic 6 | 7 | Specify `package` package name and `dist-tag` default `latest`. 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/.vuepress/configs/navbar/en.ts: -------------------------------------------------------------------------------- 1 | import type { NavbarConfig } from '@bfehub/vuepress-theme-vmi' 2 | 3 | export const en: NavbarConfig = [ 4 | { 5 | text: 'Guide', 6 | link: '/guide/', 7 | }, 8 | { 9 | text: 'Components', 10 | link: '/components/', 11 | }, 12 | ] 13 | -------------------------------------------------------------------------------- /packages/components/npm-badge/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import NpmBadge from './src/npm-badge.vue' 3 | 4 | NpmBadge.name = 'VpNpmBadge' 5 | NpmBadge.install = function (app: App) { 6 | app.component(NpmBadge.name, NpmBadge) 7 | } 8 | 9 | export const VpNpmBadge = NpmBadge 10 | -------------------------------------------------------------------------------- /.commitlintrc.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const packages = fs.readdirSync(path.resolve(__dirname, 'packages')) 5 | 6 | module.exports = { 7 | extends: ['@commitlint/config-conventional'], 8 | rules: { 9 | 'scope-enum': [2, 'always', [...packages]], 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/index.ts: -------------------------------------------------------------------------------- 1 | import Previewer from './components/Previewer.vue' 2 | import Example from './components/Example.vue' 3 | import SourceCode from './components/SourceCode.vue' 4 | import SourceCodeItem from './components/SourceCodeItem.vue' 5 | 6 | export { Previewer, Example, SourceCode, SourceCodeItem } 7 | -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/index.ts: -------------------------------------------------------------------------------- 1 | import { pageMapPlugin } from './pageMapPlugin.js' 2 | 3 | export * from './pageMapPlugin.js' 4 | export * from './resolve/index.js' 5 | export * from './plugins/index.js' 6 | export * from './utils/index.js' 7 | export * from './dev/index.js' 8 | 9 | export default pageMapPlugin 10 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-iframe.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client-internal/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __VUEPRESS_VERSION__: string 2 | declare const __VUEPRESS_DEV__: boolean 3 | declare const __VUEPRESS_SSR__: boolean 4 | declare const __VUE_HMR_RUNTIME__: Record 5 | declare const __VUE_OPTIONS_API__: boolean 6 | declare const __VUE_PROD_DEVTOOLS__: boolean 7 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/index.ts: -------------------------------------------------------------------------------- 1 | import { codeBlockPlugin } from './codeBlockPlugin.js' 2 | 3 | export * from './codeBlockPlugin.js' 4 | export * from './parse/index.js' 5 | export * from './plugins/index.js' 6 | export * from './resolve/index.js' 7 | export * from './utils/index.js' 8 | 9 | export default codeBlockPlugin 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true, 4 | "source.fixAll.stylelint": true 5 | }, 6 | "stylelint.validate": [ 7 | "css", 8 | "less", 9 | "postcss", 10 | "scss", 11 | "html", 12 | "xml", 13 | "vue", 14 | "svelte", 15 | "php" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/dev/index.ts: -------------------------------------------------------------------------------- 1 | // from https://github.com/vuepress/vuepress-next/tree/main/packages/%40vuepress/cli/src/commands/dev 2 | export * from './watchPageFiles.js' 3 | export * from './handlePageAdd.js' 4 | export * from './handlePageChange.js' 5 | export * from './handlePageUnlink.js' 6 | export * from './pageDepsHelper.js' 7 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "lib": ["DOM", "ES2020"], 5 | "module": "ESNext", 6 | "moduleResolution": "NodeNext", 7 | "noEmitOnError": true, 8 | "noImplicitAny": false, 9 | "skipLibCheck": true, 10 | "target": "ES2020", 11 | "sourceMap": false 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["docs"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/plugin-code-block/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib", 6 | "types": ["@vuepress/client/types", "vite/client"], 7 | "paths": { 8 | "@internal/*": ["./src/client-internal/*.d.ts"] 9 | } 10 | }, 11 | "include": ["./src"] 12 | } 13 | -------------------------------------------------------------------------------- /docs/.vuepress/client.ts: -------------------------------------------------------------------------------- 1 | import { defineClientConfig } from '@vuepress/client' 2 | import { VpNpmBadge } from '@bfehub/vuepress-components' 3 | // import { ElButton } from 'element-plus' 4 | import 'element-plus/dist/index.css' 5 | 6 | export default defineClientConfig({ 7 | enhance({ app }) { 8 | app.use(VpNpmBadge) 9 | // app.use(ElButton) 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/components/Previewer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /docs/zh/guide/README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | Vmi 是一个用于组件开发场景的 VuePress 的插件集合。尽量通过插件能有相似 Dumi 的 Demo 体验,又能保持 VuePress 强大的文档编写能力。 4 | 5 | ## 特性 6 | 7 | - 强大的 demo 演示能力,支持多种展示模式。 8 | 9 | - 支持页面路径映射,自定义组织组件文档。 10 | 11 | - 支持翻译缺失,自动生成缺失语言的页面。 12 | 13 | - 主题样式布局同步(**待完善**)。 14 | 15 | ## 示例 16 | 17 | 亮色模式 18 | 19 | ![light](/demos/light.png) 20 | 21 | 暗黑模式 22 | 23 | ![dark](/demos/dark.png) 24 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/components/SourceCodeItem.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/parse/parseInline.ts: -------------------------------------------------------------------------------- 1 | import type { Node } from 'posthtml-parser' 2 | import type { PageCodeDep } from '../utils/index.js' 3 | 4 | export const parseInline = (node: Node, dep: PageCodeDep): Node => { 5 | if (typeof node !== 'object') { 6 | return node 7 | } 8 | 9 | node.tag = dep.compName 10 | node.attrs = {} 11 | 12 | return node 13 | } 14 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /packages/plugin-page-missing/src/node/pageMissingPlugin.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from '@vuepress/core' 2 | import { resolvePageMissing } from './resolve/index.js' 3 | 4 | export const pageMissingPlugin = (): Plugin => { 5 | return { 6 | name: '@bfehub/vuepress-plugin-page-missing', 7 | 8 | async onInitialized(app) { 9 | await resolvePageMissing(app) 10 | }, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "references": [ 4 | { "path": "./packages/theme-vmi/tsconfig.build.json" }, 5 | { "path": "./packages/plugin-page-map/tsconfig.build.json" }, 6 | { "path": "./packages/plugin-page-missing/tsconfig.build.json" }, 7 | { "path": "./packages/plugin-code-block/tsconfig.build.json" } 8 | ], 9 | "files": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/utils/cache.ts: -------------------------------------------------------------------------------- 1 | const Cache = new Map() 2 | 3 | export const getCache = (key: string) => { 4 | Cache.get(key) 5 | } 6 | 7 | export const setCache = (key: string, content: any) => { 8 | Cache.set(key, content) 9 | } 10 | 11 | export const isCacheChange = (key: string, content: any) => { 12 | const oldContent = Cache.get(key) 13 | return content !== oldContent 14 | } 15 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-basic.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-debug.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-desc.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-title.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /docs/zh/components/README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | 用于编写文档使用的组件。 4 | 5 | ## 使用 6 | 7 | ```sh 8 | npm i -D @bfehub/vuepress-components@1.60.x 9 | ``` 10 | 11 | ```ts 12 | // docs/.vuepress/config.ts 13 | import { defineClientConfig } from '@vuepress/client' 14 | import { VpNpmBadge } from '@bfehub/vuepress-components' 15 | 16 | export default defineClientConfig({ 17 | enhance({ app }) { 18 | app.use(VpNpmBadge) 19 | }, 20 | }) 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-iframe-url.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /docs/zh/guide/demos/demo-tabs.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@bfehub/eslint-config-vue', 4 | rules: { 5 | '@typescript-eslint/ban-ts-comment': 'off', 6 | }, 7 | globals: { 8 | __VUEPRESS_VERSION__: 'readonly', 9 | __VUEPRESS_DEV__: 'readonly', 10 | __VUEPRESS_SSR__: 'readonly', 11 | __VUE_HMR_RUNTIME__: 'readonly', 12 | __VUE_OPTIONS_API__: 'readonly', 13 | __VUE_PROD_DEVTOOLS__: 'readonly', 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/styles/index.scss: -------------------------------------------------------------------------------- 1 | @use 'vars'; 2 | @use 'vars-dark'; 3 | @use 'normalize'; 4 | 5 | @use 'arrow'; 6 | @use 'badge'; 7 | @use 'code'; 8 | @use 'code-dark'; 9 | @use 'code-group'; 10 | @use 'custom-container'; 11 | @use 'home'; 12 | @use 'layout'; 13 | @use 'navbar'; 14 | @use 'navbar-dropdown'; 15 | @use 'page'; 16 | @use 'sidebar'; 17 | @use 'toc'; 18 | @use 'transitions'; 19 | 20 | @use '@vuepress/plugin-palette/style'; 21 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | @import '@vuepress/plugin-palette/palette'; 2 | 3 | // responsive breakpoints 4 | $MQNarrow: 1200px !default; 5 | $MQMobile: 719px !default; 6 | $MQMobileNarrow: 419px !default; 7 | 8 | // code languages 9 | $codeLang: 'c' 'cpp' 'cs' 'css' 'dart' 'docker' 'fs' 'go' 'html' 'java' 'js' 10 | 'json' 'kt' 'less' 'makefile' 'md' 'php' 'py' 'rb' 'rs' 'sass' 'scss' 'sh' 11 | 'styl' 'ts' 'toml' 'vue' 'yml' !default; 12 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | 3 | @mixin content_wrapper { 4 | max-width: var(--content-width); 5 | padding: 2rem 2.5rem; 6 | margin: 0 auto; 7 | 8 | @media (max-width: $MQNarrow) { 9 | padding: 2rem; 10 | } 11 | 12 | @media (max-width: $MQMobileNarrow) { 13 | padding: 1.5rem; 14 | } 15 | } 16 | 17 | @mixin dropdown_wrapper { 18 | overflow: hidden; 19 | transition: height 0.1s ease-out; 20 | } 21 | -------------------------------------------------------------------------------- /docs/components/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | A component used to write documentation. 4 | 5 | ## Using 6 | 7 | ```sh 8 | npm i -D @bfehub/vuepress-components@1.60.x 9 | ``` 10 | 11 | ```ts 12 | // docs/.vuepress/config.ts 13 | import { defineClientConfig } from '@vuepress/client' 14 | import { VpNpmBadge } from '@bfehub/vuepress-components' 15 | 16 | export default defineClientConfig({ 17 | enhance({ app }) { 18 | app.use(VpNpmBadge) 19 | }, 20 | }) 21 | ``` 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "jsx": "preserve", 6 | "module": "ES2020", 7 | "paths": { 8 | "@bfehub/*": ["./packages/*/src"], 9 | "@internal/*": ["./packages/plugin-code-block/src/client-internal/*.d.ts"] 10 | }, 11 | "types": ["vite/client"] 12 | }, 13 | "include": ["**/.vuepress/**/*", "packages/**/*"], 14 | "exclude": ["node_modules", ".temp", "lib", "dist"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/utils/resolveRepoType.ts: -------------------------------------------------------------------------------- 1 | import { isLinkHttp } from '@vuepress/shared' 2 | 3 | export type RepoType = 'GitHub' | 'GitLab' | 'Gitee' | 'Bitbucket' | null 4 | 5 | export const resolveRepoType = (repo: string): RepoType => { 6 | if (!isLinkHttp(repo) || /github\.com/.test(repo)) return 'GitHub' 7 | if (/bitbucket\.org/.test(repo)) return 'Bitbucket' 8 | if (/gitlab\.com/.test(repo)) return 'GitLab' 9 | if (/gitee\.com/.test(repo)) return 'Gitee' 10 | return null 11 | } 12 | -------------------------------------------------------------------------------- /docs/zh/guide/theme-vmi.md: -------------------------------------------------------------------------------- 1 | # 主题参考 2 | 3 | 这套主题修改于 `vuepress` 的官方的 `theme-default`。 4 | 5 | 现在没有做太多配置上的更改所以配置参考 [默认主题](https://v2.vuepress.vuejs.org/zh/reference/default-theme/config.html) 的配置。 6 | 7 | 后续可能会对主题做大的修改,你可以使用任何的主题,以上的插件和主题没有强绑定。 8 | 9 | ## 安装 10 | 11 | ```sh 12 | npm i -D @bfehub/vuepress-theme-vmi@1.60.x 13 | ``` 14 | 15 | ```js 16 | import { defaultTheme } from '@bfehub/vuepress-theme-vmi' 17 | 18 | export default { 19 | theme: defaultTheme({ 20 | // 在这里进行配置 21 | }), 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/styles/transitions.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * dropdown 3 | */ 4 | .dropdown-enter-from, 5 | .dropdown-leave-to { 6 | height: 0 !important; 7 | } 8 | 9 | /** 10 | * fade-slide-y 11 | */ 12 | .fade-slide-y-enter-active { 13 | transition: all 0.2s ease; 14 | } 15 | 16 | .fade-slide-y-leave-active { 17 | transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1); 18 | } 19 | 20 | .fade-slide-y-enter-from, 21 | .fade-slide-y-leave-to { 22 | opacity: 0; 23 | transform: translateY(10px); 24 | } 25 | -------------------------------------------------------------------------------- /docs/.vuepress/configs/sidebar/en.ts: -------------------------------------------------------------------------------- 1 | import type { SidebarConfig } from '@bfehub/vuepress-theme-vmi' 2 | 3 | export const en: SidebarConfig = { 4 | '/guide/': [ 5 | { 6 | text: 'Guide', 7 | children: ['/guide/README.md', '/guide/page-missing.md'], 8 | }, 9 | ], 10 | '/components/': [ 11 | { 12 | text: 'Intro', 13 | children: ['/components/README.md'], 14 | }, 15 | { 16 | text: 'Components', 17 | children: ['/components/npm-badge/index.md'], 18 | }, 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /packages/theme-vmi/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @bfehub/vuepress-theme-vmi 2 | 3 | ## 1.60.0 4 | 5 | ### Minor Changes 6 | 7 | - Update VuePress 2.0.0-beta.60 8 | 9 | ## 1.1.0 10 | 11 | ### Minor Changes 12 | 13 | - 68e593d: Update VuePress 2.0.0-beta.53 14 | 15 | ## 1.0.4 16 | 17 | ### Patch Changes 18 | 19 | - Optimized implementation 20 | 21 | ## 1.0.3 22 | 23 | ### Patch Changes 24 | 25 | - update vuepress 2.0.0-beta.49 26 | 27 | ## 1.0.2 28 | 29 | ### Patch Changes 30 | 31 | - 32 | 33 | ## 1.0.1 34 | 35 | ### Patch Changes 36 | 37 | - 38 | -------------------------------------------------------------------------------- /packages/components/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @bfehub/vuepress-components 2 | 3 | ## 1.60.0 4 | 5 | ### Minor Changes 6 | 7 | - Update VuePress 2.0.0-beta.60 8 | 9 | ## 1.1.0 10 | 11 | ### Minor Changes 12 | 13 | - 68e593d: Update VuePress 2.0.0-beta.53 14 | 15 | ## 1.0.4 16 | 17 | ### Patch Changes 18 | 19 | - Optimized implementation 20 | 21 | ## 1.0.3 22 | 23 | ### Patch Changes 24 | 25 | - update vuepress 2.0.0-beta.49 26 | 27 | ## 1.0.2 28 | 29 | ### Patch Changes 30 | 31 | - 32 | 33 | ## 1.0.1 34 | 35 | ### Patch Changes 36 | 37 | - 38 | -------------------------------------------------------------------------------- /packages/plugin-page-map/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @bfehub/vuepress-plugin-page-map 2 | 3 | ## 1.60.0 4 | 5 | ### Minor Changes 6 | 7 | - Update VuePress 2.0.0-beta.60 8 | 9 | ## 1.1.0 10 | 11 | ### Minor Changes 12 | 13 | - 68e593d: Update VuePress 2.0.0-beta.53 14 | 15 | ## 1.0.4 16 | 17 | ### Patch Changes 18 | 19 | - Optimized implementation 20 | 21 | ## 1.0.3 22 | 23 | ### Patch Changes 24 | 25 | - update vuepress 2.0.0-beta.49 26 | 27 | ## 1.0.2 28 | 29 | ### Patch Changes 30 | 31 | - 32 | 33 | ## 1.0.1 34 | 35 | ### Patch Changes 36 | 37 | - 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # System 2 | .DS_Store 3 | 4 | # Coverage directory 5 | coverage 6 | 7 | # Build Generate 8 | lib 9 | dist 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Caches 19 | .temp 20 | .cache 21 | .eslintcache 22 | .stylelintcache 23 | 24 | # Dependency directories 25 | node_modules 26 | bower_components 27 | jspm_packages 28 | 29 | # Editor directories and files 30 | .idea 31 | *.suo 32 | *.ntvs* 33 | *.njsproj 34 | *.sln 35 | *.sw? 36 | 37 | # Typescript build info 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /packages/plugin-page-missing/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @bfehub/vuepress-plugin-page-missing 2 | 3 | ## 1.60.0 4 | 5 | ### Minor Changes 6 | 7 | - Update VuePress 2.0.0-beta.60 8 | 9 | ## 1.1.0 10 | 11 | ### Minor Changes 12 | 13 | - 68e593d: Update VuePress 2.0.0-beta.53 14 | 15 | ## 1.0.4 16 | 17 | ### Patch Changes 18 | 19 | - Optimized implementation 20 | 21 | ## 1.0.3 22 | 23 | ### Patch Changes 24 | 25 | - update vuepress 2.0.0-beta.49 26 | 27 | ## 1.0.2 28 | 29 | ### Patch Changes 30 | 31 | - 32 | 33 | ## 1.0.1 34 | 35 | ### Patch Changes 36 | 37 | - 38 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/DropdownTransition.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/Page.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 27 | -------------------------------------------------------------------------------- /packages/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bfehub/vuepress-components", 3 | "type": "module", 4 | "version": "1.60.0", 5 | "description": "VuePress components", 6 | "keywords": [ 7 | "vuepress", 8 | "components" 9 | ], 10 | "license": "MIT", 11 | "author": "haiweilian", 12 | "main": "index.ts", 13 | "homepage": "https://github.com/bfehub/vmi", 14 | "dependencies": { 15 | "vue": "^3.2.47" 16 | }, 17 | "devDependencies": { 18 | "@bfehub/vuepress-components": "workspace:*" 19 | }, 20 | "publishConfig": { 21 | "access": "public" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "docs:dev", 9 | "type": "node-terminal", 10 | "request": "launch", 11 | "command": "pnpm docs:dev" 12 | }, 13 | { 14 | "name": "docs:build", 15 | "type": "node-terminal", 16 | "request": "launch", 17 | "command": "pnpm docs:build" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/icons/Open.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/composables/useThemeData.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useThemeData as _useThemeData, 3 | useThemeLocaleData as _useThemeLocaleData, 4 | } from '@vuepress/plugin-theme-data/client' 5 | import type { 6 | ThemeDataRef, 7 | ThemeLocaleDataRef, 8 | } from '@vuepress/plugin-theme-data/client' 9 | import type { DefaultThemeData } from '../../shared/index.js' 10 | 11 | export const useThemeData = (): ThemeDataRef => 12 | _useThemeData() 13 | export const useThemeLocaleData = (): ThemeLocaleDataRef => 14 | _useThemeLocaleData() 15 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/global/Badge.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 32 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/ToggleSidebarButton.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/utils/highlight.ts: -------------------------------------------------------------------------------- 1 | import { createMarkdown } from '@vuepress/markdown' 2 | import { resolveHighlighter } from '@vuepress/plugin-prismjs' 3 | 4 | export const highlight = ( 5 | code: string, 6 | lang = 'text', 7 | noLineNumbers = true 8 | ): string => { 9 | return createMarkdown({ 10 | highlight: resolveHighlighter(lang), 11 | }).render( 12 | '```' + 13 | lang + 14 | (noLineNumbers ? ':no-line-numbers' : '') + 15 | '\n' + 16 | code + 17 | '```' 18 | ) 19 | } 20 | 21 | export const markdownText = (text: string) => { 22 | return createMarkdown().render(text) 23 | } 24 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/icons/CopySuccess.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/clientConfig.ts: -------------------------------------------------------------------------------- 1 | import { defineClientConfig } from '@vuepress/client' 2 | import Previewer from './components/Previewer.vue' 3 | import Example from './components/Example.vue' 4 | import SourceCode from './components/SourceCode.vue' 5 | import SourceCodeItem from './components/SourceCodeItem.vue' 6 | 7 | import './styles/index.scss' 8 | 9 | export default defineClientConfig({ 10 | enhance({ app }) { 11 | app.component('VmiPreviewer', Previewer) 12 | app.component('VmiExample', Example) 13 | app.component('VmiSourceCode', SourceCode) 14 | app.component('VmiSourceCodeItem', SourceCodeItem) 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/composables/useScrollPromise.ts: -------------------------------------------------------------------------------- 1 | export interface ScrollPromise { 2 | wait(): Promise | null 3 | pending: () => void 4 | resolve: () => void 5 | } 6 | 7 | let promise: Promise | null = null 8 | let promiseResolve: (() => void) | null = null 9 | 10 | const scrollPromise: ScrollPromise = { 11 | wait: () => promise, 12 | pending: () => { 13 | promise = new Promise((resolve) => (promiseResolve = resolve)) 14 | }, 15 | resolve: () => { 16 | promiseResolve?.() 17 | promise = null 18 | promiseResolve = null 19 | }, 20 | } 21 | 22 | export const useScrollPromise = (): ScrollPromise => scrollPromise 23 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/icons/File.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/global/CodeGroupItem.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 32 | -------------------------------------------------------------------------------- /docs/.vuepress/configs/sidebar/zh.ts: -------------------------------------------------------------------------------- 1 | import type { SidebarConfig } from '@bfehub/vuepress-theme-vmi' 2 | 3 | export const zh: SidebarConfig = { 4 | '/zh/guide/': [ 5 | { 6 | text: '指南', 7 | children: [ 8 | '/zh/guide/README.md', 9 | '/zh/guide/code-block.md', 10 | '/zh/guide/page-map.md', 11 | '/zh/guide/page-missing.md', 12 | '/zh/guide/theme-vmi.md', 13 | ], 14 | }, 15 | ], 16 | '/zh/components/': [ 17 | { 18 | text: '指引', 19 | children: ['/zh/components/README.md'], 20 | }, 21 | { 22 | text: '组件', 23 | children: ['/zh/components/npm-badge/index.md'], 24 | }, 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/styles/badge.scss: -------------------------------------------------------------------------------- 1 | .badge { 2 | display: inline-block; 3 | height: 18px; 4 | padding: 0 6px; 5 | font-size: 14px; 6 | line-height: 18px; 7 | color: var(--c-bg); 8 | vertical-align: top; 9 | border-radius: 3px; 10 | transition: color var(--t-color), background-color var(--t-color); 11 | 12 | &.tip { 13 | background-color: var(--c-badge-tip); 14 | } 15 | 16 | &.warning { 17 | background-color: var(--c-badge-warning); 18 | } 19 | 20 | &.danger { 21 | background-color: var(--c-badge-danger); 22 | } 23 | 24 | .table-of-contents & { 25 | vertical-align: middle; 26 | } 27 | 28 | & + & { 29 | margin-left: 5px; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/styles/toc.scss: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | 3 | .table-of-contents { 4 | .badge { 5 | vertical-align: middle; 6 | } 7 | } 8 | 9 | // plugin-toc 10 | .vuepress-toc { 11 | position: fixed; 12 | top: calc(var(--navbar-height) + 2rem); 13 | right: 0; 14 | width: var(--toc-width); 15 | height: calc(100% - (var(--navbar-height) + 4rem)); 16 | overflow: auto; 17 | } 18 | 19 | .vuepress-toc-link { 20 | font-size: 0.8em; 21 | color: var(--c-text); 22 | cursor: pointer; 23 | 24 | &:hover, 25 | &.active { 26 | color: var(--c-text-accent); 27 | } 28 | } 29 | 30 | @media (max-width: $MQNarrow) { 31 | .vuepress-toc { 32 | display: none; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/zh/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | title: 首页 4 | # heroImage: /images/hero.png 5 | actions: 6 | - text: 快速上手 7 | link: /zh/guide/ 8 | type: primary 9 | features: 10 | - title: 简洁至上 11 | details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。 12 | - title: Vue 驱动 13 | details: 享受 Vue 的开发体验,可以在 Markdown 中使用 Vue 组件,又可以使用 Vue 来开发自定义主题。 14 | - title: 高性能 15 | details: VuePress 会为每个页面预渲染生成静态的 HTML,同时,每个页面被加载的时候,将作为 SPA 运行。 16 | - title: 主题 17 | details: 提供了一个开箱即用的默认主题。你也可以挑选一个社区主题,或者创建一个你自己的主题。 18 | - title: 插件 19 | details: 灵活的插件API,使得插件可以为你的站点提供许多即插即用的功能。 20 | - title: 打包工具 21 | details: 默认的打包工具是 Vite ,也同样支持 Webpack 。选一个你喜欢的来使用吧! 22 | footer: MIT Licensed | Copyright © 2018-present Evan You 23 | --- 24 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/HomeFooter.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/plugins/proxy.ts: -------------------------------------------------------------------------------- 1 | import type { App } from '@vuepress/core' 2 | import type { Plugin } from 'vite' 3 | 4 | export const vitePageProxy = (app: App): Plugin => { 5 | return { 6 | name: '@bfehub/vuepress-plugin-code-block:proxy', 7 | configureServer(server) { 8 | const reg = new RegExp(`^${app.options.base}-(\\w+)\\.html`) 9 | return () => { 10 | // 这里的 req.url 已经被重定向过了,所以以 - 开头的改回原始的后续继续交给 vite 处理。 11 | server.middlewares.use(async (req, res, next) => { 12 | if (req.originalUrl?.match(reg)) { 13 | req.url = req.originalUrl.replace(app.options.base, '/') 14 | } 15 | await next() 16 | }) 17 | } 18 | }, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vmi 2 | 3 | [文档及演示](https://bfehub.github.io/vmi/zh/guide/) 4 | 5 | **当前依赖 VuePress v2.0.0-beta.60 版本** 6 | 7 | Vmi 是一个用于组件开发场景的 VuePress 的插件集合。尽量通过插件能有相似 Dumi 的 Demo 体验,又能保持 VuePress 强大的文档编写能力。 8 | 9 | ## 特性 10 | 11 | - 强大的 demo 演示能力,支持多种展示模式。 12 | 13 | - 支持页面路径映射,自定义组织组件文档。 14 | 15 | - 支持翻译缺失,自动生成缺失语言的页面。 16 | 17 | - 主题样式布局同步(**待完善**)。 18 | 19 | ## 示例 20 | 21 | 亮色模式 22 | 23 | ![light](./docs/.vuepress/public/demos/light.png) 24 | 25 | 暗黑模式 26 | 27 | ![dark](./docs/.vuepress/public/demos/dark.png) 28 | 29 | ## Issue 30 | 31 | If you have a better suggestion, please [create an issue](https://github.com/bfehub/vmi/issues) 32 | 33 | ## License 34 | 35 | The code is released under [the MIT license](https://github.com/bfehub/vmi/blob/master/LICENSE) 36 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client-iframe/composables/siteData.ts: -------------------------------------------------------------------------------- 1 | import { siteData as siteDataRaw } from '@internal/siteData' 2 | import type { SiteData } from '@vuepress/shared' 3 | import { ref } from 'vue' 4 | import type { Ref } from 'vue' 5 | 6 | export type { SiteData } 7 | 8 | /** 9 | * Ref wrapper of `SiteData` 10 | */ 11 | export type SiteDataRef = Ref 12 | 13 | /** 14 | * Global site data ref 15 | */ 16 | export const siteData: SiteDataRef = ref(siteDataRaw) 17 | 18 | /** 19 | * Returns the ref of the site data 20 | */ 21 | export const useSiteData = (): SiteDataRef => siteData 22 | 23 | if (import.meta.hot) { 24 | // reuse vue HMR runtime 25 | __VUE_HMR_RUNTIME__.updateSiteData = (data: SiteData) => { 26 | siteData.value = data 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/styles/arrow.scss: -------------------------------------------------------------------------------- 1 | .arrow { 2 | display: inline-block; 3 | width: 0; 4 | height: 0; 5 | 6 | &.up { 7 | border-right: 4px solid transparent; 8 | border-bottom: 6px solid var(--c-bg-arrow); 9 | border-left: 4px solid transparent; 10 | } 11 | 12 | &.down { 13 | border-top: 6px solid var(--c-bg-arrow); 14 | border-right: 4px solid transparent; 15 | border-left: 4px solid transparent; 16 | } 17 | 18 | &.right { 19 | border-top: 4px solid transparent; 20 | border-bottom: 4px solid transparent; 21 | border-left: 6px solid var(--c-bg-arrow); 22 | } 23 | 24 | &.left { 25 | border-top: 4px solid transparent; 26 | border-right: 6px solid var(--c-bg-arrow); 27 | border-bottom: 4px solid transparent; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/parse/parseRaw.ts: -------------------------------------------------------------------------------- 1 | import type { Node } from 'posthtml-parser' 2 | import type { PageCodeDep } from '../utils/index.js' 3 | import { readSource } from './readSource.js' 4 | 5 | export const parseRaw = (node: Node, dep: PageCodeDep): Node => { 6 | if (typeof node !== 'object') { 7 | return node 8 | } 9 | 10 | const sources = readSource(dep.compPath, false, false) 11 | 12 | if (sources.length === 1) { 13 | return sources[0].highlightCode 14 | } 15 | 16 | node.tag = 'CodeGroup' 17 | node.attrs = {} 18 | node.content = sources.map((source) => { 19 | return { 20 | tag: 'CodeGroupItem', 21 | attrs: { 22 | title: source.name, 23 | }, 24 | content: source.highlightCode, 25 | } 26 | }) 27 | 28 | return node 29 | } 30 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/icons/Copy.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/node/utils/resolveContainerPluginOptions.ts: -------------------------------------------------------------------------------- 1 | import type { ContainerPluginOptions } from '@vuepress/plugin-container' 2 | import type { DefaultThemeData } from '../../shared/index.js' 3 | 4 | /** 5 | * Resolve options for @vuepress/plugin-container 6 | * 7 | * For custom containers default title 8 | */ 9 | export const resolveContainerPluginOptions = ( 10 | localeOptions: DefaultThemeData, 11 | type: 'tip' | 'warning' | 'danger' 12 | ): ContainerPluginOptions => { 13 | const locales = Object.entries(localeOptions.locales || {}).reduce( 14 | (result, [key, value]) => { 15 | result[key] = { 16 | defaultInfo: value?.[type] ?? localeOptions[type], 17 | } 18 | return result 19 | }, 20 | {} 21 | ) 22 | 23 | return { 24 | type, 25 | locales, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/components/HomeFeatures.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client-iframe/components/Vuepress.ts: -------------------------------------------------------------------------------- 1 | import { components } from '@internal/pagesVmiComponents' 2 | import { computed, defineComponent, h } from 'vue' 3 | import { useRoute } from 'vue-router' 4 | 5 | /** 6 | * Demo rendered content 7 | */ 8 | export const Vuepress = defineComponent({ 9 | name: 'Vuepress', 10 | setup() { 11 | const route = useRoute() 12 | const component = computed(() => components[route.params.name as string]) 13 | return () => 14 | component.value 15 | ? // use page component 16 | h(component.value) 17 | : // fallback content 18 | h( 19 | 'div', 20 | __VUEPRESS_DEV__ 21 | ? 'Page does not exist. This is a fallback content.' 22 | : '404 Not Found' 23 | ) 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/icons/Edit.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /packages/plugin-page-missing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bfehub/vuepress-plugin-page-missing", 3 | "type": "module", 4 | "version": "1.60.0", 5 | "description": "VuePress plugin - page-missing", 6 | "files": [ 7 | "lib" 8 | ], 9 | "keywords": [ 10 | "vuepress-plugin", 11 | "vuepress", 12 | "plugin", 13 | "page-missing" 14 | ], 15 | "license": "MIT", 16 | "author": "haiweilian", 17 | "main": "lib/node/index.js", 18 | "types": "lib/node/index.d.ts", 19 | "homepage": "https://github.com/bfehub/vmi", 20 | "scripts": { 21 | "build": "tsc -b tsconfig.build.json", 22 | "clean": "rimraf lib *.tsbuildinfo" 23 | }, 24 | "dependencies": { 25 | "@vuepress/core": "2.0.0-beta.60", 26 | "@vuepress/utils": "2.0.0-beta.60" 27 | }, 28 | "publishConfig": { 29 | "access": "public" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client-iframe/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, Router } from 'vue-router' 2 | import { removeEndingSlash } from '@vuepress/shared' 3 | import { Vuepress } from './components/index.js' 4 | import { siteData } from './composables/index.js' 5 | 6 | /** 7 | * Create vue-router instance 8 | */ 9 | export const createVueRouter = (): Router => { 10 | const router = createRouter({ 11 | history: createWebHashHistory(removeEndingSlash(siteData.value.base)), 12 | routes: [ 13 | { 14 | path: '/:name', 15 | component: Vuepress, 16 | }, 17 | ], 18 | scrollBehavior: (to, from, savedPosition) => { 19 | if (savedPosition) return savedPosition 20 | if (to.hash) return { el: to.hash } 21 | return { top: 0 } 22 | }, 23 | }) 24 | 25 | return router 26 | } 27 | -------------------------------------------------------------------------------- /packages/plugin-page-map/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bfehub/vuepress-plugin-page-map", 3 | "type": "module", 4 | "version": "1.60.0", 5 | "description": "VuePress plugin - page-map", 6 | "files": [ 7 | "lib" 8 | ], 9 | "keywords": [ 10 | "vuepress-plugin", 11 | "vuepress", 12 | "plugin", 13 | "page-map" 14 | ], 15 | "license": "MIT", 16 | "author": "haiweilian", 17 | "main": "lib/node/index.js", 18 | "types": "lib/node/index.d.ts", 19 | "homepage": "https://github.com/bfehub/vmi", 20 | "scripts": { 21 | "build": "tsc -b tsconfig.build.json", 22 | "clean": "rimraf lib *.tsbuildinfo" 23 | }, 24 | "dependencies": { 25 | "@vuepress/core": "2.0.0-beta.60", 26 | "@vuepress/utils": "2.0.0-beta.60", 27 | "chokidar": "^3.5.3" 28 | }, 29 | "publishConfig": { 30 | "access": "public" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/parse/parseNodeAttrs.ts: -------------------------------------------------------------------------------- 1 | import { CodeUserConfig } from '../../shared/index.js' 2 | 3 | export const parseNodeAttrs = ( 4 | config: CodeUserConfig, 5 | attrs: Record 6 | ) => { 7 | Object.keys(config).forEach((key) => { 8 | if (typeof config[key] === 'string') { 9 | attrs[key] = config[key] 10 | } else { 11 | attrs[key] = JSON.stringify(config[key]) 12 | } 13 | }) 14 | 15 | Object.keys(attrs).forEach((key) => { 16 | if (key.startsWith('demoUrl')) { 17 | if (!attrs[key].startsWith('http') && config.baseDemoUrl) { 18 | attrs[key] = config.baseDemoUrl + attrs[key] 19 | } 20 | } 21 | 22 | if (attrs[key] === 'true') { 23 | attrs[key] = '' 24 | } 25 | 26 | if (attrs[key] === 'false') { 27 | delete attrs[key] 28 | } 29 | }) 30 | 31 | return attrs 32 | } 33 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/icons/Refresh.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/composables/useNavLink.ts: -------------------------------------------------------------------------------- 1 | import type { NavLink } from '../../shared/index.js' 2 | import { useResolveRouteWithRedirect } from './useResolveRouteWithRedirect.js' 3 | 4 | declare module 'vue-router' { 5 | interface RouteMeta { 6 | title?: string 7 | } 8 | } 9 | 10 | /** 11 | * Resolve NavLink props from string 12 | * 13 | * @example 14 | * - Input: '/README.md' 15 | * - Output: { text: 'Home', link: '/' } 16 | */ 17 | export const useNavLink = (item: string): NavLink => { 18 | // the route path of vue-router is url-encoded, and we expect users are using 19 | // non-url-encoded string in theme config, so we need to url-encode it first to 20 | // resolve the route correctly 21 | const resolved = useResolveRouteWithRedirect(encodeURI(item)) 22 | return { 23 | text: resolved.meta.title || item, 24 | link: resolved.name === '404' ? item : resolved.fullPath, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/layouts/NotFound.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/components/Example.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 40 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "docs:build": "vuepress build --clean-cache", 7 | "docs:clean": "rimraf .vuepress/.temp .vuepress/.cache .vuepress/dist", 8 | "docs:dev": "vuepress dev --clean-cache", 9 | "docs:serve": "anywhere -s -h localhost -d .vuepress/dist" 10 | }, 11 | "dependencies": { 12 | "@bfehub/vuepress-components": "workspace:*", 13 | "@bfehub/vuepress-plugin-code-block": "workspace:*", 14 | "@bfehub/vuepress-plugin-page-map": "workspace:*", 15 | "@bfehub/vuepress-plugin-page-missing": "workspace:*", 16 | "@bfehub/vuepress-theme-vmi": "workspace:*", 17 | "@vuepress/client": "2.0.0-beta.60", 18 | "@vuepress/plugin-search": "2.0.0-beta.60", 19 | "@vuepress/theme-default": "2.0.0-beta.60", 20 | "anywhere": "^1.5.0", 21 | "element-plus": "^2.2.2", 22 | "vue": "^3.2.47", 23 | "vuepress": "2.0.0-beta.60" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/resolve/resolvePages.ts: -------------------------------------------------------------------------------- 1 | import { debug, globby } from '@vuepress/utils' 2 | import { createPage } from '../utils/index.js' 3 | import type { App, Page } from '@vuepress/core' 4 | import type { PageMapPluginOptions } from '../index.js' 5 | 6 | const log = debug('vuepress:page-page/resolve') 7 | 8 | /** 9 | * Resolve pages for vuepress app 10 | */ 11 | export const resolvePages = async ( 12 | app: App, 13 | options: PageMapPluginOptions 14 | ): Promise => { 15 | log('resolvePages start') 16 | 17 | // resolve page absolute file paths according to the page patterns 18 | const pageFilePaths = await globby(options.patterns, { 19 | absolute: true, 20 | // cwd: app.dir.source(), 21 | }) 22 | 23 | // create pages from files 24 | const pages = await Promise.all( 25 | pageFilePaths.map((filePath) => { 26 | return createPage(app, options, filePath) 27 | }) 28 | ) 29 | 30 | log('resolvePages finish') 31 | 32 | return pages 33 | } 34 | -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/dev/handlePageUnlink.ts: -------------------------------------------------------------------------------- 1 | import { 2 | preparePagesComponents, 3 | preparePagesData, 4 | preparePagesRoutes, 5 | } from '@vuepress/core' 6 | import type { App, Page } from '@vuepress/core' 7 | import type { PageMapPluginOptions } from '../index.js' 8 | 9 | /** 10 | * Event handler for page unlink event 11 | * 12 | * Returns the removed page 13 | */ 14 | export const handlePageUnlink = async ( 15 | app: App, 16 | options: PageMapPluginOptions, 17 | filePath: string 18 | ): Promise => { 19 | // check if the unlinked page is existed 20 | const pageIndex = app.pages.findIndex((page) => page.filePath === filePath) 21 | if (pageIndex === -1) { 22 | return null 23 | } 24 | 25 | const page = app.pages[pageIndex] 26 | 27 | // remove the old page 28 | app.pages.splice(pageIndex, 1) 29 | 30 | // re-prepare page files 31 | await preparePagesComponents(app) 32 | await preparePagesData(app) 33 | await preparePagesRoutes(app) 34 | 35 | return page 36 | } 37 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/composables/useResolveRouteWithRedirect.ts: -------------------------------------------------------------------------------- 1 | import { isFunction, isString } from '@vuepress/shared' 2 | import { useRouter } from 'vue-router' 3 | import type { Router } from 'vue-router' 4 | 5 | /** 6 | * Resolve a route with redirection 7 | */ 8 | export const useResolveRouteWithRedirect = ( 9 | ...args: Parameters 10 | ): ReturnType => { 11 | const router = useRouter() 12 | const route = router.resolve(...args) 13 | const lastMatched = route.matched[route.matched.length - 1] 14 | if (!lastMatched?.redirect) { 15 | return route 16 | } 17 | const { redirect } = lastMatched 18 | const resolvedRedirect = isFunction(redirect) ? redirect(route) : redirect 19 | const resolvedRedirectObj = isString(resolvedRedirect) 20 | ? { path: resolvedRedirect } 21 | : resolvedRedirect 22 | return useResolveRouteWithRedirect({ 23 | hash: route.hash, 24 | query: route.query, 25 | params: route.params, 26 | ...resolvedRedirectObj, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/icons/UnExpand.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/utils/isActiveSidebarItem.ts: -------------------------------------------------------------------------------- 1 | import type { RouteLocationNormalizedLoaded } from 'vue-router' 2 | import type { ResolvedSidebarItem } from '../../shared/index.js' 3 | 4 | const normalizePath = (path: string): string => 5 | decodeURI(path) 6 | .replace(/#.*$/, '') 7 | .replace(/(index)?\.(md|html)$/, '') 8 | 9 | const isActiveLink = ( 10 | link: string, 11 | route: RouteLocationNormalizedLoaded 12 | ): boolean => { 13 | if (route.hash === link) { 14 | return true 15 | } 16 | const currentPath = normalizePath(route.path) 17 | const targetPath = normalizePath(link) 18 | return currentPath === targetPath 19 | } 20 | 21 | export const isActiveSidebarItem = ( 22 | item: ResolvedSidebarItem, 23 | route: RouteLocationNormalizedLoaded 24 | ): boolean => { 25 | if (item.link && isActiveLink(item.link, route)) { 26 | return true 27 | } 28 | 29 | if (item.children) { 30 | return item.children.some((child) => isActiveSidebarItem(child, route)) 31 | } 32 | 33 | return false 34 | } 35 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/resolve/resolveScriptSetup.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from '@vuepress/core' 2 | import type { PageCodeDep, PageCodeDepsHelper } from '../utils/index.js' 3 | 4 | const scriptRegExp = /([\s\S]*)<\/script>/ 5 | 6 | export const resolveScriptSetup = (page: Page, store: PageCodeDepsHelper) => { 7 | const deps = store.get(page.filePath!) 8 | if (!deps.length) return 9 | 10 | page.sfcBlocks.scriptSetup ??= {} as any 11 | 12 | let original = page.sfcBlocks.scriptSetup.content || '' 13 | if (original.trim().startsWith(' { 21 | return `\n 22 | \n` 29 | } 30 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | title: Home 4 | # heroImage: /images/hero.png 5 | actions: 6 | - text: Get Started 7 | link: /zh/guide/ 8 | type: primary 9 | features: 10 | - title: Simplicity First 11 | details: Minimal setup with markdown-centered project structure helps you focus on writing. 12 | - title: Vue-Powered 13 | details: Enjoy the dev experience of Vue, use Vue components in markdown, and develop custom themes with Vue. 14 | - title: Performant 15 | details: VuePress generates pre-rendered static HTML for each page, and runs as an SPA once a page is loaded. 16 | - title: Themes 17 | details: Providing a default theme out of the box. You can also choose a community theme or create your own one. 18 | - title: Plugins 19 | details: Flexible plugin API, allowing plugins to provide lots of plug-and-play features for your site. 20 | - title: Bundlers 21 | details: Default bundler is Vite, while Webpack is also supported. Choose the one you like! 22 | footer: MIT Licensed | Copyright © 2018-present Evan You 23 | --- 24 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/resolve/resolveHtmlBlock.ts: -------------------------------------------------------------------------------- 1 | import type { App } from '@vuepress/core' 2 | import type { Markdown, MarkdownEnv } from '@vuepress/markdown' 3 | import type { CodeBlockPluginOptions } from '../index.js' 4 | import type { PageCodeDepsHelper } from '../utils/index.js' 5 | import { parseCodeBlock } from '../parse/index.js' 6 | 7 | export const resolveHtmlBlock = ( 8 | md: Markdown, 9 | app: App, 10 | store: PageCodeDepsHelper, 11 | options: CodeBlockPluginOptions 12 | ) => { 13 | const rawRule = md.renderer.rules.html_inline! 14 | 15 | md.renderer.rules.html_inline = function ( 16 | tokens, 17 | idx, 18 | opts, 19 | env: MarkdownEnv, 20 | self 21 | ) { 22 | const content = tokens[idx].content 23 | 24 | if (content.startsWith(`<${options.name}`)) { 25 | tokens[idx].content = parseCodeBlock( 26 | app, 27 | store, 28 | options, 29 | content, 30 | env.filePath! 31 | ) 32 | } 33 | 34 | return rawRule(tokens, idx, opts, env, self) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/resolve/resolvePageHeaders.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from '@vuepress/core' 2 | import type { PageHeader } from '@vuepress/shared' 3 | import type { PageCodeDepsHelper } from '../utils/index.js' 4 | import { slugify } from '@mdit-vue/shared' 5 | 6 | export const resolvePageHeaders = ( 7 | page: Page, 8 | store: PageCodeDepsHelper, 9 | titleToHeader: boolean 10 | ) => { 11 | if (titleToHeader) { 12 | if (page.frontmatter.headers === false) { 13 | return false 14 | } 15 | } else { 16 | if (page.frontmatter.headers !== true) { 17 | return false 18 | } 19 | } 20 | 21 | const deps = store.get(page.filePath!) 22 | const headers: PageHeader[] = [] 23 | 24 | for (const dep of deps) { 25 | if (dep.compAttrs.title) { 26 | headers.push({ 27 | level: 2, 28 | title: dep.compAttrs.title, 29 | slug: slugify(dep.compAttrs.title), 30 | link: slugify(dep.compAttrs.title), 31 | children: [], 32 | }) 33 | } 34 | } 35 | 36 | page.data.headers = headers 37 | } 38 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/styles/code-dark.scss: -------------------------------------------------------------------------------- 1 | html.dark { 2 | .language-css .token.string, 3 | .style .token.string, 4 | .token.entity, 5 | .token.operator, 6 | .token.url { 7 | background: transparent; 8 | } 9 | 10 | .token.constant, 11 | .token.deleted, 12 | .token.property, 13 | .token.symbol, 14 | .token.tag { 15 | color: #f92672; 16 | } 17 | 18 | .token.attr-name, 19 | .token.builtin, 20 | .token.char, 21 | .token.inserted, 22 | .token.selector, 23 | .token.string { 24 | color: #a6e22e; 25 | } 26 | 27 | .token.atrule, 28 | .token.attr-value, 29 | .token.keyword { 30 | color: #e6db74; 31 | } 32 | 33 | .token.punctuation { 34 | color: hsl(0deg 0% 100% / 85%); 35 | } 36 | 37 | .token.keyword { 38 | color: #66d9ef; 39 | } 40 | 41 | .token.boolean, 42 | .token.number { 43 | color: #ae81ff; 44 | } 45 | 46 | .theme-default-content { 47 | pre, 48 | pre[class*='language-'] { 49 | code { 50 | color: hsl(0deg 0% 100% / 85%); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/theme-vmi/templates/build.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 20 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/components/npm-badge/src/npm-badge.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 44 | 45 | 50 | -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/resolve/resolveOptions.ts: -------------------------------------------------------------------------------- 1 | import type { PageMapPluginOptions } from '../index.js' 2 | 3 | /** 4 | * Create options with default values 5 | */ 6 | export const resolveOptions = ( 7 | config: Partial 8 | ): PageMapPluginOptions => { 9 | return Object.assign( 10 | { 11 | patterns: [], 12 | 13 | pathMapRule(path: string) { 14 | const paths = path.split('/') 15 | const len = paths.length 16 | 17 | // /User/project/path/npm-badge/index.md 18 | // -> components/npm-badge/index.md 19 | 20 | // /User/project/path/npm-badge/index.zh-CN.md 21 | // -> components/npm-badge/index.zh-CN.md 22 | 23 | // /User/project/path/npm-badge/docs/index.md 24 | // -> components/npm-badge/index.md 25 | 26 | // /User/project/path/npm-badge/docs/index.zh-CN.md 27 | // -> components/npm-badge/index.zh-CN.md 28 | 29 | return `components/${ 30 | paths[len - 2] === 'docs' ? paths[len - 3] : paths[len - 2] 31 | }/${paths[len - 1]}` 32 | }, 33 | }, 34 | config 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /docs/guide/page-missing.md: -------------------------------------------------------------------------------- 1 | # 翻译缺失 2 | 3 | 将默认语言的文档作为未翻译语言的兜底文档。 4 | 5 | ::: tip 6 | 此插件目前只在 `onInitialized` 钩子调用,它不会创建真实的文件只是复制当前页面的信息创建一个新的页面。 7 | ::: 8 | 9 | ## 安装 10 | 11 | ```sh 12 | npm i -D @bfehub/vuepress-plugin-page-missing@1.60.x 13 | ``` 14 | 15 | ```js 16 | import { pageMissingPlugin } from '@bfehub/vuepress-plugin-page-missing' 17 | 18 | export default { 19 | plugins: [ 20 | pageMissingPlugin(), 21 | ], 22 | } 23 | ``` 24 | 25 | ## 如何使用 26 | 27 | 首先需要配置 `vuepress` 的 `locales` 配置。 28 | 29 | ```js 30 | module.exports = { 31 | locales: { 32 | '/': { 33 | lang: 'en-US', 34 | }, 35 | 36 | '/zh/': { 37 | lang: 'zh-CN', 38 | }, 39 | }, 40 | } 41 | ``` 42 | 43 | 如果有以下目录结构。 44 | 45 | ```sh 46 | ├── docs 47 | │ ├── guide 48 | │ │ └── page-missing.md 49 | │ └── zh 50 | │ │ └── guide 51 | │ │ ├── # 缺失的 page-missing.md 页面 52 | │ components 53 | │ ├── npm-badge 54 | │ │ ├── docs 55 | │ │ │ ├── index.md 56 | │ │ │ └── # 如果使用 *页面映射* 插件同样支持生成 index.zh-CN.md 页面 57 | ``` 58 | 59 | 那么就会自动生成 `/zh/guide/page-missing.html` 和 `/zh/components/npm-badge/index.html`。 60 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/prepare/prepareClientIframe.ts: -------------------------------------------------------------------------------- 1 | import { fs, path } from '@vuepress/utils' 2 | import type { App } from '@vuepress/core' 3 | 4 | /** 5 | * Generate iframe client temp file 6 | */ 7 | export const prepareClientIframe = async (app: App) => { 8 | const cwd = process.cwd() 9 | const source = app.dir.source() 10 | 11 | const clientPath = [ 12 | path.resolve(cwd, 'vuepress.client-iframe.ts'), 13 | path.resolve(cwd, 'vuepress.client-iframe.js'), 14 | path.resolve(cwd, 'vuepress.client-iframe.mjs'), 15 | path.resolve(source, '.vuepress/client-iframe.ts'), 16 | path.resolve(source, '.vuepress/client-iframe.js'), 17 | path.resolve(source, '.vuepress/client-iframe.mjs'), 18 | ].find((item) => fs.pathExistsSync(item)) 19 | 20 | let content = '' 21 | if (clientPath) { 22 | content = `\ 23 | import clientConfig0 from '${clientPath}' 24 | 25 | export const clientIframeConfigs = [ 26 | clientConfig0, 27 | ] 28 | ` 29 | } else { 30 | content = `\ 31 | export const clientIframeConfigs = [] 32 | ` 33 | } 34 | 35 | await app.writeTemp('internal/clientIframeConfigs.js', content) 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 haiweilian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/plugin-code-block/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @bfehub/vuepress-plugin-code-block 2 | 3 | ## 1.60.3 4 | 5 | ### Patch Changes 6 | 7 | - f89fca9: fix: components should not be rendered when demoUrl config 8 | - f89fca9: fix: adding a cache to make the HMR correct 9 | 10 | ## 1.60.2 11 | 12 | ### Patch Changes 13 | 14 | - 9b3971f: support baseDemoUrl in global config 15 | 16 | ## 1.60.1 17 | 18 | ### Patch Changes 19 | 20 | - 0c0d3b7: fix: entry find error when render 21 | 22 | ## 1.60.0 23 | 24 | ### Minor Changes 25 | 26 | - Update VuePress 2.0.0-beta.60 27 | 28 | ## 1.1.0 29 | 30 | ### Minor Changes 31 | 32 | - 68e593d: Update VuePress 2.0.0-beta.53 33 | 34 | ## 1.0.4 35 | 36 | ### Patch Changes 37 | 38 | - Optimized implementation 39 | 40 | ## 1.0.3 41 | 42 | ### Patch Changes 43 | 44 | - update vuepress 2.0.0-beta.49 45 | - Updated dependencies 46 | - @bfehub/vuepress-plugin-page-map@1.0.3 47 | 48 | ## 1.0.2 49 | 50 | ### Patch Changes 51 | 52 | - 53 | 54 | - Updated dependencies 55 | - @bfehub/vuepress-plugin-page-map@1.0.2 56 | 57 | ## 1.0.1 58 | 59 | ### Patch Changes 60 | 61 | - 62 | 63 | - Updated dependencies 64 | - @bfehub/vuepress-plugin-page-map@1.0.1 65 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/utils/resolve.ts: -------------------------------------------------------------------------------- 1 | import { fs, path } from '@vuepress/utils' 2 | 3 | const extensions = ['.js', '.ts', '.jsx', '.tsx', '.json', '.vue'] 4 | 5 | export const resolve = (...paths: string[]): string | null => { 6 | const filepath = path.resolve(...paths) 7 | 8 | if (tryResolveFile(filepath)) { 9 | return filepath 10 | } 11 | 12 | if (tryResolveDirectory(filepath)) { 13 | for (const ext of extensions) { 14 | const findpath = filepath + '/index' + ext 15 | if (tryResolveFile(findpath)) { 16 | return findpath 17 | } 18 | } 19 | } else { 20 | for (const ext of extensions) { 21 | const findpath = filepath + ext 22 | if (tryResolveFile(findpath)) { 23 | return findpath 24 | } 25 | } 26 | } 27 | 28 | return null 29 | } 30 | 31 | function tryResolveFile(file: string): boolean { 32 | try { 33 | const res = fs.statSync(file) 34 | return res.isFile() 35 | } catch (error) { 36 | return false 37 | } 38 | } 39 | 40 | function tryResolveDirectory(file: string): boolean { 41 | try { 42 | const res = fs.statSync(file) 43 | return res.isDirectory() 44 | } catch (error) { 45 | return false 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-and-deploy: 10 | # concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession. 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 🛎️ 14 | uses: actions/checkout@v3 15 | 16 | - name: Setup Node 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: 16 20 | 21 | - name: Setup Pnpm 22 | uses: pnpm/action-setup@v2 23 | with: 24 | version: 7 25 | 26 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 27 | run: | 28 | pnpm install --frozen-lockfile 29 | pnpm run docs:release 30 | 31 | - name: Deploy 🚀 32 | uses: JamesIves/github-pages-deploy-action@v4.2.5 33 | with: 34 | branch: gh-pages # The branch the action should deploy to. 35 | folder: docs/.vuepress/dist # The folder the action should deploy. 36 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/client/icons/Expand.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/dev/handlePageAdd.ts: -------------------------------------------------------------------------------- 1 | import { 2 | preparePageComponent, 3 | preparePageData, 4 | preparePagesComponents, 5 | preparePagesData, 6 | preparePagesRoutes, 7 | } from '@vuepress/core' 8 | import type { App, Page } from '@vuepress/core' 9 | import { createPage } from '../utils/index.js' 10 | import type { PageMapPluginOptions } from '../index.js' 11 | 12 | /** 13 | * Event handler for page add event 14 | * 15 | * Returns the added page 16 | */ 17 | export const handlePageAdd = async ( 18 | app: App, 19 | options: PageMapPluginOptions, 20 | filePath: string 21 | ): Promise => { 22 | // check if the added page is duplicated 23 | const pageIndex = app.pages.findIndex((page) => page.filePath === filePath) 24 | if (pageIndex !== -1) { 25 | return null 26 | } 27 | 28 | // create page 29 | const page = await createPage(app, options, filePath) 30 | 31 | // add the new page 32 | app.pages.push(page) 33 | 34 | // prepare page files 35 | await preparePageComponent(app, page) 36 | await preparePageData(app, page) 37 | 38 | // prepare pages entry 39 | await preparePagesComponents(app) 40 | await preparePagesData(app) 41 | await preparePagesRoutes(app) 42 | 43 | return page 44 | } 45 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/resolve/resolveOptions.ts: -------------------------------------------------------------------------------- 1 | import type { CodeBlockPluginOptions } from '../index.js' 2 | import type { CodeLocaleConfig } from '../../shared/index.js' 3 | 4 | export const DEFAULT_LOCALE_OPTIONS: CodeLocaleConfig = { 5 | '/': { 6 | hideText: 'Hide', 7 | showText: 'Expand', 8 | copyText: 'Copy', 9 | copySuccessText: 'Copy Success', 10 | }, 11 | '/zh/': { 12 | hideText: '隐藏', 13 | showText: '显示', 14 | copyText: '复制', 15 | copySuccessText: '复制成功', 16 | }, 17 | } 18 | 19 | /** 20 | * Create options with default values 21 | */ 22 | export const resolveOptions = ({ 23 | name = 'demo', 24 | headers = false, 25 | config = {}, 26 | locales = {}, 27 | }: Partial): CodeBlockPluginOptions => { 28 | if (!locales['/']) { 29 | locales['/'] = {} 30 | } 31 | 32 | if (!locales['/zh/']) { 33 | locales['/zh/'] = {} 34 | } 35 | 36 | Object.assign(locales['/'], { 37 | ...DEFAULT_LOCALE_OPTIONS['/'], 38 | ...locales['/'], 39 | }) 40 | 41 | Object.assign(locales['/zh/'], { 42 | ...DEFAULT_LOCALE_OPTIONS['/zh/'], 43 | ...locales['/zh/'], 44 | }) 45 | 46 | return { 47 | name, 48 | headers, 49 | config, 50 | locales, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/actions/using-workflows/about-workflows 2 | name: Release 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # https://github.com/actions/checkout 14 | - name: Checkout Repo 15 | uses: actions/checkout@v2 16 | 17 | # https://github.com/actions/setup-node 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 16.x 22 | 23 | # https://github.com/pnpm/action-setup 24 | - name: Setup Pnpm 25 | uses: pnpm/action-setup@v2 26 | with: 27 | version: 7.x 28 | 29 | # https://pnpm.io/zh/cli/install#--frozen-lockfile 30 | - name: Install Dependencies 31 | run: pnpm install --frozen-lockfile 32 | 33 | # https://github.com/changesets/action 34 | - name: Create Release Pull Request or Publish to npm 35 | uses: changesets/action@v1 36 | with: 37 | publish: pnpm release 38 | version: pnpm version-packages 39 | commit: 'chore: version packages' 40 | env: 41 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/prepare/prepareVmiComponents.ts: -------------------------------------------------------------------------------- 1 | import type { App } from '@vuepress/core' 2 | import type { PageCodeDep, PageCodeDepsHelper } from '../utils/index.js' 3 | import { setCache, isCacheChange } from '../utils/index.js' 4 | 5 | /** 6 | * Generate component path to components map temp file 7 | */ 8 | export const prepareVmiComponents = async ( 9 | app: App, 10 | store: PageCodeDepsHelper 11 | ) => { 12 | const map = new Map() 13 | for (const page of app.pages) { 14 | for (const comp of store.get(page.filePath)) { 15 | comp.isGenerateIframe && map.set(comp.compPath, comp) 16 | } 17 | } 18 | 19 | const content = `\ 20 | import { defineAsyncComponent } from 'vue' 21 | 22 | export const components = {\ 23 | ${[...map.values()] 24 | .map( 25 | ({ compHash, compPath }) => ` 26 | // path: ${compPath} 27 | ${JSON.stringify(compHash)}: defineAsyncComponent(() => import(${ 28 | compHash ? `/* webpackChunkName: "${compHash}" */` : '' 29 | }${JSON.stringify(compPath)})),` 30 | ) 31 | .join('')} 32 | } 33 | ` 34 | 35 | const writeTempPath = 'internal/pagesVmiComponents.js' 36 | if (isCacheChange(writeTempPath, content)) { 37 | setCache(writeTempPath, content) 38 | await app.writeTemp(writeTempPath, content) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/shared/page.ts: -------------------------------------------------------------------------------- 1 | import type { GitPluginPageData } from '@vuepress/plugin-git' 2 | import type { NavLink, SidebarConfig } from './nav.js' 3 | 4 | export interface DefaultThemePageData extends GitPluginPageData { 5 | filePathRelative: string | null 6 | } 7 | 8 | export interface DefaultThemePageFrontmatter { 9 | home?: boolean 10 | navbar?: boolean 11 | pageClass?: string 12 | } 13 | 14 | export interface DefaultThemeHomePageFrontmatter 15 | extends DefaultThemePageFrontmatter { 16 | home: true 17 | heroImage?: string 18 | heroImageDark?: string 19 | heroAlt?: string 20 | heroHeight?: number 21 | heroText?: string | null 22 | tagline?: string | null 23 | actions?: { 24 | text: string 25 | link: string 26 | type?: 'primary' | 'secondary' 27 | }[] 28 | features?: { 29 | title: string 30 | details: string 31 | }[] 32 | footer?: string 33 | footerHtml?: boolean 34 | } 35 | 36 | export interface DefaultThemeNormalPageFrontmatter 37 | extends DefaultThemePageFrontmatter { 38 | home?: false 39 | editLink?: boolean 40 | editLinkPattern?: string 41 | lastUpdated?: boolean 42 | contributors?: boolean 43 | sidebar?: 'auto' | false | SidebarConfig 44 | sidebarDepth?: number 45 | prev?: string | NavLink 46 | next?: string | NavLink 47 | } 48 | -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/pageMapPlugin.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from '@vuepress/core' 2 | import { resolveOptions, resolvePages } from './resolve/index.js' 3 | import { watchPageFiles } from './dev/index.js' 4 | import { vitePageHMR } from './plugins/index.js' 5 | 6 | /** 7 | * Options of @bfehub/vuepress-plugin-page-map 8 | */ 9 | export interface PageMapPluginOptions { 10 | /** 11 | * 匹配规则 12 | */ 13 | patterns: string[] 14 | 15 | /** 16 | * 自定义路径替换 17 | */ 18 | pathMapRule: (path: string) => string 19 | } 20 | 21 | export const pageMapPlugin = ( 22 | config: Partial = {} 23 | ): Plugin => { 24 | const options = resolveOptions(config) 25 | 26 | return { 27 | name: '@bfehub/vuepress-plugin-page-map', 28 | 29 | multiple: true, 30 | 31 | async onInitialized(app) { 32 | app.pages.push(...(await resolvePages(app, options))) 33 | }, 34 | 35 | onWatched: (app, watchers) => { 36 | watchers.push(...watchPageFiles(app, options)) 37 | }, 38 | 39 | extendsBundlerOptions(bundlerOptions, app) { 40 | if (app.options.bundler.name === '@vuepress/bundler-vite') { 41 | bundlerOptions.viteOptions ??= {} 42 | bundlerOptions.viteOptions.plugins ??= [] 43 | bundlerOptions.viteOptions.plugins.push(vitePageHMR(app)) 44 | } 45 | }, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/plugin-page-map/src/node/plugins/hmr.ts: -------------------------------------------------------------------------------- 1 | import { handlePageChange as handlePageMapPageChange } from '../dev/index.js' 2 | import type { App } from '@vuepress/core' 3 | import type { Plugin, HmrContext } from 'vite' 4 | 5 | // https://cn.vitejs.dev/guide/api-plugin.html#handlehotupdate 6 | export const vitePageHMR = (app: App): Plugin => { 7 | return { 8 | name: '@bfehub/vuepress-plugin-page-map:hmr', 9 | enforce: 'post', 10 | async handleHotUpdate(ctx: HmrContext) { 11 | for (const module of ctx.modules) { 12 | for (const importer of module.importers) { 13 | const tempPath = importer.file 14 | if (tempPath.endsWith('.html.vue')) { 15 | await handlePageChange(app, tempPath) 16 | } 17 | } 18 | } 19 | }, 20 | } 21 | } 22 | 23 | async function handlePageChange(app: App, tempPath: string) { 24 | for (const page of app.pages) { 25 | if (page.componentFilePath === tempPath) { 26 | // _PageMapPluginOptions from plugin-page-map/src/node/utils/createPage.ts 27 | if ((page as any)._PageMapPluginOptions) { 28 | await handlePageMapPageChange( 29 | app, 30 | (page as any)._PageMapPluginOptions, 31 | page.filePath 32 | ) 33 | } else { 34 | // await handleVuePressPageChange(app, page.filePath) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/plugins/hmr.ts: -------------------------------------------------------------------------------- 1 | import { handlePageChange as handleVuePressPageChange } from '@vuepress/cli' 2 | import type { App } from '@vuepress/core' 3 | import type { Plugin, HmrContext } from 'vite' 4 | 5 | // https://cn.vitejs.dev/guide/api-plugin.html#handlehotupdate 6 | export const vitePageHMR = (app: App): Plugin => { 7 | return { 8 | name: '@bfehub/vuepress-plugin-code-block:hmr', 9 | enforce: 'post', 10 | async handleHotUpdate(ctx: HmrContext) { 11 | for (const module of ctx.modules) { 12 | for (const importer of module.importers) { 13 | const tempPath = importer.file 14 | if (tempPath.endsWith('.html.vue')) { 15 | await handlePageChange(app, tempPath) 16 | } 17 | } 18 | } 19 | }, 20 | } 21 | } 22 | 23 | async function handlePageChange(app: App, tempPath: string) { 24 | for (const page of app.pages) { 25 | if (page.componentFilePath === tempPath) { 26 | // _PageMapPluginOptions from plugin-page-map/src/node/utils/createPage.ts 27 | if ((page as any)._PageMapPluginOptions) { 28 | // await handlePageMapPageChange( 29 | // app, 30 | // (page as any)._PageMapPluginOptions, 31 | // page.filePath 32 | // ) 33 | } else { 34 | await handleVuePressPageChange(app, page.filePath) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-code-block/src/node/utils/scan.ts: -------------------------------------------------------------------------------- 1 | // Modify from https://www.npmjs.com/package/import-scan 2 | 3 | export const scanImports = (code: string) => { 4 | const imports: string[] = [] 5 | const lexer = 6 | /(\/\/.*$)|(\/\*)|(?:^[ \t]*import(?: [^\n]+? from)?[ ]+["'`]([^"'`]+)["'`])|(?:(?<=[^\w.$])(?:require|import)\(\s*["'`]([^"'`]+)["'`]\s*\))/gm 7 | 8 | while (true) { 9 | const match = lexer.exec(code) 10 | if (!match) return imports 11 | 12 | // Line comment 13 | if (match[1]) continue 14 | 15 | // Block comment 16 | if (match[2]) { 17 | let start = match.index + 2 18 | while (true) { 19 | const end = code.indexOf('*/', start) 20 | if (end < 0) return imports 21 | if (isEscaped(code, end)) { 22 | start = end + 1 23 | continue 24 | } 25 | lexer.lastIndex = end + 2 26 | break 27 | } 28 | } 29 | 30 | // Imported path 31 | else { 32 | const path = match[3] || match[4] 33 | if (imports.indexOf(path) < 0) imports.push(path) 34 | } 35 | } 36 | } 37 | 38 | // Returns true when the given string ends with an unescaped escape. 39 | function isEscaped(str: string, fromIndex: number) { 40 | const ESCAPE = '\\'.charCodeAt(0) 41 | let i = fromIndex 42 | let n = 0 43 | while (i && str.charCodeAt(--i) === ESCAPE) n++ 44 | return n % 2 === 1 45 | } 46 | -------------------------------------------------------------------------------- /packages/plugin-code-block/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bfehub/vuepress-plugin-code-block", 3 | "type": "module", 4 | "version": "1.60.3", 5 | "description": "VuePress plugin - code-block", 6 | "files": [ 7 | "lib" 8 | ], 9 | "keywords": [ 10 | "vuepress-plugin", 11 | "vuepress", 12 | "plugin", 13 | "code-block" 14 | ], 15 | "license": "MIT", 16 | "author": "haiweilian", 17 | "main": "lib/node/index.js", 18 | "types": "lib/node/index.d.ts", 19 | "homepage": "https://github.com/bfehub/vmi", 20 | "scripts": { 21 | "build": "tsc -b tsconfig.build.json", 22 | "clean": "rimraf lib *.tsbuildinfo", 23 | "copy": "cpx \"src/**/*.{d.ts,vue,scss}\" lib" 24 | }, 25 | "dependencies": { 26 | "@mdit-vue/shared": "^0.7.0", 27 | "@types/markdown-it": "^12.2.3", 28 | "@vuepress/cli": "2.0.0-beta.60", 29 | "@vuepress/client": "2.0.0-beta.60", 30 | "@vuepress/core": "2.0.0-beta.60", 31 | "@vuepress/markdown": "2.0.0-beta.60", 32 | "@vuepress/plugin-prismjs": "2.0.0-beta.60", 33 | "@vuepress/shared": "2.0.0-beta.60", 34 | "@vuepress/utils": "2.0.0-beta.60", 35 | "@vueuse/core": "^8.2.1", 36 | "markdown-it": "^13.0.1", 37 | "posthtml-parser": "^0.11.0", 38 | "posthtml-render": "^3.0.0", 39 | "vue": "^3.2.47", 40 | "vue-router": "^4.0.15" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/styles/vars-dark.scss: -------------------------------------------------------------------------------- 1 | html.dark { 2 | // brand colors 3 | --c-brand: #3aa675; 4 | --c-brand-light: #349469; 5 | 6 | // background colors 7 | --c-bg: #22272e; 8 | --c-bg-light: #2b313a; 9 | --c-bg-lighter: #262c34; 10 | 11 | // text colors 12 | --c-text: #adbac7; 13 | --c-text-light: #96a7b7; 14 | --c-text-lighter: #8b9eb0; 15 | --c-text-lightest: #8094a8; 16 | 17 | // border colors 18 | --c-border: #3e4c5a; 19 | --c-border-dark: #34404c; 20 | 21 | // custom container colors 22 | --c-tip: #318a62; 23 | --c-warning: #ceab00; 24 | --c-warning-bg: #7e755b; 25 | --c-warning-title: #ceac03; 26 | --c-warning-text: #362e00; 27 | --c-danger: #940000; 28 | --c-danger-bg: #806161; 29 | --c-danger-title: #610000; 30 | --c-danger-text: #3a0000; 31 | --c-details-bg: #323843; 32 | 33 | // code blocks vars 34 | --code-bg-color: #282c34; 35 | --code-hl-bg-color: #363b46; 36 | --code-ln-color: #9e9e9e; 37 | } 38 | 39 | // plugin-docsearch 40 | html.dark .DocSearch { 41 | --docsearch-logo-color: var(--c-text); 42 | --docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309; 43 | --docsearch-key-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 44 | 0 2px 2px 0 rgb(3 4 9 / 30%); 45 | --docsearch-key-gradient: linear-gradient(-225deg, #444950, #1c1e21); 46 | --docsearch-footer-shadow: inset 0 1px 0 0 rgb(73 76 106 / 50%), 47 | 0 -4px 8px 0 rgb(0 0 0 / 20%); 48 | } 49 | -------------------------------------------------------------------------------- /packages/plugin-page-missing/src/node/resolve/resolvePageMissing.ts: -------------------------------------------------------------------------------- 1 | import { path } from '@vuepress/utils' 2 | import { resolvePageHtmlInfo } from '@vuepress/core' 3 | import type { App, Page } from '@vuepress/core' 4 | 5 | export const resolvePageMissing = async (app: App): Promise => { 6 | const locales = app.options.locales 7 | const langs = Object.keys(locales) 8 | 9 | const pages = app.pages 10 | const missing: Page[] = [] 11 | 12 | for (const page of pages) { 13 | if (page.path === '/404.html') continue 14 | 15 | for (const lang of langs) { 16 | // 如果不是默认语言的页面不处理 17 | if (lang === '/') continue 18 | if (page.path.startsWith(lang)) continue 19 | 20 | // 查询是否已经存在对应的页面 21 | const localePath = path.join(lang, page.path) 22 | if (pages.find((page) => page.path === localePath)) continue 23 | 24 | // 不存在复制页面更改路径信息 25 | const { htmlFilePath, htmlFilePathRelative } = resolvePageHtmlInfo({ 26 | app, 27 | path: localePath, 28 | }) 29 | 30 | missing.push({ 31 | ...page, 32 | 33 | key: `${page.key}-missing`, 34 | 35 | path: localePath, 36 | pathLocale: lang, 37 | pathInferred: localePath, 38 | 39 | filePath: page.filePath, 40 | filePathRelative: path.join(lang, page.filePathRelative), 41 | 42 | htmlFilePath, 43 | htmlFilePathRelative, 44 | }) 45 | } 46 | } 47 | 48 | pages.push(...missing) 49 | 50 | return missing 51 | } 52 | -------------------------------------------------------------------------------- /packages/theme-vmi/src/client/styles/code-group.scss: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | 3 | /** 4 | * code-group 5 | */ 6 | .code-group__nav { 7 | padding-top: 10px; 8 | padding-bottom: calc(1.7rem - 6px); 9 | padding-left: 10px; 10 | margin-top: 0.85rem; 11 | // 2 * margin + border-radius of
 tag
12 |   margin-bottom: calc(-1.7rem - 6px);
13 |   background-color: var(--code-bg-color);
14 |   border-top-left-radius: 6px;
15 |   border-top-right-radius: 6px;
16 | }
17 | 
18 | .code-group__ul {
19 |   display: inline-flex;
20 |   padding-left: 0;
21 |   margin: auto 0;
22 |   list-style: none;
23 | }
24 | 
25 | .code-group__nav-tab {
26 |   padding: 5px;
27 |   font-size: 0.85em;
28 |   font-weight: 600;
29 |   line-height: 1.4;
30 |   // color: rgb(255 255 255 / 90%);
31 |   cursor: pointer;
32 |   background-color: transparent;
33 |   border: 0;
34 | }
35 | 
36 | .code-group__nav-tab:focus {
37 |   outline: none;
38 | }
39 | 
40 | .code-group__nav-tab:focus-visible {
41 |   outline: 1px solid rgb(255 255 255 / 90%);
42 | }
43 | 
44 | .code-group__nav-tab-active {
45 |   border-bottom: var(--c-brand) 1px solid;
46 | }
47 | 
48 | @media (max-width: $MQMobileNarrow) {
49 |   .code-group__nav {
50 |     margin-right: -1.5rem;
51 |     margin-left: -1.5rem;
52 |     border-radius: 0;
53 |   }
54 | }
55 | 
56 | /**
57 |  * code-group-item
58 |  */
59 | .code-group-item {
60 |   display: none;
61 | }
62 | 
63 | .code-group-item__active {
64 |   display: block;
65 | }
66 | 
67 | .code-group-item > pre {
68 |   background-color: orange;
69 | }
70 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/styles/page.scss:
--------------------------------------------------------------------------------
 1 | @import '_mixins';
 2 | @import '_variables';
 3 | 
 4 | .page {
 5 |   display: block;
 6 |   padding-bottom: 2rem;
 7 | }
 8 | 
 9 | .page-meta {
10 |   @include content_wrapper;
11 | 
12 |   padding-top: 1rem;
13 |   padding-bottom: 1rem;
14 |   overflow: auto;
15 | 
16 |   .meta-item {
17 |     margin-top: 0.8rem;
18 |     cursor: default;
19 | 
20 |     .meta-item-label {
21 |       font-weight: 500;
22 |       color: var(--c-text-lighter);
23 |     }
24 | 
25 |     .meta-item-info {
26 |       font-weight: 400;
27 |       color: var(--c-text-quote);
28 |     }
29 |   }
30 | 
31 |   .edit-link {
32 |     display: inline-block;
33 |     margin-right: 0.25rem;
34 |   }
35 | 
36 |   .last-updated {
37 |     float: right;
38 |   }
39 | }
40 | 
41 | @media (max-width: $MQMobile) {
42 |   .page-meta {
43 |     .last-updated {
44 |       float: none;
45 |       font-size: 0.8em;
46 |     }
47 | 
48 |     .contributors {
49 |       font-size: 0.8em;
50 |     }
51 |   }
52 | }
53 | 
54 | .page-nav {
55 |   @include content_wrapper;
56 | 
57 |   padding-top: 1rem;
58 |   padding-bottom: 0;
59 | 
60 |   .inner {
61 |     min-height: 2rem;
62 |     padding-top: 1rem;
63 |     margin-top: 0;
64 |     overflow: auto;
65 |     border-top: 1px solid var(--c-border);
66 |     transition: border-color var(--t-color);
67 |   }
68 | 
69 |   .prev {
70 |     a::before {
71 |       content: '←';
72 |     }
73 |   }
74 | 
75 |   .next {
76 |     float: right;
77 | 
78 |     a::after {
79 |       content: '→';
80 |     }
81 |   }
82 | }
83 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/shared/nav.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Base nav item, displayed as text
 3 |  */
 4 | export interface NavItem {
 5 |   text: string
 6 |   ariaLabel?: string
 7 | }
 8 | 
 9 | /**
10 |  * Base nav group, has nav items children
11 |  */
12 | export interface NavGroup extends NavItem {
13 |   children: T[]
14 | }
15 | 
16 | /**
17 |  * Props for ``
18 |  */
19 | export interface NavLink extends NavItem {
20 |   link: string
21 |   rel?: string
22 |   target?: string
23 |   activeMatch?: string
24 | }
25 | 
26 | /**
27 |  * Navbar types
28 |  */
29 | // user config
30 | export type NavbarItem = NavLink
31 | export type NavbarGroup = NavGroup
32 | export type NavbarConfig = (NavbarItem | NavbarGroup | string)[]
33 | // resolved
34 | export type ResolvedNavbarItem = NavbarItem | NavGroup
35 | 
36 | /**
37 |  * Sidebar types
38 |  */
39 | // user config
40 | export type SidebarItem = NavItem & Partial
41 | export type SidebarGroup = SidebarItem &
42 |   NavGroup
43 | export type SidebarGroupCollapsible = SidebarGroup & {
44 |   collapsible?: boolean
45 | }
46 | export type SidebarConfigArray = (
47 |   | SidebarItem
48 |   | SidebarGroupCollapsible
49 |   | string
50 | )[]
51 | export type SidebarConfigObject = Record
52 | export type SidebarConfig = SidebarConfigArray | SidebarConfigObject
53 | // resolved
54 | export type ResolvedSidebarItem = SidebarItem &
55 |   Partial> & {
56 |     collapsible?: boolean
57 |   }
58 | 


--------------------------------------------------------------------------------
/packages/plugin-code-block/src/node/utils/dep.ts:
--------------------------------------------------------------------------------
 1 | import type { CodeNodeConfig } from '../../shared/index.js'
 2 | 
 3 | export interface PageCodeDep {
 4 |   /**
 5 |    * 组件所在页面路径
 6 |    */
 7 |   pagePath: string
 8 |   /**
 9 |    * 组件绝对路径
10 |    */
11 |   compPath: string
12 |   /**
13 |    * 组件 hash 名称
14 |    */
15 |   compHash: string
16 |   /**
17 |    * 组件名称
18 |    */
19 |   compName: string
20 |   /**
21 |    * 组件属性
22 |    */
23 |   compAttrs: CodeNodeConfig
24 |   /**
25 |    * 是否生成 iframe 模式
26 |    */
27 |   isGenerateIframe?: boolean
28 | }
29 | 
30 | /**
31 |  * Page deps helper
32 |  */
33 | export interface PageCodeDepsHelper {
34 |   /**
35 |    * Get all code's that depend on the `pagePath`
36 |    */
37 |   get: (path: string) => PageCodeDep[]
38 | 
39 |   /**
40 |    * Handle deps when adding a code
41 |    */
42 |   add: (code: PageCodeDep) => void
43 | 
44 |   /**
45 |    * Handle deps when removing a code
46 |    */
47 |   remove: (code: PageCodeDep) => void
48 | }
49 | 
50 | /**
51 |  * Create page deps helper
52 |  */
53 | export const createPageCodeDepsHelper = (): PageCodeDepsHelper => {
54 |   const store = new Map>()
55 | 
56 |   return {
57 |     get(path) {
58 |       return [...(store.get(path)?.values() || [])]
59 |     },
60 | 
61 |     add(code) {
62 |       if (!store.has(code.pagePath)) {
63 |         store.set(code.pagePath, new Map())
64 |       }
65 |       store.get(code.pagePath)?.set(code.compPath, code)
66 |     },
67 | 
68 |     remove(code) {
69 |       store.get(code.pagePath)?.delete(code.compPath)
70 |     },
71 |   }
72 | }
73 | 


--------------------------------------------------------------------------------
/packages/plugin-code-block/src/node/parse/readSource.ts:
--------------------------------------------------------------------------------
 1 | import { fs, path, warn } from '@vuepress/utils'
 2 | import { highlight, scanImports, resolve } from '../utils/index.js'
 3 | import type { CodeSource } from '../../shared/index.js'
 4 | 
 5 | export const readSource = (
 6 |   filePath: string,
 7 |   scanImport?: boolean,
 8 |   noLineNumbers?: boolean
 9 | ): CodeSource[] => {
10 |   const sources: CodeSource[] = []
11 | 
12 |   const source = readFileSource(filePath, noLineNumbers)
13 |   sources.push(source)
14 | 
15 |   if (scanImport) {
16 |     parseImports(source.rawCode, path.dirname(filePath), sources, noLineNumbers)
17 |   }
18 | 
19 |   return sources
20 | }
21 | 
22 | export const parseImports = (
23 |   rawCode: string,
24 |   dirPath: string,
25 |   sources: CodeSource[],
26 |   noLineNumbers: boolean
27 | ) => {
28 |   const imports = scanImports(rawCode)
29 | 
30 |   for (const mod of imports) {
31 |     if (mod.startsWith('.')) {
32 |       const filePath = resolve(dirPath, mod)
33 |       if (filePath) {
34 |         sources.push(readFileSource(filePath, noLineNumbers))
35 |       }
36 |     }
37 |   }
38 | }
39 | 
40 | export const readFileSource = (
41 |   filePath: string,
42 |   noLineNumbers: boolean
43 | ): CodeSource => {
44 |   let code = ''
45 |   const name = path.basename(filePath)
46 | 
47 |   if (fs.existsSync(filePath)) {
48 |     code = fs.readFileSync(filePath, 'utf-8')
49 |   } else {
50 |     warn('not exists path:' + path)
51 |   }
52 | 
53 |   return {
54 |     name,
55 |     rawCode: code,
56 |     highlightCode: highlight(code, path.extname(name).slice(1), noLineNumbers),
57 |   }
58 | }
59 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@bfehub/vuepress-theme-vmi",
 3 |   "type": "module",
 4 |   "version": "1.60.0",
 5 |   "description": "Vmi theme of VuePress",
 6 |   "files": [
 7 |     "lib",
 8 |     "templates"
 9 |   ],
10 |   "keywords": [
11 |     "vuepress-theme",
12 |     "vuepress",
13 |     "theme",
14 |     "vmi"
15 |   ],
16 |   "license": "MIT",
17 |   "author": "haiweilian",
18 |   "main": "lib/node/index.js",
19 |   "types": "lib/node/index.d.ts",
20 |   "homepage": "https://github.com/bfehub/vmi",
21 |   "scripts": {
22 |     "build": "tsc -b tsconfig.build.json",
23 |     "clean": "rimraf lib *.tsbuildinfo",
24 |     "copy": "cpx \"src/**/*.{d.ts,vue,scss}\" lib"
25 |   },
26 |   "dependencies": {
27 |     "@vuepress/client": "2.0.0-beta.60",
28 |     "@vuepress/core": "2.0.0-beta.60",
29 |     "@vuepress/plugin-active-header-links": "2.0.0-beta.60",
30 |     "@vuepress/plugin-back-to-top": "2.0.0-beta.60",
31 |     "@vuepress/plugin-container": "2.0.0-beta.60",
32 |     "@vuepress/plugin-external-link-icon": "2.0.0-beta.60",
33 |     "@vuepress/plugin-git": "2.0.0-beta.60",
34 |     "@vuepress/plugin-medium-zoom": "2.0.0-beta.60",
35 |     "@vuepress/plugin-nprogress": "2.0.0-beta.60",
36 |     "@vuepress/plugin-palette": "2.0.0-beta.60",
37 |     "@vuepress/plugin-prismjs": "2.0.0-beta.60",
38 |     "@vuepress/plugin-theme-data": "2.0.0-beta.60",
39 |     "@vuepress/plugin-toc": "2.0.0-beta.60",
40 |     "@vuepress/shared": "2.0.0-beta.60",
41 |     "@vuepress/utils": "2.0.0-beta.60",
42 |     "@vueuse/core": "^8.2.1",
43 |     "sass": "^1.51.0",
44 |     "vue": "^3.2.47",
45 |     "vue-router": "^4.0.14"
46 |   },
47 |   "publishConfig": {
48 |     "access": "public"
49 |   }
50 | }
51 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "root",
 3 |   "private": true,
 4 |   "type": "module",
 5 |   "description": "A theme for vuepress",
 6 |   "scripts": {
 7 |     "prepare": "husky install",
 8 |     "preinstall": "npx only-allow pnpm",
 9 |     "commit": "npx git-cz",
10 |     "clean": "pnpm --parallel --stream clean",
11 |     "dev:tsc": "pnpm build:tsc --watch",
12 |     "dev:copy": "pnpm build:copy --watch",
13 |     "build": "pnpm build:all && pnpm build:copy",
14 |     "build:all": "pnpm -r --stream build",
15 |     "build:copy": "pnpm --parallel --stream copy",
16 |     "build:tsc": "tsc -b tsconfig.build.json",
17 |     "docs:dev": "pnpm --filter=docs docs:dev",
18 |     "docs:clean": "pnpm --filter=docs docs:clean",
19 |     "docs:build": "pnpm --filter=docs docs:build",
20 |     "docs:serve": "pnpm --filter=docs docs:serve",
21 |     "docs:release": "pnpm build && pnpm docs:build",
22 |     "version-packages": "changeset version",
23 |     "release": "pnpm build && changeset publish"
24 |   },
25 |   "devDependencies": {
26 |     "@bfehub/eslint-config-vue": "^1.3.0",
27 |     "@bfehub/stylelint-config-basic": "^1.3.0",
28 |     "@changesets/cli": "^2.22.0",
29 |     "@commitlint/cli": "^16.2.4",
30 |     "@commitlint/config-conventional": "^16.2.4",
31 |     "commitizen": "^4.2.4",
32 |     "cpx2": "^4.2.0",
33 |     "cz-conventional-changelog": "^3.3.0",
34 |     "eslint": "^8.14.0",
35 |     "husky": "^7.0.4",
36 |     "lint-staged": "^12.4.1",
37 |     "postcss": "^8.4.13",
38 |     "prettier": "^2.6.2",
39 |     "rimraf": "^3.0.2",
40 |     "stylelint": "^14.8.2",
41 |     "typescript": "^4.6.4",
42 |     "vite": "^4.1.0"
43 |   },
44 |   "engines": {
45 |     "node": ">=14",
46 |     "pnpm": ">=7"
47 |   }
48 | }
49 | 


--------------------------------------------------------------------------------
/packages/plugin-page-map/src/node/dev/pageDepsHelper.ts:
--------------------------------------------------------------------------------
 1 | import type { Page } from '@vuepress/core'
 2 | 
 3 | /**
 4 |  * Page deps helper
 5 |  */
 6 | export interface PageDepsHelper {
 7 |   /**
 8 |    * Handle deps when adding a page
 9 |    */
10 |   add: (page: Page) => string[]
11 | 
12 |   /**
13 |    * Handle deps when removing a page
14 |    */
15 |   remove: (page: Page) => string[]
16 | 
17 |   /**
18 |    * Get all pages that depend on the `dep`
19 |    */
20 |   get: (dep: string) => string[]
21 | }
22 | 
23 | /**
24 |  * Create page deps helper
25 |  */
26 | export const createPageDepsHelper = (): PageDepsHelper => {
27 |   const store = new Map>()
28 |   return {
29 |     add: ({ deps, filePathRelative }) => {
30 |       const depsAdded: string[] = []
31 |       if (filePathRelative) {
32 |         deps.forEach((item) => {
33 |           if (!store.has(item)) {
34 |             store.set(item, new Set())
35 |             depsAdded.push(item)
36 |           }
37 |           store.get(item)?.add(filePathRelative)
38 |         })
39 |       }
40 |       return depsAdded
41 |     },
42 |     remove: ({ deps, filePathRelative }) => {
43 |       const depsRemoved: string[] = []
44 |       if (filePathRelative) {
45 |         deps.forEach((item) => {
46 |           const pagePathsSet = store.get(item)
47 |           pagePathsSet?.delete(filePathRelative)
48 |           if (pagePathsSet?.size === 0) {
49 |             store.delete(item)
50 |             depsRemoved.push(item)
51 |           }
52 |         })
53 |       }
54 |       return depsRemoved
55 |     },
56 |     get: (dep) => {
57 |       const pagePathsSet = store.get(dep)
58 |       return pagePathsSet ? [...pagePathsSet] : []
59 |     },
60 |   }
61 | }
62 | 


--------------------------------------------------------------------------------
/packages/plugin-code-block/src/node/plugins/iframe.ts:
--------------------------------------------------------------------------------
 1 | import { fs } from '@vuepress/utils'
 2 | import type { App } from '@vuepress/core'
 3 | import type { Plugin } from 'vite'
 4 | 
 5 | export const vitePageIframe = (app: App): Plugin => {
 6 |   return {
 7 |     name: '@bfehub/vuepress-plugin-code-block:iframe',
 8 |     async config(config) {
 9 |       if (config.build.ssr) return
10 | 
11 |       const input = config.build.rollupOptions.input
12 |       const inputs: string[] = []
13 | 
14 |       if (typeof input === 'string') {
15 |         inputs.push(input)
16 |       } else if (typeof input === 'object') {
17 |         inputs.push(...Object.values(input))
18 |       } else if (Array.isArray(input)) {
19 |         inputs.push(...(input as any[]))
20 |       }
21 |       inputs.push(app.dir.temp('vite-root/-iframe.html'))
22 | 
23 |       config.build.rollupOptions.input = inputs
24 | 
25 |       await app.writeTemp(
26 |         'vite-root/-iframe.html',
27 |         fs
28 |           .readFileSync(
29 |             app.env.isBuild
30 |               ? app.options.templateBuild
31 |               : app.options.templateDev
32 |           )
33 |           .toString()
34 |           .replace(
35 |             /<\/body>/,
36 |             `\
37 | 
40 | `
41 |           )
42 |       )
43 |     },
44 |     generateBundle(_, bundle) {
45 |       // Make sure app chunk is first
46 |       Object.entries(bundle).forEach(([key, val]) => {
47 |         if (val.type === 'chunk' && val.name !== 'app' && val.isEntry) {
48 |           delete bundle[key]
49 |           bundle[key] = val
50 |         }
51 |       })
52 |     },
53 |   }
54 | }
55 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/components/NavbarBrand.vue:
--------------------------------------------------------------------------------
 1 | 
42 | 
43 | 
56 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/config.ts:
--------------------------------------------------------------------------------
 1 | import { defineClientConfig } from '@vuepress/client'
 2 | import { h } from 'vue'
 3 | import { Badge, CodeGroup, CodeGroupItem } from './components/global/index.js'
 4 | import {
 5 |   setupDarkMode,
 6 |   setupSidebarItems,
 7 |   useScrollPromise,
 8 | } from './composables/index.js'
 9 | import Layout from './layouts/Layout.vue'
10 | import NotFound from './layouts/NotFound.vue'
11 | 
12 | import './styles/index.scss'
13 | 
14 | export default defineClientConfig({
15 |   enhance({ app, router }) {
16 |     app.component('Badge', Badge)
17 |     app.component('CodeGroup', CodeGroup)
18 |     app.component('CodeGroupItem', CodeGroupItem)
19 | 
20 |     // compat with @vuepress/plugin-external-link-icon
21 |     app.component('AutoLinkExternalIcon', () => {
22 |       const ExternalLinkIcon = app.component('ExternalLinkIcon')
23 |       if (ExternalLinkIcon) {
24 |         return h(ExternalLinkIcon)
25 |       }
26 |       return null
27 |     })
28 | 
29 |     // compat with @vuepress/plugin-docsearch and @vuepress/plugin-search
30 |     app.component('NavbarSearch', () => {
31 |       const SearchComponent =
32 |         app.component('Docsearch') || app.component('SearchBox')
33 |       if (SearchComponent) {
34 |         return h(SearchComponent)
35 |       }
36 |       return null
37 |     })
38 | 
39 |     // handle scrollBehavior with transition
40 |     const scrollBehavior = router.options.scrollBehavior!
41 |     router.options.scrollBehavior = async (...args) => {
42 |       await useScrollPromise().wait()
43 |       return scrollBehavior(...args)
44 |     }
45 |   },
46 | 
47 |   setup() {
48 |     setupDarkMode()
49 |     setupSidebarItems()
50 |   },
51 | 
52 |   layouts: {
53 |     Layout,
54 |     NotFound,
55 |   },
56 | })
57 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/utils/resolveEditLink.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   isLinkHttp,
 3 |   removeEndingSlash,
 4 |   removeLeadingSlash,
 5 | } from '@vuepress/shared'
 6 | import { resolveRepoType } from './resolveRepoType.js'
 7 | import type { RepoType } from './resolveRepoType.js'
 8 | 
 9 | export const editLinkPatterns: Record, string> = {
10 |   GitHub: ':repo/edit/:branch/:path',
11 |   GitLab: ':repo/-/edit/:branch/:path',
12 |   Gitee: ':repo/edit/:branch/:path',
13 |   Bitbucket:
14 |     ':repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default',
15 | }
16 | 
17 | const resolveEditLinkPatterns = ({
18 |   docsRepo,
19 |   editLinkPattern,
20 | }: {
21 |   docsRepo: string
22 |   editLinkPattern?: string
23 | }): string | null => {
24 |   if (editLinkPattern) {
25 |     return editLinkPattern
26 |   }
27 | 
28 |   const repoType = resolveRepoType(docsRepo)
29 |   if (repoType !== null) {
30 |     return editLinkPatterns[repoType]
31 |   }
32 | 
33 |   return null
34 | }
35 | 
36 | export const resolveEditLink = ({
37 |   docsRepo,
38 |   docsBranch,
39 |   docsDir,
40 |   filePathRelative,
41 |   editLinkPattern,
42 | }: {
43 |   docsRepo: string
44 |   docsBranch: string
45 |   docsDir: string
46 |   filePathRelative: string | null
47 |   editLinkPattern?: string
48 | }): string | null => {
49 |   if (!filePathRelative) return null
50 | 
51 |   const pattern = resolveEditLinkPatterns({ docsRepo, editLinkPattern })
52 |   if (!pattern) return null
53 | 
54 |   return pattern
55 |     .replace(
56 |       /:repo/,
57 |       isLinkHttp(docsRepo) ? docsRepo : `https://github.com/${docsRepo}`
58 |     )
59 |     .replace(/:branch/, docsBranch)
60 |     .replace(
61 |       /:path/,
62 |       removeLeadingSlash(`${removeEndingSlash(docsDir)}/${filePathRelative}`)
63 |     )
64 | }
65 | 


--------------------------------------------------------------------------------
/packages/plugin-page-map/src/node/dev/handlePageChange.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   preparePageComponent,
 3 |   preparePageData,
 4 |   preparePagesComponents,
 5 |   preparePagesData,
 6 |   preparePagesRoutes,
 7 | } from '@vuepress/core'
 8 | import type { App, Page } from '@vuepress/core'
 9 | import { createPage } from '../utils/index.js'
10 | import type { PageMapPluginOptions } from '../index.js'
11 | 
12 | /**
13 |  * Event handler for page change event
14 |  *
15 |  * Returns the old page and the new page tuple
16 |  */
17 | export const handlePageChange = async (
18 |   app: App,
19 |   options: PageMapPluginOptions,
20 |   filePath: string
21 | ): Promise<[Page, Page] | null> => {
22 |   // get page index of the changed file
23 |   const pageIndex = app.pages.findIndex((page) => page.filePath === filePath)
24 |   if (pageIndex === -1) {
25 |     return null
26 |   }
27 | 
28 |   // get the old page of the changed file
29 |   const pageOld = app.pages[pageIndex]
30 | 
31 |   // create a new page from the changed file
32 |   const pageNew = await createPage(app, options, filePath)
33 | 
34 |   // replace the old page with the new page
35 |   app.pages.splice(pageIndex, 1, pageNew)
36 | 
37 |   // prepare page files
38 |   await preparePageComponent(app, pageNew)
39 |   await preparePageData(app, pageNew)
40 | 
41 |   const isPathChanged = pageOld.path !== pageNew.path
42 |   const isRouteMetaChanged =
43 |     JSON.stringify(pageOld.routeMeta) !== JSON.stringify(pageNew.routeMeta)
44 | 
45 |   // prepare pages entry if the path is changed
46 |   if (isPathChanged) {
47 |     await preparePagesComponents(app)
48 |     await preparePagesData(app)
49 |   }
50 | 
51 |   // prepare pages routes if the path or routeMeta is changed
52 |   if (isPathChanged || isRouteMetaChanged) {
53 |     await preparePagesRoutes(app)
54 |   }
55 | 
56 |   return [pageOld, pageNew]
57 | }
58 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/node/utils/assignDefaultLocaleOptions.ts:
--------------------------------------------------------------------------------
 1 | import type {
 2 |   DefaultThemeLocaleData,
 3 |   DefaultThemeLocaleOptions,
 4 | } from '../../shared/index.js'
 5 | 
 6 | export const DEFAULT_LOCALE_OPTIONS: DefaultThemeLocaleOptions = {
 7 |   // color mode
 8 |   colorMode: 'auto',
 9 |   colorModeSwitch: true,
10 | 
11 |   // navbar
12 |   navbar: [],
13 |   logo: null,
14 |   repo: null,
15 |   selectLanguageText: 'Languages',
16 |   selectLanguageAriaLabel: 'Select language',
17 | 
18 |   // sidebar
19 |   sidebar: 'auto',
20 |   sidebarDepth: 2,
21 | 
22 |   // page meta
23 |   editLink: true,
24 |   editLinkText: 'Edit this page',
25 |   lastUpdated: true,
26 |   lastUpdatedText: 'Last Updated',
27 |   contributors: true,
28 |   contributorsText: 'Contributors',
29 | 
30 |   // 404 page messages
31 |   notFound: [
32 |     `There's nothing here.`,
33 |     `How did we get here?`,
34 |     `That's a Four-Oh-Four.`,
35 |     `Looks like we've got some broken links.`,
36 |   ],
37 |   backToHome: 'Take me home',
38 | 
39 |   // a11y
40 |   openInNewWindow: 'open in new window',
41 |   toggleColorMode: 'toggle color mode',
42 |   toggleSidebar: 'toggle sidebar',
43 | }
44 | 
45 | export const DEFAULT_LOCALE_DATA: DefaultThemeLocaleData = {
46 |   // navbar
47 |   selectLanguageName: 'English',
48 | }
49 | 
50 | /**
51 |  * Assign default options
52 |  */
53 | export const assignDefaultLocaleOptions = (
54 |   localeOptions: DefaultThemeLocaleOptions
55 | ): void => {
56 |   if (!localeOptions.locales) {
57 |     localeOptions.locales = {}
58 |   }
59 | 
60 |   if (!localeOptions.locales['/']) {
61 |     localeOptions.locales['/'] = {}
62 |   }
63 | 
64 |   Object.assign(localeOptions, {
65 |     ...DEFAULT_LOCALE_OPTIONS,
66 |     ...localeOptions,
67 |   })
68 | 
69 |   Object.assign(localeOptions.locales['/'], {
70 |     ...DEFAULT_LOCALE_DATA,
71 |     ...localeOptions.locales['/'],
72 |   })
73 | }
74 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/styles/custom-container.scss:
--------------------------------------------------------------------------------
 1 | .custom-container {
 2 |   transition: color var(--t-color), border-color var(--t-color),
 3 |     background-color var(--t-color);
 4 | 
 5 |   .custom-container-title {
 6 |     font-weight: 600;
 7 | 
 8 |     &:not(:only-child) {
 9 |       margin-bottom: -0.4rem;
10 |     }
11 |   }
12 | 
13 |   &.tip,
14 |   &.warning,
15 |   &.danger {
16 |     padding: 0.1rem 1.5rem;
17 |     margin: 1rem 0;
18 |     border-left-style: solid;
19 |     border-left-width: 0.5rem;
20 |   }
21 | 
22 |   &.tip {
23 |     color: var(--c-tip-text);
24 |     background-color: var(--c-tip-bg);
25 |     border-color: var(--c-tip);
26 | 
27 |     .custom-container-title {
28 |       color: var(--c-tip-title);
29 |     }
30 | 
31 |     a {
32 |       color: var(--c-tip-text-accent);
33 |     }
34 |   }
35 | 
36 |   &.warning {
37 |     color: var(--c-warning-text);
38 |     background-color: var(--c-warning-bg);
39 |     border-color: var(--c-warning);
40 | 
41 |     .custom-container-title {
42 |       color: var(--c-warning-title);
43 |     }
44 | 
45 |     a {
46 |       color: var(--c-warning-text-accent);
47 |     }
48 |   }
49 | 
50 |   &.danger {
51 |     color: var(--c-danger-text);
52 |     background-color: var(--c-danger-bg);
53 |     border-color: var(--c-danger);
54 | 
55 |     .custom-container-title {
56 |       color: var(--c-danger-title);
57 |     }
58 | 
59 |     a {
60 |       color: var(--c-danger-text-accent);
61 |     }
62 |   }
63 | 
64 |   &.details {
65 |     position: relative;
66 |     display: block;
67 |     padding: 1.6em;
68 |     margin: 1.6em 0;
69 |     background-color: var(--c-details-bg);
70 |     border-radius: 2px;
71 | 
72 |     h4 {
73 |       margin-top: 0;
74 |     }
75 | 
76 |     figure,
77 |     p {
78 |       &:last-child {
79 |         padding-bottom: 0;
80 |         margin-bottom: 0;
81 |       }
82 |     }
83 | 
84 |     summary {
85 |       cursor: pointer;
86 |       outline: none;
87 |     }
88 |   }
89 | }
90 | 


--------------------------------------------------------------------------------
/docs/.vuepress/config.ts:
--------------------------------------------------------------------------------
 1 | import * as path from 'path'
 2 | import { defineUserConfig } from 'vuepress'
 3 | import { defaultTheme } from '@bfehub/vuepress-theme-vmi'
 4 | import { pageMapPlugin } from '@bfehub/vuepress-plugin-page-map'
 5 | import { pageMissingPlugin } from '@bfehub/vuepress-plugin-page-missing'
 6 | import { codeBlockPlugin } from '@bfehub/vuepress-plugin-code-block'
 7 | import { searchPlugin } from '@vuepress/plugin-search'
 8 | import { navbar, sidebar } from './configs/index.js'
 9 | 
10 | export default defineUserConfig({
11 |   base: '/vmi/',
12 | 
13 |   locales: {
14 |     '/': {
15 |       lang: 'en-US',
16 |       title: 'Vmi',
17 |       description: 'Vuepress plug-ins and themes',
18 |     },
19 | 
20 |     '/zh/': {
21 |       lang: 'zh-CN',
22 |       title: 'Vmi',
23 |       description: '用于组件开发场景的 VuePress 的插件和主题',
24 |     },
25 |   },
26 | 
27 |   theme: defaultTheme({
28 |     logo: '/images/hero.png',
29 | 
30 |     repo: 'https://github.com/bfehub/vmi',
31 | 
32 |     locales: {
33 |       '/': {
34 |         navbar: navbar.en,
35 |         sidebar: sidebar.en,
36 |       },
37 | 
38 |       '/zh/': {
39 |         navbar: navbar.zh,
40 |         sidebar: sidebar.zh,
41 | 
42 |         selectLanguageName: '简体中文',
43 |         selectLanguageText: '选择语言',
44 |         selectLanguageAriaLabel: '选择语言',
45 |       },
46 |     },
47 |   }),
48 | 
49 |   plugins: [
50 |     // @bfehub/vuepress-plugin-page-map
51 |     pageMapPlugin({
52 |       patterns: [
53 |         `${path.resolve(process.cwd(), '../packages/components/**/*.md')}`,
54 |         `!${path.resolve(
55 |           process.cwd(),
56 |           '../packages/components/**/node_modules'
57 |         )}`,
58 |       ],
59 |     }),
60 | 
61 |     // @bfehub/vuepress-plugin-page-missing
62 |     pageMissingPlugin(),
63 | 
64 |     // @bfehub/vuepress-plugin-code-block
65 |     codeBlockPlugin({
66 |       config: {
67 |         baseDemoUrl: 'https://v2.vuepress.vuejs.org',
68 |       },
69 |     }),
70 | 
71 |     // @vuepress/plugin-search
72 |     searchPlugin(),
73 |   ],
74 | })
75 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/components/SidebarItems.vue:
--------------------------------------------------------------------------------
 1 | 
48 | 
49 | 
58 | 


--------------------------------------------------------------------------------
/packages/plugin-code-block/src/client-iframe/app.ts:
--------------------------------------------------------------------------------
 1 | import { clientConfigs } from '@internal/clientConfigs'
 2 | import { clientIframeConfigs } from '@internal/clientIframeConfigs'
 3 | import { createApp, h } from 'vue'
 4 | import { RouterView } from 'vue-router'
 5 | import type { CreateVueAppFunction } from '@vuepress/client'
 6 | import { siteData } from './composables/index.js'
 7 | import { createVueRouter } from './router.js'
 8 | 
 9 | export const createVueApp: CreateVueAppFunction = async () => {
10 |   // create vue app
11 |   const app = createApp({
12 |     name: 'VuepressApp',
13 | 
14 |     setup() {
15 |       // invoke all client setup
16 |       for (const clientConfig of clientIframeConfigs) {
17 |         clientConfig.setup?.()
18 |       }
19 | 
20 |       return () => [
21 |         h(RouterView),
22 |         ...clientConfigs
23 |           .concat(clientIframeConfigs)
24 |           .flatMap(({ rootComponents = [] }) =>
25 |             rootComponents.map((component) => h(component))
26 |           ),
27 |       ]
28 |     },
29 |   })
30 | 
31 |   // create vue-router instance
32 |   const router = createVueRouter()
33 | 
34 |   // setup devtools in dev mode
35 |   // if (__VUEPRESS_DEV__ || __VUE_PROD_DEVTOOLS__) {
36 |   //   setupDevtools(app, globalComputed)
37 |   // }
38 | 
39 |   // invoke all client enhance
40 |   for (const clientConfig of clientConfigs.concat(clientIframeConfigs)) {
41 |     await clientConfig.enhance?.({ app, router, siteData })
42 |   }
43 | 
44 |   // vue-router will start to initialize once it is installed
45 |   // via `app.use()`, but users might make some modifications
46 |   // to router in client enhance, so we install it after that.
47 |   // this can also avoid the `scrollBehavior` issue on initial
48 |   // navigation.
49 |   app.use(router)
50 | 
51 |   return {
52 |     app,
53 |     router,
54 |   }
55 | }
56 | 
57 | // mount app in client bundle
58 | if (!__VUEPRESS_SSR__) {
59 |   createVueApp().then(({ app, router }) => {
60 |     router.isReady().then(() => {
61 |       app.mount('#app')
62 |     })
63 |   })
64 | }
65 | 


--------------------------------------------------------------------------------
/packages/plugin-page-map/src/node/utils/createPage.ts:
--------------------------------------------------------------------------------
 1 | import { createPage as _createPage, inferPagePath } from '@vuepress/core'
 2 | import { path } from '@vuepress/utils'
 3 | import type { App, Page } from '@vuepress/core'
 4 | import { PageMapPluginOptions } from '../index.js'
 5 | 
 6 | export const createPage = async (
 7 |   app: App,
 8 |   options: PageMapPluginOptions,
 9 |   filePath: string
10 | ): Promise => {
11 |   // components/npm-badge/index.md
12 |   // components/npm-badge/index.zh-CN.md
13 |   const pathMap = options.pathMapRule(filePath)
14 | 
15 |   // index.md
16 |   // index.zh-CN.md
17 |   const name = path.basename(pathMap)
18 |   const ext = path.extname(name)
19 |   const lang = path.extname(name.slice(0, -ext.length)).slice(1)
20 | 
21 |   // {
22 |   //   '/': {
23 |   //     lang: 'en-US',
24 |   //   },
25 |   //   '/zh/': {
26 |   //     lang: 'zh-CN',
27 |   //   },
28 |   // }
29 |   let langPrefix = '/'
30 |   Object.entries(app.siteData.locales).forEach(([key, value]) => {
31 |     if (value.lang === lang) {
32 |       langPrefix = key
33 |     }
34 |   })
35 | 
36 |   // components/npm-badge/index.md -> /components/npm-badge/index.md
37 |   // components/npm-badge/index.zh-CN.md -> /zh/components/npm-badge/index.md
38 |   const relativePath = `${langPrefix}${
39 |     lang ? pathMap.replace(`.${lang}`, '') : pathMap
40 |   }`.slice(1)
41 | 
42 |   // infer page path according to file path
43 |   const { pathInferred, pathLocale } = inferPagePath({
44 |     app,
45 |     filePathRelative: relativePath,
46 |   })
47 | 
48 |   // create page use @vuepress/core
49 |   const page = await _createPage(app, {
50 |     path: pathInferred || relativePath,
51 |     filePath,
52 |   })
53 | 
54 |   // override attribute
55 |   page.lang = app.siteData.locales?.[langPrefix]?.lang || app.siteData.lang
56 |   page.pathLocale = pathLocale
57 |   page.pathInferred = pathInferred
58 |   page.filePathRelative = relativePath
59 | 
60 |   page.data.lang = page.lang
61 |   ;(page.data as any).filePathRelative = page.filePathRelative
62 |   ;(page as any)._PageMapPluginOptions = options
63 | 
64 |   return page
65 | }
66 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/components/ToggleColorModeButton.vue:
--------------------------------------------------------------------------------
 1 | 
11 | 
12 | 
58 | 


--------------------------------------------------------------------------------
/packages/plugin-code-block/src/node/codeBlockPlugin.ts:
--------------------------------------------------------------------------------
 1 | import { path, getDirname } from '@vuepress/utils'
 2 | import type { Plugin } from '@vuepress/core'
 3 | import type { CodeUserConfig, CodeLocaleConfig } from '../shared/index.js'
 4 | import { createPageCodeDepsHelper } from './utils/index.js'
 5 | import { prepareClientIframe, prepareVmiComponents } from './prepare/index.js'
 6 | import { vitePageHMR, vitePageProxy, vitePageIframe } from './plugins/index.js'
 7 | import {
 8 |   resolveOptions,
 9 |   resolveHtmlBlock,
10 |   resolveScriptSetup,
11 |   resolvePageHeaders,
12 | } from './resolve/index.js'
13 | 
14 | const __dirname = getDirname(import.meta.url)
15 | 
16 | /**
17 |  * Options of @bfehub/vuepress-plugin-code-block
18 |  */
19 | export interface CodeBlockPluginOptions {
20 |   name: string
21 |   headers: boolean
22 |   config: CodeUserConfig
23 |   locales: CodeLocaleConfig
24 | }
25 | 
26 | export const codeBlockPlugin = (
27 |   config: Partial = {}
28 | ): Plugin => {
29 |   const store = createPageCodeDepsHelper()
30 | 
31 |   const options = resolveOptions(config)
32 | 
33 |   return {
34 |     name: '@bfehub/vuepress-plugin-code-block',
35 | 
36 |     clientConfigFile: path.resolve(__dirname, '../client/clientConfig.js'),
37 | 
38 |     extendsMarkdown(md, app) {
39 |       resolveHtmlBlock(md, app, store, options)
40 |     },
41 | 
42 |     async extendsPage(page, app) {
43 |       resolveScriptSetup(page, store)
44 |       resolvePageHeaders(page, store, options.headers)
45 |       app.pages && (await prepareVmiComponents(app, store))
46 |     },
47 | 
48 |     async onInitialized(app) {
49 |       await prepareClientIframe(app)
50 |       await prepareVmiComponents(app, store)
51 |     },
52 | 
53 |     extendsBundlerOptions(bundlerOptions, app) {
54 |       if (app.options.bundler.name === '@vuepress/bundler-vite') {
55 |         bundlerOptions.viteOptions ??= {}
56 |         bundlerOptions.viteOptions.plugins ??= []
57 |         bundlerOptions.viteOptions.plugins.push(
58 |           vitePageHMR(app),
59 |           vitePageProxy(app),
60 |           vitePageIframe(app)
61 |         )
62 |       }
63 |     },
64 |   }
65 | }
66 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/composables/useDarkMode.ts:
--------------------------------------------------------------------------------
 1 | import { usePreferredDark, useStorage } from '@vueuse/core'
 2 | import { computed, inject, onMounted, onUnmounted, provide, watch } from 'vue'
 3 | import type { InjectionKey, WritableComputedRef } from 'vue'
 4 | import { useThemeLocaleData } from './useThemeData.js'
 5 | 
 6 | export type DarkModeRef = WritableComputedRef
 7 | 
 8 | export const darkModeSymbol: InjectionKey = Symbol(
 9 |   __VUEPRESS_DEV__ ? 'darkMode' : ''
10 | )
11 | 
12 | /**
13 |  * Inject dark mode global computed
14 |  */
15 | export const useDarkMode = (): DarkModeRef => {
16 |   const isDarkMode = inject(darkModeSymbol)
17 |   if (!isDarkMode) {
18 |     throw new Error('useDarkMode() is called without provider.')
19 |   }
20 |   return isDarkMode
21 | }
22 | 
23 | /**
24 |  * Create dark mode ref and provide as global computed in setup
25 |  */
26 | export const setupDarkMode = (): void => {
27 |   const themeLocale = useThemeLocaleData()
28 |   const isDarkPreferred = usePreferredDark()
29 |   const darkStorage = useStorage(
30 |     'vuepress-color-scheme',
31 |     themeLocale.value.colorMode
32 |   )
33 | 
34 |   const isDarkMode = computed({
35 |     get() {
36 |       // disable color mode switching
37 |       if (!themeLocale.value.colorModeSwitch) {
38 |         return themeLocale.value.colorMode === 'dark'
39 |       }
40 |       // auto detected from prefers-color-scheme
41 |       if (darkStorage.value === 'auto') {
42 |         return isDarkPreferred.value
43 |       }
44 |       // storage value
45 |       return darkStorage.value === 'dark'
46 |     },
47 |     set(val) {
48 |       if (val === isDarkPreferred.value) {
49 |         darkStorage.value = 'auto'
50 |       } else {
51 |         darkStorage.value = val ? 'dark' : 'light'
52 |       }
53 |     },
54 |   })
55 |   provide(darkModeSymbol, isDarkMode)
56 | 
57 |   updateHtmlDarkClass(isDarkMode)
58 | }
59 | 
60 | export const updateHtmlDarkClass = (isDarkMode: DarkModeRef): void => {
61 |   const update = (value = isDarkMode.value): void => {
62 |     // set `class="dark"` on `` element
63 |     const htmlEl = window?.document.querySelector('html')
64 |     htmlEl?.classList.toggle('dark', value)
65 |   }
66 | 
67 |   onMounted(() => {
68 |     watch(isDarkMode, update, { immediate: true })
69 |   })
70 | 
71 |   onUnmounted(() => update())
72 | }
73 | 


--------------------------------------------------------------------------------
/packages/plugin-code-block/src/node/parse/parseCodeBlock.ts:
--------------------------------------------------------------------------------
 1 | import { path, hash } from '@vuepress/utils'
 2 | import { Node, parser } from 'posthtml-parser'
 3 | import { render } from 'posthtml-render'
 4 | import type { App } from '@vuepress/core'
 5 | import type { CodeBlockPluginOptions } from '../index.js'
 6 | import type { PageCodeDep, PageCodeDepsHelper } from '../utils/index.js'
 7 | import type { CodeNodeConfig } from '../../shared/index.js'
 8 | import { parseNodeAttrs } from './parseNodeAttrs.js'
 9 | import { parseVue } from './parseVue.js'
10 | import { parseInline } from './parseInline.js'
11 | import { parseRaw } from './parseRaw.js'
12 | 
13 | export const parseCodeBlock = (
14 |   app: App,
15 |   store: PageCodeDepsHelper,
16 |   options: CodeBlockPluginOptions,
17 |   content: string,
18 |   pagePath: string
19 | ): string => {
20 |   const html: Node[] = parser(content)
21 | 
22 |   let i = -1
23 |   for (const node of html) {
24 |     i++
25 | 
26 |     if (typeof node !== 'object') {
27 |       continue
28 |     }
29 | 
30 |     if (node.tag !== options.name) {
31 |       continue
32 |     }
33 | 
34 |     if (typeof node.attrs?.src !== 'string') {
35 |       continue
36 |     }
37 | 
38 |     const props = (node.attrs = parseNodeAttrs(
39 |       options.config,
40 |       node.attrs
41 |     )) as unknown as CodeNodeConfig
42 | 
43 |     const isRaw = Reflect.has(props, 'raw')
44 |     const isInline = Reflect.has(props, 'inline')
45 |     const isDebug = Reflect.has(props, 'debug')
46 |     const isExternalIframe = Reflect.has(props, 'demoUrl')
47 |     const isBuild = app.env.isBuild
48 |     const isVueCode = /^\.(vue|jsx|tsx)$/.test(path.extname(props.src))
49 | 
50 |     const dep = {} as PageCodeDep
51 |     const dirName = path.dirname(pagePath)
52 | 
53 |     dep.pagePath = pagePath
54 |     dep.compPath = path.resolve(dirName, props.src)
55 |     dep.compHash = hash(dep.compPath)
56 |     dep.compName = `VmiV${dep.compHash}`
57 |     dep.compAttrs = props
58 | 
59 |     props.id = dep.compHash
60 |     props.src = dep.compPath
61 | 
62 |     if (isDebug && isBuild) {
63 |       html[i] = ''
64 |       continue
65 |     }
66 | 
67 |     if (isInline && isVueCode) {
68 |       html[i] = parseInline(node, dep)
69 | 
70 |       store.add(dep)
71 | 
72 |       continue
73 |     }
74 | 
75 |     if (!isRaw && isVueCode) {
76 |       html[i] = parseVue(app, node, dep)
77 | 
78 |       dep.isGenerateIframe = true
79 |       !isExternalIframe && store.add(dep)
80 | 
81 |       continue
82 |     }
83 | 
84 |     html[i] = parseRaw(node, dep)
85 |   }
86 | 
87 |   return render(html)
88 | }
89 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/components/SidebarItem.vue:
--------------------------------------------------------------------------------
 1 | 
57 | 
58 | 
88 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/components/Navbar.vue:
--------------------------------------------------------------------------------
 1 | 
58 | 
59 | 
76 | 


--------------------------------------------------------------------------------
/packages/plugin-code-block/src/shared/types.ts:
--------------------------------------------------------------------------------
  1 | import { LocaleConfig } from '@vuepress/shared'
  2 | 
  3 | export type CodeLocaleConfig = LocaleConfig
  4 | 
  5 | export interface CodeLocaleData {
  6 |   /**
  7 |    * hide text
  8 |    * @default ''
  9 |    */
 10 |   hideText: string
 11 |   /**
 12 |    * show text
 13 |    * @default ''
 14 |    */
 15 |   showText: string
 16 |   /**
 17 |    * copy text
 18 |    * @default ''
 19 |    */
 20 |   copyText: string
 21 |   /**
 22 |    * copy success text
 23 |    * @default ''
 24 |    */
 25 |   copySuccessText: string
 26 | }
 27 | 
 28 | export interface CodeSource {
 29 |   /**
 30 |    * 源代码的文件名称
 31 |    */
 32 |   name: string
 33 | 
 34 |   /**
 35 |    * 源代码字符串 - 用于复制
 36 |    */
 37 |   rawCode: string
 38 | 
 39 |   /**
 40 |    * 高亮后代码字符串 - 用于展示
 41 |    */
 42 |   highlightCode: string
 43 | }
 44 | 
 45 | export interface CodeUserConfig {
 46 |   /**
 47 |    * 用于控制 Demo 预览器部分功能按钮的隐藏
 48 |    * @default []
 49 |    */
 50 |   hideActions?: Array<'EXTERNAL'>
 51 | 
 52 |   /**
 53 |    * 用于控制当前 demo 的包裹容器是否默认展开源代码显示。
 54 |    * @default false
 55 |    */
 56 |   defaultShowCode?: boolean
 57 | 
 58 |   /**
 59 |    * 用户控制 Demo 的排列方向
 60 |    * @default 'vertical'
 61 |    */
 62 |   direction?: 'vertical' | 'horizontal'
 63 |   /**
 64 |    * 用于设置 demoUrl 的基础 url
 65 |    * @default ''
 66 |    */
 67 |   baseDemoUrl?: string
 68 | }
 69 | 
 70 | export interface CodeNodeConfig extends CodeUserConfig {
 71 |   /**
 72 |    * demo 的唯一标识通过路径 hash 得到。
 73 |    */
 74 |   id: string
 75 | 
 76 |   /**
 77 |    * 当前 demo 组件的路径地址。
 78 |    * @default ''
 79 |    */
 80 |   src: string
 81 | 
 82 |   /**
 83 |    * 用于指示该 demo 为源代码,将会把内容当做代码块渲染不会渲染效果
 84 |    * @default false
 85 |    */
 86 |   raw?: boolean
 87 | 
 88 |   /**
 89 |    * 用于指示该 demo 为自由 demo,将会直接在文档中嵌入渲染,不会被 demo 容器包裹,用户也无法查看源代码。
 90 |    * @default false
 91 |    */
 92 |   inline?: boolean
 93 | 
 94 |   /**
 95 |    * 使用 iframe 模式渲染当前 demo,对于渲染 layout 型的 demo 非常有用,当我们传递数值时可以控制 iframe 的高度。
 96 |    * @default false
 97 |    */
 98 |   iframe?: boolean | number
 99 | 
100 |   /**
101 |    * 标记当前 demo 为调试 demo,这意味着在生产模式下该 demo 是不可见的;另外,调试 demo 在开发环境下也会展示一个 DEV ONLY 的标记,以便开发者将其和其他 demo 区分开来。
102 |    * @default false
103 |    */
104 |   debug?: boolean
105 | 
106 |   /**
107 |    * 用于配置 demo 的标题,配置后会在 demo 预览器中显示。
108 |    * @default ''
109 |    */
110 |   title?: string
111 | 
112 |   /**
113 |    * 用于配置 demo 的简介,配置后会在 demo 预览器中显示,支持 Markdown 语法。
114 |    * @default ''
115 |    */
116 |   desc?: string
117 | 
118 |   /**
119 |    * 用于指定该 demo 的访问链接,通常在默认渲染的 demo 无法满足展示需要时使用。
120 |    * @default false
121 |    */
122 |   demoUrl?: string
123 | 
124 |   /**
125 |    * 用于控制 demo 的包裹容器是否设置 transform 的 CSS 值以控制 position: fixed; 的元素相对于 demo 容器定位。
126 |    * @default false
127 |    */
128 |   transform?: boolean
129 | }
130 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/components/HomeHero.vue:
--------------------------------------------------------------------------------
 1 | 
75 | 
76 | 
99 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/components/PageNav.vue:
--------------------------------------------------------------------------------
  1 | 
 90 | 
 91 | 
104 | 


--------------------------------------------------------------------------------
/packages/plugin-page-map/src/node/dev/watchPageFiles.ts:
--------------------------------------------------------------------------------
 1 | import type { App, Page } from '@vuepress/core'
 2 | import { colors, logger } from '@vuepress/utils'
 3 | import * as chokidar from 'chokidar'
 4 | import type { FSWatcher } from 'chokidar'
 5 | import { handlePageAdd } from './handlePageAdd.js'
 6 | import { handlePageChange } from './handlePageChange.js'
 7 | import { handlePageUnlink } from './handlePageUnlink.js'
 8 | import { createPageDepsHelper } from './pageDepsHelper.js'
 9 | import type { PageMapPluginOptions } from '../index.js'
10 | 
11 | /**
12 |  * Watch page files and deps, return file watchers
13 |  */
14 | export const watchPageFiles = (
15 |   app: App,
16 |   options: PageMapPluginOptions
17 | ): FSWatcher[] => {
18 |   // watch page deps
19 |   const depsWatcher = chokidar.watch([], {
20 |     disableGlobbing: true,
21 |     ignoreInitial: true,
22 |   })
23 |   const depsHelper = createPageDepsHelper()
24 |   const addDeps = (page: Page): void => {
25 |     const depsToAdd = depsHelper.add(page)
26 |     depsWatcher.add(depsToAdd)
27 |   }
28 |   const removeDeps = (page: Page): void => {
29 |     const depsToRemove = depsHelper.remove(page)
30 |     depsWatcher.unwatch(depsToRemove)
31 |   }
32 |   const depsListener = async (dep: string): Promise => {
33 |     const pagePaths = depsHelper.get(dep)
34 |     if (!pagePaths) return
35 |     for (const filePathRelative of pagePaths) {
36 |       logger.info(
37 |         `dependency of page ${colors.magenta(filePathRelative)} is modified`
38 |       )
39 |       await handlePageChange(app, options, app.dir.source(filePathRelative))
40 |     }
41 |   }
42 |   depsWatcher.on('add', depsListener)
43 |   depsWatcher.on('change', depsListener)
44 |   depsWatcher.on('unlink', depsListener)
45 |   app.pages.forEach((page) => addDeps(page))
46 | 
47 |   // watch page files
48 |   const pagesWatcher = chokidar.watch(options.patterns, {
49 |     // cwd: app.dir.source(),
50 |     ignoreInitial: true,
51 |   })
52 |   pagesWatcher.on('add', async (filePathRelative) => {
53 |     logger.info(`page ${colors.magenta(filePathRelative)} is created`)
54 |     const page = await handlePageAdd(
55 |       app,
56 |       options,
57 |       app.dir.source(filePathRelative)
58 |     )
59 |     if (page === null) return
60 |     addDeps(page)
61 |   })
62 |   pagesWatcher.on('change', async (filePathRelative) => {
63 |     logger.info(`page ${colors.magenta(filePathRelative)} is modified`)
64 |     const result = await handlePageChange(
65 |       app,
66 |       options,
67 |       app.dir.source(filePathRelative)
68 |     )
69 |     if (result === null) return
70 |     const [pageOld, pageNew] = result
71 |     removeDeps(pageOld)
72 |     addDeps(pageNew)
73 |   })
74 |   pagesWatcher.on('unlink', async (filePathRelative) => {
75 |     logger.info(`page ${colors.magenta(filePathRelative)} is removed`)
76 |     const page = await handlePageUnlink(
77 |       app,
78 |       options,
79 |       app.dir.source(filePathRelative)
80 |     )
81 |     if (page === null) return
82 |     removeDeps(page)
83 |   })
84 | 
85 |   return [pagesWatcher, depsWatcher]
86 | }
87 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/styles/sidebar.scss:
--------------------------------------------------------------------------------
  1 | @import '_mixins';
  2 | @import '_variables';
  3 | 
  4 | .sidebar {
  5 |   ul {
  6 |     padding: 0;
  7 |     margin: 0;
  8 |     list-style-type: none;
  9 |   }
 10 | 
 11 |   a {
 12 |     display: inline-block;
 13 |   }
 14 | 
 15 |   .navbar-items {
 16 |     display: none;
 17 |     padding: 0.5rem 0 0.75rem;
 18 |     border-bottom: 1px solid var(--c-border);
 19 |     transition: border-color var(--t-color);
 20 | 
 21 |     a {
 22 |       font-weight: 600;
 23 |     }
 24 | 
 25 |     .navbar-item {
 26 |       display: block;
 27 |       padding: 0.5rem 0 0.5rem 1.5rem;
 28 |       font-size: 1.1em;
 29 |       line-height: 1.25rem;
 30 |     }
 31 |   }
 32 | 
 33 |   .sidebar-items {
 34 |     padding: 0.5rem 0;
 35 |   }
 36 | }
 37 | 
 38 | @media (max-width: $MQMobile) {
 39 |   .sidebar {
 40 |     .navbar-items {
 41 |       display: block;
 42 | 
 43 |       .navbar-dropdown-wrapper
 44 |         .navbar-dropdown
 45 |         .navbar-dropdown-item
 46 |         a.router-link-active::after {
 47 |         top: calc(1rem - 2px);
 48 |       }
 49 |     }
 50 | 
 51 |     .sidebar-items {
 52 |       padding: 1rem 0;
 53 |     }
 54 |   }
 55 | }
 56 | 
 57 | .sidebar-item {
 58 |   color: var(--c-text);
 59 |   cursor: default;
 60 |   border-left: 0.25rem solid transparent;
 61 | 
 62 |   &:focus-visible {
 63 |     outline-width: 1px;
 64 |     outline-offset: -1px;
 65 |   }
 66 | 
 67 |   &.active:not(p.sidebar-heading) {
 68 |     font-weight: 600;
 69 |     color: var(--c-text-accent);
 70 |     border-left-color: var(--c-text-accent);
 71 |   }
 72 | 
 73 |   &.sidebar-heading {
 74 |     box-sizing: border-box;
 75 |     width: 100%;
 76 |     padding: 0.35rem 1.5rem 0.35rem 1.25rem;
 77 |     margin: 0;
 78 |     font-size: 1.1em;
 79 |     font-weight: bold;
 80 |     transition: color 0.15s ease;
 81 | 
 82 |     &.collapsible {
 83 |       cursor: pointer;
 84 | 
 85 |       & + .sidebar-item-children {
 86 |         @include dropdown_wrapper;
 87 | 
 88 |         margin-bottom: 0.75rem;
 89 |       }
 90 |     }
 91 | 
 92 |     .arrow {
 93 |       position: relative;
 94 |       top: -0.12em;
 95 |       left: 0.5em;
 96 |     }
 97 |   }
 98 | 
 99 |   &:not(.sidebar-heading) {
100 |     box-sizing: border-box;
101 |     display: inline-block;
102 |     width: 100%;
103 |     padding: 0.35rem 1rem 0.35rem 2rem;
104 |     margin: 0;
105 |     font-size: 1em;
106 |     font-weight: 400;
107 |     line-height: 1.4;
108 | 
109 |     & + .sidebar-item-children {
110 |       padding-left: 1rem;
111 |       font-size: 0.95em;
112 |     }
113 | 
114 |     .sidebar-item-children .sidebar-item-children & {
115 |       padding: 0.25rem 1rem 0.25rem 1.75rem;
116 | 
117 |       &.active {
118 |         font-weight: 500;
119 |         border-left-color: transparent;
120 |       }
121 |     }
122 | 
123 |     a.sidebar-heading + .sidebar-item-children &.active {
124 |       border-left-color: transparent;
125 |     }
126 |   }
127 | }
128 | 
129 | a.sidebar-item {
130 |   cursor: pointer;
131 | 
132 |   &:hover {
133 |     color: var(--c-text-accent);
134 |   }
135 | }
136 | 


--------------------------------------------------------------------------------
/docs/guide/README.md:
--------------------------------------------------------------------------------
 1 | # Introduction
 2 | 
 3 | VuePress is a markdown-centered static site generator. You can write your content (documentations, blogs, etc.) in [Markdown](https://en.wikipedia.org/wiki/Markdown), then VuePress will help you to generate a static site to host them.
 4 | 
 5 | The purpose of creating VuePress was to support the documentation of Vue.js and its sub-projects, but now it has been helping a large amount of users to build their documentation, blogs, and other static sites.
 6 | 
 7 | ## How It Works
 8 | 
 9 | A VuePress site is in fact a single-page application (SPA) powered by [Vue](https://vuejs.org/) and [Vue Router](https://router.vuejs.org).
10 | 
11 | Routes are generated according to the relative path of your markdown files. Each Markdown file is compiled into HTML with [markdown-it](https://github.com/markdown-it/markdown-it) and then processed as the template of a Vue component. This allows you to directly use Vue inside your Markdown files and is great when you need to embed dynamic content.
12 | 
13 | During development, we start a normal dev-server, and serve the VuePress site as a normal SPA. If you’ve used Vue before, you will notice the familiar development experience when you are writing and developing with VuePress.
14 | 
15 | During build, we create a server-rendered version of the VuePress site and render the corresponding HTML by virtually visiting each route. This approach is inspired by [Nuxt](https://nuxtjs.org/)'s `nuxt generate` command and other projects like [Gatsby](https://www.gatsbyjs.org/).
16 | 
17 | ## Why Not ...?
18 | 
19 | ### Nuxt
20 | 
21 | Nuxt is an outstanding Vue SSR framework, and it is capable of doing what VuePress does. But Nuxt is designed for building applications, while VuePress is more lightweight and focused on content-centric static sites.
22 | 
23 | ### VitePress
24 | 
25 | VitePress is the little brother of VuePress. It's also created and maintained by our Vue.js team. It's even more lightweight and faster than VuePress. However, as a tradeoff, it's more opinionated and less configurable. For example, it does not support plugins. But VitePress is powerful enough to make your content online if you don't need advanced customizations.
26 | 
27 | It might not be an appropriate comparison, but you can take VuePress and VitePress as Laravel and Lumen.
28 | 
29 | ### Docsify / Docute
30 | 
31 | Both are great projects and also Vue-powered. Except they are both fully runtime-driven and therefore not SEO-friendly. If you don’t care for SEO and don’t want to mess with installing dependencies, these are still great choices.
32 | 
33 | ### Hexo
34 | 
35 | Hexo has been serving the Vue 2.x docs well. The biggest problem is that its theming system is static and string-based - we want to take advantage of Vue for both the layout and the interactivity. Also, Hexo’s Markdown rendering isn’t the most flexible to configure.
36 | 
37 | ### GitBook
38 | 
39 | We’ve been using GitBook for most of our sub project docs. The primary problem with GitBook is that its development reload performance is intolerable with a large amount of files. The default theme also has a pretty limiting navigation structure, and the theming system is, again, not Vue based. The team behind GitBook is also more focused on turning it into a commercial product rather than an open-source tool.
40 | 


--------------------------------------------------------------------------------
/packages/plugin-code-block/src/node/parse/parseVue.ts:
--------------------------------------------------------------------------------
  1 | import type { App } from '@vuepress/core'
  2 | import type { Node } from 'posthtml-parser'
  3 | import type { PageCodeDep } from '../utils/index.js'
  4 | import { slugify } from '@mdit-vue/shared'
  5 | import { readSource } from './readSource.js'
  6 | import { markdownText } from '../utils/highlight.js'
  7 | 
  8 | export const parseVue = (app: App, node: Node, dep: PageCodeDep): Node => {
  9 |   if (typeof node !== 'object') {
 10 |     return node
 11 |   }
 12 | 
 13 |   const sources = readSource(dep.compPath, true, true)
 14 | 
 15 |   const iframeSrc = dep.compAttrs.demoUrl
 16 |     ? dep.compAttrs.demoUrl
 17 |     : `${app.options.base}-iframe.html#/${dep.compAttrs.id}`
 18 | 
 19 |   const isShowDebugStyle =
 20 |     Reflect.has(dep.compAttrs, 'debug') ||
 21 |     Reflect.has(dep.compAttrs, 'data-debug')
 22 | 
 23 |   node.tag = 'VmiPreviewer'
 24 |   node.attrs = {
 25 |     direction: dep.compAttrs.direction,
 26 |   }
 27 |   node.content = [
 28 |     {
 29 |       tag: 'VmiExample',
 30 |       attrs: {
 31 |         id: dep.compAttrs.id,
 32 |         iframe: dep.compAttrs.iframe,
 33 |         iframeSrc,
 34 |         transform: dep.compAttrs.transform,
 35 |       },
 36 |       content: [
 37 |         {
 38 |           tag: dep.compName,
 39 |         },
 40 |       ],
 41 |     },
 42 |     {
 43 |       tag: 'div',
 44 |       attrs: {
 45 |         class: 'vmi-previewer-content',
 46 |       },
 47 |       content: [
 48 |         dep.compAttrs.title
 49 |           ? {
 50 |               tag: 'div',
 51 |               attrs: {
 52 |                 id: slugify(dep.compAttrs.title),
 53 |                 class: 'vmi-previewer-title',
 54 |               },
 55 |               content: [
 56 |                 {
 57 |                   tag: 'a',
 58 |                   attrs: {
 59 |                     href: `#${slugify(dep.compAttrs.title)}`,
 60 |                     class: 'header-anchor',
 61 |                   },
 62 |                   content: dep.compAttrs.title,
 63 |                 },
 64 |               ],
 65 |             }
 66 |           : null,
 67 |         dep.compAttrs.desc
 68 |           ? {
 69 |               tag: 'div',
 70 |               attrs: {
 71 |                 class: 'vmi-previewer-desc',
 72 |               },
 73 |               content: markdownText(dep.compAttrs.desc),
 74 |             }
 75 |           : null,
 76 |         {
 77 |           tag: 'VmiSourceCode',
 78 |           attrs: {
 79 |             id: dep.compAttrs.id,
 80 |             iframe: dep.compAttrs.iframe,
 81 |             iframeSrc,
 82 |             defaultShowCode: dep.compAttrs.defaultShowCode,
 83 |             hideActions: dep.compAttrs.hideActions as unknown as string,
 84 |             filePath: app.env.isDev ? dep.compPath : '',
 85 |           },
 86 |           content: sources.map((source) => {
 87 |             return {
 88 |               tag: 'VmiSourceCodeItem',
 89 |               attrs: {
 90 |                 name: source.name,
 91 |                 rawCode: encodeURIComponent(source.rawCode),
 92 |                 highlightCode: encodeURIComponent(source.highlightCode),
 93 |               },
 94 |             }
 95 |           }),
 96 |         },
 97 |       ],
 98 |     },
 99 |   ]
100 | 
101 |   if (isShowDebugStyle) {
102 |     node.attrs['data-debug'] = true
103 |   }
104 | 
105 |   return node
106 | }
107 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/styles/normalize.scss:
--------------------------------------------------------------------------------
  1 | html,
  2 | body {
  3 |   padding: 0;
  4 |   margin: 0;
  5 |   background-color: var(--c-bg);
  6 |   transition: background-color var(--t-color);
  7 | }
  8 | 
  9 | html.dark {
 10 |   color-scheme: dark;
 11 | }
 12 | 
 13 | html {
 14 |   font-size: 16px;
 15 | }
 16 | 
 17 | body {
 18 |   font-family: var(--font-family);
 19 |   -webkit-font-smoothing: antialiased;
 20 |   -moz-osx-font-smoothing: grayscale;
 21 |   font-size: 1rem;
 22 |   color: var(--c-text);
 23 | }
 24 | 
 25 | a {
 26 |   font-weight: 500;
 27 |   color: var(--c-text-accent);
 28 |   text-decoration: none;
 29 |   overflow-wrap: break-word;
 30 | }
 31 | 
 32 | p a code {
 33 |   font-weight: 400;
 34 |   color: var(--c-text-accent);
 35 | }
 36 | 
 37 | kbd {
 38 |   padding: 0 0.15em;
 39 |   font-family: var(--font-family-code);
 40 |   color: var(--c-text);
 41 |   background: var(--c-bg-lighter);
 42 |   border: solid 0.15rem var(--c-border-dark);
 43 |   border-bottom: solid 0.25rem var(--c-border-dark);
 44 |   border-radius: 0.15rem;
 45 | }
 46 | 
 47 | code {
 48 |   padding: 0.25rem 0.5rem;
 49 |   margin: 0;
 50 |   font-family: var(--font-family-code);
 51 |   font-size: 0.85em;
 52 |   color: var(--c-text-lighter);
 53 |   overflow-wrap: break-word;
 54 |   background-color: var(--c-bg-lighter);
 55 |   border-radius: 3px;
 56 |   transition: background-color var(--t-color);
 57 | }
 58 | 
 59 | blockquote {
 60 |   padding: 0.25rem 0 0.25rem 1rem;
 61 |   margin: 1rem 0;
 62 |   font-size: 1rem;
 63 |   color: var(--c-text-quote);
 64 |   border-left: 0.2rem solid var(--c-border-dark);
 65 | 
 66 |   & > p {
 67 |     margin: 0;
 68 |   }
 69 | }
 70 | 
 71 | ul,
 72 | ol {
 73 |   padding-left: 1.2em;
 74 | }
 75 | 
 76 | strong {
 77 |   font-weight: 600;
 78 | }
 79 | 
 80 | h1,
 81 | h2,
 82 | h3,
 83 | h4,
 84 | h5,
 85 | h6 {
 86 |   font-weight: 600;
 87 |   line-height: 1.25;
 88 | 
 89 |   &:focus-visible {
 90 |     outline: none;
 91 |   }
 92 | 
 93 |   &:hover .header-anchor {
 94 |     opacity: 1;
 95 |   }
 96 | }
 97 | 
 98 | h1 {
 99 |   font-size: 2.2rem;
100 | }
101 | 
102 | h2 {
103 |   padding-bottom: 0.3rem;
104 |   font-size: 1.65rem;
105 |   border-bottom: 1px solid var(--c-border);
106 |   transition: border-color var(--t-color);
107 | }
108 | 
109 | h3 {
110 |   font-size: 1.35rem;
111 | }
112 | 
113 | h4 {
114 |   font-size: 1.15rem;
115 | }
116 | 
117 | h5 {
118 |   font-size: 1.05rem;
119 | }
120 | 
121 | h6 {
122 |   font-size: 1rem;
123 | }
124 | 
125 | a.header-anchor {
126 |   float: left;
127 |   padding-right: 0.23em;
128 |   margin-top: 0.125em;
129 |   margin-left: -0.87em;
130 |   font-size: 0.85em;
131 |   opacity: 0;
132 | 
133 |   &:hover {
134 |     text-decoration: none;
135 |   }
136 | 
137 |   &:focus-visible {
138 |     opacity: 1;
139 |   }
140 | }
141 | 
142 | p,
143 | ul,
144 | ol {
145 |   line-height: 1.7;
146 | }
147 | 
148 | hr {
149 |   border: 0;
150 |   border-top: 1px solid var(--c-border);
151 | }
152 | 
153 | table {
154 |   display: block;
155 |   margin: 1rem 0;
156 |   overflow-x: auto;
157 |   border-collapse: collapse;
158 |   transition: border-color var(--t-color);
159 | }
160 | 
161 | tr {
162 |   border-top: 1px solid var(--c-border-dark);
163 |   transition: border-color var(--t-color);
164 | 
165 |   &:nth-child(2n) {
166 |     background-color: var(--c-bg-light);
167 |     transition: background-color var(--t-color);
168 |   }
169 | }
170 | 
171 | th,
172 | td {
173 |   padding: 0.6em 1em;
174 |   border: 1px solid var(--c-border-dark);
175 |   transition: border-color var(--t-color);
176 | }
177 | 


--------------------------------------------------------------------------------
/packages/plugin-code-block/src/client/styles/index.scss:
--------------------------------------------------------------------------------
  1 | .vmi-previewer {
  2 |   background-color: var(--c-bg);
  3 |   border: 1px solid var(--c-border);
  4 |   border-radius: 1px;
  5 | 
  6 |   &[data-debug] {
  7 |     margin-top: 32px;
  8 |     border-color: #ffcb00;
  9 | 
 10 |     &::before {
 11 |       float: left;
 12 |       padding: 3px 6px;
 13 |       margin-top: -18px;
 14 |       margin-left: -1px;
 15 |       font-size: 12px;
 16 |       line-height: 1;
 17 |       color: #735600;
 18 |       text-shadow: 0.5px 0.5px 0 hsl(0deg 0% 100% / 50%);
 19 |       content: 'DEV ONLY';
 20 |       background-color: #ffcb00;
 21 |       border-top-left-radius: 1px;
 22 |       border-top-right-radius: 1px;
 23 |     }
 24 |   }
 25 | }
 26 | 
 27 | .vmi-previewer-example {
 28 |   padding: 40px 24px;
 29 | }
 30 | 
 31 | .vmi-previewer-title {
 32 |   position: relative;
 33 | 
 34 |   a.header-anchor {
 35 |     position: absolute;
 36 |     top: -11px;
 37 |     left: 16px;
 38 |     padding: 0 8px;
 39 |     margin: 0;
 40 |     font-size: 1rem;
 41 |     font-weight: 500;
 42 |     color: var(--c-text);
 43 |     background: var(--c-bg);
 44 |     opacity: 1;
 45 |   }
 46 | 
 47 |   + .vmi-previewer-desc {
 48 |     padding-top: 6px;
 49 |   }
 50 | }
 51 | 
 52 | .vmi-previewer-desc {
 53 |   padding: 0 24px;
 54 |   border-top: 1px dashed var(--c-border);
 55 | }
 56 | 
 57 | .vmi-previewer-actions {
 58 |   display: flex;
 59 |   align-items: center;
 60 |   justify-content: space-between;
 61 |   height: 40px;
 62 |   padding: 0 5px;
 63 |   border-top: 1px dashed var(--c-border);
 64 | 
 65 |   &-button {
 66 |     display: flex;
 67 |     align-items: center;
 68 |     justify-content: center;
 69 |     float: left;
 70 |     width: 32px;
 71 |     height: 32px;
 72 |     font-size: 20px;
 73 |     cursor: pointer;
 74 |     opacity: 0.6;
 75 |     transition: opacity 0.5s ease;
 76 | 
 77 |     &:hover {
 78 |       opacity: 1;
 79 |     }
 80 |   }
 81 | }
 82 | 
 83 | .vmi-previewer-sources {
 84 |   border-top: 1px dashed var(--c-border);
 85 | 
 86 |   pre[class*='language-'] {
 87 |     margin-top: 0;
 88 |     margin-bottom: 0;
 89 |   }
 90 | 
 91 |   div[class*='language-'] {
 92 |     border-radius: 0;
 93 |   }
 94 | 
 95 |   &-item {
 96 |     overflow: auto;
 97 |   }
 98 | }
 99 | 
100 | .vmi-previewer-tabs {
101 |   position: relative;
102 |   display: flex;
103 |   border-bottom: 1px dashed var(--c-border);
104 | 
105 |   &-tab {
106 |     position: relative;
107 |     box-sizing: border-box;
108 |     display: flex;
109 |     align-items: center;
110 |     padding: 0 16px;
111 |     font-size: 14px;
112 |     line-height: 36px;
113 |     cursor: pointer;
114 |     background: transparent;
115 |     border: 0;
116 |     outline: none;
117 | 
118 |     &:hover {
119 |       color: var(--c-brand);
120 |     }
121 | 
122 |     svg {
123 |       margin-right: 5px;
124 |     }
125 |   }
126 | 
127 |   .active::before {
128 |     position: absolute;
129 |     bottom: 0;
130 |     left: 0;
131 |     display: block;
132 |     width: 100%;
133 |     height: 2px;
134 |     pointer-events: none;
135 |     content: '';
136 |     background: var(--c-brand);
137 |     transition: left 0.2s, width 0.2s;
138 |   }
139 | }
140 | 
141 | @media (min-width: 1000px) {
142 |   .vmi-previewer-horizontal {
143 |     display: flex;
144 | 
145 |     .vmi-previewer-example {
146 |       width: 300px;
147 | 
148 |       > iframe {
149 |         height: 100%;
150 |       }
151 |     }
152 | 
153 |     .vmi-previewer-content {
154 |       flex: 1;
155 |       overflow: auto;
156 |       border-left: 1px dashed var(--c-border);
157 |     }
158 | 
159 |     .vmi-previewer-actions {
160 |       border-top: 0;
161 |     }
162 |   }
163 | }
164 | 


--------------------------------------------------------------------------------
/docs/zh/guide/page-map.md:
--------------------------------------------------------------------------------
  1 | # 页面映射
  2 | 
  3 | 通常在编写组件的文档的时候我们可能会把组件的文档直接和组件的实现放在一起,这时候 VuePress 不能很好的处理路径。
  4 | 
  5 | ## 安装
  6 | 
  7 | ```sh
  8 | npm i -D @bfehub/vuepress-plugin-page-map@1.60.x
  9 | ```
 10 | 
 11 | ```js
 12 | import { pageMapPlugin } from '@bfehub/vuepress-plugin-page-map'
 13 | 
 14 | export default {
 15 |   plugins: [
 16 |     pageMapPlugin(),
 17 |   ],
 18 | }
 19 | ```
 20 | 
 21 | ## 如何使用
 22 | 
 23 | 如果有以下目录结构。
 24 | 
 25 | ```sh
 26 | ├── docs
 27 | │   ├── README.md
 28 | │   ├── components // 介绍组件的文档
 29 | │   │   └── README.md
 30 | ├── packages
 31 | │   ├── components
 32 | │   │   ├── npm-badge
 33 | │   │   │   ├── docs // 具体组件的文档
 34 | │   │   │   │   ├── index.md
 35 | │   │   │   │   └── index.zh-CN.md
 36 | │   │   │   ├── examples
 37 | │   │   │   │   └── basic.vue
 38 | │   │   │   └── src
 39 | │   │   │       └── npm-badge.vue
 40 | ```
 41 | 
 42 | 以上组件的文档的 _访问路径_ 和 _临时文件_ 的路径就会被转换成。
 43 | 
 44 | `/packages/components/npm-badge/docs/index.md` => `/components/npm-badge/index.md`
 45 | 
 46 | `/packages/components/npm-badge/docs/index.zh-CN.md` => `/zh/components/npm-badge/index.md`
 47 | 
 48 | ## 多语言规则
 49 | 
 50 | 首先需要配置 `vuepress` 的 `locales` 配置。
 51 | 
 52 | ```js
 53 | module.exports = {
 54 |   locales: {
 55 |     '/': {
 56 |       lang: 'en-US',
 57 |     },
 58 | 
 59 |     '/zh/': {
 60 |       lang: 'zh-CN',
 61 |     },
 62 |   },
 63 | }
 64 | ```
 65 | 
 66 | 文件的名称加上对应 `lang` 的名称,会在最终路径前缀加上语言的路径。
 67 | 
 68 | `components/npm-badge/index.md` -> `/components/npm-badge/index.md`
 69 | 
 70 | `components/npm-badge/index.en-US.md` -> `/components/npm-badge/index.md`
 71 | 
 72 | `components/npm-badge/index.zh-CN.md` -> `/zh/components/npm-badge/index.md`
 73 | 
 74 | 然后可以在 `sidebar` 配置中这样使用。
 75 | 
 76 | ```js
 77 | export const en = {
 78 |   '/components/': [
 79 |     {
 80 |       text: 'Intro',
 81 |       children: ['/components/README.md'],
 82 |     },
 83 |     {
 84 |       text: 'Components',
 85 |       children: ['/components/npm-badge/index.md'],
 86 |     },
 87 |   ],
 88 | }
 89 | ```
 90 | 
 91 | ```js
 92 | export const zh = {
 93 |   '/zh/components/': [
 94 |     {
 95 |       text: '指引',
 96 |       children: ['/zh/components/README.md'],
 97 |     },
 98 |     {
 99 |       text: '组件',
100 |       children: ['/zh/components/npm-badge/index.md'],
101 |     },
102 |   ],
103 | }
104 | ```
105 | 
106 | ## 自定义配置
107 | 
108 | ```ts
109 | export interface PageMapPluginOptions {
110 |   /**
111 |    * 匹配规则
112 |    */
113 |   patterns: string[]
114 | 
115 |   /**
116 |    * 自定义路径规则
117 |    */
118 |   pathMapRule: (path: string) => string
119 | }
120 | ```
121 | 
122 | ### 查找范围
123 | 
124 | 定义查找文件的路径从 `vuepress` 根配置开启,默认配置从 `docs` 目录时如下。
125 | 
126 | ```js
127 | pageMapPlugin({
128 |   patterns: [
129 |     `${path.resolve(process.cwd(), '../packages/**/*.md')}`,
130 |     `!${path.resolve(process.cwd(), '../packages/**/node_modules')}`,
131 |   ],
132 | })
133 | ```
134 | 
135 | ### 路径处理
136 | 
137 | 统一的路径映射处理,只需处理到想要的路径即可,多语言在内部处理,默认配置如下。
138 | 
139 | ```js
140 | pageMapPlugin({
141 |   pathMapRule(path) {
142 |     const paths = path.split('/')
143 |     const len = paths.length
144 | 
145 |     // /User/project/path/npm-badge/index.md
146 |     // -> components/npm-badge/index.md
147 | 
148 |     // /User/project/path/npm-badge/index.zh-CN.md
149 |     // -> components/npm-badge/index.zh-CN.md
150 | 
151 |     // /User/project/path/npm-badge/docs/index.md
152 |     // -> components/npm-badge/index.md
153 | 
154 |     // /User/project/path/npm-badge/docs/index.zh-CN.md
155 |     // -> components/npm-badge/index.zh-CN.md
156 | 
157 |     return `components/${
158 |       paths[len - 2] === 'docs' ? paths[len - 3] : paths[len - 2]
159 |     }/${paths[len - 1]}`
160 |   },
161 | })
162 | ```
163 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/components/AutoLink.vue:
--------------------------------------------------------------------------------
  1 | 
 10 | 
 11 | 
 87 | 
 88 | 
115 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/styles/home.scss:
--------------------------------------------------------------------------------
  1 | @import '_variables';
  2 | 
  3 | .home {
  4 |   display: block;
  5 |   max-width: var(--homepage-width);
  6 |   padding: var(--navbar-height) 2rem 0;
  7 |   margin: 0 auto;
  8 | 
  9 |   .hero {
 10 |     text-align: center;
 11 | 
 12 |     img {
 13 |       display: block;
 14 |       max-width: 100%;
 15 |       max-height: 280px;
 16 |       margin: 3rem auto 1.5rem;
 17 |     }
 18 | 
 19 |     h1 {
 20 |       font-size: 3rem;
 21 |     }
 22 | 
 23 |     h1,
 24 |     .description,
 25 |     .actions {
 26 |       margin: 1.8rem auto;
 27 |     }
 28 | 
 29 |     .actions {
 30 |       display: flex;
 31 |       flex-wrap: wrap;
 32 |       gap: 1rem;
 33 |       justify-content: center;
 34 |     }
 35 | 
 36 |     .description {
 37 |       max-width: 35rem;
 38 |       font-size: 1.6rem;
 39 |       line-height: 1.3;
 40 |       color: var(--c-text-lightest);
 41 |     }
 42 | 
 43 |     .action-button {
 44 |       box-sizing: border-box;
 45 |       display: inline-block;
 46 |       padding: 0.8rem 1.6rem;
 47 |       font-size: 1.2rem;
 48 |       border-style: solid;
 49 |       border-width: 2px;
 50 |       border-radius: 4px;
 51 |       transition: background-color var(--t-color);
 52 | 
 53 |       &.primary {
 54 |         color: var(--c-bg);
 55 |         background-color: var(--c-brand);
 56 |         border-color: var(--c-brand);
 57 | 
 58 |         &:hover {
 59 |           background-color: var(--c-brand-light);
 60 |         }
 61 |       }
 62 | 
 63 |       &.secondary {
 64 |         color: var(--c-brand);
 65 |         background-color: var(--c-bg);
 66 |         border-color: var(--c-brand);
 67 | 
 68 |         &:hover {
 69 |           color: var(--c-bg);
 70 |           background-color: var(--c-brand-light);
 71 |         }
 72 |       }
 73 |     }
 74 |   }
 75 | 
 76 |   .features {
 77 |     display: flex;
 78 |     flex-wrap: wrap;
 79 |     align-content: stretch;
 80 |     align-items: flex-start;
 81 |     justify-content: space-between;
 82 |     padding: 1.2rem 0;
 83 |     margin-top: 2.5rem;
 84 |     border-top: 1px solid var(--c-border);
 85 |     transition: border-color var(--t-color);
 86 |   }
 87 | 
 88 |   .feature {
 89 |     flex-basis: 30%;
 90 |     flex-grow: 1;
 91 |     max-width: 30%;
 92 | 
 93 |     h2 {
 94 |       padding-bottom: 0;
 95 |       font-size: 1.4rem;
 96 |       font-weight: 500;
 97 |       color: var(--c-text-light);
 98 |       border-bottom: none;
 99 |     }
100 | 
101 |     p {
102 |       color: var(--c-text-lighter);
103 |     }
104 |   }
105 | 
106 |   .footer {
107 |     padding: 2.5rem;
108 |     color: var(--c-text-lighter);
109 |     text-align: center;
110 |     border-top: 1px solid var(--c-border);
111 |     transition: border-color var(--t-color);
112 |   }
113 | }
114 | 
115 | @media (max-width: $MQMobile) {
116 |   .home {
117 |     .features {
118 |       flex-direction: column;
119 |     }
120 | 
121 |     .feature {
122 |       max-width: 100%;
123 |       padding: 0 2.5rem;
124 |     }
125 |   }
126 | }
127 | 
128 | @media (max-width: $MQMobileNarrow) {
129 |   .home {
130 |     padding-right: 1.5rem;
131 |     padding-left: 1.5rem;
132 | 
133 |     .hero {
134 |       img {
135 |         max-height: 210px;
136 |         margin: 2rem auto 1.2rem;
137 |       }
138 | 
139 |       h1 {
140 |         font-size: 2rem;
141 |       }
142 | 
143 |       h1,
144 |       .description,
145 |       .actions {
146 |         margin: 1.2rem auto;
147 |       }
148 | 
149 |       .description {
150 |         font-size: 1.2rem;
151 |       }
152 | 
153 |       .action-button {
154 |         padding: 0.6rem 1.2rem;
155 |         font-size: 1rem;
156 |       }
157 |     }
158 | 
159 |     .feature {
160 |       h2 {
161 |         font-size: 1.25rem;
162 |       }
163 |     }
164 |   }
165 | }
166 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/styles/navbar.scss:
--------------------------------------------------------------------------------
  1 | @import '_variables';
  2 | 
  3 | .navbar {
  4 |   --navbar-line-height: calc(
  5 |     var(--navbar-height) - 2 * var(--navbar-padding-v)
  6 |   );
  7 | 
  8 |   padding: var(--navbar-padding-v) var(--navbar-padding-h);
  9 |   line-height: var(--navbar-line-height);
 10 | 
 11 |   .logo {
 12 |     height: var(--navbar-line-height);
 13 |     margin-right: var(--navbar-padding-v);
 14 |     vertical-align: top;
 15 |   }
 16 | 
 17 |   .site-name {
 18 |     position: relative;
 19 |     font-size: 1.3rem;
 20 |     font-weight: 600;
 21 |     color: var(--c-text);
 22 |   }
 23 | 
 24 |   .navbar-items-wrapper {
 25 |     position: absolute;
 26 |     top: var(--navbar-padding-v);
 27 |     right: var(--navbar-padding-h);
 28 |     box-sizing: border-box;
 29 |     display: flex;
 30 |     height: var(--navbar-line-height);
 31 |     padding-left: var(--navbar-padding-h);
 32 |     font-size: 0.9rem;
 33 |     white-space: nowrap;
 34 | 
 35 |     .search-box {
 36 |       flex: 0 0 auto;
 37 |       vertical-align: top;
 38 |     }
 39 |   }
 40 | }
 41 | 
 42 | @media (max-width: $MQMobile) {
 43 |   .navbar {
 44 |     padding-left: 4rem;
 45 | 
 46 |     .can-hide {
 47 |       display: none;
 48 |     }
 49 | 
 50 |     .site-name {
 51 |       width: calc(100vw - 9.4rem);
 52 |       overflow: hidden;
 53 |       text-overflow: ellipsis;
 54 |       white-space: nowrap;
 55 |     }
 56 |   }
 57 | }
 58 | 
 59 | /**
 60 |  * navbar-items
 61 |  */
 62 | .navbar-items {
 63 |   display: inline-block;
 64 | 
 65 |   a {
 66 |     display: inline-block;
 67 |     line-height: 1.4rem;
 68 |     color: inherit;
 69 | 
 70 |     &:hover,
 71 |     &.router-link-active {
 72 |       color: var(--c-text-accent);
 73 |     }
 74 |   }
 75 | 
 76 |   .navbar-item {
 77 |     position: relative;
 78 |     display: inline-block;
 79 |     margin-left: 2rem;
 80 |     line-height: var(--navbar-line-height);
 81 | 
 82 |     // &:first-child {
 83 |     //   margin-left: 0;
 84 |     // }
 85 |   }
 86 | }
 87 | 
 88 | @media (max-width: $MQMobile) {
 89 |   .navbar-items {
 90 |     .navbar-item {
 91 |       margin-left: 0;
 92 |     }
 93 |   }
 94 | }
 95 | 
 96 | @media (min-width: $MQMobile) {
 97 |   .navbar-items a {
 98 |     &:hover,
 99 |     &.router-link-active {
100 |       color: var(--c-text);
101 |     }
102 |   }
103 | 
104 |   .navbar-item > a {
105 |     &:hover,
106 |     &.router-link-active {
107 |       margin-bottom: -2px;
108 |       border-bottom: 2px solid var(--c-text-accent);
109 |     }
110 |   }
111 | }
112 | 
113 | /**
114 |  * toggle sidebar button
115 |  */
116 | .toggle-sidebar-button {
117 |   position: absolute;
118 |   top: 0.6rem;
119 |   left: 1rem;
120 |   display: none;
121 |   padding: 0.6rem;
122 |   cursor: pointer;
123 | }
124 | 
125 | .toggle-sidebar-button .icon {
126 |   display: flex;
127 |   flex-direction: column;
128 |   align-items: center;
129 |   justify-content: center;
130 |   width: 1.25rem;
131 |   height: 1.25rem;
132 |   cursor: inherit;
133 | 
134 |   span {
135 |     display: inline-block;
136 |     width: 100%;
137 |     height: 2px;
138 |     background-color: var(--c-text);
139 |     border-radius: 2px;
140 |     transition: transform var(--t-transform);
141 | 
142 |     &:nth-child(2) {
143 |       margin: 6px 0;
144 |     }
145 |   }
146 | }
147 | 
148 | @media screen and (max-width: $MQMobile) {
149 |   .toggle-sidebar-button {
150 |     display: block;
151 |   }
152 | }
153 | 
154 | /**
155 |  * toggle color mode button
156 |  */
157 | .toggle-color-mode-button {
158 |   display: flex;
159 |   margin: auto;
160 |   margin-left: 1rem;
161 |   color: var(--c-text);
162 |   cursor: pointer;
163 |   background: none;
164 |   border: 0;
165 |   opacity: 0.8;
166 | 
167 |   &:hover {
168 |     opacity: 1;
169 |   }
170 | 
171 |   .icon {
172 |     width: 1.25rem;
173 |     height: 1.25rem;
174 |   }
175 | }
176 | 
177 | .DocSearch {
178 |   transition: background-color var(--t-color);
179 | }
180 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/components/NavbarDropdown.vue:
--------------------------------------------------------------------------------
  1 | 
 51 | 
 52 | 
127 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/components/PageMeta.vue:
--------------------------------------------------------------------------------
  1 | 
 95 | 
 96 | 
125 | 


--------------------------------------------------------------------------------
/packages/theme-vmi/src/client/layouts/Layout.vue:
--------------------------------------------------------------------------------
  1 | 
 75 | 
 76 | 
135 | 


--------------------------------------------------------------------------------