diff --git a/.gitignore b/.gitignore
index 6ac12083..d397107d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,5 @@ lib
coverage
yarn.lock
es
+package-lock.json
+tmp/
\ No newline at end of file
diff --git a/README.md b/README.md
index 0bb8844d..8cf57ac6 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,7 @@ React.render(, container);
|component | "div"|"span" | "span"| wrap component name |
|supportServerRender | boolean | false| whether to support server render |
|onReady | function | | only call when supportServerRender is true, upload is rendered completely |
-|action| string | | form action url |
+|action| string | function(file): string | Promise<string> | | form action url |
|data| object/function(file) | | other data object to post or a function which returns a data object |
|headers| object | {} | http headers to post, available in modern browsers |
|accept | string | | input accept attribute |
@@ -113,7 +113,8 @@ abort(file?: File) => void: abort the uploading file
#### Download Popup Problem
-In iframe uploader way, the content-type of response should be `text/plain` or `text/html`.[referense](https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation)
+In iframe uploader way, the content-type of response should be `text/plain` or `text/html`. [See more about
+Content-Type Negotiation](https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation)
What's more, in iframe mode, the response's status should always be `200 OK`, otherwise you might get an `Access is denied` error in IE 8/9.
diff --git a/examples/customRequest.html b/examples/customRequest.html
new file mode 100644
index 00000000..48cdce85
--- /dev/null
+++ b/examples/customRequest.html
@@ -0,0 +1 @@
+placeholder
diff --git a/examples/customRequest.js b/examples/customRequest.js
new file mode 100644
index 00000000..9e9f8d2f
--- /dev/null
+++ b/examples/customRequest.js
@@ -0,0 +1,83 @@
+/* eslint no-console:0 */
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Upload from 'rc-upload';
+import axios from 'axios';
+
+const uploadProps = {
+ action: '/upload.do',
+ multiple: false,
+ data: { a: 1, b: 2 },
+ headers: {
+ Authorization: '$prefix $token',
+ },
+ onStart(file) {
+ console.log('onStart', file, file.name);
+ },
+ onSuccess(ret, file) {
+ console.log('onSuccess', ret, file.name);
+ },
+ onError(err) {
+ console.log('onError', err);
+ },
+ onProgress({ percent }, file) {
+ console.log('onProgress', `${percent}%`, file.name);
+ },
+ customRequest({
+ action,
+ data,
+ file,
+ filename,
+ headers,
+ onError,
+ onProgress,
+ onSuccess,
+ withCredentials,
+ }) {
+ // EXAMPLE: post form-data with 'axios'
+ const formData = new FormData();
+ if (data) {
+ Object.keys(data).map(key => {
+ formData.append(key, data[key]);
+ });
+ }
+ formData.append(filename, file);
+
+ axios
+ .post(action, formData, {
+ withCredentials,
+ headers,
+ onUploadProgress: ({ total, loaded }) => {
+ onProgress({ percent: Math.round(loaded / total * 100).toFixed(2) }, file);
+ },
+ })
+ .then(({ data: response }) => {
+ onSuccess(response, file);
+ })
+ .catch(onError);
+
+ return {
+ abort() {
+ console.log('upload progress is aborted.');
+ },
+ };
+ },
+};
+
+const Test = () => {
+ return (
+
+ );
+};
+
+ReactDOM.render(, document.getElementById('__react-content'));
diff --git a/examples/directoryUpload.html b/examples/directoryUpload.html
new file mode 100644
index 00000000..b3a42524
--- /dev/null
+++ b/examples/directoryUpload.html
@@ -0,0 +1 @@
+placeholder
\ No newline at end of file
diff --git a/examples/directoryUpload.js b/examples/directoryUpload.js
new file mode 100644
index 00000000..f710edd1
--- /dev/null
+++ b/examples/directoryUpload.js
@@ -0,0 +1,50 @@
+/* eslint no-console:0 */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Upload from 'rc-upload';
+
+class Test extends React.Component {
+ constructor(props) {
+ super(props);
+ this.uploaderProps = {
+ action: '/upload.do',
+ data: { a: 1, b: 2 },
+ headers: {
+ Authorization: 'xxxxxxx',
+ },
+ directory: true,
+ beforeUpload(file) {
+ console.log('beforeUpload', file.name);
+ },
+ onStart: (file) => {
+ console.log('onStart', file.name);
+ // this.refs.inner.abort(file);
+ },
+ onSuccess(file) {
+ console.log('onSuccess', file);
+ },
+ onProgress(step, file) {
+ console.log('onProgress', Math.round(step.percent), file.name);
+ },
+ onError(err) {
+ console.log('onError', err);
+ },
+ };
+ }
+ render() {
+ return ();
+ }
+}
+
+ReactDOM.render(, document.getElementById('__react-content'));
diff --git a/examples/simple.js b/examples/simple.js
index 3d7bc442..7cf2209c 100644
--- a/examples/simple.js
+++ b/examples/simple.js
@@ -78,8 +78,14 @@ class Test extends React.Component {
height: 500,
}}
>
-
- 开始上传2
+
+ 开始上传2
+
diff --git a/package.json b/package.json
index 2f0d0902..25106bb8 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"coverage": "jest --coverage && cat ./coverage/lcov.info | coveralls"
},
"devDependencies": {
+ "axios": "^0.18.0",
"co-busboy": "^1.3.0",
"coveralls": "^2.13.1",
"expect.js": "0.3.x",
@@ -53,8 +54,8 @@
"dependencies": {
"babel-runtime": "6.x",
"classnames": "^2.2.5",
- "warning": "2.x",
- "prop-types": "^15.5.7"
+ "prop-types": "^15.5.7",
+ "warning": "2.x"
},
"jest": {
"collectCoverageFrom": [
diff --git a/src/AjaxUploader.jsx b/src/AjaxUploader.jsx
index 7f8f102d..43e653d2 100644
--- a/src/AjaxUploader.jsx
+++ b/src/AjaxUploader.jsx
@@ -6,6 +6,7 @@ import classNames from 'classnames';
import defaultRequest from './request';
import getUid from './uid';
import attrAccept from './attr-accept';
+import traverseFileTree from './traverseFileTree';
class AjaxUploader extends Component {
static propTypes = {
@@ -14,6 +15,7 @@ class AjaxUploader extends Component {
prefixCls: PropTypes.string,
className: PropTypes.string,
multiple: PropTypes.bool,
+ directory: PropTypes.bool,
disabled: PropTypes.bool,
accept: PropTypes.string,
children: PropTypes.any,
@@ -22,6 +24,10 @@ class AjaxUploader extends Component {
PropTypes.object,
PropTypes.func,
]),
+ action: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.func,
+ ]),
headers: PropTypes.object,
beforeUpload: PropTypes.func,
customRequest: PropTypes.func,
@@ -54,16 +60,21 @@ class AjaxUploader extends Component {
}
onFileDrop = e => {
+ e.preventDefault();
+
if (e.type === 'dragover') {
- e.preventDefault();
return;
}
- const files = Array.prototype.slice.call(e.dataTransfer.files).filter(
+ let files = Array.prototype.slice.call(e.dataTransfer.files).filter(
file => attrAccept(file, this.props.accept)
);
- this.uploadFiles(files);
- e.preventDefault();
+ if (this.props.directory) {
+ traverseFileTree(e.dataTransfer.items, this.uploadFiles);
+ } else {
+ files = e.dataTransfer.files;
+ this.uploadFiles(files);
+ }
}
componentDidMount() {
@@ -75,7 +86,7 @@ class AjaxUploader extends Component {
this.abort();
}
- uploadFiles(files) {
+ uploadFiles = (files) => {
const postFiles = Array.prototype.slice.call(files);
postFiles.forEach((file) => {
file.uid = getUid();
@@ -95,10 +106,9 @@ class AjaxUploader extends Component {
before.then((processedFile) => {
const processedFileType = Object.prototype.toString.call(processedFile);
if (processedFileType === '[object File]' || processedFileType === '[object Blob]') {
- this.post(processedFile);
- } else {
- this.post(file);
+ return this.post(processedFile);
}
+ return this.post(file);
}).catch(e => {
console && console.log(e); // eslint-disable-line
});
@@ -117,28 +127,36 @@ class AjaxUploader extends Component {
if (typeof data === 'function') {
data = data(file);
}
- const { uid } = file;
- const request = props.customRequest || defaultRequest;
- this.reqs[uid] = request({
- action: props.action,
- filename: props.name,
- file,
- data,
- headers: props.headers,
- withCredentials: props.withCredentials,
- onProgress: onProgress ? e => {
- onProgress(e, file);
- } : null,
- onSuccess: (ret, xhr) => {
- delete this.reqs[uid];
- props.onSuccess(ret, file, xhr);
- },
- onError: (err, ret) => {
- delete this.reqs[uid];
- props.onError(err, ret, file);
- },
+ new Promise(resolve => {
+ const { action } = props;
+ if (typeof action === 'function') {
+ return resolve(action(file));
+ }
+ resolve(action);
+ }).then(action => {
+ const { uid } = file;
+ const request = props.customRequest || defaultRequest;
+ this.reqs[uid] = request({
+ action,
+ filename: props.name,
+ file,
+ data,
+ headers: props.headers,
+ withCredentials: props.withCredentials,
+ onProgress: onProgress ? e => {
+ onProgress(e, file);
+ } : null,
+ onSuccess: (ret, xhr) => {
+ delete this.reqs[uid];
+ props.onSuccess(ret, file, xhr);
+ },
+ onError: (err, ret) => {
+ delete this.reqs[uid];
+ props.onError(err, ret, file);
+ },
+ });
+ onStart(file);
});
- onStart(file);
}
reset() {
@@ -176,7 +194,7 @@ class AjaxUploader extends Component {
render() {
const {
component: Tag, prefixCls, className, disabled,
- style, multiple, accept, children,
+ style, multiple, accept, children, directory,
} = this.props;
const cls = classNames({
[prefixCls]: true,
@@ -203,6 +221,8 @@ class AjaxUploader extends Component {
key={this.state.uid}
style={{ display: 'none' }}
accept={accept}
+ directory={directory ? 'directory' : null}
+ webkitdirectory={directory ? 'webkitdirectory' : null}
multiple={multiple}
onChange={this.onChange}
/>
diff --git a/src/IframeUploader.jsx b/src/IframeUploader.jsx
index e4f71cd4..871209b4 100644
--- a/src/IframeUploader.jsx
+++ b/src/IframeUploader.jsx
@@ -31,7 +31,10 @@ class IframeUploader extends Component {
PropTypes.object,
PropTypes.func,
]),
- action: PropTypes.string,
+ action: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.func,
+ ]),
name: PropTypes.string,
}
@@ -142,7 +145,7 @@ class IframeUploader extends Component {