Jdlrobson has uploaded a new change for review. https://gerrit.wikimedia.org/r/53769
Change subject: Story 347: Provide nicer filenames ...................................................................... Story 347: Provide nicer filenames Filenames are descriptions with date appended accurate to minutes Change-Id: I8b5f2a78b9f73a23abc505ac424ab3926b81bf6d --- M MobileFrontend.i18n.php M MobileFrontend.php M javascripts/modules/mf-photo.js M tests/js/test_mf-photo.js 4 files changed, 122 insertions(+), 13 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MobileFrontend refs/changes/69/53769/1 diff --git a/MobileFrontend.i18n.php b/MobileFrontend.i18n.php index 0b3be89..792e2fc 100644 --- a/MobileFrontend.i18n.php +++ b/MobileFrontend.i18n.php @@ -233,6 +233,7 @@ 'mobile-frontend-image-uploading-long' => 'Image still uploading! Thanks for your patience.', 'mobile-frontend-image-uploading-cancel' => '<a href="#">Cancel</a> if this is taking too long.', 'mobile-frontend-photo-upload-error' => 'Error, try again.', + 'mobile-frontend-photo-upload-error-filename' => 'Error, please provide a more descriptive summary.', 'mobile-frontend-photo-upload-success-article' => 'Success! Your image is now live on this page.', 'mobile-frontend-photo-license' => 'By clicking "Submit", you agree to our [//wikimediafoundation.org/wiki/Terms_of_use Terms of Use] and agree to release your photo under the [//creativecommons.org/licenses/by-sa/3.0/ Creative Commons Attribution-ShareAlike 3.0 License].', 'mobile-frontend-photo-submit' => 'Submit', @@ -577,6 +578,7 @@ 'mobile-frontend-image-uploading-long' => 'Text that displays whilst an image is taking long to upload', 'mobile-frontend-image-uploading-cancel' => 'Text saying that user can cancel the image upload. Word "cancel" should be a link.', 'mobile-frontend-photo-upload-error' => 'Text that displays when a photo fails to upload', + 'mobile-frontend-photo-upload-error-filename' => 'Text that displays when a photo fails to upload due to filename', 'mobile-frontend-photo-upload-success-article' => 'Text that displays when a photo has been successfully added to a page.', 'mobile-frontend-photo-license' => 'Text notifying user of license that image will be published under. You can change the URL to a "local" Wikipedia URL, but you cannot make it point to the country specific CC BY-SA 3.0 license.', 'mobile-frontend-photo-submit' => 'Caption for the submit button on the photo uplaod form. diff --git a/MobileFrontend.php b/MobileFrontend.php index 573a344..7bef703 100644 --- a/MobileFrontend.php +++ b/MobileFrontend.php @@ -378,6 +378,7 @@ 'mobile-frontend-photo-article-edit-comment', 'mobile-frontend-photo-article-donate-comment', 'mobile-frontend-photo-upload-error', + 'mobile-frontend-photo-upload-error-filename', 'mobile-frontend-photo-upload-success-article', 'mobile-frontend-photo-caption-placeholder', 'mobile-frontend-image-loading', diff --git a/javascripts/modules/mf-photo.js b/javascripts/modules/mf-photo.js index 1c4071e..1c91dbd 100644 --- a/javascripts/modules/mf-photo.js +++ b/javascripts/modules/mf-photo.js @@ -52,15 +52,75 @@ }; } - function generateFileName( file, pageTitle ) { - // FIXME: deal with long and invalid names - var name = 'Lead_Photo_For_' + pageTitle.replace( / /g, '_' ) + Math.random(), - extension; + // Originally written by Brion for WikiLovesMonuments app + function trimUtf8String( str, allowedLength ) { + // Count UTF-8 bytes to see where we need to crop long names. + var bytes = 0, chars = 0, codeUnit, len, i; - name = name.replace( String.fromCharCode( 27 ), '-' ); - name = name.replace( /[\x7f\.\[#<>\[\]\|\{\}\/:]/g, '-' ); - extension = file.name.slice( file.name.lastIndexOf( '.' ) + 1 ); - return name + '.' + extension; + for ( i = 0; i < str.length; i++ ) { + // JavaScript strings are UTF-16. + codeUnit = str.charCodeAt( i ); + + // http://en.wikipedia.org/wiki/UTF-8#Description + if ( codeUnit < 0x80 ) { + len = 1; + } else if ( codeUnit < 0x800 ) { + len = 2; + } else if ( codeUnit >= 0xd800 && codeUnit < 0xe000 ) { + // http://en.wikipedia.org/wiki/UTF-16#Description + // Code point is one half of a surrogate pair. + // This and its partner combine to form a single 4 byte character in UTF-8. + len = 4; + } else { + len = 3; + } + + if ( bytes + len <= allowedLength ) { + bytes += len; + chars++; + if ( len === 4 ) { + // Skip over second half of surrogate pair as a unit. + chars++; + i++; + } + } else { + // Ran out of bytes. + return str.substr( 0, chars ); + } + } + + // We fit! + return str; + } + + /** + * Generates a file name from a description and a date + * Removes illegal characters + * Respects maximum file name length (240 bytes) + * + * @param {String} description Data to be preprocessed and added to options + * @param {Date} date An optional date (defaults to current date) + * @return {String} a legal filename + */ + function generateFileName( description, date ) { + var allowedLength = 240, // 240 bytes maximum enforced by MediaWiki + delimiter = ' ', name, suffix; + + date = date || new Date(); + name = description.replace( String.fromCharCode( 27 ), delimiter ); + name = name.replace( /[\x7f\.\[#<>\[\]\|\{\}\/:]/g, delimiter ); + + function pad( number ) { + return number < 9 ? '0' + number : number; + } + + // FIXME: should we really assume jpg? + suffix = delimiter + date.getFullYear() + delimiter + + pad( date.getMonth() + 1 ) + delimiter + pad( date.getDate() ) + delimiter + + pad( date.getHours() ) + delimiter + pad( date.getMinutes() ) + '.jpg'; + + allowedLength = allowedLength - suffix.length; + return trimUtf8String( name, allowedLength ) + suffix; } PhotoApi = Api.extend( { @@ -86,9 +146,9 @@ function doUpload( token ) { var formData = new FormData(), descTextToAppend; - options.fileName = generateFileName( options.file, options.pageTitle ); descTextToAppend = mw.config.get( 'wgPhotoUploadAppendToDesc' ); descTextToAppend = descTextToAppend ? '\n\n' + descTextToAppend : ''; + options.fileName = generateFileName( options.description ); formData.append( 'action', 'upload' ); formData.append( 'format', 'json' ); @@ -119,13 +179,22 @@ self.emit( 'progress', ev.loaded / ev.total ); } } ).done( function( data ) { - var descriptionUrl = ''; + var descriptionUrl = '', + warnings = data.upload ? data.upload.warnings : false; if ( !data || !data.upload ) { // error uploading image result.reject( data.error ? data.error.info : '' ); return; } - options.fileName = data.upload.filename || data.upload.warnings.duplicate['0']; + options.fileName = data.upload.filename; + if ( warnings ) { + if ( warnings.duplicate ) { + options.fileName = warnings.duplicate[ '0' ]; + } else if ( warnings.exists ) { + return result.reject( 'Filename exists', + mw.msg( 'mobile-frontend-photo-upload-error-filename' ) ); + } + } // FIXME: API doesn't return this information on duplicate images... if ( data.upload.imageinfo ) { descriptionUrl = data.upload.imageinfo.descriptionurl; @@ -396,8 +465,8 @@ descriptionUrl: descriptionUrl, url: self.preview.imageUrl } ); - } ).fail( function( err ) { - popup.show( mw.msg( 'mobile-frontend-photo-upload-error' ), 'toast error' ); + } ).fail( function( err, msg ) { + popup.show( msg || mw.msg( 'mobile-frontend-photo-upload-error' ), 'toast error' ); self.log( { action: 'error', errorText: err } ); self.emit( 'error' ); } ); @@ -454,8 +523,10 @@ } M.define( 'photo', { + generateFileName: generateFileName, isSupported: isSupported, PhotoUploader: PhotoUploader, + trimUtf8String: trimUtf8String, // just for testing _needsPhoto: needsPhoto, _PhotoUploadProgress: PhotoUploadProgress diff --git a/tests/js/test_mf-photo.js b/tests/js/test_mf-photo.js index 05aec3a..df8d348 100644 --- a/tests/js/test_mf-photo.js +++ b/tests/js/test_mf-photo.js @@ -63,4 +63,39 @@ ); } ); +test( 'generateFileName', function() { + var date = new Date( 2010, 9, 15, 12, 51 ), + name = photo.generateFileName( 'Jon eating bacon next to an armadillo', date ); + strictEqual( name, 'Jon eating bacon next to an armadillo 2010 10 15 12 51.jpg', + 'Check file name is description with appended date' ); +} ); + +test( 'generateFileName test padding', function() { + var date = new Date( 2013, 2, 1, 12, 51 ), // note 0 = january + name = photo.generateFileName( 'Tomasz eating bacon next to a dinosaur', date ); + strictEqual( name, 'Tomasz eating bacon next to a dinosaur 2013 03 01 12 51.jpg', + 'Check file name is description with appended date and numbers were padded' ); +} ); + +test( 'generateFileName long line', function() { + var i, + longDescription = '', + date = new Date( 2013, 2, 1, 12, 51 ), name; + + for ( i=0; i < 240; i++ ) { + longDescription += 'a'; + } + name = photo.generateFileName( longDescription, date ); + strictEqual( name.length, 240, 'Check file name was shortened to the minimum length' ); + strictEqual( name.substr( 233, 7 ), ' 51.jpg', 'ends with date' ); +} ); + + +test( 'trimUtf8String', function() { + strictEqual( photo.trimUtf8String( 'Just a string', 20 ), 'Just a string', 'ascii string fits' ); + strictEqual( photo.trimUtf8String( 'Just a string', 10 ), 'Just a str', 'ascii string truncated' ); + strictEqual( photo.trimUtf8String( 'Júst á stríng', 10 ), 'Júst á s', 'latin1 string truncated' ); + strictEqual( photo.trimUtf8String( 'こんにちは', 10 ), 'こんに', 'CJK string truncated' ); +} ); + }( jQuery, mw.mobileFrontend ) ); -- To view, visit https://gerrit.wikimedia.org/r/53769 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I8b5f2a78b9f73a23abc505ac424ab3926b81bf6d Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/MobileFrontend Gerrit-Branch: master Gerrit-Owner: Jdlrobson <jrob...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits