Merge "resourceloader: Don't call wfExpandUrl() on load.php urls"
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.Upload.Dialog.js
1 ( function ( $, mw ) {
2
3 /**
4 * mw.Upload.Dialog encapsulates the process of uploading a file
5 * to MediaWiki using the {@link mw.Upload mw.Upload} model.
6 * The dialog emits events that can be used to get the stashed
7 * upload and the final file. It can be extended to accept
8 * additional fields from the user for specific scenarios like
9 * for Commons, or campaigns.
10 *
11 * ## Structure
12 *
13 * The {@link OO.ui.ProcessDialog dialog} has three steps:
14 *
15 * - **Upload**: Has a {@link OO.ui.SelectFileWidget field} to get the file object.
16 *
17 * - **Information**: Has a {@link OO.ui.FormLayout form} to collect metadata. This can be
18 * extended.
19 *
20 * - **Insert**: Has details on how to use the file that was uploaded.
21 *
22 * Each step has a form associated with it defined in
23 * {@link mw.Upload.Dialog#renderUploadForm renderUploadForm},
24 * {@link mw.Upload.Dialog#renderInfoForm renderInfoForm}, and
25 * {@link mw.Upload.Dialog#renderInsertForm renderInfoForm}. The
26 * {@link mw.Upload.Dialog#getFile getFile},
27 * {@link mw.Upload.Dialog#getFilename getFilename}, and
28 * {@link mw.Upload.Dialog#getText getText} methods are used to get
29 * the information filled in these forms, required to call
30 * {@link mw.Upload mw.Upload}.
31 *
32 * ## Usage
33 *
34 * To use, setup a {@link OO.ui.WindowManager window manager} like for normal
35 * dialogs:
36 *
37 * var uploadDialog = new mw.Upload.Dialog();
38 * var windowManager = new OO.ui.WindowManager();
39 * $( 'body' ).append( windowManager.$element );
40 * windowManager.addWindows( [ uploadDialog ] );
41 * windowManager.openWindow( uploadDialog );
42 *
43 * The dialog's closing promise,
44 * {@link mw.Upload.Dialog#event-fileUploaded fileUploaded},
45 * and {@link mw.Upload.Dialog#event-fileSaved fileSaved} events can
46 * be used to get details of the upload.
47 *
48 * ## Extending
49 *
50 * To extend using {@link mw.Upload mw.Upload}, override
51 * {@link mw.Upload.Dialog#renderInfoForm renderInfoForm} to render
52 * the form required for the specific use-case. Update the
53 * {@link mw.Upload.Dialog#getFilename getFilename}, and
54 * {@link mw.Upload.Dialog#getText getText} methods to return data
55 * from your newly created form. If you added new fields you'll also have
56 * to update the {@link #getTeardownProcess} method.
57 *
58 * If you plan to use a different upload model, apart from what is mentioned
59 * above, you'll also have to override the
60 * {@link mw.Upload.Dialog#getUploadObject getUploadObject} method to
61 * return the new model. The {@link mw.Upload.Dialog#saveFile saveFile}, and
62 * the {@link mw.Upload.Dialog#uploadFile uploadFile} methods need to be
63 * overriden to use the new model and data returned from the forms.
64 *
65 * @class mw.Upload.Dialog
66 * @uses mw.Upload
67 * @extends OO.ui.ProcessDialog
68 */
69 mw.Upload.Dialog = function ( config ) {
70 // Parent constructor
71 mw.Upload.Dialog.parent.call( this, config );
72 };
73
74 /* Setup */
75
76 OO.inheritClass( mw.Upload.Dialog, OO.ui.ProcessDialog );
77
78 /* Static Properties */
79
80 /**
81 * @inheritdoc
82 * @property title
83 */
84 /*jshint -W024*/
85 mw.Upload.Dialog.static.title = mw.msg( 'upload-dialog-title' );
86
87 /**
88 * @inheritdoc
89 * @property actions
90 */
91 mw.Upload.Dialog.static.actions = [
92 {
93 flags: 'safe',
94 action: 'cancel',
95 label: mw.msg( 'upload-dialog-button-cancel' ),
96 modes: [ 'upload', 'insert', 'save' ]
97 },
98 {
99 flags: [ 'primary', 'progressive' ],
100 label: mw.msg( 'upload-dialog-button-done' ),
101 action: 'insert',
102 modes: 'insert'
103 },
104 {
105 flags: [ 'primary', 'constructive' ],
106 label: mw.msg( 'upload-dialog-button-save' ),
107 action: 'save',
108 modes: 'save'
109 },
110 {
111 flags: [ 'primary', 'progressive' ],
112 label: mw.msg( 'upload-dialog-button-upload' ),
113 action: 'upload',
114 modes: 'upload'
115 }
116 ];
117 /*jshint +W024*/
118
119 /* Properties */
120
121 /**
122 * @property {OO.ui.FormLayout} uploadForm
123 * The form rendered in the first step to get the file object.
124 * Rendered in {@link mw.Upload.Dialog#renderUploadForm renderUploadForm}.
125 */
126
127 /**
128 * @property {OO.ui.FormLayout} infoForm
129 * The form rendered in the second step to get metadata.
130 * Rendered in {@link mw.Upload.Dialog#renderInfoForm renderInfoForm}
131 */
132
133 /**
134 * @property {OO.ui.FormLayout} insertForm
135 * The form rendered in the third step to show usage
136 * Rendered in {@link mw.Upload.Dialog#renderInsertForm renderInsertForm}
137 */
138
139 /* Events */
140
141 /**
142 * A `fileUploaded` event is emitted from the
143 * {@link mw.Upload.Dialog#uploadFile uploadFile} method.
144 *
145 * @event fileUploaded
146 */
147
148 /**
149 * A `fileSaved` event is emitted from the
150 * {@link mw.Upload.Dialog#saveFile saveFile} method.
151 *
152 * @event fileSaved
153 */
154
155 /* Methods */
156
157 /**
158 * @inheritdoc
159 */
160 mw.Upload.Dialog.prototype.initialize = function () {
161 mw.Upload.Dialog.parent.prototype.initialize.call( this );
162
163 this.renderUploadForm();
164 this.renderInfoForm();
165 this.renderInsertForm();
166
167 this.uploadFormPanel = new OO.ui.PanelLayout( {
168 scrollable: true,
169 padded: true,
170 content: [ this.uploadForm ]
171 } );
172 this.infoFormPanel = new OO.ui.PanelLayout( {
173 scrollable: true,
174 padded: true,
175 content: [ this.infoForm ]
176 } );
177 this.insertFormPanel = new OO.ui.PanelLayout( {
178 scrollable: true,
179 padded: true,
180 content: [ this.insertForm ]
181 } );
182
183 this.panels = new OO.ui.StackLayout();
184 this.panels.addItems( [
185 this.uploadFormPanel,
186 this.infoFormPanel,
187 this.insertFormPanel
188 ] );
189
190 this.$body.append( this.panels.$element );
191 };
192
193 /**
194 * @inheritdoc
195 */
196 mw.Upload.Dialog.prototype.getBodyHeight = function () {
197 return 300;
198 };
199
200 /**
201 * Switch between the panels.
202 *
203 * @param {string} panel Panel name: 'upload', 'info', 'insert'
204 */
205 mw.Upload.Dialog.prototype.switchPanels = function ( panel ) {
206 switch ( panel ) {
207 case 'upload':
208 this.panels.setItem( this.uploadFormPanel );
209 this.actions.setMode( 'upload' );
210 break;
211 case 'info':
212 this.panels.setItem( this.infoFormPanel );
213 this.actions.setMode( 'save' );
214 break;
215 case 'insert':
216 this.panels.setItem( this.insertFormPanel );
217 this.actions.setMode( 'insert' );
218 break;
219 }
220 };
221
222 /**
223 * @inheritdoc
224 */
225 mw.Upload.Dialog.prototype.getSetupProcess = function ( data ) {
226 return mw.Upload.Dialog.parent.prototype.getSetupProcess.call( this, data )
227 .next( function () {
228 this.upload = this.getUploadObject();
229 this.switchPanels( 'upload' );
230 this.actions.setAbilities( { upload: false } );
231 }, this );
232 };
233
234 /**
235 * @inheritdoc
236 */
237 mw.Upload.Dialog.prototype.getActionProcess = function ( action ) {
238 var dialog = this;
239
240 if ( action === 'upload' ) {
241 return new OO.ui.Process( function () {
242 dialog.filenameWidget.setValue( dialog.getFile().name );
243 dialog.switchPanels( 'info' );
244 dialog.actions.setAbilities( { save: false } );
245 return dialog.uploadFile();
246 } );
247 }
248 if ( action === 'save' ) {
249 return new OO.ui.Process( dialog.saveFile() );
250 }
251 if ( action === 'insert' ) {
252 return new OO.ui.Process( function () {
253 dialog.close( dialog.upload );
254 } );
255 }
256 if ( action === 'cancel' ) {
257 return new OO.ui.Process( dialog.close() );
258 }
259
260 return mw.Upload.Dialog.parent.prototype.getActionProcess.call( this, action );
261 };
262
263 /**
264 * @inheritdoc
265 */
266 mw.Upload.Dialog.prototype.getTeardownProcess = function ( data ) {
267 return mw.Upload.Dialog.parent.prototype.getTeardownProcess.call( this, data )
268 .next( function () {
269 // Clear the values of all fields
270 this.selectFileWidget.setValue( null );
271 this.filenameWidget.setValue( null ).setValidityFlag( true );
272 this.descriptionWidget.setValue( null ).setValidityFlag( true );
273 this.filenameUsageWidget.setValue( null );
274 }, this );
275 };
276
277 /* Uploading */
278
279 /**
280 * Get the upload model object required for this dialog. Can be
281 * extended to different models.
282 *
283 * @return {mw.Upload}
284 */
285 mw.Upload.Dialog.prototype.getUploadObject = function () {
286 return new mw.Upload();
287 };
288
289 /**
290 * Uploads the file that was added in the upload form. Uses
291 * {@link mw.Upload.Dialog#getFile getFile} to get the HTML5
292 * file object.
293 *
294 * @protected
295 * @fires fileUploaded
296 * @return {jQuery.Promise}
297 */
298 mw.Upload.Dialog.prototype.uploadFile = function () {
299 var dialog = this,
300 file = this.getFile();
301 this.upload.setFile( file );
302 this.uploadPromise = this.upload.uploadToStash();
303 this.uploadPromise.then( function () {
304 dialog.emit( 'fileUploaded' );
305 } );
306
307 return this.uploadPromise;
308 };
309
310 /**
311 * Saves the stash finalizes upload. Uses
312 * {@link mw.Upload.Dialog#getFilename getFilename}, and
313 * {@link mw.Upload.Dialog#getText getText} to get details from
314 * the form.
315 *
316 * @protected
317 * @fires fileSaved
318 * @returns {jQuery.Promise} Rejects the promise with an
319 * {@link OO.ui.Error error}, or resolves if the upload was successful.
320 */
321 mw.Upload.Dialog.prototype.saveFile = function () {
322 var dialog = this,
323 promise = $.Deferred();
324
325 this.upload.setFilename( this.getFilename() );
326 this.upload.setText( this.getText() );
327
328 this.uploadPromise.always( function () {
329
330 if ( dialog.upload.getState() === mw.Upload.State.ERROR ) {
331 promise.reject( new OO.ui.Error( mw.msg( 'upload-dialog-error' ) ) );
332 return false;
333 }
334
335 if ( dialog.upload.getState() === mw.Upload.State.WARNING ) {
336 promise.reject( new OO.ui.Error( mw.msg( 'upload-dialog-error' ) ) );
337 return false;
338 }
339
340 dialog.upload.finishStashUpload().always( function () {
341 var name;
342
343 if ( dialog.upload.getState() === mw.Upload.State.ERROR ) {
344 promise.reject( new OO.ui.Error( mw.msg( 'upload-dialog-error' ) ) );
345 return false;
346 }
347
348 if ( dialog.upload.getState() === mw.Upload.State.WARNING ) {
349 promise.reject( new OO.ui.Error( mw.msg( 'upload-dialog-warning' ) ) );
350 return false;
351 }
352
353 // Normalize page name and localise the 'File:' prefix
354 name = new mw.Title( 'File:' + dialog.upload.getFilename() ).toString();
355 dialog.filenameUsageWidget.setValue( '[[' + name + ']]' );
356 dialog.switchPanels( 'insert' );
357
358 promise.resolve();
359 dialog.emit( 'fileSaved' );
360 } );
361 } );
362
363 return promise.promise();
364 };
365
366 /* Form renderers */
367
368 /**
369 * Renders and returns the upload form and sets the
370 * {@link mw.Upload.Dialog#uploadForm uploadForm} property.
371 * Validates the form and
372 * {@link OO.ui.ActionSet#setAbilities sets abilities}
373 * for the dialog accordingly.
374 *
375 * @protected
376 * @returns {OO.ui.FormLayout}
377 */
378 mw.Upload.Dialog.prototype.renderUploadForm = function () {
379 var fieldset,
380 dialog = this;
381
382 this.selectFileWidget = new OO.ui.SelectFileWidget();
383 fieldset = new OO.ui.FieldsetLayout( { label: mw.msg( 'upload-dialog-label-select-file' ) } );
384 fieldset.addItems( [ this.selectFileWidget ] );
385 this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
386
387 // Validation
388 this.selectFileWidget.on( 'change', function ( value ) {
389 dialog.actions.setAbilities( { upload: !!value } );
390 } );
391
392 return this.uploadForm;
393 };
394
395 /**
396 * Renders and returns the information form for collecting
397 * metadata and sets the {@link mw.Upload.Dialog#infoForm infoForm}
398 * property.
399 * Validates the form and
400 * {@link OO.ui.ActionSet#setAbilities sets abilities}
401 * for the dialog accordingly.
402 *
403 * @protected
404 * @returns {OO.ui.FormLayout}
405 */
406 mw.Upload.Dialog.prototype.renderInfoForm = function () {
407 var fieldset,
408 dialog = this;
409
410 this.filenameWidget = new OO.ui.TextInputWidget( {
411 indicator: 'required',
412 required: true,
413 validate: /.+/
414 } );
415 this.descriptionWidget = new OO.ui.TextInputWidget( {
416 indicator: 'required',
417 required: true,
418 validate: /.+/,
419 multiline: true,
420 autosize: true
421 } );
422
423 fieldset = new OO.ui.FieldsetLayout( {
424 label: mw.msg( 'upload-dialog-label-infoform-title' )
425 } );
426 fieldset.addItems( [
427 new OO.ui.FieldLayout( this.filenameWidget, {
428 label: mw.msg( 'upload-dialog-label-infoform-name' ),
429 align: 'top'
430 } ),
431 new OO.ui.FieldLayout( this.descriptionWidget, {
432 label: mw.msg( 'upload-dialog-label-infoform-description' ),
433 align: 'top'
434 } )
435 ] );
436 this.infoForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
437
438 // Validation
439 function checkValidity() {
440 $.when(
441 dialog.filenameWidget.getValidity(),
442 dialog.descriptionWidget.getValidity()
443 ).done( function () {
444 dialog.actions.setAbilities( { save: true } );
445 } ).fail( function () {
446 dialog.actions.setAbilities( { save: false } );
447 } );
448 }
449 this.filenameWidget.on( 'change', checkValidity );
450 this.descriptionWidget.on( 'change', checkValidity );
451
452 return this.infoForm;
453 };
454
455 /**
456 * Renders and returns the insert form to show file usage and
457 * sets the {@link mw.Upload.Dialog#insertForm insertForm} property.
458 *
459 * @protected
460 * @returns {OO.ui.FormLayout}
461 */
462 mw.Upload.Dialog.prototype.renderInsertForm = function () {
463 var fieldset;
464
465 this.filenameUsageWidget = new OO.ui.TextInputWidget();
466 fieldset = new OO.ui.FieldsetLayout( {
467 label: mw.msg( 'upload-dialog-label-usage-title' )
468 } );
469 fieldset.addItems( [
470 new OO.ui.FieldLayout( this.filenameUsageWidget, {
471 label: mw.msg( 'upload-dialog-label-usage-filename' ),
472 align: 'top'
473 } )
474 ] );
475 this.insertForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
476
477 return this.insertForm;
478 };
479
480 /* Getters */
481
482 /**
483 * Gets the file object from the
484 * {@link mw.Upload.Dialog#uploadForm upload form}.
485 *
486 * @protected
487 * @returns {File|null}
488 */
489 mw.Upload.Dialog.prototype.getFile = function () {
490 return this.selectFileWidget.getValue();
491 };
492
493 /**
494 * Gets the file name from the
495 * {@link mw.Upload.Dialog#infoForm information form}.
496 *
497 * @protected
498 * @returns {string}
499 */
500 mw.Upload.Dialog.prototype.getFilename = function () {
501 return this.filenameWidget.getValue();
502 };
503
504 /**
505 * Gets the page text from the
506 * {@link mw.Upload.Dialog#infoForm information form}.
507 *
508 * @protected
509 * @returns {string}
510 */
511 mw.Upload.Dialog.prototype.getText = function () {
512 return this.descriptionWidget.getValue();
513 };
514 }( jQuery, mediaWiki ) );