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

Reply via email to