WMDE-leszek has uploaded a new change for review. https://gerrit.wikimedia.org/r/300881
Change subject: Add support for GENDER to the username label in the tooltip ...................................................................... Add support for GENDER to the username label in the tooltip After fetching a batch of revision data, user names are extracted and another API query is made to get gender preferences for users. This change also moves a code responsible for MediaWiki API calls to its own class. Bug: T136367 Change-Id: Id11fe14e9ca37829141ae92b13b51f10f992eb96 --- M extension.json M i18n/en.json M i18n/qqq.json A modules/ext.RevisionSlider.Api.js M modules/ext.RevisionSlider.Revision.js M modules/ext.RevisionSlider.RevisionList.js M modules/ext.RevisionSlider.RevisionListView.js D modules/ext.RevisionSlider.fetchRevisions.js M modules/ext.RevisionSlider.init.js M tests/qunit/RevisionSlider.Revision.test.js M tests/qunit/RevisionSlider.RevisionList.test.js 11 files changed, 255 insertions(+), 70 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/RevisionSlider refs/changes/81/300881/1 diff --git a/extension.json b/extension.json index bc8bc38..315ead3 100644 --- a/extension.json +++ b/extension.json @@ -36,7 +36,7 @@ "ext.RevisionSlider.Revision", "ext.RevisionSlider.RevisionList", "ext.RevisionSlider.HelpDialog", - "ext.RevisionSlider.fetchRevisions", + "ext.RevisionSlider.Api", "ext.RevisionSlider.arrows.left", "ext.RevisionSlider.arrows.right", "ext.RevisionSlider.pointers.lower", @@ -54,9 +54,9 @@ "ext.RevisionSlider.noscript": { "styles": "modules/ext.RevisionSlider.noscript.css" }, - "ext.RevisionSlider.fetchRevisions": { + "ext.RevisionSlider.Api": { "scripts": [ - "modules/ext.RevisionSlider.fetchRevisions.js" + "modules/ext.RevisionSlider.Api.js" ] }, "ext.RevisionSlider.Revision": { diff --git a/i18n/en.json b/i18n/en.json index 48fdf34..f86e3e0 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -12,7 +12,7 @@ "revisionslider-label-page-size": "<strong>Page size:</strong> $1 {{PLURAL:$2|byte|bytes}}", "revisionslider-label-change-size": "<strong>Change size:</strong> $1 {{PLURAL:$2|byte|bytes}}", "revisionslider-label-comment": "Comment: ", - "revisionslider-label-username": "<strong>Username:</strong> [[$2|$1]]", + "revisionslider-label-username": "<strong>{{GENDER:$2|Username}}:</strong> [[$3|$1]]", "revisionslider-minoredit": "This is a minor edit", "revisionslider-loading-placeholder": "The RevisionSlider is loading...", "revisionslider-loading-failed": "The RevisionSlider failed to load.", diff --git a/i18n/qqq.json b/i18n/qqq.json index 334aa97..24d86d5 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -14,7 +14,7 @@ "revisionslider-label-page-size": "Label describing the size of this revision.\nParameters:\n* $1 - Formatted page size.\n* $2 - Page size as raw number used for PLURAL.\n{{Identical|Page size}}", "revisionslider-label-change-size": "Label describing the size of the change compared to the revision before.\nParameters:\n* $1 - Formatted change size colored with markup.\n* $2 - Change size as raw number used for PLURAL", "revisionslider-label-comment": "Label showing the edit summary of a revision.\n{{Identical|Comment}}", - "revisionslider-label-username": "Label for the revision's author's username.\n{{doc-important|<nowiki>{{</nowiki>[[Gender|GENDER]]<nowiki>}}</nowiki> is '''NOT''' supported.}}\n\nParameters:\n* $1 - Username, $2 - user page or Special:Contributors subpage for IP addresses.\n\n{{Identical|Username}}", + "revisionslider-label-username": "Label for the revision's author's username.\nParameters:\n* $1 - Username,\n* $2 - Gender as in user preferences (\"male\", \"female\" or \"unknown\", passed to GENDER),\n* $3 - User page or Special:Contributors subpage for IP addresses.\n\n{{Identical|Username}}", "revisionslider-minoredit": "Text labeling a minor edit.", "revisionslider-loading-placeholder": "Message shown while the RevisionSlider is still loading on a diff page. Once loaded the message is removed.", "revisionslider-loading-failed": "Message shown if the RevisionSlider fails to initially load.", diff --git a/modules/ext.RevisionSlider.Api.js b/modules/ext.RevisionSlider.Api.js new file mode 100644 index 0000000..3a7cdb1 --- /dev/null +++ b/modules/ext.RevisionSlider.Api.js @@ -0,0 +1,63 @@ +( function ( mw, $ ) { + /** + * @param {string} apiUrl + * @constructor + */ + var Api = function ( apiUrl ) { + this.url = apiUrl; + }; + + $.extend( Api.prototype, { + url: '', + + /** + * Fetches up to 500 revisions at a time + * + * @param {Object} options - Options containing callbacks for `success` and `error` as well as fields for + * `pageName` and `startId` + */ + fetchRevisions: function ( options ) { + $.ajax( { + url: this.url, + data: { + action: 'query', + prop: 'revisions', + format: 'json', + rvprop: 'ids|timestamp|user|comment|parsedcomment|size|flags', + titles: options.pageName, + formatversion: 2, + rvstartid: options.startId, + 'continue': '', + rvlimit: 500 + }, + success: options.success, + error: options.error + } ); + }, + + /** + * Fetches gender data for up to 500 user names + * + * @param {Object} options - Options containing callbacks for `success` and `error` as well as list + * of user names in `users` + */ + fetchUserGenderData: function ( options ) { + $.ajax( { + url: this.url, + data: { + action: 'query', + list: 'users', + format: 'json', + usprop: 'gender', + ususers: options.users.join( '|' ), + uslimit: 500 + }, + success: options.success, + error: options.error + } ); + } + } ); + + mw.libs.revisionSlider = mw.libs.revisionSlider || {}; + mw.libs.revisionSlider.Api = Api; +}( mediaWiki, jQuery ) ); diff --git a/modules/ext.RevisionSlider.Revision.js b/modules/ext.RevisionSlider.Revision.js index bd8cc10..1aadb44 100644 --- a/modules/ext.RevisionSlider.Revision.js +++ b/modules/ext.RevisionSlider.Revision.js @@ -59,6 +59,11 @@ user: '', /** + * @type {string} + */ + userGender: '', + + /** * @type {number} */ relativeSize: 0, @@ -133,6 +138,20 @@ }, /** + * @param {string} gender + */ + setUserGender: function ( gender ) { + this.userGender = gender; + }, + + /** + * @return {string} + */ + getUserGender: function () { + return this.userGender; + }, + + /** * @param {number} size */ setRelativeSize: function ( size ) { diff --git a/modules/ext.RevisionSlider.RevisionList.js b/modules/ext.RevisionSlider.RevisionList.js index e346990..e4a6591 100644 --- a/modules/ext.RevisionSlider.RevisionList.js +++ b/modules/ext.RevisionSlider.RevisionList.js @@ -65,6 +65,29 @@ }, /** + * @return {string[]} + */ + getUserNames: function () { + var allUsers = this.revisions.map( function ( revision ) { + return revision.getUser(); + } ); + return allUsers.filter( function ( value, index, array ) { + return value !== '' && array.indexOf( value ) === index; + } ); + }, + + /** + * @param {Object} userGenderData + */ + setUserGenders: function ( userGenderData ) { + this.revisions.forEach( function ( revision ) { + if ( revision.getUser() !== '' && typeof userGenderData[ revision.getUser() ] !== 'undefined' ) { + revision.setUserGender( userGenderData[ revision.getUser() ] ); + } + } ); + }, + + /** * @return {RevisionListView} */ getView: function () { diff --git a/modules/ext.RevisionSlider.RevisionListView.js b/modules/ext.RevisionSlider.RevisionListView.js index fa2408d..2eed472 100644 --- a/modules/ext.RevisionSlider.RevisionListView.js +++ b/modules/ext.RevisionSlider.RevisionListView.js @@ -141,7 +141,7 @@ $( '<p>' ).append( mw.message( 'revisionslider-label-date', rev.getFormattedDate() ).parseDom() ), - this.makeUserLine( rev.getUser() ), + this.makeUserLine( rev.getUser(), rev.getUserGender() ), this.makeCommentLine( rev ), this.makePageSizeLine( rev.getSize() ), this.makeChangeSizeLine( rev.getRelativeSize() ), @@ -164,15 +164,19 @@ * Generates the HTML for the user label * * @param {string} userString + * @param {string} userGender * @return {string|jQuery} */ - makeUserLine: function ( userString ) { + makeUserLine: function ( userString, userGender ) { if ( !userString ) { return ''; } + if ( !userGender ) { + userGender = 'unknown'; + } return $( '<bdi>' ).append( $( '<p>' ).append( - mw.message( 'revisionslider-label-username', mw.html.escape( userString ), this.getUserPage( userString ) ).parseDom() + mw.message( 'revisionslider-label-username', mw.html.escape( userString ), userGender, this.getUserPage( userString ) ).parseDom() ) ); }, diff --git a/modules/ext.RevisionSlider.fetchRevisions.js b/modules/ext.RevisionSlider.fetchRevisions.js deleted file mode 100644 index ca02918..0000000 --- a/modules/ext.RevisionSlider.fetchRevisions.js +++ /dev/null @@ -1,29 +0,0 @@ -( function ( mw, $ ) { - mw.libs.revisionSlider = mw.libs.revisionSlider || {}; - - /** - * @member RevisionSlider - * Fetches up to 500 revisions at a time - * - * @param {Object} options - Options containing callbacks for `success` and `error` as well as fields for - * `pageName` and `startId` - */ - mw.libs.revisionSlider.fetchRevisions = function ( options ) { - $.ajax( { - url: mw.util.wikiScript( 'api' ), - data: { - action: 'query', - prop: 'revisions', - format: 'json', - rvprop: 'ids|timestamp|user|comment|parsedcomment|size|flags', - titles: options.pageName, - formatversion: 2, - rvstartid: options.startId, - 'continue': '', - rvlimit: 500 - }, - success: options.success, - error: options.error - } ); - }; -}( mediaWiki, jQuery ) ); diff --git a/modules/ext.RevisionSlider.init.js b/modules/ext.RevisionSlider.init.js index de04bc3..c65afdb 100644 --- a/modules/ext.RevisionSlider.init.js +++ b/modules/ext.RevisionSlider.init.js @@ -1,7 +1,25 @@ ( function ( mw, $ ) { + var api = new mw.libs.revisionSlider.Api( mw.util.wikiScript( 'api' ) ); + + /** + * @param {Array} data + * @return {Object} + */ + function getUserGenderData( data ) { + var genderData = {}, + usersWithGender = data.filter( function ( item ) { + return typeof item.gender !== 'undefined' && item.gender !== 'unknown'; + } ); + usersWithGender.forEach( function ( item ) { + genderData[ item.name ] = item.gender; + } ); + return genderData; + } + mw.track( 'counter.MediaWiki.RevisionSlider.event.init' ); mw.libs.revisionSlider.userOffset = mw.user.options.values.timecorrection ? mw.user.options.values.timecorrection.split( '|' )[ 1 ] : mw.config.values.extRevisionSliderTimeOffset; - mw.libs.revisionSlider.fetchRevisions( { + + api.fetchRevisions( { pageName: mw.config.get( 'wgPageName' ), startId: mw.config.get( 'wgCurRevisionId' ), @@ -19,32 +37,50 @@ revs.reverse(); revisionList = new mw.libs.revisionSlider.RevisionList( mw.libs.revisionSlider.makeRevisions( revs ) ); - $container = $( '#mw-revslider-container' ); - slider = new mw.libs.revisionSlider.Slider( revisionList ); - slider.getView().render( $container ); - if ( !mw.user.options.get( 'userjs-revslider-hidehelp' ) ) { - mw.libs.revisionSlider.HelpDialog.show(); - ( new mw.Api() ).saveOption( 'userjs-revslider-hidehelp', true ); - } + api.fetchUserGenderData( { + users: revisionList.getUserNames(), + success: function ( data ) { + var users = data.query.users; - $container.append( - $( '<button>' ) - .click( function () { + if ( users ) { + revisionList.setUserGenders( getUserGenderData( users ) ); + } + + $container = $( '#mw-revslider-container' ); + slider = new mw.libs.revisionSlider.Slider( revisionList ); + slider.getView().render( $container ); + + if ( !mw.user.options.get( 'userjs-revslider-hidehelp' ) ) { mw.libs.revisionSlider.HelpDialog.show(); - } ) - .text( mw.message( 'revisionslider-show-help' ).text() ) - .addClass( 'mw-revslider-show-help' ) - .tipsy( { - gravity: $( 'body' ).hasClass( 'ltr' ) ? 'se' : 'sw', - offset: 15, - title: function () { - return mw.msg( 'revisionslider-show-help-tooltip' ); - } - } ) - ); + ( new mw.Api() ).saveOption( 'userjs-revslider-hidehelp', true ); + } - $( '#mw-revslider-placeholder' ).remove(); + $container.append( + $( '<button>' ) + .click( function () { + mw.libs.revisionSlider.HelpDialog.show(); + } ) + .text( mw.message( 'revisionslider-show-help' ).text() ) + .addClass( 'mw-revslider-show-help' ) + .tipsy( { + gravity: $( 'body' ).hasClass( 'ltr' ) ? 'se' : 'sw', + offset: 15, + title: function () { + return mw.msg( 'revisionslider-show-help-tooltip' ); + } + } ) + ); + + $( '#mw-revslider-placeholder' ).remove(); + }, + error: function ( err ) { + $( '#mw-revslider-placeholder' ) + .text( mw.message( 'revisionslider-loading-failed' ).text() ); + console.log( err ); + mw.track( 'counter.MediaWiki.RevisionSlider.error.init.genders' ); + } + } ); } catch ( err ) { if ( err === 'RS-rev-out-of-range' ) { $( '#mw-revslider-placeholder' ) diff --git a/tests/qunit/RevisionSlider.Revision.test.js b/tests/qunit/RevisionSlider.Revision.test.js index 38ccc81..eb9a958 100644 --- a/tests/qunit/RevisionSlider.Revision.test.js +++ b/tests/qunit/RevisionSlider.Revision.test.js @@ -1,4 +1,6 @@ ( function ( mw ) { + var Revision = mw.libs.revisionSlider.Revision; + QUnit.module( 'ext.RevisionSlider.Revision' ); QUnit.test( 'create Revision', function ( assert ) { @@ -9,7 +11,7 @@ timestamp: '2016-04-26T10:27:14Z', // 10:27, 26 Apr 2016 user: 'meh' }, - rev = new mw.libs.revisionSlider.Revision( data ); + rev = new Revision( data ); mw.libs.revisionSlider.userOffset = 0; @@ -17,6 +19,7 @@ assert.equal( rev.getComment(), data.comment ); assert.equal( rev.getParsedComment(), data.parsedcomment ); assert.equal( rev.getUser(), data.user ); + assert.equal( rev.getUserGender(), '' ); assert.equal( rev.isMinor(), false ); if ( mw.config.get( 'wgUserLanguage' ) === 'en' ) { @@ -25,7 +28,7 @@ } ); QUnit.test( 'isMinor with minor empty string', function ( assert ) { - var rev = new mw.libs.revisionSlider.Revision( { + var rev = new Revision( { minor: '' } ); @@ -33,7 +36,7 @@ } ); QUnit.test( 'isMinor with minor true', function ( assert ) { - var rev = new mw.libs.revisionSlider.Revision( { + var rev = new Revision( { minor: true } ); @@ -42,13 +45,13 @@ QUnit.test( 'get and set relative size', function ( assert ) { var size = 5, - rev = new mw.libs.revisionSlider.Revision( {} ); + rev = new Revision( {} ); rev.setRelativeSize( size ); assert.equal( rev.getRelativeSize(), size ); } ); QUnit.revisionSlider.testOrSkip( 'getFormattedDate, offset: 0', function ( assert ) { - var rev = new mw.libs.revisionSlider.Revision( { + var rev = new Revision( { timestamp: '2016-04-26T10:27:14Z' // 10:27, 26 Apr 2016 } ); @@ -58,7 +61,7 @@ }, mw.config.get( 'wgUserLanguage' ) !== 'en' ); QUnit.revisionSlider.testOrSkip( 'getFormattedDate, offset: 120 (treat as hours, +2h)', function ( assert ) { - var rev = new mw.libs.revisionSlider.Revision( { + var rev = new Revision( { timestamp: '2016-04-26T10:27:14Z' // 10:27, 26 Apr 2016 } ); @@ -69,7 +72,7 @@ }, mw.config.get( 'wgUserLanguage' ) !== 'en' ); QUnit.revisionSlider.testOrSkip( 'getFormattedDate, negative offset: -420 (treat as hours, -7h)', function ( assert ) { - var rev = new mw.libs.revisionSlider.Revision( { + var rev = new Revision( { timestamp: '2016-04-26T10:27:14Z' // 10:27, 26 Apr 2016 } ); @@ -80,7 +83,7 @@ }, mw.config.get( 'wgUserLanguage' ) !== 'en' ); QUnit.test( 'hasEmptyComment comment with whitespaces', function ( assert ) { - var rev = new mw.libs.revisionSlider.Revision( { + var rev = new Revision( { comment: ' ' } ); @@ -88,7 +91,7 @@ } ); QUnit.test( 'hasEmptyComment comment with chars', function ( assert ) { - var rev = new mw.libs.revisionSlider.Revision( { + var rev = new Revision( { comment: ' comment ' } ); @@ -96,12 +99,22 @@ } ); QUnit.test( 'hasEmptyComment comment with unicode chars', function ( assert ) { - var rev = new mw.libs.revisionSlider.Revision( { + var rev = new Revision( { comment: 'ברוכים' } ); assert.notOk( rev.hasEmptyComment() ); } ); + QUnit.test( 'setUserGender adjusts a gender', function ( assert ) { + var rev = new Revision( { user: 'Foo' } ); + + assert.equal( rev.getUserGender(), '' ); + + rev.setUserGender( 'female' ); + + assert.equal( rev.getUserGender(), 'female' ); + } ); + } )( mediaWiki ); diff --git a/tests/qunit/RevisionSlider.RevisionList.test.js b/tests/qunit/RevisionSlider.RevisionList.test.js index 0557b6c..9a4186b 100644 --- a/tests/qunit/RevisionSlider.RevisionList.test.js +++ b/tests/qunit/RevisionSlider.RevisionList.test.js @@ -27,6 +27,62 @@ assert.equal( revs.getRevisions()[ 2 ].getRelativeSize(), -8 ); } ); + QUnit.test( 'getUserNames returns a list of unique names', function ( assert ) { + var revs = new RevisionList( [ + new Revision( { revid: 1, user: 'User1' } ), + new Revision( { revid: 2, user: 'User2' } ), + new Revision( { revid: 3, user: 'User1' } ) + ] ), + userNames = revs.getUserNames(); + + assert.deepEqual( userNames, [ 'User1', 'User2' ] ); + } ); + + QUnit.test( 'getUserNames skips revisions without user specified', function ( assert ) { + var revs = new RevisionList( [ + new Revision( { revid: 1, user: 'User1' } ), + new Revision( { revid: 2 } ) + ] ), + userNames = revs.getUserNames(); + + assert.deepEqual( userNames, [ 'User1' ] ); + } ); + + QUnit.test( 'setUserGenders adjusts revision data', function ( assert ) { + var revs = new RevisionList( [ + new Revision( { revid: 1, user: 'User1' } ), + new Revision( { revid: 2, user: 'User2' } ), + new Revision( { revid: 3, user: 'User3' } ) + ] ), + genders = { User1: 'female', User2: 'male', User3: 'unknown' }; + + assert.equal( revs.getRevisions()[ 0 ].getUserGender(), '' ); + assert.equal( revs.getRevisions()[ 1 ].getUserGender(), '' ); + assert.equal( revs.getRevisions()[ 2 ].getUserGender(), '' ); + + revs.setUserGenders( genders ); + + assert.equal( revs.getRevisions()[ 0 ].getUserGender(), 'female' ); + assert.equal( revs.getRevisions()[ 1 ].getUserGender(), 'male' ); + assert.equal( revs.getRevisions()[ 2 ].getUserGender(), 'unknown' ); + } ); + + QUnit.test( 'setUserGenders no gender for a user', function ( assert ) { + var revs = new RevisionList( [ + new Revision( { revid: 1, user: 'User1' } ), + new Revision( { revid: 2, user: 'User2' } ) + ] ), + genders = { User1: 'female' }; + + assert.equal( revs.getRevisions()[ 0 ].getUserGender(), '' ); + assert.equal( revs.getRevisions()[ 1 ].getUserGender(), '' ); + + revs.setUserGenders( genders ); + + assert.equal( revs.getRevisions()[ 0 ].getUserGender(), 'female' ); + assert.equal( revs.getRevisions()[ 1 ].getUserGender(), '' ); + } ); + QUnit.test( 'makeRevisions converts revision data into list of Revision objects', function ( assert ) { var revs = [ { revid: 1, size: 5 }, -- To view, visit https://gerrit.wikimedia.org/r/300881 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Id11fe14e9ca37829141ae92b13b51f10f992eb96 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/RevisionSlider Gerrit-Branch: master Gerrit-Owner: WMDE-leszek <leszek.mani...@wikimedia.de> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits