@zjlab-fe/data-hub-ui 0.21.6 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/components/tip-tap/extensions/index.d.ts +1 -1
- package/es/assets/cardImg.png.js +3 -0
- package/es/assets/closeBtn.png.js +3 -0
- package/es/assets/icon-tag-header.png.js +3 -0
- package/es/assets/source.png.js +3 -0
- package/es/components/FileUploader/FileUploadModal.js +24 -0
- package/es/components/FileUploader/FileUploader.js +20 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploader.js +34 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderContextProvider.js +46 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderFileReceiver.css.js +36 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderFileReceiver.js +57 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderFileReceiverContainer.js +24 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderFileReceiverDesc.js +9 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderList.js +46 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderListItem.js +46 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderListItemFail.js +21 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderListItemPaused.js +20 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderListItemUploading.js +43 -0
- package/es/components/FileUploader/FileUploaderImpl/FileUploaderlListItemFinished.js +32 -0
- package/es/components/FileUploader/Locale/translations.js +52 -0
- package/es/components/FileUploader/Locale/useTranslate.js +16 -0
- package/es/components/FileUploader/UploadStoreProvider/UploadStoreProvider.js +31 -0
- package/es/components/FileUploader/UploadStoreProvider/uploadStoreReducer.js +62 -0
- package/es/components/FileUploader/UploadStoreProvider/useOnFileAllUploaded.js +31 -0
- package/es/components/FileUploader/UploadStoreProvider/useSaveUnfinishedUploads.js +42 -0
- package/es/components/FileUploader/UploadStoreProvider/useSetupMergingEvent.js +45 -0
- package/es/components/FileUploader/hooks/useCancelUpload.js +40 -0
- package/es/components/FileUploader/hooks/useCurrentFile.js +61 -0
- package/es/components/FileUploader/hooks/useCurrentFileOnUploading.js +52 -0
- package/es/components/FileUploader/hooks/useCurrentFileSetupAndBackUp.js +39 -0
- package/es/components/FileUploader/hooks/useFormatFiles.js +64 -0
- package/es/components/FileUploader/hooks/useFormatFilesFindDuplicates.js +75 -0
- package/es/components/FileUploader/hooks/useFormatFilesRemoveDuplicates.js +21 -0
- package/es/components/FileUploader/hooks/useHandleChunkCompletion.js +31 -0
- package/es/components/FileUploader/hooks/useOnMounted.js +24 -0
- package/es/components/FileUploader/hooks/useOnMountedResumeUnfinished.js +58 -0
- package/es/components/FileUploader/hooks/useRetryUpload.js +33 -0
- package/es/components/FileUploader/hooks/useUploadChunk.js +46 -0
- package/es/components/FileUploader/icons/CloseFilled.js +7 -0
- package/es/components/FileUploader/icons/UnkownFileIcon.js +7 -0
- package/es/components/FileUploader/icons/UploadIcon.js +7 -0
- package/es/components/FileUploader/index.css.js +532 -0
- package/es/components/FileUploader/index.js +23 -0
- package/es/components/FileUploader/types.js +13 -0
- package/es/components/FileUploader/utils/calcDisplaySize.js +20 -0
- package/es/components/FileUploader/utils/checkExpiryTime.js +15 -0
- package/es/components/FileUploader/utils/createFileToUpload.js +60 -0
- package/es/components/FileUploader/utils/fileDB/addFilesToIndexdb.js +14 -0
- package/es/components/FileUploader/utils/fileDB/deleteFileInStore.js +18 -0
- package/es/components/FileUploader/utils/fileDB/deleteObjectStore.js +25 -0
- package/es/components/FileUploader/utils/fileDB/index.js +77 -0
- package/es/components/FileUploader/utils/fileDB/openDBAndRetrieveData.js +90 -0
- package/es/components/FileUploader/utils/fileDB/retrieveExistingData.js +49 -0
- package/es/components/FileUploader/utils/modalConfig.css.js +20 -0
- package/es/components/FileUploader/utils/modalConfig.js +32 -0
- package/es/components/FileUploader/utils/retrieveAllFiles.js +70 -0
- package/es/components/FileUploader/utils/splitFileIntoChunk.js +15 -0
- package/es/components/SDK-modal/document-link.js +8 -0
- package/es/components/SDK-modal/index.js +25 -0
- package/es/components/SDK-modal/index.module.scss.js +49 -0
- package/es/components/SDK-modal/inner-modal.js +59 -0
- package/es/components/SDK-modal/locale/translations.js +17 -0
- package/es/components/apply-perm-modal/index.js +53 -0
- package/es/components/apply-perm-modal/index.module.scss.js +96 -0
- package/es/components/auth-tag/index.js +23 -0
- package/es/components/bench-card/index.js +20 -0
- package/es/components/bench-card/index.module.scss.js +48 -0
- package/es/components/confirm-again/index.js +15 -0
- package/es/components/confirm-again/index.scss.js +37 -0
- package/es/components/copy/index.js +41 -0
- package/es/components/copy/index.module.scss.js +15 -0
- package/es/components/corpus-card/index.js +88 -0
- package/es/components/corpus-card/index.module.scss.js +171 -0
- package/es/components/feature-card/classNames.js +72 -0
- package/es/components/feature-card/components.js +48 -0
- package/es/components/feature-card/context.js +13 -0
- package/es/components/feature-card/index.css.js +655 -0
- package/es/components/feature-card/index.js +48 -0
- package/es/components/file-preview/data-table/index.js +94 -0
- package/es/components/file-preview/data-table/index.module.scss.js +54 -0
- package/es/components/file-preview/excel-preview/index.js +132 -0
- package/es/components/file-preview/excel-preview/index.module.scss.js +11 -0
- package/es/components/file-preview/excel-preview/utils.js +46 -0
- package/es/components/file-preview/img-preview/index.js +18 -0
- package/es/components/file-preview/img-preview/index.module.scss.js +17 -0
- package/es/components/file-preview/index.js +124 -0
- package/es/components/file-preview/index.module.scss.js +32 -0
- package/es/components/file-preview/json-preview/index.js +166 -0
- package/es/components/file-preview/markdown-preview/index.js +53 -0
- package/es/components/file-preview/markdown-preview/index.module.scss.js +1055 -0
- package/es/components/file-preview/parquet-preview/index.js +54 -0
- package/es/components/file-preview/pdf-preview/index.js +215 -0
- package/es/components/file-preview/pdf-preview/index.module.scss.js +16 -0
- package/es/components/file-preview/table-preview/index.js +43 -0
- package/es/components/file-preview/table-preview/index.module.scss.js +11 -0
- package/es/components/file-preview/txt-preview/index.js +24 -0
- package/es/components/file-preview/util.js +94 -0
- package/es/components/file-uploader/components/circle-progress.css.js +39 -0
- package/es/components/file-uploader/components/circle-progress.js +12 -0
- package/es/components/file-uploader/components/hooks/use-drop-zone.js +74 -0
- package/es/components/file-uploader/components/hooks/use-file-validation.js +52 -0
- package/es/components/file-uploader/components/icons.js +19 -0
- package/es/components/file-uploader/components/theme.css.js +64 -0
- package/es/components/file-uploader/components/uploader-drop-zone.css.js +66 -0
- package/es/components/file-uploader/components/uploader-drop-zone.js +74 -0
- package/es/components/file-uploader/components/uploader-file-item.css.js +186 -0
- package/es/components/file-uploader/components/uploader-file-item.js +63 -0
- package/es/components/file-uploader/components/uploader-file-list.css.js +43 -0
- package/es/components/file-uploader/components/uploader-file-list.js +27 -0
- package/es/components/file-uploader/components/uploader.css.js +20 -0
- package/es/components/file-uploader/components/uploader.js +51 -0
- package/es/components/file-uploader/components/uploading-status.css.js +52 -0
- package/es/components/file-uploader/components/uploading-status.js +54 -0
- package/es/components/file-uploader/components/utils/directory-reader.js +65 -0
- package/es/components/file-uploader/constants.js +31 -0
- package/es/components/file-uploader/context/action-registry.js +69 -0
- package/es/components/file-uploader/context/file-state-store.js +83 -0
- package/es/components/file-uploader/context/handler-registry.js +119 -0
- package/es/components/file-uploader/context/upload-context.js +36 -0
- package/es/components/file-uploader/context/upload-provider.js +77 -0
- package/es/components/file-uploader/context/upload-reducer.js +21 -0
- package/es/components/file-uploader/context/use-upload-engine.js +440 -0
- package/es/components/file-uploader/engine/queue-callbacks.js +257 -0
- package/es/components/file-uploader/engine/queue-manager.js +195 -0
- package/es/components/file-uploader/engine/queue-uploader.js +25 -0
- package/es/components/file-uploader/hooks/use-upload.js +327 -0
- package/es/components/file-uploader/utils/chunk-handlers.js +66 -0
- package/es/components/file-uploader/utils/ensure-handlers.js +53 -0
- package/es/components/file-uploader/utils/error-handler.js +84 -0
- package/es/components/file-uploader/utils/upload-controllers.js +43 -0
- package/es/components/file-uploader/utils/upload-handler.js +68 -0
- package/es/components/file-uploader/utils/validate-handlers.js +16 -0
- package/es/components/floating-label-input/index.js +46 -0
- package/es/components/floating-label-input/index.module.scss.js +30 -0
- package/es/components/floating-layer/index.js +15 -0
- package/es/components/floating-layer/index.scss.js +31 -0
- package/es/components/header/index.js +10 -0
- package/es/components/header/index.module.scss.js +16 -0
- package/es/components/input-tag/index.js +65 -0
- package/es/components/menu/index.js +96 -0
- package/es/components/menu/index.module.scss.js +188 -0
- package/es/components/model-card/index.js +20 -0
- package/es/components/model-card/index.module.scss.js +116 -0
- package/es/components/notion-editor/code-block/index.js +18 -0
- package/es/components/notion-editor/code-block/languageDetector.js +134 -0
- package/es/components/notion-editor/code-block/languages.js +42 -0
- package/es/components/notion-editor/index.js +258 -0
- package/es/components/notion-editor/index.scss.js +421 -0
- package/es/components/notion-editor/parseMarkdown.js +184 -0
- package/es/components/operator-chain/components/AddButton.js +20 -0
- package/es/components/operator-chain/components/IOCard.js +8 -0
- package/es/components/operator-chain/components/SingleCard.js +12 -0
- package/es/components/operator-chain/components/VirtualDropdown.js +56 -0
- package/es/components/operator-chain/index.js +83 -0
- package/es/components/operator-chain/index.module.scss.js +232 -0
- package/es/components/permission-editor/index.js +9 -0
- package/es/components/permission-editor/permissionEditModal/index.js +37 -0
- package/es/components/permission-editor/permissionEditor/index.js +253 -0
- package/es/components/permission-editor/permissionEditor/index.module.scss.js +22 -0
- package/es/components/permission-editor/permissionViewPopover/index.js +9 -0
- package/es/components/popover-select/index.js +42 -0
- package/es/components/popover-select/index.module.scss.js +22 -0
- package/es/components/radio-card/index.js +24 -0
- package/es/components/radio-card/index.scss.js +58 -0
- package/es/components/section-heading/classNames.js +22 -0
- package/es/components/section-heading/components.js +27 -0
- package/es/components/section-heading/context.js +13 -0
- package/es/components/section-heading/index.css.js +648 -0
- package/es/components/section-heading/index.js +37 -0
- package/es/components/status-tag/index.js +31 -0
- package/es/components/tag-group-filter/dataSizeFilter.js +100 -0
- package/es/components/tag-group-filter/index.js +53 -0
- package/es/components/tag-group-filter/index.module.scss.js +134 -0
- package/es/components/tag-group-filter/tagFilter.js +19 -0
- package/es/components/tag-view/index.js +138 -0
- package/es/components/tag-view/index.module.scss.js +56 -0
- package/es/components/tag-view/useSize.js +41 -0
- package/es/components/tip-tap/bubble-menu.js +47 -0
- package/es/components/tip-tap/editor.js +34 -0
- package/es/components/tip-tap/extensions/index.js +133 -0
- package/es/components/tip-tap/extensions/markdown-paste.js +148 -0
- package/es/components/tip-tap/icons/index.js +29 -0
- package/es/components/tip-tap/reader.js +15 -0
- package/es/components/tip-tap/styles/editor.css.js +863 -0
- package/es/components/tip-tap/toolbar/components/color-picker.js +76 -0
- package/es/components/tip-tap/toolbar/components/heading-dropdown.js +44 -0
- package/es/components/tip-tap/toolbar/components/image-button.js +26 -0
- package/es/components/tip-tap/toolbar/components/link-button.js +107 -0
- package/es/components/tip-tap/toolbar/components/table-button.js +37 -0
- package/es/components/tip-tap/toolbar/components/toolbar-button.js +7 -0
- package/es/components/tip-tap/toolbar/index.js +39 -0
- package/es/components/uploadDrawer/UploadStoreProvider.js +61 -0
- package/es/components/uploadDrawer/fileUploadDrawer.js +66 -0
- package/es/components/uploadDrawer/fileUploadDrawerFileDragger.js +71 -0
- package/es/components/uploadDrawer/fileUploadDrawerList.js +54 -0
- package/es/components/uploadDrawer/fileUploadDrawerListProgressButton.js +54 -0
- package/es/components/uploadDrawer/hooks/useCancelUpload.js +37 -0
- package/es/components/uploadDrawer/hooks/useFilterFiles.js +107 -0
- package/es/components/uploadDrawer/hooks/useFinishUpload.js +33 -0
- package/es/components/uploadDrawer/hooks/useFormatFiles.js +54 -0
- package/es/components/uploadDrawer/hooks/useProgressButtonRef.js +22 -0
- package/es/components/uploadDrawer/hooks/useRemoveFilesInUploadQueue.js +21 -0
- package/es/components/uploadDrawer/hooks/useResumeUnfinishedUploads.js +52 -0
- package/es/components/uploadDrawer/hooks/useRetryUpload.js +35 -0
- package/es/components/uploadDrawer/hooks/useSaveUnfinishedUploads.js +42 -0
- package/es/components/uploadDrawer/hooks/useSetupUpload.js +80 -0
- package/es/components/uploadDrawer/hooks/useUploadFileToOSS.js +23 -0
- package/es/components/uploadDrawer/index.css.js +511 -0
- package/es/components/uploadDrawer/index.js +43 -0
- package/es/components/uploadDrawer/utils/calcDisplaySize.js +19 -0
- package/es/components/uploadDrawer/utils/constant.js +16 -0
- package/es/components/uploadDrawer/utils/createFileToUpload.js +51 -0
- package/es/components/uploadDrawer/utils/fileDB/deleteFileInStore.js +28 -0
- package/es/components/uploadDrawer/utils/fileDB/deleteObjectStore.js +28 -0
- package/es/components/uploadDrawer/utils/fileDB/handleAddFilesToDB.js +37 -0
- package/es/components/uploadDrawer/utils/fileDB/index.js +9 -0
- package/es/components/uploadDrawer/utils/fileDB/openDB.js +111 -0
- package/es/components/uploadDrawer/utils/retrieveAllFiles.js +110 -0
- package/es/components/uploadDrawer/utils/shouldCreateNewFile.js +13 -0
- package/es/components/uploadDrawer/utils/splitFileIntoChunk.js +15 -0
- package/es/index.js +42 -1
- package/es/lib/utils.js +8 -0
- package/lib/index.js +1 -1
- package/package.json +164 -154
- package/rollup.es.config.mjs +177 -0
- package/tsconfig.rollup.json +7 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useEditorState } from '@tiptap/react';
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { IconBold, IconHighlight } from '../../icons/index.js';
|
|
5
|
+
|
|
6
|
+
function ColorPicker({ editor, type, tooltip }) {
|
|
7
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
8
|
+
const dropdownRef = useRef(null);
|
|
9
|
+
const colors = [
|
|
10
|
+
'#000000',
|
|
11
|
+
'#374151',
|
|
12
|
+
'#6b7280',
|
|
13
|
+
'#9ca3af',
|
|
14
|
+
'#ef4444',
|
|
15
|
+
'#f97316',
|
|
16
|
+
'#eab308',
|
|
17
|
+
'#22c55e',
|
|
18
|
+
'#14b8a6',
|
|
19
|
+
'#3b82f6',
|
|
20
|
+
'#6366f1',
|
|
21
|
+
'#a855f7',
|
|
22
|
+
'#ec4899',
|
|
23
|
+
'#f43f5e',
|
|
24
|
+
'#84cc16',
|
|
25
|
+
'#06b6d4',
|
|
26
|
+
];
|
|
27
|
+
// 使用 useEditorState 来同步编辑器状态
|
|
28
|
+
const editorState = useEditorState({
|
|
29
|
+
editor,
|
|
30
|
+
selector: (ctx) => {
|
|
31
|
+
if (type === 'textColor') {
|
|
32
|
+
return {
|
|
33
|
+
isActive: !!ctx.editor.getAttributes('textStyle').color,
|
|
34
|
+
color: ctx.editor.getAttributes('textStyle').color,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
return {
|
|
39
|
+
isActive: ctx.editor.isActive('highlight'),
|
|
40
|
+
color: ctx.editor.getAttributes('highlight').color,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
function handleClickOutside(e) {
|
|
47
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
48
|
+
setIsOpen(false);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
52
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
53
|
+
}, []);
|
|
54
|
+
function handleColorSelect(color) {
|
|
55
|
+
if (type === 'textColor') {
|
|
56
|
+
if (color) {
|
|
57
|
+
editor.chain().focus().setColor(color).run();
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
editor.chain().focus().unsetColor().run();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
if (color) {
|
|
65
|
+
editor.chain().focus().toggleHighlight({ color }).run();
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
editor.chain().focus().unsetHighlight().run();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
setIsOpen(false);
|
|
72
|
+
}
|
|
73
|
+
return (jsxs("div", { className: `tip-tap-dropdown ${isOpen ? 'tip-tap-dropdown--open' : ''}`, ref: dropdownRef, children: [jsxs("button", { className: `tip-tap-btn ${(editorState === null || editorState === void 0 ? void 0 : editorState.isActive) ? 'tip-tap-btn--active' : ''}`, onClick: () => setIsOpen(!isOpen), type: "button", style: type === 'textColor' && (editorState === null || editorState === void 0 ? void 0 : editorState.color) ? { color: editorState.color } : {}, children: [type === 'textColor' ? (jsx(IconBold, { style: { borderBottom: `3px solid ${(editorState === null || editorState === void 0 ? void 0 : editorState.color) || '#000'}` } })) : (jsx(IconHighlight, {})), jsx("span", { className: "tip-tap-tooltip", children: tooltip })] }), jsx("div", { className: "tip-tap-dropdown__menu", children: jsxs("div", { className: "tip-tap-color-picker", children: [jsx("button", { className: "tip-tap-color-picker__swatch tip-tap-color-picker__swatch--reset", onClick: () => handleColorSelect(null), title: "\u79FB\u9664\u989C\u8272", type: "button" }), colors.map((color) => (jsx("button", { className: "tip-tap-color-picker__swatch", style: { background: color }, onClick: () => handleColorSelect(color), type: "button" }, color)))] }) })] }));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { ColorPicker };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { useEditorState } from '@tiptap/react';
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { IconParagraph, IconH1, IconH2, IconH3, IconChevronDown } from '../../icons/index.js';
|
|
5
|
+
|
|
6
|
+
function HeadingDropdown({ editor }) {
|
|
7
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
8
|
+
const dropdownRef = useRef(null);
|
|
9
|
+
const headings = [
|
|
10
|
+
{ level: 0, label: '正文', icon: jsx(IconParagraph, {}) },
|
|
11
|
+
{ level: 1, label: '标题 1', icon: jsx(IconH1, {}) },
|
|
12
|
+
{ level: 2, label: '标题 2', icon: jsx(IconH2, {}) },
|
|
13
|
+
{ level: 3, label: '标题 3', icon: jsx(IconH3, {}) },
|
|
14
|
+
];
|
|
15
|
+
// 使用 useEditorState 来同步编辑器状态
|
|
16
|
+
const editorState = useEditorState({
|
|
17
|
+
editor,
|
|
18
|
+
selector: (ctx) => {
|
|
19
|
+
const currentLevel = headings.find((h) => h.level === 0 ? ctx.editor.isActive('paragraph') : ctx.editor.isActive('heading', { level: h.level }));
|
|
20
|
+
return currentLevel;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
function handleClickOutside(e) {
|
|
25
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
26
|
+
setIsOpen(false);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
30
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
31
|
+
}, []);
|
|
32
|
+
function handleSelect(level) {
|
|
33
|
+
if (level === 0) {
|
|
34
|
+
editor.chain().focus().setParagraph().run();
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
editor.chain().focus().toggleHeading({ level }).run();
|
|
38
|
+
}
|
|
39
|
+
setIsOpen(false);
|
|
40
|
+
}
|
|
41
|
+
return (jsxs("div", { className: `tip-tap-dropdown ${isOpen ? 'tip-tap-dropdown--open' : ''}`, ref: dropdownRef, children: [jsxs("button", { className: `tip-tap-dropdown__trigger ${editorState && editorState.level !== 0 ? 'tip-tap-dropdown__trigger--active' : ''}`, onClick: () => setIsOpen(!isOpen), type: "button", children: [(editorState === null || editorState === void 0 ? void 0 : editorState.icon) || jsx(IconParagraph, {}), jsx("span", { children: (editorState === null || editorState === void 0 ? void 0 : editorState.label) || '正文' }), jsx(IconChevronDown, { className: "tip-tap-dropdown__arrow" })] }), jsx("div", { className: "tip-tap-dropdown__menu", children: headings.map(({ level, label, icon }) => (jsxs("button", { className: `tip-tap-dropdown__item ${(editorState === null || editorState === void 0 ? void 0 : editorState.level) === level ? 'tip-tap-dropdown__item--active' : ''}`, onClick: () => handleSelect(level), type: "button", children: [jsx("span", { className: "tip-tap-dropdown__item-icon", children: icon }), label] }, level))) })] }));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { HeadingDropdown };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { IconImage } from '../../icons/index.js';
|
|
4
|
+
|
|
5
|
+
function ImageButton({ editor }) {
|
|
6
|
+
const fileInputRef = useRef(null);
|
|
7
|
+
function handleFileSelect(e) {
|
|
8
|
+
var _a;
|
|
9
|
+
const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
10
|
+
if (file) {
|
|
11
|
+
const reader = new FileReader();
|
|
12
|
+
reader.onload = (event) => {
|
|
13
|
+
var _a;
|
|
14
|
+
const result = (_a = event.target) === null || _a === void 0 ? void 0 : _a.result;
|
|
15
|
+
if (typeof result === 'string') {
|
|
16
|
+
editor.chain().focus().setImage({ src: result }).run();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
reader.readAsDataURL(file);
|
|
20
|
+
}
|
|
21
|
+
e.target.value = '';
|
|
22
|
+
}
|
|
23
|
+
return (jsxs(Fragment, { children: [jsxs("button", { className: "tip-tap-btn", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, type: "button", children: [jsx(IconImage, {}), jsx("span", { className: "tip-tap-tooltip", children: "\u63D2\u5165\u56FE\u7247" })] }), jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: handleFileSelect })] }));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { ImageButton };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useEditorState } from '@tiptap/react';
|
|
3
|
+
import { getMarkRange } from '@tiptap/core';
|
|
4
|
+
import { useState, useRef, useEffect } from 'react';
|
|
5
|
+
import { IconLink, IconCheck, IconTrash } from '../../icons/index.js';
|
|
6
|
+
|
|
7
|
+
function LinkButton({ editor }) {
|
|
8
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
9
|
+
const [label, setLabel] = useState('');
|
|
10
|
+
const [url, setUrl] = useState('');
|
|
11
|
+
const dropdownRef = useRef(null);
|
|
12
|
+
const labelInputRef = useRef(null);
|
|
13
|
+
// 使用 useEditorState 来同步编辑器状态
|
|
14
|
+
const editorState = useEditorState({
|
|
15
|
+
editor,
|
|
16
|
+
selector: (ctx) => ({
|
|
17
|
+
isActive: ctx.editor.isActive('link'),
|
|
18
|
+
href: ctx.editor.getAttributes('link').href || '',
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
function handleClickOutside(e) {
|
|
23
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
24
|
+
setIsOpen(false);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
28
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
29
|
+
}, []);
|
|
30
|
+
function handleToggleOpen() {
|
|
31
|
+
if (!isOpen) {
|
|
32
|
+
// 打开时:获取选中的文本作为标签,获取链接 URL
|
|
33
|
+
let selectedText = '';
|
|
34
|
+
const { from, to } = editor.state.selection;
|
|
35
|
+
if (editorState === null || editorState === void 0 ? void 0 : editorState.isActive) {
|
|
36
|
+
// 如果当前在链接上,获取整个链接的文本
|
|
37
|
+
const linkType = editor.schema.marks.link;
|
|
38
|
+
const $pos = editor.state.doc.resolve(from);
|
|
39
|
+
const range = getMarkRange($pos, linkType);
|
|
40
|
+
if (range) {
|
|
41
|
+
selectedText = editor.state.doc.textBetween(range.from, range.to, '');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// 普通选择文本
|
|
46
|
+
selectedText = editor.state.doc.textBetween(from, to, '');
|
|
47
|
+
}
|
|
48
|
+
setLabel(selectedText || '');
|
|
49
|
+
setUrl((editorState === null || editorState === void 0 ? void 0 : editorState.href) || '');
|
|
50
|
+
setIsOpen(true);
|
|
51
|
+
// 使用 queueMicrotask 代替 setTimeout,更可靠且不需要延迟
|
|
52
|
+
queueMicrotask(() => {
|
|
53
|
+
var _a;
|
|
54
|
+
(_a = labelInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
setIsOpen(false);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function normalizeUrl(inputUrl) {
|
|
62
|
+
const trimmed = inputUrl.trim();
|
|
63
|
+
if (!trimmed)
|
|
64
|
+
return '';
|
|
65
|
+
// 如果已经有协议前缀,直接返回
|
|
66
|
+
if (/^https?:\/\//i.test(trimmed) || /^mailto:/i.test(trimmed) || /^tel:/i.test(trimmed)) {
|
|
67
|
+
return trimmed;
|
|
68
|
+
}
|
|
69
|
+
// 否则添加 https:// 前缀
|
|
70
|
+
return `https://${trimmed}`;
|
|
71
|
+
}
|
|
72
|
+
function handleSubmit() {
|
|
73
|
+
const normalizedUrl = normalizeUrl(url);
|
|
74
|
+
if (normalizedUrl && label) {
|
|
75
|
+
// 插入带标签的链接
|
|
76
|
+
editor
|
|
77
|
+
.chain()
|
|
78
|
+
.focus()
|
|
79
|
+
.insertContent({
|
|
80
|
+
type: 'text',
|
|
81
|
+
text: label,
|
|
82
|
+
marks: [{ type: 'link', attrs: { href: normalizedUrl } }],
|
|
83
|
+
})
|
|
84
|
+
.run();
|
|
85
|
+
}
|
|
86
|
+
else if (normalizedUrl) {
|
|
87
|
+
// 只有 URL,使用现有逻辑
|
|
88
|
+
editor.chain().focus().extendMarkRange('link').setLink({ href: normalizedUrl }).run();
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// 没有 URL,移除链接
|
|
92
|
+
editor.chain().focus().unsetLink().run();
|
|
93
|
+
}
|
|
94
|
+
setIsOpen(false);
|
|
95
|
+
setLabel('');
|
|
96
|
+
setUrl('');
|
|
97
|
+
}
|
|
98
|
+
function handleRemove() {
|
|
99
|
+
editor.chain().focus().unsetLink().run();
|
|
100
|
+
setIsOpen(false);
|
|
101
|
+
setLabel('');
|
|
102
|
+
setUrl('');
|
|
103
|
+
}
|
|
104
|
+
return (jsxs("div", { className: `tip-tap-dropdown ${isOpen ? 'tip-tap-dropdown--open' : ''}`, ref: dropdownRef, children: [jsxs("button", { className: `tip-tap-btn ${(editorState === null || editorState === void 0 ? void 0 : editorState.isActive) ? 'tip-tap-btn--active' : ''}`, onClick: handleToggleOpen, type: "button", children: [jsx(IconLink, {}), jsx("span", { className: "tip-tap-tooltip", children: "\u94FE\u63A5" })] }), jsx("div", { className: "tip-tap-dropdown__menu", style: { width: 'auto', padding: 0 }, children: jsxs("div", { className: "tip-tap-link-popup", style: { flexDirection: 'column', alignItems: 'stretch' }, children: [jsx("input", { ref: labelInputRef, type: "text", className: "tip-tap-link-popup__input", placeholder: "\u8F93\u5165\u94FE\u63A5\u6587\u672C...", value: label, onChange: (e) => setLabel(e.target.value), onKeyDown: (e) => e.key === 'Enter' && handleSubmit(), style: { marginBottom: '8px' } }), jsx("input", { type: "text", className: "tip-tap-link-popup__input", placeholder: "\u8F93\u5165\u94FE\u63A5\u5730\u5740...", value: url, onChange: (e) => setUrl(e.target.value), onKeyDown: (e) => e.key === 'Enter' && handleSubmit() }), jsxs("div", { style: { display: 'flex', gap: '4px', marginTop: '8px' }, children: [jsx("button", { className: "tip-tap-link-popup__btn", onClick: handleSubmit, type: "button", children: jsx(IconCheck, {}) }), (editorState === null || editorState === void 0 ? void 0 : editorState.isActive) && (jsx("button", { className: "tip-tap-link-popup__btn tip-tap-link-popup__btn--danger", onClick: handleRemove, type: "button", children: jsx(IconTrash, {}) }))] })] }) })] }));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export { LinkButton };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useEditorState } from '@tiptap/react';
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { IconTable } from '../../icons/index.js';
|
|
5
|
+
|
|
6
|
+
function TableButton({ editor }) {
|
|
7
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
8
|
+
const [hoveredCell, setHoveredCell] = useState({ row: 0, col: 0 });
|
|
9
|
+
const dropdownRef = useRef(null);
|
|
10
|
+
// 使用 useEditorState 来同步编辑器状态
|
|
11
|
+
const editorState = useEditorState({
|
|
12
|
+
editor,
|
|
13
|
+
selector: (ctx) => ({
|
|
14
|
+
isActive: ctx.editor.isActive('table'),
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
function handleClickOutside(e) {
|
|
19
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
20
|
+
setIsOpen(false);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
24
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
25
|
+
}, []);
|
|
26
|
+
function handleInsertTable(rows, cols) {
|
|
27
|
+
editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();
|
|
28
|
+
setIsOpen(false);
|
|
29
|
+
}
|
|
30
|
+
return (jsxs("div", { className: `tip-tap-dropdown ${isOpen ? 'tip-tap-dropdown--open' : ''}`, ref: dropdownRef, children: [jsxs("button", { className: `tip-tap-btn ${(editorState === null || editorState === void 0 ? void 0 : editorState.isActive) ? 'tip-tap-btn--active' : ''}`, onClick: () => setIsOpen(!isOpen), type: "button", children: [jsx(IconTable, {}), jsx("span", { className: "tip-tap-tooltip", children: "\u63D2\u5165\u8868\u683C" })] }), jsx("div", { className: "tip-tap-dropdown__menu", style: { padding: 0 }, children: jsxs("div", { className: "tip-tap-table-menu", children: [jsx("div", { className: "tip-tap-table-menu__label", children: "\u9009\u62E9\u8868\u683C\u5927\u5C0F" }), jsx("div", { className: "tip-tap-table-menu__grid", children: Array.from({ length: 36 }, (_, i) => {
|
|
31
|
+
const row = Math.floor(i / 6) + 1;
|
|
32
|
+
const col = (i % 6) + 1;
|
|
33
|
+
return (jsx("button", { className: `tip-tap-table-menu__cell ${row <= hoveredCell.row && col <= hoveredCell.col ? 'tip-tap-table-menu__cell--active' : ''}`, onMouseEnter: () => setHoveredCell({ row, col }), onClick: () => handleInsertTable(row, col), type: "button" }, i));
|
|
34
|
+
}) }), jsx("div", { className: "tip-tap-table-menu__size", children: hoveredCell.row > 0 ? `${hoveredCell.row} × ${hoveredCell.col}` : '选择大小' })] }) })] }));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { TableButton };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
function ToolbarButton({ icon, tooltip, onClick, active = false, disabled = false, }) {
|
|
4
|
+
return (jsxs("button", { className: `tip-tap-btn ${active ? 'tip-tap-btn--active' : ''}`, onClick: onClick, disabled: disabled, type: "button", children: [icon, jsx("span", { className: "tip-tap-tooltip", children: tooltip })] }));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export { ToolbarButton };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useEditorState } from '@tiptap/react';
|
|
3
|
+
import { IconUndo, IconRedo, IconBold, IconItalic, IconUnderline, IconStrikethrough, IconAlignLeft, IconAlignCenter, IconAlignRight, IconListBullet, IconListNumbered, IconQuote, IconCode, IconMinus } from '../icons/index.js';
|
|
4
|
+
import { ToolbarButton } from './components/toolbar-button.js';
|
|
5
|
+
import { HeadingDropdown } from './components/heading-dropdown.js';
|
|
6
|
+
import { ColorPicker } from './components/color-picker.js';
|
|
7
|
+
import { LinkButton } from './components/link-button.js';
|
|
8
|
+
import { ImageButton } from './components/image-button.js';
|
|
9
|
+
import { TableButton } from './components/table-button.js';
|
|
10
|
+
|
|
11
|
+
function Toolbar({ editor, className = '' }) {
|
|
12
|
+
// 使用 useEditorState 来同步编辑器状态
|
|
13
|
+
const editorState = useEditorState({
|
|
14
|
+
editor,
|
|
15
|
+
selector: (ctx) => ({
|
|
16
|
+
// 文本格式
|
|
17
|
+
isBold: ctx.editor.isActive('bold'),
|
|
18
|
+
isItalic: ctx.editor.isActive('italic'),
|
|
19
|
+
isUnderline: ctx.editor.isActive('underline'),
|
|
20
|
+
isStrike: ctx.editor.isActive('strike'),
|
|
21
|
+
// 对齐方式
|
|
22
|
+
isAlignLeft: ctx.editor.isActive({ textAlign: 'left' }),
|
|
23
|
+
isAlignCenter: ctx.editor.isActive({ textAlign: 'center' }),
|
|
24
|
+
isAlignRight: ctx.editor.isActive({ textAlign: 'right' }),
|
|
25
|
+
// 列表
|
|
26
|
+
isBulletList: ctx.editor.isActive('bulletList'),
|
|
27
|
+
isOrderedList: ctx.editor.isActive('orderedList'),
|
|
28
|
+
// 块元素
|
|
29
|
+
isBlockquote: ctx.editor.isActive('blockquote'),
|
|
30
|
+
isCodeBlock: ctx.editor.isActive('codeBlock'),
|
|
31
|
+
// 撤销/重做能力
|
|
32
|
+
canUndo: ctx.editor.can().undo(),
|
|
33
|
+
canRedo: ctx.editor.can().redo(),
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
return (jsxs("div", { className: `tip-tap-toolbar ${className}`, children: [jsxs("div", { className: "tip-tap-toolbar__group", children: [jsx(ToolbarButton, { icon: jsx(IconUndo, {}), tooltip: "\u64A4\u9500", onClick: () => editor.chain().focus().undo().run(), disabled: !(editorState === null || editorState === void 0 ? void 0 : editorState.canUndo) }), jsx(ToolbarButton, { icon: jsx(IconRedo, {}), tooltip: "\u91CD\u505A", onClick: () => editor.chain().focus().redo().run(), disabled: !(editorState === null || editorState === void 0 ? void 0 : editorState.canRedo) })] }), jsx("div", { className: "tip-tap-toolbar__divider" }), jsxs("div", { className: "tip-tap-toolbar__group", children: [jsx(ToolbarButton, { icon: jsx(IconBold, {}), tooltip: "\u7C97\u4F53", onClick: () => editor.chain().focus().toggleBold().run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isBold }), jsx(ToolbarButton, { icon: jsx(IconItalic, {}), tooltip: "\u659C\u4F53", onClick: () => editor.chain().focus().toggleItalic().run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isItalic }), jsx(ToolbarButton, { icon: jsx(IconUnderline, {}), tooltip: "\u4E0B\u5212\u7EBF", onClick: () => editor.chain().focus().toggleUnderline().run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isUnderline }), jsx(ToolbarButton, { icon: jsx(IconStrikethrough, {}), tooltip: "\u5220\u9664\u7EBF", onClick: () => editor.chain().focus().toggleStrike().run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isStrike })] }), jsx("div", { className: "tip-tap-toolbar__divider" }), jsx(HeadingDropdown, { editor: editor }), jsx("div", { className: "tip-tap-toolbar__divider" }), jsxs("div", { className: "tip-tap-toolbar__group", children: [jsx(ColorPicker, { editor: editor, type: "textColor", tooltip: "\u6587\u5B57\u989C\u8272" }), jsx(ColorPicker, { editor: editor, type: "highlight", tooltip: "\u80CC\u666F\u9AD8\u4EAE" })] }), jsx("div", { className: "tip-tap-toolbar__divider" }), jsxs("div", { className: "tip-tap-toolbar__group", children: [jsx(ToolbarButton, { icon: jsx(IconAlignLeft, {}), tooltip: "\u5DE6\u5BF9\u9F50", onClick: () => editor.chain().focus().setTextAlign('left').run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isAlignLeft }), jsx(ToolbarButton, { icon: jsx(IconAlignCenter, {}), tooltip: "\u5C45\u4E2D\u5BF9\u9F50", onClick: () => editor.chain().focus().setTextAlign('center').run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isAlignCenter }), jsx(ToolbarButton, { icon: jsx(IconAlignRight, {}), tooltip: "\u53F3\u5BF9\u9F50", onClick: () => editor.chain().focus().setTextAlign('right').run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isAlignRight })] }), jsx("div", { className: "tip-tap-toolbar__divider" }), jsxs("div", { className: "tip-tap-toolbar__group", children: [jsx(ToolbarButton, { icon: jsx(IconListBullet, {}), tooltip: "\u65E0\u5E8F\u5217\u8868", onClick: () => editor.chain().focus().toggleBulletList().run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isBulletList }), jsx(ToolbarButton, { icon: jsx(IconListNumbered, {}), tooltip: "\u6709\u5E8F\u5217\u8868", onClick: () => editor.chain().focus().toggleOrderedList().run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isOrderedList })] }), jsx("div", { className: "tip-tap-toolbar__divider" }), jsxs("div", { className: "tip-tap-toolbar__group", children: [jsx(ToolbarButton, { icon: jsx(IconQuote, {}), tooltip: "\u5F15\u7528", onClick: () => editor.chain().focus().toggleBlockquote().run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isBlockquote }), jsx(ToolbarButton, { icon: jsx(IconCode, {}), tooltip: "\u4EE3\u7801\u5757", onClick: () => editor.chain().focus().toggleCodeBlock().run(), active: editorState === null || editorState === void 0 ? void 0 : editorState.isCodeBlock }), jsx(ToolbarButton, { icon: jsx(IconMinus, {}), tooltip: "\u5206\u5272\u7EBF", onClick: () => editor.chain().focus().setHorizontalRule().run() })] }), jsx("div", { className: "tip-tap-toolbar__divider" }), jsxs("div", { className: "tip-tap-toolbar__group", children: [jsx(LinkButton, { editor: editor }), jsx(ImageButton, { editor: editor }), jsx(TableButton, { editor: editor })] })] }));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { Toolbar as default };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { createContext, useReducer } from 'react';
|
|
3
|
+
import { FILE_UPLOAD_STATUS } from './utils/constant.js';
|
|
4
|
+
import 'tslib';
|
|
5
|
+
import { useSaveUnfinishedUploads } from './hooks/useSaveUnfinishedUploads.js';
|
|
6
|
+
|
|
7
|
+
const TEMP_STORE = {};
|
|
8
|
+
const UploadStoreContext = createContext(TEMP_STORE);
|
|
9
|
+
const UploadStoreDispatchContext = createContext(() => null);
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated This component is deprecated and will be removed in future releases.
|
|
12
|
+
*/
|
|
13
|
+
function UploadDrawerUploadStoreProvider({ children }) {
|
|
14
|
+
const [uploadStore, dispatch] = useReducer(uploadReducer, {});
|
|
15
|
+
useSaveUnfinishedUploads(uploadStore);
|
|
16
|
+
return (jsx(UploadStoreContext.Provider, { value: uploadStore, children: jsx(UploadStoreDispatchContext.Provider, { value: dispatch, children: children }) }));
|
|
17
|
+
}
|
|
18
|
+
function uploadReducer(uploads, action) {
|
|
19
|
+
switch (action.type) {
|
|
20
|
+
case 'add': {
|
|
21
|
+
return Object.assign(Object.assign({}, uploads), { [action.identifier]: [] });
|
|
22
|
+
}
|
|
23
|
+
case 'remove_file': {
|
|
24
|
+
const fileList = uploads[action.identifier];
|
|
25
|
+
const fileIndex = fileList.findIndex((p) => p.fileName === action.file.fileName);
|
|
26
|
+
return Object.assign(Object.assign({}, uploads), { [action.identifier]: [...fileList.slice(0, fileIndex), ...fileList.slice(fileIndex + 1)] });
|
|
27
|
+
}
|
|
28
|
+
case 'edit_file': {
|
|
29
|
+
const fileList = uploads[action.identifier];
|
|
30
|
+
const fileIndex = fileList.findIndex((p) => p.fileName === action.file.fileName);
|
|
31
|
+
return Object.assign(Object.assign({}, uploads), { [action.identifier]: [...fileList.slice(0, fileIndex), action.file, ...fileList.slice(fileIndex + 1)] });
|
|
32
|
+
}
|
|
33
|
+
case 'add_file': {
|
|
34
|
+
const fileList = uploads[action.identifier];
|
|
35
|
+
const fileIndex = fileList.findIndex((p) => p.fileName === action.file.fileName);
|
|
36
|
+
if (fileIndex === -1) {
|
|
37
|
+
return Object.assign(Object.assign({}, uploads), { [action.identifier]: [...fileList, action.file] });
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
return Object.assign(Object.assign({}, uploads), { [action.identifier]: [
|
|
41
|
+
...fileList.slice(0, fileIndex),
|
|
42
|
+
action.file,
|
|
43
|
+
...fileList.slice(fileIndex + 1),
|
|
44
|
+
] });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
case 'add_files': {
|
|
48
|
+
const fileList = uploads[action.identifier];
|
|
49
|
+
return Object.assign(Object.assign({}, uploads), { [action.identifier]: [...fileList, ...action.files] });
|
|
50
|
+
}
|
|
51
|
+
case 'clear_finished': {
|
|
52
|
+
const fileList = uploads[action.identifier];
|
|
53
|
+
return Object.assign(Object.assign({}, uploads), { [action.identifier]: fileList.filter((f) => f.fileStatus !== FILE_UPLOAD_STATUS.FINISHED) });
|
|
54
|
+
}
|
|
55
|
+
default: {
|
|
56
|
+
throw Error('Unknown action');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { TEMP_STORE, UploadStoreContext, UploadStoreDispatchContext, UploadDrawerUploadStoreProvider as default };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { __awaiter } from 'tslib';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { Drawer, Empty } from 'antd';
|
|
4
|
+
import { useContext, useState, useRef, useEffect } from 'react';
|
|
5
|
+
import { LoadingOutlined } from '@ant-design/icons';
|
|
6
|
+
import { UploadStoreContext, UploadStoreDispatchContext } from './UploadStoreProvider.js';
|
|
7
|
+
import FileUploadDrawerFileDragger from './fileUploadDrawerFileDragger.js';
|
|
8
|
+
import FileUploading from './fileUploadDrawerList.js';
|
|
9
|
+
import { FILE_UPLOAD_STATUS } from './utils/constant.js';
|
|
10
|
+
import { useFormatFiles } from './hooks/useFormatFiles.js';
|
|
11
|
+
import { useUploadFileToOSS } from './hooks/useUploadFileToOSS.js';
|
|
12
|
+
import { useResumeUnfinishedUploads } from './hooks/useResumeUnfinishedUploads.js';
|
|
13
|
+
import { FileUploadDrawerContext } from './index.js';
|
|
14
|
+
|
|
15
|
+
function FileUploadDrawer({ open, identifier, accept, dragAreaDescription, footer, title = '数据上传', showFinished = false, directory = false, onClose, }) {
|
|
16
|
+
var _a, _b;
|
|
17
|
+
const uploadStore = useContext(UploadStoreContext);
|
|
18
|
+
const setUploadsStore = useContext(UploadStoreDispatchContext);
|
|
19
|
+
const { onSuccess } = useContext(FileUploadDrawerContext);
|
|
20
|
+
const formatFiles = useFormatFiles();
|
|
21
|
+
const uploadFileToOSS = useUploadFileToOSS();
|
|
22
|
+
const resumeUnfinishedUploads = useResumeUnfinishedUploads(identifier);
|
|
23
|
+
const [raws, setRaws] = useState([]);
|
|
24
|
+
const currentIdentifier = useRef('');
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (currentIdentifier.current === identifier) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// 添加对uploadStoreContext的检测
|
|
30
|
+
if (!uploadStore || !setUploadsStore) {
|
|
31
|
+
console.warn('FileUploadDrawer 必须在 UploadStoreProvider 内使用');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
currentIdentifier.current = identifier;
|
|
35
|
+
if (!uploadStore[identifier]) {
|
|
36
|
+
setUploadsStore({ type: 'add', identifier });
|
|
37
|
+
}
|
|
38
|
+
resumeUnfinishedUploads();
|
|
39
|
+
}, [identifier, resumeUnfinishedUploads, uploadStore, setUploadsStore]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const fileList = uploadStore[identifier];
|
|
42
|
+
if (!fileList)
|
|
43
|
+
return;
|
|
44
|
+
const shouldUpload = !fileList.some((file) => file.fileStatus === FILE_UPLOAD_STATUS.UPLOADING);
|
|
45
|
+
const fileToUpload = fileList.find((file) => file.fileStatus === FILE_UPLOAD_STATUS.INIT);
|
|
46
|
+
if (shouldUpload && fileToUpload) {
|
|
47
|
+
uploadFileToOSS(fileToUpload, onSuccess);
|
|
48
|
+
}
|
|
49
|
+
}, [identifier, onSuccess, uploadFileToOSS, uploadStore]);
|
|
50
|
+
return (jsxs(Drawer, { className: "mylib-upload-drawer", open: open, title: title, width: 480, onClose: onClose, footer: footer, children: [jsx(FileUploadDrawerFileDragger, { directory: directory, accept: accept, dragAreaDescription: dragAreaDescription, onFileReceived: onFileReceived }), raws.length === 0 && ((_a = uploadStore[identifier]) === null || _a === void 0 ? void 0 : _a.length) === 0 && (jsx(Empty, { className: "mt-20", description: "\u6682\u65E0\u4E0A\u4F20\u5185\u5BB9" })), raws.length > 0 && (jsxs("div", { className: "flex justify-center gap-4 mt-4", children: [jsx(LoadingOutlined, {}), "\u9884\u68C0\u4E0A\u4F20\u6587\u4EF6\u4E2D(", raws.length, ")"] })), ((_b = uploadStore[identifier]) === null || _b === void 0 ? void 0 : _b.length) > 0 && (jsx(FileUploading, { showFinished: showFinished, identifier: identifier }))] }));
|
|
51
|
+
function onFileReceived(files) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
setRaws((prev) => [...prev, ...files]);
|
|
54
|
+
yield formatFiles(files);
|
|
55
|
+
setRaws((prev) => {
|
|
56
|
+
const prevSet = new Set(prev);
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
prevSet.delete(file);
|
|
59
|
+
}
|
|
60
|
+
return [...prevSet];
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { FileUploadDrawer as default };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { __awaiter } from 'tslib';
|
|
2
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { Flex } from 'antd';
|
|
4
|
+
import { InboxOutlined } from '@ant-design/icons';
|
|
5
|
+
import { useMemo, useRef, useState } from 'react';
|
|
6
|
+
import './utils/constant.js';
|
|
7
|
+
import { retrieveAllFiles } from './utils/retrieveAllFiles.js';
|
|
8
|
+
|
|
9
|
+
const containerClassName = 'border-dashed rounded border bg-gray-50 p-4 hover:border-green-600 border-gray-300';
|
|
10
|
+
function FileUploadDrawerFileDragger({ accept = '', directory = true, dragAreaDescription, onFileReceived, }) {
|
|
11
|
+
// 将accept字符串转换为Set以便快速查找
|
|
12
|
+
const acceptList = useMemo(() => new Set(accept ? accept.split(',') : []), [accept]);
|
|
13
|
+
const fileInputRef = useRef(null);
|
|
14
|
+
const [isDragOver, setIsDragOver] = useState(false);
|
|
15
|
+
// 拖拽相关的事件处理函数
|
|
16
|
+
const handleDragEvents = {
|
|
17
|
+
onDragLeave: () => setIsDragOver(false),
|
|
18
|
+
onDragEnter: (e) => e.preventDefault(),
|
|
19
|
+
onDragOver: (e) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
setIsDragOver(true);
|
|
22
|
+
},
|
|
23
|
+
onDrop: handleDrop,
|
|
24
|
+
};
|
|
25
|
+
return (jsxs(Fragment, { children: [jsxs(Flex, Object.assign({ vertical: true, gap: "1rem", align: "center", className: `${isDragOver ? 'border-green-600' : ''} ${containerClassName}` }, handleDragEvents, { onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: [jsx("p", { className: "text-5xl text-green-600 m-0", children: jsx(InboxOutlined, {}) }), jsx("div", { className: "text-base text-center", children: renderDragAreaDescription() })] })), jsx("input", { ref: fileInputRef, type: "file", accept: accept, hidden: true, multiple: true, onChange: handleInputChange, onClick: () => {
|
|
26
|
+
if (fileInputRef.current) {
|
|
27
|
+
fileInputRef.current.value = ''; // 清空input的value,确保同一文件可以重复上传
|
|
28
|
+
}
|
|
29
|
+
} })] }));
|
|
30
|
+
// 处理文件拖拽
|
|
31
|
+
function handleDrop(e) {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
setIsDragOver(false);
|
|
35
|
+
// 获取所有拖拽的文件或文件夹
|
|
36
|
+
const filePromises = Array.from(e.dataTransfer.items)
|
|
37
|
+
.map((item) => item.webkitGetAsEntry())
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.map((entry) => retrieveAllFiles(entry, directory, acceptList));
|
|
40
|
+
// 等待所有文件处理完成
|
|
41
|
+
const results = yield Promise.allSettled(filePromises);
|
|
42
|
+
// 过滤出成功的结果并展平数组
|
|
43
|
+
const files = results
|
|
44
|
+
.filter((result) => result.status === 'fulfilled')
|
|
45
|
+
.flatMap((result) => result.value);
|
|
46
|
+
if (files.length) {
|
|
47
|
+
onFileReceived(files);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// 处理文件选择
|
|
52
|
+
function handleInputChange() {
|
|
53
|
+
var _a;
|
|
54
|
+
const files = (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.files;
|
|
55
|
+
if (files === null || files === void 0 ? void 0 : files.length) {
|
|
56
|
+
onFileReceived(Array.from(files));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// 渲染拖拽区域的描述文本
|
|
60
|
+
function renderDragAreaDescription() {
|
|
61
|
+
if (!dragAreaDescription) {
|
|
62
|
+
return jsx("p", { className: "text-base m-0", children: "\u70B9\u51FB\u6216\u62D6\u62FD\u4E0A\u4F20\u6587\u4EF6" });
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(dragAreaDescription)) {
|
|
65
|
+
return dragAreaDescription.map((text, index) => (jsx("p", { className: "text-base m-0", children: text }, index)));
|
|
66
|
+
}
|
|
67
|
+
return jsx("p", { className: "text-base m-0", children: dragAreaDescription });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { FileUploadDrawerFileDragger as default };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { __awaiter } from 'tslib';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { FileTextOutlined, DeleteOutlined } from '@ant-design/icons';
|
|
4
|
+
import { useContext, useState, useEffect } from 'react';
|
|
5
|
+
import { UploadStoreContext, UploadStoreDispatchContext } from './UploadStoreProvider.js';
|
|
6
|
+
import { FILE_UPLOAD_STATUS } from './utils/constant.js';
|
|
7
|
+
import FileUploadDrawerListProgressButton from './fileUploadDrawerListProgressButton.js';
|
|
8
|
+
import { Flex, Button } from 'antd';
|
|
9
|
+
import { useRetryUpload } from './hooks/useRetryUpload.js';
|
|
10
|
+
import { useCancelUpload } from './hooks/useCancelUpload.js';
|
|
11
|
+
|
|
12
|
+
function FileUploading({ identifier, showFinished, }) {
|
|
13
|
+
const uploadContext = useContext(UploadStoreContext);
|
|
14
|
+
const cancelUpload = useCancelUpload();
|
|
15
|
+
const retryUpload = useRetryUpload();
|
|
16
|
+
const [fileList, setFileList] = useState([]);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
// 根据条件过滤文件列表
|
|
19
|
+
const filteredList = uploadContext[identifier].filter((file) => {
|
|
20
|
+
// 排除已取消的文件
|
|
21
|
+
if (file.fileStatus === FILE_UPLOAD_STATUS.CANCELED) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
// 如果不显示已完成文件,则排除已完成的文件
|
|
25
|
+
if (showFinished === false && file.fileStatus === FILE_UPLOAD_STATUS.FINISHED) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
});
|
|
30
|
+
setFileList(filteredList);
|
|
31
|
+
}, [identifier, showFinished, uploadContext]);
|
|
32
|
+
return (jsx("div", { className: "my-4", children: fileList.map((file) => (jsx(FileListItem, { identifier: identifier, file: file, showFinished: showFinished, onButtonClick: handleButtonClick }, file.fileName))) }));
|
|
33
|
+
// 处理按钮点击事件
|
|
34
|
+
function handleButtonClick(file) {
|
|
35
|
+
file.fileStatus === FILE_UPLOAD_STATUS.PAUSED
|
|
36
|
+
? retryUpload(file) // 如果文件暂停,则重试上传
|
|
37
|
+
: cancelUpload(file); // 否则取消上传
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function FileListItem({ identifier, file, showFinished, onButtonClick }) {
|
|
41
|
+
const setUploadsStore = useContext(UploadStoreDispatchContext);
|
|
42
|
+
const showDelete = typeof showFinished === 'object' && showFinished.withDelete;
|
|
43
|
+
return (jsx("div", { className: "mb-2 last:mb-0", children: jsxs("div", { className: "px-4 my-2 relative flex justify-between items-center", children: [jsxs(Flex, { align: "center", gap: ".5rem", title: file.fileName, children: [jsx(FileTextOutlined, {}), jsx("span", { className: "truncate w-40", children: file.fileName })] }), jsxs(Flex, { align: "center", gap: ".5rem", children: [jsx(FileUploadDrawerListProgressButton, { status: file.fileStatus, percent: Number(file.percentage.toFixed(0)), onClick: () => onButtonClick(file) }), file.fileStatus === FILE_UPLOAD_STATUS.FINISHED && showDelete && (jsx(Button, { type: "text", size: "large", className: "px-3", disabled: showDelete.disabled, title: `${showDelete.disabled ? '删除-已禁用' : '删除'}`, onClick: () => onDelete(file, showDelete.onDelete), children: jsx(DeleteOutlined, {}) }))] })] }) }));
|
|
44
|
+
function onDelete(file, onDeleteImpl) {
|
|
45
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
const result = yield onDeleteImpl(file.fileName);
|
|
47
|
+
if (result) {
|
|
48
|
+
setUploadsStore({ type: 'remove_file', identifier, file });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { FileUploading as default };
|