Index: trunk/extensions/UploadWizard/UploadWizard.i18n.php |
— | — | @@ -132,6 +132,7 @@ |
133 | 133 | 'mwe-upwiz-help-allowed-filename-extensions' => 'Allowed filename extensions', |
134 | 134 | 'mwe-upwiz-upload-error-duplicate' => 'This file was previously uploaded to this wiki.', |
135 | 135 | 'mwe-upwiz-upload-error-stashed-anyway' => 'Upload anyway?', |
| 136 | + 'mwe-upwiz-upload-error-unknown-filename-error' => 'We could not read or understand the filename "$1" for unknown reasons.', |
136 | 137 | 'mwe-upwiz-ok' => 'OK', |
137 | 138 | 'mwe-upwiz-cancel' => 'Cancel', |
138 | 139 | 'mwe-upwiz-fileexists-replace' => 'A file with the title "$1" exists already. Please change your title to something unique.', |
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizardUploadInterface.js |
— | — | @@ -14,15 +14,10 @@ |
15 | 15 | _this.div = $j('<div class="mwe-upwiz-file"></div>').get(0); |
16 | 16 | _this.isFilled = false; |
17 | 17 | |
18 | | - _this.$fileInputCtrl = $j('<input size="1" class="mwe-upwiz-file-input" name="file" type="file"/>') |
19 | | - .change( function() { |
20 | | - _this.clearErrors(); |
21 | | - _this.upload.extractLocalFileInfo( |
22 | | - this, // the file input |
23 | | - function() { _this.fileChanged(); } |
24 | | - ); |
25 | | - } ); |
| 18 | + _this.$fileInputCtrl = $j('<input size="1" class="mwe-upwiz-file-input" name="file" type="file"/>'); |
26 | 19 | |
| 20 | + _this.initFileInputCtrl(); |
| 21 | + |
27 | 22 | _this.$indicator = $j( '<div class="mwe-upwiz-file-indicator"></div>' ); |
28 | 23 | |
29 | 24 | visibleFilenameDiv = $j('<div class="mwe-upwiz-visible-file"></div>') |
— | — | @@ -225,61 +220,138 @@ |
226 | 221 | this.setStatus( msgKey, args ); |
227 | 222 | }, |
228 | 223 | |
| 224 | + |
| 225 | + initFileInputCtrl: function() { |
| 226 | + var _this = this; |
| 227 | + this.$fileInputCtrl.change( function() { |
| 228 | + _this.clearErrors(); |
| 229 | + _this.upload.checkFile( |
| 230 | + this, // the file input |
| 231 | + function() { _this.fileChangedOk(); }, |
| 232 | + function( code, info ) { _this.fileChangedError( code, info ); } |
| 233 | + ); |
| 234 | + } ); |
| 235 | + }, |
| 236 | + |
| 237 | + |
229 | 238 | /** |
230 | | - * Run this when the value of the file input has changed. Check the file for various forms of goodness. |
231 | | - * If okay, then update the visible filename (due to CSS trickery the real file input is invisible) |
| 239 | + * Run this when the value of the file input has changed and we know it's acceptable -- this |
| 240 | + * will update interface to show as much info as possible, including preview. |
| 241 | + * n.b. in older browsers we only will know the filename |
232 | 242 | */ |
233 | | - fileChanged: function() { |
| 243 | + fileChangedOk: function() { |
234 | 244 | var _this = this; |
235 | | - var extension = _this.upload.title.getExtension(); |
236 | | - var hasExtension = ! mw.isEmpty( extension ); |
| 245 | + _this.updateFilename(); |
237 | 246 | |
238 | | - /* this should not be in the interface */ |
239 | | - var isGoodExtension = false; |
240 | | - if ( hasExtension ) { |
241 | | - isGoodExtension = $j.inArray( extension.toLowerCase(), mw.UploadWizard.config[ 'fileExtensions' ] ) !== -1; |
| 247 | + // set the status string - e.g. "256 Kb, 100 x 200" |
| 248 | + var statusItems = []; |
| 249 | + if ( this.upload.imageinfo && this.upload.imageinfo.width && this.upload.imageinfo.height ) { |
| 250 | + statusItems.push( this.upload.imageinfo.width + '\u00d7' + this.upload.imageinfo.height ); |
242 | 251 | } |
243 | | - if ( hasExtension && isGoodExtension ) { |
244 | | - _this.updateFilename(); |
245 | | - } else { |
246 | | - var $errorMessage; |
247 | | - // Check if firefogg should be recommended to be installed ( user selects an extension that can be converted) |
248 | | - if( mw.UploadWizard.config['enableFirefogg'] |
249 | | - && |
250 | | - $j.inArray( extension.toLowerCase(), mw.UploadWizard.config['transcodeExtensionList'] ) !== -1 |
251 | | - ){ |
252 | | - $errorMessage = $j( '<p>' ).msg('mwe-upwiz-upload-error-bad-extension-video-firefogg', |
253 | | - mw.Firefogg.getFirefoggInstallUrl(), |
254 | | - 'http://commons.wikimedia.org/wiki/Help:Converting_video' |
255 | | - ); |
256 | | - |
257 | | - } else { |
258 | | - var errorKey = hasExtension ? 'mwe-upwiz-upload-error-bad-filename-extension' : 'mwe-upwiz-upload-error-bad-filename-no-extension'; |
259 | | - $errorMessage = $j( '<p>' ).msg( errorKey, extension ); |
260 | | - } |
261 | | - $( '<div></div>' ) |
262 | | - .append( |
263 | | - $errorMessage, |
264 | | - $j( '<p>' ).msg( 'mwe-upwiz-allowed-filename-extensions' ), |
265 | | - $j( '<blockquote>' ).append( $j( '<tt>' ).append( |
266 | | - mw.UploadWizard.config[ 'fileExtensions' ].join( " " ) |
267 | | - ) ) |
268 | | - ) |
269 | | - .dialog({ |
270 | | - width: 500, |
271 | | - zIndex: 200000, |
272 | | - autoOpen: true, |
273 | | - title: gM( 'mwe-upwiz-help-popup' ) + ': ' + gM( 'mwe-upwiz-help-allowed-filename-extensions' ), |
274 | | - modal: true |
275 | | - }); |
| 252 | + if ( this.upload.file ) { |
| 253 | + statusItems.push( mw.units.bytes( this.upload.file.size ) ); |
276 | 254 | } |
| 255 | + |
| 256 | + this.clearStatus(); |
| 257 | + this.setStatusString( statusItems.join( ' \u00b7 ' ) ); |
277 | 258 | |
278 | | - this.clearStatus(); |
279 | | - if ( this.upload.file ) { |
280 | | - this.setStatusString( mw.units.bytes( this.upload.file.size ) ); |
| 259 | + // do preview if we can |
| 260 | + if ( mw.fileApi.isAvailable() && _this.upload.file && mw.fileApi.isPreviewableFile( _this.upload.file ) ) { |
| 261 | + var dataUrlReader = new FileReader(); |
| 262 | + dataUrlReader.onload = function() { |
| 263 | + var image = document.createElement( 'img' ); |
| 264 | + image.onload = function() { |
| 265 | + $.publishReady( 'thumbnails.' + _this.upload.index, image ); |
| 266 | + }; |
| 267 | + image.src = dataUrlReader.result; |
| 268 | + _this.upload.thumbnails['*'] = image; |
| 269 | + }; |
| 270 | + dataUrlReader.readAsDataURL( _this.upload.file ); |
281 | 271 | } |
| 272 | + |
282 | 273 | }, |
283 | 274 | |
| 275 | + fileChangedError: function( code, info ) { |
| 276 | + var filename = this.$fileInputCtrl.value; |
| 277 | + |
| 278 | + // ok we now have a fileInputCtrl with a "bad" file in it |
| 279 | + // you cannot blank a file input ctrl in all browsers, so we |
| 280 | + // replace existing file input with empty clone |
| 281 | + var $newFileInput = this.$fileInputCtrl.clone(); |
| 282 | + this.$fileInputCtrl.replaceWith( $newFileInput ); |
| 283 | + this.$fileInputCtrl = $newFileInput; |
| 284 | + this.initFileInputCtrl(); |
| 285 | + |
| 286 | + if ( code === 'ext' ) { |
| 287 | + this.showBadExtensionError( filename, info ); |
| 288 | + } else if ( code === 'noext' ) { |
| 289 | + this.showMissingExtensionError( filename ); |
| 290 | + } else if ( code === 'dup' ) { |
| 291 | + this.showDuplicateError( filename ); |
| 292 | + } else if ( code === 'unparseable' ) { |
| 293 | + this.showUnparseableFilenameError( filename ); |
| 294 | + } else { |
| 295 | + this.showUnknownError( code, filename ); |
| 296 | + } |
| 297 | + }, |
| 298 | + |
| 299 | + showUnparseableFilenameError: function( filename ) { |
| 300 | + this.showFilenameError( gM( 'mwe-upwiz-unparseable-filename', filename ) ); |
| 301 | + }, |
| 302 | + |
| 303 | + showBadExtensionError: function( filename, extension ) { |
| 304 | + var $errorMessage; |
| 305 | + // Check if firefogg should be recommended to be installed ( user selects an extension that can be converted) |
| 306 | + if ( mw.UploadWizard.config['enableFirefogg'] |
| 307 | + && |
| 308 | + $j.inArray( extension.toLowerCase(), mw.UploadWizard.config['transcodeExtensionList'] ) !== -1 |
| 309 | + ) { |
| 310 | + $errorMessage = $j( '<p>' ).msg('mwe-upwiz-upload-error-bad-extension-video-firefogg', |
| 311 | + mw.Firefogg.getFirefoggInstallUrl(), |
| 312 | + 'http://commons.wikimedia.org/wiki/Help:Converting_video' |
| 313 | + ); |
| 314 | + } else { |
| 315 | + $errorMessage = $j( '<p>' ).msg( 'mwe-upwiz-upload-error-bad-filename-extension', extension ); |
| 316 | + } |
| 317 | + this.showFilenameError( $errorMessage ); |
| 318 | + }, |
| 319 | + |
| 320 | + showMissingExtensionError: function( filename ) { |
| 321 | + this.showExtensionError( $j( '<p>' ).msg( 'mwe-upwiz-upload-error-bad-filename-no-extension' ) ); |
| 322 | + }, |
| 323 | + |
| 324 | + showUnknownFilenameError: function( filename ) { |
| 325 | + this.showFilenameError( $j( '<p>' ).msg( 'mwe-upwiz-upload-error-unknown-filename-error', filename ) ); |
| 326 | + }, |
| 327 | + |
| 328 | + showExtensionError: function( $errorMessage ) { |
| 329 | + this.showFilenameError( |
| 330 | + $( '<div></div>' ).append( |
| 331 | + $errorMessage, |
| 332 | + $j( '<p>' ).msg( 'mwe-upwiz-allowed-filename-extensions' ), |
| 333 | + $j( '<blockquote>' ).append( $j( '<tt>' ).append( |
| 334 | + mw.UploadWizard.config[ 'fileExtensions' ].join( " " ) |
| 335 | + ) ) |
| 336 | + ) |
| 337 | + ); |
| 338 | + }, |
| 339 | + |
| 340 | + showDuplicateError: function() { |
| 341 | + // to be implemented |
| 342 | + }, |
| 343 | + |
| 344 | + showFilenameError: function( $text ) { |
| 345 | + $( '<div>' ) |
| 346 | + .append( $text ) |
| 347 | + .dialog({ |
| 348 | + width: 500, |
| 349 | + zIndex: 200000, |
| 350 | + autoOpen: true, |
| 351 | + modal: true |
| 352 | + }); |
| 353 | + }, |
| 354 | + |
| 355 | + |
284 | 356 | /** |
285 | 357 | * Move the file input to cover a certain element on the page. |
286 | 358 | * We use invisible file inputs because this is the only way to style a file input |
— | — | @@ -330,30 +402,13 @@ |
331 | 403 | // visible filename |
332 | 404 | $j( _this.form ).find( '.mwe-upwiz-visible-file-filename-text' ).html( path ); |
333 | 405 | |
334 | | - var filename = mw.UploadWizardUtil.getBasename( path ); |
335 | | - try { |
336 | | - _this.upload.title = new mw.Title( filename, 'file' ); |
337 | | - } catch ( e ) { |
338 | | - $( '<div>' ) |
339 | | - .msg( 'mwe-upwiz-unparseable-filename', filename ) |
340 | | - .dialog({ |
341 | | - width: 500, |
342 | | - zIndex: 200000, |
343 | | - autoOpen: true, |
344 | | - modal: true |
345 | | - }); |
346 | | - _this.$fileInputCtrl.val(); |
347 | | - return; |
348 | | - } |
349 | | - |
350 | 406 | // Set the filename we tell to the API to be the current timestamp + the filename |
351 | 407 | // This is because we don't actually care what the filename is at this point, we just want it to be unique for this session and have the |
352 | 408 | // proper file extension. |
353 | 409 | // Also, it avoids a problem -- the API only returns one error at a time and it thinks that the same-filename error is more important than same-content. |
354 | 410 | // But for UploadWizard, at this stage, it's the reverse. We want to stop same-content dead, but for now we ignore same-filename |
355 | | - $j( _this.filenameCtrl ).val( ( new Date() ).getTime().toString() +_this.upload.title.getMain() ); |
| 411 | + $j( _this.filenameCtrl ).val( ( new Date() ).getTime().toString() + path ); |
356 | 412 | |
357 | | - |
358 | 413 | // deal with styling the file inputs and making it react to mouse |
359 | 414 | if ( ! _this.isFilled ) { |
360 | 415 | var $div = $j( _this.div ); |
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizard.js |
— | — | @@ -252,97 +252,99 @@ |
253 | 253 | }, |
254 | 254 | |
255 | 255 | /** |
256 | | - * Called when the file is entered into the file input |
257 | | - * Get as much data as possible with this browser, including thumbnail. |
258 | | - * TODO exif |
| 256 | + * Called when the file is entered into the file input. |
| 257 | + * Error out if filename or its contents are determined to be unacceptable |
| 258 | + * Proceed to thumbnail extraction and image info if acceptable |
259 | 259 | * @param {HTMLFileInput} file input field |
260 | | - * @param {Function} callback when ready |
| 260 | + * @param {Function()} callback when ok, and upload object is ready |
| 261 | + * @param {Function(String, Mixed)} callback when filename or contents in error. Signature of string code, mixed info |
261 | 262 | */ |
262 | | - extractLocalFileInfo: function( fileInput, callback ) { |
| 263 | + checkFile: function( fileInput, fileNameOk, fileNameErr ) { |
| 264 | + // check if local file is acceptable |
| 265 | + |
263 | 266 | var _this = this; |
264 | | - if ( mw.fileApi.isAvailable() ) { |
265 | | - if ( fileInput.files && fileInput.files.length ) { |
266 | | - // TODO multiple files in an input |
267 | | - this.file = fileInput.files[0]; |
268 | | - } |
269 | | - // TODO check max upload size, alert user if too big |
270 | | - this.transportWeight = this.file.size; |
| 267 | + |
| 268 | + // TODO check if filename has been used already in this wizard |
271 | 269 | |
272 | | - if ( mw.fileApi.isPreviewableFile( this.file ) ) { |
273 | | - /* |
274 | | - * The following part is a bit tricky, but not easy to untangle in code |
275 | | - * First, we will read the file as binary |
276 | | - * in the binary reader's onload function |
277 | | - * we try to get metadata with jpegmeta library, and add it to the upload object |
278 | | - * we re-read the file as a data URL |
279 | | - * in that file reader's onload function |
280 | | - * assign it to an image |
281 | | - * in that image's onload function |
282 | | - * publish the event, that this upload can now do thumbnails, with the image itself |
283 | | - * (listeners will then use that image to create thumbnails in the DOM, |
284 | | - * with local scaling or canvas) |
285 | | - */ |
286 | | - |
287 | | - var image = document.createElement( 'img' ); |
288 | | - image.onload = function() { |
289 | | - $.publishReady( 'thumbnails.' + _this.index, image ); |
290 | | - }; |
291 | | - |
292 | | - var binReader = new FileReader(); |
293 | | - binReader.onload = function() { |
294 | | - var meta; |
295 | | - try { |
296 | | - meta = mw.libs.jpegmeta( binReader.result, _this.file.fileName ); |
297 | | - meta._binary_data = null; |
298 | | - } catch ( e ) { |
299 | | - meta = null; |
300 | | - } |
301 | | - _this.extractMetadataFromJpegMeta( meta ); |
302 | | - |
303 | | - var dataUrlReader = new FileReader(); |
304 | | - dataUrlReader.onload = function() { |
305 | | - image.src = dataUrlReader.result; |
306 | | - _this.thumbnails['*'] = image; |
307 | | - }; |
308 | | - dataUrlReader.readAsDataURL( _this.file ); |
309 | | - }; |
310 | | - binReader.readAsBinaryString( _this.file ); |
311 | | - } |
312 | | - } |
| 270 | + // Check if filename is acceptable |
313 | 271 | // TODO sanitize filename |
314 | 272 | var filename = fileInput.value; |
315 | 273 | try { |
316 | 274 | this.title = new mw.Title( mw.UploadWizardUtil.getBasename( filename ).replace( /:/g, '_' ), 'file' ); |
317 | 275 | } catch ( e ) { |
318 | | - this.setError( 'mwe-upwiz-unparseable-filename', filename ); |
| 276 | + fileNameErr( 'unparseable' ); |
319 | 277 | } |
320 | | - if ( this.title ) { |
321 | | - callback(); |
| 278 | + |
| 279 | + // Check if extension is acceptable |
| 280 | + var extension = this.title.getExtension(); |
| 281 | + if ( mw.isEmpty( extension ) ) { |
| 282 | + fileNameErr( 'noext' ); |
| 283 | + } else { |
| 284 | + if ( $j.inArray( extension.toLowerCase(), mw.UploadWizard.config[ 'fileExtensions' ] ) === -1 ) { |
| 285 | + fileNameErr( 'ext', extension ); |
| 286 | + } else { |
| 287 | + |
| 288 | + // extract more info via fileAPI |
| 289 | + if ( mw.fileApi.isAvailable() ) { |
| 290 | + if ( fileInput.files && fileInput.files.length ) { |
| 291 | + // TODO multiple files in an input |
| 292 | + this.file = fileInput.files[0]; |
| 293 | + } |
| 294 | + // TODO check max upload size, alert user if too big |
| 295 | + this.transportWeight = this.file.size; |
| 296 | + if ( !mw.isDefined( this.imageinfo ) ) { |
| 297 | + this.imageinfo = {}; |
| 298 | + } |
| 299 | + |
| 300 | + var binReader = new FileReader(); |
| 301 | + binReader.onload = function() { |
| 302 | + var meta; |
| 303 | + try { |
| 304 | + meta = mw.libs.jpegmeta( binReader.result, _this.file.fileName ); |
| 305 | + meta._binary_data = null; |
| 306 | + } catch ( e ) { |
| 307 | + meta = null; |
| 308 | + } |
| 309 | + _this.extractMetadataFromJpegMeta( meta ); |
| 310 | + fileNameOk(); |
| 311 | + }; |
| 312 | + binReader.readAsBinaryString( _this.file ); |
| 313 | + } else { |
| 314 | + fileChangedOk(); |
| 315 | + } |
| 316 | + |
| 317 | + } |
| 318 | + |
322 | 319 | } |
| 320 | + |
323 | 321 | }, |
324 | 322 | |
| 323 | + |
325 | 324 | |
| 325 | + |
326 | 326 | /** |
327 | 327 | * Map fields from jpegmeta's metadata return into our format (which is more like the imageinfo returned from the API |
328 | 328 | * @param {Object} (as returned by jpegmeta) |
329 | 329 | */ |
330 | 330 | extractMetadataFromJpegMeta: function( meta ) { |
331 | | - if ( !mw.isDefined( this.imageinfo ) ) { |
332 | | - this.imageinfo = {}; |
333 | | - } |
334 | | - if ( !mw.isDefined( this.imageinfo.metadata ) ) { |
335 | | - this.imageinfo.metadata = {}; |
336 | | - } |
337 | | - if ( meta.tiff && meta.tiff.Orientation ) { |
338 | | - this.imageinfo.metadata.orientation = meta.tiff.Orientation.value; |
339 | | - } |
340 | | - if ( meta.general ) { |
341 | | - if ( meta.general.pixelHeight ) { |
342 | | - this.imageinfo.height = meta.general.pixelHeight.value; |
| 331 | + if ( mw.isDefined( meta ) && meta !== null && typeof meta === 'object' ) { |
| 332 | + if ( !mw.isDefined( this.imageinfo ) ) { |
| 333 | + this.imageinfo = {}; |
343 | 334 | } |
344 | | - if ( meta.general.pixelWidth ) { |
345 | | - this.imageinfo.width = meta.general.pixelWidth.value; |
| 335 | + if ( !mw.isDefined( this.imageinfo.metadata ) ) { |
| 336 | + this.imageinfo.metadata = {}; |
346 | 337 | } |
| 338 | + if ( meta.tiff && meta.tiff.Orientation ) { |
| 339 | + this.imageinfo.metadata.orientation = meta.tiff.Orientation.value; |
| 340 | + } |
| 341 | + if ( meta.general ) { |
| 342 | + if ( meta.general.pixelHeight ) { |
| 343 | + this.imageinfo.height = meta.general.pixelHeight.value; |
| 344 | + } |
| 345 | + if ( meta.general.pixelWidth ) { |
| 346 | + this.imageinfo.width = meta.general.pixelWidth.value; |
| 347 | + } |
| 348 | + } |
347 | 349 | } |
348 | 350 | }, |
349 | 351 | |
— | — | @@ -889,6 +891,7 @@ |
890 | 892 | return fileUrl; |
891 | 893 | } |
892 | 894 | |
| 895 | + |
893 | 896 | }; |
894 | 897 | |
895 | 898 | |