Jdlrobson has submitted this change and it was merged. 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, 133 insertions(+), 14 deletions(-) Approvals: Yuvipanda: Looks good to me, but someone else must approve Jdlrobson: Verified; Looks good to me, approved diff --git a/MobileFrontend.i18n.php b/MobileFrontend.i18n.php index cb513c2..23612e4 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', @@ -578,6 +579,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 6b786a7..c6910b0 100644 --- a/MobileFrontend.php +++ b/MobileFrontend.php @@ -379,6 +379,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 71b6031..5f44835 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 {String} suffix: An optional file extension e.g. '.jpg' or '.gif' + * @param {Date} date: An optional date (defaults to current date) + * @return {String} a legal filename + */ + function generateFileName( description, fileSuffix, date ) { + var allowedLength = 240, // 240 bytes maximum enforced by MediaWiki + delimiter = '-', name, suffix; + + fileSuffix = fileSuffix || '.jpg'; + date = date || new Date(); + name = description.replace( /[\x1B\n\x7f\.\[#<>\[\]\|\{\}\/:]/g, delimiter ); + + function pad( number ) { + return number < 9 ? '0' + number : number; + } + + suffix = ' ' + date.getFullYear() + delimiter + + pad( date.getMonth() + 1 ) + delimiter + pad( date.getDate() ) + ' ' + + pad( date.getHours() ) + delimiter + pad( date.getMinutes() ) + fileSuffix; + + allowedLength = allowedLength - suffix.length; + return trimUtf8String( name, allowedLength ) + suffix; } PhotoApi = Api.extend( { @@ -85,10 +145,12 @@ 'mobile-frontend-photo-article-donate-comment'; function doUpload( token ) { - var formData = new FormData(), descTextToAppend; - options.fileName = generateFileName( options.file, options.pageTitle ); + var formData = new FormData(), descTextToAppend, + ext = options.file.name.slice( options.file.name.lastIndexOf( '.' ) + 1 ); + descTextToAppend = mw.config.get( 'wgPhotoUploadAppendToDesc' ); descTextToAppend = descTextToAppend ? '\n\n' + descTextToAppend : ''; + options.fileName = generateFileName( options.description, '.' + ext ); formData.append( 'action', 'upload' ); formData.append( 'format', 'json' ); @@ -119,13 +181,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; @@ -397,8 +468,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' ); } ); @@ -455,8 +526,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..8e4c7ca 100644 --- a/tests/js/test_mf-photo.js +++ b/tests/js/test_mf-photo.js @@ -63,4 +63,47 @@ ); } ); +test( 'generateFileName', function() { + var date = new Date( 2010, 9, 15, 12, 51 ), + name = photo.generateFileName( 'Jon eating bacon next to an armadillo', '.jpg', 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', '.jpg', 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, '.jpg', 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( 'generateFileName with new lines', function() { + var + description = 'One\nTwo\nThree', + date = new Date( 2013, 2, 1, 12, 51 ), name; + + name = photo.generateFileName( description, '.jpg', date ); + strictEqual( name, 'One-Two-Three 2013-03-01 12-51.jpg', 'New lines converted' ); +} ); + +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: merged Gerrit-Change-Id: I8b5f2a78b9f73a23abc505ac424ab3926b81bf6d Gerrit-PatchSet: 5 Gerrit-Project: mediawiki/extensions/MobileFrontend Gerrit-Branch: master Gerrit-Owner: Jdlrobson <jrob...@wikimedia.org> Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org> Gerrit-Reviewer: JGonera <jgon...@wikimedia.org> Gerrit-Reviewer: Jdlrobson <jrob...@wikimedia.org> Gerrit-Reviewer: Siebrand <siebr...@wikimedia.org> Gerrit-Reviewer: Yuvipanda <yuvipa...@gmail.com> Gerrit-Reviewer: jenkins-bot _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits