vitepress-plugin-file-tree 0.1.1 → 0.3.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" };
@@ -7,17 +7,18 @@ const _hoisted_2$1 = {
7
7
  key: 0,
8
8
  class: "vp-file-tree-title"
9
9
  };
10
- const _sfc_main = /* @__PURE__ */ defineComponent({
10
+ const _sfc_main = /*@__PURE__*/ defineComponent({
11
11
  __name: "VPFileTree",
12
12
  props: {
13
13
  title: {},
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
  };
@@ -35,7 +36,7 @@ const _hoisted_4 = {
35
36
  key: 0,
36
37
  class: "group"
37
38
  };
38
- const _sfc_main$1 = /* @__PURE__ */ defineComponent({
39
+ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
39
40
  __name: "VPFileTreeNode",
40
41
  props: {
41
42
  type: {},
@@ -1,8 +1,8 @@
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
5
- const _sfc_main = /* @__PURE__ */ defineComponent({
5
+ const _sfc_main = /*@__PURE__*/ defineComponent({
6
6
  __name: "VPFileTree",
7
7
  __ssrInlineRender: true,
8
8
  props: {
@@ -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
  };
@@ -28,7 +29,7 @@ _sfc_main.setup = (props, ctx) => {
28
29
  };
29
30
  //#endregion
30
31
  //#region src/client/VPFileTreeNode.vue
31
- const _sfc_main$1 = /* @__PURE__ */ defineComponent({
32
+ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
32
33
  __name: "VPFileTreeNode",
33
34
  __ssrInlineRender: true,
34
35
  props: {
@@ -1,6 +1,9 @@
1
1
  import { PluginSimple } from "markdown-it";
2
2
 
3
- //#region src/node/fileTreePlugin.d.ts
3
+ //#region src/node/plugin.d.ts
4
+ declare const fileTree: (options?: unknown) => import("vitepress-tuck").VitepressPlugin;
5
+ //#endregion
6
+ //#region src/node/types.d.ts
4
7
  /**
5
8
  * File tree node structure
6
9
  *
@@ -24,24 +27,20 @@ interface FileTreeNode {
24
27
  interface FileTreeAttrs {
25
28
  title?: string;
26
29
  }
30
+ //#endregion
31
+ //#region src/node/fileTreeToCMDText.d.ts
27
32
  /**
28
- * Parse raw file tree content to node tree structure
33
+ * Convert file tree to command line text format
29
34
  *
30
- * 解析原始文件树内容为节点树结构
35
+ * 将文件树转换为命令行文本格式
31
36
  *
32
- * @param content - Raw file tree text content / 文件树的原始文本内容
33
- * @returns File tree node array / 文件树节点数组
37
+ * @param nodes - File tree nodes / 文件树节点
38
+ * @param prefix - Line prefix / 行前缀
39
+ * @returns CMD text / CMD 文本
34
40
  */
35
- declare function parseFileTreeRawContent(content: string): FileTreeNode[];
36
- /**
37
- * Parse single node info string, extract filename, comment, type, etc.
38
- *
39
- * 解析单个节点的 info 字符串,提取文件名、注释、类型等属性
40
- *
41
- * @param info - Node description string / 节点描述字符串
42
- * @returns File tree node props / 文件树节点属性
43
- */
44
- declare function parseFileTreeNodeInfo(info: string): Omit<FileTreeNode, 'children' | 'level'>;
41
+ declare function fileTreeToCMDText(nodes: FileTreeNode[], prefix?: string): string;
42
+ //#endregion
43
+ //#region src/node/markdown.d.ts
45
44
  /**
46
45
  * @example
47
46
  * ```ts
@@ -58,7 +57,49 @@ declare function parseFileTreeNodeInfo(info: string): Omit<FileTreeNode, 'childr
58
57
  */
59
58
  declare const fileTreeMarkdownPlugin: PluginSimple;
60
59
  //#endregion
61
- //#region src/node/index.d.ts
62
- declare const _default: (option?: unknown) => import("vitepress-tuck").VitepressPlugin;
60
+ //#region src/node/parseContentWithContainer.d.ts
61
+ /**
62
+ * Parse the content from the `::: file-tree` container into a node tree structure.
63
+ *
64
+ * 从 `::: file-tree` 容器中解析内容为节点树结构
65
+ *
66
+ * @param content - Raw file tree text content / 文件树的原始文本内容
67
+ * @returns File tree node array / 文件树节点数组
68
+ */
69
+ declare function parseContentWithContainer(content: string): FileTreeNode[];
70
+ //#endregion
71
+ //#region src/node/parseContentWithFence.d.ts
72
+ /**
73
+ * Parse `tree` command output format into a structured file tree node array.
74
+ *
75
+ * Converts text like:
76
+ * ```
77
+ * .
78
+ * ├── src
79
+ * │ ├── index.ts
80
+ * │ └── utils.ts
81
+ * └── package.json # project config
82
+ * ```
83
+ *
84
+ * into a `FileTreeNode[]` tree structure, with support for inline comments
85
+ * (text after `#`) on each entry.
86
+ *
87
+ * 将 `tree` 命令行输出格式解析为结构化的文件树节点数组
88
+ *
89
+ * @param content - Raw `tree` command output text / `tree` 命令输出的原始文本
90
+ * @returns Structured file tree node array / 结构化的文件树节点数组
91
+ */
92
+ declare function parseContentWithFence(content: string): FileTreeNode[];
93
+ //#endregion
94
+ //#region src/node/parseNodeInfo.d.ts
95
+ /**
96
+ * Parse single node info string, extract filename, comment, type, etc.
97
+ *
98
+ * 解析单个节点的 info 字符串,提取文件名、注释、类型等属性
99
+ *
100
+ * @param info - Node description string / 节点描述字符串
101
+ * @returns File tree node props / 文件树节点属性
102
+ */
103
+ declare function parseNodeInfo(info: string): Omit<FileTreeNode, 'children' | 'level'>;
63
104
  //#endregion
64
- export { type FileTreeAttrs, type FileTreeNode, _default as default, fileTreeMarkdownPlugin, parseFileTreeNodeInfo, parseFileTreeRawContent };
105
+ export { FileTreeAttrs, FileTreeNode, fileTree as default, fileTree, fileTreeMarkdownPlugin, fileTreeToCMDText, parseContentWithContainer, parseContentWithFence, parseNodeInfo };
@@ -1,40 +1,28 @@
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
3
+ import { removeTrailingSlash } from "@pengzhanbo/utils";
4
+ //#region src/node/fileTreeToCMDText.ts
5
5
  /**
6
- * Parse raw file tree content to node tree structure
6
+ * Convert file tree to command line text format
7
7
  *
8
- * 解析原始文件树内容为节点树结构
8
+ * 将文件树转换为命令行文本格式
9
9
  *
10
- * @param content - Raw file tree text content / 文件树的原始文本内容
11
- * @returns File tree node array / 文件树节点数组
10
+ * @param nodes - File tree nodes / 文件树节点
11
+ * @param prefix - Line prefix / 行前缀
12
+ * @returns CMD text / CMD 文本
12
13
  */
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);
14
+ function fileTreeToCMDText(nodes, prefix = "") {
15
+ let content = prefix ? "" : ".\n";
16
+ for (let i = 0, l = nodes.length; i < l; i++) {
17
+ const { filename, children } = nodes[i];
18
+ content += `${prefix + (i === l - 1 ? "└── " : "├── ")}${filename}\n`;
19
+ const child = children.filter((n) => n.filename !== "…");
20
+ if (child.length) content += fileTreeToCMDText(child, prefix + (i === l - 1 ? " " : "│ "));
35
21
  }
36
- return root.children;
22
+ return content;
37
23
  }
24
+ //#endregion
25
+ //#region src/node/parseNodeInfo.ts
38
26
  /**
39
27
  * Regex for focus marker
40
28
  *
@@ -49,7 +37,7 @@ const RE_FOCUS = /^\*\*(.*)\*\*(?:$|\s+)/;
49
37
  * @param info - Node description string / 节点描述字符串
50
38
  * @returns File tree node props / 文件树节点属性
51
39
  */
52
- function parseFileTreeNodeInfo(info) {
40
+ function parseNodeInfo(info) {
53
41
  let filename = "";
54
42
  let comment = "";
55
43
  let focus = false;
@@ -88,25 +76,108 @@ function parseFileTreeNodeInfo(info) {
88
76
  diff
89
77
  };
90
78
  }
79
+ //#endregion
80
+ //#region src/node/parseContentWithContainer.ts
91
81
  /**
92
- * Convert file tree to command line text format
82
+ * Parse the content from the `::: file-tree` container into a node tree structure.
93
83
  *
94
- * 将文件树转换为命令行文本格式
84
+ * 从 `::: file-tree` 容器中解析内容为节点树结构
95
85
  *
96
- * @param nodes - File tree nodes / 文件树节点
97
- * @param prefix - Line prefix / 行前缀
98
- * @returns CMD text / CMD 文本
86
+ * @param content - Raw file tree text content / 文件树的原始文本内容
87
+ * @returns File tree node array / 文件树节点数组
99
88
  */
100
- function fileTreeToCMDText(nodes, prefix = "") {
101
- let content = prefix ? "" : ".\n";
102
- for (let i = 0, l = nodes.length; i < l; i++) {
103
- const { filename, children } = nodes[i];
104
- content += `${prefix + (i === l - 1 ? "└── " : "├── ")}${filename}\n`;
105
- const child = children.filter((n) => n.filename !== "…");
106
- if (child.length) content += fileTreeToCMDText(child, prefix + (i === l - 1 ? " " : "│ "));
89
+ function parseContentWithContainer(content) {
90
+ const root = {
91
+ level: -1,
92
+ children: []
93
+ };
94
+ const stack = [root];
95
+ const lines = content.trimEnd().split("\n");
96
+ const spaceLength = lines[0]?.match(/^\s*/)?.[0].length ?? 0;
97
+ for (const line of lines) {
98
+ const match = line.match(/^(\s*)-(.*)$/);
99
+ if (!match) continue;
100
+ const level = Math.floor((match[1].length - spaceLength) / 2);
101
+ const info = match[2].trim();
102
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) stack.pop();
103
+ const parent = stack[stack.length - 1];
104
+ const node = {
105
+ level,
106
+ children: [],
107
+ ...parseNodeInfo(info)
108
+ };
109
+ parent.children.push(node);
110
+ stack.push(node);
107
111
  }
108
- return content;
112
+ return root.children;
109
113
  }
114
+ //#endregion
115
+ //#region src/node/parseContentWithFence.ts
116
+ /**
117
+ * Regex for matching a single line of `tree` command output.
118
+ *
119
+ * Matches lines like:
120
+ * ├── filename
121
+ * │ ├── filename
122
+ * │ └── filename
123
+ * └── filename # optional comment
124
+ *
125
+ * - Group 1: prefix segments, each is either `│ ` (has next sibling) or ` ` (last sibling)
126
+ * - Group 2: branch marker, either `├── ` (non-last) or `└── ` (last)
127
+ * - Group 3: the filename with optional comment
128
+ *
129
+ * 匹配 `tree` 命令输出的单行正则
130
+ */
131
+ const TREE_LINE_RE = /^((?:│ {3}| {4})*)([├└]── )(.+)$/u;
132
+ /**
133
+ * Parse `tree` command output format into a structured file tree node array.
134
+ *
135
+ * Converts text like:
136
+ * ```
137
+ * .
138
+ * ├── src
139
+ * │ ├── index.ts
140
+ * │ └── utils.ts
141
+ * └── package.json # project config
142
+ * ```
143
+ *
144
+ * into a `FileTreeNode[]` tree structure, with support for inline comments
145
+ * (text after `#`) on each entry.
146
+ *
147
+ * 将 `tree` 命令行输出格式解析为结构化的文件树节点数组
148
+ *
149
+ * @param content - Raw `tree` command output text / `tree` 命令输出的原始文本
150
+ * @returns Structured file tree node array / 结构化的文件树节点数组
151
+ */
152
+ function parseContentWithFence(content) {
153
+ const root = {
154
+ level: -1,
155
+ children: []
156
+ };
157
+ const stack = [root];
158
+ const lines = content.trimEnd().split("\n");
159
+ const start = lines[0]?.trim() === "." ? 1 : 0;
160
+ for (let i = start; i < lines.length; i++) {
161
+ const match = lines[i].match(TREE_LINE_RE);
162
+ if (!match) continue;
163
+ const prefix = match[1];
164
+ const info = match[3].trim();
165
+ const level = prefix.length / 4;
166
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) stack.pop();
167
+ const parent = stack[stack.length - 1];
168
+ if (parent !== root && parent.type === "file") parent.type = "folder";
169
+ const node = {
170
+ level,
171
+ children: [],
172
+ ...parseNodeInfo(info)
173
+ };
174
+ parent.children.push(node);
175
+ stack.push(node);
176
+ }
177
+ return root.children;
178
+ }
179
+ //#endregion
180
+ //#region src/node/markdown.ts
110
181
  /**
111
182
  * @example
112
183
  * ```ts
@@ -150,18 +221,28 @@ ${renderedComment}${children.length > 0 ? renderFileTree(children, meta) : ""}
150
221
  }).join("\n");
151
222
  createContainerSyntaxPlugin(md, "file-tree", (tokens, index) => {
152
223
  const token = tokens[index];
153
- const nodes = parseFileTreeRawContent(token.content);
224
+ const nodes = parseContentWithContainer(token.content);
154
225
  const meta = token.meta;
155
- const text = fileTreeToCMDText(nodes).trim();
226
+ const text = encodeURIComponent(fileTreeToCMDText(nodes).trim());
156
227
  return `<VPFileTree${stringifyAttrs({
157
228
  title: meta.title,
158
229
  text
159
230
  })}>${renderFileTree(nodes, meta)}</VPFileTree>`;
160
231
  });
232
+ const rawFence = md.renderer.rules.fence;
233
+ md.renderer.rules.fence = (...args) => {
234
+ const [tokens, index] = args;
235
+ const token = tokens[index];
236
+ const info = token.info.trim();
237
+ if (!info.startsWith("file-tree") && !info.startsWith("tree")) return rawFence(...args);
238
+ const text = token.content.trim();
239
+ const nodes = parseContentWithFence(text);
240
+ return `<VPFileTree text="${encodeURIComponent(text)}">${renderFileTree(nodes, {})}</VPFileTree>`;
241
+ };
161
242
  };
162
243
  //#endregion
163
- //#region src/node/index.ts
164
- var node_default = definePlugin(() => ({
244
+ //#region src/node/plugin.ts
245
+ const fileTree = definePlugin(() => ({
165
246
  name: "vitepress-plugin-file-tree",
166
247
  client: { enhance: "enhanceAppWithFileTree" },
167
248
  markdown: { config: (md) => {
@@ -173,4 +254,7 @@ var node_default = definePlugin(() => ({
173
254
  }
174
255
  }));
175
256
  //#endregion
176
- export { node_default as default, fileTreeMarkdownPlugin, parseFileTreeNodeInfo, parseFileTreeRawContent };
257
+ //#region src/node/index.ts
258
+ var node_default = fileTree;
259
+ //#endregion
260
+ export { node_default as default, fileTree, fileTreeMarkdownPlugin, fileTreeToCMDText, parseContentWithContainer, parseContentWithFence, parseNodeInfo };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vitepress-plugin-file-tree",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.3.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",
@@ -35,8 +35,8 @@
35
35
  "dependencies": {
36
36
  "@pengzhanbo/utils": "^3.7.3",
37
37
  "@vueuse/core": "^14.3.0",
38
- "vitepress-plugin-toolkit": "0.1.1",
39
- "vitepress-tuck": "0.1.1"
38
+ "vitepress-plugin-toolkit": "0.3.0",
39
+ "vitepress-tuck": "0.3.0"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public",