Merge "mediawiki.jqueryMsg: Implement `<nowiki>` support"
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.ForeignStructuredUpload.BookletLayout.js
1 /*global moment */
2 ( function ( $, mw ) {
3
4 /**
5 * mw.ForeignStructuredUpload.BookletLayout encapsulates the process
6 * of uploading a file to MediaWiki using the mw.ForeignStructuredUpload model.
7 *
8 * var uploadDialog = new mw.Upload.Dialog( {
9 * bookletClass: mw.ForeignStructuredUpload.BookletLayout,
10 * booklet: {
11 * target: 'local'
12 * }
13 * } );
14 * var windowManager = new OO.ui.WindowManager();
15 * $( 'body' ).append( windowManager.$element );
16 * windowManager.addWindows( [ uploadDialog ] );
17 *
18 * @class mw.ForeignStructuredUpload.BookletLayout
19 * @uses mw.ForeignStructuredUpload
20 * @extends mw.Upload.BookletLayout
21 * @cfg {string} [target] Used to choose the target repository.
22 * If nothing is passed, the {@link mw.ForeignUpload#property-target default} is used.
23 */
24 mw.ForeignStructuredUpload.BookletLayout = function ( config ) {
25 config = config || {};
26 // Parent constructor
27 mw.ForeignStructuredUpload.BookletLayout.parent.call( this, config );
28
29 this.target = config.target;
30 };
31
32 /* Setup */
33
34 OO.inheritClass( mw.ForeignStructuredUpload.BookletLayout, mw.Upload.BookletLayout );
35
36 /* Uploading */
37
38 /**
39 * @inheritdoc
40 */
41 mw.ForeignStructuredUpload.BookletLayout.prototype.initialize = function () {
42 var booklet = this;
43 return mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this ).then(
44 function () {
45 return $.when(
46 // Point the CategorySelector to the right wiki
47 booklet.upload.getApi().then( function ( api ) {
48 // If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything
49 if ( api.apiUrl ) {
50 // Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance
51 booklet.categoriesWidget.api = new mw.ForeignApi( api.apiUrl );
52 }
53 return $.Deferred().resolve();
54 } ),
55 // Set up booklet fields and license messages to match configuration
56 booklet.upload.loadConfig().then( function ( config ) {
57 var
58 msgPromise,
59 isLocal = booklet.upload.target === 'local',
60 fields = config.fields,
61 msgs = config.licensemessages[ isLocal ? 'local' : 'foreign' ];
62
63 // Hide disabled fields
64 booklet.descriptionField.toggle( !!fields.description );
65 booklet.categoriesField.toggle( !!fields.categories );
66 booklet.dateField.toggle( !!fields.date );
67 // Update form validity
68 booklet.onInfoFormChange();
69
70 // Load license messages from the remote wiki if we don't have these messages locally
71 // (this means that we only load messages from the foreign wiki for custom config)
72 if ( mw.message( 'upload-form-label-own-work-message-' + msgs ).exists() ) {
73 msgPromise = $.Deferred().resolve();
74 } else {
75 msgPromise = booklet.upload.apiPromise.then( function ( api ) {
76 return api.loadMessages( [
77 'upload-form-label-own-work-message-' + msgs,
78 'upload-form-label-not-own-work-message-' + msgs,
79 'upload-form-label-not-own-work-local-' + msgs
80 ] );
81 } );
82 }
83
84 // Update license messages
85 return msgPromise.then( function () {
86 booklet.$ownWorkMessage
87 .msg( 'upload-form-label-own-work-message-' + msgs )
88 .find( 'a' ).attr( 'target', '_blank' );
89 booklet.$notOwnWorkMessage
90 .msg( 'upload-form-label-not-own-work-message-' + msgs )
91 .find( 'a' ).attr( 'target', '_blank' );
92 booklet.$notOwnWorkLocal
93 .msg( 'upload-form-label-not-own-work-local-' + msgs )
94 .find( 'a' ).attr( 'target', '_blank' );
95 } );
96 } )
97 );
98 }
99 ).then(
100 null,
101 // Always resolve, never reject
102 function () { return $.Deferred().resolve(); }
103 );
104 };
105
106 /**
107 * Returns a {@link mw.ForeignStructuredUpload mw.ForeignStructuredUpload}
108 * with the {@link #cfg-target target} specified in config.
109 *
110 * @protected
111 * @return {mw.Upload}
112 */
113 mw.ForeignStructuredUpload.BookletLayout.prototype.createUpload = function () {
114 return new mw.ForeignStructuredUpload( this.target );
115 };
116
117 /* Form renderers */
118
119 /**
120 * @inheritdoc
121 */
122 mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm = function () {
123 var fieldset,
124 layout = this;
125
126 // These elements are filled with text in #initialize
127 // TODO Refactor this to be in one place
128 this.$ownWorkMessage = $( '<p>' )
129 .addClass( 'mw-foreignStructuredUpload-bookletLayout-license' );
130 this.$notOwnWorkMessage = $( '<p>' );
131 this.$notOwnWorkLocal = $( '<p>' );
132
133 this.selectFileWidget = new OO.ui.SelectFileWidget( {
134 showDropTarget: true
135 } );
136 this.messageLabel = new OO.ui.LabelWidget( {
137 label: $( '<div>' ).append(
138 this.$notOwnWorkMessage,
139 this.$notOwnWorkLocal
140 )
141 } );
142 this.ownWorkCheckbox = new OO.ui.CheckboxInputWidget().on( 'change', function ( on ) {
143 layout.messageLabel.toggle( !on );
144 } );
145
146 fieldset = new OO.ui.FieldsetLayout();
147 fieldset.addItems( [
148 new OO.ui.FieldLayout( this.selectFileWidget, {
149 align: 'top'
150 } ),
151 new OO.ui.FieldLayout( this.ownWorkCheckbox, {
152 align: 'inline',
153 label: $( '<div>' ).append(
154 $( '<p>' ).text( mw.msg( 'upload-form-label-own-work' ) ),
155 this.$ownWorkMessage
156 )
157 } ),
158 new OO.ui.FieldLayout( this.messageLabel, {
159 align: 'top'
160 } )
161 ] );
162 this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
163
164 // Validation
165 this.selectFileWidget.on( 'change', this.onUploadFormChange.bind( this ) );
166 this.ownWorkCheckbox.on( 'change', this.onUploadFormChange.bind( this ) );
167
168 this.selectFileWidget.on( 'change', function () {
169 var file = layout.getFile();
170
171 // Set the date to lastModified once we have the file
172 if ( layout.getDateFromLastModified( file ) !== undefined ) {
173 layout.dateWidget.setValue( layout.getDateFromLastModified( file ) );
174 }
175
176 // Check if we have EXIF data and set to that where available
177 layout.getDateFromExif( file ).done( function ( date ) {
178 layout.dateWidget.setValue( date );
179 } );
180
181 layout.updateFilePreview();
182 } );
183
184 return this.uploadForm;
185 };
186
187 /**
188 * @inheritdoc
189 */
190 mw.ForeignStructuredUpload.BookletLayout.prototype.onUploadFormChange = function () {
191 var file = this.selectFileWidget.getValue(),
192 ownWork = this.ownWorkCheckbox.isSelected(),
193 valid = !!file && ownWork;
194 this.emit( 'uploadValid', valid );
195 };
196
197 /**
198 * @inheritdoc
199 */
200 mw.ForeignStructuredUpload.BookletLayout.prototype.renderInfoForm = function () {
201 var fieldset;
202
203 this.filePreview = new OO.ui.Widget( {
204 classes: [ 'mw-upload-bookletLayout-filePreview' ]
205 } );
206 this.progressBarWidget = new OO.ui.ProgressBarWidget( {
207 progress: 0
208 } );
209 this.filePreview.$element.append( this.progressBarWidget.$element );
210
211 this.filenameWidget = new OO.ui.TextInputWidget( {
212 required: true,
213 validate: /.+/
214 } );
215 this.descriptionWidget = new OO.ui.TextInputWidget( {
216 required: true,
217 validate: /\S+/,
218 multiline: true,
219 autosize: true
220 } );
221 this.categoriesWidget = new mw.widgets.CategorySelector( {
222 // Can't be done here because we don't know the target wiki yet... done in #initialize.
223 // api: new mw.ForeignApi( ... ),
224 $overlay: this.$overlay
225 } );
226 this.dateWidget = new mw.widgets.DateInputWidget( {
227 $overlay: this.$overlay,
228 required: true,
229 mustBeBefore: moment().add( 1, 'day' ).locale( 'en' ).format( 'YYYY-MM-DD' ) // Tomorrow
230 } );
231
232 this.filenameField = new OO.ui.FieldLayout( this.filenameWidget, {
233 label: mw.msg( 'upload-form-label-infoform-name' ),
234 align: 'top',
235 classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ],
236 notices: [ mw.msg( 'upload-form-label-infoform-name-tooltip' ) ]
237 } );
238 this.descriptionField = new OO.ui.FieldLayout( this.descriptionWidget, {
239 label: mw.msg( 'upload-form-label-infoform-description' ),
240 align: 'top',
241 classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ],
242 notices: [ mw.msg( 'upload-form-label-infoform-description-tooltip' ) ]
243 } );
244 this.categoriesField = new OO.ui.FieldLayout( this.categoriesWidget, {
245 label: mw.msg( 'upload-form-label-infoform-categories' ),
246 align: 'top'
247 } );
248 this.dateField = new OO.ui.FieldLayout( this.dateWidget, {
249 label: mw.msg( 'upload-form-label-infoform-date' ),
250 align: 'top'
251 } );
252
253 fieldset = new OO.ui.FieldsetLayout( {
254 label: mw.msg( 'upload-form-label-infoform-title' )
255 } );
256 fieldset.addItems( [
257 this.filenameField,
258 this.descriptionField,
259 this.categoriesField,
260 this.dateField
261 ] );
262 this.infoForm = new OO.ui.FormLayout( {
263 classes: [ 'mw-upload-bookletLayout-infoForm' ],
264 items: [ this.filePreview, fieldset ]
265 } );
266
267 // Validation
268 this.filenameWidget.on( 'change', this.onInfoFormChange.bind( this ) );
269 this.descriptionWidget.on( 'change', this.onInfoFormChange.bind( this ) );
270 this.dateWidget.on( 'change', this.onInfoFormChange.bind( this ) );
271
272 this.on( 'fileUploadProgress', function ( progress ) {
273 this.progressBarWidget.setProgress( progress * 100 );
274 }.bind( this ) );
275
276 return this.infoForm;
277 };
278
279 /**
280 * @inheritdoc
281 */
282 mw.ForeignStructuredUpload.BookletLayout.prototype.onInfoFormChange = function () {
283 var layout = this,
284 validityPromises = [];
285
286 validityPromises.push( this.filenameWidget.getValidity() );
287 if ( this.descriptionField.isVisible() ) {
288 validityPromises.push( this.descriptionWidget.getValidity() );
289 }
290 if ( this.dateField.isVisible() ) {
291 validityPromises.push( this.dateWidget.getValidity() );
292 }
293
294 $.when.apply( $, validityPromises ).done( function () {
295 layout.emit( 'infoValid', true );
296 } ).fail( function () {
297 layout.emit( 'infoValid', false );
298 } );
299 };
300
301 /* Getters */
302
303 /**
304 * @inheritdoc
305 */
306 mw.ForeignStructuredUpload.BookletLayout.prototype.getText = function () {
307 var language = mw.config.get( 'wgContentLanguage' );
308 this.upload.clearDescriptions();
309 this.upload.addDescription( language, this.descriptionWidget.getValue() );
310 this.upload.setDate( this.dateWidget.getValue() );
311 this.upload.clearCategories();
312 this.upload.addCategories( this.categoriesWidget.getItemsData() );
313 return this.upload.getText();
314 };
315
316 /**
317 * Get original date from EXIF data
318 *
319 * @param {Object} file
320 * @return {jQuery.Promise} Promise resolved with the EXIF date
321 */
322 mw.ForeignStructuredUpload.BookletLayout.prototype.getDateFromExif = function ( file ) {
323 var fileReader,
324 deferred = $.Deferred();
325
326 if ( file && file.type === 'image/jpeg' ) {
327 fileReader = new FileReader();
328 fileReader.onload = function () {
329 var fileStr, arr, i, metadata;
330
331 if ( typeof fileReader.result === 'string' ) {
332 fileStr = fileReader.result;
333 } else {
334 // Array buffer; convert to binary string for the library.
335 arr = new Uint8Array( fileReader.result );
336 fileStr = '';
337 for ( i = 0; i < arr.byteLength; i++ ) {
338 fileStr += String.fromCharCode( arr[ i ] );
339 }
340 }
341
342 try {
343 metadata = mw.libs.jpegmeta( this.result, file.name );
344 } catch ( e ) {
345 metadata = null;
346 }
347
348 if ( metadata !== null && metadata.exif !== undefined && metadata.exif.DateTimeOriginal ) {
349 deferred.resolve( moment( metadata.exif.DateTimeOriginal, 'YYYY:MM:DD' ).format( 'YYYY-MM-DD' ) );
350 } else {
351 deferred.reject();
352 }
353 };
354
355 if ( 'readAsBinaryString' in fileReader ) {
356 fileReader.readAsBinaryString( file );
357 } else if ( 'readAsArrayBuffer' in fileReader ) {
358 fileReader.readAsArrayBuffer( file );
359 } else {
360 // We should never get here
361 deferred.reject();
362 throw new Error( 'Cannot read thumbnail as binary string or array buffer.' );
363 }
364 }
365
366 return deferred.promise();
367 };
368
369 /**
370 * Get last modified date from file
371 *
372 * @param {Object} file
373 * @return {Object} Last modified date from file
374 */
375 mw.ForeignStructuredUpload.BookletLayout.prototype.getDateFromLastModified = function ( file ) {
376 if ( file && file.lastModified ) {
377 return moment( file.lastModified ).format( 'YYYY-MM-DD' );
378 }
379 };
380
381 /* Setters */
382
383 /**
384 * @inheritdoc
385 */
386 mw.ForeignStructuredUpload.BookletLayout.prototype.clear = function () {
387 mw.ForeignStructuredUpload.BookletLayout.parent.prototype.clear.call( this );
388
389 this.ownWorkCheckbox.setSelected( false );
390 this.categoriesWidget.setItemsFromData( [] );
391 this.dateWidget.setValue( '' ).setValidityFlag( true );
392 };
393
394 }( jQuery, mediaWiki ) );