vitepress-plugin-file-tree 0.1.0 → 0.2.0

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.
package/README.md CHANGED
@@ -117,3 +117,24 @@ Use `::: file-tree` container to create a file tree.
117
117
  | `- …` or `- ...` | Placeholder (non-clickable) |
118
118
 
119
119
  Indentation uses 2 spaces per level. A copy button is included to copy the file tree as command-line style text.
120
+
121
+ ### Tree Fenced Code Syntax
122
+
123
+ You can also use a fenced code block with the `tree` or `file-tree` language identifier,
124
+ which accepts content in the format of the `tree` command-line tool output:
125
+
126
+ ````md
127
+ ```tree
128
+ .
129
+ ├── src/
130
+ │ ├── components/
131
+ │ │ ├── Button.vue
132
+ │ │ └── Input.vue
133
+ │ └── index.ts
134
+ ├── package.json
135
+ └── README.md
136
+ ```
137
+ ````
138
+
139
+ The fenced code syntax supports the same node annotations as the container syntax
140
+ (`**filename**`, `# comment`, `++/--`, trailing `/` for folders).
@@ -1,5 +1,5 @@
1
1
  import "../style.css";
2
- import { createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, inject, mergeProps, normalizeClass, openBlock, ref, renderSlot, toDisplayString, unref, vShow, withDirectives } from "vue";
2
+ import { computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, inject, mergeProps, normalizeClass, openBlock, ref, renderSlot, toDisplayString, unref, vShow, withDirectives } from "vue";
3
3
  import { VPCopyButton } from "vitepress-plugin-toolkit/client";
4
4
  //#region src/client/VPFileTree.vue
5
5
  const _hoisted_1$1 = { class: "vp-file-tree has-copy" };
@@ -14,10 +14,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
14
14
  text: {}
15
15
  },
16
16
  setup(__props) {
17
+ const content = computed(() => decodeURIComponent(__props.text));
17
18
  return (_ctx, _cache) => {
18
19
  return openBlock(), createElementBlock("div", _hoisted_1$1, [
19
20
  __props.title ? (openBlock(), createElementBlock("p", _hoisted_2$1, toDisplayString(__props.title), 1)) : createCommentVNode("v-if", true),
20
- createVNode(unref(VPCopyButton), { text: __props.text }, null, 8, ["text"]),
21
+ createVNode(unref(VPCopyButton), { text: content.value }, null, 8, ["text"]),
21
22
  renderSlot(_ctx.$slots, "default")
22
23
  ]);
23
24
  };
@@ -1,4 +1,4 @@
1
- import { defineComponent, inject, mergeProps, ref, unref, useSSRContext } from "vue";
1
+ import { computed, defineComponent, inject, mergeProps, ref, unref, useSSRContext } from "vue";
2
2
  import { ssrInterpolate, ssrRenderAttr, ssrRenderAttrs, ssrRenderClass, ssrRenderComponent, ssrRenderSlot, ssrRenderStyle } from "vue/server-renderer";
3
3
  import { VPCopyButton } from "vitepress-plugin-toolkit/client";
4
4
  //#region src/client/VPFileTree.vue
@@ -10,11 +10,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
10
10
  text: {}
11
11
  },
12
12
  setup(__props) {
13
+ const content = computed(() => decodeURIComponent(__props.text));
13
14
  return (_ctx, _push, _parent, _attrs) => {
14
15
  _push(`<div${ssrRenderAttrs(mergeProps({ class: "vp-file-tree has-copy" }, _attrs))}>`);
15
16
  if (__props.title) _push(`<p class="vp-file-tree-title">${ssrInterpolate(__props.title)}</p>`);
16
17
  else _push(`<!---->`);
17
- _push(ssrRenderComponent(unref(VPCopyButton), { text: __props.text }, null, _parent));
18
+ _push(ssrRenderComponent(unref(VPCopyButton), { text: content.value }, null, _parent));
18
19
  ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent);
19
20
  _push(`</div>`);
20
21
  };
@@ -1,6 +1,23 @@
1
1
  import { PluginSimple } from "markdown-it";
2
2
 
3
3
  //#region src/node/fileTreePlugin.d.ts
