Jdlrobson has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/332928 )
Change subject: Hygiene: Skin moved to mobile.frontend library ...................................................................... Hygiene: Skin moved to mobile.frontend library Change-Id: Ieb5cb151c009b40f41b4f6acbd0f33feac876914 --- R build_resources/mobile.frontend/Skin.js M build_resources/mobile.frontend/index.js M extension.json M includes/MobileFrontend.hooks.php M resources/mobile.frontend/index.js M resources/skins.minerva.scripts/preInit.js R tests/qunit/mobile.frontend/test_Skin.js 7 files changed, 393 insertions(+), 8 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MobileFrontend refs/changes/28/332928/1 diff --git a/resources/mobile.startup/Skin.js b/build_resources/mobile.frontend/Skin.js similarity index 98% rename from resources/mobile.startup/Skin.js rename to build_resources/mobile.frontend/Skin.js index 7b4579d..d6fddb1 100644 --- a/resources/mobile.startup/Skin.js +++ b/build_resources/mobile.frontend/Skin.js @@ -1,8 +1,8 @@ ( function ( M, $ ) { - var browser = mw.mf.Browser.getSingleton(), - View = mw.mf.View, - icons = mw.mf.icons; + var browser = require( './Browser' ).getSingleton(), + View = require( './View' ), + icons = require( './icons' ); /** * Get the id of the section $el belongs to. @@ -372,6 +372,6 @@ } ); Skin.getSectionId = getSectionId; - M.define( 'mobile.startup/Skin', Skin ); + module.exports = Skin; }( mw.mobileFrontend, jQuery ) ); diff --git a/build_resources/mobile.frontend/index.js b/build_resources/mobile.frontend/index.js index 5d3dfc3..3f1415c 100644 --- a/build_resources/mobile.frontend/index.js +++ b/build_resources/mobile.frontend/index.js @@ -2,6 +2,7 @@ icons: require( './icons' ), Browser: require( './Browser' ), Icon: require( './Icon' ), + Skin: require( './Skin' ), View: require( './View' ), util: require( './util.js' ) }; diff --git a/extension.json b/extension.json index e1b8ea2..95c08f4 100644 --- a/extension.json +++ b/extension.json @@ -579,7 +579,6 @@ "resources/mobile.startup/Section.js", "resources/mobile.startup/Thumbnail.js", "resources/mobile.startup/Page.js", - "resources/mobile.startup/Skin.js", "resources/mobile.startup/OverlayManager.js" ], "position": "bottom" diff --git a/includes/MobileFrontend.hooks.php b/includes/MobileFrontend.hooks.php index fb6013f..4b1f637 100644 --- a/includes/MobileFrontend.hooks.php +++ b/includes/MobileFrontend.hooks.php @@ -329,6 +329,7 @@ $dependencies[] = 'mobile.frontend'; $testFiles[] = 'tests/qunit/mobile.frontend/test_browser.js'; $testFiles[] = 'tests/qunit/mobile.frontend/test_View.js'; + $testFiles[] = 'tests/qunit/mobile.frontend/test_Skin.js'; $testModule = [ 'dependencies' => $dependencies, diff --git a/resources/mobile.frontend/index.js b/resources/mobile.frontend/index.js index 99fe584..3ab9cb8 100644 --- a/resources/mobile.frontend/index.js +++ b/resources/mobile.frontend/index.js @@ -48,8 +48,9 @@ icons: __webpack_require__( 1 ), Browser: __webpack_require__( 8 ), Icon: __webpack_require__( 2 ), + Skin: __webpack_require__( 9 ), View: __webpack_require__( 3 ), - util: __webpack_require__( 9 ) + util: __webpack_require__( 10 ) }; @@ -1571,6 +1572,389 @@ /***/ }, /* 9 */ +/***/ function(module, exports, __webpack_require__) { + + ( function ( M, $ ) { + + var browser = __webpack_require__( 8 ).getSingleton(), + View = __webpack_require__( 3 ), + icons = __webpack_require__( 1 ); + + /** + * Get the id of the section $el belongs to. + * @param {jQuery.Object} $el + * @return {string|null} either the anchor (id attribute of the section heading + * or null if none found) + * @ignore + */ + function getSectionId( $el ) { + var id, + hSelector = 'h1,h2,h3,h4,h5,h6', + $parent = $el.parent(), + // e.g. matches Subheading in + // <h2>H</h2><div><h3 id="subheading">Subh</h3><a class="element"></a></div> + $heading = $el.prevAll( hSelector ).eq( 0 ); + + if ( $heading.length ) { + id = $heading.find( '.mw-headline' ).attr( 'id' ); + if ( id ) { + return id; + } + } + if ( $parent.length ) { + // if we couldnt find a sibling heading, check the sibling of the parents + // consider <div><h2 /><div><$el/></div></div> + return getSectionId( $parent ); + } else { + return null; + } + } + + /** + * Representation of the current skin being rendered. + * + * @class Skin + * @extends View + * @uses Browser + * @uses Page + * + * @constructor + * @param {Object} options Configuration options + */ + function Skin( options ) { + var self = this; + + this.page = options.page; + this.name = options.name; + this.mainMenu = options.mainMenu; + View.call( this, options ); + // Must be run after merging with defaults as must be defined. + this.tabletModules = options.tabletModules; + this.referencesGateway = options.referencesGateway; + + /** + * Tests current window size and if suitable loads styles and scripts specific for larger devices + * + * @method + * @ignore + */ + function loadWideScreenModules() { + if ( browser.isWideScreen() ) { + // Adjust screen for tablets + if ( self.page.inNamespace( '' ) ) { + mw.loader.using( self.tabletModules ).always( function () { + self.off( '_resize' ); + self.emit.call( self, 'changed' ); + } ); + } + } + } + M.on( 'resize', $.proxy( this, 'emit', '_resize' ) ); + this.on( '_resize', loadWideScreenModules ); + this.emit( '_resize' ); + + if ( + !mw.config.get( 'wgImagesDisabled' ) && + mw.config.get( 'wgMFLazyLoadImages' ) + ) { + $( function () { + self.loadImages(); + } ); + } + + if ( mw.config.get( 'wgMFLazyLoadReferences' ) ) { + M.on( 'before-section-toggled', $.proxy( this.lazyLoadReferences, this ) ); + } + } + + OO.mfExtend( Skin, View, { + /** + * @inheritdoc + * Skin contains components that we do not control + */ + isBorderBox: false, + /** + * @inheritdoc + * @cfg {Object} defaults Default options hash. + * @cfg {Page} defaults.page page the skin is currently rendering + * @cfg {Array} defaults.tabletModules modules to load when in tablet + * @cfg {MainMenu} defaults.mainMenu instance of the mainMenu + * @cfg {ReferencesGateway} defaults.referencesGateway instance of references gateway + */ + defaults: { + page: undefined, + tabletModules: [], + mainMenu: undefined + }, + + /** + * @inheritdoc + */ + events: {}, + + /** + * Close navigation if content tapped + * @param {jQuery.Event} ev + * @private + */ + _onPageCenterClick: function ( ev ) { + var $target = $( ev.target ); + + // Make sure the menu is open and we are not clicking on the menu button + if ( + this.mainMenu.isOpen() && + !$target.hasClass( 'main-menu-button' ) + ) { + this.mainMenu.closeNavigationDrawers(); + ev.preventDefault(); + } + }, + + /** + * @inheritdoc + */ + postRender: function () { + var $el = this.$el; + if ( browser.supportsAnimations() ) { + $el.addClass( 'animations' ); + } + if ( browser.supportsTouchEvents() ) { + $el.addClass( 'touch-events' ); + } + $( '<div class="transparent-shield cloaked-element">' ).appendTo( '#mw-mf-page-center' ); + /** + * @event changed + * Fired when appearance of skin changes. + */ + this.emit( 'changed' ); + // FIXME: Move back into events when T98200 resolved + this.$( '#mw-mf-page-center' ).on( 'click', + $.proxy( this, '_onPageCenterClick' ) ); + }, + + /** + * Return the instance of MainMenu + * @return {MainMenu} + */ + getMainMenu: function () { + return this.mainMenu; + }, + + /** + * Load images on demand + * @param {jQuery.Object} [$container] The container that should be + * searched for image placeholders. Defaults to "#content". + */ + loadImages: function ( $container ) { + var self = this, + offset = $( window ).height() * 1.5, + imagePlaceholders; + + $container = $container || this.$( '#content' ); + imagePlaceholders = $container.find( '.lazy-image-placeholder' ).toArray(); + + /** + * Load remaining images in viewport + */ + function _loadImages() { + + imagePlaceholders = $.grep( imagePlaceholders, function ( placeholder ) { + var $placeholder = $( placeholder ); + + if ( + mw.viewport.isElementCloseToViewport( placeholder, offset ) && + // If a placeholder is an inline element without a height attribute set it will record as hidden + // to circumvent this we also need to test the height (see T143768). + ( $placeholder.is( ':visible' ) || $placeholder.height() === 0 ) + ) { + self.loadImage( $placeholder ); + return false; + } + + return true; + } ); + + if ( !imagePlaceholders.length ) { + M.off( 'scroll:throttled', _loadImages ); + M.off( 'resize:throttled', _loadImages ); + M.off( 'section-toggled', _loadImages ); + self.off( 'changed', _loadImages ); + } + + } + + M.on( 'scroll:throttled', _loadImages ); + M.on( 'resize:throttled', _loadImages ); + M.on( 'section-toggled', _loadImages ); + this.on( 'changed', _loadImages ); + + _loadImages(); + }, + + /** + * Load an image on demand + * @param {jQuery.Object} $placeholder + */ + loadImage: function ( $placeholder ) { + var + width = $placeholder.attr( 'data-width' ), + height = $placeholder.attr( 'data-height' ), + // Image will start downloading + $downloadingImage = $( '<img/>' ); + + // When the image has loaded + $downloadingImage.on( 'load', function () { + // Swap the HTML inside the placeholder (to keep the layout and + // dimensions the same and not trigger layouts + $placeholder.empty().append( $downloadingImage ); + // Set the loaded class after insertion of the HTML to trigger the + // animations. + $placeholder.addClass( 'loaded' ); + } ); + + // Trigger image download after binding the load handler + $downloadingImage.attr( { + 'class': $placeholder.attr( 'data-class' ), + width: width, + height: height, + src: $placeholder.attr( 'data-src' ), + alt: $placeholder.attr( 'data-alt' ), + srcset: $placeholder.attr( 'data-srcset' ) + } ); + }, + + /** + * Load the references section content from API if it's not already loaded. + * + * All references tags content will be loaded per section. + * + * @param {Object} data Information about the section. It's in the following form: + * { + * @property {string} page, + * @property {boolean} wasExpanded, + * @property {jQuery.Object} $heading, + * @property {boolean} isReferenceSection + * } + * @return {jQuery.Deferred} rejected when not a reference section. + */ + lazyLoadReferences: function ( data ) { + var $content, $spinner, + gateway = this.referencesGateway, + self = this; + + // If the section was expanded before toggling, do not load anything as + // section is being collapsed now. + // Also return early if lazy loading is not required or the section is + // not a reference section + if ( + data.wasExpanded || + !data.isReferenceSection + ) { + return; + } + + $content = data.$heading.next(); + + if ( !$content.data( 'are-references-loaded' ) ) { + $content.children().addClass( 'hidden' ); + $spinner = $( icons.spinner().toHtmlString() ).prependTo( $content ); + + // First ensure we retrieve all of the possible lists + return gateway.getReferencesLists( data.page ) + .done( function () { + var lastId; + + $content.find( '.mf-lazy-references-placeholder' ).each( function () { + var refListIndex = 0, + $placeholder = $( this ), + // search for id of the collapsible heading + id = getSectionId( $placeholder ); + + if ( lastId !== id ) { + // If the placeholder belongs to a new section reset index + refListIndex = 0; + lastId = id; + } else { + // otherwise increment it + refListIndex++; + } + + if ( id ) { + gateway.getReferencesList( data.page, id ).done( function ( refListElements ) { + // Note if no section html is provided no substitution will happen so user is + // forced to rely on placeholder link. + if ( refListElements && refListElements[refListIndex] ) { + $placeholder.replaceWith( refListElements[refListIndex] ); + } + } ); + } + } ); + // Show the section now the references lists have been placed. + $spinner.remove(); + $content.children().removeClass( 'hidden' ); + /** + * @event references-loaded + * Fired when references list is loaded into the HTML + */ + self.emit( 'references-loaded', self.page ); + } ) + .fail( function () { + $spinner.remove(); + // unhide on a failure + $content.children().removeClass( 'hidden' ); + } ) + .always( function () { + // lazy load images if any + self.loadImages( $content ); + // Do not attempt further loading even if we're unable to load this time. + $content.data( 'are-references-loaded', 1 ); + } ); + } else { + return $.Deferred().reject(); + } + }, + + /** + * Returns the appropriate license message including links/name to + * terms of use (if any) and license page + * @return {string} + */ + getLicenseMsg: function () { + var licenseMsg, + mfLicense = mw.config.get( 'wgMFLicense' ), + licensePlural = mw.language.convertNumber( mfLicense.plural ); + + if ( mfLicense.link ) { + if ( $( '#footer-places-terms-use' ).length > 0 ) { + licenseMsg = mw.msg( + 'mobile-frontend-editor-licensing-with-terms', + mw.message( + 'mobile-frontend-editor-terms-link', + $( '#footer-places-terms-use a' ).attr( 'href' ) + ).parse(), + mfLicense.link, + licensePlural + ); + } else { + licenseMsg = mw.msg( + 'mobile-frontend-editor-licensing', + mfLicense.link, + licensePlural + ); + } + } + return licenseMsg; + } + } ); + + Skin.getSectionId = getSectionId; + module.exports = Skin; + + }( mw.mobileFrontend, jQuery ) ); + + +/***/ }, +/* 10 */ /***/ function(module, exports) { var util; diff --git a/resources/skins.minerva.scripts/preInit.js b/resources/skins.minerva.scripts/preInit.js index c92fc7e..8f0f361 100644 --- a/resources/skins.minerva.scripts/preInit.js +++ b/resources/skins.minerva.scripts/preInit.js @@ -14,7 +14,7 @@ Page = M.require( 'mobile.startup/Page' ), mainMenu = M.require( 'skins.minerva.scripts.top/mainMenu' ), toast = M.require( 'mobile.toast/toast' ), - Skin = M.require( 'mobile.startup/Skin' ), + Skin = mw.mf.Skin, ReferencesMobileViewGateway = M.require( 'mobile.references.gateway/ReferencesMobileViewGateway' ), diff --git a/tests/qunit/mobile.startup/test_Skin.js b/tests/qunit/mobile.frontend/test_Skin.js similarity index 98% rename from tests/qunit/mobile.startup/test_Skin.js rename to tests/qunit/mobile.frontend/test_Skin.js index 7cab79d..b2e4c50 100644 --- a/tests/qunit/mobile.startup/test_Skin.js +++ b/tests/qunit/mobile.frontend/test_Skin.js @@ -1,6 +1,6 @@ ( function ( M, $ ) { var Page = M.require( 'mobile.startup/Page' ), - Skin = M.require( 'mobile.startup/Skin' ); + Skin = mw.mf.Skin; QUnit.module( 'MobileFrontend Skin.js', { setup: function () { -- To view, visit https://gerrit.wikimedia.org/r/332928 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ieb5cb151c009b40f41b4f6acbd0f33feac876914 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