rspress-plugin-file-tree 0.1.1
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/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/components/Tree/Expand.d.ts +14 -0
- package/dist/components/Tree/Expand.js +104 -0
- package/dist/components/Tree/FileIcon.d.ts +8 -0
- package/dist/components/Tree/FileIcon.js +12 -0
- package/dist/components/Tree/FileTreeRender.d.ts +11 -0
- package/dist/components/Tree/FileTreeRender.js +7 -0
- package/dist/components/Tree/Tree.d.ts +20 -0
- package/dist/components/Tree/Tree.js +52 -0
- package/dist/components/Tree/TreeContext.d.ts +9 -0
- package/dist/components/Tree/TreeContext.js +7 -0
- package/dist/components/Tree/TreeFile.d.ts +14 -0
- package/dist/components/Tree/TreeFile.js +26 -0
- package/dist/components/Tree/TreeFolder.d.ts +14 -0
- package/dist/components/Tree/TreeFolder.js +37 -0
- package/dist/components/Tree/TreeFolderIcon.d.ts +8 -0
- package/dist/components/Tree/TreeFolderIcon.js +12 -0
- package/dist/components/Tree/TreeIndents.d.ts +6 -0
- package/dist/components/Tree/TreeIndents.js +10 -0
- package/dist/components/Tree/TreeStatusIcon.d.ts +9 -0
- package/dist/components/Tree/TreeStatusIcon.js +13 -0
- package/dist/components/helpers.d.ts +5 -0
- package/dist/components/helpers.js +35 -0
- package/dist/components/presets.d.ts +2 -0
- package/dist/components/presets.js +4 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +31 -0
- package/dist/parser.d.ts +7 -0
- package/dist/parser.js +37 -0
- package/doc_build/static/search_index.aad48136.json +1 -0
- package/docs/index.md +26 -0
- package/image.png +0 -0
- package/package.json +44 -0
- package/rspress.config.ts +13 -0
- package/src/components/Tree/Expand.tsx +149 -0
- package/src/components/Tree/FileIcon.tsx +41 -0
- package/src/components/Tree/FileTreeRender.tsx +16 -0
- package/src/components/Tree/Tree.tsx +112 -0
- package/src/components/Tree/TreeContext.tsx +18 -0
- package/src/components/Tree/TreeFile.tsx +69 -0
- package/src/components/Tree/TreeFolder.tsx +108 -0
- package/src/components/Tree/TreeFolderIcon.tsx +40 -0
- package/src/components/Tree/TreeIndents.tsx +26 -0
- package/src/components/Tree/TreeStatusIcon.tsx +45 -0
- package/src/components/Tree/index.less +178 -0
- package/src/components/helpers.ts +42 -0
- package/src/components/presets.ts +5 -0
- package/src/index.ts +50 -0
- package/src/parser.ts +50 -0
- package/tsconfig.json +8 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# rspress-plugin-file-tree
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 546dcf0: release beta
|
|
8
|
+
- 5e28903: fixup publish config
|
|
9
|
+
- Updated dependencies [546dcf0]
|
|
10
|
+
- Updated dependencies [5e28903]
|
|
11
|
+
- rspress-plugin-devkit@0.1.1
|
|
12
|
+
|
|
13
|
+
## 0.1.1-beta.0
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 546dcf0: release beta
|
|
18
|
+
- Updated dependencies [546dcf0]
|
|
19
|
+
- rspress-plugin-devkit@0.1.1-beta.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Linbudu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# rspress-plugin-file-tree
|
|
2
|
+
|
|
3
|
+
Rspress plugin that add file tree view support.
|
|
4
|
+
|
|
5
|
+
Write tree view using code block with language `tree`:
|
|
6
|
+
|
|
7
|
+
````markdown
|
|
8
|
+
```tree
|
|
9
|
+
.
|
|
10
|
+
├── rspress.config.ts
|
|
11
|
+
├── src
|
|
12
|
+
│ ├── components
|
|
13
|
+
│ │ ├── FileTreeRender.tsx
|
|
14
|
+
│ │ ├── Tree
|
|
15
|
+
│ │ │ ├── Expand.tsx
|
|
16
|
+
│ │ │ ├── FileIcon.tsx
|
|
17
|
+
│ │ │ ├── Tree.tsx
|
|
18
|
+
│ │ │ ├── TreeContext.tsx
|
|
19
|
+
│ │ │ ├── TreeFile.tsx
|
|
20
|
+
│ │ │ ├── TreeFolder.tsx
|
|
21
|
+
│ │ │ ├── TreeFolderIcon.tsx
|
|
22
|
+
│ │ │ ├── TreeIndents.tsx
|
|
23
|
+
│ │ │ ├── TreeStatusIcon.tsx
|
|
24
|
+
│ │ │ ├── index.less
|
|
25
|
+
│ │ │ └── index.tsx
|
|
26
|
+
│ │ ├── helpers.ts
|
|
27
|
+
│ │ └── presets.ts
|
|
28
|
+
│ ├── index.ts
|
|
29
|
+
│ └── parser.ts
|
|
30
|
+
└── tsconfig.json
|
|
31
|
+
```
|
|
32
|
+
````
|
|
33
|
+
|
|
34
|
+
And it will be rendered as:
|
|
35
|
+
|
|
36
|
+
<div align="center">
|
|
37
|
+
<img src="./image.png" alt="sample" width="400" height="300" />
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
> [!NOTE]
|
|
41
|
+
>
|
|
42
|
+
> **The renderer component was forked from [Geist UI](https://geist-ui.dev/) which created by [witt](https://github.com/unix), huge thanks to his great work!**
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm i rspress-plugin-file-tree
|
|
48
|
+
pnpm add rspress-plugin-file-tree
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import * as path from 'path';
|
|
53
|
+
import { defineConfig } from 'rspress/config';
|
|
54
|
+
import fileTree from 'rspress-plugin-file-tree';
|
|
55
|
+
|
|
56
|
+
export default defineConfig({
|
|
57
|
+
root: path.join(__dirname, 'docs'),
|
|
58
|
+
plugins: [fileTree()],
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Configure
|
|
63
|
+
|
|
64
|
+
## initialExpandDepth
|
|
65
|
+
|
|
66
|
+
Initial expand depth of the tree view.
|
|
67
|
+
|
|
68
|
+
- Type: `number`
|
|
69
|
+
- Default: `0`
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import * as path from 'path';
|
|
73
|
+
import { defineConfig } from 'rspress/config';
|
|
74
|
+
import fileTree from 'rspress-plugin-file-tree';
|
|
75
|
+
|
|
76
|
+
export default defineConfig({
|
|
77
|
+
root: path.join(__dirname, 'docs'),
|
|
78
|
+
plugins: [
|
|
79
|
+
fileTree({
|
|
80
|
+
initialExpandDepth: Infinity,
|
|
81
|
+
}),
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type ShapeType = {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const getRealShape: (el: HTMLElement | null) => ShapeType;
|
|
7
|
+
export type ShapeResult = [ShapeType, () => void];
|
|
8
|
+
export type ExpandProps = {
|
|
9
|
+
isExpanded?: boolean;
|
|
10
|
+
delay?: number;
|
|
11
|
+
parentExpanded?: boolean[];
|
|
12
|
+
};
|
|
13
|
+
declare const Expand: React.FC<React.PropsWithChildren<ExpandProps>>;
|
|
14
|
+
export default Expand;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
export const getRealShape = (el) => {
|
|
5
|
+
const defaultShape = { width: 0, height: 0 };
|
|
6
|
+
if (!el || typeof window === 'undefined')
|
|
7
|
+
return defaultShape;
|
|
8
|
+
const rect = el.getBoundingClientRect();
|
|
9
|
+
const { width, height } = window.getComputedStyle(el);
|
|
10
|
+
const getCSSStyleVal = (str, parentNum) => {
|
|
11
|
+
if (!str)
|
|
12
|
+
return 0;
|
|
13
|
+
const strVal = str.includes('px')
|
|
14
|
+
? +str.split('px')[0]
|
|
15
|
+
: str.includes('%')
|
|
16
|
+
? +str.split('%')[0] * parentNum * 0.01
|
|
17
|
+
: str;
|
|
18
|
+
return Number.isNaN(+strVal) ? 0 : +strVal;
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
width: getCSSStyleVal(`${width}`, rect.width),
|
|
22
|
+
height: getCSSStyleVal(`${height}`, rect.height),
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const useRealShape = (ref) => {
|
|
26
|
+
const [state, setState] = useState({
|
|
27
|
+
width: 0,
|
|
28
|
+
height: 0,
|
|
29
|
+
});
|
|
30
|
+
const update = () => {
|
|
31
|
+
const { width, height } = getRealShape(ref.current);
|
|
32
|
+
setState({ width, height });
|
|
33
|
+
};
|
|
34
|
+
useEffect(() => update(), [ref.current]);
|
|
35
|
+
return [state, update];
|
|
36
|
+
};
|
|
37
|
+
const defaultProps = {
|
|
38
|
+
isExpanded: false,
|
|
39
|
+
delay: 200,
|
|
40
|
+
};
|
|
41
|
+
const Expand = ({ isExpanded, delay, parentExpanded = [], children, }) => {
|
|
42
|
+
const [height, setHeight] = useState(isExpanded ? 'auto' : '0');
|
|
43
|
+
const [selfExpanded, setSelfExpanded] = useState(isExpanded);
|
|
44
|
+
const [visible, setVisible] = useState(isExpanded);
|
|
45
|
+
const contentRef = useRef(null);
|
|
46
|
+
const entryTimer = useRef();
|
|
47
|
+
const leaveTimer = useRef();
|
|
48
|
+
const resetTimer = useRef();
|
|
49
|
+
const [state, updateShape] = useRealShape(contentRef);
|
|
50
|
+
const [parentClosed, setParentClosed] = useState(false);
|
|
51
|
+
useEffect(() => setHeight(`${state.height}px`), [state.height]);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
// show element or reset height.
|
|
54
|
+
// force an update once manually, even if the element does not change.
|
|
55
|
+
// (the height of the element might be "auto")
|
|
56
|
+
if (isExpanded) {
|
|
57
|
+
setVisible(isExpanded);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
updateShape();
|
|
61
|
+
setHeight(`${state.height}px`);
|
|
62
|
+
}
|
|
63
|
+
// show expand animation
|
|
64
|
+
entryTimer.current = window.setTimeout(() => {
|
|
65
|
+
setSelfExpanded(isExpanded);
|
|
66
|
+
clearTimeout(entryTimer.current);
|
|
67
|
+
}, 30);
|
|
68
|
+
// Reset height after animation
|
|
69
|
+
if (isExpanded) {
|
|
70
|
+
resetTimer.current = window.setTimeout(() => {
|
|
71
|
+
setHeight('auto');
|
|
72
|
+
clearTimeout(resetTimer.current);
|
|
73
|
+
}, delay);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
leaveTimer.current = window.setTimeout(() => {
|
|
77
|
+
setVisible(isExpanded);
|
|
78
|
+
clearTimeout(leaveTimer.current);
|
|
79
|
+
}, delay / 2);
|
|
80
|
+
}
|
|
81
|
+
return () => {
|
|
82
|
+
clearTimeout(entryTimer.current);
|
|
83
|
+
clearTimeout(leaveTimer.current);
|
|
84
|
+
clearTimeout(resetTimer.current);
|
|
85
|
+
};
|
|
86
|
+
}, [isExpanded]);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const parentClosed = parentExpanded.some((i) => i === false);
|
|
89
|
+
setParentClosed(parentClosed);
|
|
90
|
+
}, [parentExpanded]);
|
|
91
|
+
return (_jsx("div", { className: clsx('rspress-file-tree-expand-container', {
|
|
92
|
+
'rspress-file-tree-expand-container-expanded': isExpanded,
|
|
93
|
+
}), style: {
|
|
94
|
+
height: 0,
|
|
95
|
+
visibility: visible && !parentClosed ? 'visible' : 'hidden',
|
|
96
|
+
transition: `height ${delay}ms ease`,
|
|
97
|
+
...(selfExpanded
|
|
98
|
+
? { height, visibility: parentClosed ? 'hidden' : 'visible' }
|
|
99
|
+
: {}),
|
|
100
|
+
}, children: _jsx("div", { ref: contentRef, className: clsx('rspress-file-tree-expand-content'), children: children }) }));
|
|
101
|
+
};
|
|
102
|
+
Expand.defaultProps = defaultProps;
|
|
103
|
+
Expand.displayName = 'GeistExpand';
|
|
104
|
+
export default Expand;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { buildClassName } from '../presets';
|
|
3
|
+
const defaultProps = {
|
|
4
|
+
width: 22,
|
|
5
|
+
height: 22,
|
|
6
|
+
};
|
|
7
|
+
const TreeFileIcon = ({ color, width, height, }) => {
|
|
8
|
+
return (_jsxs("svg", { className: buildClassName('file-icon'), viewBox: "0 0 24 24", width: width, height: height, stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", shapeRendering: "geometricPrecision", children: [_jsx("path", { d: "M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z" }), _jsx("path", { d: "M13 2v7h7" })] }));
|
|
9
|
+
};
|
|
10
|
+
TreeFileIcon.defaultProps = defaultProps;
|
|
11
|
+
TreeFileIcon.displayName = 'GeistTreeFileIcon';
|
|
12
|
+
export default TreeFileIcon;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Tree from './Tree';
|
|
2
|
+
import TreeFile from './TreeFile';
|
|
3
|
+
import TreeFolder from './TreeFolder';
|
|
4
|
+
import './index.less';
|
|
5
|
+
export type TreeComponentType = typeof Tree & {
|
|
6
|
+
File: typeof TreeFile;
|
|
7
|
+
Folder: typeof TreeFolder;
|
|
8
|
+
};
|
|
9
|
+
export type { TreeProps, TreeFile } from './Tree';
|
|
10
|
+
declare const _default: TreeComponentType;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export declare const tuple: <T extends string[]>(...args: T) => T;
|
|
3
|
+
declare const FileTreeValueType: ["directory", "file"];
|
|
4
|
+
export type TreeFile = {
|
|
5
|
+
type: (typeof FileTreeValueType)[number];
|
|
6
|
+
name: string;
|
|
7
|
+
extra?: string;
|
|
8
|
+
files?: Array<TreeFile>;
|
|
9
|
+
};
|
|
10
|
+
interface Props {
|
|
11
|
+
tree?: TreeFile[];
|
|
12
|
+
initialExpand?: boolean;
|
|
13
|
+
onClick?: (path: string) => void;
|
|
14
|
+
className?: string;
|
|
15
|
+
initialExpandDepth?: number;
|
|
16
|
+
}
|
|
17
|
+
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>;
|
|
18
|
+
export type TreeProps = Props & NativeAttrs;
|
|
19
|
+
declare const Tree: React.FC<React.PropsWithChildren<TreeProps>>;
|
|
20
|
+
export default Tree;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import TreeFile from './TreeFile';
|
|
4
|
+
import TreeFolder from './TreeFolder';
|
|
5
|
+
import { TreeContext } from './TreeContext';
|
|
6
|
+
import { sortChildren } from '../helpers';
|
|
7
|
+
import { buildClassName } from '../presets';
|
|
8
|
+
import { useDark } from 'rspress/runtime';
|
|
9
|
+
export const tuple = (...args) => args;
|
|
10
|
+
const FileTreeValueType = tuple('directory', 'file');
|
|
11
|
+
const directoryType = FileTreeValueType[0];
|
|
12
|
+
const defaultProps = {
|
|
13
|
+
initialExpand: false,
|
|
14
|
+
className: '',
|
|
15
|
+
};
|
|
16
|
+
const makeChildren = (value = []) => {
|
|
17
|
+
if (!value || !value.length)
|
|
18
|
+
return null;
|
|
19
|
+
return value
|
|
20
|
+
.sort((a, b) => {
|
|
21
|
+
if (a.type !== b.type)
|
|
22
|
+
return a.type !== directoryType ? 1 : -1;
|
|
23
|
+
return `${a.name}`.charCodeAt(0) - `${b.name}`.charCodeAt(0);
|
|
24
|
+
})
|
|
25
|
+
.map((item, index) => {
|
|
26
|
+
if (item.type === directoryType)
|
|
27
|
+
return (_jsx(TreeFolder, { name: item.name, extra: item.extra, parentExpanded: [], children: makeChildren(item.files) }, `folder-${item.name}-${index}`));
|
|
28
|
+
return (_jsx(TreeFile, { name: item.name, extra: item.extra, parentExpanded: [] }, `file-${item.name}-${index}`));
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const Tree = ({ children, onClick, initialExpand, initialExpandDepth, tree, className, ...props }) => {
|
|
32
|
+
if (!tree)
|
|
33
|
+
return null;
|
|
34
|
+
const isImperative = Boolean(tree.length > 0);
|
|
35
|
+
const onFileClick = (path) => {
|
|
36
|
+
onClick && onClick(path);
|
|
37
|
+
};
|
|
38
|
+
const initialValue = useMemo(() => ({
|
|
39
|
+
onFileClick,
|
|
40
|
+
initialExpand,
|
|
41
|
+
initialExpandDepth,
|
|
42
|
+
isImperative,
|
|
43
|
+
}), [initialExpand]);
|
|
44
|
+
const customChildren = isImperative
|
|
45
|
+
? makeChildren(tree)
|
|
46
|
+
: sortChildren(children, TreeFolder);
|
|
47
|
+
const dark = useDark();
|
|
48
|
+
return (_jsx(TreeContext.Provider, { value: initialValue, children: _jsx("div", { "data-dark": String(dark), className: buildClassName(), ...props, children: customChildren }) }));
|
|
49
|
+
};
|
|
50
|
+
Tree.defaultProps = defaultProps;
|
|
51
|
+
Tree.displayName = 'GeistTree';
|
|
52
|
+
export default Tree;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface TreeConfig {
|
|
3
|
+
onFileClick?: (path: string) => void;
|
|
4
|
+
initialExpand: boolean;
|
|
5
|
+
initialExpandDepth?: number;
|
|
6
|
+
isImperative: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const TreeContext: React.Context<TreeConfig>;
|
|
9
|
+
export declare const useTreeContext: () => TreeConfig;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface Props {
|
|
3
|
+
name: string;
|
|
4
|
+
extra?: string;
|
|
5
|
+
parentPath?: string;
|
|
6
|
+
level?: number;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>;
|
|
10
|
+
export type TreeFileProps = Props & NativeAttrs & {
|
|
11
|
+
parentExpanded: boolean[];
|
|
12
|
+
};
|
|
13
|
+
declare const TreeFile: React.FC<React.PropsWithChildren<TreeFileProps>>;
|
|
14
|
+
export default TreeFile;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import TreeFileIcon from './FileIcon';
|
|
4
|
+
import { useTreeContext } from './TreeContext';
|
|
5
|
+
import TreeIndents from './TreeIndents';
|
|
6
|
+
import { makeChildPath, stopPropagation } from '../helpers';
|
|
7
|
+
import { buildClassName } from '../presets';
|
|
8
|
+
const defaultProps = {
|
|
9
|
+
level: 0,
|
|
10
|
+
className: '',
|
|
11
|
+
parentPath: '',
|
|
12
|
+
};
|
|
13
|
+
const TreeFile = ({ name, parentPath, level, extra, className, parentExpanded, ...props }) => {
|
|
14
|
+
const { onFileClick } = useTreeContext();
|
|
15
|
+
const currentPath = useMemo(() => makeChildPath(name, parentPath), []);
|
|
16
|
+
const clickHandler = (event) => {
|
|
17
|
+
stopPropagation(event);
|
|
18
|
+
onFileClick && onFileClick(currentPath);
|
|
19
|
+
};
|
|
20
|
+
return (_jsx("div", { className: buildClassName('file'), onClick: clickHandler, ...props, children: _jsxs("div", { className: buildClassName('file-names'), style: {
|
|
21
|
+
marginLeft: `calc(1.875rem * ${level})`,
|
|
22
|
+
}, children: [_jsx(TreeIndents, { count: level }), _jsx("span", { className: buildClassName('file-icon'), children: _jsx(TreeFileIcon, {}) }), _jsxs("span", { className: buildClassName('file-name'), children: [name, extra && (_jsx("span", { className: buildClassName('file-extra'), children: extra }))] })] }) }));
|
|
23
|
+
};
|
|
24
|
+
TreeFile.defaultProps = defaultProps;
|
|
25
|
+
TreeFile.displayName = 'GeistTreeFile';
|
|
26
|
+
export default TreeFile;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface Props {
|
|
3
|
+
name: string;
|
|
4
|
+
extra?: string;
|
|
5
|
+
parentPath?: string;
|
|
6
|
+
level?: number;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>;
|
|
10
|
+
export type TreeFolderProps = Props & NativeAttrs & {
|
|
11
|
+
parentExpanded: boolean[];
|
|
12
|
+
};
|
|
13
|
+
declare const TreeFolder: React.FC<React.PropsWithChildren<TreeFolderProps>>;
|
|
14
|
+
export default TreeFolder;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import TreeFile from './TreeFile';
|
|
4
|
+
import Expand from './Expand';
|
|
5
|
+
import TreeIndents from './TreeIndents';
|
|
6
|
+
import { useTreeContext } from './TreeContext';
|
|
7
|
+
import TreeFolderIcon from './TreeFolderIcon';
|
|
8
|
+
import TreeStatusIcon from './TreeStatusIcon';
|
|
9
|
+
import { sortChildren, makeChildPath, stopPropagation, setChildrenProps, } from '../helpers';
|
|
10
|
+
import { buildClassName } from '../presets';
|
|
11
|
+
const defaultProps = {
|
|
12
|
+
level: 0,
|
|
13
|
+
className: '',
|
|
14
|
+
parentPath: '',
|
|
15
|
+
};
|
|
16
|
+
const TreeFolder = ({ name, children, parentPath, level: parentLevel, extra, className, parentExpanded = [], ...props }) => {
|
|
17
|
+
const { initialExpand, isImperative, initialExpandDepth = 0, } = useTreeContext();
|
|
18
|
+
const [expanded, setExpanded] = useState(() => {
|
|
19
|
+
return parentLevel + 1 <= initialExpandDepth;
|
|
20
|
+
});
|
|
21
|
+
const currentPath = useMemo(() => makeChildPath(name, parentPath), []);
|
|
22
|
+
const clickHandler = () => setExpanded(!expanded);
|
|
23
|
+
const nextChildren = setChildrenProps(children, {
|
|
24
|
+
parentPath: currentPath,
|
|
25
|
+
level: parentLevel + 1,
|
|
26
|
+
parentExpanded: [...parentExpanded, expanded],
|
|
27
|
+
}, [TreeFolder, TreeFile]);
|
|
28
|
+
const sortedChildren = isImperative
|
|
29
|
+
? nextChildren
|
|
30
|
+
: sortChildren(nextChildren, TreeFolder);
|
|
31
|
+
return (_jsxs("div", { className: buildClassName('folder'), onClick: clickHandler, ...props, children: [_jsxs("div", { className: buildClassName('folder-names'), style: {
|
|
32
|
+
marginLeft: `calc(1.875rem * ${parentLevel})`,
|
|
33
|
+
}, children: [_jsx(TreeIndents, { count: parentLevel }), _jsx("span", { className: buildClassName('folder-status'), children: _jsx(TreeStatusIcon, { active: expanded }) }), _jsx("span", { className: buildClassName('folder-icon'), children: _jsx(TreeFolderIcon, {}) }), _jsxs("span", { className: buildClassName('folder-name'), children: [name, extra && (_jsx("span", { className: buildClassName('folder-extra'), children: extra }))] })] }), _jsx(Expand, { isExpanded: expanded, parentExpanded: parentExpanded, children: _jsx("div", { className: buildClassName('folder-content'), onClick: stopPropagation, children: sortedChildren }) })] }));
|
|
34
|
+
};
|
|
35
|
+
TreeFolder.defaultProps = defaultProps;
|
|
36
|
+
TreeFolder.displayName = 'GeistTreeFolder';
|
|
37
|
+
export default TreeFolder;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { buildClassName } from '../presets';
|
|
3
|
+
const defaultProps = {
|
|
4
|
+
width: 22,
|
|
5
|
+
height: 22,
|
|
6
|
+
};
|
|
7
|
+
const TreeFolderIcon = ({ color, width, height, }) => {
|
|
8
|
+
return (_jsx("svg", { className: buildClassName('folder-icon'), viewBox: "0 0 24 24", width: width, height: height, stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", shapeRendering: "geometricPrecision", children: _jsx("path", { d: "M2.707 7.454V5.62C2.707 4.725 3.469 4 4.409 4h4.843c.451 0 .884.17 1.204.474l.49.467c.126.12.296.186.473.186h8.399c.94 0 1.55.695 1.55 1.59v.737m-18.661 0h-.354a.344.344 0 00-.353.35l.508 11.587c.015.34.31.609.668.609h17.283c.358 0 .652-.269.667-.61L22 7.805a.344.344 0 00-.353-.35h-.278m-18.662 0h18.662" }) }));
|
|
9
|
+
};
|
|
10
|
+
TreeFolderIcon.defaultProps = defaultProps;
|
|
11
|
+
TreeFolderIcon.displayName = 'GeistTreeFolderIcon';
|
|
12
|
+
export default TreeFolderIcon;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { buildClassName } from '../presets';
|
|
3
|
+
const TreeIndents = ({ count }) => {
|
|
4
|
+
if (count === 0)
|
|
5
|
+
return null;
|
|
6
|
+
return (_jsx(_Fragment, { children: [...new Array(count)].map((_, index) => (_jsx("span", { className: buildClassName('indent'), style: {
|
|
7
|
+
left: `calc(-1.875rem * ${index + 1} + 0.75rem)`,
|
|
8
|
+
} }, `indent-${index}`))) }));
|
|
9
|
+
};
|
|
10
|
+
export default TreeIndents;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { buildClassName } from '../presets';
|
|
3
|
+
const defaultProps = {
|
|
4
|
+
width: 12,
|
|
5
|
+
height: 12,
|
|
6
|
+
active: false,
|
|
7
|
+
};
|
|
8
|
+
const TreeStatusIcon = ({ color, width, height, active, }) => {
|
|
9
|
+
return (_jsxs("svg", { className: buildClassName('folder-status-icon'), viewBox: "0 0 24 24", width: width, height: height, stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", shapeRendering: "geometricPrecision", children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), !active && _jsx("path", { d: "M12 8v8" }), _jsx("path", { d: "M8 12h8" })] }));
|
|
10
|
+
};
|
|
11
|
+
TreeStatusIcon.defaultProps = defaultProps;
|
|
12
|
+
TreeStatusIcon.displayName = 'GeistTreeStatusIcon';
|
|
13
|
+
export default TreeStatusIcon;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
export declare const sortChildren: (children: ReactNode | undefined, folderComponentType: React.ElementType) => (string | number | React.ReactElement<any, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal)[];
|
|
3
|
+
export declare const makeChildPath: (name: string, parentPath?: string) => string;
|
|
4
|
+
export declare const stopPropagation: (event: React.MouseEvent) => void;
|
|
5
|
+
export declare const setChildrenProps: (children: ReactNode | undefined, props: Record<string, unknown>, targetComponents?: Array<React.ElementType>) => ReactNode | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export const sortChildren = (children, folderComponentType) => {
|
|
3
|
+
return React.Children.toArray(children).sort((a, b) => {
|
|
4
|
+
if (!React.isValidElement(a) || !React.isValidElement(b))
|
|
5
|
+
return 0;
|
|
6
|
+
if (a.type !== b.type)
|
|
7
|
+
return a.type !== folderComponentType ? 1 : -1;
|
|
8
|
+
return `${a.props.name}`.charCodeAt(0) - `${b.props.name}`.charCodeAt(0);
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
export const makeChildPath = (name, parentPath) => {
|
|
12
|
+
if (!parentPath)
|
|
13
|
+
return name;
|
|
14
|
+
return `${parentPath}/${name}`;
|
|
15
|
+
};
|
|
16
|
+
export const stopPropagation = (event) => {
|
|
17
|
+
event.stopPropagation();
|
|
18
|
+
event.nativeEvent.stopImmediatePropagation();
|
|
19
|
+
};
|
|
20
|
+
export const setChildrenProps = (children, props, targetComponents = []) => {
|
|
21
|
+
if (React.Children.count(children) === 0)
|
|
22
|
+
return [];
|
|
23
|
+
const allowAll = targetComponents.length === 0;
|
|
24
|
+
const clone = (child, props = {}) => React.cloneElement(child, props);
|
|
25
|
+
return React.Children.map(children, (item) => {
|
|
26
|
+
if (!React.isValidElement(item))
|
|
27
|
+
return item;
|
|
28
|
+
if (allowAll)
|
|
29
|
+
return clone(item, props);
|
|
30
|
+
const isAllowed = targetComponents.find((child) => child === item.type);
|
|
31
|
+
if (isAllowed)
|
|
32
|
+
return clone(item, props);
|
|
33
|
+
return item;
|
|
34
|
+
});
|
|
35
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { PresetConfigMutator, RemarkCodeBlockToGlobalComponentPluginFactory, } from 'rspress-plugin-devkit';
|
|
3
|
+
import { parseInput } from './parser';
|
|
4
|
+
export default function rspressPluginFileTree(options = {}) {
|
|
5
|
+
const { initialExpandDepth = 0 } = options;
|
|
6
|
+
const remarkFileTree = new RemarkCodeBlockToGlobalComponentPluginFactory({
|
|
7
|
+
components: [
|
|
8
|
+
{
|
|
9
|
+
lang: 'tree',
|
|
10
|
+
componentPath: path.join(__dirname, './components/Tree/FileTreeRender.tsx'),
|
|
11
|
+
propsProvider(code) {
|
|
12
|
+
return {
|
|
13
|
+
tree: parseInput(code),
|
|
14
|
+
initialExpandDepth,
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
name: 'rspress-plugin-file-tree',
|
|
22
|
+
config(config) {
|
|
23
|
+
return new PresetConfigMutator(config).disableMdxRs().toConfig();
|
|
24
|
+
},
|
|
25
|
+
markdown: {
|
|
26
|
+
remarkPlugins: [remarkFileTree.remarkPlugin],
|
|
27
|
+
globalComponents: remarkFileTree.mdxComponents,
|
|
28
|
+
},
|
|
29
|
+
builderConfig: remarkFileTree.builderConfig,
|
|
30
|
+
};
|
|
31
|
+
}
|