@spacecorexdev/core 0.0.4 → 0.0.5

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.
Files changed (132) hide show
  1. package/package.json +2 -36
  2. package/src/api/_base.ts +243 -0
  3. package/src/api/index.ts +17 -0
  4. package/src/api/project.ts +1345 -0
  5. package/src/api/user.ts +1174 -0
  6. package/src/contract.ts +50 -0
  7. package/src/hooks/index.ts +2 -0
  8. package/{dist/hooks/index.js → src/hooks/useLongPress.ts} +53 -49
  9. package/src/hooks/useOnlineStatus.ts +22 -0
  10. package/src/index.ts +14 -0
  11. package/src/types/id.ts +2 -0
  12. package/src/types/index.ts +5 -0
  13. package/src/types/project/base.ts +63 -0
  14. package/src/types/project/contract.ts +79 -0
  15. package/src/types/project/contractStatus.tsx +15 -0
  16. package/src/types/project/index.ts +5 -0
  17. package/src/types/project/member.ts +46 -0
  18. package/src/types/project/progress.ts +43 -0
  19. package/src/types/projectRole.ts +17 -0
  20. package/src/types/theme.ts +1 -0
  21. package/src/types/user/index.ts +1 -0
  22. package/src/types/user/role.ts +7 -0
  23. package/src/ui/CommentTimeline/index.tsx +330 -0
  24. package/src/ui/FileManager/FileContextMenu.tsx +628 -0
  25. package/src/ui/FileManager/FileGridView.tsx +529 -0
  26. package/src/ui/FileManager/FileTableView.tsx +405 -0
  27. package/src/ui/FileManager/FileToolbar.tsx +119 -0
  28. package/src/ui/FileManager/FileVersionHistoryModal.tsx +175 -0
  29. package/src/ui/FileManager/MoveCopyToProjectModal.tsx +119 -0
  30. package/src/ui/FileManager/ShareToWechatModal.tsx +419 -0
  31. package/src/ui/FileManager/hooks/useFileManagerActions.ts +2532 -0
  32. package/src/ui/FileManager/hooks/useFileManagerState.ts +419 -0
  33. package/src/ui/FileManager/index.tsx +580 -0
  34. package/src/ui/FileManager/projectFileManagerApi.ts +57 -0
  35. package/src/ui/FileManager/types.ts +184 -0
  36. package/src/ui/FileManager/utils/fileDeltaWorker.ts +193 -0
  37. package/src/ui/FileManager/utils/fileTree.ts +178 -0
  38. package/src/ui/FileManager/utils/fileUpload.ts +714 -0
  39. package/src/ui/FileManager/utils/fileUtils.ts +65 -0
  40. package/src/ui/InvoiceRecordsList/index.tsx +813 -0
  41. package/src/ui/MyFileSelectorModal.tsx +261 -0
  42. package/src/ui/ProjectSelector/index.tsx +472 -0
  43. package/src/ui/ProjectSelector/projectCache.ts +40 -0
  44. package/src/ui/RoleSelector/index.tsx +473 -0
  45. package/src/ui/RoleSelector/roleCache.ts +40 -0
  46. package/src/ui/UIAlert.tsx +399 -0
  47. package/src/ui/UIAlertExample.tsx +129 -0
  48. package/src/ui/UIConfirm.tsx +335 -0
  49. package/src/ui/UIConfirmExample.tsx +165 -0
  50. package/src/ui/UIDatePicker.tsx +146 -0
  51. package/src/ui/UIDateRangePicker.tsx +38 -0
  52. package/src/ui/UIFilePreview/ExcelEditor/index.tsx +617 -0
  53. package/src/ui/UIFilePreview/ExcelEditor/types.ts +24 -0
  54. package/src/ui/UIFilePreview/ExportDropdown.css +85 -0
  55. package/src/ui/UIFilePreview/ExportDropdown.tsx +109 -0
  56. package/src/ui/UIFilePreview/IndexedDBUtils.ts +564 -0
  57. package/src/ui/UIFilePreview/MarkdownEditor/exportMarkdownPdf.ts +576 -0
  58. package/src/ui/UIFilePreview/MarkdownEditor/index.tsx +677 -0
  59. package/src/ui/UIFilePreview/MarkdownEditor/markdownEditor.css +1005 -0
  60. package/src/ui/UIFilePreview/MarkdownEditor/types.ts +15 -0
  61. package/src/ui/UIFilePreview/PPTXEditor/index.tsx +602 -0
  62. package/src/ui/UIFilePreview/RichDocEditor/exportRDocMarkdown.ts +77 -0
  63. package/src/ui/UIFilePreview/RichDocEditor/exportRDocPdf.ts +374 -0
  64. package/src/ui/UIFilePreview/RichDocEditor/index.tsx +730 -0
  65. package/src/ui/UIFilePreview/RichDocEditor/richdoc-editor.css +59 -0
  66. package/src/ui/UIFilePreview/UIFilePreview.tsx +611 -0
  67. package/src/ui/UIFilePreview/WordEditor/index.tsx +3933 -0
  68. package/src/ui/UIFilePreview/WordEditor/style.ts +1213 -0
  69. package/src/ui/UIFilePreview/WordEditor/word-editor.css +999 -0
  70. package/src/ui/UIFilePreview/filePreview.css +193 -0
  71. package/src/ui/UIFilePreview/filetypes/archive.tsx +66 -0
  72. package/src/ui/UIFilePreview/filetypes/cad.tsx +287 -0
  73. package/src/ui/UIFilePreview/filetypes/excel.tsx +355 -0
  74. package/src/ui/UIFilePreview/filetypes/image.tsx +20 -0
  75. package/src/ui/UIFilePreview/filetypes/imageLoader.ts +63 -0
  76. package/src/ui/UIFilePreview/filetypes/pdf.tsx +156 -0
  77. package/src/ui/UIFilePreview/filetypes/ppt.tsx +535 -0
  78. package/src/ui/UIFilePreview/filetypes/pptExportPdf.ts +523 -0
  79. package/src/ui/UIFilePreview/filetypes/video.tsx +482 -0
  80. package/src/ui/UIFilePreview/filetypes/word.tsx +245 -0
  81. package/src/ui/UIFilePreview/filetypes/wordExportMarkdown.ts +86 -0
  82. package/src/ui/UIFilePreview/filetypes/wordExportPdf.tsx +427 -0
  83. package/src/ui/UIFilePreview/index.ts +35 -0
  84. package/src/ui/UIFilePreview/pdfPagination.ts +143 -0
  85. package/src/ui/UIFilePreview/previewUtils.tsx +531 -0
  86. package/src/ui/UIFileUpload.tsx +382 -0
  87. package/src/ui/UIImageUpload.tsx +195 -0
  88. package/src/ui/UILoading.tsx +255 -0
  89. package/src/ui/UILoadingExample.tsx +139 -0
  90. package/src/ui/UISelect.tsx +105 -0
  91. package/src/ui/UIToast.tsx +381 -0
  92. package/src/ui/UIToastExample.tsx +69 -0
  93. package/src/ui/UIToastSingle.tsx +279 -0
  94. package/src/ui/UIViewToggle.tsx +64 -0
  95. package/src/ui/UserSelector/index.tsx +583 -0
  96. package/src/ui/index.ts +84 -0
  97. package/src/ui/upload/image.ts +135 -0
  98. package/src/ui/upload/index.ts +73 -0
  99. package/src/ui/upload/submitFile.ts +106 -0
  100. package/src/ui/upload/submitForm.ts +124 -0
  101. package/src/ui/upload/upload.err.ts +15 -0
  102. package/src/ui/upload/upload.ts +250 -0
  103. package/src/utils/CacheDepartmentInfo.ts +372 -0
  104. package/src/utils/CacheFileContent.ts +315 -0
  105. package/src/utils/CachePermissionInfo.ts +235 -0
  106. package/src/utils/CacheRoleInfo.ts +303 -0
  107. package/src/utils/CacheUserInfo.ts +818 -0
  108. package/src/utils/CacheViewToggleInfo.ts +182 -0
  109. package/src/utils/MessageContentUtils.ts +268 -0
  110. package/src/utils/clipboard.ts +70 -0
  111. package/src/utils/companyEmail.ts +19 -0
  112. package/src/utils/forbidDebugger.ts +59 -0
  113. package/src/utils/formatters.ts +94 -0
  114. package/src/utils/htmlUtils.tsx +59 -0
  115. package/src/utils/index.ts +18 -0
  116. package/src/utils/keyboard.ts +114 -0
  117. package/src/utils/mammothApi.ts +16 -0
  118. package/src/utils/polyfills.ts +928 -0
  119. package/src/utils/regex.ts +14 -0
  120. package/src/utils/storage.ts +95 -0
  121. package/src/utils/workerBootstrap.ts +87 -0
  122. package/dist/api/index.js +0 -1553
  123. package/dist/contract.js +0 -5
  124. package/dist/index.js +0 -4492
  125. package/dist/types/index.js +0 -49
  126. package/dist/ui/index.css +0 -1876
  127. package/dist/ui/index.js +0 -22245
  128. package/dist/utils/index.js +0 -4296
  129. /package/{dist → src}/styles/ant-modal.css +0 -0
  130. /package/{dist → src}/styles/ant-spin.css +0 -0
  131. /package/{dist → src}/styles/ant-table.css +0 -0
  132. /package/{dist → src}/styles/index.css +0 -0
