Jdlrobson has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/332924 )
Change subject: WIP: Building of common code is done by webpack ...................................................................... WIP: Building of common code is done by webpack Change-Id: I2e3e71a3b709aa029e1087e442178ba13c1e9d0b --- R build_resources/mobile.frontend/Browser.js A build_resources/mobile.frontend/index.js A build_resources/mobile.frontend/util.js M extension.json M includes/MobileFrontend.hooks.php M package.json M resources/mobile.editor.common/EditorOverlayBase.js A resources/mobile.frontend/index.js M resources/mobile.mainMenu/MainMenu.js M resources/mobile.nearby/Nearby.js M resources/mobile.overlays/Overlay.js M resources/mobile.pagelist/PageList.js M resources/mobile.startup/Skin.js D resources/mobile.startup/util.js M resources/mobile.toggle/toggle.js M resources/skins.minerva.backtotop/init.js M resources/skins.minerva.newusers/init.js M resources/skins.minerva.scripts/search.js R tests/qunit/mobile.frontend/test_browser.js M tests/qunit/mobile.toggle/test_toggle.js A webpack.config.js 21 files changed, 408 insertions(+), 75 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MobileFrontend refs/changes/24/332924/1 diff --git a/resources/mobile.browser/browser.js b/build_resources/mobile.frontend/Browser.js similarity index 98% rename from resources/mobile.browser/browser.js rename to build_resources/mobile.frontend/Browser.js index 1ac1ade..d72cfad 100644 --- a/resources/mobile.browser/browser.js +++ b/build_resources/mobile.frontend/Browser.js @@ -205,5 +205,5 @@ return browser; }; - M.define( 'mobile.browser/Browser', Browser ); + module.exports = Browser; }( mw.mobileFrontend, jQuery ) ); diff --git a/build_resources/mobile.frontend/index.js b/build_resources/mobile.frontend/index.js new file mode 100644 index 0000000..3ad67d0 --- /dev/null +++ b/build_resources/mobile.frontend/index.js @@ -0,0 +1,4 @@ +module.exports = mediaWiki.mf = { + Browser: require( './Browser' ), + util: require( './util.js' ) +}; diff --git a/build_resources/mobile.frontend/util.js b/build_resources/mobile.frontend/util.js new file mode 100644 index 0000000..e82339d --- /dev/null +++ b/build_resources/mobile.frontend/util.js @@ -0,0 +1,41 @@ +var util; + +/** + * Utility library + * @class util + * @singleton + */ +util = { + /** + * Escape dots and colons in a hash, jQuery doesn't like them beause they + * look like CSS classes and pseudoclasses. See + * http://bugs.jquery.com/ticket/5241 + * http://stackoverflow.com/questions/350292/how-do-i-get-jquery-to-select-elements-with-a-period-in-their-id + * + * @method + * @param {string} hash A hash to escape + * @return {string} + */ + escapeHash: function ( hash ) { + return hash.replace( /(:|\.)/g, '\\$1' ); + }, + /** + * Return wgWikiBaseItemID config variable or 'wikidataid' query parameter if exits + * @return {null|string} + */ + getWikiBaseItemId: function () { + var id = mw.config.get( 'wgWikibaseItemId' ), + idOverride; + + if ( !id ) { + idOverride = mw.util.getParamValue( 'wikidataid' ); + if ( idOverride ) { + mw.config.set( 'wgWikibaseItemId', idOverride ); + id = idOverride; + } + } + return id; + } +}; + +module.exports = util; diff --git a/extension.json b/extension.json index 4e27aa9..2c0ad61 100644 --- a/extension.json +++ b/extension.json @@ -331,18 +331,6 @@ "resources/mobile.context/context.js" ] }, - "mobile.browser": { - "targets": [ - "mobile", - "desktop" - ], - "dependencies": [ - "mobile.view" - ], - "scripts": [ - "resources/mobile.browser/browser.js" - ] - }, "mobile.mainMenu.icons": { "class": "ResourceLoaderImageModule", "selector": ".mw-ui-icon-mf-{name}:before", @@ -366,7 +354,7 @@ "dependencies": [ "mobile.mainMenu.icons", "mobile.view", - "mobile.browser", + "mobile.frontend", "mobile.loggingSchemas.mobileWebMainMenuClickTracking" ], "position": "bottom", @@ -444,7 +432,7 @@ ], "dependencies": [ "mobile.view", - "mobile.browser", + "mobile.frontend", "mobile.pagelist.styles", "mobile.pagesummary.styles" ], @@ -551,15 +539,24 @@ "BackToTopOverlay.hogan": "resources/mobile.backtotop/BackToTopOverlay.hogan" } }, + "mobile.frontend": { + "targets": [ + "mobile", + "desktop" + ], + "scripts": [ + "resources/mobile.frontend/index.js" + ] + }, "mobile.startup": { "targets": [ "mobile", "desktop" ], "dependencies": [ + "mobile.frontend", "mobile.context", "mobile.modifiedBar", - "mobile.browser", "mobile.oo", "mobile.user", "mediawiki.util", @@ -584,7 +581,6 @@ "resources/mobile.startup/panel.less" ], "scripts": [ - "resources/mobile.startup/util.js", "resources/mobile.startup/PageGateway.js", "resources/mobile.startup/Anchor.js", "resources/mobile.startup/Button.js", @@ -1681,7 +1677,7 @@ ], "dependencies": [ "mobile.backtotop", - "mobile.browser" + "mobile.frontend" ], "scripts": [ "resources/skins.minerva.backtotop/init.js" diff --git a/includes/MobileFrontend.hooks.php b/includes/MobileFrontend.hooks.php index b5c899a..4dd9bc1 100644 --- a/includes/MobileFrontend.hooks.php +++ b/includes/MobileFrontend.hooks.php @@ -326,6 +326,9 @@ } } + $dependencies[] = 'mobile.frontend'; + $testFiles[] = 'tests/qunit/mobile.frontend/test_browser.js'; + $testModule = [ 'dependencies' => $dependencies, 'templates' => [ diff --git a/package.json b/package.json index d5c8a7d..0234171 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "private": true, "scripts": { "test": "grunt test && npm run doc", + "build": "webpack", "predoc": "bundle install --path vendor/bundle", "doc": "bundle exec jsduck" }, diff --git a/resources/mobile.editor.common/EditorOverlayBase.js b/resources/mobile.editor.common/EditorOverlayBase.js index c9d71f3..48032fd 100644 --- a/resources/mobile.editor.common/EditorOverlayBase.js +++ b/resources/mobile.editor.common/EditorOverlayBase.js @@ -1,7 +1,7 @@ ( function ( M, $ ) { var Overlay = M.require( 'mobile.overlays/Overlay' ), PageGateway = M.require( 'mobile.startup/PageGateway' ), - browser = M.require( 'mobile.browser/Browser' ).getSingleton(), + browser = mw.mf.Browser.getSingleton(), Icon = M.require( 'mobile.startup/Icon' ), toast = M.require( 'mobile.toast/toast' ), user = M.require( 'mobile.user/user' ); diff --git a/resources/mobile.frontend/index.js b/resources/mobile.frontend/index.js new file mode 100644 index 0000000..5923292 --- /dev/null +++ b/resources/mobile.frontend/index.js @@ -0,0 +1,316 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + module.exports = mediaWiki.mf = { + Browser: __webpack_require__( 1 ), + util: __webpack_require__( 2 ) + }; + + +/***/ }, +/* 1 */ +/***/ function(module, exports) { + + ( function ( M, $ ) { + var browser; + + /** + * Memoize a class method. Caches the result of the method based on the + * arguments. Instances do not share a cache. + * @ignore + * @param {Function} method Method to be memoized + * @return {Function} + */ + function memoize( method ) { + /** + * Memoized version of the method + * @ignore + * @return {Function} + */ + var memoized = function () { + var cache = this[ '__cache' + memoized.cacheId ] || + ( this[ '__cache' + memoized.cacheId ] = {} ), + key = [].join.call( arguments, '|' ); + if ( cache.hasOwnProperty( key ) ) { + return cache[ key ]; + } + return ( cache[ key ] = method.apply( this, arguments ) ); + }; + memoized.cacheId = Date.now().toString() + Math.random().toString(); + return memoized; + } + + /** + * Representation of user's current browser + * @class Browser + * @param {string} ua the user agent of the current browser + * @param {jQuery.Object} $container an element to associate with the Browser object + */ + function Browser( ua, $container ) { + this.userAgent = ua; + this.$el = $container; + if ( this.isAndroid2() ) { + this.lockViewport(); + } + this._fixIosLandscapeBug(); + } + + Browser.prototype = { + /** + * When rotating to landscape stop page zooming on ios 4 and 5. + * @private + */ + _fixIosLandscapeBug: function () { + var self = this, + viewport = this.$el.find( 'meta[name="viewport"]' )[0]; + + // see http://adactio.com/journal/4470/ (fixed in ios 6) + if ( viewport && ( this.isIos( 4 ) || this.isIos( 5 ) ) ) { + this.lockViewport(); + document.addEventListener( 'gesturestart', function () { + self.lockViewport(); + }, false ); + } + }, + /** + * Returns whether the current browser is an ios device. + * FIXME: jquery.client does not support iPad detection so we cannot use it. + * @param {number} [version] integer describing a specific version you want to test against. + * @return {boolean} + */ + isIos: memoize( function ( version ) { + var ua = this.userAgent, + ios = /ipad|iphone|ipod/i.test( ua ); + + if ( ios && version ) { + switch ( version ) { + case 8: + // Test UA for iOS8. Or for simulator look for Version 8 + // In the iOS simulator the OS is the host machine OS version + // This makes testing in iOS8 simulator work as expected + return /OS 8_/.test( ua ) || /Version\/8/.test( ua ); + case 4: + return /OS 4_/.test( ua ); + case 5: + return /OS 5_/.test( ua ); + default: + return false; + } + } else { + return ios; + } + } ), + /** + * Locks the viewport so that pinch zooming is disabled + */ + lockViewport: function () { + if ( this.$el ) { + this.$el.find( 'meta[name="viewport"]' ) + .attr( 'content', 'initial-scale=1.0, maximum-scale=1.0, user-scalable=no' ); + } + }, + /** + * Determine if a device is Android 2. + * @method + * @return {boolean} + */ + isAndroid2: memoize( function () { + return /Android 2/.test( this.userAgent ); + } ), + /** + * Determine if a device has a widescreen. + * @method + * @return {boolean} + */ + isWideScreen: memoize( function () { + var val = parseInt( mw.config.get( 'wgMFDeviceWidthTablet' ), 10 ); + // Check portrait and landscape mode to be consistent + return window.innerWidth >= val || window.innerHeight >= val; + } ), + /** + * Checks browser support for a given CSS property + * @param {string} [property] the name of the property being tested + * @return {boolean} + */ + supportsCSSProperty: memoize( function ( property ) { + var elem = document.createElement( 'foo' ); + + // We only test webkit because that's the only prefix needed at the moment by + // supportsAnimations. If usage of supportsCSSProperty is expanded, the list of prefixes + // will need to be as well + return elem.style[ property ] !== undefined || + elem.style[ 'webkit' + property.charAt( 0 ).toUpperCase() + property.slice( 1 ) ] !== undefined; + } ), + /** + * Checks browser support for CSS transforms, transitions + * and CSS animation. + * Currently assumes support for the latter 2 in the case of the + * former. + * See http://stackoverflow.com/a/12621264/365238 + * + * @return {boolean} + */ + supportsAnimations: memoize( function () { + // don't trust Android 2.x, really + // animations cause textareas to misbehave on it + // (http://stackoverflow.com/a/5734984/365238) + if ( this.isAndroid2() ) { + return false; + } + + return this.supportsCSSProperty( 'animationName' ) && + this.supportsCSSProperty( 'transform' ) && + this.supportsCSSProperty( 'transition' ); + } ), + /** + * Whether touchstart and other touch events are supported by the current browser. + * + * @method + * @return {boolean} + */ + supportsTouchEvents: memoize( function () { + return 'ontouchstart' in window; + } ), + /** + * Detect if browser supports geolocation + * @method + * @return {boolean} + */ + supportsGeoLocation: memoize( function () { + return 'geolocation' in navigator; + } ), + /** + * Detect if we support file input uploads + * @return {boolean} + */ + supportsFileUploads: memoize( function () { + var browserSupported; + // If already calculated, just return it + if ( this._fileUploads !== undefined ) { + return this._fileUploads; + } + + // deal with known false positives which don't support file input (bug 47374) + if ( this.userAgent.match( /Windows Phone (OS 7|8.0)/ ) ) { + this._fileUploads = false; + } else { + browserSupported = ( + typeof FileReader !== 'undefined' && + typeof FormData !== 'undefined' && + // Firefox OS 1.0 turns <input type="file"> into <input type="text"> + ( $( '<input type="file"/>' ).prop( 'type' ) === 'file' ) + ); + this._fileUploads = browserSupported && + !mw.config.get( 'wgImagesDisabled', false ); + } + return this._fileUploads; + } ) + }; + + /** + * @static + * @return {Browser} + */ + Browser.getSingleton = function () { + if ( !browser ) { + browser = new Browser( window.navigator.userAgent, $( 'html' ) ); + } + return browser; + }; + + module.exports = Browser; + }( mw.mobileFrontend, jQuery ) ); + + +/***/ }, +/* 2 */ +/***/ function(module, exports) { + + var util; + + /** + * Utility library + * @class util + * @singleton + */ + util = { + /** + * Escape dots and colons in a hash, jQuery doesn't like them beause they + * look like CSS classes and pseudoclasses. See + * http://bugs.jquery.com/ticket/5241 + * http://stackoverflow.com/questions/350292/how-do-i-get-jquery-to-select-elements-with-a-period-in-their-id + * + * @method + * @param {string} hash A hash to escape + * @return {string} + */ + escapeHash: function ( hash ) { + return hash.replace( /(:|\.)/g, '\\$1' ); + }, + /** + * Return wgWikiBaseItemID config variable or 'wikidataid' query parameter if exits + * @return {null|string} + */ + getWikiBaseItemId: function () { + var id = mw.config.get( 'wgWikibaseItemId' ), + idOverride; + + if ( !id ) { + idOverride = mw.util.getParamValue( 'wikidataid' ); + if ( idOverride ) { + mw.config.set( 'wgWikibaseItemId', idOverride ); + id = idOverride; + } + } + return id; + } + }; + + module.exports = util; + + +/***/ } +/******/ ]); \ No newline at end of file diff --git a/resources/mobile.mainMenu/MainMenu.js b/resources/mobile.mainMenu/MainMenu.js index ef76296..f26fa34 100644 --- a/resources/mobile.mainMenu/MainMenu.js +++ b/resources/mobile.mainMenu/MainMenu.js @@ -1,5 +1,5 @@ ( function ( M, $ ) { - var browser = M.require( 'mobile.browser/Browser' ).getSingleton(), + var browser = mw.mf.Browser.getSingleton(), View = M.require( 'mobile.view/View' ); /** diff --git a/resources/mobile.nearby/Nearby.js b/resources/mobile.nearby/Nearby.js index c9f46fa..156d0e7 100644 --- a/resources/mobile.nearby/Nearby.js +++ b/resources/mobile.nearby/Nearby.js @@ -2,7 +2,7 @@ var MessageBox = M.require( 'mobile.messageBox/MessageBox' ), NearbyGateway = M.require( 'mobile.nearby/NearbyGateway' ), WatchstarPageList = M.require( 'mobile.pagelist.scripts/WatchstarPageList' ), - browser = M.require( 'mobile.browser/Browser' ).getSingleton(), + browser = mw.mf.Browser.getSingleton(), icons = M.require( 'mobile.startup/icons' ); /** diff --git a/resources/mobile.overlays/Overlay.js b/resources/mobile.overlays/Overlay.js index 8f2eeb3..fc56c57 100644 --- a/resources/mobile.overlays/Overlay.js +++ b/resources/mobile.overlays/Overlay.js @@ -5,7 +5,7 @@ Button = M.require( 'mobile.startup/Button' ), Anchor = M.require( 'mobile.startup/Anchor' ), icons = M.require( 'mobile.startup/icons' ), - browser = M.require( 'mobile.browser/Browser' ).getSingleton(), + browser = mw.mf.Browser.getSingleton(), $window = $( window ); /** diff --git a/resources/mobile.pagelist/PageList.js b/resources/mobile.pagelist/PageList.js index 869c60d..1644d3a 100644 --- a/resources/mobile.pagelist/PageList.js +++ b/resources/mobile.pagelist/PageList.js @@ -1,7 +1,7 @@ ( function ( M, $ ) { var View = M.require( 'mobile.view/View' ), - browser = M.require( 'mobile.browser/Browser' ).getSingleton(); + browser = mw.mf.Browser.getSingleton(); /** * List of items page view diff --git a/resources/mobile.startup/Skin.js b/resources/mobile.startup/Skin.js index d53789d..b9eb356 100644 --- a/resources/mobile.startup/Skin.js +++ b/resources/mobile.startup/Skin.js @@ -1,6 +1,6 @@ ( function ( M, $ ) { - var browser = M.require( 'mobile.browser/Browser' ).getSingleton(), + var browser = mw.mf.Browser.getSingleton(), View = M.require( 'mobile.view/View' ), icons = M.require( 'mobile.startup/icons' ); diff --git a/resources/mobile.startup/util.js b/resources/mobile.startup/util.js deleted file mode 100644 index 95d7bc8..0000000 --- a/resources/mobile.startup/util.js +++ /dev/null @@ -1,44 +0,0 @@ -( function ( M ) { - var util; - - /** - * Utility library - * @class util - * @singleton - */ - util = { - /** - * Escape dots and colons in a hash, jQuery doesn't like them beause they - * look like CSS classes and pseudoclasses. See - * http://bugs.jquery.com/ticket/5241 - * http://stackoverflow.com/questions/350292/how-do-i-get-jquery-to-select-elements-with-a-period-in-their-id - * - * @method - * @param {string} hash A hash to escape - * @return {string} - */ - escapeHash: function ( hash ) { - return hash.replace( /(:|\.)/g, '\\$1' ); - }, - /** - * Return wgWikiBaseItemID config variable or 'wikidataid' query parameter if exits - * @return {null|string} - */ - getWikiBaseItemId: function () { - var id = mw.config.get( 'wgWikibaseItemId' ), - idOverride; - - if ( !id ) { - idOverride = mw.util.getParamValue( 'wikidataid' ); - if ( idOverride ) { - mw.config.set( 'wgWikibaseItemId', idOverride ); - id = idOverride; - } - } - return id; - } - }; - - M.define( 'mobile.startup/util', util ); - -}( mw.mobileFrontend, jQuery ) ); diff --git a/resources/mobile.toggle/toggle.js b/resources/mobile.toggle/toggle.js index 5730130..3ed92da 100644 --- a/resources/mobile.toggle/toggle.js +++ b/resources/mobile.toggle/toggle.js @@ -1,8 +1,8 @@ ( function ( M, $ ) { var context = M.require( 'mobile.context/context' ), settings = M.require( 'mobile.settings/settings' ), - browser = M.require( 'mobile.browser/Browser' ).getSingleton(), - escapeHash = M.require( 'mobile.startup/util' ).escapeHash, + browser = mw.mf.Browser.getSingleton(), + escapeHash = mw.mf.util.escapeHash, arrowOptions = { name: 'arrow', additionalClassNames: 'indicator' diff --git a/resources/skins.minerva.backtotop/init.js b/resources/skins.minerva.backtotop/init.js index 0b2723e..6483b32 100644 --- a/resources/skins.minerva.backtotop/init.js +++ b/resources/skins.minerva.backtotop/init.js @@ -1,7 +1,7 @@ ( function ( M, $ ) { var BackToTopOverlay = M.require( 'mobile.backtotop/BackToTopOverlay' ), backtotop = new BackToTopOverlay(), - browser = M.require( 'mobile.browser/Browser' ).getSingleton(); + browser = mw.mf.Browser.getSingleton(); // check if browser user agent is iOS (T141598) if ( browser.isIos() ) { diff --git a/resources/skins.minerva.newusers/init.js b/resources/skins.minerva.newusers/init.js index 586acde..9ef4b3e 100644 --- a/resources/skins.minerva.newusers/init.js +++ b/resources/skins.minerva.newusers/init.js @@ -12,7 +12,7 @@ ( function ( M, $ ) { var PageActionOverlay = require( 'mobile.pointerOverlay' ), util = M.require( 'mobile.startup/util' ), - escapeHash = util.escapeHash, + escapeHash = mw.mf.util.escapeHash, inEditor = window.location.hash.indexOf( '#editor/' ) > -1, hash = window.location.hash, editOverlay, target, $target, href; diff --git a/resources/skins.minerva.scripts/search.js b/resources/skins.minerva.scripts/search.js index 6caeef5..2c74535 100644 --- a/resources/skins.minerva.scripts/search.js +++ b/resources/skins.minerva.scripts/search.js @@ -1,7 +1,7 @@ ( function ( M, $ ) { var SearchOverlay, SearchGateway, router = require( 'mediawiki.router' ), - browser = M.require( 'mobile.browser/Browser' ).getSingleton(); + browser = mw.mf.Browser.getSingleton(); /** * Reveal the search overlay diff --git a/tests/qunit/mobile.browser/test_browser.js b/tests/qunit/mobile.frontend/test_browser.js similarity index 98% rename from tests/qunit/mobile.browser/test_browser.js rename to tests/qunit/mobile.frontend/test_browser.js index 39e7817..bf2e400 100644 --- a/tests/qunit/mobile.browser/test_browser.js +++ b/tests/qunit/mobile.frontend/test_browser.js @@ -1,5 +1,5 @@ ( function ( $, M ) { - var Browser = M.require( 'mobile.browser/Browser' ), + var Browser = mw.mf.Browser, // Use an empty html element to avoid calling methods in _fixIosLandscapeBug $html = $( '<html>' ); diff --git a/tests/qunit/mobile.toggle/test_toggle.js b/tests/qunit/mobile.toggle/test_toggle.js index 76998ba..4b628f5 100644 --- a/tests/qunit/mobile.toggle/test_toggle.js +++ b/tests/qunit/mobile.toggle/test_toggle.js @@ -3,7 +3,7 @@ var toggle, sectionHtml = mw.template.get( 'tests.mobilefrontend', 'section.hogan' ).render(), settings = M.require( 'mobile.settings/settings' ), - browser = M.require( 'mobile.browser/Browser' ).getSingleton(), + browser = mw.mf.Browser.getSingleton(), page = { title: 'Toggle test' }, Toggler = M.require( 'mobile.toggle/Toggler' ); diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..5b855f3 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,16 @@ +var path = require( 'path' ); +var webpack = require( 'webpack' ); + +module.exports = { + output: { + // The absolute path to the output directory. + path: path.resolve( __dirname, 'resources/' ), + + // Write each chunk (entries, here) to a file named after the entry, e.g. + // the "index" entry gets written to index.js. + filename: '/[name]/index.js' + }, + entry: { + 'mobile.frontend': './build_resources/mobile.frontend/index.js' + } +}; -- To view, visit https://gerrit.wikimedia.org/r/332924 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I2e3e71a3b709aa029e1087e442178ba13c1e9d0b Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/MobileFrontend Gerrit-Branch: mfui Gerrit-Owner: Jdlrobson <jrob...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits