{{ feature.details }}
21 |{{ getMsg() }}22 | 23 |
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 |