\`\`\` lang 后使用 `title="filepath"` 声明当前代码块的文件路径
+- 如果在 `::: code-tree` 未声明 `entry="filepath"`,可以在代码块 \`\`\` lang 后使用 `:active` 声明当前代码块为展开状态
+- 如果未指定展开的文件路径,默认展开第一个文件
+
+::: details 代码块上为什么是 `title="filepath"` 而不是 `filepath="filepath"` ?
+因为主题已经在 [代码块上提供了标题语法的支持](../code/features.md#代码块标题) ,沿用已有的语法支持
+可以减少学习成本。
+:::
+
+**输入:**
+
+````md :collapsed-lines
+::: code-tree title="Vue App" height="400px" entry="src/main.ts"
+```vue title="src/components/HelloWorld.vue"
+
+ ['"])(?.+?)\k )?(?:\s+|$)/ +const RE_ATTR_VALUE = /(?:^|\s+)(?[\w-]+)(?:=(? ['"])(?.+?)\k |=(?\S+))?(?:\s+|$)/ export function resolveAttrs = Record >(info: string): { attrs: T @@ -18,7 +18,8 @@ export function resolveAttrs = Record // eslint-disable-next-line no-cond-assign while (matched = info.match(RE_ATTR_VALUE)) { - const { attr, value = true } = matched.groups! + const { attr, valueWithQuote, valueWithoutQuote } = matched.groups! + const value = valueWithQuote || valueWithoutQuote || true let v = typeof value === 'string' ? value.trim() : value if (v === 'true') v = true @@ -31,3 +32,9 @@ export function resolveAttrs = Record return { attrs: attrs as T, rawAttrs } } + +export function resolveAttr(info: string, key: string): string | undefined { + const pattern = new RegExp(`(?:^|\\s+)${key}(?:=(? ['"])(?.+?)\\k |=(?\\S+))?(?:\\s+|$)`) + const groups = info.match(pattern)?.groups + return groups?.valueWithQuote || groups?.valueWithoutQuote +} diff --git a/plugins/plugin-md-power/src/shared/codeTree.ts b/plugins/plugin-md-power/src/shared/codeTree.ts new file mode 100644 index 00000000..96c2ca5a --- /dev/null +++ b/plugins/plugin-md-power/src/shared/codeTree.ts @@ -0,0 +1,6 @@ +import type { FileTreeIconMode } from './fileTree' + +export interface CodeTreeOptions { + icon?: FileTreeIconMode + height?: string | number +} diff --git a/plugins/plugin-md-power/src/shared/plugin.ts b/plugins/plugin-md-power/src/shared/plugin.ts index 4bbceb68..ced08c89 100644 --- a/plugins/plugin-md-power/src/shared/plugin.ts +++ b/plugins/plugin-md-power/src/shared/plugin.ts @@ -1,5 +1,6 @@ import type { CanIUseOptions } from './caniuse.js' import type { CodeTabsOptions } from './codeTabs.js' +import type { CodeTreeOptions } from './codeTree.js' import type { FileTreeOptions } from './fileTree.js' import type { IconsOptions } from './icons.js' import type { NpmToOptions } from './npmTo.js' @@ -191,6 +192,21 @@ export interface MarkdownPowerPluginOptions { */ fileTree?: boolean | FileTreeOptions + /** + * 是否启用 代码树 容器语法 和 嵌入语法 + * + * ```md + * ::: code-tree + * ::: + * ``` + * + * `@[code-tree](file_path)` + * + * + * @default false + */ + codeTree?: boolean | CodeTreeOptions + /** * 是否启用 demo 语法 */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d259b5a2..27c3e62c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -278,6 +278,9 @@ catalogs: shiki: specifier: ^3.3.0 version: 3.3.0 + tinyglobby: + specifier: 0.2.13 + version: 0.2.13 tm-grammars: specifier: ^1.23.16 version: 1.23.16 @@ -645,6 +648,9 @@ importers: stylus: specifier: catalog:dev version: 0.64.0 + tinyglobby: + specifier: catalog:prod + version: 0.2.13 tm-grammars: specifier: catalog:prod version: 1.23.16 @@ -4217,14 +4223,6 @@ packages: fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} - fdir@6.4.3: - resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - fdir@6.4.4: resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} peerDependencies: @@ -6579,10 +6577,6 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyglobby@0.2.12: - resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.13: resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} engines: {node: '>=12.0.0'} @@ -9698,7 +9692,7 @@ snapshots: package-manager-detector: 1.1.0 semver: 7.7.1 tinyexec: 0.3.2 - tinyglobby: 0.2.12 + tinyglobby: 0.2.13 yaml: 2.7.0 transitivePeerDependencies: - magicast @@ -10671,7 +10665,7 @@ snapshots: jsonc-eslint-parser: 2.4.0 pathe: 2.0.3 pnpm-workspace-yaml: 0.3.1 - tinyglobby: 0.2.12 + tinyglobby: 0.2.13 yaml-eslint-parser: 1.3.0 eslint-plugin-regexp@2.7.0(eslint@9.25.1(jiti@2.4.2)): @@ -10927,10 +10921,6 @@ snapshots: dependencies: format: 0.2.2 - fdir@6.4.3(picomatch@4.0.2): - optionalDependencies: - picomatch: 4.0.2 - fdir@6.4.4(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -13515,11 +13505,6 @@ snapshots: tinyexec@0.3.2: {} - tinyglobby@0.2.12: - dependencies: - fdir: 6.4.3(picomatch@4.0.2) - picomatch: 4.0.2 - tinyglobby@0.2.13: dependencies: fdir: 6.4.4(picomatch@4.0.2) @@ -13593,7 +13578,7 @@ snapshots: source-map: 0.8.0-beta.0 sucrase: 3.35.0 tinyexec: 0.3.2 - tinyglobby: 0.2.12 + tinyglobby: 0.2.13 tree-kill: 1.2.2 optionalDependencies: postcss: 8.5.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6af9013c..ecff8b43 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -110,6 +110,7 @@ catalogs: package-manager-detector: ^1.2.0 picocolors: ^1.1.1 shiki: ^3.3.0 + tinyglobby: 0.2.13 tm-grammars: ^1.23.16 tm-themes: ^1.10.5 unplugin: ^2.3.2 diff --git a/theme/src/client/styles/code.css b/theme/src/client/styles/code.css index 6b617d27..81f13d65 100644 --- a/theme/src/client/styles/code.css +++ b/theme/src/client/styles/code.css @@ -115,13 +115,15 @@ html:not([data-theme="dark"]) .vp-code span { .vp-doc div[class*="language-"].line-numbers-mode .line-numbers { position: absolute; top: 0; - bottom: 0; /* rtl:ignore */ left: 0; z-index: 3; width: 32px; + height: fit-content; + min-height: 100%; padding-top: 20px; + padding-bottom: 20px; font-family: var(--vp-font-family-mono); font-size: var(--vp-code-font-size); line-height: var(--vp-code-line-height); diff --git a/theme/src/node/detector/fields.ts b/theme/src/node/detector/fields.ts index d51d8662..cd6985a9 100644 --- a/theme/src/node/detector/fields.ts +++ b/theme/src/node/detector/fields.ts @@ -47,6 +47,7 @@ export const MARKDOWN_POWER_FIELDS: (keyof MarkdownPowerPluginOptions)[] = [ 'caniuse', 'codeSandbox', 'codeTabs', + 'codeTree', 'codepen', 'demo', 'fileTree', diff --git a/theme/src/node/plugins/code.ts b/theme/src/node/plugins/code.ts index c1bc2e74..86060661 100644 --- a/theme/src/node/plugins/code.ts +++ b/theme/src/node/plugins/code.ts @@ -43,7 +43,7 @@ export function codePlugins(pluginOptions: ThemeBuiltinPlugins): PluginConfig { langs: uniq([...twoslash ? ['ts', 'js', 'vue', 'json', 'bash', 'sh'] : [], ...langs]), codeBlockTitle: (title, code) => { const icon = getIcon(title) - return ` ${code}` + return `${code}` }, twoslash: isPlainObject(twoslashOptions) ? {