4
+ /**
5
+ * @example
6
+ * ```ts
7
+ * import { fileTreeMarkdownPlugin } from 'vitepress-plugin-file-tree'
8
+ * import { defineConfig } from 'vitepress'
9
+ * export default defineConfig({
10
+ * markdown: {
11
+ * config: (md) => {
12
+ * md.use(fileTreeMarkdownPlugin)
13
+ * },
14
+ * },
15
+ * })
16
+ * ```
17
+ */
18
+ declare const fileTreeMarkdownPlugin: PluginSimple;
19
+ //#endregion
20
+ //#region src/node/types.d.ts
4
21
  /**
5
22
  * File tree node structure
6
23
  *
@@ -24,15 +41,52 @@ interface FileTreeNode {
24
41
  interface FileTreeAttrs {
25
42
  title?: string;
26
43
  }
44
+ //#endregion
45
+ //#region src/node/container.d.ts
27
46
  /**
28
- * Parse raw file tree content to node tree structure
47
+ * Parse the content from the `::: file-tree` container into a node tree structure.
29
48
  *
30
- * 解析原始文件树内容为节点树结构
49
+ * 从 `::: file-tree` 容器中解析内容为节点树结构
31
50
  *
32
51
  * @param content - Raw file tree text content / 文件树的原始文本内容
33
52
  * @returns File tree node array / 文件树节点数组
34
53
  */
35
- declare function parseFileTreeRawContent(content: string): FileTreeNode[];
54
+ declare function parseContentWithContainer(content: string): FileTreeNode[];
55
+ /**
56
+ * Convert file tree to command line text format
57
+ *
58
+ * 将文件树转换为命令行文本格式
59
+ *
60
+ * @param nodes - File tree nodes / 文件树节点
61
+ * @param prefix - Line prefix / 行前缀
62
+ * @returns CMD text / CMD 文本
63
+ */
64
+ declare function fileTreeToCMDText(nodes: FileTreeNode[], prefix?: string): string;
65
+ //#endregion
66
+ //#region src/node/fence.d.ts
67
+ /**
68
+ * Parse `tree` command output format into a structured file tree node array.
69
+ *
70
+ * Converts text like:
71
+ * ```
72
+ * .
73
+ * ├── src
74
+ * │ ├── index.ts
75
+ * │ └── utils.ts
76
+ * └── package.json # project config
77
+ * ```
78
+ *
79
+ * into a `FileTreeNode[]` tree structure, with support for inline comments
80
+ * (text after `#`) on each entry.
81
+ *
82
+ * 将 `tree` 命令行输出格式解析为结构化的文件树节点数组
83
+ *
84
+ * @param content - Raw `tree` command output text / `tree` 命令输出的原始文本
85
+ * @returns Structured file tree node array / 结构化的文件树节点数组
86
+ */
87
+ declare function parseContentWithFence(content: string): FileTreeNode[];
88
+ //#endregion
89
+ //#region src/node/parseNodeInfo.d.ts
36
90
  /**
37
91
  * Parse single node info string, extract filename, comment, type, etc.
38
92
  *
@@ -41,24 +95,9 @@ declare function parseFileTreeRawContent(content: string): FileTreeNode[];
41
95
  * @param info - Node description string / 节点描述字符串
42
96
  * @returns File tree node props / 文件树节点属性
43
97
  */
44
- declare function parseFileTreeNodeInfo(info: string): Omit<FileTreeNode, 'children' | 'level'>;
45
- /**
46
- * @example
47
- * ```ts
48
- * import { fileTreeMarkdownPlugin } from 'vitepress-plugin-file-tree'
49
- * import { defineConfig } from 'vitepress'
50
- * export default defineConfig({
51
- * markdown: {
52
- * config: (md) => {
53
- * md.use(fileTreeMarkdownPlugin)
54
- * },
55
- * },
56
- * })
57
- * ```
58
- */
59
- declare const fileTreeMarkdownPlugin: PluginSimple;
98
+ declare function parseNodeInfo(info: string): Omit<FileTreeNode, 'children' | 'level'>;
60
99
  //#endregion
61
100
  //#region src/node/index.d.ts
62
101
  declare const _default: (option?: unknown) => import("vitepress-tuck").VitepressPlugin;
63
102
  //#endregion
64
- export { type FileTreeAttrs, type FileTreeNode, _default as default, fileTreeMarkdownPlugin, parseFileTreeNodeInfo, parseFileTreeRawContent };
103
+ export { FileTreeAttrs, FileTreeNode, _default as default, fileTreeMarkdownPlugin, fileTreeToCMDText, parseContentWithContainer, parseContentWithFence, parseNodeInfo };
@@ -1,40 +1,7 @@
1
1
  import { definePlugin } from "vitepress-tuck";
2
- import { removeTrailingSlash } from "@pengzhanbo/utils";
3
2
  import { createContainerSyntaxPlugin, stringifyAttrs } from "vitepress-plugin-toolkit";
