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