vuepress-plugin-md-power 1.0.0-rc.144 → 1.0.0-rc.146

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,15 @@
1
1
  <script setup lang="ts">
2
2
  import type { Ref } from 'vue'
3
- import { FadeInExpandTransition } from '@vuepress/helper/client'
4
3
  import { inject, ref } from 'vue'
5
4
 
6
- import '@vuepress/helper/transition/fade-in-height-expand.css'
7
-
8
5
  const props = defineProps<{
9
6
  type: 'file' | 'folder'
10
7
  filename: string
8
+ level: number
9
+ diff?: 'add' | 'remove'
11
10
  expanded?: boolean
12
11
  focus?: boolean
12
+ filepath?: string
13
13
  }>()
14
14
 
15
15
  const activeFileTreeNode = inject<Ref<string>>('active-file-tree-node', ref(''))
@@ -24,7 +24,7 @@ function nodeClick() {
24
24
  if (props.filename === '…' || props.filename === '...')
25
25
  return
26
26
 
27
- onNodeClick(props.filename, props.type)
27
+ onNodeClick(props.filepath || props.filename, props.type)
28
28
  }
29
29
 
30
30
  function toggle(ev: MouseEvent) {
@@ -48,19 +48,21 @@ function toggle(ev: MouseEvent) {
48
48
  [type]: true,
49
49
  focus,
50
50
  expanded: type === 'folder' ? active : false,
51
- active: type === 'file' ? activeFileTreeNode === filename : false,
51
+ active: type === 'file' ? activeFileTreeNode === filepath : false,
52
+ diff,
53
+ add: diff === 'add',
54
+ remove: diff === 'remove',
52
55
  }"
56
+ :style="{ '--file-tree-level': -level }"
53
57
  @click="toggle"
54
58
  >
55
59
  <slot name="icon" />
56
60
  <span class="name" :class="[type]">{{ filename }}</span>
57
61
  <span v-if="$slots.comment" class="comment"><slot name="comment" /></span>
58
62
  </p>
59
- <FadeInExpandTransition>
60
- <div v-if="type === 'folder'" v-show="active" class="group">
61
- <slot />
62
- </div>
63
- </FadeInExpandTransition>
63
+ <div v-if="type === 'folder'" v-show="active" class="group">
64
+ <slot />
65
+ </div>
64
66
  </div>
65
67
  </template>
66
68
 
@@ -100,24 +102,40 @@ function toggle(ev: MouseEvent) {
100
102
 
101
103
  .vp-file-tree .vp-file-tree-info::after {
102
104
  position: absolute;
103
- top: 1px;
104
- right: 0;
105
- bottom: 1px;
106
- left: -16px;
105
+ top: 0;
106
+ right: -16px;
107
+ bottom: 0;
108
+ left: calc(var(--file-tree-level) * 28px - 32px);
107
109
  z-index: 0;
108
110
  display: block;
109
111
  pointer-events: none;
110
112
  content: "";
111
113
  background-color: transparent;
112
- border-radius: 6px;
113
114
  transition: background-color var(--vp-t-color);
114
115
  }
115
116
 
116
117
  .vp-file-tree .vp-file-tree-info.active::after,
117
- .vp-file-tree .vp-file-tree-info:hover::after {
118
+ .vp-file-tree .vp-file-tree-info:not(.diff):hover::after {
118
119
  background-color: var(--vp-c-default-soft);
119
120
  }
120
121
 
122
+ .vp-file-tree .vp-file-tree-info.diff::after {
123
+ padding-left: 4px;
124
+ font-size: 1.25em;
125
+ }
126
+
127
+ .vp-file-tree .vp-file-tree-info.diff.add::after {
128
+ color: var(--vp-c-success-1);
129
+ content: "+";
130
+ background-color: var(--vp-c-success-soft);
131
+ }
132
+
133
+ .vp-file-tree .vp-file-tree-info.diff.remove::after {
134
+ color: var(--vp-c-danger-1);
135
+ content: "-";
136
+ background-color: var(--vp-c-danger-soft);
137
+ }
138
+
121
139
  .vp-file-tree .vp-file-tree-info.folder {
122
140
  cursor: pointer;
123
141
  }
@@ -0,0 +1,159 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, provide, ref, useTemplateRef, watch } from 'vue'
3
+
4
+ const props = withDefaults(defineProps<{
5
+ title?: string
6
+ height?: string
7
+ entryFile?: string
8
+ }>(), { height: '320px' })
9
+
10
+ const activeNode = ref(props.entryFile || '')
11
+ const isEmpty = ref(true)
12
+ const codePanel = useTemplateRef<HTMLDivElement>('codePanel')
13
+
14
+ provide('active-file-tree-node', activeNode)
15
+ provide('on-file-tree-node-click', (filepath: string, type: 'file' | 'folder') => {
16
+ if (type === 'file') {
17
+ activeNode.value = filepath
18
+ }
19
+ })
20
+
21
+ onMounted(() => {
22
+ watch(
23
+ () => activeNode.value,
24
+ () => {
25
+ if (codePanel.value) {
26
+ const items = Array.from(codePanel.value.querySelectorAll('.code-block-title'))
27
+ let hasActive = false
28
+ items.forEach((item) => {
29
+ if (item.getAttribute('data-title') === activeNode.value) {
30
+ item.classList.add('active')
31
+ hasActive = true
32
+ }
33
+ else {
34
+ item.classList.remove('active')
35
+ }
36
+ })
37
+ isEmpty.value = !hasActive
38
+ }
39
+ },
40
+ { immediate: true },
41
+ )
42
+ })
43
+ </script>
44
+
45
+ <template>
46
+ <div class="vp-code-tree">
47
+ <div class="code-tree-panel" :style="{ 'max-height': props.height }">
48
+ <div v-if="title" class="code-tree-title" :title="title">
49
+ <span>{{ title }}</span>
50
+ </div>
51
+ <div class="vp-file-tree">
52
+ <slot name="file-tree" />
53
+ </div>
54
+ </div>
55
+ <div ref="codePanel" class="code-panel" :style="{ height: props.height }">
56
+ <slot />
57
+ <div v-if="isEmpty" class="code-tree-empty">
58
+ <span class="vpi-code-tree-empty" />
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </template>
63
+
64
+ <style>
65
+ .vp-code-tree {
66
+ width: 100%;
67
+ margin: 16px 0;
68
+ overflow: hidden;
69
+ border: solid 1px var(--vp-c-divider);
70
+ border-radius: 6px;
71
+ }
72
+
73
+ @media (min-width: 768px) {
74
+ .vp-code-tree {
75
+ display: grid;
76
+ grid-template-columns: repeat(3, minmax(0, 1fr));
77
+ }
78
+ }
79
+
80
+ .vp-code-tree .code-tree-panel {
81
+ display: flex;
82
+ flex-direction: column;
83
+ border-bottom: solid 1px var(--vp-c-divider);
84
+ }
85
+
86
+ @media (min-width: 768px) {
87
+ .vp-code-tree .code-tree-panel {
88
+ border-right: solid 1px var(--vp-c-divider);
89
+ border-bottom: none;
90
+ }
91
+ }
92
+
93
+ .vp-code-tree .code-tree-panel .code-tree-title {
94
+ height: 40px;
95
+ padding: 0 16px;
96
+ overflow: hidden;
97
+ font-weight: 500;
98
+ line-height: 40px;
99
+ text-overflow: ellipsis;
100
+ white-space: nowrap;
101
+ border-bottom: solid 1px var(--vp-c-divider);
102
+ }
103
+
104
+ .vp-code-tree .code-tree-panel .vp-file-tree {
105
+ flex: 1 2;
106
+ margin: 0;
107
+ overflow: auto;
108
+ background-color: transparent;
109
+ border: none;
110
+ border-radius: 0;
111
+ }
112
+
113
+ .vp-code-tree .code-tree-panel .vp-file-tree .vp-file-tree-info.file {
114
+ cursor: pointer;
115
+ }
116
+
117
+ .vp-code-tree .code-panel {
118
+ grid-column: span 2 / span 2;
119
+ }
120
+
121
+ .vp-code-tree .code-panel [class*="language-"] {
122
+ flex: 1 2;
123
+ margin: 16px 0 0;
124
+ overflow: auto;
125
+ border-bottom-right-radius: 0;
126
+ border-bottom-left-radius: 0;
127
+ }
128
+
129
+ .vp-code-tree .code-panel .code-block-title {
130
+ display: none;
131
+ height: 100%;
132
+ }
133
+
134
+ .vp-code-tree .code-panel .code-block-title.active {
135
+ display: flex;
136
+ flex-direction: column;
137
+ }
138
+
139
+ .vp-code-tree .code-panel .code-block-title .code-block-title-bar {
140
+ margin-top: 0;
141
+ border-radius: 0;
142
+ }
143
+
144
+ .vp-code-tree .code-panel .code-tree-empty {
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+ width: 100%;
149
+ height: 100%;
150
+ }
151
+
152
+ .vp-code-tree .code-panel .code-tree-empty .vpi-code-tree-empty {
153
+ --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='256' height='256' viewBox='0 0 256 256'%3E%3Cpath fill='%23000' d='m198.24 62.63l15.68-17.25a8 8 0 0 0-11.84-10.76L186.4 51.86A95.95 95.95 0 0 0 57.76 193.37l-15.68 17.25a8 8 0 1 0 11.84 10.76l15.68-17.24A95.95 95.95 0 0 0 198.24 62.63M48 128a80 80 0 0 1 127.6-64.25l-107 117.73A79.63 79.63 0 0 1 48 128m80 80a79.55 79.55 0 0 1-47.6-15.75l107-117.73A79.95 79.95 0 0 1 128 208'/%3E%3C/svg%3E");
154
+
155
+ width: 128px;
156
+ height: 128px;
157
+ color: var(--vp-c-default-soft);
158
+ }
159
+ </style>
@@ -0,0 +1,94 @@
1
+ <script lang="ts" setup>
2
+ defineProps<{
3
+ name: string
4
+ type?: string
5
+ required?: boolean
6
+ optional?: boolean
7
+ defaultValue?: string
8
+ }>()
9
+ </script>
10
+
11
+ <template>
12
+ <div class="vp-field">
13
+ <p class="field-meta">
14
+ <span class="name">{{ name }}</span>
15
+ <span v-if="required || optional" :class="{ required, optional }">{{ required ? 'Required' : optional ? 'Optional' : '' }}</span>
16
+ <span v-if="type" class="type"><code>{{ type }}</code></span>
17
+ </p>
18
+ <p v-if="defaultValue" class="default-value">
19
+ <code>{{ defaultValue }}</code>
20
+ </p>
21
+ <div v-if="$slots.default" class="description">
22
+ <slot />
23
+ </div>
24
+ </div>
25
+ </template>
26
+
27
+ <style>
28
+ .vp-field {
29
+ width: 100%;
30
+ margin: 16px 0;
31
+ transition: border-color var(--vp-t-color);
32
+ }
33
+
34
+ .vp-field + .vp-field {
35
+ padding-top: 8px;
36
+ border-top: solid 1px var(--vp-c-divider);
37
+ }
38
+
39
+ .vp-field .field-meta {
40
+ display: flex;
41
+ gap: 8px;
42
+ align-items: flex-start;
43
+ margin: 8px 0;
44
+ }
45
+
46
+ .vp-field .field-meta .name {
47
+ font-size: 18px;
48
+ font-weight: 500;
49
+ }
50
+
51
+ .vp-field .field-meta .required,
52
+ .vp-field .field-meta .optional {
53
+ display: inline-block;
54
+ padding: 2px 8px;
55
+ font-size: 12px;
56
+ font-style: italic;
57
+ line-height: 1;
58
+ border-radius: 8px;
59
+ }
60
+
61
+ .vp-field .field-meta .required {
62
+ color: var(--vp-c-success-2);
63
+ border: solid 1px var(--vp-c-success-2);
64
+ }
65
+
66
+ .vp-field .field-meta .optional {
67
+ color: var(--vp-c-text-3);
68
+ border: solid 1px var(--vp-c-divider);
69
+ }
70
+
71
+ .vp-field .field-meta .type {
72
+ flex: 1 2;
73
+ text-align: right;
74
+ }
75
+
76
+ .vp-field .default-value {
77
+ margin: 0;
78
+ font-size: 14px;
79
+ line-height: 1;
80
+ }
81
+
82
+ .vp-field .description :where(p, ul, ol) {
83
+ margin: 8px 0;
84
+ line-height: 24px;
85
+ color: var(--vp-c-text-2);
86
+ }
87
+
88
+ .vp-field-group {
89
+ padding: 0 20px;
90
+ margin: 16px 0;
91
+ border: solid 1px var(--vp-c-divider);
92
+ border-radius: 6px;
93
+ }
94
+ </style>
@@ -153,6 +153,11 @@ interface PlotOptions {
153
153
  trigger?: 'hover' | 'click';
154
154
  }
155
155
 
156
+ interface CodeTreeOptions {
157
+ icon?: FileTreeIconMode;
158
+ height?: string | number;
159
+ }
160
+
156
161
  type ThemeOptions = BuiltinTheme | {
157
162
  light: BuiltinTheme;
158
163
  dark: BuiltinTheme;
@@ -268,6 +273,12 @@ interface MarkdownPowerPluginOptions {
268
273
  * @default false
269
274
  */
270
275
  chat?: boolean;
276
+ /**
277
+ * 是否启用 field / field-group 容器
278
+ *
279
+ * @default false
280
+ */
281
+ field?: boolean;
271
282
  /**
272
283
  * 是否启用 bilibili 视频嵌入
273
284
  *
@@ -336,6 +347,20 @@ interface MarkdownPowerPluginOptions {
336
347
  * @default false
337
348
  */
338
349
  fileTree?: boolean | FileTreeOptions;
350
+ /**
351
+ * 是否启用 代码树 容器语法 和 嵌入语法
352
+ *
353
+ * ```md
354
+ * ::: code-tree
355
+ * :::
356
+ * ```
357
+ *
358
+ * `@[code-tree](file_path)`
359
+ *
360
+ *
361
+ * @default false
362
+ */
363
+ codeTree?: boolean | CodeTreeOptions;
339
364
  /**
340
365
  * 是否启用 demo 语法
341
366
  */
package/lib/node/index.js CHANGED
@@ -947,7 +947,7 @@ import { fs, logger, path } from "vuepress/utils";
947
947
 
948
948
  // src/node/utils/resolveAttrs.ts
949
949
  import { camelCase } from "@pengzhanbo/utils";
950
- var RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w-]+)(?:=\s*(?<quote>['"])(?<value>.+?)\k<quote>)?(?:\s+|$)/;
950
+ var RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w-]+)(?:=(?<quote>['"])(?<valueWithQuote>.+?)\k<quote>|=(?<valueWithoutQuote>\S+))?(?:\s+|$)/;
951
951
  function resolveAttrs(info) {
952
952
  info = info.trim();
953
953
  if (!info)
@@ -956,17 +956,25 @@ function resolveAttrs(info) {
956
956
  const rawAttrs = info;
957
957
  let matched;
958
958
  while (matched = info.match(RE_ATTR_VALUE)) {
959
- const { attr, value = true } = matched.groups;
959
+ const { attr, valueWithQuote, valueWithoutQuote } = matched.groups;
960
+ const value = valueWithQuote || valueWithoutQuote || true;
960
961
  let v = typeof value === "string" ? value.trim() : value;
961
962
  if (v === "true")
962
963
  v = true;
963
964
  else if (v === "false")
964
965
  v = false;
966
+ else if (v === '""' || v === "''")
967
+ v = "";
965
968
  attrs2[camelCase(attr)] = v;
966
969
  info = info.slice(matched[0].length);
967
970
  }
968
971
  return { attrs: attrs2, rawAttrs };
969
972
  }
973
+ function resolveAttr(info, key) {
974
+ const pattern = new RegExp(`(?:^|\\s+)${key}(?:=(?<quote>['"])(?<valueWithQuote>.+?)\\k<quote>|=(?<valueWithoutQuote>\\S+))?(?:\\s+|$)`);
975
+ const groups = info.match(pattern)?.groups;
976
+ return groups?.valueWithQuote || groups?.valueWithoutQuote;
977
+ }
970
978
 
971
979
  // src/node/enhance/imageSize.ts
972
980
  var REG_IMG = /!\[.*?\]\(.*?\)/g;
@@ -1146,8 +1154,121 @@ async function resolveImageSize(app, url, remote = false) {
1146
1154
  import { addViteOptimizeDepsInclude } from "@vuepress/helper";
1147
1155
  import { isPackageExists as isPackageExists3 } from "local-pkg";
1148
1156
 
1149
- // src/node/container/index.ts
1150
- import { isPlainObject as isPlainObject2 } from "@vuepress/helper";
1157
+ // src/node/container/codeTree.ts
1158
+ import path3 from "node:path";
1159
+ import { globSync } from "tinyglobby";
1160
+ import { removeLeadingSlash } from "vuepress/shared";
1161
+
1162
+ // src/node/demo/supports/file.ts
1163
+ import fs2 from "node:fs";
1164
+ import { createRequire } from "node:module";
1165
+ import path2 from "node:path";
1166
+ import process from "node:process";
1167
+ var require2 = createRequire(process.cwd());
1168
+ function findFile(app, env, url) {
1169
+ if (url.startsWith("/"))
1170
+ return app.dir.source(url.slice(1));
1171
+ if (url.startsWith("./") || url.startsWith("../"))
1172
+ return app.dir.source(path2.dirname(env.filePathRelative), url);
1173
+ if (url.startsWith("@source/")) {
1174
+ return app.dir.source(url.slice("@source/".length));
1175
+ }
1176
+ try {
1177
+ return require2.resolve(url);
1178
+ } catch {
1179
+ return url;
1180
+ }
1181
+ }
1182
+ function readFileSync(filepath2) {
1183
+ try {
1184
+ return fs2.readFileSync(filepath2, "utf-8");
1185
+ } catch {
1186
+ return false;
1187
+ }
1188
+ }
1189
+ function writeFileSync(filepath2, content) {
1190
+ const dirname = path2.dirname(filepath2);
1191
+ fs2.mkdirSync(dirname, { recursive: true });
1192
+ fs2.writeFileSync(filepath2, content, "utf-8");
1193
+ }
1194
+
1195
+ // src/node/embed/createEmbedRuleBlock.ts
1196
+ function createEmbedRuleBlock(md, {
1197
+ type,
1198
+ name = type,
1199
+ syntaxPattern,
1200
+ beforeName = "import_code",
1201
+ ruleOptions = { alt: ["paragraph", "reference", "blockquote", "list"] },
1202
+ meta,
1203
+ content
1204
+ }) {
1205
+ const MIN_LENGTH = type.length + 5;
1206
+ const START_CODES = [64, 91, ...type.split("").map((c) => c.charCodeAt(0))];
1207
+ md.block.ruler.before(
1208
+ beforeName,
1209
+ name,
1210
+ (state, startLine, endLine, silent) => {
1211
+ const pos = state.bMarks[startLine] + state.tShift[startLine];
1212
+ const max = state.eMarks[startLine];
1213
+ if (pos + MIN_LENGTH > max)
1214
+ return false;
1215
+ for (let i = 0; i < START_CODES.length; i += 1) {
1216
+ if (state.src.charCodeAt(pos + i) !== START_CODES[i])
1217
+ return false;
1218
+ }
1219
+ const content2 = state.src.slice(pos, max);
1220
+ const match = content2.match(syntaxPattern);
1221
+ if (!match)
1222
+ return false;
1223
+ if (silent)
1224
+ return true;
1225
+ const token = state.push(name, "", 0);
1226
+ token.meta = meta(match);
1227
+ token.content = content2;
1228
+ token.map = [startLine, startLine + 1];
1229
+ state.line = startLine + 1;
1230
+ return true;
1231
+ },
1232
+ ruleOptions
1233
+ );
1234
+ md.renderer.rules[name] = (tokens, index, _, env) => {
1235
+ const token = tokens[index];
1236
+ token.content = content(token.meta, token.content, env);
1237
+ return token.content;
1238
+ };
1239
+ }
1240
+
1241
+ // src/node/utils/parseRect.ts
1242
+ function parseRect(str, unit = "px") {
1243
+ if (Number.parseFloat(str) === Number(str))
1244
+ return `${str}${unit}`;
1245
+ return str;
1246
+ }
1247
+
1248
+ // src/node/utils/stringifyAttrs.ts
1249
+ import { isBoolean, isNull, isNumber, isString, isUndefined, kebabCase } from "@pengzhanbo/utils";
1250
+ function stringifyAttrs(attrs2, withUndefined = false) {
1251
+ const result = Object.entries(attrs2).map(([key, value]) => {
1252
+ const k = kebabCase(key);
1253
+ if (isUndefined(value) || value === "undefined")
1254
+ return withUndefined ? `:${k}="undefined"` : "";
1255
+ if (isNull(value) || value === "null")
1256
+ return withUndefined ? `:${k}="null"` : "";
1257
+ if (value === "true")
1258
+ value = true;
1259
+ if (value === "false")
1260
+ value = false;
1261
+ if (isBoolean(value))
1262
+ return value ? `${k}` : "";
1263
+ if (isNumber(value))
1264
+ return `:${k}="${value}"`;
1265
+ if (isString(value) && (value[0] === "{" || value[0] === "["))
1266
+ return `:${k}="${value.replaceAll('"', "'")}"`;
1267
+ const hasDynamic = key[0] === ":";
1268
+ return `${hasDynamic ? ":" : ""}${k}="${String(value)}"`;
1269
+ }).filter(Boolean).join(" ");
1270
+ return result ? ` ${result}` : "";
1271
+ }
1151
1272
 
1152
1273
  // src/node/container/createContainer.ts
1153
1274
  import container from "markdown-it-container";
@@ -1210,6 +1331,161 @@ function createContainerSyntaxPlugin(md, type, render) {
1210
1331
  md.renderer.rules[`${type}_container`] = render ?? defaultRender;
1211
1332
  }
1212
1333
 
1334
+ // src/node/container/codeTree.ts
1335
+ var UNSUPPORTED_FILE_TYPES = [
1336
+ /* image */
1337
+ "jpg",
1338
+ "jpeg",
1339
+ "png",
1340
+ "gif",
1341
+ "avif",
1342
+ "webp",
1343
+ /* media */
1344
+ "mp3",
1345
+ "mp4",
1346
+ "ogg",
1347
+ "m3u8",
1348
+ "m3u",
1349
+ "flv",
1350
+ "webm",
1351
+ "wav",
1352
+ "flac",
1353
+ "aac",
1354
+ /* document */
1355
+ "pdf",
1356
+ "doc",
1357
+ "docx",
1358
+ "ppt",
1359
+ "pptx",
1360
+ "xls",
1361
+ "xlsx"
1362
+ ];
1363
+ function parseFileNodes(files) {
1364
+ const nodes = [];
1365
+ for (const file of files) {
1366
+ const parts = removeLeadingSlash(file).split("/");
1367
+ let node = nodes;
1368
+ for (let i = 0; i < parts.length; i++) {
1369
+ const part = parts[i];
1370
+ const isFile = i === parts.length - 1;
1371
+ let child = node.find((n) => n.filename === part);
1372
+ if (!child) {
1373
+ child = {
1374
+ level: i + 1,
1375
+ filename: part,
1376
+ filepath: isFile ? file : void 0,
1377
+ children: isFile ? void 0 : []
1378
+ };
1379
+ node.push(child);
1380
+ }
1381
+ if (!isFile && child.children)
1382
+ node = child.children;
1383
+ }
1384
+ }
1385
+ return nodes;
1386
+ }
1387
+ function codeTreePlugin(md, app, options = {}) {
1388
+ const getIcon = (filename, type, mode) => {
1389
+ mode ||= options.icon || "colored";
1390
+ if (mode === "simple")
1391
+ return type === "folder" ? defaultFolder : defaultFile;
1392
+ return getFileIcon(filename, type);
1393
+ };
1394
+ function renderFileTree(nodes, mode) {
1395
+ return nodes.map((node) => {
1396
+ const props = {
1397
+ filename: node.filename,
1398
+ level: node.level,
1399
+ type: node.children?.length ? "folder" : "file",
1400
+ expanded: true,
1401
+ filepath: node.filepath
1402
+ };
1403
+ return `<FileTreeNode${stringifyAttrs(props)}>
1404
+ <template #icon><VPIcon name="${getIcon(node.filename, props.type, mode)}" /></template>
1405
+ ${node.children?.length ? renderFileTree(node.children, mode) : ""}
1406
+ </FileTreeNode>`;
1407
+ }).join("\n");
1408
+ }
1409
+ createContainerPlugin(md, "code-tree", {
1410
+ before: (info, tokens, index) => {
1411
+ const files = [];
1412
+ let activeFile;
1413
+ for (let i = index + 1; !(tokens[i].nesting === -1 && tokens[i].type === "container_code-tree_close"); i++) {
1414
+ const token = tokens[i];
1415
+ if (token.type === "fence" && token.tag === "code") {
1416
+ const fenceInfo = md.utils.unescapeAll(token.info);
1417
+ const title2 = resolveAttr(fenceInfo, "title");
1418
+ if (title2) {
1419
+ files.push(title2);
1420
+ if (fenceInfo.includes(":active"))
1421
+ activeFile = title2;
1422
+ }
1423
+ }
1424
+ }
1425
+ const { attrs: attrs2 } = resolveAttrs(info);
1426
+ const { title, icon, height, entry } = attrs2;
1427
+ const fileTreeNodes = parseFileNodes(files);
1428
+ const entryFile = activeFile || entry || files[0];
1429
+ const h = height || String(options.height);
1430
+ return `<VPCodeTree${stringifyAttrs({ title, entryFile, height: h ? parseRect(h) : void 0 })}><template #file-tree>${renderFileTree(fileTreeNodes, icon)}</template>`;
1431
+ },
1432
+ after: () => "</VPCodeTree>"
1433
+ });
1434
+ createEmbedRuleBlock(md, {
1435
+ type: "code-tree",
1436
+ syntaxPattern: /^@\[code-tree([^\]]*)\]\(([^)]*)\)/,
1437
+ meta: ([, info, dir]) => {
1438
+ const { attrs: attrs2 } = resolveAttrs(info);
1439
+ const h = attrs2.height || String(options.height);
1440
+ return {
1441
+ title: attrs2.title,
1442
+ entryFile: attrs2.entry,
1443
+ icon: attrs2.icon,
1444
+ height: h ? parseRect(h) : void 0,
1445
+ dir
1446
+ };
1447
+ },
1448
+ content: ({ dir, icon, ...props }, _, env) => {
1449
+ const codeTreeFiles = env.codeTreeFiles ??= [];
1450
+ const root = findFile(app, env, dir);
1451
+ const files = globSync("**/*", {
1452
+ cwd: root,
1453
+ onlyFiles: true,
1454
+ dot: true,
1455
+ ignore: ["**/node_modules/**", "**/.DS_Store", "**/.gitkeep"]
1456
+ }).sort((a, b) => {
1457
+ const al = a.split("/").length;
1458
+ const bl = b.split("/").length;
1459
+ return bl - al;
1460
+ });
1461
+ props.entryFile ||= files[0];
1462
+ const codeContent = files.map((file) => {
1463
+ const ext = path3.extname(file).slice(1);
1464
+ if (UNSUPPORTED_FILE_TYPES.includes(ext)) {
1465
+ return "";
1466
+ }
1467
+ const filepath2 = path3.join(root, file);
1468
+ codeTreeFiles.push(filepath2);
1469
+ const content = readFileSync(filepath2);
1470
+ return `\`\`\`${ext || "txt"} title="${file}"
1471
+ ${content}
1472
+ \`\`\``;
1473
+ }).filter(Boolean).join("\n");
1474
+ const fileTreeNodes = parseFileNodes(files);
1475
+ return `<VPCodeTree${stringifyAttrs(props)}><template #file-tree>${renderFileTree(fileTreeNodes, icon)}</template>${md.render(codeContent, cleanMarkdownEnv(env))}</VPCodeTree>`;
1476
+ }
1477
+ });
1478
+ }
1479
+ function extendsPageWithCodeTree(page) {
1480
+ const markdownEnv = page.markdownEnv;
1481
+ const codeTreeFiles = markdownEnv.codeTreeFiles ?? [];
1482
+ if (codeTreeFiles.length)
1483
+ page.deps.push(...codeTreeFiles);
1484
+ }
1485
+
1486
+ // src/node/container/index.ts
1487
+ import { isPlainObject as isPlainObject2 } from "@vuepress/helper";
1488
+
1213
1489
  // src/node/container/align.ts
1214
1490
  var alignList = ["left", "center", "right", "justify"];
1215
1491
  function alignPlugin(md) {
@@ -1220,31 +1496,6 @@ function alignPlugin(md) {
1220
1496
  }
1221
1497
  }
1222
1498
 
1223
- // src/node/utils/stringifyAttrs.ts
1224
- import { isBoolean, isNull, isNumber, isString, isUndefined, kebabCase } from "@pengzhanbo/utils";
1225
- function stringifyAttrs(attrs2, withUndefined = false) {
1226
- const result = Object.entries(attrs2).map(([key, value]) => {
1227
- const k = kebabCase(key);
1228
- if (isUndefined(value) || value === "undefined")
1229
- return withUndefined ? `:${k}="undefined"` : "";
1230
- if (isNull(value) || value === "null")
1231
- return withUndefined ? `:${k}="null"` : "";
1232
- if (value === "true")
1233
- value = true;
1234
- if (value === "false")
1235
- value = false;
1236
- if (isBoolean(value))
1237
- return value ? `${k}` : "";
1238
- if (isNumber(value))
1239
- return `:${k}="${value}"`;
1240
- if (isString(value) && (value[0] === "{" || value[0] === "["))
1241
- return `:${k}="${value.replaceAll('"', "'")}"`;
1242
- const hasDynamic = key[0] === ":";
1243
- return `${hasDynamic ? ":" : ""}${k}="${String(value)}"`;
1244
- }).filter(Boolean).join(" ");
1245
- return result ? ` ${result}` : "";
1246
- }
1247
-
1248
1499
  // src/node/container/card.ts
1249
1500
  function cardPlugin(md) {
1250
1501
  createContainerPlugin(md, "card", {
@@ -1448,6 +1699,23 @@ function demoWrapperPlugin(md) {
1448
1699
  });
1449
1700
  }
1450
1701
 
1702
+ // src/node/container/field.ts
1703
+ import { isUndefined as isUndefined2 } from "@pengzhanbo/utils";
1704
+ function fieldPlugin(md) {
1705
+ createContainerPlugin(md, "field", {
1706
+ before: (info) => {
1707
+ const { attrs: attrs2 } = resolveAttrs(info);
1708
+ const { name, type, required, optional, default: defaultValue } = attrs2;
1709
+ const props = stringifyAttrs({ name, required, optional });
1710
+ return `<VPField${props}${!isUndefined2(type) ? ` type="${type}"` : ""}${!isUndefined2(defaultValue) ? ` default-value="${defaultValue}"` : ""}>`;
1711
+ },
1712
+ after: () => "</VPField>"
1713
+ });
1714
+ createContainerPlugin(md, "field-group", {
1715
+ before: () => '<div class="vp-field-group">'
1716
+ });
1717
+ }
1718
+
1451
1719
  // src/node/container/fileTree.ts
1452
1720
  import { removeEndingSlash } from "vuepress/shared";
1453
1721
  function parseFileTreeRawContent(content) {
@@ -1477,6 +1745,14 @@ function parseFileTreeNodeInfo(info) {
1477
1745
  let focus = false;
1478
1746
  let expanded = true;
1479
1747
  let type = "file";
1748
+ let diff;
1749
+ if (info.startsWith("++")) {
1750
+ info = info.slice(2).trim();
1751
+ diff = "add";
1752
+ } else if (info.startsWith("--")) {
1753
+ info = info.slice(2).trim();
1754
+ diff = "remove";
1755
+ }
1480
1756
  info = info.replace(RE_FOCUS, (_, matched) => {
1481
1757
  filename = matched;
1482
1758
  focus = true;
@@ -1493,7 +1769,7 @@ function parseFileTreeNodeInfo(info) {
1493
1769
  expanded = false;
1494
1770
  filename = removeEndingSlash(filename);
1495
1771
  }
1496
- return { filename, comment, focus, expanded, type };
1772
+ return { filename, comment, focus, expanded, type, diff };
1497
1773
  }
1498
1774
  function fileTreePlugin(md, options = {}) {
1499
1775
  const getIcon = (filename, type, mode) => {
@@ -1504,7 +1780,7 @@ function fileTreePlugin(md, options = {}) {
1504
1780
  };
1505
1781
  const renderFileTree = (nodes, meta) => nodes.map((node) => {
1506
1782
  const { info, level, children } = node;
1507
- const { filename, comment, focus, expanded, type } = parseFileTreeNodeInfo(info);
1783
+ const { filename, comment, focus, expanded, type, diff } = parseFileTreeNodeInfo(info);
1508
1784
  const isOmit = filename === "\u2026" || filename === "...";
1509
1785
  if (children.length === 0 && type === "folder") {
1510
1786
  children.push({ info: "\u2026", level: level + 1, children: [] });
@@ -1516,7 +1792,9 @@ function fileTreePlugin(md, options = {}) {
1516
1792
  expanded: nodeType === "folder" ? expanded : false,
1517
1793
  focus,
1518
1794
  type: nodeType,
1519
- filename
1795
+ diff,
1796
+ filename,
1797
+ level
1520
1798
  };
1521
1799
  return `<FileTreeNode${stringifyAttrs(props)}>
1522
1800
  ${renderedIcon}${renderedComment}${children.length > 0 ? renderFileTree(children, meta) : ""}
@@ -1536,9 +1814,9 @@ ${renderedIcon}${renderedComment}${children.length > 0 ? renderFileTree(children
1536
1814
  }
1537
1815
 
1538
1816
  // src/node/container/langRepl.ts
1539
- import { promises as fs2 } from "node:fs";
1817
+ import { promises as fs3 } from "node:fs";
1540
1818
  import { resolveModule } from "local-pkg";
1541
- import { colors, logger as logger2, path as path2 } from "vuepress/utils";
1819
+ import { colors, logger as logger2, path as path4 } from "vuepress/utils";
1542
1820
  async function langReplPlugin(app, md, {
1543
1821
  theme,
1544
1822
  go = false,
@@ -1562,10 +1840,10 @@ async function langReplPlugin(app, md, {
1562
1840
  theme ??= { light: "github-light", dark: "github-dark" };
1563
1841
  const data = { grammars: {} };
1564
1842
  try {
1565
- const themesPath = path2.dirname(resolveModule("tm-themes"));
1566
- const grammarsPath = path2.dirname(resolveModule("tm-grammars"));
1567
- const readTheme = (theme2) => read(path2.join(themesPath, "themes", `${theme2}.json`));
1568
- const readGrammar = (grammar) => read(path2.join(grammarsPath, "grammars", `${grammar}.json`));
1843
+ const themesPath = path4.dirname(resolveModule("tm-themes"));
1844
+ const grammarsPath = path4.dirname(resolveModule("tm-grammars"));
1845
+ const readTheme = (theme2) => read(path4.join(themesPath, "themes", `${theme2}.json`));
1846
+ const readGrammar = (grammar) => read(path4.join(grammarsPath, "grammars", `${grammar}.json`));
1569
1847
  if (typeof theme === "string") {
1570
1848
  data.theme = await readTheme(theme);
1571
1849
  } else {
@@ -1590,7 +1868,7 @@ async function langReplPlugin(app, md, {
1590
1868
  }
1591
1869
  async function read(file) {
1592
1870
  try {
1593
- const content = await fs2.readFile(file, "utf-8");
1871
+ const content = await fs3.readFile(file, "utf-8");
1594
1872
  return JSON.parse(content);
1595
1873
  } catch {
1596
1874
  }
@@ -2089,96 +2367,22 @@ async function containerPlugin(app, md, options) {
2089
2367
  if (options.fileTree) {
2090
2368
  fileTreePlugin(md, isPlainObject2(options.fileTree) ? options.fileTree : {});
2091
2369
  }
2370
+ if (options.codeTree) {
2371
+ codeTreePlugin(md, app, isPlainObject2(options.codeTree) ? options.codeTree : {});
2372
+ }
2092
2373
  if (options.timeline)
2093
2374
  timelinePlugin(md);
2094
2375
  if (options.collapse)
2095
2376
  collapsePlugin(md);
2096
2377
  if (options.chat)
2097
2378
  chatPlugin(md);
2379
+ if (options.field)
2380
+ fieldPlugin(md);
2098
2381
  }
2099
2382
 
2100
2383
  // src/node/demo/demo.ts
2101
2384
  import container2 from "markdown-it-container";
2102
2385
 
2103
- // src/node/embed/createEmbedRuleBlock.ts
2104
- function createEmbedRuleBlock(md, {
2105
- type,
2106
- name = type,
2107
- syntaxPattern,
2108
- beforeName = "import_code",
2109
- ruleOptions = { alt: ["paragraph", "reference", "blockquote", "list"] },
2110
- meta,
2111
- content
2112
- }) {
2113
- const MIN_LENGTH = type.length + 5;
2114
- const START_CODES = [64, 91, ...type.split("").map((c) => c.charCodeAt(0))];
2115
- md.block.ruler.before(
2116
- beforeName,
2117
- name,
2118
- (state, startLine, endLine, silent) => {
2119
- const pos = state.bMarks[startLine] + state.tShift[startLine];
2120
- const max = state.eMarks[startLine];
2121
- if (pos + MIN_LENGTH > max)
2122
- return false;
2123
- for (let i = 0; i < START_CODES.length; i += 1) {
2124
- if (state.src.charCodeAt(pos + i) !== START_CODES[i])
2125
- return false;
2126
- }
2127
- const content2 = state.src.slice(pos, max);
2128
- const match = content2.match(syntaxPattern);
2129
- if (!match)
2130
- return false;
2131
- if (silent)
2132
- return true;
2133
- const token = state.push(name, "", 0);
2134
- token.meta = meta(match);
2135
- token.content = content2;
2136
- token.map = [startLine, startLine + 1];
2137
- state.line = startLine + 1;
2138
- return true;
2139
- },
2140
- ruleOptions
2141
- );
2142
- md.renderer.rules[name] = (tokens, index, _, env) => {
2143
- const token = tokens[index];
2144
- token.content = content(token.meta, token.content, env);
2145
- return token.content;
2146
- };
2147
- }
2148
-
2149
- // src/node/demo/supports/file.ts
2150
- import fs3 from "node:fs";
2151
- import { createRequire } from "node:module";
2152
- import path3 from "node:path";
2153
- import process from "node:process";
2154
- var require2 = createRequire(process.cwd());
2155
- function findFile(app, env, url) {
2156
- if (url.startsWith("/"))
2157
- return app.dir.source(url.slice(1));
2158
- if (url.startsWith("./") || url.startsWith("../"))
2159
- return app.dir.source(path3.dirname(env.filePathRelative), url);
2160
- if (url.startsWith("@source/")) {
2161
- return app.dir.source(url.slice("@source/".length));
2162
- }
2163
- try {
2164
- return require2.resolve(url);
2165
- } catch {
2166
- return url;
2167
- }
2168
- }
2169
- function readFileSync(filepath2) {
2170
- try {
2171
- return fs3.readFileSync(filepath2, "utf-8");
2172
- } catch {
2173
- return false;
2174
- }
2175
- }
2176
- function writeFileSync(filepath2, content) {
2177
- const dirname = path3.dirname(filepath2);
2178
- fs3.mkdirSync(dirname, { recursive: true });
2179
- fs3.writeFileSync(filepath2, content, "utf-8");
2180
- }
2181
-
2182
2386
  // src/node/demo/markdown.ts
2183
2387
  function markdownEmbed(app, md, env, { url, title, desc, codeSetting = "", expanded = false }) {
2184
2388
  const filepath2 = findFile(app, env, url);
@@ -2214,7 +2418,7 @@ var markdownContainerRender = {
2214
2418
 
2215
2419
  // src/node/demo/normal.ts
2216
2420
  import fs5 from "node:fs";
2217
- import path5 from "node:path";
2421
+ import path6 from "node:path";
2218
2422
 
2219
2423
  // src/node/demo/supports/compiler.ts
2220
2424
  import { isPackageExists } from "local-pkg";
@@ -2295,8 +2499,8 @@ function importer(func) {
2295
2499
 
2296
2500
  // src/node/demo/supports/insertScript.ts
2297
2501
  var SCRIPT_RE = /<script.*?>/;
2298
- function insertSetupScript({ export: name, path: path9 }, env) {
2299
- const imports = `import ${name ? `${name} from ` : ""}'${path9}';`;
2502
+ function insertSetupScript({ export: name, path: path10 }, env) {
2503
+ const imports = `import ${name ? `${name} from ` : ""}'${path10}';`;
2300
2504
  const scriptSetup = env.sfcBlocks.scriptSetup ??= {
2301
2505
  type: "script",
2302
2506
  content: "<script setup>\n</script>",
@@ -2312,7 +2516,7 @@ ${imports}`);
2312
2516
 
2313
2517
  // src/node/demo/watcher.ts
2314
2518
  import fs4 from "node:fs";
2315
- import path4 from "node:path";
2519
+ import path5 from "node:path";
2316
2520
  import { watch } from "chokidar";
2317
2521
  var renderDone = null;
2318
2522
  var renderCount = 0;
@@ -2348,30 +2552,30 @@ function demoWatcher(app, watchers) {
2348
2552
  if (!watcher) {
2349
2553
  watcher = watch([], { ignoreInitial: true });
2350
2554
  }
2351
- Object.keys(tasks).forEach((path9) => {
2352
- watcher.add(path9);
2555
+ Object.keys(tasks).forEach((path10) => {
2556
+ watcher.add(path10);
2353
2557
  });
2354
2558
  const code = readFileSync(app.dir.temp(target));
2355
2559
  if (code) {
2356
2560
  const paths = JSON.parse(code || "{}");
2357
- Object.entries(paths).forEach(([path9, output]) => {
2358
- watcher.add(path9);
2359
- tasks[path9] = output;
2561
+ Object.entries(paths).forEach(([path10, output]) => {
2562
+ watcher.add(path10);
2563
+ tasks[path10] = output;
2360
2564
  });
2361
2565
  }
2362
2566
  updateWatchFiles(app);
2363
- watcher.on("change", (path9) => {
2364
- if (tasks[path9]) {
2365
- const code2 = readFileSync(path9);
2567
+ watcher.on("change", (path10) => {
2568
+ if (tasks[path10]) {
2569
+ const code2 = readFileSync(path10);
2366
2570
  if (code2 === false)
2367
2571
  return;
2368
2572
  const source = parseEmbedCode(code2);
2369
- compileCode(source, tasks[path9]);
2573
+ compileCode(source, tasks[path10]);
2370
2574
  }
2371
2575
  });
2372
- watcher.on("unlink", (path9) => {
2373
- delete tasks[path9];
2374
- watcher.unwatch(path9);
2576
+ watcher.on("unlink", (path10) => {
2577
+ delete tasks[path10];
2578
+ watcher.unwatch(path10);
2375
2579
  });
2376
2580
  watchers.push({
2377
2581
  close: () => {
@@ -2380,17 +2584,17 @@ function demoWatcher(app, watchers) {
2380
2584
  }
2381
2585
  });
2382
2586
  }
2383
- function addTask(app, path9, output) {
2384
- if (tasks[path9])
2587
+ function addTask(app, path10, output) {
2588
+ if (tasks[path10])
2385
2589
  return;
2386
- tasks[path9] = output;
2590
+ tasks[path10] = output;
2387
2591
  if (watcher) {
2388
- watcher.add(path9);
2592
+ watcher.add(path10);
2389
2593
  }
2390
2594
  updateWatchFiles(app);
2391
2595
  }
2392
2596
  async function updateWatchFiles(app) {
2393
- await fs4.promises.mkdir(app.dir.temp(path4.dirname(target)), { recursive: true });
2597
+ await fs4.promises.mkdir(app.dir.temp(path5.dirname(target)), { recursive: true });
2394
2598
  await fs4.promises.writeFile(app.dir.temp(target), JSON.stringify(tasks));
2395
2599
  }
2396
2600
 
@@ -2482,7 +2686,7 @@ function normalEmbed(app, md, env, { url, title, desc, codeSetting = "", expande
2482
2686
  }
2483
2687
  const source = parseEmbedCode(code);
2484
2688
  const prefix = (env.filePathRelative || "").replace(/\.md$/, "").replace(/\//g, "-");
2485
- const basename = path5.basename(filepath2).replace(/-|\./g, "_");
2689
+ const basename = path6.basename(filepath2).replace(/-|\./g, "_");
2486
2690
  const name = `Demo${basename[0].toUpperCase()}${basename.slice(1)}`;
2487
2691
  const demo = { type: "normal", export: name, path: filepath2 };
2488
2692
  const output = app.dir.temp(target2, `${prefix}-${name}.js`);
@@ -2502,7 +2706,7 @@ var normalContainerRender = {
2502
2706
  const { url, title, desc, expanded = false } = meta;
2503
2707
  const name = `DemoContainer${url}`;
2504
2708
  const prefix = (env.filePathRelative || "").replace(/\.md$/, "").replace(/\//g, "-");
2505
- const output = app.dir.temp(path5.join(target2, `${prefix}-${name}.js`));
2709
+ const output = app.dir.temp(path6.join(target2, `${prefix}-${name}.js`));
2506
2710
  env.demoFiles ??= [];
2507
2711
  if (!env.demoFiles.some((d) => d.path === output)) {
2508
2712
  const demo = { type: "normal", export: name, gitignore: true, path: output };
@@ -2580,7 +2784,7 @@ function normalizeAlias(info) {
2580
2784
  }
2581
2785
 
2582
2786
  // src/node/demo/vue.ts
2583
- import path6 from "node:path";
2787
+ import path7 from "node:path";
2584
2788
  function vueEmbed(app, md, env, { url, title, desc, codeSetting = "", expanded = false }) {
2585
2789
  const filepath2 = findFile(app, env, url);
2586
2790
  const code = readFileSync(filepath2);
@@ -2588,8 +2792,8 @@ function vueEmbed(app, md, env, { url, title, desc, codeSetting = "", expanded =
2588
2792
  console.warn("[vuepress-plugin-md-power] Cannot read vue file:", filepath2);
2589
2793
  return "";
2590
2794
  }
2591
- const basename = path6.basename(filepath2).replace(/-|\./g, "_");
2592
- const ext = path6.extname(filepath2).slice(1);
2795
+ const basename = path7.basename(filepath2).replace(/-|\./g, "_");
2796
+ const ext = path7.extname(filepath2).slice(1);
2593
2797
  const name = `Demo${basename[0].toUpperCase()}${basename.slice(1)}`;
2594
2798
  const demo = { type: "vue", export: name, path: filepath2 };
2595
2799
  env.demoFiles ??= [];
@@ -2613,7 +2817,7 @@ var vueContainerRender = {
2613
2817
  const componentName = `DemoContainer${url}`;
2614
2818
  const prefix = (env.filePathRelative || "").replace(/\.md$/, "").replace(/\//g, "-");
2615
2819
  env.demoFiles ??= [];
2616
- const output = app.dir.temp(path6.join(target3, `${prefix}-${componentName}`));
2820
+ const output = app.dir.temp(path7.join(target3, `${prefix}-${componentName}`));
2617
2821
  if (codeMap.vue || codeMap.js || codeMap.ts) {
2618
2822
  let scriptOutput = output;
2619
2823
  let content = "";
@@ -2670,7 +2874,7 @@ var STYLE_RE2 = /<style.*?>/;
2670
2874
  function transformImports(code, filepath2) {
2671
2875
  return code.replace(IMPORT_RE, (matched, url) => {
2672
2876
  if (url.startsWith("./") || url.startsWith("../")) {
2673
- return matched.replace(url, `${path6.resolve(path6.dirname(filepath2), url)}`);
2877
+ return matched.replace(url, `${path7.resolve(path7.dirname(filepath2), url)}`);
2674
2878
  }
2675
2879
  return matched;
2676
2880
  });
@@ -2768,10 +2972,10 @@ function extendsPageWithDemo(page) {
2768
2972
  const markdownEnv = page.markdownEnv;
2769
2973
  const demoFiles = markdownEnv.demoFiles ?? [];
2770
2974
  page.deps.push(
2771
- ...demoFiles.filter(({ type }) => type === "markdown").map(({ path: path9 }) => path9)
2975
+ ...demoFiles.filter(({ type }) => type === "markdown").map(({ path: path10 }) => path10)
2772
2976
  );
2773
2977
  (page.frontmatter.gitInclude ??= []).push(
2774
- ...demoFiles.filter(({ gitignore }) => !gitignore).map(({ path: path9 }) => path9)
2978
+ ...demoFiles.filter(({ gitignore }) => !gitignore).map(({ path: path10 }) => path10)
2775
2979
  );
2776
2980
  }
2777
2981
 
@@ -2904,13 +3108,6 @@ function resolveVersions(versions) {
2904
3108
  };
2905
3109
  }
2906
3110
 
2907
- // src/node/utils/parseRect.ts
2908
- function parseRect(str, unit = "px") {
2909
- if (Number.parseFloat(str) === Number(str))
2910
- return `${str}${unit}`;
2911
- return str;
2912
- }
2913
-
2914
3111
  // src/node/embed/code/codepen.ts
2915
3112
  var codepenPlugin = (md) => {
2916
3113
  createEmbedRuleBlock(md, {
@@ -3001,7 +3198,7 @@ var replitPlugin = (md) => {
3001
3198
  };
3002
3199
 
3003
3200
  // src/node/embed/pdf.ts
3004
- import { path as path7 } from "vuepress/utils";
3201
+ import { path as path8 } from "vuepress/utils";
3005
3202
  var pdfPlugin = (md) => {
3006
3203
  createEmbedRuleBlock(md, {
3007
3204
  type: "pdf",
@@ -3017,7 +3214,7 @@ var pdfPlugin = (md) => {
3017
3214
  width: attrs2.width ? parseRect(attrs2.width) : "100%",
3018
3215
  height: attrs2.height ? parseRect(attrs2.height) : "",
3019
3216
  ratio: attrs2.ratio ? parseRect(attrs2.ratio) : "",
3020
- title: path7.basename(src || "")
3217
+ title: path8.basename(src || "")
3021
3218
  };
3022
3219
  },
3023
3220
  content: (meta) => `<PDFViewer${stringifyAttrs(meta)} />`
@@ -3622,11 +3819,11 @@ function inlineSyntaxPlugin(md, options) {
3622
3819
 
3623
3820
  // src/node/prepareConfigFile.ts
3624
3821
  import { ensureEndingSlash } from "@vuepress/helper";
3625
- import { getDirname, path as path8 } from "vuepress/utils";
3822
+ import { getDirname, path as path9 } from "vuepress/utils";
3626
3823
  var { url: filepath } = import.meta;
3627
3824
  var __dirname = getDirname(filepath);
3628
3825
  var CLIENT_FOLDER = ensureEndingSlash(
3629
- path8.resolve(__dirname, "../client")
3826
+ path9.resolve(__dirname, "../client")
3630
3827
  );
3631
3828
  async function prepareConfigFile(app, options) {
3632
3829
  const imports = /* @__PURE__ */ new Set();
@@ -3675,10 +3872,14 @@ async function prepareConfigFile(app, options) {
3675
3872
  imports.add(`import CanIUse from '${CLIENT_FOLDER}components/CanIUse.vue'`);
3676
3873
  enhances.add(`app.component('CanIUseViewer', CanIUse)`);
3677
3874
  }
3678
- if (options.fileTree) {
3875
+ if (options.fileTree || options.codeTree) {
3679
3876
  imports.add(`import FileTreeNode from '${CLIENT_FOLDER}components/FileTreeNode.vue'`);
3680
3877
  enhances.add(`app.component('FileTreeNode', FileTreeNode)`);
3681
3878
  }
3879
+ if (options.codeTree) {
3880
+ imports.add(`import VPCodeTree from '${CLIENT_FOLDER}components/VPCodeTree.vue'`);
3881
+ enhances.add(`app.component('VPCodeTree', VPCodeTree)`);
3882
+ }
3682
3883
  if (options.artPlayer) {
3683
3884
  imports.add(`import ArtPlayer from '${CLIENT_FOLDER}components/ArtPlayer.vue'`);
3684
3885
  enhances.add(`app.component('ArtPlayer', ArtPlayer)`);
@@ -3716,6 +3917,10 @@ async function prepareConfigFile(app, options) {
3716
3917
  if (options.chat) {
3717
3918
  imports.add(`import '${CLIENT_FOLDER}styles/chat.css'`);
3718
3919
  }
3920
+ if (options.field) {
3921
+ imports.add(`import VPField from '${CLIENT_FOLDER}components/VPField.vue'`);
3922
+ enhances.add(`app.component('VPField', VPField)`);
3923
+ }
3719
3924
  return app.writeTemp(
3720
3925
  "md-power/config.js",
3721
3926
  `import { defineClientConfig } from 'vuepress/client'
@@ -3780,6 +3985,8 @@ function markdownPowerPlugin(options = {}) {
3780
3985
  extendsPage: (page) => {
3781
3986
  if (options.demo)
3782
3987
  extendsPageWithDemo(page);
3988
+ if (options.codeTree)
3989
+ extendsPageWithCodeTree(page);
3783
3990
  }
3784
3991
  };
3785
3992
  }
@@ -152,6 +152,11 @@ interface PlotOptions {
152
152
  trigger?: 'hover' | 'click';
153
153
  }
154
154
 
155
+ interface CodeTreeOptions {
156
+ icon?: FileTreeIconMode;
157
+ height?: string | number;
158
+ }
159
+
155
160
  type ThemeOptions = BuiltinTheme | {
156
161
  light: BuiltinTheme;
157
162
  dark: BuiltinTheme;
@@ -267,6 +272,12 @@ interface MarkdownPowerPluginOptions {
267
272
  * @default false
268
273
  */
269
274
  chat?: boolean;
275
+ /**
276
+ * 是否启用 field / field-group 容器
277
+ *
278
+ * @default false
279
+ */
280
+ field?: boolean;
270
281
  /**
271
282
  * 是否启用 bilibili 视频嵌入
272
283
  *
@@ -335,6 +346,20 @@ interface MarkdownPowerPluginOptions {
335
346
  * @default false
336
347
  */
337
348
  fileTree?: boolean | FileTreeOptions;
349
+ /**
350
+ * 是否启用 代码树 容器语法 和 嵌入语法
351
+ *
352
+ * ```md
353
+ * ::: code-tree
354
+ * :::
355
+ * ```
356
+ *
357
+ * `@[code-tree](file_path)`
358
+ *
359
+ *
360
+ * @default false
361
+ */
362
+ codeTree?: boolean | CodeTreeOptions;
338
363
  /**
339
364
  * 是否启用 demo 语法
340
365
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vuepress-plugin-md-power",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.144",
4
+ "version": "1.0.0-rc.146",
5
5
  "description": "The Plugin for VuePress 2 - markdown power",
6
6
  "author": "pengzhanbo <volodymyr@foxmail.com>",
7
7
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "peerDependencies": {
34
34
  "artplayer": "^5.2.3",
35
35
  "dashjs": "^5.0.1",
36
- "esbuild": "^0.25.3",
36
+ "esbuild": "^0.25.4",
37
37
  "hls.js": "^1.6.2",
38
38
  "less": "^4.3.0",
39
39
  "markdown-it": "^14.1.0",
@@ -61,15 +61,15 @@
61
61
  }
62
62
  },
63
63
  "dependencies": {
64
- "@mdit/plugin-attrs": "^0.16.7",
65
- "@mdit/plugin-footnote": "^0.16.8",
66
- "@mdit/plugin-mark": "^0.16.0",
67
- "@mdit/plugin-sub": "^0.16.0",
68
- "@mdit/plugin-sup": "^0.16.0",
69
- "@mdit/plugin-tab": "^0.16.0",
70
- "@mdit/plugin-tasklist": "^0.16.0",
64
+ "@mdit/plugin-attrs": "^0.18.0",
65
+ "@mdit/plugin-footnote": "^0.18.0",
66
+ "@mdit/plugin-mark": "^0.18.0",
67
+ "@mdit/plugin-sub": "^0.18.0",
68
+ "@mdit/plugin-sup": "^0.18.0",
69
+ "@mdit/plugin-tab": "^0.18.0",
70
+ "@mdit/plugin-tasklist": "^0.18.0",
71
71
  "@pengzhanbo/utils": "^2.1.0",
72
- "@vuepress/helper": "2.0.0-rc.98",
72
+ "@vuepress/helper": "2.0.0-rc.99",
73
73
  "@vueuse/core": "^13.1.0",
74
74
  "chokidar": "3.6.0",
75
75
  "image-size": "^2.0.2",
@@ -78,6 +78,7 @@
78
78
  "markdown-it-container": "^4.0.0",
79
79
  "nanoid": "^5.1.5",
80
80
  "shiki": "^3.3.0",
81
+ "tinyglobby": "0.2.13",
81
82
  "tm-grammars": "^1.23.16",
82
83
  "tm-themes": "^1.10.5",
83
84
  "vue": "^3.5.13"