4
- //#region src/node/fileTreePlugin.ts
5
- /**
6
- * Parse raw file tree content to node tree structure
7
- *
8
- * 解析原始文件树内容为节点树结构
9
- *
10
- * @param content - Raw file tree text content / 文件树的原始文本内容
11
- * @returns File tree node array / 文件树节点数组
12
- */
13
- function parseFileTreeRawContent(content) {
14
- const root = {
15
- level: -1,
16
- children: []
17
- };
18
- const stack = [root];
19
- const lines = content.trimEnd().split("\n");
20
- const spaceLength = lines[0]?.match(/^\s*/)?.[0].length ?? 0;
21
- for (const line of lines) {
22
- const match = line.match(/^(\s*)-(.*)$/);
23
- if (!match) continue;
24
- const level = Math.floor((match[1].length - spaceLength) / 2);
25
- const info = match[2].trim();
26
- while (stack.length > 0 && stack[stack.length - 1].level >= level) stack.pop();
27
- const parent = stack[stack.length - 1];
28
- const node = {
29
- level,
30
- children: [],
31
- ...parseFileTreeNodeInfo(info)
32
- };
33
- parent.children.push(node);
34
- stack.push(node);
35
- }
36
- return root.children;
37
- }
3
+ import { removeTrailingSlash } from "@pengzhanbo/utils";
4
+ //#region src/node/parseNodeInfo.ts
38
5
  /**
39
6
  * Regex for focus marker
40
7
  *
@@ -49,7 +16,7 @@ const RE_FOCUS = /^\*\*(.*)\*\*(?:$|\s+)/;
49
16
  * @param info - Node description string / 节点描述字符串
50
17
  * @returns File tree node props / 文件树节点属性
51
18
  */
