mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/MultimediaViewer
synced 2024-11-28 10:00:18 +00:00
Add file usage data to MMV metadata panel
Interface code is its own class which does not depend on the main interface class it can be unit tested (and eventually moved into its own file to make browsing the code easier). IMO we should aim to eventually break up the interface into similar classes (with a simple init/empty/set interface + custom events where it makes sense). Also, sneak-introducing LESS! API usage could be more effective (globalusage is a separate API call; it needn't be), but we will have to rewrite that part soon anyway, so it should pass for now. Bug: 60087 Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/44 Change-Id: Ibe5c323cdeab4a378316925f0c3efb3dc7ef5997
This commit is contained in:
parent
f7925a1a4f
commit
30563de71d
|
@ -72,6 +72,12 @@ $messages['en'] = array(
|
|||
'multimediaviewer-geoloc-coord' => '$1° $2′ $3″ $4',
|
||||
'multimediaviewer-geoloc-coords' => '$1, $2',
|
||||
'multimediaviewer-geolocation' => 'Location: $1',
|
||||
|
||||
'multimediaviewer-fileusage-count' => 'Used in $1 {{PLURAL:$1|page|pages}}',
|
||||
'multimediaviewer-fileusage-count-more' => 'Used in more than $1 {{PLURAL:$1|pages}}',
|
||||
'multimediaviewer-fileusage-link' => 'View all uses',
|
||||
'multimediaviewer-fileusage-local-section' => 'On this site',
|
||||
'multimediaviewer-fileusage-global-section' => 'On other sites',
|
||||
);
|
||||
|
||||
/** Message documentation (Message documentation)
|
||||
|
@ -191,6 +197,20 @@ Used as <code>$4</code> in {{msg-mw|Multimediaviewer-geoloc-coord}}.',
|
|||
Both are formatted according to {{msg-mw|Multimediaviewer-geoloc-coord}}.',
|
||||
'multimediaviewer-geolocation' => 'Message for displaying a location. Parameters:
|
||||
* $1 - a location which is formatted by {{msg-mw|Multimediaviewer-geoloc-coords}}',
|
||||
|
||||
'multimediaviewer-fileusage-count' => 'Title for the list of pages which use this image. $1 is the number of such pages.
|
||||
|
||||
See also:
|
||||
* {{msg-mw|Multimediaviewer-fileusage-count-more}}',
|
||||
'multimediaviewer-fileusage-count-more' => 'Title for the list of pages which use this image. $1 is the number of such pages.
|
||||
This is used when the number is too large to accurately determine; it can be much larger than the value given in $1.
|
||||
|
||||
See also:
|
||||
* {{msg-mw|Multimediaviewer-fileusage-count}}',
|
||||
'multimediaviewer-fileusage-link' => 'Text of link pointing to Special:WhatLinksHere or Special:GlobalUsage
|
||||
when there are too many pages to fit into the metadata box',
|
||||
'multimediaviewer-fileusage-local-section' => 'Section title for the local usages',
|
||||
'multimediaviewer-fileusage-global-section' => 'Section title for the global usages',
|
||||
);
|
||||
|
||||
/** Arabic (العربية)
|
||||
|
|
|
@ -99,6 +99,7 @@ $wgResourceModules['mmv.lightboxinterface'] = array_merge( array(
|
|||
'oojs',
|
||||
'multilightbox.interface',
|
||||
'mmv.ui.description',
|
||||
'mmv.ui.fileUsage',
|
||||
),
|
||||
), $moduleInfoMMV );
|
||||
|
||||
|
@ -161,6 +162,7 @@ $wgResourceModules['mmv.provider'] = array_merge( array(
|
|||
),
|
||||
|
||||
'dependencies' => array(
|
||||
'mediawiki.Title',
|
||||
'mmv.model',
|
||||
'oojs',
|
||||
),
|
||||
|
@ -193,6 +195,23 @@ $wgResourceModules['mmv.ui.description'] = array_merge( array(
|
|||
),
|
||||
), $moduleInfoMMV );
|
||||
|
||||
$wgResourceModules['mmv.ui.fileUsage'] = array_merge( array(
|
||||
'scripts' => array(
|
||||
'mmv.ui.fileUsage.js',
|
||||
),
|
||||
|
||||
'styles' => array(
|
||||
'mmv.ui.fileUsage.less',
|
||||
),
|
||||
|
||||
'dependencies' => array(
|
||||
'mediawiki.Uri',
|
||||
'mediawiki.jqueryMsg',
|
||||
'mmv.ui',
|
||||
'oojs',
|
||||
),
|
||||
), $moduleInfoMMV );
|
||||
|
||||
$wgResourceModules['mmv'] = array_merge( array(
|
||||
'scripts' => array(
|
||||
'mmv.js',
|
||||
|
@ -209,6 +228,7 @@ $wgResourceModules['mmv'] = array_merge( array(
|
|||
'mmv.lightboximage',
|
||||
'jquery.fullscreen',
|
||||
'jquery.throttle.debounce',
|
||||
'mediawiki.Uri',
|
||||
'mediawiki.Title',
|
||||
'jquery.ui.dialog',
|
||||
'jquery.hidpi',
|
||||
|
@ -244,6 +264,12 @@ $wgResourceModules['mmv'] = array_merge( array(
|
|||
'multimediaviewer-geoloc-coord',
|
||||
'multimediaviewer-geoloc-coords',
|
||||
'multimediaviewer-geolocation',
|
||||
|
||||
'multimediaviewer-fileusage-count',
|
||||
'multimediaviewer-fileusage-count-more',
|
||||
'multimediaviewer-fileusage-link',
|
||||
'multimediaviewer-fileusage-local-section',
|
||||
'multimediaviewer-fileusage-global-section',
|
||||
),
|
||||
), $moduleInfoMMV );
|
||||
|
||||
|
|
|
@ -101,9 +101,11 @@ class MultimediaViewerHooks {
|
|||
* @return bool
|
||||
*/
|
||||
public static function resourceLoaderGetConfigVars( &$vars ) {
|
||||
global $wgAPIPropModules;
|
||||
$vars['wgMultimediaViewer'] = array(
|
||||
'infoLink' => self::$infoLink,
|
||||
'discussionLink' => self::$discussionLink,
|
||||
'globalUsageAvailable' => isset( $wgAPIPropModules['globalusage'] ),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
@ -123,6 +125,7 @@ class MultimediaViewerHooks {
|
|||
'tests/qunit/mmv.provider.test.js',
|
||||
'tests/qunit/mmv.lightboxinterface.test.js',
|
||||
'tests/qunit/mmv.ui.description.test.js',
|
||||
'tests/qunit/mmv.ui.fileUsage.test.js',
|
||||
'tests/qunit/lightboximage.test.js',
|
||||
'tests/qunit/lightboxinterface.test.js',
|
||||
'tests/qunit/multilightbox.test.js',
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
"../resources/mmv/mmv.lightboxinterface.js",
|
||||
"../resources/mmv/mmv.lightboximage.js",
|
||||
"../resources/mmv/mmv.model.js",
|
||||
"../resources/mmv/mmv.provider.js",
|
||||
"../resources/mmv/mmv.ui.description.js",
|
||||
"../resources/mmv/mmv.ui.fileUsage.js",
|
||||
"../resources/mmv/mmv.ui.js"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -181,10 +181,13 @@ body.mobile .mw-mlb-controls,
|
|||
.mw-mlb-image-desc-div,
|
||||
.mw-mlb-image-links-div {
|
||||
display: inline-block;
|
||||
height: 150px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.mw-mlb-image-desc-div {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.mw-mlb-description-backup,
|
||||
.mw-mlb-image-desc {
|
||||
font-size: 0.8em;
|
||||
|
|
|
@ -114,6 +114,25 @@
|
|||
*/
|
||||
this.api = new mw.Api();
|
||||
|
||||
/**
|
||||
* @property {mw.mmv.dataProvider.ImageUsage}
|
||||
* @private
|
||||
*/
|
||||
this.imageUsageDataProvider = new mw.mmv.dataProvider.ImageUsage( this.api );
|
||||
|
||||
/**
|
||||
* @property {mw.mmv.dataProvider.GlobalUsage}
|
||||
* @private
|
||||
*/
|
||||
this.globalUsageDataProvider = new mw.mmv.dataProvider.GlobalUsage( this.api, {
|
||||
doNotUseApi: !mw.config.get( 'wgMultimediaViewer' ).globalUsageAvailable
|
||||
} );
|
||||
// replace with this one to test global usage on a local wiki without going through all the
|
||||
// hassle required for installing the extension:
|
||||
//this.globalUsageDataProvider = new mw.mmv.dataProvider.GlobalUsage(
|
||||
// new mw.Api( {ajax: { url: 'http://commons.wikimedia.org/w/api.php', dataType: 'jsonp' } } )
|
||||
//);
|
||||
|
||||
/**
|
||||
* imageInfo object, used for caching - promises will resolve with
|
||||
* an mw.mmv.model.Image object, a repoInfo object, the best width for
|
||||
|
@ -419,8 +438,10 @@
|
|||
* @param {mw.LightboxImage} image
|
||||
* @param {mw.mmv.model.Image} imageData
|
||||
* @param {mw.mmv.model.Repo} repoData
|
||||
* @param {mw.mmv.model.FileUsage} localUsage
|
||||
* @param {mw.mmv.model.FileUsage} globalUsage
|
||||
*/
|
||||
MMVP.setImageInfo = function ( image, imageData, repoData ) {
|
||||
MMVP.setImageInfo = function ( image, imageData, repoData, localUsage, globalUsage ) {
|
||||
var gfpid,
|
||||
msgname,
|
||||
fileTitle = image.filePageTitle,
|
||||
|
@ -536,6 +557,8 @@
|
|||
|
||||
this.setLocationData( imageData );
|
||||
ui.$locationLi.toggleClass( 'empty', !imageData.hasCoords() );
|
||||
|
||||
ui.fileUsage.set( localUsage, globalUsage );
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -651,7 +674,7 @@
|
|||
|
||||
mdpid = this.profileStart( 'metadata-fetch' );
|
||||
|
||||
this.fetchImageInfo( image.filePageTitle ).done( function ( imageData, repoInfo, targetWidth, requestedWidth ) {
|
||||
this.fetchImageInfoAndFileUsageInfo( image.filePageTitle ).then( function ( imageData, repoInfo, targetWidth, requestedWidth, localUsage, globalUsage ) {
|
||||
var repoData = mw.mmv.model.Repo.newFromRepoInfo( repoInfo[imageData.repo] );
|
||||
|
||||
viewer.profileEnd( mdpid );
|
||||
|
@ -664,7 +687,7 @@
|
|||
viewer.loadAndSetImage( viewer.lightbox.iface, imageData, targetWidth, requestedWidth, 'image-load' );
|
||||
|
||||
viewer.lightbox.iface.$imageDiv.removeClass( 'empty' );
|
||||
viewer.setImageInfo( image, imageData, repoData );
|
||||
viewer.setImageInfo( image, imageData, repoData, localUsage, globalUsage );
|
||||
} );
|
||||
|
||||
comingFromPopstate = false;
|
||||
|
@ -815,6 +838,37 @@
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets file usage info.
|
||||
* @param {mw.Title} fileTitle Title of the file page for the image.
|
||||
* @returns {jQuery.Promise.<mw.mmv.model.FileUsage, mw.mmv.model.FileUsage>} a promise
|
||||
* resolving to a local and a global file usage object
|
||||
* FIXME should be parallel with the other fetches, or even better if it can be integrated
|
||||
* into the same API calls. Lets get it out first though.
|
||||
*/
|
||||
MMVP.fetchFileUsageInfo = function ( fileTitle ) {
|
||||
return $.when(
|
||||
this.imageUsageDataProvider.get( fileTitle ),
|
||||
this.globalUsageDataProvider.get( fileTitle )
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets all file-related info.
|
||||
* @param {mw.Title} fileTitle Title of the file page for the image.
|
||||
* @returns {jQuery.Promise.<mw.mmv.model.Image, mw.mmv.model.Repo, Number, Number,
|
||||
* mw.mmv.model.FileUsage, mw.mmv.model.FileUsage>}
|
||||
*/
|
||||
MMVP.fetchImageInfoAndFileUsageInfo = function ( fileTitle ) {
|
||||
return $.when(
|
||||
this.fetchImageInfo( fileTitle ),
|
||||
this.fetchFileUsageInfo( fileTitle )
|
||||
).then( function( first, second ) {
|
||||
var d = $.Deferred();
|
||||
return d.resolve.apply( d, first.concat( second ) );
|
||||
} );
|
||||
};
|
||||
|
||||
MMVP.loadIndex = function ( index ) {
|
||||
var $clicked = $( imgsSelector ).eq( index );
|
||||
if ( index < this.lightbox.images.length && index >= 0 ) {
|
||||
|
|
|
@ -60,6 +60,8 @@
|
|||
this.$location.empty();
|
||||
this.$locationLi.addClass( 'empty' );
|
||||
|
||||
this.fileUsage.empty();
|
||||
|
||||
this.$useFile.data( 'title', null );
|
||||
this.$useFile.data( 'link', null );
|
||||
this.$useFile.data( 'src', null );
|
||||
|
@ -268,7 +270,12 @@
|
|||
this.initializeDatetime();
|
||||
this.initializeUploader();
|
||||
this.initializeLocation();
|
||||
this.initializeFileUsage();
|
||||
this.initializeReuse();
|
||||
|
||||
this.fileUsage = new mw.mmv.ui.FileUsage(
|
||||
$( '<div>' ).appendTo( this.$imageMetadata )
|
||||
);
|
||||
this.fileUsage.init();
|
||||
};
|
||||
|
||||
LIP.initializeRepoLink = function () {
|
||||
|
@ -331,7 +338,7 @@
|
|||
.appendTo( this.$locationLi );
|
||||
};
|
||||
|
||||
LIP.initializeFileUsage = function () {
|
||||
LIP.initializeReuse = function () {
|
||||
var ui = this;
|
||||
|
||||
this.$useFileLi = $( '<li>' )
|
||||
|
@ -343,13 +350,13 @@
|
|||
.prop( 'href', '#' )
|
||||
.text( mw.message( 'multimediaviewer-use-file' ).text() )
|
||||
.click( function () {
|
||||
ui.openFileUsageDialog();
|
||||
ui.openReuseDialog();
|
||||
return false;
|
||||
} )
|
||||
.appendTo( this.$useFileLi );
|
||||
};
|
||||
|
||||
LIP.openFileUsageDialog = function () {
|
||||
LIP.openReuseDialog = function () {
|
||||
// Only open dialog once
|
||||
if ( this.$dialog ) {
|
||||
return false;
|
||||
|
|
8
resources/mmv/mmv.mixins.less
Normal file
8
resources/mmv/mmv.mixins.less
Normal file
|
@ -0,0 +1,8 @@
|
|||
// from http://css3please.com/
|
||||
.box-round(@radius) {
|
||||
-webkit-border-radius: @radius; // Android ≤ 1.6, iOS 1-3.2, Safari 3-4
|
||||
border-radius: @radius; // Android 2.1+, Chrome, Firefox 4+, IE 9+, iOS 4+, Opera 10.50+, Safari 5+
|
||||
|
||||
// useful if you don't want a bg color from leaking outside the border:
|
||||
background-clip: padding-box; // Android 2.2+, Chrome, Firefox 4+, IE 9+, iOS 4+, Opera 10.50+, Safari 4+
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
/**
|
||||
* @class mw.mmv.dataProvider.Api
|
||||
* Base class for API-based data providers.
|
||||
* @abstract
|
||||
* @constructor
|
||||
* @param {mw.Api} api
|
||||
|
@ -26,18 +27,21 @@
|
|||
*/
|
||||
function Api( api, options ) {
|
||||
/**
|
||||
* @property {mediaWiki.Api} api
|
||||
* API object for dependency injection.
|
||||
* @property {mw.Api}
|
||||
*/
|
||||
this.api = api;
|
||||
|
||||
/**
|
||||
* @property {Object} options
|
||||
* Options object; the exact format and meaning is unspecified and could be different
|
||||
* from subclass to subclass.
|
||||
* @property {Object}
|
||||
*/
|
||||
this.options = options || {};
|
||||
|
||||
/**
|
||||
* @property {Object<String, jQuery.Promise>} cache
|
||||
* API call cache.
|
||||
* @property {Object.<string, jQuery.Promise>} cache
|
||||
* @protected
|
||||
*/
|
||||
this.cache = {};
|
||||
|
@ -60,17 +64,17 @@
|
|||
|
||||
|
||||
/**
|
||||
* @class mw,mmv.dataProvider.ImageUsage
|
||||
* @class mw.mmv.dataProvider.ImageUsage
|
||||
* Gets file usage information on the local wiki.
|
||||
* @extends mw.mmv.dataProvider.Api
|
||||
* @inheritdoc
|
||||
* @param {mw.Api} api
|
||||
* @param {Object} [options]
|
||||
* @param {Number[]} [options.namespaces] list of namespace ids
|
||||
* @param {Number} [options.apiLimit] number of entries to get from the API. If there are
|
||||
* @param {number[]} [options.namespaces] list of namespace ids
|
||||
* @param {number} [options.apiLimit] number of entries to get from the API. If there are
|
||||
* more pages than this, we won't have an accurate count.
|
||||
* (Also, influences query performance.)
|
||||
* @param {Number} [options.dataLimit] number of entries to actually put into the model.
|
||||
* @param {number} [options.dataLimit] number of entries to actually put into the model.
|
||||
*/
|
||||
function ImageUsage( api, options ) {
|
||||
options = $.extend( {
|
||||
|
@ -128,22 +132,26 @@
|
|||
|
||||
|
||||
/**
|
||||
* @class mw,mmv.dataProvider.GlobalUsage
|
||||
* @class mw.mmv.dataProvider.GlobalUsage
|
||||
* Gets file usage information on all wikis but the local one.
|
||||
* This needs the GlobalUsage extension to be installed.
|
||||
* @see <https://www.mediawiki.org/wiki/Extension:GlobalUsage>
|
||||
* This needs the [GlobalUsage extension](https://www.mediawiki.org/wiki/Extension:GlobalUsage)
|
||||
* to be installed.
|
||||
* @extends mw.mmv.dataProvider.Api
|
||||
* @inheritdoc
|
||||
* @param {mw.Api} api
|
||||
* @param {Object} [options]
|
||||
* @param {Number[]} [options.namespaces] list of namespace ids
|
||||
* @param {Number} [options.apiLimit] number of entries to get from the API. If there are
|
||||
* @param {number[]} [options.namespaces] list of namespace ids
|
||||
* @param {number} [options.apiLimit] number of entries to get from the API. If there are
|
||||
* more pages than this, we won't have an accurate count.
|
||||
* (Also, influences query performance.)
|
||||
* @param {Number} [options.dataLimit] number of entries to actually put into the model.
|
||||
* @param {boolean} [options.doNotUseApi] If true, always returns an empty result immediately,
|
||||
* without doing an actual API call. Used when the GlobalUsage extension (and thus the
|
||||
* API) is not available.
|
||||
* @param {number} [options.dataLimit] number of entries to actually put into the model.
|
||||
*/
|
||||
function GlobalUsage( api, options ) {
|
||||
options = $.extend( {
|
||||
doNotUseApi: false,
|
||||
apiLimit: 100,
|
||||
dataLimit: 10
|
||||
}, options );
|
||||
|
@ -160,7 +168,15 @@
|
|||
*/
|
||||
GlobalUsage.prototype.get = function( file ) {
|
||||
var dataProvider = this,
|
||||
cacheKey = file.getPrefixedDb();
|
||||
cacheKey = file.getPrefixedDb(),
|
||||
fileUsage;
|
||||
|
||||
if ( this.options.doNotUseApi ) {
|
||||
fileUsage = new mw.mmv.model.FileUsage( file, mw.mmv.model.FileUsage.Scope.GLOBAL,
|
||||
[], 0, false );
|
||||
fileUsage.fake = true;
|
||||
return $.Deferred().resolve( fileUsage );
|
||||
}
|
||||
|
||||
if ( !this.cache[cacheKey] ) {
|
||||
this.cache[cacheKey] = this.api.get( {
|
||||
|
@ -177,7 +193,7 @@
|
|||
// pages is an associative array indexed by pageid, turn it into proper array
|
||||
pages = $.map( data.query.pages, function ( v ) { return v; } );
|
||||
// the API returns a result for non-existent files as well so pages[0] will always exist
|
||||
pages = $.map( pages[0].globalusage, function( item ) {
|
||||
pages = $.map( pages[0].globalusage || {}, function( item ) {
|
||||
return {
|
||||
wiki: item.wiki,
|
||||
page: new mw.Title( item.title, item.ns )
|
||||
|
|
203
resources/mmv/mmv.ui.fileUsage.js
Normal file
203
resources/mmv/mmv.ui.fileUsage.js
Normal file
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
( function ( mw, $ ) {
|
||||
/**
|
||||
* File usage interface (what wiki pages is this file used on?)
|
||||
* @class mw.mmv.interface.FileUsage
|
||||
* @constructor
|
||||
* @param {jQuery} $container
|
||||
*/
|
||||
function FileUsage( $container ) {
|
||||
/**
|
||||
* The HTML element in which the file usage shall be shown.
|
||||
* @property {jQuery}
|
||||
*/
|
||||
this.$container = $container;
|
||||
|
||||
/**
|
||||
* The title of the file usage block.
|
||||
* @property {jQuery}
|
||||
*/
|
||||
this.$title = null;
|
||||
|
||||
/**
|
||||
* The list which contains the wiki pages using the file (and also some miscellaneous
|
||||
* stuff like 'view more' links).
|
||||
* @property {jQuery}
|
||||
*/
|
||||
this.$usageList = null;
|
||||
}
|
||||
|
||||
/** Never show more than this many local usages */
|
||||
FileUsage.prototype.MAX_LOCAL = 3;
|
||||
/** Never show more than this many global usages */
|
||||
FileUsage.prototype.MAX_GLOBAL = 3;
|
||||
|
||||
/**
|
||||
* Sets up the interface. Must be called before any other methods.
|
||||
*/
|
||||
FileUsage.prototype.init = function() {
|
||||
this.$title = $( '<h3>' ).appendTo( this.$container );
|
||||
this.$usageList = $( '<ul>' ).appendTo( this.$container );
|
||||
this.$container.addClass( 'mw-mlb-fileusage-container' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the interface.
|
||||
*/
|
||||
FileUsage.prototype.empty = function() {
|
||||
this.$title.text( '' );
|
||||
this.$usageList.empty();
|
||||
this.$container.addClass( 'empty' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays file usage based on the passed usage objects..
|
||||
* @param {mw.mmv.model.FileUsage} localUsage
|
||||
* @param {mw.mmv.model.FileUsage} globalUsage
|
||||
*/
|
||||
FileUsage.prototype.set = function( localUsage, globalUsage ) {
|
||||
var usageCount = localUsage.totalCount + globalUsage.totalCount,
|
||||
countMessage = 'multimediaviewer-fileusage-count';
|
||||
|
||||
if ( localUsage.totalCount || globalUsage.totalCount ) {
|
||||
this.$container.removeClass( 'empty' );
|
||||
|
||||
if ( localUsage.totalCountIsLowerBound || globalUsage.totalCountIsLowerBound ) {
|
||||
// "more than 100 uses" sounds nicer than "more than 103 uses"
|
||||
usageCount = Math.max( localUsage.totalCount, globalUsage.totalCount );
|
||||
countMessage = 'multimediaviewer-fileusage-count-more';
|
||||
}
|
||||
this.$title.msg( countMessage, mw.language.convertNumber( usageCount ) );
|
||||
|
||||
this.$usageList.empty();
|
||||
this.addSection( localUsage, mw.mmv.model.FileUsage.Scope.LOCAL, this.MAX_LOCAL,
|
||||
this.getLocalUsageUrl( localUsage.file ) );
|
||||
this.addSection( globalUsage, mw.mmv.model.FileUsage.Scope.GLOBAL, this.MAX_GLOBAL,
|
||||
this.getGlobalUsageUrl( globalUsage.file ) );
|
||||
} else {
|
||||
this.empty();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a usage section to the list - a title, a list of links, and a 'view more' link
|
||||
* (some or all of these might be omitted when it makes sense).
|
||||
* @param {mw.mmv.model.FileUsage} fileUsage
|
||||
* @param {mw.mmv.model.FileUsage.Scope} sectionType will be used for the section title
|
||||
* @param {number} limit max number of entries to show
|
||||
* @param {string} viewAllLink a link for the rest of entries, if there are more than the limit
|
||||
* @protected
|
||||
*/
|
||||
FileUsage.prototype.addSection = function( fileUsage, sectionType, limit, viewAllLink ) {
|
||||
if ( fileUsage.totalCount ) {
|
||||
this.$usageList.append(
|
||||
$( '<li>' ).addClass( 'mw-mlb-fileusage-' + sectionType + '-section' )
|
||||
.msg( 'multimediaviewer-fileusage-' + sectionType + '-section' )
|
||||
);
|
||||
this.addPageLinks( fileUsage.pages.slice( 0, limit ) );
|
||||
if ( fileUsage.pages.length > limit ) {
|
||||
this.addViewAllLink( viewAllLink );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends links pointing to the given pages to the end of the usage list.
|
||||
* @param {{wiki: (string|null), page: mw.Title}[]} pages
|
||||
* @protected
|
||||
*/
|
||||
FileUsage.prototype.addPageLinks = function( pages ) {
|
||||
var ui = this;
|
||||
|
||||
this.$usageList.append( $.map( pages, function( item ) {
|
||||
var pageUrl = ui.getFileUrl( item.page, item.wiki ),
|
||||
pageLink = $( '<a>' ).attr( 'href', pageUrl ).text( item.page.getMainText() );
|
||||
|
||||
if ( item.wiki ) {
|
||||
// external link - show the wiki name next to it
|
||||
return $( '<li>' ).append( pageLink ).append(
|
||||
$( '<aside>' ).text( item.wiki )
|
||||
);
|
||||
} else {
|
||||
return $( '<li>' ).append( pageLink );
|
||||
}
|
||||
} ) );
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a 'View all' link (with the given URL) to the end of the usage list.
|
||||
* @param {string} url
|
||||
* @protected
|
||||
*/
|
||||
FileUsage.prototype.addViewAllLink = function( url ) {
|
||||
this.$usageList.find( 'li:first' ).append(
|
||||
$( '<span>' ).addClass( 'mw-mlb-fileusage-view-all' ).append(
|
||||
$( '<a>' ).msg( 'multimediaviewer-fileusage-link' )
|
||||
.attr( 'href', url )
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an URL to the given file.
|
||||
* @param {mw.Title} page
|
||||
* @param {string} wiki domain name
|
||||
* @protected
|
||||
*/
|
||||
FileUsage.prototype.getFileUrl = function( page, wiki ) {
|
||||
// TODO the nice way to handle this would be to have a mw.IwTitle class for interwiki links
|
||||
if ( wiki ) {
|
||||
return new mw.Uri( wiki + page.getUrl() ).toString();
|
||||
} else {
|
||||
return page.getUrl();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a Special:WhatLinksHere link for the given file.
|
||||
* @param {mw.Title} file
|
||||
* @protected
|
||||
*/
|
||||
FileUsage.prototype.getLocalUsageUrl = function( file ) {
|
||||
// TODO special page name should be localized
|
||||
return new mw.Uri( mw.config.get( 'wgScript' ) ).extend( {
|
||||
title: 'Special:WhatLinksHere/' + file.getPrefixedDb(),
|
||||
hidetrans: 1,
|
||||
hidelinks: 1,
|
||||
hideredirs: 1
|
||||
} ).toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a Special:GlobalUsage link for the given file.
|
||||
* @param {mw.Title} file
|
||||
* @protected
|
||||
*/
|
||||
FileUsage.prototype.getGlobalUsageUrl = function( file ) {
|
||||
// TODO special page name should be localized
|
||||
return new mw.Uri( mw.config.get( 'wgScript' ) ).extend( {
|
||||
title: 'Special:GlobalUsage',
|
||||
target: file.getPrefixedDb(),
|
||||
filterlocal: 1
|
||||
} ).toString();
|
||||
};
|
||||
|
||||
mw.mmv.ui = mw.mmv.ui || {};
|
||||
mw.mmv.ui.FileUsage = FileUsage;
|
||||
} ) ( mediaWiki, jQuery );
|
59
resources/mmv/mmv.ui.fileUsage.less
Normal file
59
resources/mmv/mmv.ui.fileUsage.less
Normal file
|
@ -0,0 +1,59 @@
|
|||
@import "mmv.mixins";
|
||||
|
||||
.mw-mlb-fileusage-container {
|
||||
@box-color: #FFFFFF;
|
||||
@heading-color: #F8F8F8;
|
||||
@inner-border-color: #DDDDDD;
|
||||
@outer-border-color: #C9C9C9;
|
||||
@box-shadow-color: #D0D0D0;
|
||||
@info-color: #565656;
|
||||
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
width: 50%;
|
||||
margin-left: 50%; /* placeholder for More panel that will go to the left side */
|
||||
|
||||
h3 {
|
||||
color: @info-color;
|
||||
font-weight: normal;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0 30px 10px 0;
|
||||
border: 1px solid @outer-border-color;
|
||||
border-bottom: 2px solid @box-shadow-color;
|
||||
.box-round(3px);
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid @inner-border-color;
|
||||
padding: 3px 5px 3px 15px;
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
background-color: @box-color;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.mw-mlb-fileusage-local-section, &.mw-mlb-fileusage-global-section, &.mw-mlb-fileusage-view-all {
|
||||
color: @info-color;
|
||||
background-color: @heading-color;
|
||||
}
|
||||
aside {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
max-width: 25%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 0.9em;
|
||||
color: @info-color;
|
||||
}
|
||||
.mw-mlb-fileusage-view-all {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@
|
|||
* @param {mw.mmv.model.FileUsage.Scope} scope see {@link mw.mmv.model.FileUsage#scope}
|
||||
* @param {{wiki: (string|null), page: mw.Title}[]} pages see {@link mw.mmv.model.FileUsage#pages}
|
||||
* @param {number} [totalCount] see {@link mw.mmv.model.FileUsage#totalCount}
|
||||
* @param {boolean} [totalCountIsLowerBound = false] see {@link mw.mmv.model.FileUsage#totalCountIsLowerBound} *
|
||||
* @param {boolean} [totalCountIsLowerBound = false] see {@link mw.mmv.model.FileUsage#totalCountIsLowerBound}
|
||||
*/
|
||||
function FileUsage(
|
||||
file,
|
||||
|
|
|
@ -204,6 +204,31 @@
|
|||
} );
|
||||
} );
|
||||
|
||||
// no globalusage field - this happens when the extension is not installed
|
||||
QUnit.asyncTest( 'GlobalUsage missing data test', 1, function ( assert ) {
|
||||
var api = { get: function() {
|
||||
return $.Deferred().resolve( {
|
||||
query: {
|
||||
pages: {
|
||||
'-1': {
|
||||
ns: 6,
|
||||
title: 'File:Stuff.jpg',
|
||||
missing: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
} },
|
||||
options = {},
|
||||
file = new mw.Title( 'File:Stuff.jpg' ),
|
||||
globalUsageDataProvider = new mw.mmv.dataProvider.GlobalUsage( api, options );
|
||||
|
||||
globalUsageDataProvider.get( file ).then( function( fileUsage ) {
|
||||
assert.strictEqual( fileUsage.totalCount, 0, 'Count flag is set correctly' );
|
||||
QUnit.start();
|
||||
} );
|
||||
} );
|
||||
|
||||
QUnit.asyncTest( 'GlobalUsage fail test', 1, function ( assert ) {
|
||||
var api = { get: function() {
|
||||
return $.Deferred().resolve( {
|
||||
|
@ -226,4 +251,17 @@
|
|||
QUnit.start();
|
||||
} );
|
||||
} );
|
||||
|
||||
QUnit.asyncTest( 'GlobalUsage doNotUseApi test', 2, function ( assert ) {
|
||||
var api = {},
|
||||
options = { doNotUseApi: true },
|
||||
file = new mw.Title( 'File:Stuff.jpg' ),
|
||||
globalUsageDataProvider = new mw.mmv.dataProvider.GlobalUsage( api, options );
|
||||
|
||||
globalUsageDataProvider.get( file ).done( function( fileUsage ) {
|
||||
assert.strictEqual( fileUsage.pages.length, 0, 'Does not return any pages' );
|
||||
assert.ok( fileUsage.fake );
|
||||
QUnit.start();
|
||||
} );
|
||||
} );
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
141
tests/qunit/mmv.ui.fileUsage.test.js
Normal file
141
tests/qunit/mmv.ui.fileUsage.test.js
Normal file
|
@ -0,0 +1,141 @@
|
|||
( function ( mw, $ ) {
|
||||
QUnit.module( 'mmv.ui.fileUsage', QUnit.newMwEnvironment() );
|
||||
|
||||
QUnit.test( 'File usage panel with no usage', 1, function( assert ) {
|
||||
var fileUsage = new mw.mmv.ui.FileUsage( $( '#qunit-fixture' ) ),
|
||||
file = new mw.Title( 'File:Foo' ),
|
||||
localUsage = new mw.mmv.model.FileUsage( file, 'local', [] ),
|
||||
globalUsage = new mw.mmv.model.FileUsage( file, 'global', [] );
|
||||
|
||||
fileUsage.init();
|
||||
fileUsage.set( localUsage, globalUsage );
|
||||
|
||||
assert.ok( $( '#qunit-fixture' ).hasClass( 'empty' ) );
|
||||
} );
|
||||
|
||||
QUnit.test( 'File usage panel with local usage', 7, function( assert ) {
|
||||
var $list,
|
||||
fileUsage = new mw.mmv.ui.FileUsage( $( '#qunit-fixture' ) ),
|
||||
file = new mw.Title( 'File:Foo' ),
|
||||
localUsage = new mw.mmv.model.FileUsage( file, 'local', [
|
||||
{ wiki: null, page: new mw.Title( 'Bar' ) },
|
||||
{ wiki: null, page: new mw.Title( 'Baz' ) }
|
||||
] ),
|
||||
globalUsage = new mw.mmv.model.FileUsage( file, 'global', [] );
|
||||
|
||||
fileUsage.init();
|
||||
fileUsage.set( localUsage, globalUsage );
|
||||
|
||||
assert.ok( ! $( '#qunit-fixture' ).hasClass( 'empty' ) );
|
||||
|
||||
$list = $( '#qunit-fixture li:not([class])' );
|
||||
assert.strictEqual( $list.length, 2 );
|
||||
assert.strictEqual( $list.eq( 0 ).text(), 'Bar' );
|
||||
assert.strictEqual( $list.eq( 1 ).text(), 'Baz' );
|
||||
|
||||
assert.strictEqual( $( '#qunit-fixture .mw-mlb-fileusage-local-section' ).length, 1 );
|
||||
assert.strictEqual( $( '#qunit-fixture .mw-mlb-fileusage-global-section' ).length, 0 );
|
||||
assert.strictEqual( $( '#qunit-fixture .mw-mlb-fileusage-view-all' ).length, 0 );
|
||||
} );
|
||||
|
||||
QUnit.test( 'File usage panel with local usage and overflow', 3, function( assert ) {
|
||||
var $list,
|
||||
fileUsage = new mw.mmv.ui.FileUsage( $( '#qunit-fixture' ) ),
|
||||
file = new mw.Title( 'File:Foo' ),
|
||||
localUsage = new mw.mmv.model.FileUsage( file, 'local', [
|
||||
{ wiki: null, page: new mw.Title( 'Bar' ) },
|
||||
{ wiki: null, page: new mw.Title( 'Baz' ) },
|
||||
{ wiki: null, page: new mw.Title( 'Boom' ) },
|
||||
{ wiki: null, page: new mw.Title( 'Boing' ) }
|
||||
] ),
|
||||
globalUsage = new mw.mmv.model.FileUsage( file, 'global', [] );
|
||||
|
||||
fileUsage.init();
|
||||
fileUsage.set( localUsage, globalUsage );
|
||||
|
||||
assert.ok( ! $( '#qunit-fixture' ).hasClass( 'empty' ) );
|
||||
|
||||
$list = $( '#qunit-fixture li:not([class])' );
|
||||
assert.strictEqual( $list.length, fileUsage.MAX_LOCAL );
|
||||
assert.strictEqual( $( '#qunit-fixture .mw-mlb-fileusage-view-all' ).length, 1 );
|
||||
} );
|
||||
|
||||
QUnit.test( 'File usage panel with global usage', 9, function( assert ) {
|
||||
var $list,
|
||||
fileUsage = new mw.mmv.ui.FileUsage( $( '#qunit-fixture' ) ),
|
||||
file = new mw.Title( 'File:Foo' ),
|
||||
localUsage = new mw.mmv.model.FileUsage( file, 'local', [] ),
|
||||
globalUsage = new mw.mmv.model.FileUsage( file, 'global', [
|
||||
{ wiki: 'x.com', page: new mw.Title( 'Bar' ) },
|
||||
{ wiki: 'y.com', page: new mw.Title( 'Baz' ) }
|
||||
] );
|
||||
|
||||
fileUsage.init();
|
||||
fileUsage.set( localUsage, globalUsage );
|
||||
|
||||
assert.ok( ! $( '#qunit-fixture' ).hasClass( 'empty' ) );
|
||||
|
||||
$list = $( '#qunit-fixture li:not([class])' );
|
||||
assert.strictEqual( $list.length, 2 );
|
||||
assert.strictEqual( $list.eq( 0 ).find( 'a' ).text(), 'Bar' );
|
||||
assert.strictEqual( $list.eq( 1 ).find( 'a' ).text(), 'Baz' );
|
||||
assert.strictEqual( $list.eq( 0 ).find( 'aside' ).text(), 'x.com' );
|
||||
assert.strictEqual( $list.eq( 1 ).find( 'aside' ).text(), 'y.com' );
|
||||
|
||||
assert.strictEqual( $( '#qunit-fixture .mw-mlb-fileusage-local-section' ).length, 0 );
|
||||
assert.strictEqual( $( '#qunit-fixture .mw-mlb-fileusage-global-section' ).length, 1 );
|
||||
assert.strictEqual( $( '#qunit-fixture .mw-mlb-fileusage-view-all' ).length, 0 );
|
||||
} );
|
||||
|
||||
QUnit.test( 'File usage panel with lots of uses', 3, function( assert ) {
|
||||
var $list,
|
||||
totalCount = 100,
|
||||
fileUsage = new mw.mmv.ui.FileUsage( $( '#qunit-fixture' ) ),
|
||||
file = new mw.Title( 'File:Foo' ),
|
||||
localUsage = new mw.mmv.model.FileUsage( file, 'local', [
|
||||
{ wiki: null, page: new mw.Title( 'Bar' ) },
|
||||
{ wiki: null, page: new mw.Title( 'Baz' ) },
|
||||
{ wiki: null, page: new mw.Title( 'Boom' ) },
|
||||
{ wiki: null, page: new mw.Title( 'Boing' ) }
|
||||
], totalCount, true ),
|
||||
globalUsage = new mw.mmv.model.FileUsage( file, 'global', [
|
||||
{ wiki: 'x.com', page: new mw.Title( 'Bar' ) },
|
||||
{ wiki: 'x.com', page: new mw.Title( 'Baz' ) },
|
||||
{ wiki: 'y.com', page: new mw.Title( 'Bar' ) },
|
||||
{ wiki: 'y.com', page: new mw.Title( 'Baz' ) }
|
||||
] );
|
||||
|
||||
fileUsage.init();
|
||||
fileUsage.set( localUsage, globalUsage );
|
||||
|
||||
$list = $( '#qunit-fixture li:not([class])' );
|
||||
assert.strictEqual( $list.length, fileUsage.MAX_LOCAL + fileUsage.MAX_GLOBAL );
|
||||
assert.strictEqual( $( '#qunit-fixture .mw-mlb-fileusage-view-all' ).length, 2 );
|
||||
assert.ok( $( '#qunit-fixture h3' ).text().match( totalCount ) );
|
||||
} );
|
||||
|
||||
QUnit.test( 'The interface is emptied properly when necessary', 4, function( assert ) {
|
||||
var $list,
|
||||
fileUsage = new mw.mmv.ui.FileUsage( $( '#qunit-fixture' ) ),
|
||||
file = new mw.Title( 'File:Foo' ),
|
||||
localUsage = new mw.mmv.model.FileUsage( file, 'local', [
|
||||
{ wiki: null, page: new mw.Title( 'Bar' ) },
|
||||
{ wiki: null, page: new mw.Title( 'Baz' ) }
|
||||
] ),
|
||||
globalUsage = new mw.mmv.model.FileUsage( file, 'global', [] );
|
||||
|
||||
fileUsage.init();
|
||||
fileUsage.set( localUsage, globalUsage );
|
||||
|
||||
assert.ok( ! $( '#qunit-fixture' ).hasClass( 'empty' ) );
|
||||
|
||||
fileUsage.empty();
|
||||
|
||||
assert.ok( $( '#qunit-fixture' ).hasClass( 'empty' ) );
|
||||
|
||||
$list = $( '#qunit-fixture li:not([class])' );
|
||||
assert.strictEqual( $list.length, 0 );
|
||||
|
||||
assert.strictEqual( $( '#qunit-fixture h3' ).text(), '' );
|
||||
} );
|
||||
}( mediaWiki, jQuery ) );
|
Loading…
Reference in a new issue