diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index f8af91c4e..000000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,6 +0,0 @@ -# Please Read This BEFORE Opening Up a New Issue or Pull Request # - -The issue tracker in this project is for bug reports or feature requests ONLY. If you have a support question, -please see http://fineuploader.com/support.html which contains instructions on where you can browse and open support -requests. All pull requests and issues _must_ include all required fields in the respective template. If items -from these fields are missing, the pull request or issue report may be closed as a result. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index a9dcb0a6b..a22338494 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,39 +1,67 @@ -## Type of issue [REQUIRED] -{Choose one: "bug" or "feature request"} +## Type of issue +- [ ] Bug report +- [ ] Feature request +- [ ] Support request -## Uploader type [REQUIRED] -{Choose at least one: "traditional", "S3", "Azure"} +## Uploader type +- [ ] Traditional +- [ ] S3 +- [ ] Azure -## Bug details [DELETE EVERYTHING IN THIS SECTION IF THIS IS A FEATURE REQUEST] -### Fine Uploader version [REQUIRED] + +
+Bug Report + +#### Fine Uploader version {example: 5.5.1} -### Browsers where the bug is reproducible [REQUIRED] +#### Browsers where the bug is reproducible {example: "Firefox" and "IE11"} -### Operating systems where the bug is reproducible [REQUIRED] -{example: "iOS 9.1.0" and "Windows 8.1" +#### Operating systems where the bug is reproducible +{example: "iOS 9.1.0" and "Windows 8.1"} -### Exact steps required to reproduce the issue [REQUIRED] +#### Exact steps required to reproduce the issue For example: 1. Select 3 files 2. Pause the 2nd file before it completes, but after it has started. 3. Attempt to resume the paused file. -### All of your Fine Uploader initialization JavaScript code [REQUIRED] +#### All relevant Fine Uploader-related code that you have written {simply copy and paste the JS used to control Fine Uploader browsers-ide} +{also include your template HTML if related to a UI issue} -### Your Fine Uploader template markup (if using Fine Uploader UI and the issue is UI-related) +#### Your Fine Uploader template markup (if using Fine Uploader UI and the issue is UI-related) {simply copy and paste your template markup} -### Detailed explanation of the problem [REQUIRED] -{please describe the bug here} +#### Detailed explanation of the problem +{describe the bug here} +
+ + + + +
+Feature Request +#### Feature request details +{why is this feature important, not just for you, but for many others?} +
-## Feature request details [DELETE EVERYTHING IN THIS SECTION IF THIS IS A BUG REPORT] -### Detailed description of the feature -{also include why this feature is necessary and what problems it solves} + +
+Support Request + +#### Fine Uploader version +{example: 5.5.1} + +#### Question +{ask your support question here & be as detailed and clear as possible} + +#### Related code +{post any code related to your question here} +
diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 26b8cb22a..5e0bae919 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -1,14 +1,10 @@ -## Brief description of the changes [REQUIRED] -{also describe what problem(s) these changes solve} +## Brief description of the changes +{also describe what problem(s) these changes solve & reference any related issues/PRs} -## What browsers and operating systems have you tested these changes on? [REQUIRED] +## What browsers and operating systems have you tested these changes on? {example: Safari on iOS 9.1.0 and IE11 on Windows 8.1} -## Are all automated tests passing? [REQUIRED] -{Choose one: "yes" or "no". If "no", then why not?} - - -## Is this pull request against develop or some other non-master branch? [REQUIRED] -{Choose one: "yes" or "no". If "no", then why not?} +## Have you written unit tests? If not, explain why. +{unit tests should accompany almost all PRs, unless the change is to documentation} diff --git a/.gitignore b/.gitignore index 11982d1ef..a8c2d3a3a 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ bin/ src npm-debug.log +Vagrantfile test/dev/handlers/s3/composer.lock test/dev/handlers/traditional/files diff --git a/Makefile b/Makefile index 8f396e674..17752a38e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,14 @@ version=$(shell node -pe "require('./package.json').version") dist-out-dir = _dist pub-dir = $(dist-out-dir)/$(version) -npm-bin = $(shell npm bin) + +# properly get npm-bin in cygwin (Eg. CYGWIN_NT-10.0) +platform = $(shell uname -s) +ifeq ($(findstring _NT,$(platform)),_NT) + npm-bin = $(shell cygpath -u $(shell npm bin)) +else + npm-bin = $(shell npm bin) +endif build-out-dir = _build src-dir = client @@ -357,6 +364,7 @@ setup-dist: mkdir -p $(pub-dir) cp LICENSE README.md package.json $(pub-dir) cp -pR $(src-dir)/commonjs/ $(pub-dir)/lib/ + cp -pR $(src-dir)/typescript $(pub-dir)/ copy-build-to-dist: mkdir -p $(pub-dir)/$(PUB-SUBDIR) diff --git a/README.md b/README.md index dfe24a9a1..c77afe423 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,11 @@ [![Build Status](https://travis-ci.org/FineUploader/fine-uploader.svg?branch=master)](https://travis-ci.org/FineUploader/fine-uploader) [![npm](https://img.shields.io/npm/v/fine-uploader.svg)](https://www.npmjs.com/package/fine-uploader) [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) -[![Stackoverflow](https://img.shields.io/badge/ask-on%20stack%20overflow-brightgreen.svg)](http://stackoverflow.com/questions/tagged/fine-uploader) [![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/fineuploader.svg?style=social&label=Follow%20%40FineUploader)](https://twitter.com/fineuploader) [**Documentation**](http://docs.fineuploader.com) | [**Examples**](http://fineuploader.com/demos) | -[**Support**](http://fineuploader.com/support.html) | +[**Support**](../../issues) | [**Blog**](http://blog.fineuploader.com/) | [**Changelog**](../../releases) diff --git a/client/js/uploader.basic.api.js b/client/js/uploader.basic.api.js index 8643edf36..cf507059e 100644 --- a/client/js/uploader.basic.api.js +++ b/client/js/uploader.basic.api.js @@ -308,6 +308,10 @@ return false; }, + removeFileRef: function(id) { + this._handler.expunge(id); + }, + reset: function() { this.log("Resetting uploader..."); @@ -393,6 +397,35 @@ return this._uploadData.uuidChanged(id, newUuid); }, + /** + * Expose the internal status of a file id to the public api for manual state changes + * @public + * + * @param {Number} id, + * @param {String} newStatus + * + * @todo Implement the remaining methods + */ + setStatus: function(id, newStatus) { + var fileRecord = this.getUploads({id: id}); + if (!fileRecord) { + throw new qq.Error(id + " is not a valid file ID."); + } + + switch (newStatus) { + case qq.status.DELETED: + this._onDeleteComplete(id, null, false); + break; + case qq.status.DELETE_FAILED: + this._onDeleteComplete(id, null, true); + break; + default: + var errorMessage = "Method setStatus called on '" + name + "' not implemented yet for " + newStatus; + this.log(errorMessage); + throw new qq.Error(errorMessage); + } + }, + uploadStoredFiles: function() { if (this._storedIds.length === 0) { this._itemError("noFilesError"); @@ -1063,6 +1096,35 @@ }); }, + _handleDeleteSuccess: function(id) { + if (this.getUploads({id: id}).status !== qq.status.DELETED) { + var name = this.getName(id); + + this._netUploadedOrQueued--; + this._netUploaded--; + this._handler.expunge(id); + this._uploadData.setStatus(id, qq.status.DELETED); + this.log("Delete request for '" + name + "' has succeeded."); + } + }, + + _handleDeleteFailed: function(id, xhrOrXdr) { + var name = this.getName(id); + + this._uploadData.setStatus(id, qq.status.DELETE_FAILED); + this.log("Delete request for '" + name + "' has failed.", "error"); + + // Check first if xhrOrXdr is actually passed or valid + // For error reporting, we only have access to the response status if this is not + // an `XDomainRequest`. + if (!xhrOrXdr || xhrOrXdr.withCredentials === undefined) { + this._options.callbacks.onError(id, name, "Delete request failed", xhrOrXdr); + } + else { + this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhrOrXdr.status, xhrOrXdr); + } + }, + // Creates an extra button element _initExtraButton: function(spec) { var button = this._createUploadButton({ @@ -1416,24 +1478,10 @@ var name = this.getName(id); if (isError) { - this._uploadData.setStatus(id, qq.status.DELETE_FAILED); - this.log("Delete request for '" + name + "' has failed.", "error"); - - // For error reporting, we only have access to the response status if this is not - // an `XDomainRequest`. - if (xhrOrXdr.withCredentials === undefined) { - this._options.callbacks.onError(id, name, "Delete request failed", xhrOrXdr); - } - else { - this._options.callbacks.onError(id, name, "Delete request failed with response code " + xhrOrXdr.status, xhrOrXdr); - } + this._handleDeleteFailed(id, xhrOrXdr); } else { - this._netUploadedOrQueued--; - this._netUploaded--; - this._handler.expunge(id); - this._uploadData.setStatus(id, qq.status.DELETED); - this.log("Delete request for '" + name + "' has succeeded."); + this._handleDeleteSuccess(id); } }, diff --git a/client/js/version.js b/client/js/version.js index 329e6a42f..4d8e1c54f 100644 --- a/client/js/version.js +++ b/client/js/version.js @@ -1,2 +1,2 @@ /*global qq */ -qq.version = "5.13.0"; +qq.version = "5.14.0"; diff --git a/client/typescript/fine-uploader.d.ts b/client/typescript/fine-uploader.d.ts new file mode 100644 index 000000000..4ef8d5755 --- /dev/null +++ b/client/typescript/fine-uploader.d.ts @@ -0,0 +1,3380 @@ +// Type definitions for FineUploader 5.x.x +// Project: http://fineuploader.com/ +// Definitions by: Sukhdeep Singh + +/** + * The FineUploader namespace contains all the methods, options, events and types + */ +declare namespace FineUploader { + + /* ========================================================== CORE & UI ===================================================================== */ + /** + * type for `resizeInfo` object + */ + interface ResizeInfo { + /** + * The original `File` or `Blob` object, if available. + */ + blob?: File | Blob; + /** + * Desired height of the image after the resize operation. + */ + height?: number; + /** + * The original HTMLImageElement object, if available. + */ + image?: HTMLImageElement; + /** + * `HTMLCanvasElement` element containing the original image data (not resized). + */ + sourceCanvas?: HTMLCanvasElement; + /** + * `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image. + */ + targetCanvas?: HTMLCanvasElement; + /** + * Desired width of the image after the resize operation. + */ + width?: number; + } + + /** + * Callback type for `customResizer` parameter + */ + interface CustomResizerCallBack { + /** + * Contribute this function to manually resize images using alternate 3rd party libraries + * + * @param ResizeInfo resizeInfo : the ResizeInfo object containing all the resize values/options + * @returns Promise : Once the resize is complete, the function must return a promise + */ + (resizeInfo: ResizeInfo): PromiseOptions; + } + + /** + * A BlobWrapper object type + */ + interface BlobWrapper { + /** + * the bytes of the `Blob` object being uploaded + */ + blob?: Blob; + /** + * the name of the `Blob` + */ + name?: string; + } + + /** + * A CanvasWrapper Object type + */ + interface CanvasWrapper { + /** + * the `` to be converted to a file & then uploaded + */ + canvas?: HTMLCanvasElement; + /** + * the name to assign to the created file + */ + name?: string; + /** + * `1`-`100` value indicating the desired quality of the converted file (only for `image/jpeg`) + */ + quality?: number; + /** + * MIME type of the file to create from this `` + */ + type?: MimeType; + } + + /** + * Resumable file object type + */ + interface ResumableFileObject { + /** + * filename + */ + name?: string; + /** + * the unique id + */ + uuid?: number; + /** + * the index of the part where the resume will start from + */ + partIdx?: number; + } + + /** + * Resumable file object type for S3 + */ + interface S3ResumableFileObject extends ResumableFileObject { + /** + * The associated object's S3 key + */ + key?: string; + } + + /** + * Resumable file object type for Azure + */ + interface AzureResumableFileObject extends ResumableFileObject { + /** + * The associated file's blob name in Azure Blob Storage + */ + key?: string; + } + + /** + * type for getUploads method's filter parameter + */ + interface UploadFilter { + /** + * the id of the file + */ + id?: number | number[]; + /** + * the uuid of the file + */ + uuid?: number | number[]; + /** + * status of the file + */ + status?: string | string[]; + } + + /** + * type for getUploads method's return object + */ + interface FoundUploadItems extends UploadFilter { + /** + * the name of the file + */ + name?: string; + /** + * the size of the file + */ + size?: string; + } + + /** + * ScaleImageOptions + */ + interface ScaleImageOptions { + /** + * required + */ + maxSize: number; + /** + * @default `true` + */ + orient?: boolean; + /** + * defaults to the type of the reference image + */ + type?: string; + /** + * number between `0` and `100` + * + * @default `80` + */ + quality?: number; + /** + * @default `false` + */ + includeExif?: boolean; + /** + * Ignored if the current browser does not support image previews. + * + * If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. + * + * Once the resize is complete, your promise must be fulfilled. + * You may, of course, reject your returned `Promise` is the resize fails in some way. + */ + customResizer?: CustomResizerCallBack; + } + + /** + * formatFileName function type + */ + interface FormatFileNameFuncton { + (fileOrBlobName: string): String | void; + } + + /** + * BlobsOptions + */ + interface BlobsOptions { + /** + * The default name to be used for nameless `Blob`s + * + * @default `Misc data` + */ + defaultName?: string; + } + + /** + * CameraOptions + */ + interface CameraOptions { + /** + * `null` allows camera access on the default button in iOS. + * + * Otherwise provide an extra button container element to target + * + * @default `null` + */ + button?: HTMLElement; + /** + * Enable or disable camera access on iOS (iPod, iPhone, and iPad) devices. + * + * ###Note: + * Enabling this will disable multiple file selection + * + * @default `false` + */ + ios?: boolean; + } + + /** + * ConcurrentOptions + */ + interface ConcurrentOptions { + /** + * Allow multiple chunks to be uploaded simultaneously per file + * + * @default `false` + */ + enabled?: boolean; + } + + /** + * ChunkingOptions + */ + interface ChunkingOptions { + /** + * concurrent Chunking options + */ + concurrent?: ConcurrentOptions; + /** + * Enable or disable splitting the file separate chunks. Each chunks is sent in a separate requested + * + * @default `false` + */ + enabled?: boolean; + /** + * Ensure every file is uploaded in chunks, even if the file can only be split up into 1 chunk. + * + * Does not apply if chunking is not possible in the current browser + * + * @default `false` + */ + mandatory?: boolean; + /** + * The maximum size of each chunk, in bytes + * + * @default `2000000` + */ + partSize?: number; + /** + * ParamNamesOptions + */ + paramNames?: ParamNamesOptions; + /** + * SuccessOptions + */ + success?: SuccessOptions; + + } + + /** + * ParamNamesOptions + */ + interface ParamNamesOptions { + /** + * Name of the parameter passed with a chunked request that specifies the size in bytes of the associated chunk + * + * @default `'qqchunksize'` + */ + chunkSize?: string; + /** + * Name of the parameter passed with a chunked request that specifies the starting byte of the associated chunk + * + * @default `'qqpartbyteoffset'` + */ + partByteOffset?: string; + /** + * Name of the parameter passed with a chunked request that specifies the index of the associated partition + * + * @default `'qqpartindex'` + */ + partIndex?: string; + /** + * Name of the parameter passed with a chunked request that specifies the total number of chunks associated with the `File` or `Blob` + * + * @default `'qqtotalparts'` + */ + totalParts?: string; + /** + * Sent with the first request of the resume with a value of `true` + * + * @default `'qqresume'` + */ + resuming?: string; + /** + * totalFileSize + * + * @default `'qqtotalfilesize'` + */ + totalFileSize?: string; + } + + /** + * SuccessOptions + */ + interface SuccessOptions { + /** + * Endpoint to send a POST after all chunks have been successfully uploaded for each file. + * + * Required if the `concurrent.enabled` option is set + * + * @default `null` + */ + endpoint?: string; + } + + /** + * CorsOptions + */ + interface CorsOptions { + /** + * Enable or disable cross-origin requests from IE9 and older where XDomainRequest must be used + * + * @default `false` + */ + allowXdr?: boolean; + /** + * Enable or disable cross-domain requests + * + * @default `false` + */ + expected?: boolean; + /** + * Enable or disable sending credentials along with each cross-domain request. Ignored if allowXdr is true and IE9 is being used + * + * @default `false` + */ + sendCredentials?: boolean; + } + + /** + * DeleteFileOptions + */ + interface DeleteFileOptions { + /** + * Any additional headers to attach to all delete file requests + * + * @default `{}` + */ + customHeaders?: any; + /** + * Enable or disable deletion of uploaded files + * + * @default `false` + */ + enabled?: boolean; + /** + * The endpoint to which delete file requests are sent. + * + * @default `'/server/upload'` + */ + endpoint?: string; + /** + * Set the method to use for delete requests. + * + * Accepts `'POST'` or `'DELETE'` + * + * @default `'DELETE'` + */ + method?: string; + /** + * Any additional parameters to attach to delete file requests + * + * @default `{}` + */ + params?: any; + } + + /** + * ExtraButtonsOptions + */ + interface ExtraButtonsOptions { + /** + * The container element for the upload button + * + * @default `undefined` + */ + element: HTMLElement; + /** + * This value will be used when creating the `title` attribute for the underlying ``. + * + * If not provided, the `text.fileInputTitle` option will be used instead + * + * @default `'file input'` + */ + fileInputTitle?: string; + /** + * `true` to allow folders to be selected, `false` to allow files to be selected. + * + * @default `false` + */ + folders?: boolean; + /** + * Specify to override the default `multiple` value + * + * @default `true` + */ + multiple?: boolean; + /** + * Specify to override the default `validation` option specified. + * + * Any `validation` properties not specified will be inherited from the default `validation` option + * + * @default `validation` + */ + validation?: any; + } + + /** + * FormOptions + */ + interface FormOptions { + /** + * This can be the ID of the
or a reference to the element + * + * @default `'qq-form'` + */ + element?: string | HTMLElement; + /** + * If Fine Uploader is able to attach to a form, this value takes the place of the base `autoUpload` option + * + * @default `false` + */ + autoUpload?: boolean; + /** + * Set this to `false` if you do not want Fine Uploader to intercept attempts to submit your form. + * + * By default, Fine Uploader will intercept submit attempts and instead upload all submitted files, including data from your form in each upload request + * + * @default `true` + */ + interceptSubmit?: boolean; + } + + /** + * Messages + */ + interface Messages { + /** + * Text passed to the error event handler if a submitted item is zero bits + * + * @default `'{file} is empty, please select files again without it.'` + */ + emptyError?: string; + /** + * Text passed to the error event handler if an image is too tall + * + * @default `'Image is too tall.'` + */ + maxHeightImageError?: string; + /** + * Text passed to the error event handler if an image is too wide + * + * @default `'Image is too wide.'` + */ + maxWidthImageError?: string; + /** + * Text passed to the error event handler if an image is not tall enough + * + * @default `'Image is not tall enough.'` + */ + minHeightImageError?: string; + /** + * Text passed to the error event handler if an image is not wide enough + * + * @default `'Image is not wide enough.'` + */ + minWidthImageError?: string; + /** + * Text passed to the error event handler if the item is too small + * + * @default `'{file} is too small, minimum file size is {minSizeLimit}.'` + */ + minSizeError?: string; + /** + * Text passed to the error event handler if any empty array of items is submitted + * + * @default `'No files to upload.'` + */ + noFilesError?: string; + /** + * Text displayed to the user when they attempt to leave the page while uploads are still in progress + * + * @default `'The files are being uploaded, if you leave now the upload will be canceled.'` + */ + onLeave?: string; + /** + * Text passed to the error event handler if a retry attempt is declared a failed due to a violation of the `validation.itemLimit` rule + * + * @default `'Retry failed - you have reached your file limit.'` + */ + retryFailTooManyItemsError?: string; + /** + * Text passed to the error event handler if a submitted item is too large. + * + * @default `'{file} is too large, maximum file size is {sizeLimit}.'` + */ + sizeError?: string; + /** + * Text passed to the error event handler if a submit is ignored due to a violation of the `validation.itemLimit` rules + * + * @default `'Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.'` + */ + tooManyItemsError?: string; + /** + * Text passed to the error event handler if an invalid file type is detected + * + * @default `'{file} has an invalid extension. Valid extension(s): {extensions}.'` + */ + typeError?: string; + /** + * Message displayed if the browser is iOS8 Safari and the corresponding workarounds option is not disabled + * + * @default `'Unrecoverable error - this browser does not permit file uploading of any kind due to serious bugs in iOS8 Safari. Please use iOS8 Chrome until Apple fixes these issues.'` + */ + unsupportedBrowserIos8Safari?: string; + } + + /** + * PasteOptions + */ + interface PasteOptions { + /** + * The default name given to pasted images + * + * @default `'pasted_image'` + */ + defaultName?: string; + /** + * Enable this feature by providing any HTMLElement here + * + * @default `null` + */ + targetElement?: HTMLElement; + } + + /** + * ResumeOptions + */ + interface ResumeOptions { + /** + * The number of days before a persistent resume record will expire + * + * @default `7` + */ + recordsExpireIn?: number; + /** + * Enable or disable the ability to resume failed or stopped chunked uploads + * + * @default `false` + */ + enabled?: boolean; + /** + * paramNames.resuming - Sent with the first request of the resume with a value of `true`. + * + * @default `'qqresume'` + */ + paramNames?: ParamNamesOptions; + } + + /** + * RetryOptions + */ + interface RetryOptions { + /** + * The number of seconds to wait between auto retry attempts + * + * @default `5` + */ + autoAttemptDelay?: number; + /** + * Enable or disable retrying uploads that receive any error response + * + * @default `false` + */ + enableAuto?: boolean; + /** + * The maximum number of times to attempt to retry a failed upload + * + * @default `3` + */ + maxAutoAttempts?: number; + /** + * This property will be looked for in the server response and, if found and `true`, will indicate that no more retries should be attempted for this item + * + * @default `'preventRetry'` + */ + preventRetryResponseProperty?: string; + } + + /** + * RequestOptions + */ + interface RequestOptions { + /** + * Additional headers sent along with each upload request + */ + customHeaders?: any; + /** + * The endpoint to send upload requests to + * + * @default `'/server/upload'` + */ + endpoint?: string; + /** + * The name of the parameter passed if the original filename has been edited or a `Blob` is being sent + * + * @default `'qqfilename'` + */ + filenameParam?: string; + /** + * Force all uploads to use multipart encoding + * + * @default `true` + */ + forceMultipart?: boolean; + /** + * The attribute of the input element which will contain the file name. + * + * For non-multipart-encoded upload requests, this will be included as a parameter in the query string of the URI with a value equal to the file name + * + * @default `'qqfile'` + */ + inputName?: string; + /** + * Specify a method to use when sending files to a traditional endpoint. This option is ignored in older browsers (such as IE 9 and older) + * + * @default `'POST'` + */ + method?: string; + /** + * The parameters that shall be sent with each upload request + */ + params?: any; + /** + * Enable or disable sending parameters in the request body. + * + * If `false`, parameters are sent in the URL. + * Otherwise, parameters are sent in the request body + * + * @default `true` + */ + paramsInBody?: boolean; + /** + * The name of the parameter the uniquely identifies each associated item. The value is a Level 4 UUID + * + * @default `'qquuid'` + */ + uuidName?: string; + /** + * The name of the parameter passed that specifies the total file size in bytes + * + * @default `'qqtotalfilesize'` + */ + totalFileSizeName?: string; + } + + /** + * SizeOptions + */ + interface SizeOptions { + /** + * name property will be appended to the file name of the scaled file + */ + name?: string; + /** + * maximum size + */ + maxSize?: number; + /** + * MIME type + */ + type?: string; + } + + /** + * ScalingOptions + */ + interface ScalingOptions { + /** + * Ignored if the current browser does not support image previews. + * + * If you want to use an alternate scaling library, you must contribute a function for this option that returns a Promise. + * Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned Promise is the resize fails in some way + * + * @default `undefined` + */ + customResizer?: CustomResizerCallBack; + /** + * A value between `1` and `100` that describes the requested quality of scaled images. + * + * Ignored unless the scaled image type target is `image/jpeg` + * + * @default `80` + */ + defaultQuality?: number + /** + * Scaled images will assume this image type if you don't specify a specific type in your size object, or if the type specified in the size object is not valid. + * + * You generally should not use any value other than `image/jpeg` or `image/png` here. + * + * The default value of null will ensure the scaled image type is `PNG`, unless the original file is a `JPEG`, in which case the scaled file will also be a `JPEG`. + * The default is probably the safest option. + * + * @default `null` + */ + defaultType?: string; + /** + * Text sent to your `complete` event handler as an `error` property of the `response` param if a scaled image could not be generated + * + * @default `'failed to scale'` + */ + failureText?: string; + /** + * Ensure the `EXIF` data from the reference image is inserted into the scaled image. Only applicable when both the reference and the target are type `image/jpeg` + * + * @default `false` + */ + includeExif?: boolean; + /** + * Set this to `false` if you do not want scaled images to be re-oriented based on parsed `EXIF` data before they are uploaded + * + * @default `true` + */ + orient?: boolean; + /** + * Set this to `false` if you don't want to original file to be uploaded as well + * + * @default `true` + */ + sendOriginal?: boolean; + /** + * An array containing size objects that describe scaled versions of each submitted image that should be generated and uploaded + * + * @default `[]` + */ + sizes?: SizeOptions; + } + + /** + * SessionOptions + */ + interface SessionOptions { + /** + * Any additional headers you would like included with the `GET` request sent to your server. Ignored in `IE9` and `IE8` if the endpoint is cross-origin + * + * @default `{}` + */ + customHeaders?: any; + /** + * If non-null, Fine Uploader will send a `GET` request on startup to this endpoint, expecting a `JSON` response containing data about the initial file list to populate + * + * @default `null` + */ + endpoint?: string; + /** + * Any parameters you would like passed with the associated `GET` request to your server + * + * @default `{}` + */ + params?: any; + /** + * Set this to `false` if you do not want the file list to be retrieved from the server as part of a reset. + * + * @default `true` + */ + refreshOnReset?: boolean + } + + /** + * TextOptions + */ + interface TextOptions { + /** + * In the event of non-200 response from the server sans the 'error' property, this message will be passed to the 'error' event handler + * + * @default `'Upload failure reason unknown'` + */ + defaultResponseError?: string; + /** + * The value for the `title` attribute attached to the `` maintained by Fine Uploader for each upload button. + * + * This is used as hover text, among other things. + * + * @default `'file input'` + */ + fileInputTitle?: string; + /** + * Symbols used to represent file size, in ascending order + * + * @default `['kB', 'MB', 'GB', 'TB', 'PB', 'EB']` + */ + sizeSymbols?: string[]; + } + + /** + * ImageOptions + */ + interface ImageOptions { + /** + * Restrict images to a maximum height in pixels (wherever possible) + * + * @default `0` + */ + maxHeight?: number; + /** + * Restrict images to a maximum width in pixels (wherever possible) + * + * @default `0` + */ + maxWidth?: number; + /** + * Restrict images to a minimum height in pixels (wherever possible) + * + * @default `0` + */ + minHeight?: number; + /** + * Restrict images to a minimum width in pixels (wherever possible) + * + * @default `0` + */ + minWidth?: number; + } + + /** + * ValidationOptions + */ + interface ValidationOptions { + /** + * Used by the file selection dialog. + * + * Restrict the valid file types that appear in the selection dialog by listing valid content-type specifiers + * + * @default `null` + */ + acceptFiles?: any; + /** + * Specify file valid file extensions here to restrict uploads to specific types + * + * @default `[]` + */ + allowedExtensions?: string[]; + /** + * Maximum number of items that can be potentially uploaded in this session. + * + * Will reject all items that are added or retried after this limit is reached + * + * @default `0` + */ + itemLimit?: number; + /** + * The minimum allowable size, in bytes, for an item + * + * @default `0` + */ + minSizeLimit?: number; + /** + * The maximum allowable size, in bytes, for an item + * + * @default `0` + */ + sizeLimit?: number; + /** + * When `true` the first invalid item will stop processing further files + * + * @default `true` + */ + stopOnFirstInvalidFile?: boolean; + /** + * ImageOptions + */ + image?: ImageOptions; + } + + /** + * WorkArounds options + */ + interface WorkArounds { + /** + * Ensures all `` elements tracked by Fine Uploader do NOT contain a `multiple` attribute to work around an issue present in iOS7 & 8 that otherwise results in 0-sized uploaded videos + * + * @default `true` + */ + iosEmptyVideos?: boolean; + /** + * Ensures all `` elements tracked by Fine Uploader always have a `multiple` attribute present. + * + * This only applies to iOS8 Chrome and iOS8 UIWebView, and is put in place to work around an issue that causes the browser to crash when a file input element does not contain a `multiple` attribute inside of a `UIWebView` container created by an iOS8 app compiled with and iOS7 SDK + * + * @default `false` + */ + ios8BrowserCrash?: boolean; + /** + * Disables Fine Uploader and displays a message to the user in iOS 8.0.0 Safari. + * + * Due to serious bugs in iOS 8.0.0 Safari, uploading is not possible. + * This was apparently fixed in subsequent builds of iOS8, so this workaround only targets 8.0.0 + * + * @default `true` + */ + ios8SafariUploads?: boolean; + } + + interface PromiseOptions { + /** + * Register callbacks from success and failure. + * + * The promise instance that then is called on will pass any values into the provided callbacks. + * If success or failure have already occurred before these callbacks have been registered, then they will be called immediately after this call has been executed. + * Each subsequent call to then registers an additional set of callbacks. + * + * @param Function successCallback : The function to call when the promise is successfully fulfilled + * @param Function failureCallback : The function to call when the promise is unsuccessfully fulfilled + * @return PromiseOptions : An instance of a promise + */ + then(successCallback: Function, failureCallback: Function): PromiseOptions; + + /** + * Register callbacks for success or failure. + * + * Invoked when the promise is fulfilled regardless of the result. + * The promise instance that done is called on will pass any values into the provided callback. + * Each call to done registers an additional set of callbacks + * + * @param Function callback : The function to call when the promise is fulfilled, successful or not. + * @return PromiseOptions : An instance of a promise + */ + done(callback: Function): PromiseOptions; + + /** + * Call this on a promise to indicate success. + * The parameter values will depend on the situation. + * + * @param Object param : The value to pass to the promise's success handler. + * @return PromiseOptions : An instance of a promise + */ + success(param: any): PromiseOptions; + + /** + * Call this on a promise to indicate failure. + * The parameter values will depend on the situation. + * + * @param Object param : The value to pass to the promise's failure handler. + * @return PromiseOptions : An instance of a promise + */ + failure(param: any): PromiseOptions; + } + + + /* ====================================== Core Callback functions ==================================== */ + + /** + * onAutoRetry function type + */ + interface OnAutoRetry { + /** + * @param number id : The current file's id + * @param string name : The current file's name + * @param number attemptNumber : The number of retry attempts for the current file so far + */ + (id: number, name: string, attemptNumber: number): void; + } + + /** + * onCancel function type + */ + interface OnCancel { + /** + * @param number id : The current file's id + * @param string name : The current file's name + */ + (id: number, name: string): boolean | PromiseOptions | void; + } + + /** + * onComplete function type + */ + interface OnComplete { + /** + * @param number id : The current file's id + * @param string name : The current file's name + * @param Object responseJSON : The raw response from the server + * @param XMLHttpRequest xhr : The object used to make the request + */ + (id: number, name: string, responseJSON: any, xhr: XMLHttpRequest): void; + } + + /** + * onAllComplete function type + */ + interface OnAllComplete { + /** + * @param number[] succeeded : IDs of all files in the group that have uploaded successfully (status = `qq.status.UPLOAD_SUCCESSFUL`) + * @param number[] failed : IDs of all files in the group that have failed (status = `qq.status.UPLOAD_FAILED`) + */ + (succeeded: number[], failed: number[]): void; + } + + /** + * onDelete function type + */ + interface OnDelete { + /** + * @param number id : The current file's id + */ + (id: number): void; + } + + /** + * onDeleteComplete function type + */ + interface OnDeleteComplete { + /** + * @param number id : The current file's id + * @param XMLHttpRequest xhr : The object used to make the request + * @param boolean isError : `true` if there has been an error, `false` otherwise + */ + (id: number, xhr: XMLHttpRequest, isError: boolean): void; + } + + /** + * onError function type + */ + interface OnError { + /** + * @param number id : The current file's id + * @param string name : The current file's name + * @param string errorReason : The reason for the current error + * @param XMLHttpRequest xhr : The object used to make the request + */ + (id: number, name: string, errorReason: string, xhr: XMLHttpRequest): void; + } + + /** + * onManualRetry function type + */ + interface OnManualRetry { + /** + * @param number id : The current file's id + * @param string name : The current file's name + */ + (id: number, name: string): boolean | void; + } + + /** + * onPasteReceived function type + */ + interface OnPasteReceived { + /** + * @param Blob blob : An object encapsulating the image pasted from the clipboard + */ + (blob: Blob): PromiseOptions | void; + } + + /** + * onProgress function type + */ + interface OnProgress { + /** + * @param number id : The current file's id + * @param string name : The current file's name + * @param number uploadedBytes : The number of bytes that have been uploaded so far + * @param number totalBytes : The total number of bytes that comprise this file + */ + (id: number, name: string, uploadedBytes: number, totalBytes: number): void; + } + + /** + * onResume function type + */ + interface OnResume { + /** + * @param number id : The current file's id + * @param string name : The current file's name + * @param Object chunkData : The chunk that will be sent next when file upload resumes + */ + (id: number, name: string, chunkData: any): void; + } + + /** + * onSessionRequestComplete function type + */ + interface OnSessionRequestComplete { + /** + * @param any[] response : The raw response data + * @param boolean success : Indicates whether success has been achieved or not + * @param XMLHttpRequest xhrOrXdr : The raw request + */ + (response: any[], success: boolean, xhrOrXdr: XMLHttpRequest): void; + } + + /** + * onStatusChange function type + */ + interface OnStatusChange { + /** + * @param number id : The current file's id + * @param string oldStatus : The previous item status + * @param string newStatus : The new status of the item + */ + (id: number, oldStatus: string, newStatus: string): void; + } + + /** + * onSubmit function type + */ + interface OnSubmit { + /** + * @param number id : The current file's id + * @param string name : The current file's name + */ + (id: number, name: string): boolean | PromiseOptions | void; + } + + /** + * onSubmitDelete function type + */ + interface OnSubmitDelete { + /** + * @param number id : The current file's id + */ + (id: number): PromiseOptions | void; + } + + /** + * onSubmitted function type + */ + interface OnSubmitted { + /** + * @param number id : The current file's id + * @param string name : The current file's name + */ + (id: number, name: string): void; + } + + /** + * onTotalProgress function type + */ + interface OnTotalProgress { + /** + * @param number totalUploadedBytes : The number of bytes that have been uploaded so far in this batch + * @param number totalBytes : The total number of bytes that comprise all files in the batch + */ + (totalUploadedBytes: number, totalBytes: number): void; + } + + /** + * onUpload function type + */ + interface OnUpload { + /** + * @param number id : The current file's id + * @param string name : The current file's name + */ + (id: number, name: string): void; + } + + /** + * properties for chunkData object + */ + interface ChunkData { + /** + * the 0-based index of the associated partition + */ + partIndex: number; + /** + * the byte offset of the current chunk + */ + startByte: number; + /** + * the last byte of the current chunk + */ + endByte: number; + /** + * the total number of partitions associated with the `File` or `Blob` + */ + totalParts: number; + } + + /** + * onUploadChunk function type + */ + interface OnUploadChunk { + /** + * @param number id : The current file's id + * @param string name : The current file's name + * @param ChunkData chunkData : An object encapsulating the current chunk of data about to be uploaded + */ + (id: number, name: string, chunkData: ChunkData): void; + } + + /** + * onUploadChunkSuccess function type + */ + interface OnUploadChunkSuccess { + /** + * @param number id : The current file's id + * @param ChunkData chunkData : An object encapsulating the current chunk of data about to be uploaded + * @param Object responseJSON : The raw response from the server + * @param XMLHttpRequest xhr : The object used to make the request + */ + (id: number, chunkData: ChunkData, responseJSON: any, xhr: XMLHttpRequest): void; + } + + /** + * blobData object + */ + interface BlobDataObject { + /** + * the name of the file + */ + name: string; + /** + * the size of the file + */ + size: number; + } + + /** + * onValidate function type + */ + interface OnValidate { + /** + * @param BlobDataObject data : An object with a name and size property + * @param HTMLElement buttonContainer : The button corresponding to the respective file if the file was submitted to Fine Uploader using a tracked button + */ + (data: BlobDataObject, buttonContainer?: HTMLElement): PromiseOptions | void; + } + + /** + * onValidateBatch function type + */ + interface OnValidateBatch { + /** + * @param BlobDataObject[] fileOrBlobDataArray : An array of Objects with name and size properties + * @param HTMLElement buttonContainer : The button corresponding to the respective file if the file was submitted to Fine Uploader using a tracked button + */ + (fileOrBlobDataArray: BlobDataObject[], buttonContainer: HTMLElement): PromiseOptions | void; + } + + /** + * Core callback functions + */ + interface CoreEvents { + /** + * Called before each automatic retry attempt for a failed item** + */ + onAutoRetry?: OnAutoRetry; + /** + * Called when the item has been canceled. Return `false` to prevent the upload from being canceled. + * + * Also can return a promise if non-blocking work is required here. Calling `failure()` on the promise is equivalent to returning `false`. + * + * If using a Promise, then processing of the cancel request will be deferred until the promise is fullfilled. + * + * Since there is no way to 'pause' the upload in progress while waiting for the promise to be fullfilled the upload may actually complete until the promise has actually be fullfilled + */ + onCancel?: OnCancel; + /** + * Called when the item has finished uploading. + * + * The `responseJSON` will contain the raw response from the server including the 'success' property which indicates whether the upload succeeded. + */ + onComplete?: OnComplete; + /** + * Called when all submitted items have reached a point of termination. + * + * A file has reached a point of termination if it has been cancelled, rejected, or uploaded (failed or successful). + * + * For example, if a file in the group is paused, and all other files in the group have uploaded successfully the allComplete event will not be invoked for the group until that paused file is either continued and completes the uploading process, or canceled. + * + * This event will not be called if all files in the group have been cancelled or rejected (i.e. if none of the files have reached a status of `qq.status.UPLOAD_SUCCESSFUL` or `qq.status.UPLOAD_FAILED`) + */ + onAllComplete?: OnAllComplete; + /** + * Called just before a delete request is sent for the associated item. + * + * ###Note: + * This is not the correct callback to influence the delete request. + * To do that, use the `onSubmitDelete` callback instead + */ + onDelete?: OnDelete; + /** + * Called just after receiving a response from the server for a delete file request** + */ + onDeleteComplete?: OnDeleteComplete; + /** + * Called whenever an exceptional condition occurs** + */ + onError?: OnError; + /** + * Called before each manual retry attempt. + * + * Return `false` to prevent this and all future retry attempts on the associated item + */ + onManualRetry?: OnManualRetry; + /** + * Called when a pasted image has been received (before upload). + * + * The pasted image is represented as a `Blob`. Also can return a `Promise` if non-blocking work is required here. + * + * If using a `Promise` the value of the success parameter must be the name to associate with the pasted image. + * + * If the associated attempt is marked a failure then you should include a string explaining the reason in your failure callback for the `Promise` + * + * ###NOTE: + * The `promptForName` option, if `true`, will effectively wipe away any custom implementation of this callback. + * + * The two are not meant to be used together. This callback is meant to provide an alternative means to provide a name for a pasted image. + * + * If you are using Fine Uploader Core mode then you can display your own prompt for the name by overriding the default implementation of this callback + */ + onPasteReceived?: OnPasteReceived; + /** + * Called during the upload, as it progresses, but only for the AJAX uploader. + * + * For chunked uploads, this will be called for each chunk. + * Useful for implementing a progress bar + */ + onProgress?: OnProgress; + /** + * Called just before an upload is resumed. + * + * See the `uploadChunk` event for more info on the `chunkData` object + */ + onResume?: OnResume; + /** + * Invoked when a session request has completed. + * + * The `response` will be either an `Array` containing the response data or `null` if the response did not contain valid `JSON`. + * + * The `success` parameter will be `false` if ANY of the file items represented in the response could not be parsed (due to bad syntax, missing name/UUID property, etc) + */ + onSessionRequestComplete?: OnSessionRequestComplete; + /** + * Invoked whenever the status changes for any item submitted by the uploader. + * + * The status values correspond to those found in the `qq.status` object. + * + * For reference: + * * `SUBMITTED` + * * `QUEUED` + * * `UPLOADING` + * * `UPLOAD_RETRYING` + * * `UPLOAD_FAILED` + * * `UPLOAD_SUCCESSFUL` + * * `CANCELED` + * * `REJECTED` + * * `DELETED` + * * `DELETING` + * * `DELETE_FAILED` + * * `PAUSED` + */ + onStatusChange?: OnStatusChange; + /** + * Called when the item has been selected and is a candidate for uploading** + * + * This does not mean the item is going to be uploaded. Return `false` to prevent submission to the uploader. + * + * A promise can be used if non-blocking work is required. Processing of this item is deferred until the promise is fullfilled. + * + * If a promise is returned, a call to failure is the same as returning `false` + */ + onSubmit?: OnSubmit; + /** + * Called before an item has been marked for deletion has been submitted to the uploader** + * + * A promise can be used if non-blocking work is required. + * Processing of this item is deferred until the promise is fullfilled. + * If a promise is returned, a call to failure is the same as returning `false`. + * + * Use this callback to influence the delete request. + * For example, you can change the custom parameters sent with the underlying delete request using the `setDeleteParams` API method + */ + onSubmitDelete?: OnSubmitDelete; + /** + * Called when the item has been successfully submitted to the uploader. + * + * The file will upload immediately if there is: + * * a) at least one free connection (see: maxConnections option) and + * * b) autoUpload is set to true (see autoUpload option) + * + * The callback is invoked after the 'submit' event is handled without returning a false value. + * + * In Fine Uploader Core mode it is usually safe to assume that the associated elements in the UI representing the associated file have already been added to the DOM immediately before this callback is invoked + */ + onSubmitted?: OnSubmitted; + /** + * Called during a batch of uploads, as they progress, but only for the AJAX uploader. + * + * This represents the total progress of all files in the batch. Useful for implementing an aggregate progress bar. + */ + onTotalProgress?: OnTotalProgress; + /** + * Called just before an item begins uploading to the server. + */ + onUpload?: OnUpload; + /** + * Called just before a chunk request is sent. + */ + onUploadChunk?: OnUploadChunk; + /** + * This is similar to the `complete` event, except it is invoked after each chunk has been successfully uploaded. + * + * See the `uploadChunk` event for more information on the `chunkData` object + */ + onUploadChunkSuccess?: OnUploadChunkSuccess; + /** + * Called once for each selected, dropped, or `addFiles` submitted file. + * + * This callback is always invoked before the default Fine Uploader validators execute. + * + * This event will not be called if you return `false` in your `validateBatch` event handler, or if the `stopOnFirstInvalidFile` validation option is `true` and the `validate` event handler has returned `false` for an item. + * + * A promise can be used if non-blocking work is required. Processing of this item is deferred until the promise is fullfilled. + * If a promise is returned, a call to `failure` is the same as returning `false`. + * + * A buttonContainer element will be passed as the last argument, provided the file was submitted using a Fine Uploader tracked button. + * + * The `blobData` object has two properties: `name` and `size`. The `size` property will be undefined for browsers without File API support. + */ + onValidate?: OnValidate; + /** + * This callback is always invoked before the default Fine Uploader validators execute. + * + * This event will not be called if you return `false` in your `validateBatch` event handler, or if the `stopOnFirstInvalidFile` validation option is `true` and the `validate` event handler has returned `false` for an item. + * + * A promise can be used if non-blocking work is required. Processing of this item is deferred until the promise is fullfilled. If a promise is returned, a call to `failure` is the same as returning `false`. + * + * A buttonContainer element will be passed as the last argument, provided the file was submitted using a Fine Uploader tracked button. + * + * The `fileOrBlobDataArray` object has two properties: `name` and `size`. The `size` property will be undefined for browsers without File API support. + */ + onValidateBatch?: OnValidateBatch; + } + /* ====================================== END - Core Callback functions ======================================== */ + + /** + * Contains Core options + */ + interface CoreOptions { + /** + * Set to false if you want to be able to upload queued items later by calling the `uploadStoredFiles()` method + * + * @default `true` + */ + autoUpload?: boolean; + /** + * Specify an element to use as the 'select files' button. Cannot be a ` diff --git a/docs/api/methods.jmd b/docs/api/methods.jmd index 755cbdd71..6b96f970e 100644 --- a/docs/api/methods.jmd +++ b/docs/api/methods.jmd @@ -25,11 +25,16 @@ $(document).ready(function() { # Methods Traditional {: .page-header } {% endmarkdown %} -
+
{% markdown %} ## Core {% endmarkdown %} + +{{ +alert("If you pass a large `Blob` that was created using JavaScript in the browser into `addFiles`, you should consider calling the [`removeFileRef` method](#removeFileRef) after the file has been successfully uploaded to free up any memory consumed by the Blob.") +}} + {{ api_method("addFiles", "addFiles (files[, params[, endpoint]])", "Submit one or more files to the uploader. A `BlobWrapper` object: @@ -352,6 +357,15 @@ A `resizeInfo` object, which will be passed to the supplied function, contains t ]) }} +{{ api_method("removeFileRef", "removeFileRef (id)", "", +[ + { + "name": "id", + "type": "Integer", + "description": "Remove internal reference to the associated Blob/File object. For Blobs that are created via JavaScript in the browser, this will free up all consumed memory." + } +]) }} + {{ api_method("reset", "reset ()", "Reset Fine Uploader", null, null) }} {{ api_method("retry", "retry (id)", "Attempt to upload a specific item again.", @@ -534,6 +548,28 @@ A `resizeInfo` object, which will be passed to your (optional) `customResizer` f } ], null) }} +{{ api_method("setStatus", "setStatus (id, newStatus)", +""" Modify the status of an file. + +The status values correspond to those found in the `qq.status` object. Currently, the following status values may be set via this method: + +* `qq.status.DELETED` +* `qq.status.DELETE_FAILED` + +""", +[ + { + "name": "id", + "type": "Integer", + "description": "The file id." + }, + { + "name": "newStatus", + "type": "String", + "description": "The new `qq.status` value." + } +], null) }} + {{ api_method("uploadStoredFiles", "uploadStoredFiles ()", "Begin uploading all queued items. Throws a `NoFilesError` of there are no items to upload.", null, null) }}
diff --git a/docs/features/statistics-and-status-updates.jmd b/docs/features/statistics-and-status-updates.jmd index ec3ef98e1..6c632d7df 100644 --- a/docs/features/statistics-and-status-updates.jmd +++ b/docs/features/statistics-and-status-updates.jmd @@ -1,7 +1,7 @@ {% extends "_templates/base.html" %} {% set page_title = "Statistics and Status Updates" %} {% block sidebar %} -{{ api_links(methods=['getUploads'], events=['statusChange']) }} +{{ api_links(methods=['getUploads', 'setStatus'], events=['statusChange']) }} {% endblock %} {% block content %} {% markdown %} diff --git a/package.json b/package.json index 69415b969..7409ebb47 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "fine-uploader", "title": "Fine Uploader", "main": "lib/traditional.js", - "version": "5.13.0", + "types" : "typescript/fine-uploader.d.ts", + "version": "5.14.0", "description": "Multiple file upload plugin with progress-bar, drag-and-drop, direct-to-S3 & Azure uploading, client-side image scaling, preview generation, form support, chunking, auto-resume, and tons of other features.", "keywords": [ "amazon", @@ -46,10 +47,10 @@ "url": "https://github.com/FineUploader/fine-uploader/issues" }, "devDependencies": { - "clean-css-cli": "4.0.0", + "clean-css-cli": "4.0.7", "jscs": "3.0.7", "jshint": "2.9.4", - "karma": "1.4.0", + "karma": "1.4.1", "karma-firefox-launcher": "1.0.0", "karma-mocha": "1.3.0", "karma-spec-reporter": "0.0.26", diff --git a/test/unit/set-status.js b/test/unit/set-status.js new file mode 100644 index 000000000..ff7fe7fba --- /dev/null +++ b/test/unit/set-status.js @@ -0,0 +1,113 @@ +/* globals describe, beforeEach, qq, qqtest, assert, helpme, it */ + +describe("set-status.js", function() { + "use strict"; + + var testUploadEndpoint = "/test/upload", + fileTestHelper = helpme.setupFileTests(); + + var initialFiles = [{ + name: "left.jpg", + uuid: "e109af57-848b-4c2a-bca8-051374d01db1" + }, { + name: "right.jpg", + uuid: "949d16c3-727a-4c3c-8c0f-23404dcd6f3b" + }]; + + it("testing status change of DELETED with initialFiles", function() { + var uploader = new qq.FineUploaderBasic(); + uploader.addInitialFiles(initialFiles); + + var uploaderFiles = uploader.getUploads(); + var file = uploaderFiles[0]; + + uploader.setStatus(file.id, qq.status.DELETED); + + uploaderFiles = uploader.getUploads(); + file = uploaderFiles[0]; + + assert.equal(1, uploader.getNetUploads()); + assert.equal(qq.status.DELETED, file.status); + + // ensure same file can't be "deleted" twice + uploader.setStatus(file.id, qq.status.DELETED); + assert.equal(1, uploader.getNetUploads()); + }); + + it("testing status change of DELETE_FAILED with initialFiles", function() { + var uploader = new qq.FineUploaderBasic(); + uploader.addInitialFiles(initialFiles); + + var uploaderFiles = uploader.getUploads(); + var file = uploaderFiles[1]; + + uploader.setStatus(file.id, qq.status.DELETE_FAILED); + + uploaderFiles = uploader.getUploads(); + file = uploaderFiles[1]; + + assert.equal(2, uploader.getNetUploads()); + assert.equal(qq.status.DELETE_FAILED, file.status); + }); + + it("testing status change of DELETED with mock uploader", function(done) { + var uploader = new qq.FineUploaderBasic({ + autoUpload: true, + request: { + endpoint: testUploadEndpoint + } + }); + + qqtest.downloadFileAsBlob("up.jpg", "image/jpeg").then(function(blob) { + fileTestHelper.mockXhr(); + + uploader.addFiles({name: "test", blob: blob}); + uploader.uploadStoredFiles(); + fileTestHelper.getRequests()[0].respond(201, null, JSON.stringify({success: true})); + + var uploaderFiles = uploader.getUploads(); + var file = uploaderFiles[0]; + + uploader.setStatus(file.id, qq.status.DELETED); + + uploaderFiles = uploader.getUploads(); + file = uploaderFiles[0]; + + assert.equal(0, uploader.getNetUploads()); + assert.equal(qq.status.DELETED, file.status); + done(); + }); + + }); + + it("testing status change of DELETED with mock uploader", function(done) { + var uploader = new qq.FineUploaderBasic({ + autoUpload: true, + request: { + endpoint: testUploadEndpoint + } + }); + + qqtest.downloadFileAsBlob("up.jpg", "image/jpeg").then(function(blob) { + fileTestHelper.mockXhr(); + + uploader.addFiles({name: "test", blob: blob}); + uploader.uploadStoredFiles(); + fileTestHelper.getRequests()[0].respond(201, null, JSON.stringify({success: true})); + + var uploaderFiles = uploader.getUploads(); + var file = uploaderFiles[0]; + + uploader.setStatus(file.id, qq.status.DELETE_FAILED); + + uploaderFiles = uploader.getUploads(); + file = uploaderFiles[0]; + + assert.equal(1, uploader.getNetUploads()); + assert.equal(qq.status.DELETE_FAILED, file.status); + done(); + }); + + }); + +}); diff --git a/test/unit/simple-file-uploads.js b/test/unit/simple-file-uploads.js index 836a3fc46..6da26f4e3 100644 --- a/test/unit/simple-file-uploads.js +++ b/test/unit/simple-file-uploads.js @@ -478,5 +478,30 @@ if (qqtest.canDownloadFileAsBlob) { uploader.addFiles(canvasWrapper); }); }); + + it("removes reference to a Blob via API", function(done) { + qqtest.downloadFileAsBlob("up.jpg", "image/jpeg").then(function(blob) { + fileTestHelper.mockXhr(); + + var request, + uploader = new qq.FineUploaderBasic({ + autoUpload: false, + request: { endpoint: testUploadEndpoint }, + callbacks: { + onComplete: function(id) { + assert.ok(uploader.getFile(id)); + uploader.removeFileRef(id); + assert.ok(!uploader.getFile(id)); + done(); + } + } + }); + + uploader.addFiles({name: "test", blob: blob}); + uploader.uploadStoredFiles(); + + fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true})); + }); + }); }); }