JGonera has uploaded a new change for review. https://gerrit.wikimedia.org/r/106854
Change subject: [WIP] Story 1462: Move new languages overlay to stable ...................................................................... [WIP] Story 1462: Move new languages overlay to stable Create a separate initialization file for stable and beta (stable doesn't have dynamic loading of languages or secondary page actions yet). Also, add a browser test for closing the language overlay. Change-Id: I73f1f69dd8be27b6893276a5394751e924b2f1ab --- M MobileFrontend.i18n.php M includes/Resources.php M includes/skins/SkinMinerva.php M javascripts/modules/languages/LanguageOverlay.js D javascripts/modules/languages/languages.js R javascripts/modules/languages/languagesBeta.js A javascripts/modules/languages/languagesStable.js R javascripts/modules/languages/preferred.js D javascripts/modules/languagesNew/LanguageOverlay.js D less/modules/languages.less R templates/modules/languages/LanguageOverlay.html M tests/browser/features/language.feature M tests/browser/features/step_definitions/language_steps.rb M tests/browser/features/support/pages/home_page.rb R tests/javascripts/modules/languages/test_languagesStable.js 15 files changed, 144 insertions(+), 202 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MobileFrontend refs/changes/54/106854/1 diff --git a/MobileFrontend.i18n.php b/MobileFrontend.i18n.php index 485c9c0..674d9fd 100644 --- a/MobileFrontend.i18n.php +++ b/MobileFrontend.i18n.php @@ -89,7 +89,6 @@ 'mobile-frontend-current-language' => '{{#language:{{CONTENTLANG}}}}', 'mobile-frontend-language-header' => 'This page is available in $1 {{PLURAL:$1|language|languages}}', 'mobile-frontend-language-variant-header' => 'Choose {{#language:{{CONTENTLANG}}}} variant', - 'mobile-frontend-language-footer' => 'Note: This page may not be written in your preferred language. You can see which languages {{SITENAME}} supports by clicking here.', 'mobile-frontend-language-site-choose' => 'Search language', 'mobile-frontend-username' => 'Username:', 'mobile-frontend-password' => 'Password:', diff --git a/includes/Resources.php b/includes/Resources.php index 8903109..324370a 100644 --- a/includes/Resources.php +++ b/includes/Resources.php @@ -360,12 +360,6 @@ 'mobile.loggingSchemas', 'mobile.templates', ), - 'messages' => array( - // LanguageOverlay.js - 'mobile-frontend-language-header', - 'mobile-frontend-language-site-choose', - 'mobile-frontend-language-footer', - ), ), 'mobile.keepgoing' => $wgMFMobileResourceBoilerplate + array( @@ -431,6 +425,7 @@ 'javascripts/modules/talk/talk.js', 'javascripts/modules/mediaViewer.js', 'javascripts/modules/keepgoing/keepgoing.js', + 'javascripts/modules/languages/preferred.js', ), 'templates' => array( 'modules/ImageOverlay', @@ -802,39 +797,15 @@ ), ), - // FIXME: remove when new overlays in stable 'mobile.languages' => $wgMFMobileResourceBoilerplate + array( 'dependencies' => array( 'mobile.overlays', ), - 'styles' => array( - 'less/modules/languages.less', - ), 'scripts' => array( 'javascripts/modules/languages/LanguageOverlay.js', - 'javascripts/modules/languages/languages.js', ), 'templates' => array( - 'overlays/languages', - ), - 'messages' => array( - 'mobile-frontend-language-header', - 'mobile-frontend-language-site-choose', - 'mobile-frontend-language-footer', - ), - ), - - 'mobile.languages.beta' => $wgMFMobileResourceBoilerplate + array( - 'dependencies' => array( - 'mobile.overlays', - ), - 'scripts' => array( - 'javascripts/modules/languagesNew/LanguageOverlay.js', - 'javascripts/modules/languagesNew/languages.js', - 'javascripts/modules/languagesNew/preferred.js', - ), - 'templates' => array( - 'modules/languagesNew/LanguageOverlay', + 'modules/languages/LanguageOverlay', ), 'messages' => array( 'mobile-frontend-language-heading', @@ -844,6 +815,24 @@ ), ), + 'mobile.languages.stable' => $wgMFMobileResourceBoilerplate + array( + 'dependencies' => array( + 'mobile.languages', + ), + 'scripts' => array( + 'javascripts/modules/languages/languagesStable.js', + ), + ), + + 'mobile.languages.beta' => $wgMFMobileResourceBoilerplate + array( + 'dependencies' => array( + 'mobile.languages', + ), + 'scripts' => array( + 'javascripts/modules/languages/languagesBeta.js', + ), + ), + 'mobile.issues' => $wgMFMobileResourceBoilerplate + array( 'dependencies' => array( 'mobile.overlays', diff --git a/includes/skins/SkinMinerva.php b/includes/skins/SkinMinerva.php index 799ff83..f458c84 100644 --- a/includes/skins/SkinMinerva.php +++ b/includes/skins/SkinMinerva.php @@ -636,7 +636,7 @@ $modules['stableonly'] = array( 'mobile.lastEdited.stable' ); $modules['issues'] = array( 'mobile.issues' ); $modules['editor'] = array( 'mobile.editor' ); - $modules['languages'] = array( 'mobile.languages' ); + $modules['languages'] = array( 'mobile.languages.stable' ); $modules['newusers'] = array( 'mobile.newusers' ); $title = $this->getTitle(); diff --git a/javascripts/modules/languages/LanguageOverlay.js b/javascripts/modules/languages/LanguageOverlay.js index c0b3a88..eb424a0 100644 --- a/javascripts/modules/languages/LanguageOverlay.js +++ b/javascripts/modules/languages/LanguageOverlay.js @@ -1,59 +1,61 @@ -( function( M, $ ) { +( function( M, $ ) { - var Overlay = M.require( 'Overlay' ), + var OverlayNew = M.require( 'OverlayNew' ), LanguageOverlay; - LanguageOverlay = Overlay.extend( { + LanguageOverlay = OverlayNew.extend( { defaults: { - languagesLink: mw.util.getUrl( 'Special:MobileOptions/Language' ), - languagesText: mw.msg( 'mobile-frontend-language-footer' ), + heading: mw.msg( 'mobile-frontend-language-heading' ), placeholder: mw.msg( 'mobile-frontend-language-site-choose' ) }, - className: 'language-overlay mw-mf-overlay', - template: M.template.get( 'overlays/languages' ), + className: 'language-overlay overlay', + templatePartials: { + content: M.template.get( 'modules/languages/LanguageOverlay' ) + }, + initialize: function( options ) { if ( options.languages && options.languages.length ) { options.header = mw.msg( 'mobile-frontend-language-header', options.languages.length ); } if ( options.variants && options.variants.length ) { - options.variantHeader = mw.msg( 'mobile-frontend-language-header', options.variants.length ); + options.variantHeader = mw.msg( 'mobile-frontend-language-variant-header' ); } M.emit( 'language-overlay-initialize', options ); this._super( options ); }, + filterLists: function( val ) { - var matches = 0; + var $items = this.$( '.page-list li' ), $subheaders = this.$( 'h3' ); - this.$( 'ul li' ).each( function() { - var $choice = $( this ); - if ( $choice.find( 'span' ).text().toLowerCase().indexOf( val ) > -1 ) { - matches += 1; - $choice.show(); - } else { - $choice.hide(); - } - } ); - - return matches; + if ( val ) { + $subheaders.hide(); + $items.each( function() { + var $item = $( this ); + if ( $item.find( 'span' ).text().toLowerCase().indexOf( val ) > -1 ) { + $item.show(); + } else { + $item.hide(); + } + } ); + } else { + $subheaders.show(); + $items.show(); + } }, + postRender: function( options ) { - var $footer, self = this; + var self = this; this._super( options ); - $footer = this.$( '.mw-mf-overlay-footer' ); this.$( 'ul' ).find( 'a' ).on( 'click', function() { M.emit( 'language-select', $( this ).attr( 'lang' ) ); } ); - this.$( '.search' ).on( 'keyup', function() { - var matches = self.filterLists( this.value.toLowerCase() ); - if ( matches > 0 ) { - $footer.hide(); - } else { - $footer.show(); - } + this.$( '.search' ).on( 'input', function() { + self.filterLists( $( this ).val().toLowerCase() ); } ); } } ); + M.define( 'languages/LanguageOverlay', LanguageOverlay ); }( mw.mobileFrontend, jQuery ) ); diff --git a/javascripts/modules/languages/languages.js b/javascripts/modules/languages/languages.js deleted file mode 100644 index 481f343..0000000 --- a/javascripts/modules/languages/languages.js +++ /dev/null @@ -1,50 +0,0 @@ -( function( M, $ ) { - - var LanguageOverlay = M.require( 'languages/LanguageOverlay' ); - - /** - * Takes a list of languages and transforms them into an array for use in a LanguageOverlay - * - * @returns {Array} - */ - function parseList( $list ) { - var list = []; - $list.find( 'li' ).each( function() { - var $a = $( this ).find( 'a' ), lang, pageName = $a.attr( 'title' ); - - lang = { lang: $a.attr( 'lang' ), langName: $a.text(), url: $a.attr( 'href' ) }; - if ( pageName ) { - lang.pageName = pageName; - } - list.push( lang ); - } ); - return list; - } - - function initButton() { - var $section = $( '#mw-mf-language-section' ), - $h2 = $section.find( 'h2' ), - languages = parseList( $section.find( '#mw-mf-language-selection' ) ), - variants = parseList( $section.find( '#mw-mf-language-variant-selection' ) ); - - // assume the current language is not present - if ( languages.length > 0 || variants.length > 1 ) { - $( '<button>' ).text( $h2.text() ). - addClass( 'languageSelector' ). - on( 'click', function() { - new LanguageOverlay( { - variants: variants, - languages: languages - } ).show(); - } ).insertBefore( $section ); - } - $section.remove(); - } - - $( initButton ); - M.on( 'languages-loaded', initButton ); - M.define( 'modules/languages', { - parseList: parseList - } ); - -}( mw.mobileFrontend, jQuery ) ); diff --git a/javascripts/modules/languagesNew/languages.js b/javascripts/modules/languages/languagesBeta.js similarity index 90% rename from javascripts/modules/languagesNew/languages.js rename to javascripts/modules/languages/languagesBeta.js index 845cd36..6723d26 100644 --- a/javascripts/modules/languagesNew/languages.js +++ b/javascripts/modules/languages/languagesBeta.js @@ -1,6 +1,6 @@ -( function( M, $ ) { +( function( M, $ ) { - var LanguageOverlay = M.require( 'languagesNew/LanguageOverlay' ); + var LanguageOverlay = M.require( 'languages/LanguageOverlay' ); M.overlayManager.add( /^\/languages$/, function() { var LoadingOverlay = M.require( 'LoadingOverlayNew' ), diff --git a/javascripts/modules/languages/languagesStable.js b/javascripts/modules/languages/languagesStable.js new file mode 100644 index 0000000..db94993 --- /dev/null +++ b/javascripts/modules/languages/languagesStable.js @@ -0,0 +1,56 @@ +// FIXME: remove this file and mobile.languages.stable module when dynamic +// loading of languages goes to stable +( function( M, $ ) { + var LanguageOverlay = M.require( 'languages/LanguageOverlay' ), + variants, languages; + + M.overlayManager.add( /^\/languages$/, function() { + return new LanguageOverlay( { + languages: languages, + variants: variants + } ); + } ); + + /** + * Takes a list of languages and transforms them into an array for use in a LanguageOverlay + * + * @returns {Array} + */ + function parseList( $list ) { + var list = []; + $list.find( 'li' ).each( function() { + var $a = $( this ).find( 'a' ), lang, pageName = $a.attr( 'title' ); + + lang = { lang: $a.attr( 'lang' ), langname: $a.text(), url: $a.attr( 'href' ) }; + if ( pageName ) { + lang.pageName = pageName; + } + list.push( lang ); + } ); + return list; + } + + function initButton() { + var $section = $( '#mw-mf-language-section' ), + $h2 = $section.find( 'h2' ); + + languages = parseList( $section.find( '#mw-mf-language-selection' ) ); + variants = parseList( $section.find( '#mw-mf-language-variant-selection' ) ); + + // assume the current language is not present + if ( languages.length > 0 || variants.length > 1 ) { + $( '<button>' ).text( $h2.text() ). + addClass( 'languageSelector' ). + on( 'click', function() { + M.router.navigate( '/languages' ); + } ).insertBefore( $section ); + } + $section.remove(); + } + + $( initButton ); + M.on( 'page-loaded', initButton ); + + M.define( 'modules/languages/languagesStable', { parseList: parseList } ); + +}( mw.mobileFrontend, jQuery ) ); diff --git a/javascripts/modules/languagesNew/preferred.js b/javascripts/modules/languages/preferred.js similarity index 100% rename from javascripts/modules/languagesNew/preferred.js rename to javascripts/modules/languages/preferred.js diff --git a/javascripts/modules/languagesNew/LanguageOverlay.js b/javascripts/modules/languagesNew/LanguageOverlay.js deleted file mode 100644 index ba19ab8..0000000 --- a/javascripts/modules/languagesNew/LanguageOverlay.js +++ /dev/null @@ -1,61 +0,0 @@ -( function( M, $ ) { - - var OverlayNew = M.require( 'OverlayNew' ), - LanguageOverlay; - - LanguageOverlay = OverlayNew.extend( { - defaults: { - heading: mw.msg( 'mobile-frontend-language-heading' ), - placeholder: mw.msg( 'mobile-frontend-language-site-choose' ) - }, - className: 'language-overlay overlay', - templatePartials: { - content: M.template.get( 'modules/languagesNew/LanguageOverlay' ) - }, - - initialize: function( options ) { - if ( options.languages && options.languages.length ) { - options.header = mw.msg( 'mobile-frontend-language-header', options.languages.length ); - } - if ( options.variants && options.variants.length ) { - options.variantHeader = mw.msg( 'mobile-frontend-language-variant-header' ); - } - M.emit( 'language-overlay-initialize', options ); - this._super( options ); - }, - - filterLists: function( val ) { - var $items = this.$( '.page-list li' ), $subheaders = this.$( 'h3' ); - - if ( val ) { - $subheaders.hide(); - $items.each( function() { - var $item = $( this ); - if ( $item.find( 'span' ).text().toLowerCase().indexOf( val ) > -1 ) { - $item.show(); - } else { - $item.hide(); - } - } ); - } else { - $subheaders.show(); - $items.show(); - } - }, - - postRender: function( options ) { - var self = this; - this._super( options ); - - this.$( 'ul' ).find( 'a' ).on( 'click', function() { - M.emit( 'language-select', $( this ).attr( 'lang' ) ); - } ); - this.$( '.search' ).on( 'input', function() { - self.filterLists( $( this ).val().toLowerCase() ); - } ); - } - } ); - - M.define( 'languagesNew/LanguageOverlay', LanguageOverlay ); - -}( mw.mobileFrontend, jQuery ) ); diff --git a/less/modules/languages.less b/less/modules/languages.less deleted file mode 100644 index d8f9481..0000000 --- a/less/modules/languages.less +++ /dev/null @@ -1,14 +0,0 @@ -@import "../mixins.less"; - -button.languageSelector { - margin-top: @headingMargin; -} - -// remove bottom padding from language lists with variants -.language-overlay > ul { - padding-bottom: 0; - // highlight commonly used languages (alpha) - .preferred { - font-weight: bold; - } -} diff --git a/templates/modules/languagesNew/LanguageOverlay.html b/templates/modules/languages/LanguageOverlay.html similarity index 100% rename from templates/modules/languagesNew/LanguageOverlay.html rename to templates/modules/languages/LanguageOverlay.html diff --git a/tests/browser/features/language.feature b/tests/browser/features/language.feature index 0e715cc..861963a 100644 --- a/tests/browser/features/language.feature +++ b/tests/browser/features/language.feature @@ -1,7 +1,18 @@ +# FIXME: this assumes that the main page has more than one language @en.m.wikipedia.beta.wmflabs.org @en.m.wikipedia.org @test2.m.wikipedia.org -Feature: Language Validation +Feature: Language selection - Scenario: Validate Language selection availability + Background: Given I am on the home page When I click the language button - Then I move to the language screen + + Scenario: Opening language overlay + Then I see the language overlay + + Scenario: Closing language overlay (overlay button) + When I click the language overlay close button + Then I don't see the languages overlay + + Scenario: Closing language overlay (browser button) + When I click the browser back button + Then I don't see the languages overlay diff --git a/tests/browser/features/step_definitions/language_steps.rb b/tests/browser/features/step_definitions/language_steps.rb index 65935cd..bcfbad2 100644 --- a/tests/browser/features/step_definitions/language_steps.rb +++ b/tests/browser/features/step_definitions/language_steps.rb @@ -2,10 +2,14 @@ on(HomePage).language_button_element.when_present.click end -Then /^I move to the language screen$/ do - on(LanguagePage) do |page| - page.number_languages_element.element.should exist - page.search_box_placeholder_element.element.when_present.set "Esp" - page.language_search_results_element.element.should exist - end +Then(/^I see the language overlay$/) do + on(HomePage).language_overlay_element.should be_visible +end + +When(/^I click the language overlay close button$/) do + on(HomePage).language_overlay_close_button_element.click +end + +Then(/^I don't see the languages overlay$/) do + on(HomePage).language_overlay_element.should_not be_visible end diff --git a/tests/browser/features/support/pages/home_page.rb b/tests/browser/features/support/pages/home_page.rb index 6882a20..5f43481 100644 --- a/tests/browser/features/support/pages/home_page.rb +++ b/tests/browser/features/support/pages/home_page.rb @@ -36,7 +36,13 @@ div(:watch_note_removed, text: "Removed San Francisco Chronicle from your watchlist") a(:watched_link, class: "watch-this-article watched") a(:create_account, class: "mw-mf-create-account") + button(:language_button, text: "Read in another language") + div(:language_overlay, class: "language-overlay") + button(:language_overlay_close_button) do |page| + page.language_overlay_element.button_element(class: "cancel") + end + li(:edit_icon, id:"ca-edit") li(:upload_icon, id:"ca-upload") div(:fe_notification, class:"drawer position-fixed visible") diff --git a/tests/javascripts/modules/languages/test_languages.js b/tests/javascripts/modules/languages/test_languagesStable.js similarity index 95% rename from tests/javascripts/modules/languages/test_languages.js rename to tests/javascripts/modules/languages/test_languagesStable.js index 9c47289..328ae4d 100644 --- a/tests/javascripts/modules/languages/test_languages.js +++ b/tests/javascripts/modules/languages/test_languagesStable.js @@ -1,6 +1,6 @@ ( function ( M, $ ) { -var module = M.require( 'modules/languages' ); +var module = M.require( 'modules/languages/languagesStable' ); QUnit.module( 'MobileFrontend: languages.js' ); @@ -14,17 +14,17 @@ [ '<ul><li class="interwiki-de"><a href="//de.m.wikipedia.org/wiki/Unicorn" title="Unicorn" lang="de" hreflang="de">Deutsch</a></li><li class="interwiki-es"><a href="//es.m.wikipedia.org/wiki/Unicornio_(desambiguaci%C3%B3n)" title="Unicornio (desambiguación)" lang="es" hreflang="es">Español</a></li><li class="interwiki-fa"><a href="//fa.m.wikipedia.org/wiki/%D8%AA%DA%A9%E2%80%8C%D8%B4%D8%A7%D8%AE_(%D8%A7%D8%A8%D9%87%D8%A7%D9%85%E2%80%8C%D8%B2%D8%AF%D8%A7%DB%8C%DB%8C)" title="" lang="fa" hreflang="fa">فارسی</a></li><li class="interwiki-fr"><a href="//fr.m.wikipedia.org/wiki/Unicorn" title="Unicorn" lang="fr" hreflang="fr">Français</a></li><li class="interwiki-ko"><a href="//ko.m.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%98_(%EB%8F%99%EC%9D%8C%EC%9D%B4%EC%9D%98)" title="유니콘 (동음이의)" lang="ko" hreflang="ko">한국어</a></li><li class="interwiki-it"><a href="//it.m.wikipedia.org/wiki/Unicorn" title="Unicorn" lang="it" hreflang="it">Italiano</a></li><li class="interwiki-he"><a href="//he.m.wikipedia.org/wiki/%D7%97%D7%93-%D7%A7%D7%A8%D7%9F_(%D7%A4%D7%99%D7%A8%D7%95%D7%A9%D7%95%D7%A0%D7%99%D7%9D)" title="חד-קרן (פירושונים)" lang="he" hreflang="he">עברית</a></li><li class="interwiki-la"><a href="//la.m.wikipedia.org/wiki/Monoceros" title="Monoceros" lang="la" hreflang="la">Latina</a></li><li class="interwiki-lt"><a href="//lt.m.wikipedia.org/wiki/Vienaragis_(reik%C5%A1m%C4%97s)" title="Vienaragis (reikšmės)" lang="lt" hreflang="lt">Lietuvių</a></li><li class="interwiki-nl"><a href="//nl.m.wikipedia.org/wiki/Eenhoorn" title="Eenhoorn" lang="nl" hreflang="nl">Nederlands</a></li><li class="interwiki-ja"><a href="//ja.m.wikipedia.org/wiki/%E3%83%A6%E3%83%8B%E3%82%B3%E3%83%BC%E3%83%B3_(%E6%9B%96%E6%98%A7%E3%81%95%E5%9B%9E%E9%81%BF)" title="ユニコーン (曖昧さ回避)" lang="ja" hreflang="ja">日本語</a></li><li class="interwiki-pl"><a href="//pl.m.wikipedia.org/wiki/Jednoro%C5%BCec_(ujednoznacznienie)" title="Jednorożec (ujednoznacznienie)" lang="pl" hreflang="pl">Polski</a></li><li class="interwiki-pt"><a href="//pt.m.wikipedia.org/wiki/Unic%C3%B3rnio_(desambigua%C3%A7%C3%A3o)" title="Unicórnio (desambiguação)" lang="pt" hreflang="pt">Português</a></li><li class="interwiki-ru"><a href="//ru.m.wikipedia.org/wiki/%D0%95%D0%B4%D0%B8%D0%BD%D0%BE%D1%80%D0%BE%D0%B3_(%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F)" title="Единорог (значения)" lang="ru" hreflang="ru">Русский</a></li><li class="interwiki-fi"><a href="//fi.m.wikipedia.org/wiki/Unicorn" title="Unicorn" lang="fi" hreflang="fi">Suomi</a></li><li class="interwiki-uk"><a href="//uk.m.wikipedia.org/wiki/%D0%84%D0%B4%D0%B8%D0%BD%D0%BE%D1%80%D1%96%D0%B3_(%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%BD%D1%8F)" title="Єдиноріг (значення)" lang="uk" hreflang="uk">Українська</a></li></ul>', 16, - { url: '//de.m.wikipedia.org/wiki/Unicorn', lang: 'de', langName: 'Deutsch', pageName: 'Unicorn' } + { url: '//de.m.wikipedia.org/wiki/Unicorn', lang: 'de', langname: 'Deutsch', pageName: 'Unicorn' } ], [ '<ul><li><a href="//fr.m.wikipedia.org/wiki/Bonjour" lang="fr" title="Bonjour">French</a></li></ul>', 1, - { url: '//fr.m.wikipedia.org/wiki/Bonjour', lang: 'fr', langName: 'French', pageName: 'Bonjour' } + { url: '//fr.m.wikipedia.org/wiki/Bonjour', lang: 'fr', langname: 'French', pageName: 'Bonjour' } ], [ '<ul><li><a href="//klingon.m.wikipedia.org/wiki/MainPage" lang="klz">Klingon</a></li></ul>', 1, - { url: '//klingon.m.wikipedia.org/wiki/MainPage', lang: 'klz', langName: 'Klingon' } + { url: '//klingon.m.wikipedia.org/wiki/MainPage', lang: 'klz', langname: 'Klingon' } ] ]; QUnit.expect( examples.length * 2 ); -- To view, visit https://gerrit.wikimedia.org/r/106854 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I73f1f69dd8be27b6893276a5394751e924b2f1ab Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/MobileFrontend Gerrit-Branch: master Gerrit-Owner: JGonera <jgon...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits