@ai-group/chat-sdk 3.5.13 → 3.6.0-alpha.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/FileGallery/index.js +1 -1
- package/dist/cjs/components/FileGallery/index.js.map +1 -1
- 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/styles.js +4 -1
- package/dist/cjs/components/FolderTree/styles.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/components/XAdkThoughtChain/index.js +75 -39
- package/dist/cjs/components/XAdkThoughtChain/index.js.map +2 -2
- package/dist/cjs/components/XAdkThoughtChain/styles.js +27 -26
- package/dist/cjs/components/XAdkThoughtChain/styles.js.map +2 -2
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.js +9 -0
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/styles/tokens.d.ts +27 -0
- package/dist/cjs/styles/tokens.js +72 -0
- package/dist/cjs/styles/tokens.js.map +7 -0
- package/dist/cjs/types/XAdkThoughtChain.d.ts +13 -0
- package/dist/cjs/types/XAdkThoughtChain.js.map +1 -1
- package/dist/esm/components/FileGallery/index.js +1 -1
- package/dist/esm/components/FileGallery/index.js.map +1 -1
- 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/styles.js +2 -2
- package/dist/esm/components/FolderTree/styles.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 +3 -2
- package/dist/esm/components/XAdkProvider/index.js.map +1 -1
- package/dist/esm/components/XAdkThoughtChain/index.js +100 -50
- package/dist/esm/components/XAdkThoughtChain/index.js.map +1 -1
- package/dist/esm/components/XAdkThoughtChain/styles.js +20 -19
- package/dist/esm/components/XAdkThoughtChain/styles.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/styles/tokens.d.ts +27 -0
- package/dist/esm/styles/tokens.js +42 -0
- package/dist/esm/styles/tokens.js.map +1 -0
- package/dist/esm/types/XAdkThoughtChain.d.ts +13 -0
- package/dist/esm/types/XAdkThoughtChain.js.map +1 -1
- package/dist/umd/chat-sdk.min.js +1 -1
- package/package.json +1 -1
|
@@ -163,7 +163,7 @@ var FileGallery = ({
|
|
|
163
163
|
{
|
|
164
164
|
src: url,
|
|
165
165
|
alt: fileName,
|
|
166
|
-
fallback: "data:image/png;base64,
|
|
166
|
+
fallback: "data:image/png;base64,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",
|
|
167
167
|
preview: onClick ? false : { src: url }
|
|
168
168
|
}
|
|
169
169
|
),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/FileGallery/index.tsx"],
|
|
4
|
-
"sourcesContent": ["import React, { useState, useEffect } from \"react\";\nimport { Image, Tooltip, Progress } from \"antd\";\nimport {\n FileOutlined,\n FilePdfOutlined,\n FileWordOutlined,\n FileExcelOutlined,\n FilePptOutlined,\n FileImageOutlined,\n CloseOutlined,\n AudioOutlined,\n FileZipOutlined,\n VideoCameraOutlined,\n FileMarkdownOutlined,\n} from \"@ant-design/icons\";\nimport { FileItem, FileGalleryProps } from \"@/types\";\nimport { useStyles } from \"./styles\";\n\n// ==================== 工具函数 ====================\n\n/**\n * 获取文件名\n */\nconst getFileName = (file: FileItem): string => {\n return file.displayName || file.name || \"\";\n};\n\n/**\n * 获取文件大小\n */\nconst getFileSize = (file: FileItem): number => {\n return file.size || 0;\n};\n\n/**\n * 获取文件 MIME 类型\n */\nconst getMimeType = (file: FileItem): string => {\n return file.mimeType || file.type || \"\";\n};\n\n/**\n * 获取文件 URL\n */\nconst getFileUrl = (file: FileItem): string => {\n return (\n file.fileUri ||\n file.response?.fileUrl ||\n file.response?.tempUrl ||\n file.tempUrl ||\n \"\"\n );\n};\n\n/**\n * 获取文件唯一标识\n */\nconst getFileId = (file: FileItem): string => {\n return file.id || file.uid || \"\";\n};\n\n/**\n * 判断是否是图片文件\n */\nconst isImageFile = (file: FileItem): boolean => {\n const mimeType = getMimeType(file);\n if (mimeType && mimeType.startsWith(\"image/\")) return true;\n\n const fileName = getFileName(file);\n if (fileName && fileName.match(/\\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i))\n return true;\n\n const url = getFileUrl(file);\n if (url && url.match(/\\.(jpg|jpeg|png|gif|webp|bmp|svg)(\\?.*)?$/i))\n return true;\n\n return false;\n};\n\n/**\n * 判断是否是音频文件\n */\nconst isAudioFile = (file: FileItem): boolean => {\n const mimeType = getMimeType(file);\n if (mimeType && mimeType.startsWith(\"audio/\")) return true;\n\n const fileName = getFileName(file);\n return !!fileName?.match(/\\.(mp3|wav|m4a|aac|ogg|flac)$/i);\n};\n\n/**\n * 判断是否是视频文件\n */\nconst isVideoFile = (file: FileItem): boolean => {\n const mimeType = getMimeType(file);\n if (mimeType && mimeType.startsWith(\"video/\")) return true;\n\n const fileName = getFileName(file);\n return !!fileName?.match(/\\.(mp4|mov|webm|mkv|avi)$/i);\n};\n\n/**\n * 判断是否是压缩包文件\n */\nconst isZipFile = (file: FileItem): boolean => {\n const fileName = getFileName(file);\n return !!fileName?.match(/\\.(zip|rar|7z|tar|gz|bz2|xz|tgz)$/i);\n};\n\n/**\n * 判断是否是 Markdown 文件\n */\nconst isMarkdownFile = (file: FileItem): boolean => {\n const fileName = getFileName(file);\n return !!fileName?.match(/\\.(md|markdown)$/i);\n};\n\n/**\n * 获取文件扩展名\n */\nconst getFileExtension = (filename: string): string => {\n const ext = filename.split(\".\").pop();\n return ext ? ext.toLowerCase() : \"\";\n};\n\n/**\n * 获取文件图标和颜色\n */\nconst getFileIcon = (\n file: FileItem,\n): { icon: React.ReactNode; color: string } => {\n const ext = getFileExtension(getFileName(file));\n\n // PDF\n if (ext === \"pdf\") {\n return { icon: <FilePdfOutlined />, color: \"#ff4d4f\" };\n }\n\n // Word\n if ([\"doc\", \"docx\"].includes(ext)) {\n return { icon: <FileWordOutlined />, color: \"#1677ff\" };\n }\n\n // Excel\n if ([\"xls\", \"xlsx\", \"csv\"].includes(ext)) {\n return { icon: <FileExcelOutlined />, color: \"#22b35e\" };\n }\n\n // PPT\n if ([\"ppt\", \"pptx\"].includes(ext)) {\n return { icon: <FilePptOutlined />, color: \"#ff6e31\" };\n }\n\n // Audio\n if ([\"mp3\", \"wav\", \"m4a\", \"aac\", \"ogg\", \"flac\"].includes(ext)) {\n return { icon: <AudioOutlined />, color: \"#722ed1\" };\n }\n\n // Video\n if ([\"mp4\", \"mov\", \"webm\", \"mkv\", \"avi\"].includes(ext)) {\n return { icon: <VideoCameraOutlined />, color: \"#fa8c16\" };\n }\n\n // 压缩包\n if ([\"zip\", \"rar\", \"7z\", \"tar\", \"gz\", \"bz2\", \"xz\", \"tgz\"].includes(ext)) {\n return { icon: <FileZipOutlined />, color: \"#eb2f96\" };\n }\n\n // Markdown\n if ([\"md\", \"markdown\"].includes(ext)) {\n return { icon: <FileMarkdownOutlined />, color: \"#13c2c2\" };\n }\n\n // 图片\n if (isImageFile(file)) {\n return { icon: <FileImageOutlined />, color: \"#8c8c8c\" };\n }\n\n // 默认\n return { icon: <FileOutlined />, color: \"#8c8c8c\" };\n};\n\n/**\n * 格式化文件大小\n */\nconst formatFileSize = (bytes: number): string => {\n if (bytes < 1024) return bytes + \" B\";\n if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + \" KB\";\n return (bytes / (1024 * 1024)).toFixed(1) + \" MB\";\n};\n\nconst styles = useStyles();\n\n/**\n * FileGallery - 单文件展示组件\n *\n * 功能特性:\n * - 📁 支持多种文件类型展示 (图片、音频、视频、文档等)\n * - 🖼️ 图片支持预览和缩略图\n * - 🎵 音频文件支持内联播放\n * - 🎬 视频文件支持内联播放\n * - 📊 显示文件大小、类型图标\n * - 🗑️ 支持删除操作\n * - 📤 支持上传进度显示\n * - 🎨 支持卡片内部布局控制(align:图标和信息的排列方式)\n *\n * @example\n * // XAdkSender 场景 - 可删除的本地文件\n * {files.map(file => (\n * <FileGallery\n * key={file.id}\n * file={file}\n * removable\n * onRemove={handleRemove}\n * />\n * ))}\n *\n * @example\n * // XadkChatbot 场景 - 只读的远程文件\n * {files.map(file => (\n * <FileGallery key={file.id} file={file} />\n * ))}\n */\nconst FileGallery: React.FC<FileGalleryProps> = ({\n file,\n align = \"left\",\n removable = false,\n onRemove = () => {},\n className,\n style,\n onClick,\n}) => {\n const [objectUrl, setObjectUrl] = useState(\"\");\n\n useEffect(() => {\n if (!file?.file) return;\n\n const url = URL.createObjectURL(file.file);\n setObjectUrl(url);\n\n return () => URL.revokeObjectURL(url);\n }, [file?.file]);\n\n if (!file) return null;\n\n const isImage = isImageFile(file);\n const isAudio = isAudioFile(file);\n const isVideo = isVideoFile(file);\n const isZip = isZipFile(file);\n const isMarkdown = isMarkdownFile(file);\n const fileName = getFileName(file);\n const fileSize = getFileSize(file);\n const fileId = getFileId(file);\n const url = getFileUrl(file) || objectUrl;\n\n // 图片文件\n if (isImage) {\n return (\n <div\n className={`${styles.fileCard} ${align === \"right\" ? \"align-right\" : \"\"} ${className || \"\"}`}\n style={{ ...style, cursor: onClick ? \"pointer\" : undefined }}\n onClick={() => onClick?.(file)}\n >\n {/* 图片缩略图 */}\n <div className={styles.imageThumbnail}>\n <Image\n src={url}\n alt={fileName}\n fallback=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==\"\n preview={onClick ? false : { src: url }}\n />\n\n {/* 上传进度遮罩 */}\n {file.status === \"uploading\" && (\n <div className={styles.imageOverlay}>\n <Progress\n type=\"circle\"\n percent={file.progress}\n size={30}\n strokeColor=\"#1677ff\"\n />\n </div>\n )}\n </div>\n\n {/* 文件信息 */}\n <div className={styles.fileInfo}>\n <Tooltip title={fileName}>\n <div className={styles.fileName}>\n {fileName.length > 15\n ? `${fileName.substring(0, 15)}...`\n : fileName}\n </div>\n </Tooltip>\n {Number(file?.size) > 0 && (\n <div className={styles.fileSize}>{formatFileSize(fileSize)}</div>\n )}\n </div>\n\n {/* 删除按钮 */}\n {removable && (\n <button\n className={styles.fileRemoveBtn}\n onClick={() => onRemove(fileId)}\n disabled={file.status === \"uploading\"}\n aria-label=\"删除文件\"\n >\n <CloseOutlined />\n </button>\n )}\n </div>\n );\n }\n\n // 音频文件\n if (isAudio) {\n return (\n <div\n className={`${styles.fileCard} ${align === \"right\" ? \"align-right\" : \"\"} ${className || \"\"}`}\n style={style}\n >\n <div className={styles.fileIcon} style={{ color: \"#722ed1\" }}>\n <AudioOutlined />\n </div>\n\n <div className={styles.fileInfo}>\n <Tooltip title={fileName}>\n <div className={styles.fileName}>{fileName}</div>\n </Tooltip>\n\n <audio\n src={url}\n controls\n className={styles.mediaWrapper}\n style={{ width: \"100%\", height: 28 }}\n />\n </div>\n\n {removable && (\n <button\n className={styles.fileRemoveBtn}\n onClick={() => onRemove(fileId)}\n disabled={file.status === \"uploading\"}\n aria-label=\"删除文件\"\n >\n <CloseOutlined />\n </button>\n )}\n </div>\n );\n }\n\n // 视频文件\n if (isVideo) {\n return (\n <div\n className={`${styles.fileCard} ${align === \"right\" ? \"align-right\" : \"\"} ${className || \"\"}`}\n style={style}\n >\n <div className={styles.fileIcon} style={{ color: \"#fa8c16\" }}>\n <VideoCameraOutlined />\n </div>\n\n <div className={styles.fileInfo}>\n <Tooltip title={fileName}>\n <div className={styles.fileName}>{fileName}</div>\n </Tooltip>\n\n <video\n src={url}\n controls\n className={styles.mediaWrapper}\n style={{ width: \"100%\", height: 40 }}\n />\n </div>\n\n {removable && (\n <button\n className={styles.fileRemoveBtn}\n onClick={() => onRemove(fileId)}\n disabled={file.status === \"uploading\"}\n aria-label=\"删除文件\"\n >\n <CloseOutlined />\n </button>\n )}\n </div>\n );\n }\n\n // 其他文件\n const { icon, color } = getFileIcon(file);\n const isExternalUrl = url && url.startsWith(\"http\");\n\n return (\n <div\n className={`${styles.fileCard} ${align === \"right\" ? \"align-right\" : \"\"} ${className || \"\"}`}\n style={{ ...style, cursor: onClick ? \"pointer\" : undefined }}\n onClick={() => onClick?.(file)}\n >\n <div className={styles.fileIcon} style={{ color }}>\n {icon}\n </div>\n\n <div className={styles.fileInfo}>\n {isExternalUrl && !onClick ? (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={styles.fileLink}\n >\n <Tooltip title={fileName}>\n <div className={styles.fileName}>\n {fileName.length > 15\n ? `${fileName.substring(0, 15)}...`\n : fileName}\n </div>\n </Tooltip>\n </a>\n ) : (\n <Tooltip title={fileName}>\n <div className={styles.fileName}>\n {fileName.length > 15\n ? `${fileName.substring(0, 15)}...`\n : fileName}\n </div>\n </Tooltip>\n )}\n\n {file.status === \"uploading\" ? (\n <Progress\n percent={file.progress}\n size=\"small\"\n showInfo={false}\n strokeColor=\"#1677ff\"\n className={styles.progress}\n />\n ) : (\n fileSize > 0 && (\n <div className={styles.fileSize}>{formatFileSize(fileSize)}</div>\n )\n )}\n\n {file.status === \"error\" && file.errorMessage && (\n <div\n style={{\n fontSize: \"11px\",\n color: \"#ff4d4f\",\n marginTop: \"2px\",\n }}\n >\n {file.errorMessage}\n </div>\n )}\n </div>\n\n {removable && (\n <button\n className={styles.fileRemoveBtn}\n onClick={() => onRemove(fileId)}\n disabled={file.status === \"uploading\"}\n aria-label=\"删除文件\"\n >\n <CloseOutlined />\n </button>\n )}\n </div>\n );\n};\n\nexport default FileGallery;\n"],
|
|
4
|
+
"sourcesContent": ["import React, { useState, useEffect } from \"react\";\nimport { Image, Tooltip, Progress } from \"antd\";\nimport {\n FileOutlined,\n FilePdfOutlined,\n FileWordOutlined,\n FileExcelOutlined,\n FilePptOutlined,\n FileImageOutlined,\n CloseOutlined,\n AudioOutlined,\n FileZipOutlined,\n VideoCameraOutlined,\n FileMarkdownOutlined,\n} from \"@ant-design/icons\";\nimport { FileItem, FileGalleryProps } from \"@/types\";\nimport { useStyles } from \"./styles\";\n\n// ==================== 工具函数 ====================\n\n/**\n * 获取文件名\n */\nconst getFileName = (file: FileItem): string => {\n return file.displayName || file.name || \"\";\n};\n\n/**\n * 获取文件大小\n */\nconst getFileSize = (file: FileItem): number => {\n return file.size || 0;\n};\n\n/**\n * 获取文件 MIME 类型\n */\nconst getMimeType = (file: FileItem): string => {\n return file.mimeType || file.type || \"\";\n};\n\n/**\n * 获取文件 URL\n */\nconst getFileUrl = (file: FileItem): string => {\n return (\n file.fileUri ||\n file.response?.fileUrl ||\n file.response?.tempUrl ||\n file.tempUrl ||\n \"\"\n );\n};\n\n/**\n * 获取文件唯一标识\n */\nconst getFileId = (file: FileItem): string => {\n return file.id || file.uid || \"\";\n};\n\n/**\n * 判断是否是图片文件\n */\nconst isImageFile = (file: FileItem): boolean => {\n const mimeType = getMimeType(file);\n if (mimeType && mimeType.startsWith(\"image/\")) return true;\n\n const fileName = getFileName(file);\n if (fileName && fileName.match(/\\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i))\n return true;\n\n const url = getFileUrl(file);\n if (url && url.match(/\\.(jpg|jpeg|png|gif|webp|bmp|svg)(\\?.*)?$/i))\n return true;\n\n return false;\n};\n\n/**\n * 判断是否是音频文件\n */\nconst isAudioFile = (file: FileItem): boolean => {\n const mimeType = getMimeType(file);\n if (mimeType && mimeType.startsWith(\"audio/\")) return true;\n\n const fileName = getFileName(file);\n return !!fileName?.match(/\\.(mp3|wav|m4a|aac|ogg|flac)$/i);\n};\n\n/**\n * 判断是否是视频文件\n */\nconst isVideoFile = (file: FileItem): boolean => {\n const mimeType = getMimeType(file);\n if (mimeType && mimeType.startsWith(\"video/\")) return true;\n\n const fileName = getFileName(file);\n return !!fileName?.match(/\\.(mp4|mov|webm|mkv|avi)$/i);\n};\n\n/**\n * 判断是否是压缩包文件\n */\nconst isZipFile = (file: FileItem): boolean => {\n const fileName = getFileName(file);\n return !!fileName?.match(/\\.(zip|rar|7z|tar|gz|bz2|xz|tgz)$/i);\n};\n\n/**\n * 判断是否是 Markdown 文件\n */\nconst isMarkdownFile = (file: FileItem): boolean => {\n const fileName = getFileName(file);\n return !!fileName?.match(/\\.(md|markdown)$/i);\n};\n\n/**\n * 获取文件扩展名\n */\nconst getFileExtension = (filename: string): string => {\n const ext = filename.split(\".\").pop();\n return ext ? ext.toLowerCase() : \"\";\n};\n\n/**\n * 获取文件图标和颜色\n */\nconst getFileIcon = (\n file: FileItem,\n): { icon: React.ReactNode; color: string } => {\n const ext = getFileExtension(getFileName(file));\n\n // PDF\n if (ext === \"pdf\") {\n return { icon: <FilePdfOutlined />, color: \"#ff4d4f\" };\n }\n\n // Word\n if ([\"doc\", \"docx\"].includes(ext)) {\n return { icon: <FileWordOutlined />, color: \"#1677ff\" };\n }\n\n // Excel\n if ([\"xls\", \"xlsx\", \"csv\"].includes(ext)) {\n return { icon: <FileExcelOutlined />, color: \"#22b35e\" };\n }\n\n // PPT\n if ([\"ppt\", \"pptx\"].includes(ext)) {\n return { icon: <FilePptOutlined />, color: \"#ff6e31\" };\n }\n\n // Audio\n if ([\"mp3\", \"wav\", \"m4a\", \"aac\", \"ogg\", \"flac\"].includes(ext)) {\n return { icon: <AudioOutlined />, color: \"#722ed1\" };\n }\n\n // Video\n if ([\"mp4\", \"mov\", \"webm\", \"mkv\", \"avi\"].includes(ext)) {\n return { icon: <VideoCameraOutlined />, color: \"#fa8c16\" };\n }\n\n // 压缩包\n if ([\"zip\", \"rar\", \"7z\", \"tar\", \"gz\", \"bz2\", \"xz\", \"tgz\"].includes(ext)) {\n return { icon: <FileZipOutlined />, color: \"#eb2f96\" };\n }\n\n // Markdown\n if ([\"md\", \"markdown\"].includes(ext)) {\n return { icon: <FileMarkdownOutlined />, color: \"#13c2c2\" };\n }\n\n // 图片\n if (isImageFile(file)) {\n return { icon: <FileImageOutlined />, color: \"#8c8c8c\" };\n }\n\n // 默认\n return { icon: <FileOutlined />, color: \"#8c8c8c\" };\n};\n\n/**\n * 格式化文件大小\n */\nconst formatFileSize = (bytes: number): string => {\n if (bytes < 1024) return bytes + \" B\";\n if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + \" KB\";\n return (bytes / (1024 * 1024)).toFixed(1) + \" MB\";\n};\n\nconst styles = useStyles();\n\n/**\n * FileGallery - 单文件展示组件\n *\n * 功能特性:\n * - 📁 支持多种文件类型展示 (图片、音频、视频、文档等)\n * - 🖼️ 图片支持预览和缩略图\n * - 🎵 音频文件支持内联播放\n * - 🎬 视频文件支持内联播放\n * - 📊 显示文件大小、类型图标\n * - 🗑️ 支持删除操作\n * - 📤 支持上传进度显示\n * - 🎨 支持卡片内部布局控制(align:图标和信息的排列方式)\n *\n * @example\n * // XAdkSender 场景 - 可删除的本地文件\n * {files.map(file => (\n * <FileGallery\n * key={file.id}\n * file={file}\n * removable\n * onRemove={handleRemove}\n * />\n * ))}\n *\n * @example\n * // XadkChatbot 场景 - 只读的远程文件\n * {files.map(file => (\n * <FileGallery key={file.id} file={file} />\n * ))}\n */\nconst FileGallery: React.FC<FileGalleryProps> = ({\n file,\n align = \"left\",\n removable = false,\n onRemove = () => {},\n className,\n style,\n onClick,\n}) => {\n const [objectUrl, setObjectUrl] = useState(\"\");\n\n useEffect(() => {\n if (!file?.file) return;\n\n const url = URL.createObjectURL(file.file);\n setObjectUrl(url);\n\n return () => URL.revokeObjectURL(url);\n }, [file?.file]);\n\n if (!file) return null;\n\n const isImage = isImageFile(file);\n const isAudio = isAudioFile(file);\n const isVideo = isVideoFile(file);\n const isZip = isZipFile(file);\n const isMarkdown = isMarkdownFile(file);\n const fileName = getFileName(file);\n const fileSize = getFileSize(file);\n const fileId = getFileId(file);\n const url = getFileUrl(file) || objectUrl;\n\n // 图片文件\n if (isImage) {\n return (\n <div\n className={`${styles.fileCard} ${align === \"right\" ? \"align-right\" : \"\"} ${className || \"\"}`}\n style={{ ...style, cursor: onClick ? \"pointer\" : undefined }}\n onClick={() => onClick?.(file)}\n >\n {/* 图片缩略图 */}\n <div className={styles.imageThumbnail}>\n <Image\n src={url}\n alt={fileName}\n fallback=\"data:image/png;base64,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\"\n preview={onClick ? false : { src: url }}\n />\n\n {/* 上传进度遮罩 */}\n {file.status === \"uploading\" && (\n <div className={styles.imageOverlay}>\n <Progress\n type=\"circle\"\n percent={file.progress}\n size={30}\n strokeColor=\"#1677ff\"\n />\n </div>\n )}\n </div>\n\n {/* 文件信息 */}\n <div className={styles.fileInfo}>\n <Tooltip title={fileName}>\n <div className={styles.fileName}>\n {fileName.length > 15\n ? `${fileName.substring(0, 15)}...`\n : fileName}\n </div>\n </Tooltip>\n {Number(file?.size) > 0 && (\n <div className={styles.fileSize}>{formatFileSize(fileSize)}</div>\n )}\n </div>\n\n {/* 删除按钮 */}\n {removable && (\n <button\n className={styles.fileRemoveBtn}\n onClick={() => onRemove(fileId)}\n disabled={file.status === \"uploading\"}\n aria-label=\"删除文件\"\n >\n <CloseOutlined />\n </button>\n )}\n </div>\n );\n }\n\n // 音频文件\n if (isAudio) {\n return (\n <div\n className={`${styles.fileCard} ${align === \"right\" ? \"align-right\" : \"\"} ${className || \"\"}`}\n style={style}\n >\n <div className={styles.fileIcon} style={{ color: \"#722ed1\" }}>\n <AudioOutlined />\n </div>\n\n <div className={styles.fileInfo}>\n <Tooltip title={fileName}>\n <div className={styles.fileName}>{fileName}</div>\n </Tooltip>\n\n <audio\n src={url}\n controls\n className={styles.mediaWrapper}\n style={{ width: \"100%\", height: 28 }}\n />\n </div>\n\n {removable && (\n <button\n className={styles.fileRemoveBtn}\n onClick={() => onRemove(fileId)}\n disabled={file.status === \"uploading\"}\n aria-label=\"删除文件\"\n >\n <CloseOutlined />\n </button>\n )}\n </div>\n );\n }\n\n // 视频文件\n if (isVideo) {\n return (\n <div\n className={`${styles.fileCard} ${align === \"right\" ? \"align-right\" : \"\"} ${className || \"\"}`}\n style={style}\n >\n <div className={styles.fileIcon} style={{ color: \"#fa8c16\" }}>\n <VideoCameraOutlined />\n </div>\n\n <div className={styles.fileInfo}>\n <Tooltip title={fileName}>\n <div className={styles.fileName}>{fileName}</div>\n </Tooltip>\n\n <video\n src={url}\n controls\n className={styles.mediaWrapper}\n style={{ width: \"100%\", height: 40 }}\n />\n </div>\n\n {removable && (\n <button\n className={styles.fileRemoveBtn}\n onClick={() => onRemove(fileId)}\n disabled={file.status === \"uploading\"}\n aria-label=\"删除文件\"\n >\n <CloseOutlined />\n </button>\n )}\n </div>\n );\n }\n\n // 其他文件\n const { icon, color } = getFileIcon(file);\n const isExternalUrl = url && url.startsWith(\"http\");\n\n return (\n <div\n className={`${styles.fileCard} ${align === \"right\" ? \"align-right\" : \"\"} ${className || \"\"}`}\n style={{ ...style, cursor: onClick ? \"pointer\" : undefined }}\n onClick={() => onClick?.(file)}\n >\n <div className={styles.fileIcon} style={{ color }}>\n {icon}\n </div>\n\n <div className={styles.fileInfo}>\n {isExternalUrl && !onClick ? (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={styles.fileLink}\n >\n <Tooltip title={fileName}>\n <div className={styles.fileName}>\n {fileName.length > 15\n ? `${fileName.substring(0, 15)}...`\n : fileName}\n </div>\n </Tooltip>\n </a>\n ) : (\n <Tooltip title={fileName}>\n <div className={styles.fileName}>\n {fileName.length > 15\n ? `${fileName.substring(0, 15)}...`\n : fileName}\n </div>\n </Tooltip>\n )}\n\n {file.status === \"uploading\" ? (\n <Progress\n percent={file.progress}\n size=\"small\"\n showInfo={false}\n strokeColor=\"#1677ff\"\n className={styles.progress}\n />\n ) : (\n fileSize > 0 && (\n <div className={styles.fileSize}>{formatFileSize(fileSize)}</div>\n )\n )}\n\n {file.status === \"error\" && file.errorMessage && (\n <div\n style={{\n fontSize: \"11px\",\n color: \"#ff4d4f\",\n marginTop: \"2px\",\n }}\n >\n {file.errorMessage}\n </div>\n )}\n </div>\n\n {removable && (\n <button\n className={styles.fileRemoveBtn}\n onClick={() => onRemove(fileId)}\n disabled={file.status === \"uploading\"}\n aria-label=\"删除文件\"\n >\n <CloseOutlined />\n </button>\n )}\n </div>\n );\n};\n\nexport default FileGallery;\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["url"]
|
|
7
7
|
}
|
|
@@ -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
|
}
|
|
@@ -125,7 +125,7 @@ var useStyles = (0, import_common.withBasicStyles)(() => ({
|
|
|
125
125
|
z-index: 10;
|
|
126
126
|
pointer-events: none;
|
|
127
127
|
}
|
|
128
|
-
|
|
128
|
+
`,
|
|
129
129
|
/** 自定义拖拽 - 节点可拖拽样式 */
|
|
130
130
|
dragNodeHandle: import_css.css`
|
|
131
131
|
cursor: grab;
|
|
@@ -199,7 +199,10 @@ var useStyles = (0, import_common.withBasicStyles)(() => ({
|
|
|
199
199
|
margin: 2px;
|
|
200
200
|
color: #999;
|
|
201
201
|
font-size: 12px;
|
|
202
|
+
line-height: 20px;
|
|
202
203
|
transition: all 0.2s;
|
|
204
|
+
height: 20px;
|
|
205
|
+
box-sizing: border-box;
|
|
203
206
|
|
|
204
207
|
&:hover {
|
|
205
208
|
border-radius: 50%;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/FolderTree/styles.tsx"],
|
|
4
|
-
"sourcesContent": ["import { css } from \"@emotion/css\";\nimport { withBasicStyles } from \"@/styles/common\";\n\nexport const useStyles = withBasicStyles(() => ({\n container: css`\n display: flex;\n width: 100%;\n height: 500px;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n overflow: hidden;\n background: #fff;\n `,\n containerNoPreview: css`\n height: auto;\n border: none;\n border-radius: 0;\n & > *:first-child {\n border-right: none;\n }\n `,\n directoryPanel: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: auto;\n border-right: 1px solid #e8e8e8;\n background: var(--folder-tree-directory-bg, #fafafa);\n `,\n directoryTitle: css`\n padding: 12px 16px;\n font-weight: 600;\n font-size: 14px;\n border-bottom: 1px solid #e8e8e8;\n color: #333;\n `,\n directoryTreeContent: css`\n padding: 4px 8px;\n flex: 1;\n\n .ant-tree {\n background: transparent;\n height: 100%;\n }\n .ant-tree-switcher {\n margin-right: 0;\n }\n .ant-tree .ant-tree-node-content-wrapper {\n display: inline-flex;\n align-items: center;\n flex: 1;\n min-width: 0;\n padding-left: 0;\n overflow: hidden;\n }\n .ant-tree .ant-tree-title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n }\n .ant-tree-directory .ant-tree-treenode {\n position: relative;\n &::before {\n border-radius: 4px;\n }\n }\n /* 自定义拖拽样式 — 基于 ant-tree-treenode 定位,覆盖整行 */\n .ant-tree-treenode.folder-tree-drop-before::after {\n content: \"\";\n position: absolute;\n top: -2px;\n left: 0;\n right: 0;\n height: 2px;\n background: transparent;\n border-top: 2px dashed #1677ff;\n z-index: 10;\n pointer-events: none;\n }\n .ant-tree-treenode.folder-tree-drop-inside::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n border: 2px dashed #1677ff;\n border-radius: 4px;\n background: rgba(22, 119, 255, 0.04);\n z-index: 10;\n pointer-events: none;\n }\n .ant-tree-treenode.folder-tree-drop-after::after {\n content: \"\";\n position: absolute;\n bottom: -2px;\n left: 0;\n right: 0;\n height: 2px;\n background: transparent;\n border-bottom: 2px dashed #1677ff;\n z-index: 10;\n pointer-events: none;\n }\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAoB;AACpB,oBAAgC;AAEzB,IAAM,gBAAY,+BAAgB,OAAO;AAAA,EAC9C,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASX,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQpB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQhB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhB,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuEtB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAShB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKjB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlB,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcf,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import { css } from \"@emotion/css\";\nimport { withBasicStyles } from \"@/styles/common\";\n\nexport const useStyles = withBasicStyles(() => ({\n container: css`\n display: flex;\n width: 100%;\n height: 500px;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n overflow: hidden;\n background: #fff;\n `,\n containerNoPreview: css`\n height: auto;\n border: none;\n border-radius: 0;\n & > *:first-child {\n border-right: none;\n }\n `,\n directoryPanel: css`\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: auto;\n border-right: 1px solid #e8e8e8;\n background: var(--folder-tree-directory-bg, #fafafa);\n `,\n directoryTitle: css`\n padding: 12px 16px;\n font-weight: 600;\n font-size: 14px;\n border-bottom: 1px solid #e8e8e8;\n color: #333;\n `,\n directoryTreeContent: css`\n padding: 4px 8px;\n flex: 1;\n\n .ant-tree {\n background: transparent;\n height: 100%;\n }\n .ant-tree-switcher {\n margin-right: 0;\n }\n .ant-tree .ant-tree-node-content-wrapper {\n display: inline-flex;\n align-items: center;\n flex: 1;\n min-width: 0;\n padding-left: 0;\n overflow: hidden;\n }\n .ant-tree .ant-tree-title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n }\n .ant-tree-directory .ant-tree-treenode {\n position: relative;\n &::before {\n border-radius: 4px;\n }\n }\n /* 自定义拖拽样式 — 基于 ant-tree-treenode 定位,覆盖整行 */\n .ant-tree-treenode.folder-tree-drop-before::after {\n content: \"\";\n position: absolute;\n top: -2px;\n left: 0;\n right: 0;\n height: 2px;\n background: transparent;\n border-top: 2px dashed #1677ff;\n z-index: 10;\n pointer-events: none;\n }\n .ant-tree-treenode.folder-tree-drop-inside::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n border: 2px dashed #1677ff;\n border-radius: 4px;\n background: rgba(22, 119, 255, 0.04);\n z-index: 10;\n pointer-events: none;\n }\n .ant-tree-treenode.folder-tree-drop-after::after {\n content: \"\";\n position: absolute;\n bottom: -2px;\n left: 0;\n right: 0;\n height: 2px;\n background: transparent;\n border-bottom: 2px dashed #1677ff;\n z-index: 10;\n pointer-events: none;\n }\n `,\n\n /** 自定义拖拽 - 节点可拖拽样式 */\n dragNodeHandle: css`\n cursor: grab;\n user-select: none;\n\n &:active {\n cursor: grabbing;\n }\n `,\n /** 树节点容器,用于相对定位 drop indicator */\n treeNodeWrapper: css`\n position: relative;\n min-width: 0;\n overflow: hidden;\n `,\n previewPanel: css`\n flex: 1;\n height: 100%;\n overflow: auto;\n display: flex;\n flex-direction: column;\n `,\n previewTitle: css`\n padding: 10px 16px;\n font-size: 13px;\n font-weight: 500;\n border-bottom: 1px solid #e8e8e8;\n display: flex;\n align-items: center;\n justify-content: space-between;\n color: #333;\n background: #fff;\n `,\n previewContent: css`\n flex: 1;\n overflow: auto;\n padding: 16px;\n font-size: 13px;\n line-height: 1.6;\n `,\n emptyContainer: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n `,\n loadingContainer: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n `,\n treeNodeTitle: css`\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-width: 0;\n overflow: hidden;\n padding-right: 4px;\n\n > span:first-child {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n `,\n moreIcon: css`\n opacity: 0;\n cursor: pointer;\n padding: 0 4px;\n margin: 2px;\n color: #999;\n font-size: 12px;\n line-height: 20px;\n transition: all 0.2s;\n height: 20px;\n box-sizing: border-box;\n\n &:hover {\n border-radius: 50%;\n color: #333;\n background: rgba(0, 0, 0, 0.04);\n }\n `,\n treeNodeTitleHover: css`\n &:hover .folder-tree-more-icon {\n opacity: 1;\n }\n `,\n inlineInput: css`\n width: 100%;\n .ant-input {\n height: 22px;\n font-size: 13px;\n padding: 0 6px;\n border-radius: 4px;\n }\n `,\n copyBtn: css`\n cursor: pointer;\n color: #999;\n font-size: 14px;\n &:hover {\n color: #1677ff;\n }\n `,\n}));\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAoB;AACpB,oBAAgC;AAEzB,IAAM,gBAAY,+BAAgB,OAAO;AAAA,EAC9C,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASX,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQpB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQhB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhB,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuEtB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAShB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKjB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlB,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcf,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBV,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASb,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQX,EAAE;",
|
|
6
6
|
"names": []
|
|
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
|
}
|
|
@@ -40,6 +40,7 @@ var import_ChatActionContext = require("./context/ChatActionContext");
|
|
|
40
40
|
var import_SessionContext = require("./context/SessionContext");
|
|
41
41
|
var import_useADKChat = __toESM(require("../../hooks/useADKChat"));
|
|
42
42
|
var import_common = require("../../styles/common");
|
|
43
|
+
var import_tokens = require("../../styles/tokens");
|
|
43
44
|
var import_xGroupAdk = require("../../presets/xGroupAdk");
|
|
44
45
|
var import_Header = __toESM(require("./compound/Header"));
|
|
45
46
|
var import_Sidebar = __toESM(require("./compound/Sidebar"));
|
|
@@ -238,6 +239,7 @@ var XAdkProviderComponent = (0, import_react.forwardRef)(
|
|
|
238
239
|
"div",
|
|
239
240
|
{
|
|
240
241
|
style: {
|
|
242
|
+
...import_tokens.cssVarStyle,
|
|
241
243
|
display: "flex",
|
|
242
244
|
height: "100%",
|
|
243
245
|
width: "100%",
|