3 * @classdesc Upload to a wiki. Most of the functionality is implemented
4 * in {@link mw.Api#upload} and friends, but this model class will tie it
5 * together as well as let you perform actions in a logical way.
9 * var file = new OO.ui.SelectFileWidget(),
10 * button = new OO.ui.ButtonWidget( { label: 'Save' } ),
11 * upload = new mw.Upload;
13 * button.on( 'click', () => {
14 * upload.setFile( file.getValue() );
15 * upload.setFilename( file.getValue().name );
19 * $( document.body ).append( file.$element, button.$element );
21 * You can also choose to {@link mw.Upload#uploadToStash stash the upload}
22 * and {@link mw.Upload#finishStashUpload finalize} it later:
24 * var file, // Some file object
25 * upload = new mw.Upload,
26 * stashPromise = $.Deferred();
28 * upload.setFile( file );
29 * upload.uploadToStash().then( () => {
30 * stashPromise.resolve();
33 * stashPromise.then( () => {
34 * upload.setFilename( 'foo' );
35 * upload.setText( 'bar' );
36 * upload.finishStashUpload().then( () => {
37 * console.log( 'Done!' );
44 * @description Used to represent an upload in progress on the frontend.
45 * @param {Object|mw.Api} [apiconfig] A mw.Api object (or subclass), or configuration
46 * to pass to the constructor of mw.Api.
48 function Upload( apiconfig ) {
49 this.api = ( apiconfig instanceof mw.Api ) ? apiconfig : new mw.Api( apiconfig );
51 this.watchlist = false;
56 this.setState( Upload.State.NEW );
58 this.imageinfo = undefined;
61 const UP = Upload.prototype;
64 * Get the mw.Api instance used by this Upload object.
66 * @name mw.Upload.prototype.getApi
68 * @return {jQuery.Promise<mw.Api>}
70 UP.getApi = function () {
71 return $.Deferred().resolve( this.api ).promise();
75 * Set the text of the file page, to be created on file upload.
77 * @name mw.Upload.prototype.setText
79 * @param {string} text
81 UP.setText = function ( text ) {
86 * Set the filename, to be finalized on upload.
88 * @name mw.Upload.prototype.setFilename
90 * @param {string} filename
92 UP.setFilename = function ( filename ) {
93 this.filename = filename;
97 * Set the stashed file to finish uploading.
99 * @name mw.Upload.prototype.setFilekey
101 * @param {string} filekey
103 UP.setFilekey = function ( filekey ) {
104 this.setState( Upload.State.STASHED );
105 this.stashPromise = $.Deferred().resolve( ( data ) => this.api.uploadFromStash( filekey, data ) );
109 * Sets the filename based on the filename as it was on the upload.
111 * @name mw.Upload.prototype.setFilenameFromFile
114 UP.setFilenameFromFile = function () {
115 const file = this.getFile();
119 if ( file.nodeType && file.nodeType === Node.ELEMENT_NODE ) {
120 // File input element, use getBasename to cut out the path
121 this.setFilename( this.getBasename( file.value ) );
122 } else if ( file.name ) {
123 // HTML5 FileAPI File object, but use getBasename to be safe
124 this.setFilename( this.getBasename( file.name ) );
126 // If we ever implement uploading files from clipboard, they might not have a name
127 this.setFilename( '?' );
132 * Set the file to be uploaded.
134 * @name mw.Upload.prototype.setFile
136 * @param {HTMLInputElement|File|Blob} file
138 UP.setFile = function ( file ) {
143 * Set whether the file should be watchlisted after upload.
145 * @name mw.Upload.prototype.setWatchlist
147 * @param {boolean} watchlist
149 UP.setWatchlist = function ( watchlist ) {
150 this.watchlist = watchlist;
154 * Set the edit comment for the upload.
156 * @name mw.Upload.prototype.setComment
158 * @param {string} comment
160 UP.setComment = function ( comment ) {
161 this.comment = comment;
165 * Get the text of the file page, to be created on file upload.
167 * @name mw.Upload.prototype.getText
171 UP.getText = function () {
176 * Get the filename, to be finalized on upload.
178 * @name mw.Upload.prototype.getFilename
182 UP.getFilename = function () {
183 return this.filename;
187 * Get the file being uploaded.
189 * @name mw.Upload.prototype.getFile
191 * @return {HTMLInputElement|File|Blob}
193 UP.getFile = function () {
198 * Get the boolean for whether the file will be watchlisted after upload.
200 * @name mw.Upload.prototype.getWatchlist
204 UP.getWatchlist = function () {
205 return this.watchlist;
209 * Get the current value of the edit comment for the upload.
211 * @name mw.Upload.prototype.getComment
215 UP.getComment = function () {
220 * Gets the base filename from a path name.
222 * @name mw.Upload.prototype.getBasename
224 * @param {string} path
227 UP.getBasename = function ( path ) {
228 if ( path === undefined || path === null ) {
232 // Find the index of the last path separator in the
233 // path, and add 1. Then, take the entire string after that.
236 path.lastIndexOf( '/' ),
237 path.lastIndexOf( '\\' )
243 * Sets the state and state details (if any) of the upload.
245 * @name mw.Upload.prototype.setState
247 * @param {mw.Upload.State} state
248 * @param {Object} stateDetails
250 UP.setState = function ( state, stateDetails ) {
252 this.stateDetails = stateDetails;
256 * Gets the state of the upload.
258 * @name mw.Upload.prototype.getState
260 * @return {mw.Upload.State}
262 UP.getState = function () {
267 * Gets details of the current state.
269 * @name mw.Upload.prototype.getStateDetails
273 UP.getStateDetails = function () {
274 return this.stateDetails;
278 * Get the imageinfo object for the finished upload.
279 * Only available once the upload is finished! Don't try to get it
282 * @name mw.Upload.prototype.getImageInfo
284 * @return {Object|undefined}
286 UP.getImageInfo = function () {
287 return this.imageinfo;
291 * Upload the file directly.
293 * @name mw.Upload.prototype.upload
295 * @return {jQuery.Promise}
297 UP.upload = function () {
298 if ( !this.getFile() ) {
299 return $.Deferred().reject( 'No file to upload. Call setFile to add one.' );
302 if ( !this.getFilename() ) {
303 return $.Deferred().reject( 'No filename set. Call setFilename to add one.' );
306 this.setState( Upload.State.UPLOADING );
308 return this.api.chunkedUpload( this.getFile(), {
309 watchlist: ( this.getWatchlist() ) ? 1 : undefined,
310 comment: this.getComment(),
311 filename: this.getFilename(),
313 } ).then( ( result ) => {
314 this.setState( Upload.State.UPLOADED );
315 this.imageinfo = result.upload.imageinfo;
317 }, ( errorCode, result ) => {
318 if ( result && result.upload && result.upload.warnings ) {
319 this.setState( Upload.State.WARNING, result );
321 this.setState( Upload.State.ERROR, result );
323 return $.Deferred().reject( errorCode, result );
328 * Upload the file to the stash to be completed later.
330 * @name mw.Upload.prototype.uploadToStash
332 * @return {jQuery.Promise}
334 UP.uploadToStash = function () {
335 if ( !this.getFile() ) {
336 return $.Deferred().reject( 'No file to upload. Call setFile to add one.' );
339 if ( !this.getFilename() ) {
340 this.setFilenameFromFile();
343 this.setState( Upload.State.UPLOADING );
345 this.stashPromise = this.api.chunkedUploadToStash( this.getFile(), {
346 ignorewarnings: true,
347 filename: this.getFilename()
348 } ).then( ( finishStash ) => {
349 this.setState( Upload.State.STASHED );
351 }, ( errorCode, result ) => {
352 if ( result && result.upload && result.upload.warnings ) {
353 this.setState( Upload.State.WARNING, result );
355 this.setState( Upload.State.ERROR, result );
357 return $.Deferred().reject( errorCode, result );
360 return this.stashPromise;
364 * Finish a stash upload.
366 * @name mw.Upload.prototype.finishStashUpload
368 * @return {jQuery.Promise}
370 UP.finishStashUpload = function () {
371 if ( !this.stashPromise ) {
372 return $.Deferred().reject( 'This upload has not been stashed, please upload it to the stash first.' );
375 return this.stashPromise.then( ( finishStash ) => {
376 this.setState( Upload.State.UPLOADING );
378 return finishStash( {
379 ignorewarnings: false,
380 watchlist: ( this.getWatchlist() ) ? 1 : undefined,
381 comment: this.getComment(),
382 filename: this.getFilename(),
384 } ).then( ( result ) => {
385 this.setState( Upload.State.UPLOADED );
386 this.imageinfo = result.upload.imageinfo;
388 }, ( errorCode, result ) => {
389 if ( result && result.upload && result.upload.warnings ) {
390 this.setState( Upload.State.WARNING, result );
392 this.setState( Upload.State.ERROR, result );
394 return $.Deferred().reject( errorCode, result );
404 * State of uploads represented in simple terms.
407 /** Upload not yet started */
410 /** Upload finished, but there was a warning */
413 /** Upload finished, but there was an error */
416 /** Upload in progress */
419 /** Upload finished, but not published, call #finishStashUpload */
422 /** Upload finished and published */