diff --git a/bundlesize.config.json b/bundlesize.config.json index 7cb0bd8b9..0c0ecb9d6 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -18,15 +18,7 @@ }, { "resourceModule": "mmv.bootstrap", - "maxSize": "5.9 kB" - }, - { - "resourceModule": "mmv.bootstrap.autostart", - "maxSize": "1 kB" - }, - { - "resourceModule": "mmv.head", - "maxSize": "1 kB" + "maxSize": "5.6 kB" } ] } diff --git a/extension.json b/extension.json index fa651ae30..5448c682a 100644 --- a/extension.json +++ b/extension.json @@ -97,8 +97,7 @@ "mediawiki.user", "mediawiki.util", "mmv.bootstrap", - "mmv.codex", - "mmv.head" + "mmv.codex" ], "messages": [ "license-header", @@ -279,6 +278,7 @@ "extensions": "MediaViewerExtensions" } }, + "mmv.bootstrap/mmv.bootstrap.autostart.js", "mmv.bootstrap/mmv.bootstrap.js", "mmv.bootstrap/mmv.lightboximage.js", "mmv.bootstrap/mmv.Config.js", @@ -293,8 +293,7 @@ "mediawiki.router", "mediawiki.Title", "mediawiki.user", - "mediawiki.storage", - "mmv.head" + "mediawiki.storage" ], "messages": [ "multimediaviewer-loading", @@ -305,23 +304,12 @@ ] }, "mmv.bootstrap.autostart": { - "packageFiles": [ - "mmv.bootstrap.autostart/mmv.bootstrap.autostart.js" - ], - "dependencies": [ - "mmv.head", - "mmv.bootstrap" - ] + "deprecated": "Deprecated in 1.43, alias for mmw.bootstrap", + "dependencies": "mmv.bootstrap" }, "mmv.head": { - "packageFiles": [ - "mmv.head/mmv.head.js", - "mmv.head/base.js" - ], - "dependencies": [ - "mediawiki.user", - "mediawiki.storage" - ] + "deprecated": "Deprecated in 1.43, alias for mmw.bootstrap", + "dependencies": "mmv.bootstrap" } }, "ResourceFileModulePaths": { @@ -369,7 +357,6 @@ "tests/qunit/mmv/mmv.testhelpers.js" ], "dependencies": [ - "mmv.head", "mmv.bootstrap", "mmv", "mmv.ui.reuse" diff --git a/includes/Hooks.php b/includes/Hooks.php index b5725814d..5bab83584 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -111,7 +111,7 @@ class Hooks implements $isMobileFrontendView = ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) && $this->mobileContext && $this->mobileContext->shouldDisplayMobileView(); if ( !$isMobileFrontendView ) { - $out->addModules( [ 'mmv.head', 'mmv.bootstrap.autostart' ] ); + $out->addModules( [ 'mmv.bootstrap' ] ); } } diff --git a/resources/mmv.bootstrap/mmv.Config.js b/resources/mmv.bootstrap/mmv.Config.js index 53790709d..cebfd40fe 100644 --- a/resources/mmv.bootstrap/mmv.Config.js +++ b/resources/mmv.bootstrap/mmv.Config.js @@ -15,13 +15,74 @@ * along with MediaViewer. If not, see . */ -const { isMediaViewerEnabledOnClick } = require( 'mmv.head' ); const api = new mw.Api(); /** * Contains/retrieves configuration/environment information for MediaViewer. */ class Config { + + /** + * The media route prefix + * + * @return {string} + */ + static get ROUTE() { + return 'media'; + } + + /** + * RegExp representing the media route + * + * @return {RegExp} + */ + static get ROUTE_REGEXP() { + return /^\/media\/(.+)$/; + } + + /** + * RegExp representing the media position as in "File:foo.jpg/3" + * + * @return {RegExp} + */ + static get POSITION_REGEXP() { + return /\/(\d+)$/; + } + + /** + * Regular expression representing the legacy media route + * + * @return {RegExp} + */ + static get LEGACY_ROUTE_REGEXP() { + return /^mediaviewer\/(.+)$/; + } + + /** + * Returns true if MediaViewer should handle thumbnail clicks. + * + * @return {boolean} + */ + static isMediaViewerEnabledOnClick() { + return mw.config.get( 'wgMediaViewer' ) && // global opt-out switch, can be set in user JS + mw.config.get( 'wgMediaViewerOnClick' ) && // thumbnail opt-out, can be set in preferences + ( mw.user.isNamed() || !mw.storage.get( 'wgMediaViewerOnClick' ) || mw.storage.get( 'wgMediaViewerOnClick' ) === '1' ); // thumbnail opt-out for anons + } + + /** + * Returns the location hash (route string) for the given file title. + * + * @param {string} imageFileTitle the file title + * @param {number} [position] the relative position of this image to others with same file + * @return {string} the location hash + * @member mw.mmv + */ + static getMediaHash( imageFileTitle, position ) { + return position > 1 ? + `#/${ this.ROUTE }/${ encodeURI( imageFileTitle ) }/${ position }` : + `#/${ this.ROUTE }/${ encodeURI( imageFileTitle ) }`; + } + /** * (Semi-)permanently stores the setting whether MediaViewer should handle thumbnail clicks. * - for logged-in users, we use preferences @@ -79,7 +140,7 @@ class Config { * @return {boolean} */ static shouldShowStatusInfo() { - return !isMediaViewerEnabledOnClick( mw.config, mw.user, mw.storage ) && mw.storage.get( 'mmv-showStatusInfo' ) === '1'; + return !this.isMediaViewerEnabledOnClick() && mw.storage.get( 'mmv-showStatusInfo' ) === '1'; } /** @@ -112,4 +173,5 @@ class Config { } } +mw.mmv = Config; module.exports = Config; diff --git a/resources/mmv.bootstrap.autostart/mmv.bootstrap.autostart.js b/resources/mmv.bootstrap/mmv.bootstrap.autostart.js similarity index 75% rename from resources/mmv.bootstrap.autostart/mmv.bootstrap.autostart.js rename to resources/mmv.bootstrap/mmv.bootstrap.autostart.js index 3d3f25cbc..8806b73cc 100644 --- a/resources/mmv.bootstrap.autostart/mmv.bootstrap.autostart.js +++ b/resources/mmv.bootstrap/mmv.bootstrap.autostart.js @@ -18,10 +18,13 @@ // This file is used to do the global initialization that we want on the real pages, // but do not want in the tests. -const { MultimediaViewerBootstrap } = require( 'mmv.bootstrap' ); +const Config = require( './mmv.Config.js' ); +const MultimediaViewerBootstrap = require( './mmv.bootstrap.js' ); +const LightboxImage = require( './mmv.lightboximage.js' ); +const HtmlUtils = require( './mmv.HtmlUtils.js' ); const bootstrap = new MultimediaViewerBootstrap(); $( bootstrap.setupEventHandlers.bind( bootstrap ) ); -module.exports = bootstrap; +module.exports = { MultimediaViewerBootstrap, LightboxImage, Config, HtmlUtils }; diff --git a/resources/mmv.bootstrap/mmv.bootstrap.js b/resources/mmv.bootstrap/mmv.bootstrap.js index c498dd474..d247da603 100644 --- a/resources/mmv.bootstrap/mmv.bootstrap.js +++ b/resources/mmv.bootstrap/mmv.bootstrap.js @@ -15,7 +15,6 @@ * along with MultimediaViewer. If not, see . */ -const { getMediaHash, ROUTE_REGEXP, POSITION_REGEXP, LEGACY_ROUTE_REGEXP, isMediaViewerEnabledOnClick } = require( 'mmv.head' ); const Config = require( './mmv.Config.js' ); const HtmlUtils = require( './mmv.HtmlUtils.js' ); const LightboxImage = require( './mmv.lightboximage.js' ); @@ -70,10 +69,10 @@ class MultimediaViewerBootstrap { let fileTitle; viewer.comingFromHashChange = true; try { - let position = fileName.match( POSITION_REGEXP ); + let position = fileName.match( Config.POSITION_REGEXP ); if ( position ) { position = +position[ 1 ]; - fileName = fileName.replace( POSITION_REGEXP, '' ); + fileName = fileName.replace( Config.POSITION_REGEXP, '' ); } else { position = undefined; } @@ -93,8 +92,8 @@ class MultimediaViewerBootstrap { * @param {OO.Router} router */ setupRouter( router ) { - router.addRoute( ROUTE_REGEXP, this.route.bind( this ) ); - router.addRoute( LEGACY_ROUTE_REGEXP, this.route.bind( this ) ); + router.addRoute( Config.ROUTE_REGEXP, this.route.bind( this ) ); + router.addRoute( Config.LEGACY_ROUTE_REGEXP, this.route.bind( this ) ); this.router = router; } @@ -206,9 +205,6 @@ class MultimediaViewerBootstrap { this.$parsoidThumbs.each( ( i, thumb ) => this.processParsoidThumb( thumb ) ); } finally { this.thumbsReadyDeferred.resolve(); - // now that we have set up our real click handler we can remove the temporary - // handler added in mmv.head.js which just replays clicks to the real handler - $( document ).off( 'click.mmv-head' ); } } @@ -248,7 +244,7 @@ class MultimediaViewerBootstrap { $thumbContainer.on( { mouseenter: () => { // There is no point preloading if clicking the thumb won't open Media Viewer - if ( !isMediaViewerEnabledOnClick() ) { + if ( !Config.isMediaViewerEnabledOnClick() ) { return; } this.preloadOnHoverTimer = setTimeout( () => { @@ -507,7 +503,7 @@ class MultimediaViewerBootstrap { */ openImage( image ) { this.ensureEventHandlersAreSetUp(); - const hash = getMediaHash( image.filePageTitle, image.position ); + const hash = Config.getMediaHash( image.filePageTitle, image.position ); location.hash = hash; history.replaceState( MANAGED_STATE, null, hash ); } @@ -527,7 +523,7 @@ class MultimediaViewerBootstrap { } // Don't load if someone has specifically stopped us from doing so - if ( !isMediaViewerEnabledOnClick() ) { + if ( !Config.isMediaViewerEnabledOnClick() ) { return true; } @@ -553,7 +549,7 @@ class MultimediaViewerBootstrap { */ isViewerHash() { const path = location.hash.slice( 1 ); - return path.match( ROUTE_REGEXP ) || path.match( LEGACY_ROUTE_REGEXP ); + return path.match( Config.ROUTE_REGEXP ) || path.match( Config.LEGACY_ROUTE_REGEXP ); } /** @@ -685,4 +681,4 @@ class MultimediaViewerBootstrap { } } -module.exports = { MultimediaViewerBootstrap, LightboxImage, Config, HtmlUtils }; +module.exports = MultimediaViewerBootstrap; diff --git a/resources/mmv.head/base.js b/resources/mmv.head/base.js deleted file mode 100644 index c6169fff8..000000000 --- a/resources/mmv.head/base.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of the MediaWiki extension MultimediaViewer. - * - * MultimediaViewer is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * MultimediaViewer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MultimediaViewer. If not, see . - */ - -// Included on every page which has images so keep it lightweight. -module.exports = { - /** - * The media route prefix - * - * @member mw.mmv - */ - ROUTE: 'media', - /** - * RegExp representing the media route - * - * @member mw.mmv - */ - ROUTE_REGEXP: /^\/media\/(.+)$/, - /** - * RegExp representing the media position as in "File:foo.jpg/3" - * - * @member mw.mmv - */ - POSITION_REGEXP: /\/(\d+)$/, - /** - * @property {RegExp} - * Regular expression representing the legacy media route - * @member mw.mmv - */ - LEGACY_ROUTE_REGEXP: /^mediaviewer\/(.+)$/, - - /** - * Returns true if MediaViewer should handle thumbnail clicks. - * - * @param {Map} mwConfig - * @param {Object} mwUser - * @param {mw.SafeStorage} mwStorage - * @return {boolean} - */ - isMediaViewerEnabledOnClick( mwConfig = mw.config, mwUser = mw.user, mwStorage = mw.storage ) { - return mwConfig.get( 'wgMediaViewer' ) && // global opt-out switch, can be set in user JS - mwConfig.get( 'wgMediaViewerOnClick' ) && // thumbnail opt-out, can be set in preferences - ( mwUser.isNamed() || !mwStorage.get( 'wgMediaViewerOnClick' ) || mwStorage.get( 'wgMediaViewerOnClick' ) === '1' ); // thumbnail opt-out for anons - }, - - /** - * Returns the location hash (route string) for the given file title. - * - * @param {string} imageFileTitle the file title - * @param {number} [position] the relative position of this image to others with same file - * @return {string} the location hash - * @member mw.mmv - */ - getMediaHash: ( imageFileTitle, position ) => position > 1 ? - `#/media/${ encodeURI( imageFileTitle ) }/${ position }` : - `#/media/${ encodeURI( imageFileTitle ) }` -}; diff --git a/resources/mmv.head/mmv.head.js b/resources/mmv.head/mmv.head.js deleted file mode 100644 index 72131e904..000000000 --- a/resources/mmv.head/mmv.head.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is part of the MediaWiki extension MultimediaViewer. - * - * MultimediaViewer is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * MultimediaViewer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with MultimediaViewer. If not, see . - */ - -const base = require( './base.js' ); -mw.mmv = base; -module.exports = base; - -// If MediaViewer is disabled by the user, do not set up click handling. -// This is loaded before user JS so we cannot check wgMediaViewer. -if ( base.isMediaViewerEnabledOnClick() ) { - ( $( document ) ).on( 'click.mmv-head', 'a.image, a.mw-file-description', ( e ) => { - // Do not interfere with non-left clicks or if modifier keys are pressed. - // Also, make sure we do not get in a loop. - if ( ( e.button !== 0 && e.which !== 1 ) || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey || e.replayed ) { - return; - } - - // We wait for document readiness because mw.loader.using writes to the DOM - // which can cause a blank page if it happens before DOM readiness - $( () => { - mw.loader.using( [ 'mmv.bootstrap.autostart' ], ( req ) => { - const bootstrap = req( 'mmv.bootstrap.autostart' ); - bootstrap.whenThumbsReady().then( () => { - // We have to copy the properties, passing e doesn't work. Probably because of preventDefault() - $( e.target ).trigger( { type: 'click', which: 1, replayed: true } ); - } ); - } ); - } ); - - e.preventDefault(); - } ); -} diff --git a/resources/mmv.ui.reuse/mmv.EmbedFileFormatter.js b/resources/mmv.ui.reuse/mmv.EmbedFileFormatter.js index 75138a444..30825740b 100644 --- a/resources/mmv.ui.reuse/mmv.EmbedFileFormatter.js +++ b/resources/mmv.ui.reuse/mmv.EmbedFileFormatter.js @@ -15,8 +15,7 @@ * along with MediaViewer. If not, see . */ -const { getMediaHash } = require( 'mmv.head' ); -const { HtmlUtils } = require( 'mmv.bootstrap' ); +const { Config, HtmlUtils } = require( 'mmv.bootstrap' ); /** * Converts data in various formats needed by the Embed sub-dialog @@ -173,7 +172,7 @@ class EmbedFileFormatter { return HtmlUtils.jqueryToHtml( $( '

' ).append( $( '' ) - .attr( 'href', info.imageInfo.descriptionUrl + getMediaHash( info.imageInfo.title ) ) + .attr( 'href', info.imageInfo.descriptionUrl + Config.getMediaHash( info.imageInfo.title ) ) .append( $( '' ) .attr( 'src', imgUrl ) diff --git a/resources/mmv.ui.reuse/mmv.ui.reuse.share.js b/resources/mmv.ui.reuse/mmv.ui.reuse.share.js index cf913b47d..3555471c6 100644 --- a/resources/mmv.ui.reuse/mmv.ui.reuse.share.js +++ b/resources/mmv.ui.reuse/mmv.ui.reuse.share.js @@ -15,7 +15,7 @@ * along with MultimediaViewer. If not, see . */ -const { getMediaHash } = require( 'mmv.head' ); +const { Config } = require( 'mmv.bootstrap' ); const Utils = require( './mmv.ui.utils.js' ); const { UiElement } = require( 'mmv' ); @@ -56,7 +56,7 @@ class Share extends UiElement { * @param {ImageModel} image */ set( image ) { - const url = image.descriptionUrl + getMediaHash( image.title ); + const url = image.descriptionUrl + Config.getMediaHash( image.title ); this.$pageInput.val( url ); } diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js index 9161d6a7b..64c806176 100644 --- a/resources/mmv/mmv.js +++ b/resources/mmv/mmv.js @@ -16,7 +16,6 @@ */ const { Config } = require( 'mmv.bootstrap' ); -const { getMediaHash } = require( 'mmv.head' ); const ViewLogger = require( './logging/mmv.logging.ViewLogger.js' ); const Api = require( './provider/mmv.provider.Api.js' ); const GuessedThumbnailInfo = require( './provider/mmv.provider.GuessedThumbnailInfo.js' ); @@ -639,7 +638,7 @@ class MultimediaViewer { const thumb = this.thumbs[ index ]; this.loadImage( thumb ); router.navigateTo( null, { - path: getMediaHash( thumb.filePageTitle, thumb.position ), + path: Config.getMediaHash( thumb.filePageTitle, thumb.position ), useReplaceState: true } ); } diff --git a/tests/qunit/mmv/mmv.Config.test.js b/tests/qunit/mmv/mmv.Config.test.js index b1a723b3a..cb6bc0b53 100644 --- a/tests/qunit/mmv/mmv.Config.test.js +++ b/tests/qunit/mmv/mmv.Config.test.js @@ -15,7 +15,6 @@ * along with MediaViewer. If not, see . */ -const { isMediaViewerEnabledOnClick } = require( 'mmv.head' ); const { Config } = require( 'mmv.bootstrap' ); const { createLocalStorage, getFakeLocalStorage } = require( './mmv.testhelpers.js' ); const config0 = mw.config; @@ -46,39 +45,39 @@ const saveOption = mw.Api.prototype.saveOption; mw.user.isNamed.returns( true ); mw.config.get.withArgs( 'wgMediaViewer' ).returns( true ); mw.config.get.withArgs( 'wgMediaViewerOnClick' ).returns( true ); - assert.strictEqual( isMediaViewerEnabledOnClick( mw.config, mw.user ), true, 'Returns true for logged-in with standard settings' ); + assert.strictEqual( Config.isMediaViewerEnabledOnClick(), true, 'Returns true for logged-in with standard settings' ); mw.user.isNamed.returns( true ); mw.config.get.withArgs( 'wgMediaViewer' ).returns( false ); mw.config.get.withArgs( 'wgMediaViewerOnClick' ).returns( true ); - assert.strictEqual( isMediaViewerEnabledOnClick( mw.config, mw.user ), false, 'Returns false if opted out via user JS flag' ); + assert.strictEqual( Config.isMediaViewerEnabledOnClick(), false, 'Returns false if opted out via user JS flag' ); mw.user.isNamed.returns( true ); mw.config.get.withArgs( 'wgMediaViewer' ).returns( true ); mw.config.get.withArgs( 'wgMediaViewerOnClick' ).returns( false ); - assert.strictEqual( isMediaViewerEnabledOnClick( mw.config, mw.user ), false, 'Returns false if opted out via preferences' ); + assert.strictEqual( Config.isMediaViewerEnabledOnClick(), false, 'Returns false if opted out via preferences' ); mw.user.isNamed.returns( false ); mw.config.get.withArgs( 'wgMediaViewer' ).returns( false ); mw.config.get.withArgs( 'wgMediaViewerOnClick' ).returns( true ); - assert.strictEqual( isMediaViewerEnabledOnClick( mw.config, mw.user ), false, 'Returns false if anon user opted out via user JS flag' ); + assert.strictEqual( Config.isMediaViewerEnabledOnClick(), false, 'Returns false if anon user opted out via user JS flag' ); mw.user.isNamed.returns( false ); mw.config.get.withArgs( 'wgMediaViewer' ).returns( true ); mw.config.get.withArgs( 'wgMediaViewerOnClick' ).returns( false ); - assert.strictEqual( isMediaViewerEnabledOnClick( mw.config, mw.user ), false, 'Returns false if anon user opted out in some weird way' ); // apparently someone created a browser extension to do this + assert.strictEqual( Config.isMediaViewerEnabledOnClick(), false, 'Returns false if anon user opted out in some weird way' ); // apparently someone created a browser extension to do this mw.user.isNamed.returns( false ); mw.config.get.withArgs( 'wgMediaViewer' ).returns( true ); mw.config.get.withArgs( 'wgMediaViewerOnClick' ).returns( true ); mw.storage.store.getItem.withArgs( 'wgMediaViewerOnClick' ).returns( null ); - assert.strictEqual( isMediaViewerEnabledOnClick( mw.config, mw.user ), true, 'Returns true for anon with standard settings' ); + assert.strictEqual( Config.isMediaViewerEnabledOnClick(), true, 'Returns true for anon with standard settings' ); mw.user.isNamed.returns( false ); mw.config.get.withArgs( 'wgMediaViewer' ).returns( true ); mw.config.get.withArgs( 'wgMediaViewerOnClick' ).returns( true ); mw.storage.store.getItem.withArgs( 'wgMediaViewerOnClick' ).returns( '0' ); - assert.strictEqual( isMediaViewerEnabledOnClick( mw.config, mw.user ), false, 'Returns true for anon opted out via localSettings' ); + assert.strictEqual( Config.isMediaViewerEnabledOnClick(), false, 'Returns true for anon opted out via localSettings' ); } ); QUnit.test( 'setMediaViewerEnabledOnClick sense check', function ( assert ) {