Skip to content

feat: support paste upload file #543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ React.render(<Upload />, container);
|customRequest | function | null | provide an override for the default xhr behavior for additional customization|
|withCredentials | boolean | false | ajax upload with cookie send |
|openFileDialogOnClick | boolean | true | useful for drag only upload as it does not trigger on enter key or click event |
|pastable | boolean | false | support paste upload |

#### onError arguments

Expand Down
8 changes: 8 additions & 0 deletions docs/demo/paste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: paste
nav:
title: Demo
path: /demo
---

<code src="../examples/paste.tsx"/></code>
8 changes: 8 additions & 0 deletions docs/demo/pasteDirectory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: pasteDirectory
nav:
title: Demo
path: /demo
---

<code src="../examples/pasteDirectory.tsx"/></code>
44 changes: 44 additions & 0 deletions docs/examples/paste.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint no-console:0 */
import React from 'react';
import Upload from 'rc-upload';

const props = {
action: '/upload.do',
type: 'drag',
accept: '.png',
pastable: true,
beforeUpload(file) {
console.log('beforeUpload', file.name);
},
onStart: file => {
console.log('onStart', file.name);
},
onSuccess(file) {
console.log('onSuccess', file);
},
onProgress(step, file) {
console.log('onProgress', Math.round(step.percent), file.name);
},
onError(err) {
console.log('onError', err);
},
style: { display: 'inline-block', width: 200, height: 200, background: '#eee' },
};

const Test = () => {
return (
<div
style={{
margin: 100,
}}
>
<div>
<Upload {...props}>
<a>开始上传</a>
</Upload>
</div>
</div>
);
};

export default Test;
45 changes: 45 additions & 0 deletions docs/examples/pasteDirectory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* eslint no-console:0 */
import React from 'react';
import Upload from 'rc-upload';

const props = {
action: '/upload.do',
type: 'drag',
accept: '.png',
directory: true,
pastable: true,
beforeUpload(file) {
console.log('beforeUpload', file.name);
},
onStart: file => {
console.log('onStart', file.name);
},
onSuccess(file) {
console.log('onSuccess', file);
},
onProgress(step, file) {
console.log('onProgress', Math.round(step.percent), file.name);
},
onError(err) {
console.log('onError', err);
},
style: { display: 'inline-block', width: 200, height: 200, background: '#eee' },
};

const Test = () => {
return (
<div
style={{
margin: 100,
}}
>
<div>
<Upload {...props}>
<a>开始上传</a>
</Upload>
</div>
</div>
);
};

export default Test;
62 changes: 48 additions & 14 deletions src/AjaxUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,41 +66,75 @@
}
};

onFileDrop = async (e: React.DragEvent<HTMLDivElement>) => {
const { multiple } = this.props;

onFileDropOrPaste = async (e: React.DragEvent<HTMLDivElement> | ClipboardEvent) => {
e.preventDefault();

if (e.type === 'dragover') {
return;
}

if (this.props.directory) {
const files = await traverseFileTree(
Array.prototype.slice.call(e.dataTransfer.items),
(_file: RcFile) => attrAccept(_file, this.props.accept),
const { multiple, accept, directory } = this.props;
let items: DataTransferItem[] = [];
let files: File[] = [];

if (e.type === 'drop') {
const dataTransfer = (e as React.DragEvent<HTMLDivElement>).dataTransfer;
items = [...(dataTransfer.items || [])];
files = [...(dataTransfer.files || [])];
} else if (e.type === 'paste') {
const clipboardData = (e as ClipboardEvent).clipboardData;
items = [...(clipboardData.items || [])];
files = [...(clipboardData.files || [])];
}

if (directory) {
files = await traverseFileTree(Array.prototype.slice.call(items), (_file: RcFile) =>
attrAccept(_file, this.props.accept),
);
this.uploadFiles(files);
} else {
let files = [...e.dataTransfer.files].filter((file: RcFile) =>
attrAccept(file, this.props.accept),
);
let acceptFiles = [...files].filter((file: RcFile) => attrAccept(file, accept));

if (multiple === false) {
files = files.slice(0, 1);
acceptFiles = files.slice(0, 1);
}

this.uploadFiles(files);
this.uploadFiles(acceptFiles);
}
};

onPrePaste = (e: ClipboardEvent) => {
const { pastable } = this.props;

if (pastable) {
this.onFileDropOrPaste(e);
}
};

componentDidMount() {
this._isMounted = true;

const { pastable } = this.props;

if (pastable) {
document.addEventListener('paste', this.onPrePaste);
}
}

componentWillUnmount() {
this._isMounted = false;
this.abort();
document.removeEventListener('paste', this.onPrePaste);
}

componentDidUpdate(prevProps: UploadProps) {
const { pastable } = this.props;

if (pastable && !prevProps.pastable) {
document.addEventListener('paste', this.onPrePaste);

Check warning on line 134 in src/AjaxUploader.tsx

View check run for this annotation

Codecov / codecov/patch

src/AjaxUploader.tsx#L134

Added line #L134 was not covered by tests
} else if (!pastable && prevProps.pastable) {
document.removeEventListener('paste', this.onPrePaste);

Check warning on line 136 in src/AjaxUploader.tsx

View check run for this annotation

Codecov / codecov/patch

src/AjaxUploader.tsx#L136

Added line #L136 was not covered by tests
}
}

uploadFiles = (files: File[]) => {
Expand Down Expand Up @@ -299,8 +333,8 @@
onKeyDown: openFileDialogOnClick ? this.onKeyDown : () => {},
onMouseEnter,
onMouseLeave,
onDrop: this.onFileDrop,
onDragOver: this.onFileDrop,
onDrop: this.onFileDropOrPaste,
onDragOver: this.onFileDropOrPaste,
tabIndex: hasControlInside ? undefined : '0',
};
return (
Expand Down
1 change: 1 addition & 0 deletions src/interface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface UploadProps
input?: React.CSSProperties;
};
hasControlInside?: boolean;
pastable?: boolean;
}

export interface UploadProgressEvent extends Partial<ProgressEvent> {
Expand Down
Loading