@ai-group/chat-sdk 3.6.0 → 3.6.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/dist/cjs/components/FolderTree/DirectoryTree.d.ts +6 -0
- package/dist/cjs/components/FolderTree/DirectoryTree.js +5 -2
- package/dist/cjs/components/FolderTree/DirectoryTree.js.map +2 -2
- package/dist/cjs/components/FolderTree/index.js +28 -27
- package/dist/cjs/components/FolderTree/index.js.map +2 -2
- package/dist/cjs/components/FolderTree/types.d.ts +5 -0
- package/dist/cjs/components/FolderTree/types.js.map +1 -1
- package/dist/cjs/components/XAdkProvider/index.js +2 -0
- package/dist/cjs/components/XAdkProvider/index.js.map +2 -2
- package/dist/cjs/hooks/useADKChat.js +8 -6
- package/dist/cjs/hooks/useADKChat.js.map +2 -2
- package/dist/esm/components/FolderTree/DirectoryTree.d.ts +6 -0
- package/dist/esm/components/FolderTree/DirectoryTree.js +6 -2
- package/dist/esm/components/FolderTree/DirectoryTree.js.map +1 -1
- package/dist/esm/components/FolderTree/index.js +32 -28
- package/dist/esm/components/FolderTree/index.js.map +1 -1
- package/dist/esm/components/FolderTree/types.d.ts +5 -0
- package/dist/esm/components/FolderTree/types.js.map +1 -1
- package/dist/esm/components/XAdkProvider/index.js +1 -1
- package/dist/esm/components/XAdkProvider/index.js.map +1 -1
- package/dist/esm/hooks/useADKChat.js +8 -6
- package/dist/esm/hooks/useADKChat.js.map +1 -1
- package/dist/umd/chat-sdk.min.js +1 -1
- package/package.json +1 -1
|
@@ -29,6 +29,12 @@ export interface DirectoryTreeComponentProps {
|
|
|
29
29
|
createInfo?: CreateNodeContext | null;
|
|
30
30
|
/** 开始重命名回调(由父组件注入) */
|
|
31
31
|
onStartRename?: (fullPath: string, node: FolderTreeData) => void;
|
|
32
|
+
/** 创建节点回调(由父组件注入) */
|
|
33
|
+
onCreateNode?: (parentPath: string, options: {
|
|
34
|
+
type: "file" | "folder";
|
|
35
|
+
defaultName?: string;
|
|
36
|
+
extra?: any;
|
|
37
|
+
}) => void;
|
|
32
38
|
}
|
|
33
39
|
declare const DirectoryTreeComponent: React.FC<DirectoryTreeComponentProps>;
|
|
34
40
|
export default DirectoryTreeComponent;
|
|
@@ -106,7 +106,8 @@ var DirectoryTreeComponent = ({
|
|
|
106
106
|
onEditConfirm,
|
|
107
107
|
onEditCancel,
|
|
108
108
|
createInfo,
|
|
109
|
-
onStartRename
|
|
109
|
+
onStartRename,
|
|
110
|
+
onCreateNode
|
|
110
111
|
}) => {
|
|
111
112
|
const styles = (0, import_styles.useStyles)();
|
|
112
113
|
const getIcon = (0, import_react.useCallback)(
|
|
@@ -262,7 +263,8 @@ var DirectoryTreeComponent = ({
|
|
|
262
263
|
import_antd.Dropdown,
|
|
263
264
|
{
|
|
264
265
|
menu: moreActions(node, {
|
|
265
|
-
startRename: () => onStartRename == null ? void 0 : onStartRename(fullPath, node)
|
|
266
|
+
startRename: () => onStartRename == null ? void 0 : onStartRename(fullPath, node),
|
|
267
|
+
createNode: (options) => onCreateNode == null ? void 0 : onCreateNode(fullPath, options)
|
|
266
268
|
}),
|
|
267
269
|
trigger: ["click"],
|
|
268
270
|
placement: "bottomRight",
|
|
@@ -332,6 +334,7 @@ var DirectoryTreeComponent = ({
|
|
|
332
334
|
renderInlineInput,
|
|
333
335
|
createInfo,
|
|
334
336
|
onStartRename,
|
|
337
|
+
onCreateNode,
|
|
335
338
|
dragMode,
|
|
336
339
|
canDragNode,
|
|
337
340
|
onDragStart,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/FolderTree/DirectoryTree.tsx"],
|
|
4
|
-
"sourcesContent": ["import React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { Tree, Dropdown, Input } from \"antd\";\nimport { FileOutlined, FolderOutlined, MoreOutlined } from \"@ant-design/icons\";\nimport type { TreeProps } from \"antd\";\nimport type { DataNode } from \"antd/es/tree\";\nimport clsx from \"clsx\";\nimport type {\n FolderTreeData,\n FolderTreeProps,\n EditNodeInfo,\n CreateNodeContext,\n} from \"./types\";\nimport { useStyles } from \"./styles\";\nimport { useFolderDrag, type DropPosition } from \"./useFolderDrag\";\n\nconst { DirectoryTree: AntDirectoryTree } = Tree;\n\nexport interface DirectoryTreeComponentProps {\n treeData: FolderTreeData[];\n directoryIcons?: FolderTreeProps[\"directoryIcons\"];\n selectedKeys?: string[];\n expandedKeys?: string[];\n onSelect?: TreeProps[\"onSelect\"];\n onExpand?: TreeProps[\"onExpand\"];\n showLine?: FolderTreeProps[\"showLine\"];\n switcherIcon?: FolderTreeProps[\"switcherIcon\"];\n defaultExpandAll?: boolean;\n className?: string;\n directoryTitle?: FolderTreeProps[\"directoryTitle\"];\n moreActions?: FolderTreeProps[\"moreActions\"];\n width?: number | string;\n draggable?: FolderTreeProps[\"draggable\"];\n onDrop?: FolderTreeProps[\"onDrop\"];\n /** 当前编辑节点信息 */\n editNode?: EditNodeInfo | null;\n /** 编辑值变化回调 */\n onEditValueChange?: (value: string) => void;\n /** 编辑确认回调 */\n onEditConfirm?: (value?: string) => void;\n /** 编辑取消回调 */\n onEditCancel?: () => void;\n /** 新建上下文 */\n createInfo?: CreateNodeContext | null;\n /** 开始重命名回调(由父组件注入) */\n onStartRename?: (fullPath: string, node: FolderTreeData) => void;\n}\n\ninterface InlineEditInputProps {\n editKey: string;\n value: string;\n className?: string;\n onValueChange?: (value: string) => void;\n onConfirm?: (value?: string) => void;\n onCancel?: () => void;\n}\n\nconst InlineEditInput: React.FC<InlineEditInputProps> = ({\n editKey,\n value,\n className,\n onValueChange,\n onConfirm,\n onCancel,\n}) => {\n const [draft, setDraft] = useState(value);\n const committedRef = useRef(false);\n\n useEffect(() => {\n setDraft(value);\n committedRef.current = false;\n }, [editKey, value]);\n\n const commit = useCallback(() => {\n if (committedRef.current) return;\n committedRef.current = true;\n onValueChange?.(draft);\n onConfirm?.(draft);\n }, [draft, onConfirm, onValueChange]);\n\n return (\n <Input\n size=\"small\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onBlur={commit}\n onPressEnter={commit}\n onKeyDown={(e) => {\n e.stopPropagation();\n if (e.key === \"Escape\") {\n committedRef.current = true;\n onCancel?.();\n }\n }}\n autoFocus\n className={className}\n onClick={(e) => e.stopPropagation()}\n onMouseDown={(e) => e.stopPropagation()}\n onSelect={(e) => e.stopPropagation()}\n onFocus={(e) => e.stopPropagation()}\n />\n );\n};\n\nconst DirectoryTreeComponent: React.FC<DirectoryTreeComponentProps> = ({\n treeData,\n selectedKeys,\n expandedKeys,\n onSelect,\n onExpand,\n showLine = false,\n switcherIcon,\n defaultExpandAll = true,\n className,\n directoryIcons,\n directoryTitle,\n moreActions,\n width,\n draggable,\n onDrop,\n editNode,\n onEditValueChange,\n onEditConfirm,\n onEditCancel,\n createInfo,\n onStartRename,\n}) => {\n const styles = useStyles();\n\n const getIcon = useCallback(\n (node: FolderTreeData) => {\n if (directoryIcons === false || directoryIcons === null) {\n return null;\n }\n if (node.type == \"folder\") {\n const icon = directoryIcons?.directory;\n if (typeof icon === \"function\") return icon();\n return icon || <FolderOutlined />;\n }\n const filePath = node.path.toLowerCase();\n const extension = filePath.split(\".\").pop();\n if (extension) {\n const icon = directoryIcons?.[extension];\n if (icon) return typeof icon === \"function\" ? icon() : icon;\n }\n return <FileOutlined />;\n },\n [directoryIcons],\n );\n\n const buildPathSegments = useCallback(\n (node: FolderTreeData, parentSegments: string[] = []): string[] => {\n if (node.path === \"/\" && parentSegments.length === 0) {\n return [\"/\"];\n }\n return [...parentSegments, node.path].filter((s) => s !== \"\");\n },\n [],\n );\n\n const renderInlineInput = useCallback(\n (key: string, value: string) => (\n <span className={styles.treeNodeTitle}>\n <InlineEditInput\n editKey={key}\n value={value}\n className={styles.inlineInput}\n onValueChange={onEditValueChange}\n onConfirm={onEditConfirm}\n onCancel={onEditCancel}\n />\n </span>\n ),\n [onEditValueChange, onEditConfirm, onEditCancel, styles],\n );\n\n // ====== 拖拽模式 ======\n const dragMode = draggable === true ? \"file\" : draggable || false;\n\n // ====== 自定义拖拽 hook ======\n const handleDragDrop = useCallback(\n (dragNode: DataNode, dropNode: DataNode, dropPosition: DropPosition) => {\n const dragNd = dragNode as DataNode & { _originalNode?: FolderTreeData };\n const dropNd = dropNode as DataNode & { _originalNode?: FolderTreeData };\n const originalDragNode = dragNd._originalNode;\n const originalDropNode = dropNd._originalNode;\n if (!originalDragNode) return;\n\n const dragKey = String(dragNode.key);\n const dropKey = String(dropNode.key);\n const dragFileName = originalDragNode.path;\n let newPath: string;\n\n // 拖到底部 drop zone:放在根级别末尾\n if (!originalDropNode && dropKey === \"__bottom_placeholder__\") {\n newPath = dragFileName;\n onDrop?.({\n fileName: originalDragNode.title,\n extra: originalDragNode.extra,\n oldPath: dragKey,\n newPath,\n dragNode: originalDragNode,\n dropNode: { title: \"\", path: \"\" },\n });\n return;\n }\n\n if (!originalDropNode) return;\n\n if (!dropNode.isLeaf && dropPosition === 0) {\n // 拖入文件夹内部\n newPath = `${dropKey}/${dragFileName}`;\n } else {\n // 拖到节点旁边(同级)\n const dropParentPath = dropKey.includes(\"/\")\n ? dropKey.substring(0, dropKey.lastIndexOf(\"/\"))\n : \"\";\n newPath = dropParentPath\n ? `${dropParentPath}/${dragFileName}`\n : dragFileName;\n }\n\n onDrop?.({\n fileName: originalDragNode.title,\n extra: originalDragNode.extra,\n oldPath: dragKey,\n newPath,\n dragNode: originalDragNode,\n dropNode: originalDropNode,\n });\n },\n [onDrop],\n );\n\n const handleDragEnterExpand = useCallback(\n (nodeKey: string) => {\n const isExpanded = expandedKeys?.includes(nodeKey);\n if (!isExpanded) {\n onExpand?.([...(expandedKeys || []), nodeKey], {\n node: null as any,\n expanded: true,\n nativeEvent: null as any,\n });\n }\n },\n [expandedKeys, onExpand],\n );\n\n const {\n onDragStart,\n onDragEnd,\n onItemDragEnter,\n onItemDragOver,\n onItemDragLeave,\n onItemDrop,\n onContainerDragOver,\n onContainerDrop,\n canDragNode,\n } = useFolderDrag({\n dragMode,\n onDrop: handleDragDrop,\n onDragEnterExpand: handleDragEnterExpand,\n });\n\n // ====== 转换树数据 ======\n // 注意:convertToTreeData 不依赖拖拽状态,拖拽效果通过 DOM 操作实现\n const convertToTreeData = useCallback(\n (\n nodes: FolderTreeData[],\n parentSegments: string[] = [],\n parentKey: string = \"\",\n ): DataNode[] => {\n const result = nodes.map((node) => {\n const pathSegments = buildPathSegments(node, parentSegments);\n const fullPath = pathSegments.join(\"/\").replace(/^\\/+/, \"\");\n\n const isEditing = editNode && editNode.key === fullPath;\n\n const nodeChildren = node.children\n ? convertToTreeData(node.children, pathSegments, fullPath)\n : undefined;\n\n const baseNode: any = {\n key: fullPath,\n path: fullPath,\n pathSegments,\n icon: getIcon(node),\n isLeaf: !(node.type === \"folder\"),\n _originalNode: node,\n children: nodeChildren,\n };\n\n // 编辑态:直接渲染 inline input\n if (isEditing) {\n baseNode.title = renderInlineInput(\n editNode!.key,\n editNode!.currentValue,\n );\n return baseNode;\n }\n\n // title 内容(按钮、菜单等)\n const titleContent = (\n <span\n className={clsx(\n styles.treeNodeTitle,\n moreActions && styles.treeNodeTitleHover,\n )}\n >\n <span>{node.title}</span>\n {moreActions && (\n <Dropdown\n menu={moreActions(node, {\n startRename: () => onStartRename?.(fullPath, node),\n })}\n trigger={[\"click\"]}\n placement=\"bottomRight\"\n >\n <span\n className={clsx(styles.moreIcon, \"folder-tree-more-icon\")}\n onClick={(e) => e.stopPropagation()}\n >\n <MoreOutlined />\n </span>\n </Dropdown>\n )}\n </span>\n );\n\n // 启用拖拽时:在 title 外层包裹具有 data-node-key 属性的容器,\n // 用于 DOM 查询和事件绑定。拖拽状态通过 CSS class 控制。\n if (dragMode) {\n const nodeData = baseNode as DataNode & {\n _isTempNode?: boolean;\n };\n const draggableDiv = canDragNode(nodeData);\n\n baseNode.title = (\n <div\n className={clsx(\n styles.treeNodeWrapper,\n draggableDiv && styles.dragNodeHandle,\n )}\n data-node-key={fullPath}\n draggable={draggableDiv}\n onDragStart={\n draggableDiv ? (e) => onDragStart(e, baseNode) : undefined\n }\n onDragEnd={draggableDiv ? onDragEnd : undefined}\n onDragEnter={(e) => onItemDragEnter(e, baseNode)}\n onDragOver={(e) => onItemDragOver(e, baseNode)}\n onDragLeave={onItemDragLeave}\n onDrop={(e) => onItemDrop(e, baseNode)}\n >\n {titleContent}\n </div>\n );\n } else {\n baseNode.title = titleContent;\n }\n\n return baseNode;\n });\n\n // 新建模式:在目标父节点下插入临时节点\n if (createInfo && editNode && editNode.mode === \"create\") {\n const targetParentKey = createInfo.parentKey;\n if (\n targetParentKey === parentKey ||\n (targetParentKey === \"\" && parentKey === \"\") ||\n (targetParentKey === \"/\" && parentKey === \"\")\n ) {\n const tempNode: any = {\n key: editNode.key,\n title: renderInlineInput(editNode.key, editNode.currentValue),\n icon:\n createInfo.type === \"folder\" ? (\n <FolderOutlined />\n ) : (\n <FileOutlined />\n ),\n isLeaf: createInfo.type === \"file\",\n path: editNode.key,\n pathSegments: [editNode.key],\n _originalNode: null,\n _isTempNode: true,\n };\n result.push(tempNode);\n }\n }\n\n return result;\n },\n [\n buildPathSegments,\n getIcon,\n moreActions,\n styles,\n editNode,\n renderInlineInput,\n createInfo,\n onStartRename,\n dragMode,\n canDragNode,\n onDragStart,\n onDragEnd,\n onItemDragEnter,\n onItemDragOver,\n onItemDragLeave,\n onItemDrop,\n ],\n );\n\n const treeDataConverted = useMemo(\n () => convertToTreeData(treeData, [], \"\"),\n [treeData, convertToTreeData],\n );\n\n const handleSelect: TreeProps[\"onSelect\"] = useCallback(\n (keys: any, info: any) => {\n // 编辑态不响应选中\n if (editNode) return;\n // 临时节点不可选中\n const node = info.node as DataNode & { _isTempNode?: boolean };\n if (node._isTempNode) return;\n // 点击 moreActions 区域不触发选中\n const target = info.nativeEvent?.target as HTMLElement | null;\n if (\n target &&\n (target.closest(\".folder-tree-more-icon\") ||\n target.closest(\".ant-dropdown\"))\n ) {\n return;\n }\n onSelect?.(keys, info);\n },\n [onSelect, editNode],\n );\n\n const titleNode =\n directoryTitle === false || directoryTitle === null\n ? null\n : typeof directoryTitle === \"function\"\n ? directoryTitle()\n : directoryTitle;\n\n return (\n <div className={clsx(styles.directoryPanel, className)} style={{ width }}>\n {titleNode && <div className={styles.directoryTitle}>{titleNode}</div>}\n <div\n className={styles.directoryTreeContent}\n onDragOver={dragMode ? onContainerDragOver : undefined}\n onDrop={dragMode ? onContainerDrop : undefined}\n >\n <AntDirectoryTree\n style={\n {\n \"--ant-tree-directory-node-selected-bg\": \"rgba(0,0,0,0.03)\",\n \"--ant-tree-directory-node-selected-color\": \"#1a1c1e\",\n } as React.CSSProperties\n }\n treeData={treeDataConverted}\n selectedKeys={selectedKeys}\n expandedKeys={expandedKeys}\n onSelect={handleSelect}\n onExpand={onExpand}\n multiple={false}\n blockNode\n showLine={showLine as any}\n switcherIcon={switcherIcon}\n defaultExpandAll={defaultExpandAll}\n draggable={false}\n />\n </div>\n </div>\n );\n};\n\nexport default DirectoryTreeComponent;\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAMO;AACP,kBAAsC;AACtC,mBAA2D;AAG3D,kBAAiB;AAOjB,oBAA0B;AAC1B,2BAAiD;
|
|
4
|
+
"sourcesContent": ["import React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { Tree, Dropdown, Input } from \"antd\";\nimport { FileOutlined, FolderOutlined, MoreOutlined } from \"@ant-design/icons\";\nimport type { TreeProps } from \"antd\";\nimport type { DataNode } from \"antd/es/tree\";\nimport clsx from \"clsx\";\nimport type {\n FolderTreeData,\n FolderTreeProps,\n EditNodeInfo,\n CreateNodeContext,\n} from \"./types\";\nimport { useStyles } from \"./styles\";\nimport { useFolderDrag, type DropPosition } from \"./useFolderDrag\";\n\nconst { DirectoryTree: AntDirectoryTree } = Tree;\n\nexport interface DirectoryTreeComponentProps {\n treeData: FolderTreeData[];\n directoryIcons?: FolderTreeProps[\"directoryIcons\"];\n selectedKeys?: string[];\n expandedKeys?: string[];\n onSelect?: TreeProps[\"onSelect\"];\n onExpand?: TreeProps[\"onExpand\"];\n showLine?: FolderTreeProps[\"showLine\"];\n switcherIcon?: FolderTreeProps[\"switcherIcon\"];\n defaultExpandAll?: boolean;\n className?: string;\n directoryTitle?: FolderTreeProps[\"directoryTitle\"];\n moreActions?: FolderTreeProps[\"moreActions\"];\n width?: number | string;\n draggable?: FolderTreeProps[\"draggable\"];\n onDrop?: FolderTreeProps[\"onDrop\"];\n /** 当前编辑节点信息 */\n editNode?: EditNodeInfo | null;\n /** 编辑值变化回调 */\n onEditValueChange?: (value: string) => void;\n /** 编辑确认回调 */\n onEditConfirm?: (value?: string) => void;\n /** 编辑取消回调 */\n onEditCancel?: () => void;\n /** 新建上下文 */\n createInfo?: CreateNodeContext | null;\n /** 开始重命名回调(由父组件注入) */\n onStartRename?: (fullPath: string, node: FolderTreeData) => void;\n /** 创建节点回调(由父组件注入) */\n onCreateNode?: (\n parentPath: string,\n options: { type: \"file\" | \"folder\"; defaultName?: string; extra?: any },\n ) => void;\n}\n\ninterface InlineEditInputProps {\n editKey: string;\n value: string;\n className?: string;\n onValueChange?: (value: string) => void;\n onConfirm?: (value?: string) => void;\n onCancel?: () => void;\n}\n\nconst InlineEditInput: React.FC<InlineEditInputProps> = ({\n editKey,\n value,\n className,\n onValueChange,\n onConfirm,\n onCancel,\n}) => {\n const [draft, setDraft] = useState(value);\n const committedRef = useRef(false);\n\n useEffect(() => {\n setDraft(value);\n committedRef.current = false;\n }, [editKey, value]);\n\n const commit = useCallback(() => {\n if (committedRef.current) return;\n committedRef.current = true;\n onValueChange?.(draft);\n onConfirm?.(draft);\n }, [draft, onConfirm, onValueChange]);\n\n return (\n <Input\n size=\"small\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onBlur={commit}\n onPressEnter={commit}\n onKeyDown={(e) => {\n e.stopPropagation();\n if (e.key === \"Escape\") {\n committedRef.current = true;\n onCancel?.();\n }\n }}\n autoFocus\n className={className}\n onClick={(e) => e.stopPropagation()}\n onMouseDown={(e) => e.stopPropagation()}\n onSelect={(e) => e.stopPropagation()}\n onFocus={(e) => e.stopPropagation()}\n />\n );\n};\n\nconst DirectoryTreeComponent: React.FC<DirectoryTreeComponentProps> = ({\n treeData,\n selectedKeys,\n expandedKeys,\n onSelect,\n onExpand,\n showLine = false,\n switcherIcon,\n defaultExpandAll = true,\n className,\n directoryIcons,\n directoryTitle,\n moreActions,\n width,\n draggable,\n onDrop,\n editNode,\n onEditValueChange,\n onEditConfirm,\n onEditCancel,\n createInfo,\n onStartRename,\n onCreateNode,\n}) => {\n const styles = useStyles();\n\n const getIcon = useCallback(\n (node: FolderTreeData) => {\n if (directoryIcons === false || directoryIcons === null) {\n return null;\n }\n if (node.type == \"folder\") {\n const icon = directoryIcons?.directory;\n if (typeof icon === \"function\") return icon();\n return icon || <FolderOutlined />;\n }\n const filePath = node.path.toLowerCase();\n const extension = filePath.split(\".\").pop();\n if (extension) {\n const icon = directoryIcons?.[extension];\n if (icon) return typeof icon === \"function\" ? icon() : icon;\n }\n return <FileOutlined />;\n },\n [directoryIcons],\n );\n\n const buildPathSegments = useCallback(\n (node: FolderTreeData, parentSegments: string[] = []): string[] => {\n if (node.path === \"/\" && parentSegments.length === 0) {\n return [\"/\"];\n }\n return [...parentSegments, node.path].filter((s) => s !== \"\");\n },\n [],\n );\n\n const renderInlineInput = useCallback(\n (key: string, value: string) => (\n <span className={styles.treeNodeTitle}>\n <InlineEditInput\n editKey={key}\n value={value}\n className={styles.inlineInput}\n onValueChange={onEditValueChange}\n onConfirm={onEditConfirm}\n onCancel={onEditCancel}\n />\n </span>\n ),\n [onEditValueChange, onEditConfirm, onEditCancel, styles],\n );\n\n // ====== 拖拽模式 ======\n const dragMode = draggable === true ? \"file\" : draggable || false;\n\n // ====== 自定义拖拽 hook ======\n const handleDragDrop = useCallback(\n (dragNode: DataNode, dropNode: DataNode, dropPosition: DropPosition) => {\n const dragNd = dragNode as DataNode & { _originalNode?: FolderTreeData };\n const dropNd = dropNode as DataNode & { _originalNode?: FolderTreeData };\n const originalDragNode = dragNd._originalNode;\n const originalDropNode = dropNd._originalNode;\n if (!originalDragNode) return;\n\n const dragKey = String(dragNode.key);\n const dropKey = String(dropNode.key);\n const dragFileName = originalDragNode.path;\n let newPath: string;\n\n // 拖到底部 drop zone:放在根级别末尾\n if (!originalDropNode && dropKey === \"__bottom_placeholder__\") {\n newPath = dragFileName;\n onDrop?.({\n fileName: originalDragNode.title,\n extra: originalDragNode.extra,\n oldPath: dragKey,\n newPath,\n dragNode: originalDragNode,\n dropNode: { title: \"\", path: \"\" },\n });\n return;\n }\n\n if (!originalDropNode) return;\n\n if (!dropNode.isLeaf && dropPosition === 0) {\n // 拖入文件夹内部\n newPath = `${dropKey}/${dragFileName}`;\n } else {\n // 拖到节点旁边(同级)\n const dropParentPath = dropKey.includes(\"/\")\n ? dropKey.substring(0, dropKey.lastIndexOf(\"/\"))\n : \"\";\n newPath = dropParentPath\n ? `${dropParentPath}/${dragFileName}`\n : dragFileName;\n }\n\n onDrop?.({\n fileName: originalDragNode.title,\n extra: originalDragNode.extra,\n oldPath: dragKey,\n newPath,\n dragNode: originalDragNode,\n dropNode: originalDropNode,\n });\n },\n [onDrop],\n );\n\n const handleDragEnterExpand = useCallback(\n (nodeKey: string) => {\n const isExpanded = expandedKeys?.includes(nodeKey);\n if (!isExpanded) {\n onExpand?.([...(expandedKeys || []), nodeKey], {\n node: null as any,\n expanded: true,\n nativeEvent: null as any,\n });\n }\n },\n [expandedKeys, onExpand],\n );\n\n const {\n onDragStart,\n onDragEnd,\n onItemDragEnter,\n onItemDragOver,\n onItemDragLeave,\n onItemDrop,\n onContainerDragOver,\n onContainerDrop,\n canDragNode,\n } = useFolderDrag({\n dragMode,\n onDrop: handleDragDrop,\n onDragEnterExpand: handleDragEnterExpand,\n });\n\n // ====== 转换树数据 ======\n // 注意:convertToTreeData 不依赖拖拽状态,拖拽效果通过 DOM 操作实现\n const convertToTreeData = useCallback(\n (\n nodes: FolderTreeData[],\n parentSegments: string[] = [],\n parentKey: string = \"\",\n ): DataNode[] => {\n const result = nodes.map((node) => {\n const pathSegments = buildPathSegments(node, parentSegments);\n const fullPath = pathSegments.join(\"/\").replace(/^\\/+/, \"\");\n\n const isEditing = editNode && editNode.key === fullPath;\n\n const nodeChildren = node.children\n ? convertToTreeData(node.children, pathSegments, fullPath)\n : undefined;\n\n const baseNode: any = {\n key: fullPath,\n path: fullPath,\n pathSegments,\n icon: getIcon(node),\n isLeaf: !(node.type === \"folder\"),\n _originalNode: node,\n children: nodeChildren,\n };\n\n // 编辑态:直接渲染 inline input\n if (isEditing) {\n baseNode.title = renderInlineInput(\n editNode!.key,\n editNode!.currentValue,\n );\n return baseNode;\n }\n\n // title 内容(按钮、菜单等)\n const titleContent = (\n <span\n className={clsx(\n styles.treeNodeTitle,\n moreActions && styles.treeNodeTitleHover,\n )}\n >\n <span>{node.title}</span>\n {moreActions && (\n <Dropdown\n menu={moreActions(node, {\n startRename: () => onStartRename?.(fullPath, node),\n createNode: (options) => onCreateNode?.(fullPath, options),\n })}\n trigger={[\"click\"]}\n placement=\"bottomRight\"\n >\n <span\n className={clsx(styles.moreIcon, \"folder-tree-more-icon\")}\n onClick={(e) => e.stopPropagation()}\n >\n <MoreOutlined />\n </span>\n </Dropdown>\n )}\n </span>\n );\n\n // 启用拖拽时:在 title 外层包裹具有 data-node-key 属性的容器,\n // 用于 DOM 查询和事件绑定。拖拽状态通过 CSS class 控制。\n if (dragMode) {\n const nodeData = baseNode as DataNode & {\n _isTempNode?: boolean;\n };\n const draggableDiv = canDragNode(nodeData);\n\n baseNode.title = (\n <div\n className={clsx(\n styles.treeNodeWrapper,\n draggableDiv && styles.dragNodeHandle,\n )}\n data-node-key={fullPath}\n draggable={draggableDiv}\n onDragStart={\n draggableDiv ? (e) => onDragStart(e, baseNode) : undefined\n }\n onDragEnd={draggableDiv ? onDragEnd : undefined}\n onDragEnter={(e) => onItemDragEnter(e, baseNode)}\n onDragOver={(e) => onItemDragOver(e, baseNode)}\n onDragLeave={onItemDragLeave}\n onDrop={(e) => onItemDrop(e, baseNode)}\n >\n {titleContent}\n </div>\n );\n } else {\n baseNode.title = titleContent;\n }\n\n return baseNode;\n });\n\n // 新建模式:在目标父节点下插入临时节点\n if (createInfo && editNode && editNode.mode === \"create\") {\n const targetParentKey = createInfo.parentKey;\n if (\n targetParentKey === parentKey ||\n (targetParentKey === \"\" && parentKey === \"\") ||\n (targetParentKey === \"/\" && parentKey === \"\")\n ) {\n const tempNode: any = {\n key: editNode.key,\n title: renderInlineInput(editNode.key, editNode.currentValue),\n icon:\n createInfo.type === \"folder\" ? (\n <FolderOutlined />\n ) : (\n <FileOutlined />\n ),\n isLeaf: createInfo.type === \"file\",\n path: editNode.key,\n pathSegments: [editNode.key],\n _originalNode: null,\n _isTempNode: true,\n };\n result.push(tempNode);\n }\n }\n\n return result;\n },\n [\n buildPathSegments,\n getIcon,\n moreActions,\n styles,\n editNode,\n renderInlineInput,\n createInfo,\n onStartRename,\n onCreateNode,\n dragMode,\n canDragNode,\n onDragStart,\n onDragEnd,\n onItemDragEnter,\n onItemDragOver,\n onItemDragLeave,\n onItemDrop,\n ],\n );\n\n const treeDataConverted = useMemo(\n () => convertToTreeData(treeData, [], \"\"),\n [treeData, convertToTreeData],\n );\n\n const handleSelect: TreeProps[\"onSelect\"] = useCallback(\n (keys: any, info: any) => {\n // 编辑态不响应选中\n if (editNode) return;\n // 临时节点不可选中\n const node = info.node as DataNode & { _isTempNode?: boolean };\n if (node._isTempNode) return;\n // 点击 moreActions 区域不触发选中\n const target = info.nativeEvent?.target as HTMLElement | null;\n if (\n target &&\n (target.closest(\".folder-tree-more-icon\") ||\n target.closest(\".ant-dropdown\"))\n ) {\n return;\n }\n onSelect?.(keys, info);\n },\n [onSelect, editNode],\n );\n\n const titleNode =\n directoryTitle === false || directoryTitle === null\n ? null\n : typeof directoryTitle === \"function\"\n ? directoryTitle()\n : directoryTitle;\n\n return (\n <div className={clsx(styles.directoryPanel, className)} style={{ width }}>\n {titleNode && <div className={styles.directoryTitle}>{titleNode}</div>}\n <div\n className={styles.directoryTreeContent}\n onDragOver={dragMode ? onContainerDragOver : undefined}\n onDrop={dragMode ? onContainerDrop : undefined}\n >\n <AntDirectoryTree\n style={\n {\n \"--ant-tree-directory-node-selected-bg\": \"rgba(0,0,0,0.03)\",\n \"--ant-tree-directory-node-selected-color\": \"#1a1c1e\",\n } as React.CSSProperties\n }\n treeData={treeDataConverted}\n selectedKeys={selectedKeys}\n expandedKeys={expandedKeys}\n onSelect={handleSelect}\n onExpand={onExpand}\n multiple={false}\n blockNode\n showLine={showLine as any}\n switcherIcon={switcherIcon}\n defaultExpandAll={defaultExpandAll}\n draggable={false}\n />\n </div>\n </div>\n );\n};\n\nexport default DirectoryTreeComponent;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["clsx"]
|
|
7
7
|
}
|
|
@@ -283,37 +283,37 @@ var FolderTree = (0, import_react.forwardRef)((props, ref) => {
|
|
|
283
283
|
}
|
|
284
284
|
onExpandedPathsChange == null ? void 0 : onExpandedPathsChange(newPaths);
|
|
285
285
|
};
|
|
286
|
-
(0, import_react.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
286
|
+
const internalCreateNode = (0, import_react.useCallback)(
|
|
287
|
+
(parentPath, options) => {
|
|
288
|
+
const defaultName = options.defaultName || (options.type === "folder" ? "新建文件夹" : "新建文件");
|
|
289
|
+
const tempKey = `__create__${parentPath || "root"}__${Date.now()}`;
|
|
290
|
+
setCreateContext({
|
|
291
|
+
parentKey: parentPath,
|
|
292
|
+
type: options.type,
|
|
293
|
+
extra: options.extra
|
|
294
|
+
});
|
|
295
|
+
setEditNode({
|
|
296
|
+
key: tempKey,
|
|
297
|
+
type: options.type || "folder",
|
|
298
|
+
originalTitle: "",
|
|
299
|
+
currentValue: defaultName,
|
|
300
|
+
mode: "create"
|
|
301
|
+
});
|
|
302
|
+
if (!controlled && parentPath && parentPath !== "/") {
|
|
303
|
+
setExpandedPathsState((prev) => {
|
|
304
|
+
const current = prev || [];
|
|
305
|
+
if (current.includes(parentPath)) {
|
|
306
|
+
return current;
|
|
307
|
+
}
|
|
308
|
+
return [...current, parentPath];
|
|
303
309
|
});
|
|
304
|
-
if (!controlled && parentPath && parentPath !== "/") {
|
|
305
|
-
setExpandedPathsState((prev) => {
|
|
306
|
-
const current = prev || [];
|
|
307
|
-
if (current.includes(parentPath)) {
|
|
308
|
-
return current;
|
|
309
|
-
}
|
|
310
|
-
return [...current, parentPath];
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
310
|
}
|
|
314
|
-
}
|
|
311
|
+
},
|
|
315
312
|
[controlled]
|
|
316
313
|
);
|
|
314
|
+
(0, import_react.useImperativeHandle)(ref, () => ({ createNode: internalCreateNode }), [
|
|
315
|
+
internalCreateNode
|
|
316
|
+
]);
|
|
317
317
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
318
318
|
"div",
|
|
319
319
|
{
|
|
@@ -354,6 +354,7 @@ var FolderTree = (0, import_react.forwardRef)((props, ref) => {
|
|
|
354
354
|
onEditCancel: handleEditCancel,
|
|
355
355
|
createInfo: createContext,
|
|
356
356
|
onStartRename: handleStartRename,
|
|
357
|
+
onCreateNode: internalCreateNode,
|
|
357
358
|
onDrop
|
|
358
359
|
}
|
|
359
360
|
),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/FolderTree/index.tsx"],
|
|
4
|
-
"sourcesContent": ["import React, {\n useRef,\n useMemo,\n useCallback,\n useEffect,\n useState,\n forwardRef,\n useImperativeHandle,\n} from \"react\";\nimport type { TreeProps } from \"antd\";\nimport clsx from \"clsx\";\nimport type {\n FolderTreeData,\n FolderTreeProps,\n FolderTreeRef,\n EditNodeInfo,\n CreateNodeContext,\n} from \"./types\";\nimport DirectoryTreeComponent from \"./DirectoryTree\";\nimport FilePreview from \"./FilePreview\";\nimport { useStyles } from \"./styles\";\n\nexport type {\n FolderTreeData,\n FolderTreeProps,\n FileContentService,\n FolderTreeRef,\n} from \"./types\";\n\n// 展开所有节点\nfunction getAllExpandedKeys(\n nodes: FolderTreeData[],\n parentPath = \"\",\n): string[] {\n const keys: string[] = [];\n\n nodes.forEach((node) => {\n const currentPath = parentPath ? `${parentPath}/${node.path}` : node.path;\n\n if (node.children?.length) {\n keys.push(currentPath);\n keys.push(...getAllExpandedKeys(node.children, currentPath));\n }\n });\n\n return keys;\n}\n\n// 格式化数据\nfunction normalizeTreeData(nodes: FolderTreeData[]): FolderTreeData[] {\n return nodes.map((node) => ({\n ...node,\n type: node.type\n ? node.type\n : !!node.children && node.children.length > 0\n ? \"folder\"\n : \"file\",\n children: node.children ? normalizeTreeData(node.children) : undefined,\n }));\n}\n\nconst FolderTree = forwardRef<FolderTreeRef, FolderTreeProps>((props, ref) => {\n const {\n className,\n style,\n treeData,\n directoryIcons,\n previewRender,\n directoryTitle,\n previewTitle,\n selectable = true,\n defaultSelectedFile,\n defaultExpandAll = true,\n selectedFile,\n onSelectedFileChange,\n directoryTreeWidth = 280,\n emptyRender,\n defaultExpandedPaths,\n expandedPaths,\n onExpandedPathsChange,\n onFileClick,\n onFolderClick,\n fileContentService,\n moreActions,\n showLine,\n switcherIcon,\n showPreview = true,\n draggable,\n onDrop,\n onRename,\n onCreate,\n } = props;\n\n const styles = useStyles();\n\n const normalizedTreeData = useMemo(\n () => normalizeTreeData(treeData),\n [treeData],\n );\n\n // ====== 编辑态状态 ======\n const [editNode, setEditNode] = useState<EditNodeInfo | null>(null);\n const [createContext, setCreateContext] = useState<CreateNodeContext | null>(\n null,\n );\n\n // ====== 工具函数 ======\n const findNodeAndValidate = useCallback(\n (\n path: string | string[],\n validateAsFile = false,\n ): { node: FolderTreeData | undefined; isValid: boolean } => {\n if (!path) return { node: undefined, isValid: false };\n const segments = Array.isArray(path)\n ? path.filter(Boolean)\n : path.split(\"/\").filter(Boolean);\n if (segments.length === 0) return { node: undefined, isValid: false };\n\n const findNode = (\n nodes: FolderTreeData[],\n index = 0,\n ): FolderTreeData | undefined => {\n if (index >= segments.length) return undefined;\n const currentSegment = segments[index];\n for (const node of nodes) {\n if (node.path === currentSegment) {\n return index === segments.length - 1\n ? node\n : node.children\n ? findNode(node.children, index + 1)\n : undefined;\n }\n }\n return undefined;\n };\n\n const node = findNode(normalizedTreeData);\n const isValid = validateAsFile\n ? !!node && (!node?.children || node.children.length === 0)\n : !!node;\n return { node, isValid };\n },\n [normalizedTreeData],\n );\n\n const isValidSelectedFile = (filePath?: string[]): boolean =>\n !!(\n filePath &&\n filePath.length > 0 &&\n findNodeAndValidate(filePath, true).isValid\n );\n\n // ====== 编辑态逻辑 ======\n\n // 当 normalizedTreeData 变化时,检查编辑节点是否还存在\n useEffect(() => {\n if (!editNode) return;\n const { node } = findNodeAndValidate(editNode.key);\n if (!node) {\n setEditNode(null);\n setCreateContext(null);\n }\n }, [normalizedTreeData]);\n\n // 编辑值变化\n const handleEditValueChange = useCallback((value: string) => {\n setEditNode((prev) => (prev ? { ...prev, currentValue: value } : null));\n }, []);\n\n // 确认编辑\n const handleEditConfirm = useCallback((value?: string) => {\n if (!editNode) return;\n const newValue = (value ?? editNode.currentValue).trim();\n\n if (editNode.mode === \"rename\") {\n const { node } = findNodeAndValidate(editNode.key);\n onRename?.({\n key: editNode.key,\n oldTitle: editNode.originalTitle,\n newTitle: newValue,\n node: node!,\n });\n } else if (editNode.mode === \"create\" && createContext) {\n onCreate?.({\n parentKey: createContext.parentKey,\n name: newValue,\n type: createContext.type,\n extra: createContext.extra,\n });\n }\n\n setEditNode(null);\n setCreateContext(null);\n }, [editNode, createContext, findNodeAndValidate, onRename, onCreate]);\n\n // 取消编辑\n const handleEditCancel = useCallback(() => {\n setEditNode(null);\n setCreateContext(null);\n }, []);\n\n // 开始重命名\n const handleStartRename = useCallback(\n (fullPath: string, node: FolderTreeData) => {\n const titleStr =\n typeof node.title === \"string\" ? node.title : String(node.path);\n setCreateContext(null);\n setEditNode({\n key: fullPath,\n type: node.type || \"file\",\n originalTitle: titleStr,\n currentValue: titleStr,\n mode: \"rename\",\n });\n },\n [],\n );\n\n // ====== State ======\n const [selectedFileState, setSelectedFileState] = useState<string[]>(() =>\n isValidSelectedFile(selectedFile || defaultSelectedFile)\n ? selectedFile || defaultSelectedFile || []\n : [],\n );\n const controlled = expandedPaths !== undefined;\n\n const [expandedPathsState, setExpandedPathsState] = useState<string[]>(\n defaultExpandedPaths || [],\n );\n const [validSelectedFile, setValidSelectedFile] = useState<boolean>(\n isValidSelectedFile(selectedFile || defaultSelectedFile),\n );\n const [fileContent, setFileContent] = useState<string>(\"\");\n const [loadingContent, setLoadingContent] = useState<boolean>(false);\n const initializedExpandRef = useRef(false);\n\n // 受控模式同步\n useEffect(() => {\n if (selectedFile !== undefined) {\n setSelectedFileState(selectedFile);\n setValidSelectedFile(isValidSelectedFile(selectedFile));\n }\n }, [selectedFile, isValidSelectedFile]);\n\n useEffect(() => {\n if (\n !controlled &&\n defaultExpandAll &&\n !initializedExpandRef.current &&\n normalizedTreeData.length > 0\n ) {\n initializedExpandRef.current = true;\n\n setExpandedPathsState(getAllExpandedKeys(normalizedTreeData));\n }\n }, [controlled, defaultExpandAll, normalizedTreeData]);\n\n useEffect(() => {\n if (controlled) {\n setExpandedPathsState(expandedPaths);\n }\n }, [controlled, expandedPaths]);\n\n // ====== 加载文件内容 ======\n useEffect(() => {\n const loadContent = async () => {\n if (!validSelectedFile || selectedFileState.length === 0) {\n setFileContent(\"\");\n setLoadingContent(false);\n return;\n }\n\n const filePath = selectedFileState.join(\"/\");\n const segments = filePath.split(\"/\").filter((s) => s !== \"\");\n const { node } = findNodeAndValidate(segments);\n\n if (fileContentService) {\n setLoadingContent(true);\n try {\n const content = await fileContentService.loadFileContent(filePath);\n setFileContent(content);\n } catch (error) {\n setFileContent(\n `// 加载错误: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n } finally {\n setLoadingContent(false);\n }\n } else if (node?.content) {\n setFileContent(node.content);\n setLoadingContent(false);\n } else {\n setFileContent(\"\");\n setLoadingContent(false);\n }\n };\n\n loadContent();\n }, [\n validSelectedFile,\n selectedFileState,\n normalizedTreeData,\n fileContentService,\n findNodeAndValidate,\n ]);\n\n // ====== 事件处理 ======\n const handleSelect: TreeProps[\"onSelect\"] = (_keys, info) => {\n const keys = _keys as string[];\n const nodes = Array.isArray(info.selectedNodes)\n ? info.selectedNodes\n : [info.selectedNodes];\n\n const isFolder = nodes.some((node) => !node.isLeaf);\n if (isFolder) {\n if (nodes.length === 1) {\n const node = nodes[0] as unknown as FolderTreeData;\n onFolderClick?.(node.path, node.extra);\n }\n return;\n }\n\n const pathArray = keys[0]?.split(\"/\").filter(Boolean) || [];\n if (pathArray.length === 0) return;\n\n // 从原始 normalizedTreeData 中查找节点,避免拿到 antd 包装后的 JSX title\n const { node: originalNode } = findNodeAndValidate(pathArray);\n const fileName = originalNode?.title;\n const nodeContent = originalNode?.content;\n\n onSelectedFileChange?.({\n path: pathArray,\n title: fileName,\n content: nodeContent,\n extra: originalNode?.extra,\n });\n\n const isControlled = selectedFile !== undefined;\n if (!isControlled) {\n setValidSelectedFile(true);\n setSelectedFileState(pathArray);\n }\n\n if (originalNode) {\n onFileClick?.(keys[0], originalNode.content, originalNode.extra);\n }\n };\n\n const handleExpand: TreeProps[\"onExpand\"] = (keys) => {\n const newPaths = keys as string[];\n\n if (!controlled) {\n setExpandedPathsState(newPaths);\n }\n\n onExpandedPathsChange?.(newPaths);\n };\n\n // ====== 命令式 API ======\n useImperativeHandle(\n ref,\n () => ({\n createNode: (parentPath, options) => {\n const defaultName =\n options.defaultName ||\n (options.type === \"folder\" ? \"新建文件夹\" : \"新建文件\");\n const tempKey = `__create__${parentPath || \"root\"}__${Date.now()}`;\n\n setCreateContext({\n parentKey: parentPath,\n type: options.type,\n extra: options.extra,\n });\n setEditNode({\n key: tempKey,\n type: options.type || \"folder\",\n originalTitle: \"\",\n currentValue: defaultName,\n mode: \"create\",\n });\n\n // 自动展开父文件夹(仅在受控模式下需要,非受控模式由 defaultExpandAll 处理)\n if (!controlled && parentPath && parentPath !== \"/\") {\n setExpandedPathsState((prev) => {\n const current = prev || [];\n\n if (current.includes(parentPath)) {\n return current;\n }\n\n return [...current, parentPath];\n });\n }\n },\n }),\n [controlled],\n );\n\n return (\n <div\n className={clsx(\n styles.container,\n !showPreview && styles.containerNoPreview,\n className,\n )}\n style={{\n ...style,\n ...(style?.background\n ? ({\n \"--folder-tree-directory-bg\": style.background,\n } as React.CSSProperties)\n : {}),\n ...(style?.backgroundColor\n ? ({\n \"--folder-tree-directory-bg\": style.backgroundColor,\n } as React.CSSProperties)\n : {}),\n }}\n >\n <DirectoryTreeComponent\n treeData={normalizedTreeData}\n directoryIcons={directoryIcons}\n selectedKeys={\n selectable && selectedFileState && validSelectedFile\n ? [selectedFileState.join(\"/\")]\n : []\n }\n expandedKeys={controlled ? expandedPaths : expandedPathsState}\n onSelect={handleSelect}\n onExpand={handleExpand}\n defaultExpandAll={defaultExpandAll}\n directoryTitle={directoryTitle}\n moreActions={moreActions}\n showLine={showLine}\n switcherIcon={switcherIcon}\n width={directoryTreeWidth}\n draggable={draggable}\n editNode={editNode}\n onEditValueChange={handleEditValueChange}\n onEditConfirm={handleEditConfirm}\n onEditCancel={handleEditCancel}\n createInfo={createContext}\n onStartRename={handleStartRename}\n onDrop={onDrop}\n />\n {showPreview && (\n <FilePreview\n selectedFile={validSelectedFile ? selectedFileState : []}\n fileContent={fileContent}\n loading={loadingContent}\n previewTitle={previewTitle}\n previewRender={previewRender}\n emptyRender={emptyRender}\n getFileNode={(path) => {\n if (!path || path.length === 0) return undefined;\n const { node } = findNodeAndValidate(path);\n return node\n ? { title: node.title, path: node.path, content: node.content }\n : undefined;\n }}\n />\n )}\n </div>\n );\n});\n\nFolderTree.displayName = \"FolderTree\";\n\nexport default FolderTree;\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAQO;AAEP,kBAAiB;AAQjB,2BAAmC;AACnC,yBAAwB;AACxB,oBAA0B;
|
|
4
|
+
"sourcesContent": ["import React, {\n useRef,\n useMemo,\n useCallback,\n useEffect,\n useState,\n forwardRef,\n useImperativeHandle,\n} from \"react\";\nimport type { TreeProps } from \"antd\";\nimport clsx from \"clsx\";\nimport type {\n FolderTreeData,\n FolderTreeProps,\n FolderTreeRef,\n EditNodeInfo,\n CreateNodeContext,\n} from \"./types\";\nimport DirectoryTreeComponent from \"./DirectoryTree\";\nimport FilePreview from \"./FilePreview\";\nimport { useStyles } from \"./styles\";\n\nexport type {\n FolderTreeData,\n FolderTreeProps,\n FileContentService,\n FolderTreeRef,\n} from \"./types\";\n\n// 展开所有节点\nfunction getAllExpandedKeys(\n nodes: FolderTreeData[],\n parentPath = \"\",\n): string[] {\n const keys: string[] = [];\n\n nodes.forEach((node) => {\n const currentPath = parentPath ? `${parentPath}/${node.path}` : node.path;\n\n if (node.children?.length) {\n keys.push(currentPath);\n keys.push(...getAllExpandedKeys(node.children, currentPath));\n }\n });\n\n return keys;\n}\n\n// 格式化数据\nfunction normalizeTreeData(nodes: FolderTreeData[]): FolderTreeData[] {\n return nodes.map((node) => ({\n ...node,\n type: node.type\n ? node.type\n : !!node.children && node.children.length > 0\n ? \"folder\"\n : \"file\",\n children: node.children ? normalizeTreeData(node.children) : undefined,\n }));\n}\n\nconst FolderTree = forwardRef<FolderTreeRef, FolderTreeProps>((props, ref) => {\n const {\n className,\n style,\n treeData,\n directoryIcons,\n previewRender,\n directoryTitle,\n previewTitle,\n selectable = true,\n defaultSelectedFile,\n defaultExpandAll = true,\n selectedFile,\n onSelectedFileChange,\n directoryTreeWidth = 280,\n emptyRender,\n defaultExpandedPaths,\n expandedPaths,\n onExpandedPathsChange,\n onFileClick,\n onFolderClick,\n fileContentService,\n moreActions,\n showLine,\n switcherIcon,\n showPreview = true,\n draggable,\n onDrop,\n onRename,\n onCreate,\n } = props;\n\n const styles = useStyles();\n\n const normalizedTreeData = useMemo(\n () => normalizeTreeData(treeData),\n [treeData],\n );\n\n // ====== 编辑态状态 ======\n const [editNode, setEditNode] = useState<EditNodeInfo | null>(null);\n const [createContext, setCreateContext] = useState<CreateNodeContext | null>(\n null,\n );\n\n // ====== 工具函数 ======\n const findNodeAndValidate = useCallback(\n (\n path: string | string[],\n validateAsFile = false,\n ): { node: FolderTreeData | undefined; isValid: boolean } => {\n if (!path) return { node: undefined, isValid: false };\n const segments = Array.isArray(path)\n ? path.filter(Boolean)\n : path.split(\"/\").filter(Boolean);\n if (segments.length === 0) return { node: undefined, isValid: false };\n\n const findNode = (\n nodes: FolderTreeData[],\n index = 0,\n ): FolderTreeData | undefined => {\n if (index >= segments.length) return undefined;\n const currentSegment = segments[index];\n for (const node of nodes) {\n if (node.path === currentSegment) {\n return index === segments.length - 1\n ? node\n : node.children\n ? findNode(node.children, index + 1)\n : undefined;\n }\n }\n return undefined;\n };\n\n const node = findNode(normalizedTreeData);\n const isValid = validateAsFile\n ? !!node && (!node?.children || node.children.length === 0)\n : !!node;\n return { node, isValid };\n },\n [normalizedTreeData],\n );\n\n const isValidSelectedFile = (filePath?: string[]): boolean =>\n !!(\n filePath &&\n filePath.length > 0 &&\n findNodeAndValidate(filePath, true).isValid\n );\n\n // ====== 编辑态逻辑 ======\n\n // 当 normalizedTreeData 变化时,检查编辑节点是否还存在\n useEffect(() => {\n if (!editNode) return;\n const { node } = findNodeAndValidate(editNode.key);\n if (!node) {\n setEditNode(null);\n setCreateContext(null);\n }\n }, [normalizedTreeData]);\n\n // 编辑值变化\n const handleEditValueChange = useCallback((value: string) => {\n setEditNode((prev) => (prev ? { ...prev, currentValue: value } : null));\n }, []);\n\n // 确认编辑\n const handleEditConfirm = useCallback((value?: string) => {\n if (!editNode) return;\n const newValue = (value ?? editNode.currentValue).trim();\n\n if (editNode.mode === \"rename\") {\n const { node } = findNodeAndValidate(editNode.key);\n onRename?.({\n key: editNode.key,\n oldTitle: editNode.originalTitle,\n newTitle: newValue,\n node: node!,\n });\n } else if (editNode.mode === \"create\" && createContext) {\n onCreate?.({\n parentKey: createContext.parentKey,\n name: newValue,\n type: createContext.type,\n extra: createContext.extra,\n });\n }\n\n setEditNode(null);\n setCreateContext(null);\n }, [editNode, createContext, findNodeAndValidate, onRename, onCreate]);\n\n // 取消编辑\n const handleEditCancel = useCallback(() => {\n setEditNode(null);\n setCreateContext(null);\n }, []);\n\n // 开始重命名\n const handleStartRename = useCallback(\n (fullPath: string, node: FolderTreeData) => {\n const titleStr =\n typeof node.title === \"string\" ? node.title : String(node.path);\n setCreateContext(null);\n setEditNode({\n key: fullPath,\n type: node.type || \"file\",\n originalTitle: titleStr,\n currentValue: titleStr,\n mode: \"rename\",\n });\n },\n [],\n );\n\n // ====== State ======\n const [selectedFileState, setSelectedFileState] = useState<string[]>(() =>\n isValidSelectedFile(selectedFile || defaultSelectedFile)\n ? selectedFile || defaultSelectedFile || []\n : [],\n );\n const controlled = expandedPaths !== undefined;\n\n const [expandedPathsState, setExpandedPathsState] = useState<string[]>(\n defaultExpandedPaths || [],\n );\n const [validSelectedFile, setValidSelectedFile] = useState<boolean>(\n isValidSelectedFile(selectedFile || defaultSelectedFile),\n );\n const [fileContent, setFileContent] = useState<string>(\"\");\n const [loadingContent, setLoadingContent] = useState<boolean>(false);\n const initializedExpandRef = useRef(false);\n\n // 受控模式同步\n useEffect(() => {\n if (selectedFile !== undefined) {\n setSelectedFileState(selectedFile);\n setValidSelectedFile(isValidSelectedFile(selectedFile));\n }\n }, [selectedFile, isValidSelectedFile]);\n\n useEffect(() => {\n if (\n !controlled &&\n defaultExpandAll &&\n !initializedExpandRef.current &&\n normalizedTreeData.length > 0\n ) {\n initializedExpandRef.current = true;\n\n setExpandedPathsState(getAllExpandedKeys(normalizedTreeData));\n }\n }, [controlled, defaultExpandAll, normalizedTreeData]);\n\n useEffect(() => {\n if (controlled) {\n setExpandedPathsState(expandedPaths);\n }\n }, [controlled, expandedPaths]);\n\n // ====== 加载文件内容 ======\n useEffect(() => {\n const loadContent = async () => {\n if (!validSelectedFile || selectedFileState.length === 0) {\n setFileContent(\"\");\n setLoadingContent(false);\n return;\n }\n\n const filePath = selectedFileState.join(\"/\");\n const segments = filePath.split(\"/\").filter((s) => s !== \"\");\n const { node } = findNodeAndValidate(segments);\n\n if (fileContentService) {\n setLoadingContent(true);\n try {\n const content = await fileContentService.loadFileContent(filePath);\n setFileContent(content);\n } catch (error) {\n setFileContent(\n `// 加载错误: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n } finally {\n setLoadingContent(false);\n }\n } else if (node?.content) {\n setFileContent(node.content);\n setLoadingContent(false);\n } else {\n setFileContent(\"\");\n setLoadingContent(false);\n }\n };\n\n loadContent();\n }, [\n validSelectedFile,\n selectedFileState,\n normalizedTreeData,\n fileContentService,\n findNodeAndValidate,\n ]);\n\n // ====== 事件处理 ======\n const handleSelect: TreeProps[\"onSelect\"] = (_keys, info) => {\n const keys = _keys as string[];\n const nodes = Array.isArray(info.selectedNodes)\n ? info.selectedNodes\n : [info.selectedNodes];\n\n const isFolder = nodes.some((node) => !node.isLeaf);\n if (isFolder) {\n if (nodes.length === 1) {\n const node = nodes[0] as unknown as FolderTreeData;\n onFolderClick?.(node.path, node.extra);\n }\n return;\n }\n\n const pathArray = keys[0]?.split(\"/\").filter(Boolean) || [];\n if (pathArray.length === 0) return;\n\n // 从原始 normalizedTreeData 中查找节点,避免拿到 antd 包装后的 JSX title\n const { node: originalNode } = findNodeAndValidate(pathArray);\n const fileName = originalNode?.title;\n const nodeContent = originalNode?.content;\n\n onSelectedFileChange?.({\n path: pathArray,\n title: fileName,\n content: nodeContent,\n extra: originalNode?.extra,\n });\n\n const isControlled = selectedFile !== undefined;\n if (!isControlled) {\n setValidSelectedFile(true);\n setSelectedFileState(pathArray);\n }\n\n if (originalNode) {\n onFileClick?.(keys[0], originalNode.content, originalNode.extra);\n }\n };\n\n const handleExpand: TreeProps[\"onExpand\"] = (keys) => {\n const newPaths = keys as string[];\n\n if (!controlled) {\n setExpandedPathsState(newPaths);\n }\n\n onExpandedPathsChange?.(newPaths);\n };\n\n // ====== 创建节点核心逻辑(供命令式 API 和 moreActions 共享) ======\n const internalCreateNode = useCallback(\n (\n parentPath: string,\n options: { type: \"file\" | \"folder\"; defaultName?: string; extra?: any },\n ) => {\n const defaultName =\n options.defaultName ||\n (options.type === \"folder\" ? \"新建文件夹\" : \"新建文件\");\n const tempKey = `__create__${parentPath || \"root\"}__${Date.now()}`;\n\n setCreateContext({\n parentKey: parentPath,\n type: options.type,\n extra: options.extra,\n });\n setEditNode({\n key: tempKey,\n type: options.type || \"folder\",\n originalTitle: \"\",\n currentValue: defaultName,\n mode: \"create\",\n });\n\n // 自动展开父文件夹(仅在受控模式下需要,非受控模式由 defaultExpandAll 处理)\n if (!controlled && parentPath && parentPath !== \"/\") {\n setExpandedPathsState((prev) => {\n const current = prev || [];\n\n if (current.includes(parentPath)) {\n return current;\n }\n\n return [...current, parentPath];\n });\n }\n },\n [controlled],\n );\n\n // ====== 命令式 API ======\n useImperativeHandle(ref, () => ({ createNode: internalCreateNode }), [\n internalCreateNode,\n ]);\n\n return (\n <div\n className={clsx(\n styles.container,\n !showPreview && styles.containerNoPreview,\n className,\n )}\n style={{\n ...style,\n ...(style?.background\n ? ({\n \"--folder-tree-directory-bg\": style.background,\n } as React.CSSProperties)\n : {}),\n ...(style?.backgroundColor\n ? ({\n \"--folder-tree-directory-bg\": style.backgroundColor,\n } as React.CSSProperties)\n : {}),\n }}\n >\n <DirectoryTreeComponent\n treeData={normalizedTreeData}\n directoryIcons={directoryIcons}\n selectedKeys={\n selectable && selectedFileState && validSelectedFile\n ? [selectedFileState.join(\"/\")]\n : []\n }\n expandedKeys={controlled ? expandedPaths : expandedPathsState}\n onSelect={handleSelect}\n onExpand={handleExpand}\n defaultExpandAll={defaultExpandAll}\n directoryTitle={directoryTitle}\n moreActions={moreActions}\n showLine={showLine}\n switcherIcon={switcherIcon}\n width={directoryTreeWidth}\n draggable={draggable}\n editNode={editNode}\n onEditValueChange={handleEditValueChange}\n onEditConfirm={handleEditConfirm}\n onEditCancel={handleEditCancel}\n createInfo={createContext}\n onStartRename={handleStartRename}\n onCreateNode={internalCreateNode}\n onDrop={onDrop}\n />\n {showPreview && (\n <FilePreview\n selectedFile={validSelectedFile ? selectedFileState : []}\n fileContent={fileContent}\n loading={loadingContent}\n previewTitle={previewTitle}\n previewRender={previewRender}\n emptyRender={emptyRender}\n getFileNode={(path) => {\n if (!path || path.length === 0) return undefined;\n const { node } = findNodeAndValidate(path);\n return node\n ? { title: node.title, path: node.path, content: node.content }\n : undefined;\n }}\n />\n )}\n </div>\n );\n});\n\nFolderTree.displayName = \"FolderTree\";\n\nexport default FolderTree;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["node", "clsx", "DirectoryTreeComponent", "FilePreview"]
|
|
7
7
|
}
|
|
@@ -111,6 +111,11 @@ export interface FolderTreeProps {
|
|
|
111
111
|
/** 更多操作菜单,传入节点和操作方法,返回 Dropdown menu 配置 */
|
|
112
112
|
moreActions?: (node: FolderTreeData, actions?: {
|
|
113
113
|
startRename: () => void;
|
|
114
|
+
createNode: (options: {
|
|
115
|
+
type: "file" | "folder";
|
|
116
|
+
defaultName?: string;
|
|
117
|
+
extra?: any;
|
|
118
|
+
}) => void;
|
|
114
119
|
}) => DropdownProps["menu"];
|
|
115
120
|
/** 是否显示连接线 */
|
|
116
121
|
showLine?: boolean | {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/FolderTree/types.ts"],
|
|
4
|
-
"sourcesContent": ["import type { DropdownProps } from \"antd\";\nimport type React from \"react\";\n\n/** 文件树节点数据 */\nexport interface FolderTreeData<T = any> {\n title: React.ReactNode;\n path: string;\n content?: string;\n type?: \"folder\" | \"file\";\n extra?: T;\n children?: FolderTreeData<T>[];\n}\n\n/** 文件内容加载服务 */\nexport interface FileContentService {\n loadFileContent(filePath: string): Promise<string>;\n}\n\n/** 编辑节点信息 */\nexport interface EditNodeInfo {\n /** 完整路径 key */\n key: string;\n /** 原始标题 */\n originalTitle: string;\n /** 文件类型 */\n type: \"folder\" | \"file\";\n /** 当前输入值 */\n currentValue: string;\n /** 编辑模式:重命名或新建 */\n mode: \"rename\" | \"create\";\n}\n\n/** 新建上下文 */\nexport interface CreateNodeContext {\n /** 父节点 key */\n parentKey: string;\n /** 节点类型 */\n type: \"file\" | \"folder\";\n /** 附加数据 */\n extra?: any;\n}\n\n/** FolderTree 命令式 API */\nexport interface FolderTreeRef {\n /**\n * 在指定父路径下新建文件或文件夹\n * @param parentPath - 父节点完整路径(空字符串或 \"/\" 表示根路径)\n * @param options.type - 节点类型\n * @param options.defaultName - 默认名称\n * @param options.extra - 自定义附加数据\n */\n createNode: (\n parentPath: string,\n options: {\n type: \"file\" | \"folder\";\n defaultName?: string;\n extra?: any;\n },\n ) => void;\n}\n\n/** FolderTree 组件属性 */\nexport interface FolderTreeProps {\n /** 自定义类名 */\n className?: string;\n /** 自定义样式 */\n style?: React.CSSProperties;\n\n /** 树形数据 */\n treeData: FolderTreeData[];\n\n /** 自定义文件/文件夹图标,false 禁用图标 */\n directoryIcons?:\n | false\n | Record<\"directory\" | string, React.ReactNode | (() => React.ReactNode)>;\n\n /** 是否可选中文件 */\n selectable?: boolean;\n /** 受控:当前选中的文件路径 */\n selectedFile?: string[];\n /** 默认选中的文件路径 */\n defaultSelectedFile?: string[];\n /** 选中文件变化回调 */\n onSelectedFileChange?: (file: {\n path: string[];\n title?: React.ReactNode;\n content?: string;\n extra?: any;\n }) => void;\n\n /** 受控:展开的路径 */\n expandedPaths?: string[];\n /** 默认展开的路径 */\n defaultExpandedPaths?: string[];\n /** 默认是否展开所有节点 */\n defaultExpandAll?: boolean;\n /** 展开路径变化回调 */\n onExpandedPathsChange?: (paths: string[]) => void;\n\n /** 目录树面板宽度 */\n directoryTreeWidth?: number | string;\n\n /** 文件内容加载服务 */\n fileContentService?: FileContentService;\n\n /** 空状态渲染 */\n emptyRender?: false | React.ReactNode | (() => React.ReactNode);\n /** 自定义文件预览渲染 */\n previewRender?:\n | React.ReactNode\n | ((\n file: {\n content?: string;\n path: string[];\n title?: React.ReactNode;\n language: string;\n },\n info: { originNode: React.ReactNode },\n ) => React.ReactNode);\n\n /** 自定义目录标题 */\n directoryTitle?: false | React.ReactNode | (() => React.ReactNode);\n /** 自定义预览标题 */\n previewTitle?:\n | false\n | React.ReactNode\n | ((info: {\n title: React.ReactNode;\n path: string[];\n content: string;\n }) => React.ReactNode);\n\n /** 文件点击回调 */\n onFileClick?: (filePath: string, content?: string, extra?: any) => void;\n /** 文件夹点击回调 */\n onFolderClick?: (folderPath: string, extra?: any) => void;\n\n /** 更多操作菜单,传入节点和操作方法,返回 Dropdown menu 配置 */\n moreActions?: (\n node: FolderTreeData,\n actions?: {
|
|
4
|
+
"sourcesContent": ["import type { DropdownProps } from \"antd\";\nimport type React from \"react\";\n\n/** 文件树节点数据 */\nexport interface FolderTreeData<T = any> {\n title: React.ReactNode;\n path: string;\n content?: string;\n type?: \"folder\" | \"file\";\n extra?: T;\n children?: FolderTreeData<T>[];\n}\n\n/** 文件内容加载服务 */\nexport interface FileContentService {\n loadFileContent(filePath: string): Promise<string>;\n}\n\n/** 编辑节点信息 */\nexport interface EditNodeInfo {\n /** 完整路径 key */\n key: string;\n /** 原始标题 */\n originalTitle: string;\n /** 文件类型 */\n type: \"folder\" | \"file\";\n /** 当前输入值 */\n currentValue: string;\n /** 编辑模式:重命名或新建 */\n mode: \"rename\" | \"create\";\n}\n\n/** 新建上下文 */\nexport interface CreateNodeContext {\n /** 父节点 key */\n parentKey: string;\n /** 节点类型 */\n type: \"file\" | \"folder\";\n /** 附加数据 */\n extra?: any;\n}\n\n/** FolderTree 命令式 API */\nexport interface FolderTreeRef {\n /**\n * 在指定父路径下新建文件或文件夹\n * @param parentPath - 父节点完整路径(空字符串或 \"/\" 表示根路径)\n * @param options.type - 节点类型\n * @param options.defaultName - 默认名称\n * @param options.extra - 自定义附加数据\n */\n createNode: (\n parentPath: string,\n options: {\n type: \"file\" | \"folder\";\n defaultName?: string;\n extra?: any;\n },\n ) => void;\n}\n\n/** FolderTree 组件属性 */\nexport interface FolderTreeProps {\n /** 自定义类名 */\n className?: string;\n /** 自定义样式 */\n style?: React.CSSProperties;\n\n /** 树形数据 */\n treeData: FolderTreeData[];\n\n /** 自定义文件/文件夹图标,false 禁用图标 */\n directoryIcons?:\n | false\n | Record<\"directory\" | string, React.ReactNode | (() => React.ReactNode)>;\n\n /** 是否可选中文件 */\n selectable?: boolean;\n /** 受控:当前选中的文件路径 */\n selectedFile?: string[];\n /** 默认选中的文件路径 */\n defaultSelectedFile?: string[];\n /** 选中文件变化回调 */\n onSelectedFileChange?: (file: {\n path: string[];\n title?: React.ReactNode;\n content?: string;\n extra?: any;\n }) => void;\n\n /** 受控:展开的路径 */\n expandedPaths?: string[];\n /** 默认展开的路径 */\n defaultExpandedPaths?: string[];\n /** 默认是否展开所有节点 */\n defaultExpandAll?: boolean;\n /** 展开路径变化回调 */\n onExpandedPathsChange?: (paths: string[]) => void;\n\n /** 目录树面板宽度 */\n directoryTreeWidth?: number | string;\n\n /** 文件内容加载服务 */\n fileContentService?: FileContentService;\n\n /** 空状态渲染 */\n emptyRender?: false | React.ReactNode | (() => React.ReactNode);\n /** 自定义文件预览渲染 */\n previewRender?:\n | React.ReactNode\n | ((\n file: {\n content?: string;\n path: string[];\n title?: React.ReactNode;\n language: string;\n },\n info: { originNode: React.ReactNode },\n ) => React.ReactNode);\n\n /** 自定义目录标题 */\n directoryTitle?: false | React.ReactNode | (() => React.ReactNode);\n /** 自定义预览标题 */\n previewTitle?:\n | false\n | React.ReactNode\n | ((info: {\n title: React.ReactNode;\n path: string[];\n content: string;\n }) => React.ReactNode);\n\n /** 文件点击回调 */\n onFileClick?: (filePath: string, content?: string, extra?: any) => void;\n /** 文件夹点击回调 */\n onFolderClick?: (folderPath: string, extra?: any) => void;\n\n /** 更多操作菜单,传入节点和操作方法,返回 Dropdown menu 配置 */\n moreActions?: (\n node: FolderTreeData,\n actions?: {\n startRename: () => void;\n createNode: (options: {\n type: \"file\" | \"folder\";\n defaultName?: string;\n extra?: any;\n }) => void;\n },\n ) => DropdownProps[\"menu\"];\n\n /** 是否显示连接线 */\n showLine?: boolean | { showLeafIcon?: boolean | React.ReactNode };\n /** 自定义展开/收起图标 */\n switcherIcon?: React.ReactNode | ((props: any) => React.ReactNode);\n\n /** 是否显示文件预览面板,默认 true */\n showPreview?: boolean;\n\n /**\n * 拖拽模式\n * - true | 'file': 只有文件(叶子节点)可拖拽\n * - 'folder': 只有文件夹可拖拽\n * - 'all': 文件和文件夹都可拖拽\n * - false | undefined: 禁用拖拽\n */\n draggable?: boolean | \"file\" | \"folder\" | \"all\";\n\n /** 重命名确认回调,失焦或回车时触发 */\n onRename?: (info: {\n /** 节点完整路径 key */\n key: string;\n /** 原始标题 */\n oldTitle: React.ReactNode;\n /** 新标题 */\n newTitle: string;\n /** 原始节点数据 */\n node: FolderTreeData;\n }) => void;\n\n /** 新建确认回调,失焦或回车时触发 */\n onCreate?: (info: {\n /** 父节点 key */\n parentKey: string;\n /** 用户输入的名称 */\n name: string;\n /** 节点类型 */\n type: \"file\" | \"folder\";\n /** 附加数据 */\n extra?: any;\n }) => void;\n\n /** 拖拽完成回调 */\n onDrop?: (info: {\n /** 被拖拽节点的文件名 */\n fileName: React.ReactNode;\n /** 被拖拽节点的 extra 数据 */\n extra: any;\n /** 拖拽前的完整路径 */\n oldPath: string;\n /** 拖拽完成后的新路径 */\n newPath: string;\n /** 被拖拽的节点 */\n dragNode: FolderTreeData;\n /** 目标节点 */\n dropNode: FolderTreeData;\n }) => void;\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/XAdkProvider/index.tsx"],
|
|
4
|
-
"sourcesContent": ["import React, { forwardRef, useImperativeHandle, useMemo } from \"react\";\nimport { ConfigProvider } from \"antd\";\nimport { XProvider } from \"@ant-design/x\";\nimport { ChatStateContext } from \"./context/ChatStateContext\";\nimport { ChatActionContext } from \"./context/ChatActionContext\";\nimport { SessionContext } from \"./context/SessionContext\";\nimport useADKChat from \"@/hooks/useADKChat\";\nimport { primaryBlue } from \"@/styles/common\";\nimport { cssVarStyle } from \"@/styles/tokens\";\nimport type {\n XAdkProviderProps,\n XAdkProviderHandle,\n XAdkProviderConfig,\n} from \"@/types/XAdkProvider\";\nimport type { ChatHookResult } from \"@/types/ChatHook\";\nimport {\n mergeChatStrategies,\n resolvePresetUploadRequest,\n} from \"@/presets/xGroupAdk\";\n\n/**\n * XAdkProvider Props (扩展版)\n *\n * 支持两种使用方式:\n * 1. 标准协议: 提供 url/token/config,使用内置 useADKChat\n * 2. 自定义协议: 提供 chatData,使用自己的 Hook\n */\nexport interface XAdkProviderPropsExtended extends Omit<\n XAdkProviderProps,\n \"url\" | \"token\" | \"config\"\n> {\n /**\n * 选项 A: 使用标准协议 (内置 useADKChat)\n */\n url?: string;\n token: string;\n config?: XAdkProviderConfig;\n\n /**\n * 选项 B: 使用自定义 Hook 数据\n * 如果提供此参数,将忽略 url/token/config\n *\n * @example\n * // GraphQL 协议\n * const chatData = useMyGraphQLChat({ endpoint: '...' });\n * <XAdkProvider chatData={chatData}>\n * <XAdkProvider.DefaultLayout />\n * </XAdkProvider>\n *\n * @example\n * // WebSocket 协议\n * const chatData = useWebSocketChat({ url: 'ws://...' });\n * <XAdkProvider chatData={chatData}>\n * <XAdkProvider.DefaultLayout />\n * </XAdkProvider>\n */\n chatData?: ChatHookResult;\n\n children: React.ReactNode;\n}\n\n/**\n * XAdkProvider - AI 聊天 SDK 核心 Provider\n *\n * 采用 Compound Components 架构:\n * - 只负责数据和状态管理\n * - 不包含 UI 布局\n * - 通过 Context 向子组件提供数据\n * - 支持完全自定义的组件组合\n * - 支持协议解耦,可使用自定义 Hook\n *\n * @example\n * // 基础用法 - 标准协议\n * <XAdkProvider url=\"...\" token=\"...\" config={...}>\n * <XAdkProvider.Chatbot />\n * </XAdkProvider>\n *\n * @example\n * // 自定义协议 - GraphQL\n * function App() {\n * const chatData = useMyGraphQLChat({ endpoint: '...' });\n * return (\n * <XAdkProvider chatData={chatData}>\n * <XAdkProvider.DefaultLayout />\n * </XAdkProvider>\n * );\n * }\n *\n * @example\n * // 自定义布局\n * <XAdkProvider url=\"...\" token=\"...\">\n * <div style={{ display: 'flex' }}>\n * <XAdkProvider.Sidebar />\n * <div>\n * <XAdkProvider.Messages />\n * <XAdkProvider.Sender />\n * </div>\n * </div>\n * </XAdkProvider>\n */\nconst XAdkProviderComponent = forwardRef<\n XAdkProviderHandle,\n XAdkProviderPropsExtended\n>(\n (\n {\n url = window.location.origin,\n token,\n config,\n preset,\n strategies,\n chatData, // 自定义 Hook 数据\n providerId = \"defaultId\",\n onSuccess,\n onError,\n onMessage,\n onStream,\n children,\n },\n ref,\n ) => {\n const mergedStrategies = useMemo(\n () =>\n mergeChatStrategies(\n preset ?? config?.preset,\n {\n ...config?.strategies,\n ...strategies,\n },\n ),\n [preset, strategies, config?.preset, config?.strategies],\n );\n\n // 内置 Hook 数据 (仅在没有提供 chatData 时调用)\n const internalData = useADKChat({\n url,\n token: token || \"\",\n config: config || {},\n strategies: mergedStrategies,\n onError,\n onSuccess,\n onMessage,\n onStream,\n // 仅在提供了 url 和 token 时才初始化\n enabled: !chatData && !!url && !!token,\n });\n\n // 使用外部数据或内部数据\n const data = chatData || internalData;\n\n // 解构数据\n const {\n appInfo,\n startChat,\n stopChat,\n reChat,\n clearChat,\n suggestChat,\n confirmFnCall,\n deleteSession,\n updateSession,\n currentSessionId,\n sessionPagination,\n sessionList,\n messages,\n loading,\n prologue,\n suggestions,\n initialized,\n setCurrentSession,\n setMessages,\n likeMessage = async () => false,\n setStateDelta = () => {},\n } = data;\n\n const showFeedback = config?.showFeedback ?? true;\n const onFileClick = config?.onFileClick;\n const presetUploadRequest = useMemo(\n () =>\n resolvePresetUploadRequest(preset ?? config?.preset, {\n url,\n token,\n appNo: config?.appNo,\n }),\n [preset, config?.preset, url, token, config?.appNo],\n );\n const uploadRequest = config?.uploadRequest ?? presetUploadRequest;\n\n // 暴露 API 给 ref\n useImperativeHandle(\n ref,\n () => ({\n startChat,\n stopChat,\n reChat,\n clearChat,\n suggestChat,\n deleteSession,\n updateSession,\n getAppInfo: () => appInfo,\n getMessages: () => messages,\n getSessions: () => sessionList,\n setMessages,\n setCurrentSession,\n setStateDelta,\n }),\n [\n appInfo,\n messages,\n sessionList,\n startChat,\n stopChat,\n reChat,\n clearChat,\n suggestChat,\n deleteSession,\n updateSession,\n setMessages,\n setCurrentSession,\n setStateDelta,\n ],\n );\n\n // 状态 Context Value - 使用 useMemo 避免不必要的 rerender\n const chatStateValue = useMemo(\n () => ({\n messages,\n loading,\n currentSessionId,\n prologue,\n suggestions,\n appInfo,\n initialized,\n url,\n token,\n showFeedback,\n onFileClick,\n uploadRequest,\n strategies: mergedStrategies,\n }),\n [\n messages,\n loading,\n currentSessionId,\n prologue,\n suggestions,\n appInfo,\n initialized,\n showFeedback,\n onFileClick,\n uploadRequest,\n mergedStrategies,\n ],\n );\n\n // 动作 Context Value - 函数引用稳定,不需要每次都创建新对象\n const chatActionValue = useMemo(\n () => ({\n chat: startChat,\n stopChat,\n clearChat,\n reChat,\n suggestChat,\n confirmFnCall,\n setMessages,\n likeMessage,\n setStateDelta,\n }),\n [\n startChat,\n stopChat,\n clearChat,\n reChat,\n suggestChat,\n confirmFnCall,\n setMessages,\n likeMessage,\n setStateDelta,\n ],\n );\n\n // 会话 Context Value\n const sessionValue = useMemo(\n () => ({\n sessionList,\n sessionPagination,\n deleteSession,\n updateSession,\n setCurrentSession,\n }),\n [\n sessionList,\n sessionPagination,\n deleteSession,\n updateSession,\n setCurrentSession,\n ],\n );\n\n return (\n <ConfigProvider\n theme={{\n token: {\n colorPrimary: appInfo?.appTheme || primaryBlue,\n },\n }}\n >\n <XProvider>\n <ChatStateContext.Provider value={chatStateValue}>\n <ChatActionContext.Provider value={chatActionValue}>\n <SessionContext.Provider value={sessionValue}>\n <div\n style={{\n ...cssVarStyle,\n display: \"flex\",\n height: \"100%\",\n width: \"100%\",\n position: \"relative\",\n } as React.CSSProperties}\n >\n {children}\n </div>\n </SessionContext.Provider>\n </ChatActionContext.Provider>\n </ChatStateContext.Provider>\n </XProvider>\n </ConfigProvider>\n );\n },\n);\n\nXAdkProviderComponent.displayName = \"XAdkProvider\";\n\n// 导入 Compound Components\nimport Header from \"./compound/Header\";\nimport Sidebar from \"./compound/Sidebar\";\nimport Messages from \"./compound/Messages\";\nimport Sender from \"./compound/Sender\";\nimport Chatbot from \"./compound/Chatbot\";\nimport Welcome from \"./compound/Welcome\";\nimport DefaultLayout from \"./compound/DefaultLayout\";\n\n// 导出时会挂载 Compound Components\nconst XAdkProvider = XAdkProviderComponent as typeof XAdkProviderComponent & {\n Header: typeof Header;\n Sidebar: typeof Sidebar;\n Messages: typeof Messages;\n Sender: typeof Sender;\n Chatbot: typeof Chatbot;\n Welcome: typeof Welcome;\n DefaultLayout: typeof DefaultLayout;\n};\n\n// 挂载 Compound Components\nXAdkProvider.Header = Header;\nXAdkProvider.Sidebar = Sidebar;\nXAdkProvider.Messages = Messages;\nXAdkProvider.Sender = Sender;\nXAdkProvider.Chatbot = Chatbot;\nXAdkProvider.DefaultLayout = DefaultLayout;\nXAdkProvider.Welcome = Welcome;\n\nexport default XAdkProvider;\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAgE;AAChE,kBAA+B;AAC/B,eAA0B;AAC1B,8BAAiC;AACjC,+BAAkC;AAClC,4BAA+B;AAC/B,wBAAuB;AACvB,oBAA4B;AAC5B,oBAA4B;AAO5B,uBAGO;
|
|
4
|
+
"sourcesContent": ["import React, { forwardRef, useImperativeHandle, useMemo } from \"react\";\nimport { ConfigProvider } from \"antd\";\nimport { XProvider } from \"@ant-design/x\";\nimport { ChatStateContext } from \"./context/ChatStateContext\";\nimport { ChatActionContext } from \"./context/ChatActionContext\";\nimport { SessionContext } from \"./context/SessionContext\";\nimport useADKChat from \"@/hooks/useADKChat\";\nimport { primaryBlue } from \"@/styles/common\";\nimport { cssVarStyle } from \"@/styles/tokens\";\nimport type {\n XAdkProviderProps,\n XAdkProviderHandle,\n XAdkProviderConfig,\n} from \"@/types/XAdkProvider\";\nimport type { ChatHookResult } from \"@/types/ChatHook\";\nimport {\n mergeChatStrategies,\n resolvePresetUploadRequest,\n} from \"@/presets/xGroupAdk\";\n\n/**\n * XAdkProvider Props (扩展版)\n *\n * 支持两种使用方式:\n * 1. 标准协议: 提供 url/token/config,使用内置 useADKChat\n * 2. 自定义协议: 提供 chatData,使用自己的 Hook\n */\nexport interface XAdkProviderPropsExtended extends Omit<\n XAdkProviderProps,\n \"url\" | \"token\" | \"config\"\n> {\n /**\n * 选项 A: 使用标准协议 (内置 useADKChat)\n */\n url?: string;\n token: string;\n config?: XAdkProviderConfig;\n\n /**\n * 选项 B: 使用自定义 Hook 数据\n * 如果提供此参数,将忽略 url/token/config\n *\n * @example\n * // GraphQL 协议\n * const chatData = useMyGraphQLChat({ endpoint: '...' });\n * <XAdkProvider chatData={chatData}>\n * <XAdkProvider.DefaultLayout />\n * </XAdkProvider>\n *\n * @example\n * // WebSocket 协议\n * const chatData = useWebSocketChat({ url: 'ws://...' });\n * <XAdkProvider chatData={chatData}>\n * <XAdkProvider.DefaultLayout />\n * </XAdkProvider>\n */\n chatData?: ChatHookResult;\n\n children: React.ReactNode;\n}\n\n/**\n * XAdkProvider - AI 聊天 SDK 核心 Provider\n *\n * 采用 Compound Components 架构:\n * - 只负责数据和状态管理\n * - 不包含 UI 布局\n * - 通过 Context 向子组件提供数据\n * - 支持完全自定义的组件组合\n * - 支持协议解耦,可使用自定义 Hook\n *\n * @example\n * // 基础用法 - 标准协议\n * <XAdkProvider url=\"...\" token=\"...\" config={...}>\n * <XAdkProvider.Chatbot />\n * </XAdkProvider>\n *\n * @example\n * // 自定义协议 - GraphQL\n * function App() {\n * const chatData = useMyGraphQLChat({ endpoint: '...' });\n * return (\n * <XAdkProvider chatData={chatData}>\n * <XAdkProvider.DefaultLayout />\n * </XAdkProvider>\n * );\n * }\n *\n * @example\n * // 自定义布局\n * <XAdkProvider url=\"...\" token=\"...\">\n * <div style={{ display: 'flex' }}>\n * <XAdkProvider.Sidebar />\n * <div>\n * <XAdkProvider.Messages />\n * <XAdkProvider.Sender />\n * </div>\n * </div>\n * </XAdkProvider>\n */\nconst XAdkProviderComponent = forwardRef<\n XAdkProviderHandle,\n XAdkProviderPropsExtended\n>(\n (\n {\n url = window.location.origin,\n token,\n config,\n preset,\n strategies,\n chatData, // 自定义 Hook 数据\n providerId = \"defaultId\",\n onSuccess,\n onError,\n onMessage,\n onStream,\n children,\n },\n ref,\n ) => {\n const mergedStrategies = useMemo(\n () =>\n mergeChatStrategies(\n preset ?? config?.preset,\n {\n ...config?.strategies,\n ...strategies,\n },\n ),\n [preset, strategies, config?.preset, config?.strategies],\n );\n\n // 内置 Hook 数据 (仅在没有提供 chatData 时调用)\n const internalData = useADKChat({\n url,\n token: token || \"\",\n config: config || {},\n strategies: mergedStrategies,\n onError,\n onSuccess,\n onMessage,\n onStream,\n // 仅在提供了 url 和 token 时才初始化\n enabled: !chatData && !!url && !!token,\n });\n\n // 使用外部数据或内部数据\n const data = chatData || internalData;\n\n // 解构数据\n const {\n appInfo,\n startChat,\n stopChat,\n reChat,\n clearChat,\n suggestChat,\n confirmFnCall,\n deleteSession,\n updateSession,\n currentSessionId,\n sessionPagination,\n sessionList,\n messages,\n loading,\n prologue,\n suggestions,\n initialized,\n setCurrentSession,\n setMessages,\n likeMessage = async () => false,\n setStateDelta = () => {},\n } = data;\n\n const showFeedback = config?.showFeedback ?? true;\n const onFileClick = config?.onFileClick;\n const presetUploadRequest = useMemo(\n () =>\n resolvePresetUploadRequest(preset ?? config?.preset, {\n url,\n token,\n appNo: config?.appNo,\n }),\n [preset, config?.preset, url, token, config?.appNo],\n );\n const uploadRequest = config?.uploadRequest ?? presetUploadRequest;\n\n // 暴露 API 给 ref\n useImperativeHandle(\n ref,\n () => ({\n startChat,\n stopChat,\n reChat,\n clearChat,\n suggestChat,\n deleteSession,\n updateSession,\n getAppInfo: () => appInfo,\n getMessages: () => messages,\n getSessions: () => sessionList,\n setMessages,\n setCurrentSession,\n setStateDelta,\n }),\n [\n appInfo,\n messages,\n sessionList,\n startChat,\n stopChat,\n reChat,\n clearChat,\n suggestChat,\n deleteSession,\n updateSession,\n setMessages,\n setCurrentSession,\n setStateDelta,\n ],\n );\n\n // 状态 Context Value - 使用 useMemo 避免不必要的 rerender\n const chatStateValue = useMemo(\n () => ({\n messages,\n loading,\n currentSessionId,\n prologue,\n suggestions,\n appInfo,\n initialized,\n url,\n token,\n showFeedback,\n onFileClick,\n uploadRequest,\n strategies: mergedStrategies,\n }),\n [\n messages,\n loading,\n currentSessionId,\n prologue,\n suggestions,\n appInfo,\n initialized,\n url,\n token,\n showFeedback,\n onFileClick,\n uploadRequest,\n mergedStrategies,\n ],\n );\n\n // 动作 Context Value - 函数引用稳定,不需要每次都创建新对象\n const chatActionValue = useMemo(\n () => ({\n chat: startChat,\n stopChat,\n clearChat,\n reChat,\n suggestChat,\n confirmFnCall,\n setMessages,\n likeMessage,\n setStateDelta,\n }),\n [\n startChat,\n stopChat,\n clearChat,\n reChat,\n suggestChat,\n confirmFnCall,\n setMessages,\n likeMessage,\n setStateDelta,\n ],\n );\n\n // 会话 Context Value\n const sessionValue = useMemo(\n () => ({\n sessionList,\n sessionPagination,\n deleteSession,\n updateSession,\n setCurrentSession,\n }),\n [\n sessionList,\n sessionPagination,\n deleteSession,\n updateSession,\n setCurrentSession,\n ],\n );\n\n return (\n <ConfigProvider\n theme={{\n token: {\n colorPrimary: appInfo?.appTheme || primaryBlue,\n },\n }}\n >\n <XProvider>\n <ChatStateContext.Provider value={chatStateValue}>\n <ChatActionContext.Provider value={chatActionValue}>\n <SessionContext.Provider value={sessionValue}>\n <div\n style={{\n ...cssVarStyle,\n display: \"flex\",\n height: \"100%\",\n width: \"100%\",\n position: \"relative\",\n } as React.CSSProperties}\n >\n {children}\n </div>\n </SessionContext.Provider>\n </ChatActionContext.Provider>\n </ChatStateContext.Provider>\n </XProvider>\n </ConfigProvider>\n );\n },\n);\n\nXAdkProviderComponent.displayName = \"XAdkProvider\";\n\n// 导入 Compound Components\nimport Header from \"./compound/Header\";\nimport Sidebar from \"./compound/Sidebar\";\nimport Messages from \"./compound/Messages\";\nimport Sender from \"./compound/Sender\";\nimport Chatbot from \"./compound/Chatbot\";\nimport Welcome from \"./compound/Welcome\";\nimport DefaultLayout from \"./compound/DefaultLayout\";\n\n// 导出时会挂载 Compound Components\nconst XAdkProvider = XAdkProviderComponent as typeof XAdkProviderComponent & {\n Header: typeof Header;\n Sidebar: typeof Sidebar;\n Messages: typeof Messages;\n Sender: typeof Sender;\n Chatbot: typeof Chatbot;\n Welcome: typeof Welcome;\n DefaultLayout: typeof DefaultLayout;\n};\n\n// 挂载 Compound Components\nXAdkProvider.Header = Header;\nXAdkProvider.Sidebar = Sidebar;\nXAdkProvider.Messages = Messages;\nXAdkProvider.Sender = Sender;\nXAdkProvider.Chatbot = Chatbot;\nXAdkProvider.DefaultLayout = DefaultLayout;\nXAdkProvider.Welcome = Welcome;\n\nexport default XAdkProvider;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["useADKChat", "Header", "Sidebar", "Messages", "Sender", "Chatbot", "DefaultLayout", "Welcome"]
|
|
7
7
|
}
|
|
@@ -66,6 +66,8 @@ function useADKChat({
|
|
|
66
66
|
const [sessionLoading, setSessionLoading] = (0, import_react.useState)(false);
|
|
67
67
|
const sendingRef = (0, import_react.useRef)(false);
|
|
68
68
|
const ctrl = (0, import_react.useRef)(null);
|
|
69
|
+
const tokenRef = (0, import_react.useRef)(token);
|
|
70
|
+
tokenRef.current = token;
|
|
69
71
|
const { appNo, showFirstSession } = config || {};
|
|
70
72
|
const [appInfo, setAppInfo] = (0, import_react.useState)(null);
|
|
71
73
|
const [initialized, setInitialized] = (0, import_react.useState)(false);
|
|
@@ -346,7 +348,7 @@ function useADKChat({
|
|
|
346
348
|
"Content-Type": "application/json",
|
|
347
349
|
"Cache-Control": "no-cache",
|
|
348
350
|
stream: "true",
|
|
349
|
-
Authorization: `Bearer ${
|
|
351
|
+
Authorization: `Bearer ${tokenRef.current}`
|
|
350
352
|
},
|
|
351
353
|
async onopen(response) {
|
|
352
354
|
if (response.ok && response.headers.get("content-type") === import_fetch_event_source.EventStreamContentType) {
|
|
@@ -803,7 +805,7 @@ function useADKChat({
|
|
|
803
805
|
url,
|
|
804
806
|
appNo,
|
|
805
807
|
sessionId,
|
|
806
|
-
token
|
|
808
|
+
token: tokenRef.current
|
|
807
809
|
});
|
|
808
810
|
if ((result == null ? void 0 : result.code) === import_constants.API_SUCCESS_CODE) {
|
|
809
811
|
setPrologue(((_a = appInfo == null ? void 0 : appInfo.onboardingInfo) == null ? void 0 : _a.prologue) || "");
|
|
@@ -831,7 +833,7 @@ function useADKChat({
|
|
|
831
833
|
} = await (0, import_api.fetchSessionList)({
|
|
832
834
|
url,
|
|
833
835
|
appNo,
|
|
834
|
-
token,
|
|
836
|
+
token: tokenRef.current,
|
|
835
837
|
pageSize: sessionPagination.pageSize
|
|
836
838
|
});
|
|
837
839
|
if ((result == null ? void 0 : result.code) === import_constants.API_SUCCESS_CODE) {
|
|
@@ -897,7 +899,7 @@ function useADKChat({
|
|
|
897
899
|
url,
|
|
898
900
|
appNo,
|
|
899
901
|
sessionId,
|
|
900
|
-
token
|
|
902
|
+
token: tokenRef.current
|
|
901
903
|
});
|
|
902
904
|
if ((result == null ? void 0 : result.code) === import_constants.API_SUCCESS_CODE) {
|
|
903
905
|
initAppConversations(true);
|
|
@@ -914,7 +916,7 @@ function useADKChat({
|
|
|
914
916
|
const { result } = await (0, import_api.addFeedback)({
|
|
915
917
|
url,
|
|
916
918
|
appNo,
|
|
917
|
-
token,
|
|
919
|
+
token: tokenRef.current,
|
|
918
920
|
requestId: invocationId,
|
|
919
921
|
isLike,
|
|
920
922
|
...feedbackData
|
|
@@ -937,7 +939,7 @@ function useADKChat({
|
|
|
937
939
|
appNo,
|
|
938
940
|
title,
|
|
939
941
|
sessionId,
|
|
940
|
-
token
|
|
942
|
+
token: tokenRef.current
|
|
941
943
|
});
|
|
942
944
|
if ((result == null ? void 0 : result.code) === import_constants.API_SUCCESS_CODE) {
|
|
943
945
|
initAppConversations();
|