Merge "Documentation: Remove paragraph about not creating a 2nd WebRequest"
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.Upload.js
1 ( function ( mw, $ ) {
2 var UP;
3
4 /**
5 * @class mw.Upload
6 *
7 * Used to represent an upload in progress on the frontend.
8 * Most of the functionality is implemented in mw.Api.plugin.upload,
9 * but this model class will tie it together as well as let you perform
10 * actions in a logical way.
11 *
12 * A simple example:
13 *
14 * var file = new OO.ui.SelectFileWidget(),
15 * button = new OO.ui.ButtonWidget( { label: 'Save' } ),
16 * upload = new mw.Upload;
17 *
18 * button.on( 'click', function () {
19 * upload.setFile( file.getValue() );
20 * upload.setFilename( file.getValue().name );
21 * upload.upload();
22 * } );
23 *
24 * $( 'body' ).append( file.$element, button.$element );
25 *
26 * You can also choose to {@link #uploadToStash stash the upload} and
27 * {@link #finishStashUpload finalize} it later:
28 *
29 * var file, // Some file object
30 * upload = new mw.Upload,
31 * stashPromise = $.Deferred();
32 *
33 * upload.setFile( file );
34 * upload.uploadToStash().then( function () {
35 * stashPromise.resolve();
36 * } );
37 *
38 * stashPromise.then( function () {
39 * upload.setFilename( 'foo' );
40 * upload.setText( 'bar' );
41 * upload.finishStashUpload().then( function () {
42 * console.log( 'Done!' );
43 * } );
44 * } );
45 *
46 * @constructor
47 * @param {Object|mw.Api} [apiconfig] A mw.Api object (or subclass), or configuration
48 * to pass to the constructor of mw.Api.
49 */
50 function Upload( apiconfig ) {
51 this.api = ( apiconfig instanceof mw.Api ) ? apiconfig : new mw.Api( apiconfig );
52
53 this.watchlist = false;
54 this.text = '';
55 this.comment = '';
56 this.filename = null;
57 this.file = null;
58 this.setState( Upload.State.NEW );
59
60 this.imageinfo = undefined;
61 }
62
63 UP = Upload.prototype;
64
65 /**
66 * Set the text of the file page, to be created on file upload.
67 *
68 * @param {string} text
69 */
70 UP.setText = function ( text ) {
71 this.text = text;
72 };
73
74 /**
75 * Set the filename, to be finalized on upload.
76 *
77 * @param {string} filename
78 */
79 UP.setFilename = function ( filename ) {
80 this.filename = filename;
81 };
82
83 /**
84 * Sets the filename based on the filename as it was on the upload.
85 */
86 UP.setFilenameFromFile = function () {
87 var file = this.getFile();
88 if ( !file ) {
89 return;
90 }
91 if ( file.nodeType && file.nodeType === Node.ELEMENT_NODE ) {
92 // File input element, use getBasename to cut out the path
93 this.setFilename( this.getBasename( file.value ) );
94 } else if ( file.name ) {
95 // HTML5 FileAPI File object, but use getBasename to be safe
96 this.setFilename( this.getBasename( file.name ) );
97 } else {
98 // If we ever implement uploading files from clipboard, they might not have a name
99 this.setFilename( '?' );
100 }
101 };
102
103 /**
104 * Set the file to be uploaded.
105 *
106 * @param {HTMLInputElement|File} file
107 */
108 UP.setFile = function ( file ) {
109 this.file = file;
110 };
111
112 /**
113 * Set whether the file should be watchlisted after upload.
114 *
115 * @param {boolean} watchlist
116 */
117 UP.setWatchlist = function ( watchlist ) {
118 this.watchlist = watchlist;
119 };
120
121 /**
122 * Set the edit comment for the upload.
123 *
124 * @param {string} comment
125 */
126 UP.setComment = function ( comment ) {
127 this.comment = comment;
128 };
129
130 /**
131 * Get the text of the file page, to be created on file upload.
132 *
133 * @return {string}
134 */
135 UP.getText = function () {
136 return this.text;
137 };
138
139 /**
140 * Get the filename, to be finalized on upload.
141 *
142 * @return {string}
143 */
144 UP.getFilename = function () {
145 return this.filename;
146 };
147
148 /**
149 * Get the file being uploaded.
150 *
151 * @return {HTMLInputElement|File}
152 */
153 UP.getFile = function () {
154 return this.file;
155 };
156
157 /**
158 * Get the boolean for whether the file will be watchlisted after upload.
159 *
160 * @return {boolean}
161 */
162 UP.getWatchlist = function () {
163 return this.watchlist;
164 };
165
166 /**
167 * Get the current value of the edit comment for the upload.
168 *
169 * @return {string}
170 */
171 UP.getComment = function () {
172 return this.comment;
173 };
174
175 /**
176 * Gets the base filename from a path name.
177 *
178 * @param {string} path
179 * @return {string}
180 */
181 UP.getBasename = function ( path ) {
182 if ( path === undefined || path === null ) {
183 return '';
184 }
185
186 // Find the index of the last path separator in the
187 // path, and add 1. Then, take the entire string after that.
188 return path.slice(
189 Math.max(
190 path.lastIndexOf( '/' ),
191 path.lastIndexOf( '\\' )
192 ) + 1
193 );
194 };
195
196 /**
197 * Sets the state and state details (if any) of the upload.
198 *
199 * @param {mw.Upload.State} state
200 * @param {Object} stateDetails
201 */
202 UP.setState = function ( state, stateDetails ) {
203 this.state = state;
204 this.stateDetails = stateDetails;
205 };
206
207 /**
208 * Gets the state of the upload.
209 *
210 * @return {mw.Upload.State}
211 */
212 UP.getState = function () {
213 return this.state;
214 };
215
216 /**
217 * Gets details of the current state.
218 *
219 * @return {string}
220 */
221 UP.getStateDetails = function () {
222 return this.stateDetails;
223 };
224
225 /**
226 * Get the imageinfo object for the finished upload.
227 * Only available once the upload is finished! Don't try to get it
228 * beforehand.
229 *
230 * @return {Object|undefined}
231 */
232 UP.getImageInfo = function () {
233 return this.imageinfo;
234 };
235
236 /**
237 * Upload the file directly.
238 *
239 * @return {jQuery.Promise}
240 */
241 UP.upload = function () {
242 var upload = this;
243
244 if ( !this.getFile() ) {
245 return $.Deferred().reject( 'No file to upload. Call setFile to add one.' );
246 }
247
248 if ( !this.getFilename() ) {
249 return $.Deferred().reject( 'No filename set. Call setFilename to add one.' );
250 }
251
252 this.setState( Upload.State.UPLOADING );
253
254 return this.api.upload( this.getFile(), {
255 watchlist: ( this.getWatchlist() ) ? 1 : undefined,
256 comment: this.getComment(),
257 filename: this.getFilename(),
258 text: this.getText()
259 } ).then( function ( result ) {
260 upload.setState( Upload.State.UPLOADED );
261 upload.imageinfo = result.upload.imageinfo;
262 return result;
263 }, function ( errorCode, result ) {
264 if ( result && result.upload && result.upload.warnings ) {
265 upload.setState( Upload.State.WARNING, result );
266 } else {
267 upload.setState( Upload.State.ERROR, result );
268 }
269 return $.Deferred().reject( errorCode, result );
270 } );
271 };
272
273 /**
274 * Upload the file to the stash to be completed later.
275 *
276 * @return {jQuery.Promise}
277 */
278 UP.uploadToStash = function () {
279 var upload = this;
280
281 if ( !this.getFile() ) {
282 return $.Deferred().reject( 'No file to upload. Call setFile to add one.' );
283 }
284
285 if ( !this.getFilename() ) {
286 this.setFilenameFromFile();
287 }
288
289 this.setState( Upload.State.UPLOADING );
290
291 this.stashPromise = this.api.uploadToStash( this.getFile(), {
292 filename: this.getFilename()
293 } ).then( function ( finishStash ) {
294 upload.setState( Upload.State.STASHED );
295 return finishStash;
296 }, function ( errorCode, result ) {
297 if ( result && result.upload && result.upload.warnings ) {
298 upload.setState( Upload.State.WARNING, result );
299 } else {
300 upload.setState( Upload.State.ERROR, result );
301 }
302 return $.Deferred().reject( errorCode, result );
303 } );
304
305 return this.stashPromise;
306 };
307
308 /**
309 * Finish a stash upload.
310 *
311 * @return {jQuery.Promise}
312 */
313 UP.finishStashUpload = function () {
314 var upload = this;
315
316 if ( !this.stashPromise ) {
317 return $.Deferred().reject( 'This upload has not been stashed, please upload it to the stash first.' );
318 }
319
320 return this.stashPromise.then( function ( finishStash ) {
321 upload.setState( Upload.State.UPLOADING );
322
323 return finishStash( {
324 bucket: upload.bucket, // Automatically ignored if undefined
325 watchlist: ( upload.getWatchlist() ) ? 1 : undefined,
326 comment: upload.getComment(),
327 filename: upload.getFilename(),
328 text: upload.getText()
329 } ).then( function ( result ) {
330 upload.setState( Upload.State.UPLOADED );
331 upload.imageinfo = result.upload.imageinfo;
332 return result;
333 }, function ( errorCode, result ) {
334 if ( result && result.upload && result.upload.warnings ) {
335 upload.setState( Upload.State.WARNING, result );
336 } else {
337 upload.setState( Upload.State.ERROR, result );
338 }
339 return $.Deferred().reject( errorCode, result );
340 } );
341 } );
342 };
343
344 /**
345 * @enum mw.Upload.State
346 * State of uploads represented in simple terms.
347 */
348 Upload.State = {
349 /** Upload not yet started */
350 NEW: 0,
351
352 /** Upload finished, but there was a warning */
353 WARNING: 1,
354
355 /** Upload finished, but there was an error */
356 ERROR: 2,
357
358 /** Upload in progress */
359 UPLOADING: 3,
360
361 /** Upload finished, but not published, call #finishStashUpload */
362 STASHED: 4,
363
364 /** Upload finished and published */
365 UPLOADED: 5
366 };
367
368 mw.Upload = Upload;
369 }( mediaWiki, jQuery ) );