vuepress-plugin-md-power 1.0.0-rc.145 → 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.
@@ -9,6 +9,7 @@ const props = defineProps<{
9
9
  diff?: 'add' | 'remove'
10
10
  expanded?: boolean
11
11
  focus?: boolean
12
+ filepath?: string
12
13
  }>()
13
14
 
14
15
  const activeFileTreeNode = inject<Ref<string>>('active-file-tree-node', ref(''))
@@ -23,7 +24,7 @@ function nodeClick() {
23
24
  if (props.filename === '…' || props.filename === '...')
24
25
  return
25
26
 
26
- onNodeClick(props.filename, props.type)
27
+ onNodeClick(props.filepath || props.filename, props.type)
27
28
  }
28
29
 
29
30
  function toggle(ev: MouseEvent) {
@@ -47,7 +48,7 @@ function toggle(ev: MouseEvent) {
47
48
  [type]: true,
48
49
  focus,
49
50
  expanded: type === 'folder' ? active : false,
50
- active: type === 'file' ? activeFileTreeNode === filename : false,
51
+ active: type === 'file' ? activeFileTreeNode === filepath : false,
51
52
  diff,
52
53
  add: diff === 'add',
53
54
  remove: diff === 'remove',
@@ -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>
@@ -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;
@@ -342,6 +347,20 @@ interface MarkdownPowerPluginOptions {
342
347
  * @default false
343
348
  */
344
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;
345
364
  /**
346
365
  * 是否启用 demo 语法
347
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", {
@@ -1563,9 +1814,9 @@ ${renderedIcon}${renderedComment}${children.length > 0 ? renderFileTree(children
1563
1814
  }
1564
1815
 
1565
1816
  // src/node/container/langRepl.ts
1566
- import { promises as fs2 } from "node:fs";
1817
+ import { promises as fs3 } from "node:fs";
1567
1818
  import { resolveModule } from "local-pkg";
1568
- import { colors, logger as logger2, path as path2 } from "vuepress/utils";
1819
+ import { colors, logger as logger2, path as path4 } from "vuepress/utils";
1569
1820
  async function langReplPlugin(app, md, {
1570
1821
  theme,
1571
1822
  go = false,
@@ -1589,10 +1840,10 @@ async function langReplPlugin(app, md, {
1589
1840
  theme ??= { light: "github-light", dark: "github-dark" };
1590
1841
  const data = { grammars: {} };
1591
1842
  try {
1592
- const themesPath = path2.dirname(resolveModule("tm-themes"));
1593
- const grammarsPath = path2.dirname(resolveModule("tm-grammars"));
1594
- const readTheme = (theme2) => read(path2.join(themesPath, "themes", `${theme2}.json`));
1595
- 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`));
1596
1847
  if (typeof theme === "string") {
1597
1848
  data.theme = await readTheme(theme);
1598
1849
  } else {
@@ -1617,7 +1868,7 @@ async function langReplPlugin(app, md, {
1617
1868
  }
1618
1869
  async function read(file) {
1619
1870
  try {
1620
- const content = await fs2.readFile(file, "utf-8");
1871
+ const content = await fs3.readFile(file, "utf-8");
1621
1872
  return JSON.parse(content);
1622
1873
  } catch {
1623
1874
  }
@@ -2116,6 +2367,9 @@ async function containerPlugin(app, md, options) {
2116
2367
  if (options.fileTree) {
2117
2368
  fileTreePlugin(md, isPlainObject2(options.fileTree) ? options.fileTree : {});
2118
2369
  }
2370
+ if (options.codeTree) {
2371
+ codeTreePlugin(md, app, isPlainObject2(options.codeTree) ? options.codeTree : {});
2372
+ }
2119
2373
  if (options.timeline)
2120
2374
  timelinePlugin(md);
2121
2375
  if (options.collapse)
@@ -2129,85 +2383,6 @@ async function containerPlugin(app, md, options) {
2129
2383
  // src/node/demo/demo.ts
2130
2384
  import container2 from "markdown-it-container";
2131
2385
 
2132
- // src/node/embed/createEmbedRuleBlock.ts
2133
- function createEmbedRuleBlock(md, {
2134
- type,
2135
- name = type,
2136
- syntaxPattern,
2137
- beforeName = "import_code",
2138
- ruleOptions = { alt: ["paragraph", "reference", "blockquote", "list"] },
2139
- meta,
2140
- content
2141
- }) {
2142
- const MIN_LENGTH = type.length + 5;
2143
- const START_CODES = [64, 91, ...type.split("").map((c) => c.charCodeAt(0))];
2144
- md.block.ruler.before(
2145
- beforeName,
2146
- name,
2147
- (state, startLine, endLine, silent) => {
2148
- const pos = state.bMarks[startLine] + state.tShift[startLine];
2149
- const max = state.eMarks[startLine];
2150
- if (pos + MIN_LENGTH > max)
2151
- return false;
2152
- for (let i = 0; i < START_CODES.length; i += 1) {
2153
- if (state.src.charCodeAt(pos + i) !== START_CODES[i])
2154
- return false;
2155
- }
2156
- const content2 = state.src.slice(pos, max);
2157
- const match = content2.match(syntaxPattern);
2158
- if (!match)
2159
- return false;
2160
- if (silent)
2161
- return true;
2162
- const token = state.push(name, "", 0);
2163
- token.meta = meta(match);
2164
- token.content = content2;
2165
- token.map = [startLine, startLine + 1];
2166
- state.line = startLine + 1;
2167
- return true;
2168
- },
2169
- ruleOptions
2170
- );
2171
- md.renderer.rules[name] = (tokens, index, _, env) => {
2172
- const token = tokens[index];
2173
- token.content = content(token.meta, token.content, env);
2174
- return token.content;
2175
- };
2176
- }
2177
-
2178
- // src/node/demo/supports/file.ts
2179
- import fs3 from "node:fs";
2180
- import { createRequire } from "node:module";
2181
- import path3 from "node:path";
2182
- import process from "node:process";
2183
- var require2 = createRequire(process.cwd());
2184
- function findFile(app, env, url) {
2185
- if (url.startsWith("/"))
2186
- return app.dir.source(url.slice(1));
2187
- if (url.startsWith("./") || url.startsWith("../"))
2188
- return app.dir.source(path3.dirname(env.filePathRelative), url);
2189
- if (url.startsWith("@source/")) {
2190
- return app.dir.source(url.slice("@source/".length));
2191
- }
2192
- try {
2193
- return require2.resolve(url);
2194
- } catch {
2195
- return url;
2196
- }
2197
- }
2198
- function readFileSync(filepath2) {
2199
- try {
2200
- return fs3.readFileSync(filepath2, "utf-8");
2201
- } catch {
2202
- return false;
2203
- }
2204
- }
2205
- function writeFileSync(filepath2, content) {
2206
- const dirname = path3.dirname(filepath2);
2207
- fs3.mkdirSync(dirname, { recursive: true });
2208
- fs3.writeFileSync(filepath2, content, "utf-8");
2209
- }
2210
-
2211
2386
  // src/node/demo/markdown.ts
2212
2387
  function markdownEmbed(app, md, env, { url, title, desc, codeSetting = "", expanded = false }) {
2213
2388
  const filepath2 = findFile(app, env, url);
@@ -2243,7 +2418,7 @@ var markdownContainerRender = {
2243
2418
 
2244
2419
  // src/node/demo/normal.ts
2245
2420
  import fs5 from "node:fs";
2246
- import path5 from "node:path";
2421
+ import path6 from "node:path";
2247
2422
 
2248
2423
  // src/node/demo/supports/compiler.ts
2249
2424
  import { isPackageExists } from "local-pkg";
@@ -2324,8 +2499,8 @@ function importer(func) {
2324
2499
 
2325
2500
  // src/node/demo/supports/insertScript.ts
2326
2501
  var SCRIPT_RE = /<script.*?>/;
2327
- function insertSetupScript({ export: name, path: path9 }, env) {
2328
- const imports = `import ${name ? `${name} from ` : ""}'${path9}';`;
2502
+ function insertSetupScript({ export: name, path: path10 }, env) {
2503
+ const imports = `import ${name ? `${name} from ` : ""}'${path10}';`;
2329
2504
  const scriptSetup = env.sfcBlocks.scriptSetup ??= {
2330
2505
  type: "script",
2331
2506
  content: "<script setup>\n</script>",
@@ -2341,7 +2516,7 @@ ${imports}`);
2341
2516
 
2342
2517
  // src/node/demo/watcher.ts
2343
2518
  import fs4 from "node:fs";
2344
- import path4 from "node:path";
2519
+ import path5 from "node:path";
2345
2520
  import { watch } from "chokidar";
2346
2521
  var renderDone = null;
2347
2522
  var renderCount = 0;
@@ -2377,30 +2552,30 @@ function demoWatcher(app, watchers) {
2377
2552
  if (!watcher) {
2378
2553
  watcher = watch([], { ignoreInitial: true });
2379
2554
  }
2380
- Object.keys(tasks).forEach((path9) => {
2381
- watcher.add(path9);
2555
+ Object.keys(tasks).forEach((path10) => {
2556
+ watcher.add(path10);
2382
2557
  });
2383
2558
  const code = readFileSync(app.dir.temp(target));
2384
2559
  if (code) {
2385
2560
  const paths = JSON.parse(code || "{}");
2386
- Object.entries(paths).forEach(([path9, output]) => {
2387
- watcher.add(path9);
2388
- tasks[path9] = output;
2561
+ Object.entries(paths).forEach(([path10, output]) => {
2562
+ watcher.add(path10);
2563
+ tasks[path10] = output;
2389
2564
  });
2390
2565
  }
2391
2566
  updateWatchFiles(app);
2392
- watcher.on("change", (path9) => {
2393
- if (tasks[path9]) {
2394
- const code2 = readFileSync(path9);
2567
+ watcher.on("change", (path10) => {
2568
+ if (tasks[path10]) {
2569
+ const code2 = readFileSync(path10);
2395
2570
  if (code2 === false)
2396
2571
  return;
2397
2572
  const source = parseEmbedCode(code2);
2398
- compileCode(source, tasks[path9]);
2573
+ compileCode(source, tasks[path10]);
2399
2574
  }
2400
2575
  });
2401
- watcher.on("unlink", (path9) => {
2402
- delete tasks[path9];
2403
- watcher.unwatch(path9);
2576
+ watcher.on("unlink", (path10) => {
2577
+ delete tasks[path10];
2578
+ watcher.unwatch(path10);
2404
2579
  });
2405
2580
  watchers.push({
2406
2581
  close: () => {
@@ -2409,17 +2584,17 @@ function demoWatcher(app, watchers) {
2409
2584
  }
2410
2585
  });
2411
2586
  }
2412
- function addTask(app, path9, output) {
2413
- if (tasks[path9])
2587
+ function addTask(app, path10, output) {
2588
+ if (tasks[path10])
2414
2589
  return;
2415
- tasks[path9] = output;
2590
+ tasks[path10] = output;
2416
2591
  if (watcher) {
2417
- watcher.add(path9);
2592
+ watcher.add(path10);
2418
2593
  }
2419
2594
  updateWatchFiles(app);
2420
2595
  }
2421
2596
  async function updateWatchFiles(app) {
2422
- 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 });
2423
2598
  await fs4.promises.writeFile(app.dir.temp(target), JSON.stringify(tasks));
2424
2599
  }
2425
2600
 
@@ -2511,7 +2686,7 @@ function normalEmbed(app, md, env, { url, title, desc, codeSetting = "", expande
2511
2686
  }
2512
2687
  const source = parseEmbedCode(code);
2513
2688
  const prefix = (env.filePathRelative || "").replace(/\.md$/, "").replace(/\//g, "-");
2514
- const basename = path5.basename(filepath2).replace(/-|\./g, "_");
2689
+ const basename = path6.basename(filepath2).replace(/-|\./g, "_");
2515
2690
  const name = `Demo${basename[0].toUpperCase()}${basename.slice(1)}`;
2516
2691
  const demo = { type: "normal", export: name, path: filepath2 };
2517
2692
  const output = app.dir.temp(target2, `${prefix}-${name}.js`);
@@ -2531,7 +2706,7 @@ var normalContainerRender = {
2531
2706
  const { url, title, desc, expanded = false } = meta;
2532
2707
  const name = `DemoContainer${url}`;
2533
2708
  const prefix = (env.filePathRelative || "").replace(/\.md$/, "").replace(/\//g, "-");
2534
- const output = app.dir.temp(path5.join(target2, `${prefix}-${name}.js`));
2709
+ const output = app.dir.temp(path6.join(target2, `${prefix}-${name}.js`));
2535
2710
  env.demoFiles ??= [];
2536
2711
  if (!env.demoFiles.some((d) => d.path === output)) {
2537
2712
  const demo = { type: "normal", export: name, gitignore: true, path: output };
@@ -2609,7 +2784,7 @@ function normalizeAlias(info) {
2609
2784
  }
2610
2785
 
2611
2786
  // src/node/demo/vue.ts
2612
- import path6 from "node:path";
2787
+ import path7 from "node:path";
2613
2788
  function vueEmbed(app, md, env, { url, title, desc, codeSetting = "", expanded = false }) {
2614
2789
  const filepath2 = findFile(app, env, url);
2615
2790
  const code = readFileSync(filepath2);
@@ -2617,8 +2792,8 @@ function vueEmbed(app, md, env, { url, title, desc, codeSetting = "", expanded =
2617
2792
  console.warn("[vuepress-plugin-md-power] Cannot read vue file:", filepath2);
2618
2793
  return "";
2619
2794
  }
2620
- const basename = path6.basename(filepath2).replace(/-|\./g, "_");
2621
- const ext = path6.extname(filepath2).slice(1);
2795
+ const basename = path7.basename(filepath2).replace(/-|\./g, "_");
2796
+ const ext = path7.extname(filepath2).slice(1);
2622
2797
  const name = `Demo${basename[0].toUpperCase()}${basename.slice(1)}`;
2623
2798
  const demo = { type: "vue", export: name, path: filepath2 };
2624
2799
  env.demoFiles ??= [];
@@ -2642,7 +2817,7 @@ var vueContainerRender = {
2642
2817
  const componentName = `DemoContainer${url}`;
2643
2818
  const prefix = (env.filePathRelative || "").replace(/\.md$/, "").replace(/\//g, "-");
2644
2819
  env.demoFiles ??= [];
2645
- const output = app.dir.temp(path6.join(target3, `${prefix}-${componentName}`));
2820
+ const output = app.dir.temp(path7.join(target3, `${prefix}-${componentName}`));
2646
2821
  if (codeMap.vue || codeMap.js || codeMap.ts) {
2647
2822
  let scriptOutput = output;
2648
2823
  let content = "";
@@ -2699,7 +2874,7 @@ var STYLE_RE2 = /<style.*?>/;
2699
2874
  function transformImports(code, filepath2) {
2700
2875
  return code.replace(IMPORT_RE, (matched, url) => {
2701
2876
  if (url.startsWith("./") || url.startsWith("../")) {
2702
- return matched.replace(url, `${path6.resolve(path6.dirname(filepath2), url)}`);
2877
+ return matched.replace(url, `${path7.resolve(path7.dirname(filepath2), url)}`);
2703
2878
  }
2704
2879
  return matched;
2705
2880
  });
@@ -2797,10 +2972,10 @@ function extendsPageWithDemo(page) {
2797
2972
  const markdownEnv = page.markdownEnv;
2798
2973
  const demoFiles = markdownEnv.demoFiles ?? [];
2799
2974
  page.deps.push(
2800
- ...demoFiles.filter(({ type }) => type === "markdown").map(({ path: path9 }) => path9)
2975
+ ...demoFiles.filter(({ type }) => type === "markdown").map(({ path: path10 }) => path10)
2801
2976
  );
2802
2977
  (page.frontmatter.gitInclude ??= []).push(
2803
- ...demoFiles.filter(({ gitignore }) => !gitignore).map(({ path: path9 }) => path9)
2978
+ ...demoFiles.filter(({ gitignore }) => !gitignore).map(({ path: path10 }) => path10)
2804
2979
  );
2805
2980
  }
2806
2981
 
@@ -2933,13 +3108,6 @@ function resolveVersions(versions) {
2933
3108
  };
2934
3109
  }
2935
3110
 
2936
- // src/node/utils/parseRect.ts
2937
- function parseRect(str, unit = "px") {
2938
- if (Number.parseFloat(str) === Number(str))
2939
- return `${str}${unit}`;
2940
- return str;
2941
- }
2942
-
2943
3111
  // src/node/embed/code/codepen.ts
2944
3112
  var codepenPlugin = (md) => {
2945
3113
  createEmbedRuleBlock(md, {
@@ -3030,7 +3198,7 @@ var replitPlugin = (md) => {
3030
3198
  };
3031
3199
 
3032
3200
  // src/node/embed/pdf.ts
3033
- import { path as path7 } from "vuepress/utils";
3201
+ import { path as path8 } from "vuepress/utils";
3034
3202
  var pdfPlugin = (md) => {
3035
3203
  createEmbedRuleBlock(md, {
3036
3204
  type: "pdf",
@@ -3046,7 +3214,7 @@ var pdfPlugin = (md) => {
3046
3214
  width: attrs2.width ? parseRect(attrs2.width) : "100%",
3047
3215
  height: attrs2.height ? parseRect(attrs2.height) : "",
3048
3216
  ratio: attrs2.ratio ? parseRect(attrs2.ratio) : "",
3049
- title: path7.basename(src || "")
3217
+ title: path8.basename(src || "")
3050
3218
  };
3051
3219
  },
3052
3220
  content: (meta) => `<PDFViewer${stringifyAttrs(meta)} />`
@@ -3651,11 +3819,11 @@ function inlineSyntaxPlugin(md, options) {
3651
3819
 
3652
3820
  // src/node/prepareConfigFile.ts
3653
3821
  import { ensureEndingSlash } from "@vuepress/helper";
3654
- import { getDirname, path as path8 } from "vuepress/utils";
3822
+ import { getDirname, path as path9 } from "vuepress/utils";
3655
3823
  var { url: filepath } = import.meta;
3656
3824
  var __dirname = getDirname(filepath);
3657
3825
  var CLIENT_FOLDER = ensureEndingSlash(
3658
- path8.resolve(__dirname, "../client")
3826
+ path9.resolve(__dirname, "../client")
3659
3827
  );
3660
3828
  async function prepareConfigFile(app, options) {
3661
3829
  const imports = /* @__PURE__ */ new Set();
@@ -3704,10 +3872,14 @@ async function prepareConfigFile(app, options) {
3704
3872
  imports.add(`import CanIUse from '${CLIENT_FOLDER}components/CanIUse.vue'`);
3705
3873
  enhances.add(`app.component('CanIUseViewer', CanIUse)`);
3706
3874
  }
3707
- if (options.fileTree) {
3875
+ if (options.fileTree || options.codeTree) {
3708
3876
  imports.add(`import FileTreeNode from '${CLIENT_FOLDER}components/FileTreeNode.vue'`);
3709
3877
  enhances.add(`app.component('FileTreeNode', FileTreeNode)`);
3710
3878
  }
3879
+ if (options.codeTree) {
3880
+ imports.add(`import VPCodeTree from '${CLIENT_FOLDER}components/VPCodeTree.vue'`);
3881
+ enhances.add(`app.component('VPCodeTree', VPCodeTree)`);
3882
+ }
3711
3883
  if (options.artPlayer) {
3712
3884
  imports.add(`import ArtPlayer from '${CLIENT_FOLDER}components/ArtPlayer.vue'`);
3713
3885
  enhances.add(`app.component('ArtPlayer', ArtPlayer)`);
@@ -3813,6 +3985,8 @@ function markdownPowerPlugin(options = {}) {
3813
3985
  extendsPage: (page) => {
3814
3986
  if (options.demo)
3815
3987
  extendsPageWithDemo(page);
3988
+ if (options.codeTree)
3989
+ extendsPageWithCodeTree(page);
3816
3990
  }
3817
3991
  };
3818
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;
@@ -341,6 +346,20 @@ interface MarkdownPowerPluginOptions {
341
346
  * @default false
342
347
  */
343
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;
344
363
  /**
345
364
  * 是否启用 demo 语法
346
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.145",
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.17.0",
65
- "@mdit/plugin-footnote": "^0.17.0",
66
- "@mdit/plugin-mark": "^0.17.0",
67
- "@mdit/plugin-sub": "^0.17.0",
68
- "@mdit/plugin-sup": "^0.17.0",
69
- "@mdit/plugin-tab": "^0.17.0",
70
- "@mdit/plugin-tasklist": "^0.17.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"