52
- function parseFileTreeNodeInfo(info) {
19
+ function parseNodeInfo(info) {
53
20
  let filename = "";
54
21
  let comment = "";
55
22
  let focus = false;
@@ -88,6 +55,41 @@ function parseFileTreeNodeInfo(info) {
88
55
  diff
89
56
  };
90
57
  }
58
+ //#endregion
59
+ //#region src/node/container.ts
60
+ /**
61
+ * Parse the content from the `::: file-tree` container into a node tree structure.
62
+ *
63
+ * 从 `::: file-tree` 容器中解析内容为节点树结构
64
+ *
65
+ * @param content - Raw file tree text content / 文件树的原始文本内容
66
+ * @returns File tree node array / 文件树节点数组
67
+ */
68
+ function parseContentWithContainer(content) {
69
+ const root = {
70
+ level: -1,
71
+ children: []
72
+ };
73
+ const stack = [root];
74
+ const lines = content.trimEnd().split("\n");
75
+ const spaceLength = lines[0]?.match(/^\s*/)?.[0].length ?? 0;
76
+ for (const line of lines) {
77
+ const match = line.match(/^(\s*)-(.*)$/);
78
+ if (!match) continue;
79
+ const level = Math.floor((match[1].length - spaceLength) / 2);
80
+ const info = match[2].trim();
81
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) stack.pop();
82
+ const parent = stack[stack.length - 1];
83
+ const node = {
84
+ level,
85
+ children: [],
86
+ ...parseNodeInfo(info)
87
+ };
88
+ parent.children.push(node);
89
+ stack.push(node);
90
+ }
91
+ return root.children;
92
+ }
91
93
  /**
92
94
  * Convert file tree to command line text format
93
95
  *
@@ -107,6 +109,73 @@ function fileTreeToCMDText(nodes, prefix = "") {
107
109
  }
108
110
  return content;
109
111
  }
112
+ //#endregion
113
+ //#region src/node/fence.ts
114
+ /**
115
+ * Regex for matching a single line of `tree` command output.
116
+ *
117
+ * Matches lines like:
118
+ * ├── filename
119
+ * │ ├── filename
120
+ * │ └── filename
121
+ * └── filename # optional comment
122
+ *
123
+ * - Group 1: prefix segments, each is either `│ ` (has next sibling) or ` ` (last sibling)
124
+ * - Group 2: branch marker, either `├── ` (non-last) or `└── ` (last)
125
+ * - Group 3: the filename with optional comment
126
+ *
127
+ * 匹配 `tree` 命令输出的单行正则
128
+ */
129
+ const TREE_LINE_RE = /^((?:│ {3}| {4})*)([├└]── )(.+)$/u;
130
+ /**
131
+ * Parse `tree` command output format into a structured file tree node array.
132
+ *
133
+ * Converts text like:
134
+ * ```
135
+ * .
136
+ * ├── src
137
+ * │ ├── index.ts
138
+ * │ └── utils.ts
139
+ * └── package.json # project config
140
+ * ```
141
+ *
142
+ * into a `FileTreeNode[]` tree structure, with support for inline comments
143
+ * (text after `#`) on each entry.
144
+ *
145
+ * 将 `tree` 命令行输出格式解析为结构化的文件树节点数组
146
+ *
147
+ * @param content - Raw `tree` command output text / `tree` 命令输出的原始文本
148
+ * @returns Structured file tree node array / 结构化的文件树节点数组
149
+ */
150
+ function parseContentWithFence(content) {
151
+ const root = {
152
+ level: -1,
153
+ children: []
154
+ };
155
+ const stack = [root];
156
+ const lines = content.trimEnd().split("\n");
157
+ const start = lines[0]?.trim() === "." ? 1 : 0;
158
+ for (let i = start; i < lines.length; i++) {
159
+ const match = lines[i].match(TREE_LINE_RE);
160
+ if (!match) continue;
161
+ const prefix = match[1];
162
+ const info = match[3].trim();
163
+ const level = prefix.length / 4;
164
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) stack.pop();
165
+ const parent = stack[stack.length - 1];
166
+ if (parent !== root && parent.type === "file") parent.type = "folder";
167
+ const node = {
168
+ level,
169
+ children: [],
170
+ ...parseNodeInfo(info)
171
+ };
172
+ parent.children.push(node);
173
+ stack.push(node);
174
+ }
175
+ return root.children;
176
+ }
177
+ //#endregion
178
+ //#region src/node/fileTreePlugin.ts
110
179
  /**
111
180
  * @example
112
181
  * ```ts
@@ -150,14 +219,24 @@ ${renderedComment}${children.length > 0 ? renderFileTree(children, meta) : ""}
150
219
  }).join("\n");
151
220
  createContainerSyntaxPlugin(md, "file-tree", (tokens, index) => {
152
221
  const token = tokens[index];
153
- const nodes = parseFileTreeRawContent(token.content);
222
+ const nodes = parseContentWithContainer(token.content);
154
223
  const meta = token.meta;
155
- const text = fileTreeToCMDText(nodes).trim();
224
+ const text = encodeURIComponent(fileTreeToCMDText(nodes).trim());
156
225
  return `<VPFileTree${stringifyAttrs({
157
226
  title: meta.title,
158
227
  text
159
228
  })}>${renderFileTree(nodes, meta)}</VPFileTree>`;
160
229
  });
230
+ const rawFence = md.renderer.rules.fence;
231
+ md.renderer.rules.fence = (...args) => {
232
+ const [tokens, index] = args;
233
+ const token = tokens[index];
234
+ const info = token.info.trim();
235
+ if (!info.startsWith("file-tree") && !info.startsWith("tree")) return rawFence(...args);
236
+ const text = token.content.trim();
237
+ const nodes = parseContentWithFence(text);
238
+ return `<VPFileTree text="${encodeURIComponent(text)}">${renderFileTree(nodes, {})}</VPFileTree>`;
239
+ };
161
240
  };
162
241
  //#endregion
163
242
  //#region src/node/index.ts
@@ -173,4 +252,4 @@ var node_default = definePlugin(() => ({
173
252
  }
174
253
  }));
175
254
  //#endregion
176
- export { node_default as default, fileTreeMarkdownPlugin, parseFileTreeNodeInfo, parseFileTreeRawContent };
255
+ export { node_default as default, fileTreeMarkdownPlugin, fileTreeToCMDText, parseContentWithContainer, parseContentWithFence, parseNodeInfo };
package/package.json CHANGED
@@ -1,10 +1,15 @@
1
1
  {
2
2
  "name": "vitepress-plugin-file-tree",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "description": "Render file tree structure in your VitePress site.",
6
6
  "author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
7
7
  "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/pengzhanbo/vitepress-tuck.git",
11
+ "directory": "packages/plugin-file-tree"
12
+ },
8
13
  "keywords": [
9
14
  "vitepress",
10
15
  "vitepress-plugin",
@@ -30,11 +35,12 @@
30
35
  "dependencies": {
31
36
  "@pengzhanbo/utils": "^3.7.3",
32
37
  "@vueuse/core": "^14.3.0",
33
- "vitepress-plugin-toolkit": "0.1.0",
34
- "vitepress-tuck": "0.1.0"
38
+ "vitepress-plugin-toolkit": "0.2.0",
39
+ "vitepress-tuck": "0.2.0"
35
40
  },
36
41
  "publishConfig": {
37
- "access": "public"
42
+ "access": "public",
43
+ "provenance": true
38
44
  },
39
45
  "scripts": {
40
46
  "clean": "rimraf --glob ./dist",