mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-29 00:30:44 +00:00
aa9eb95455
Change the media search widget to work with resource queues and providers. Create providers based on the user's filerepo settings and aggregate their responses with the media queue. Stop asking for more results from providers that are depleted. Also fixes a rather nasty infinite-loop bug where the API returns only very few images, and the UI keeps asking for more. Bug: T78161 Bug: T88764 Change-Id: I65aed3446cd1f056476c56e6e04522c70e49e595
510 lines
11 KiB
JavaScript
510 lines
11 KiB
JavaScript
/*!
|
|
* VisualEditor DataModel MWMediaResourceProvider class.
|
|
*
|
|
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* MediaWiki media resource provider.
|
|
*
|
|
* @class
|
|
* @mixins OO.EventEmitter
|
|
*
|
|
* @constructor
|
|
* @param {Object} [config] Configuration options
|
|
*/
|
|
ve.dm.MWMediaResourceProvider = function VeDmMWMediaResourceProvider( config ) {
|
|
config = config || {};
|
|
|
|
// Source Configuration
|
|
this.apiurl = this.setAPIurl( config.apiurl );
|
|
this.name = config.name;
|
|
this.displayName = config.displayName;
|
|
this.local = config.local;
|
|
this.scriptDirUrl = config.scriptDirUrl;
|
|
|
|
// ajaxOptions configuration
|
|
this.dataType = config.dataType || 'jsonp';
|
|
this.cached = config.cached || true;
|
|
|
|
// Fetching configuration
|
|
this.fetchLimit = config.limit || 30;
|
|
this.iiprop = config.iiprop || [ 'dimensions', 'url', 'mediatype', 'extmetadata', 'timestamp' ];
|
|
this.fetchProp = config.fetchProp || 'imageinfo';
|
|
this.lang = config.lang || 'en';
|
|
|
|
this.siteInfoPromise = null;
|
|
this.thumbSizes = [];
|
|
this.imageSizes = [];
|
|
|
|
this.depleted = false;
|
|
this.offset = config.offset || 0;
|
|
this.setQuery( config.query || '' );
|
|
|
|
// Mixin constructors
|
|
OO.EventEmitter.call( this );
|
|
};
|
|
|
|
/* Setup */
|
|
OO.initClass( ve.dm.MWMediaResourceProvider );
|
|
OO.mixinClass( ve.dm.MWMediaResourceProvider, OO.EventEmitter );
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Initialize the source and get the site info.
|
|
*
|
|
* Connect to the api url and retrieve the siteinfo parameters
|
|
* that are required for fetching results.
|
|
*
|
|
* @return {jQuery.Promise} Promise that resolves when the class
|
|
* properties are set.
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.loadSiteInfo = function () {
|
|
var provider = this;
|
|
|
|
if ( !this.siteInfoPromise ) {
|
|
this.siteInfoPromise = ve.init.target.constructor.static.apiRequest( {
|
|
action: 'query',
|
|
meta: 'siteinfo'
|
|
} )
|
|
.then( function ( data ) {
|
|
if ( data.error ) {
|
|
return $.Deferred().reject();
|
|
}
|
|
provider.setImageSizes( ve.getProp( data, 'query', 'general', 'imagelimits' ) || [] );
|
|
provider.setThumbSizes( ve.getProp( data, 'query', 'general', 'thumblimits' ) || [] );
|
|
} );
|
|
}
|
|
return this.siteInfoPromise;
|
|
};
|
|
|
|
/**
|
|
* Get results from the source
|
|
*
|
|
* @return {jQuery.Promise} Promise that is resolved into an array
|
|
* of available results, or is rejected if no results are available.
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getResults = function ( howMany ) {
|
|
var xhr,
|
|
aborted = false,
|
|
provider = this;
|
|
|
|
return this.loadSiteInfo()
|
|
.then( function () {
|
|
if ( aborted ) {
|
|
return $.Deferred().reject();
|
|
}
|
|
xhr = provider.fetchAPIresults( howMany );
|
|
return xhr;
|
|
} )
|
|
.then(
|
|
function ( results ) {
|
|
if ( results.length === 0 ) {
|
|
provider.toggleDepleted( true );
|
|
}
|
|
return results;
|
|
},
|
|
// Process failed, return an empty promise
|
|
function () {
|
|
provider.toggleDepleted( true );
|
|
return $.Deferred().resolve( [] );
|
|
}
|
|
)
|
|
.promise( { abort: function () {
|
|
aborted = true;
|
|
if ( xhr ) {
|
|
xhr.abort();
|
|
}
|
|
} } );
|
|
};
|
|
|
|
/**
|
|
* Call the API for search results.
|
|
*
|
|
* @param {number} howMany The number of results to retrieve
|
|
* @return {jQuery.Promise} Promise that resolves with an array of objects that contain
|
|
* the fetched data.
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.fetchAPIresults = function ( howMany ) {
|
|
var xhr,
|
|
ajaxOptions = {},
|
|
query = this.getQuery(),
|
|
provider = this,
|
|
apiCallConfig = {
|
|
action: 'query',
|
|
generator: 'search',
|
|
gsrsearch: query,
|
|
gsrnamespace: 6,
|
|
continue: '',
|
|
gsroffset: this.getOffset(),
|
|
prop: this.getFetchProp(),
|
|
// Language of the extmetadata details
|
|
iiextmetadatalanguage: this.getLang(),
|
|
iiprop: this.getIiProp().join( '|' ),
|
|
iiurlheight: this.getMaxHeight(),
|
|
// Standard width per resource
|
|
iiurlwidth: this.getStandardWidth()
|
|
};
|
|
|
|
howMany = howMany || 20;
|
|
// Initial number of images
|
|
apiCallConfig.gsrlimit = howMany;
|
|
|
|
if ( this.isValid() ) {
|
|
if ( this.isLocal() ) {
|
|
ajaxOptions = {
|
|
url: mw.util.wikiScript( 'api' ),
|
|
// If the url is local use json
|
|
dataType: 'json'
|
|
};
|
|
} else {
|
|
ajaxOptions = {
|
|
// If 'apiurl' is set, use that. Otherwise, build the url
|
|
// from scriptDirUrl and /api.php suffix
|
|
url: this.apiurl || ( this.scriptDirUrl + '/api.php' ),
|
|
// If the url is not the same origin use jsonp
|
|
dataType: 'jsonp',
|
|
// JSON-P requests are not cached by default and get a &_=random trail.
|
|
// While setting cache=true will still bypass cache in most case due to the
|
|
// callback parameter, at least drop the &_=random trail which triggers
|
|
// an API warning (invalid parameter).
|
|
cache: true
|
|
};
|
|
}
|
|
|
|
xhr = ve.init.target.constructor.static.apiRequest( apiCallConfig, ajaxOptions );
|
|
return xhr
|
|
.then( function ( data ) {
|
|
var page, newObj,
|
|
results = [],
|
|
raw = ve.getProp( data, 'query', 'pages' );
|
|
if ( data[ 'continue' ] ) {
|
|
// Update the offset for next time
|
|
provider.setOffset( data[ 'continue' ].gsroffset );
|
|
} else {
|
|
// This is the last available set of result. Mark as depleted!
|
|
provider.toggleDepleted( true );
|
|
}
|
|
if ( raw ) {
|
|
// Strip away the page ids
|
|
for ( page in raw ) {
|
|
newObj = raw[page].imageinfo[0];
|
|
newObj.title = raw[page].title;
|
|
results.push( newObj );
|
|
}
|
|
}
|
|
return results;
|
|
} )
|
|
.promise( { abort: xhr.abort } );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get search query
|
|
*
|
|
* @return {string} search query
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getQuery = function () {
|
|
return this.query;
|
|
};
|
|
|
|
/**
|
|
* Set search query
|
|
*
|
|
* @param {string} value
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setQuery = function ( value ) {
|
|
if ( this.query !== value ) {
|
|
this.query = value;
|
|
// Reset offset
|
|
this.setOffset( 0 );
|
|
// Reset depleted status
|
|
this.toggleDepleted( false );
|
|
}
|
|
};
|
|
/**
|
|
* Set api url
|
|
*
|
|
* @param {string} API url
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setAPIurl = function ( url ) {
|
|
this.apiurl = url;
|
|
};
|
|
|
|
/**
|
|
* Set api url
|
|
*
|
|
* @return {string} API url
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getAPIurl = function () {
|
|
return this.apiurl;
|
|
};
|
|
|
|
/**
|
|
* Set name
|
|
*
|
|
* @param {string} name
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setName = function ( name ) {
|
|
this.name = name;
|
|
};
|
|
|
|
/**
|
|
* Get name
|
|
*
|
|
* @returns {string} name
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getName = function () {
|
|
return this.name;
|
|
};
|
|
|
|
/**
|
|
* Get displayName
|
|
*
|
|
* @return {string} displayName
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getDisplayName = function () {
|
|
return this.displayName;
|
|
};
|
|
|
|
/**
|
|
* Set displayName
|
|
*
|
|
* @param {string} displayName
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setDisplayName = function ( displayName ) {
|
|
this.displayName = displayName;
|
|
};
|
|
|
|
/**
|
|
* Get isLocal value
|
|
*
|
|
* @return {boolean} isLocal value
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.isLocal = function () {
|
|
return this.local;
|
|
};
|
|
|
|
/**
|
|
* Get ScriptDirUrl
|
|
*
|
|
* @return {string} ScriptDirUrl
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getScriptDirUrl = function () {
|
|
return this.scriptDirUrl;
|
|
};
|
|
|
|
/**
|
|
* Set scriptDirUrl
|
|
*
|
|
* @param {string} scriptDirUrl
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setScriptDirUrl = function ( scriptDirUrl ) {
|
|
this.scriptDirUrl = scriptDirUrl;
|
|
};
|
|
|
|
/**
|
|
* Get dataType
|
|
*
|
|
* @return {string} dataType
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getDataType = function () {
|
|
return this.dataType;
|
|
};
|
|
|
|
/**
|
|
* Set dataType
|
|
*
|
|
* @param {string} dataType
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setDataType = function ( dataType ) {
|
|
this.dataType = dataType;
|
|
};
|
|
|
|
/**
|
|
* Get cached
|
|
*
|
|
* @return {boolean} cached
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.isCached = function () {
|
|
return this.cached;
|
|
};
|
|
|
|
/**
|
|
* Get fetch limit or 'page' size. This is the number
|
|
* of results per request.
|
|
*
|
|
* @return {number} limit
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getFetchLimit = function () {
|
|
return this.limit;
|
|
};
|
|
|
|
/**
|
|
* Set limit
|
|
*
|
|
* @param {number} limit
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setFetchLimit = function ( limit ) {
|
|
this.limit = limit;
|
|
};
|
|
|
|
/**
|
|
* Get properties
|
|
*
|
|
* @return {string[]} properties
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getIiProp = function () {
|
|
return this.iiprop;
|
|
};
|
|
|
|
/**
|
|
* Get max height
|
|
*
|
|
* @return {number|undefined} Maximum height
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getMaxHeight = function () {
|
|
return this.maxHeight;
|
|
};
|
|
|
|
/**
|
|
* Set maximum height
|
|
*
|
|
* @param {number} Maximum height
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setMaxHeight = function ( maxHeight ) {
|
|
this.maxHeight = maxHeight;
|
|
};
|
|
|
|
/**
|
|
* Get standard width, based on the provider source's thumb sizes.
|
|
*
|
|
* @return {number|undefined} fetchWidth
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getStandardWidth = function () {
|
|
return this.thumbSizes && this.thumbSizes[ this.thumbSizes.length - 1 ];
|
|
};
|
|
|
|
/**
|
|
* Get prop
|
|
*
|
|
* @return {string} prop
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getFetchProp = function () {
|
|
return this.fetchProp;
|
|
};
|
|
|
|
/**
|
|
* Set prop
|
|
*
|
|
* @param {string} prop
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setFetchProp = function ( prop ) {
|
|
this.fetchProp = prop;
|
|
};
|
|
|
|
/**
|
|
* Get lang
|
|
*
|
|
* @return {string} lang
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getLang = function () {
|
|
return this.lang;
|
|
};
|
|
|
|
/**
|
|
* Set lang
|
|
*
|
|
* @param {string} lang
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setLang = function ( lang ) {
|
|
this.lang = lang;
|
|
};
|
|
|
|
/**
|
|
* Get Offset
|
|
*
|
|
* @return {number} Offset
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getOffset = function () {
|
|
return this.offset;
|
|
};
|
|
|
|
/**
|
|
* Set Offset
|
|
*
|
|
* @param {number} Offset
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setOffset = function ( offset ) {
|
|
this.offset = offset;
|
|
};
|
|
|
|
/**
|
|
* Set thumb sizes
|
|
*
|
|
* @param {number[]} sizes Available thumbnail sizes
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setThumbSizes = function ( sizes ) {
|
|
this.thumbSizes = sizes;
|
|
};
|
|
|
|
/**
|
|
* Set image sizes
|
|
*
|
|
* @param {number[]} sizes Available image sizes
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.setImageSizes = function ( sizes ) {
|
|
this.imageSizes = sizes;
|
|
};
|
|
|
|
/**
|
|
* Get thumb sizes
|
|
*
|
|
* @returns {number[]} sizes Available thumbnail sizes
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getThumbSizes = function () {
|
|
return this.thumbSizes;
|
|
};
|
|
|
|
/**
|
|
* Get image sizes
|
|
*
|
|
* @returns {number[]} sizes Available image sizes
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.getImageSizes = function () {
|
|
return this.imageSizes;
|
|
};
|
|
|
|
/**
|
|
* Check whether the provider is depleted
|
|
*
|
|
* @return {boolean} depleted
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.isDepleted = function () {
|
|
return this.depleted;
|
|
};
|
|
|
|
/**
|
|
* Toggle depleted state
|
|
*
|
|
* @param {boolean} depleted
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.toggleDepleted = function ( isDepleted ) {
|
|
this.depleted = isDepleted !== undefined ? isDepleted : !this.depleted;
|
|
};
|
|
|
|
/**
|
|
* Check if this source is valid and ready for search.
|
|
* @return {boolean} Source is valid
|
|
*/
|
|
ve.dm.MWMediaResourceProvider.prototype.isValid = function () {
|
|
return this.getQuery() &&
|
|
(
|
|
// If we don't have either 'apiurl' or 'scriptDirUrl'
|
|
// the source is invalid, and we will skip it
|
|
this.apiurl || this.scriptDirUrl !== undefined
|
|
);
|
|
};
|