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 +21 -0
- package/dist/client/browser/index.js +3 -2
- package/dist/client/ssr/index.js +3 -2
- package/dist/node/index.d.ts +59 -20
- package/dist/node/index.js +118 -39
- package/package.json +10 -4
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:
|
|
21
|
+
createVNode(unref(VPCopyButton), { text: content.value }, null, 8, ["text"]),
|
|
21
22
|
renderSlot(_ctx.$slots, "default")
|
|
22
23
|
]);
|
|
23
24
|
};
|
package/dist/client/ssr/index.js
CHANGED
|
@@ -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:
|
|
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
|
};
|
package/dist/node/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 {
|
|
103
|
+
export { FileTreeAttrs, FileTreeNode, _default as default, fileTreeMarkdownPlugin, fileTreeToCMDText, parseContentWithContainer, parseContentWithFence, parseNodeInfo };
|
package/dist/node/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 =
|
|
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,
|
|
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.
|
|
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.
|
|
34
|
-
"vitepress-tuck": "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",
|