Merge "Whitelist the <wbr> element."
[mediawiki.git] / resources / mediawiki.special / mediawiki.special.upload.js
blob3f40c549663236e8ac14a03f42a46fe41d0a8935
1 /**
2  * JavaScript for Special:Upload
3  * Note that additional code still lives in skins/common/upload.js
4  */
5 ( function ( mw, $ ) {
6         /**
7          * Add a preview to the upload form
8          */
9         $( function () {
10                 /**
11                  * Is the FileAPI available with sufficient functionality?
12                  */
13                 function hasFileAPI() {
14                         return window.FileReader !== undefined;
15                 }
17                 /**
18                  * Check if this is a recognizable image type...
19                  * Also excludes files over 10M to avoid going insane on memory usage.
20                  *
21                  * @todo is there a way we can ask the browser what's supported in <img>s?
22                  * @todo put SVG back after working around Firefox 7 bug <https://bugzilla.wikimedia.org/show_bug.cgi?id=31643>
23                  *
24                  * @param {File} file
25                  * @return boolean
26                  */
27                 function fileIsPreviewable( file ) {
28                         var known = ['image/png', 'image/gif', 'image/jpeg', 'image/svg+xml'],
29                                 tooHuge = 10 * 1024 * 1024;
30                         return ( $.inArray( file.type, known ) !== -1 ) && file.size > 0 && file.size < tooHuge;
31                 }
33                 /**
34                  * Show a thumbnail preview of PNG, JPEG, GIF, and SVG files prior to upload
35                  * in browsers supporting HTML5 FileAPI.
36                  *
37                  * As of this writing, known good:
38                  * - Firefox 3.6+
39                  * - Chrome 7.something
40                  *
41                  * @todo check file size limits and warn of likely failures
42                  *
43                  * @param {File} file
44                  */
45                 function showPreview( file ) {
46                         var $canvas,
47                                 ctx,
48                                 meta,
49                                 previewSize = 180,
50                                 thumb = $( '<div id="mw-upload-thumbnail" class="thumb tright">' +
51                                                         '<div class="thumbinner">' +
52                                                                 '<div class="mw-small-spinner" style="width: 180px; height: 180px"></div>' +
53                                                                 '<div class="thumbcaption"><div class="filename"></div><div class="fileinfo"></div></div>' +
54                                                         '</div>' +
55                                                 '</div>' );
57                         thumb.find( '.filename' ).text( file.name ).end()
58                                 .find( '.fileinfo' ).text( prettySize( file.size ) ).end();
60                         $canvas = $('<canvas width="' + previewSize + '" height="' + previewSize + '" ></canvas>');
61                         ctx = $canvas[0].getContext( '2d' );
62                         $( '#mw-htmlform-source' ).parent().prepend( thumb );
64                         fetchPreview( file, function ( dataURL ) {
65                                 var img = new Image(),
66                                         rotation = 0;
68                                 if ( meta && meta.tiff && meta.tiff.Orientation ) {
69                                         rotation = ( 360 - ( function () {
70                                                 // See includes/media/Bitmap.php
71                                                 switch ( meta.tiff.Orientation.value ) {
72                                                         case 8:
73                                                                 return 90;
74                                                         case 3:
75                                                                 return 180;
76                                                         case 6:
77                                                                 return 270;
78                                                         default:
79                                                                 return 0;
80                                                 }
81                                         }() ) ) % 360;
82                                 }
84                                 img.onload = function () {
85                                         var info, width, height, x, y, dx, dy, logicalWidth, logicalHeight;
87                                         // Fit the image within the previewSizexpreviewSize box
88                                         if ( img.width > img.height ) {
89                                                 width = previewSize;
90                                                 height = img.height / img.width * previewSize;
91                                         } else {
92                                                 height = previewSize;
93                                                 width = img.width / img.height * previewSize;
94                                         }
95                                         // Determine the offset required to center the image
96                                         dx = (180 - width) / 2;
97                                         dy = (180 - height) / 2;
98                                         switch ( rotation ) {
99                                                 // If a rotation is applied, the direction of the axis
100                                                 // changes as well. You can derive the values below by
101                                                 // drawing on paper an axis system, rotate it and see
102                                                 // where the positive axis direction is
103                                                 case 0:
104                                                         x = dx;
105                                                         y = dy;
106                                                         logicalWidth = img.width;
107                                                         logicalHeight = img.height;
108                                                         break;
109                                                 case 90:
111                                                         x = dx;
112                                                         y = dy - previewSize;
113                                                         logicalWidth = img.height;
114                                                         logicalHeight = img.width;
115                                                         break;
116                                                 case 180:
117                                                         x = dx - previewSize;
118                                                         y = dy - previewSize;
119                                                         logicalWidth = img.width;
120                                                         logicalHeight = img.height;
121                                                         break;
122                                                 case 270:
123                                                         x = dx - previewSize;
124                                                         y = dy;
125                                                         logicalWidth = img.height;
126                                                         logicalHeight = img.width;
127                                                         break;
128                                         }
130                                         ctx.clearRect( 0, 0, 180, 180 );
131                                         ctx.rotate( rotation / 180 * Math.PI );
132                                         ctx.drawImage( img, x, y, width, height );
133                                         thumb.find('.mw-small-spinner').replaceWith($canvas);
135                                         // Image size
136                                         info = mw.msg( 'widthheight', logicalWidth, logicalHeight ) +
137                                                 ', ' + prettySize( file.size );
139                                         $( '#mw-upload-thumbnail .fileinfo' ).text( info );
140                                 };
141                                 img.src = dataURL;
142                         }, mw.config.get( 'wgFileCanRotate' ) ? function ( data ) {
143                                 /*jshint camelcase: false, nomen: false */
144                                 try {
145                                         meta = mw.libs.jpegmeta( data, file.fileName );
146                                         meta._binary_data = null;
147                                 } catch ( e ) {
148                                         meta = null;
149                                 }
150                         } : null );
151                 }
153                 /**
154                  * Start loading a file into memory; when complete, pass it as a
155                  * data URL to the callback function. If the callbackBinary is set it will
156                  * first be read as binary and afterwards as data URL. Useful if you want
157                  * to do preprocessing on the binary data first.
158                  *
159                  * @param {File} file
160                  * @param {function} callback
161                  * @param {function} callbackBinary
162                  */
163                 function fetchPreview( file, callback, callbackBinary ) {
164                         var reader = new FileReader();
165                         if ( callbackBinary && 'readAsBinaryString' in reader ) {
166                                 // To fetch JPEG metadata we need a binary string; start there.
167                                 // todo:
168                                 reader.onload = function() {
169                                         callbackBinary( reader.result );
171                                         // Now run back through the regular code path.
172                                         fetchPreview( file, callback );
173                                 };
174                                 reader.readAsBinaryString( file );
175                         } else if ( callbackBinary && 'readAsArrayBuffer' in reader ) {
176                                 // readAsArrayBuffer replaces readAsBinaryString
177                                 // However, our JPEG metadata library wants a string.
178                                 // So, this is going to be an ugly conversion.
179                                 reader.onload = function() {
180                                         var i,
181                                                 buffer = new Uint8Array( reader.result ),
182                                                 string = '';
183                                         for ( i = 0; i < buffer.byteLength; i++ ) {
184                                                 string += String.fromCharCode( buffer[i] );
185                                         }
186                                         callbackBinary( string );
188                                         // Now run back through the regular code path.
189                                         fetchPreview( file, callback );
190                                 };
191                                 reader.readAsArrayBuffer( file );
192                         } else if ( 'URL' in window && 'createObjectURL' in window.URL ) {
193                                 // Supported in Firefox 4.0 and above <https://developer.mozilla.org/en/DOM/window.URL.createObjectURL>
194                                 // WebKit has it in a namespace for now but that's ok. ;)
195                                 //
196                                 // Lifetime of this URL is until document close, which is fine
197                                 // for Special:Upload -- if this code gets used on longer-running
198                                 // pages, add a revokeObjectURL() when it's no longer needed.
199                                 //
200                                 // Prefer this over readAsDataURL for Firefox 7 due to bug reading
201                                 // some SVG files from data URIs <https://bugzilla.mozilla.org/show_bug.cgi?id=694165>
202                                 callback( window.URL.createObjectURL( file ) );
203                         } else {
204                                 // This ends up decoding the file to base-64 and back again, which
205                                 // feels horribly inefficient.
206                                 reader.onload = function () {
207                                         callback( reader.result );
208                                 };
209                                 reader.readAsDataURL( file );
210                         }
211                 }
213                 /**
214                  * Format a file size attractively.
215                  * @todo match numeric formatting
216                  *
217                  * @param {number} s
218                  * @return string
219                  */
220                 function prettySize( s ) {
221                         var sizeMsgs = ['size-bytes', 'size-kilobytes', 'size-megabytes', 'size-gigabytes'];
222                         while ( s >= 1024 && sizeMsgs.length > 1 ) {
223                                 s /= 1024;
224                                 sizeMsgs = sizeMsgs.slice( 1 );
225                         }
226                         return mw.msg( sizeMsgs[0], Math.round( s ) );
227                 }
229                 /**
230                  * Clear the file upload preview area.
231                  */
232                 function clearPreview() {
233                         $( '#mw-upload-thumbnail' ).remove();
234                 }
236                 /**
237                  * Check if the file does not exceed the maximum size
238                  */
239                 function checkMaxUploadSize( file ) {
240                         var maxSize, $error;
242                         function getMaxUploadSize( type ) {
243                                 var sizes = mw.config.get( 'wgMaxUploadSize' );
245                                 if ( sizes[type] !== undefined ) {
246                                         return sizes[type];
247                                 }
248                                 return sizes['*'];
249                         }
251                         $( '.mw-upload-source-error' ).remove();
253                         maxSize = getMaxUploadSize( 'file' );
254                         if ( file.size > maxSize ) {
255                                 $error = $( '<p class="error mw-upload-source-error" id="wpSourceTypeFile-error">' +
256                                         mw.message( 'largefileserver', file.size, maxSize ).escaped() + '</p>' );
258                                 $( '#wpUploadFile' ).after( $error );
260                                 return false;
261                         }
263                         return true;
264                 }
267                 /**
268                  * Initialization
269                  */
270                 if ( hasFileAPI() ) {
271                         // Update thumbnail when the file selection control is updated.
272                         $( '#wpUploadFile' ).change( function () {
273                                 clearPreview();
274                                 if ( this.files && this.files.length ) {
275                                         // Note: would need to be updated to handle multiple files.
276                                         var file = this.files[0];
278                                         if ( !checkMaxUploadSize( file ) ) {
279                                                 return;
280                                         }
282                                         if ( fileIsPreviewable( file ) ) {
283                                                 showPreview( file );
284                                         }
285                                 }
286                         } );
287                 }
288         } );
290         /**
291          * Disable all upload source fields except the selected one
292          */
293         $( function () {
294                 var i, $row,
295                         $rows = $( '.mw-htmlform-field-UploadSourceField' );
297                 function createHandler( $currentRow ) {
298                         /**
299                          * @param {jQuery.Event}
300                          */
301                         return function () {
302                                 $( '.mw-upload-source-error' ).remove();
303                                 if ( this.checked ) {
304                                         // Disable all inputs
305                                         $rows.find( 'input[name!="wpSourceType"]' ).prop( 'disabled', true );
306                                         // Re-enable the current one
307                                         $currentRow.find( 'input' ).prop( 'disabled', false );
308                                 }
309                         };
310                 }
312                 for ( i = $rows.length; i; i-- ) {
313                         $row = $rows.eq(i - 1);
314                         $row
315                                 .find( 'input[name="wpSourceType"]' )
316                                 .change( createHandler( $row ) );
317                 }
318         } );
320 }( mediaWiki, jQuery ) );