@@ -0,0 +1,261 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import type { Key } from 'react';
3
+ import { ReloadOutlined } from '@ant-design/icons';
4
+ import { Button, Empty, Input, Modal, Spin, Tree, message } from 'antd';
5
+ import type { DataNode } from 'antd/es/tree';
6
+ import { API } from '../api/index';
7
+ import { ResponseCode } from '../api/_base';
8
+ import type { ProjectFile } from './FileManager/types';
9
+ import { buildFileTree, sortFiles } from './FileManager/utils/fileTree';
10
+ import { filterNotesRootFiles } from './FileManager/utils/fileUtils';
11
+ import { uiFilePreview } from './UIFilePreview/index';
12
+ import { CacheUserInfo } from '../utils/CacheUserInfo';
13
+
14
+ interface MyFileSelectorModalProps {
15
+ open: boolean;
16
+ confirmLoading?: boolean;
17
+ title?: string;
18
+ searchPlaceholder?: string;
19
+ selectionMode?: 'single' | 'multiple';
20
+ okText?: string | ((selectedFiles: ProjectFile[]) => string);
21
+ emptyDescription?: string;
22
+ helperText?: string | ((selectedFiles: ProjectFile[]) => string);
23
+ fileFilter?: (file: ProjectFile) => boolean;
24
+ onCancel: () => void;
25
+ onConfirm: (files: ProjectFile[]) => void | Promise<void>;
26
+ }
27
+
28
+ const isFolderFile = (file: ProjectFile) => file.fileType === 'folder';
29
+
30
+ const formatFileSize = (size?: number) => {
31
+ if (!size || size <= 0) {
32
+ return '';
33
+ }
34
+ if (size < 1024) {
35
+ return `${size} B`;
36
+ }
37
+ if (size < 1024 * 1024) {
38
+ return `${(size / 1024).toFixed(1)} KB`;
39
+ }
40
+ return `${(size / 1024 / 1024).toFixed(1)} MB`;
41
+ };
42
+
43
+ const flattenFiles = (files: ProjectFile[]): ProjectFile[] =>
44
+ files.flatMap((file) => [file, ...flattenFiles(file.children || [])]);
45
+
46
+ const getFileDisplayPath = (file: ProjectFile) => {
47
+ const folderPath = file.filePath && file.filePath !== '/' ? file.filePath : '/';
48
+ return `${folderPath}${folderPath.endsWith('/') ? '' : '/'}${file.fileName}`;
49
+ };
50
+
51
+ const getDefaultOkText = (selectedFiles: ProjectFile[], selectionMode: 'single' | 'multiple') => {
52
+ if (selectionMode === 'single') {
53
+ return selectedFiles.length ? '选择文件' : '请选择文件';
54
+ }
55
+ return selectedFiles.length ? `添加 ${selectedFiles.length} 个文件` : '添加文件';
56
+ };
57
+
58
+ const getDefaultHelperText = (selectedFiles: ProjectFile[], selectionMode: 'single' | 'multiple') => {
59
+ if (selectionMode === 'single') {
60
+ return selectedFiles.length ? `已选择:${selectedFiles[0].fileName}` : '只能选择文件,文件夹不会被选中。';
61
+ }
62
+ return `只能选择文件,文件夹不会被添加。已选择 ${selectedFiles.length} 个文件。`;
63
+ };
64
+
65
+ const filterTreeFiles = (
66
+ files: ProjectFile[],
67
+ keyword: string,
68
+ fileFilter?: (file: ProjectFile) => boolean,
69
+ ): ProjectFile[] => {
70
+ const normalizedKeyword = keyword.trim().toLowerCase();
71
+
72
+ return files.flatMap((file) => {
73
+ if (isFolderFile(file)) {
74
+ const children = filterTreeFiles(file.children || [], keyword, fileFilter);
75
+ return children.length ? [{ ...file, children }] : [];
76
+ }
77
+
78
+ const matchesKeyword = !normalizedKeyword || getFileDisplayPath(file).toLowerCase().includes(normalizedKeyword);
79
+ const matchesFilter = fileFilter ? fileFilter(file) : true;
80
+ return matchesKeyword && matchesFilter ? [file] : [];
81
+ });
82
+ };
83
+
84
+ const toTreeNodes = (
85
+ files: ProjectFile[],
86
+ selectableFileIds: Set<string>,
87
+ selectionMode: 'single' | 'multiple',
88
+ ): DataNode[] =>
89
+ files.map((file) => {
90
+ const selectable = selectableFileIds.has(file.id);
91
+ return {
92
+ key: file.id,
93
+ selectable: selectionMode === 'single' ? selectable : false,
94
+ disableCheckbox: selectionMode === 'multiple' ? !selectable : undefined,
95
+ title: (
96
+ <div className="flex w-full min-w-0 max-w-full items-center gap-2 whitespace-nowrap">
97
+ <span className="flex h-5 w-5 shrink-0 items-center justify-center text-base text-muted">
98
+ {uiFilePreview.getFileIcon({
99
+ fileName: file.fileName,
100
+ fileType: file.fileType,
101
+ size: 18,
102
+ })}
103
+ </span>
104
+ <span className="min-w-0 flex-1 truncate" title={file.fileName}>{file.fileName}</span>
105
+ {!isFolderFile(file) && (
106
+ <span className="w-16 shrink-0 text-right text-xs text-muted">{formatFileSize(file.fileSize || 0)}</span>
107
+ )}
108
+ </div>
109
+ ),
110
+ children: file.children?.length ? toTreeNodes(file.children, selectableFileIds, selectionMode) : undefined,
111
+ };
112
+ });
113
+
114
+ export const MyFileSelectorModal = ({
115
+ open,
116
+ confirmLoading = false,
117
+ title = '从我的文件选择文件',
118
+ searchPlaceholder = '搜索我的文件',
119
+ selectionMode = 'multiple',
120
+ okText,
121
+ emptyDescription,
122
+ helperText,
123
+ fileFilter,
124
+ onCancel,
125
+ onConfirm,
126
+ }: MyFileSelectorModalProps) => {
127
+ const [loading, setLoading] = useState(false);
128
+ const [projectId, setProjectId] = useState('');
129
+ const [files, setFiles] = useState<ProjectFile[]>([]);
130
+ const [selectedKeys, setSelectedKeys] = useState<Key[]>([]);
131
+ const [keyword, setKeyword] = useState('');
132
+
133
+ const loadFiles = async () => {
134
+ const user = CacheUserInfo.currentLocalUser();
135
+ if (!user?.id) {
136
+ message.warning('请先登录后再选择我的文件');
137
+ return;
138
+ }
139
+
140
+ setLoading(true);
141
+ try {
142
+ let nextProjectId = user.id;
143
+ const projectRes = await API.project.project.getProjectName({ projectId: user.id });
144
+ if (!projectRes.data) {
145
+ const enableRes = await API.project.project.enableUserPrivateProject();
146
+ if (enableRes.code !== ResponseCode.SUCCESS || !enableRes.data) {
147
+ message.error(enableRes.message || '启用我的文件失败');
148
+ return;
149
+ }
150
+ nextProjectId = enableRes.data;
151
+ }
152
+
153
+ setProjectId(nextProjectId);
154
+ const listRes = await API.project.file.listFiles({ projectId: nextProjectId });
155
+ if (listRes.code !== ResponseCode.SUCCESS) {
156
+ message.error(listRes.message || '加载我的文件失败');
157
+ return;
158
+ }
159
+
160
+ setFiles(sortFiles(filterNotesRootFiles((listRes.data || []) as ProjectFile[])));
161
+ } finally {
162
+ setLoading(false);
163
+ }
164
+ };
165
+
166
+ useEffect(() => {
167
+ if (!open) {
168
+ setSelectedKeys([]);
169
+ setKeyword('');
170
+ return;
171
+ }
172
+
173
+ void loadFiles();
174
+ }, [open]);
175
+
176
+ const fileTreeData = useMemo(() => buildFileTree(files), [files]);
177
+ const allFiles = useMemo(() => flattenFiles(fileTreeData), [fileTreeData]);
178
+ const selectableFiles = useMemo(
179
+ () => allFiles.filter((file) => !isFolderFile(file) && (!fileFilter || fileFilter(file))),
180
+ [allFiles, fileFilter],
181
+ );
182
+ const selectableFileIds = useMemo(() => new Set(selectableFiles.map((file) => file.id)), [selectableFiles]);
183
+ const fileMap = useMemo(() => new Map(selectableFiles.map((file) => [file.id, file])), [selectableFiles]);
184
+ const selectedFiles = useMemo(
185
+ () => selectedKeys.flatMap((key) => {
186
+ const file = fileMap.get(String(key));
187
+ return file ? [file] : [];
188
+ }),
189
+ [selectedKeys, fileMap],
190
+ );
191
+ const treeData = useMemo(
192
+ () => toTreeNodes(filterTreeFiles(fileTreeData, keyword, fileFilter), selectableFileIds, selectionMode),
193
+ [fileFilter, fileTreeData, keyword, selectableFileIds, selectionMode],
194
+ );
195
+
196
+ const resolvedOkText = typeof okText === 'function' ? okText(selectedFiles) : okText || getDefaultOkText(selectedFiles, selectionMode);
197
+ const resolvedHelperText = typeof helperText === 'function'
198
+ ? helperText(selectedFiles)
199
+ : helperText || getDefaultHelperText(selectedFiles, selectionMode);
200
+
201
+ return (
202
+ <Modal
203
+ title={title}
204
+ open={open}
205
+ width={720}
206
+ className="max-md:!top-2 max-md:!max-w-[calc(100vw-16px)] max-md:[&_.ant-modal-body]:!flex max-md:[&_.ant-modal-body]:!min-h-0 max-md:[&_.ant-modal-body]:!flex-1 max-md:[&_.ant-modal-body]:!flex-col max-md:[&_.ant-modal-body]:!overflow-hidden max-md:[&_.ant-modal-content]:!flex max-md:[&_.ant-modal-content]:!h-[calc(100dvh-16px)] max-md:[&_.ant-modal-content]:!flex-col max-md:[&_.ant-modal-footer]:!shrink-0"
207
+ okText={resolvedOkText}
208
+ cancelText="取消"
209
+ confirmLoading={confirmLoading}
210
+ okButtonProps={{ disabled: selectedFiles.length === 0 }}
211
+ onCancel={confirmLoading ? undefined : onCancel}
212
+ onOk={() => onConfirm(selectedFiles)}
213
+ >
214
+ <div className="flex min-h-0 flex-col gap-3 max-md:flex-1">
215
+ <div className="flex shrink-0 items-center gap-2">
216
+ <Input.Search
217
+ allowClear
218
+ placeholder={searchPlaceholder}
219
+ value={keyword}
220
+ onChange={(event) => setKeyword(event.target.value)}
221
+ />
222
+ <Button icon={<ReloadOutlined />} disabled={loading || confirmLoading} onClick={loadFiles}>
223
+ 刷新
224
+ </Button>
225
+ </div>
226
+
227
+ <div className="max-h-[52vh] min-h-[360px] overflow-y-auto overflow-x-hidden rounded-xl border border-email-divider bg-surface p-3 max-md:min-h-0 max-md:flex-1 max-md:max-h-none">
228
+ <Spin spinning={loading}>
229
+ {treeData.length ? (
230
+ <Tree
231
+ className="w-full [&_.ant-tree-list-holder-inner]:!w-full [&_.ant-tree-node-content-wrapper]:!min-w-0 [&_.ant-tree-node-content-wrapper]:!flex-1 [&_.ant-tree-node-content-wrapper]:!overflow-hidden [&_.ant-tree-title]:!block [&_.ant-tree-title]:!min-w-0 [&_.ant-tree-title]:!w-full [&_.ant-tree-treenode]:!w-full [&_.ant-tree-treenode]:!min-w-0"
232
+ checkable={selectionMode === 'multiple'}
233
+ selectable={selectionMode === 'single'}
234
+ checkStrictly
235
+ treeData={treeData}
236
+ checkedKeys={selectionMode === 'multiple' ? selectedKeys : undefined}
237
+ selectedKeys={selectionMode === 'single' ? selectedKeys : undefined}
238
+ defaultExpandAll={Boolean(keyword.trim())}
239
+ onCheck={(keys) => {
240
+ const nextKeys = Array.isArray(keys) ? keys : keys.checked;
241
+ setSelectedKeys(nextKeys.filter((key) => selectableFileIds.has(String(key))));
242
+ }}
243
+ onSelect={(keys) => {
244
+ setSelectedKeys(keys.filter((key) => selectableFileIds.has(String(key))).slice(0, 1));
245
+ }}
246
+ />
247
+ ) : (
248
+ <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={projectId ? emptyDescription || '暂无可选择的文件' : '我的文件尚未启用'} />
249
+ )}
250
+ </Spin>
251
+ </div>
252
+
253
+ <div className="shrink-0 text-xs text-muted">
254
+ {resolvedHelperText}
255
+ </div>
256
+ </div>
257
+ </Modal>
258
+ );
259
+ };
260
+
261
+ export default MyFileSelectorModal;