mw.ForeignStructuredUpload: Provide category suggestions from the right wiki

Also check for category existence on the right wiki, and generate
links pointing to the right wiki. Usually.

Bug: T116075
Change-Id: I85da301db4cb407b011277b0c00eb09a8bf3829f
This commit is contained in:
Bartosz Dziewoński 2015-10-21 00:32:19 +02:00 committed by Ori.livneh
parent 7662212384
commit 86dedeea7f
4 changed files with 112 additions and 41 deletions

View file

@ -2031,6 +2031,8 @@ return array(
'dependencies' => array(
'oojs-ui',
'mediawiki.api',
'mediawiki.ForeignApi',
'mediawiki.Title',
),
'targets' => array( 'desktop', 'mobile' ),
),

View file

@ -7,42 +7,45 @@
( function ( $, mw ) {
/**
* @class mw.widgets.CategoryCapsuleItemWidget
* @class mw.widgets.PageExistenceCache
* @private
* @param {mw.Api} [api]
*/
var processExistenceCheckQueueDebounced,
api = new mw.Api(),
currentRequest = null,
existenceCache = {},
existenceCheckQueue = {};
// The existence checking code really could be refactored into a separate class.
function PageExistenceCache( api ) {
this.api = api || new mw.Api();
this.processExistenceCheckQueueDebounced = OO.ui.debounce( this.processExistenceCheckQueue );
this.currentRequest = null;
this.existenceCache = {};
this.existenceCheckQueue = {};
}
/**
* Check for existence of pages in the queue.
*
* @private
*/
function processExistenceCheckQueue() {
PageExistenceCache.prototype.processExistenceCheckQueue = function () {
var queue, titles;
if ( currentRequest ) {
if ( this.currentRequest ) {
// Don't fire off a million requests at the same time
currentRequest.always( function () {
currentRequest = null;
processExistenceCheckQueueDebounced();
} );
this.currentRequest.always( function () {
this.currentRequest = null;
this.processExistenceCheckQueueDebounced();
}.bind( this ) );
return;
}
queue = existenceCheckQueue;
existenceCheckQueue = {};
queue = this.existenceCheckQueue;
this.existenceCheckQueue = {};
titles = Object.keys( queue ).filter( function ( title ) {
if ( existenceCache.hasOwnProperty( title ) ) {
queue[ title ].resolve( existenceCache[ title ] );
if ( this.existenceCache.hasOwnProperty( title ) ) {
queue[ title ].resolve( this.existenceCache[ title ] );
}
return !existenceCache.hasOwnProperty( title );
} );
return !this.existenceCache.hasOwnProperty( title );
}.bind( this ) );
if ( !titles.length ) {
return;
}
currentRequest = api.get( {
this.currentRequest = this.api.get( {
action: 'query',
prop: [ 'info' ],
titles: titles
@ -50,14 +53,12 @@
var index, curr, title;
for ( index in response.query.pages ) {
curr = response.query.pages[ index ];
title = mw.Title.newFromText( curr.title ).getPrefixedText();
existenceCache[ title ] = curr.missing === undefined;
queue[ title ].resolve( existenceCache[ title ] );
title = new ForeignTitle( curr.title ).getPrefixedText();
this.existenceCache[ title ] = curr.missing === undefined;
queue[ title ].resolve( this.existenceCache[ title ] );
}
} );
}
processExistenceCheckQueueDebounced = OO.ui.debounce( processExistenceCheckQueue );
}.bind( this ) );
};
/**
* Register a request to check whether a page exists.
@ -66,16 +67,35 @@
* @param {mw.Title} title
* @return {jQuery.Promise} Promise resolved with true if the page exists or false otherwise
*/
function checkPageExistence( title ) {
PageExistenceCache.prototype.checkPageExistence = function ( title ) {
var key = title.getPrefixedText();
if ( !existenceCheckQueue[ key ] ) {
existenceCheckQueue[ key ] = $.Deferred();
if ( !this.existenceCheckQueue[ key ] ) {
this.existenceCheckQueue[ key ] = $.Deferred();
}
processExistenceCheckQueueDebounced();
return existenceCheckQueue[ key ].promise();
}
this.processExistenceCheckQueueDebounced();
return this.existenceCheckQueue[ key ].promise();
};
/**
* @class mw.widgets.ForeignTitle
* @private
* @extends mw.Title
*
* @constructor
* @inheritdoc
*/
function ForeignTitle() {
ForeignTitle.parent.apply( this, arguments );
}
OO.inheritClass( ForeignTitle, mw.Title );
ForeignTitle.prototype.getNamespacePrefix = function () {
// We only need to handle categories here...
return 'Category:'; // HACK
};
/**
* @class mw.widgets.CategoryCapsuleItemWidget
*
* Category selector capsule item widget. Extends OO.ui.CapsuleItemWidget with the ability to link
* to the given page, and to show its existence status (i.e., whether it is a redlink).
*
@ -85,6 +105,7 @@
* @constructor
* @param {Object} config Configuration options
* @cfg {mw.Title} title Page title to use (required)
* @cfg {string} [apiUrl] API URL, if not the current wiki's API
*/
mw.widgets.CategoryCapsuleItemWidget = function MWWCategoryCapsuleItemWidget( config ) {
// Parent constructor
@ -95,6 +116,7 @@
// Properties
this.title = config.title;
this.apiUrl = config.apiUrl || '';
this.$link = $( '<a>' )
.text( this.label )
.attr( 'target', '_blank' )
@ -107,15 +129,39 @@
this.setMissing( false );
this.$label.replaceWith( this.$link );
this.setLabelElement( this.$link );
checkPageExistence( this.title ).done( function ( exists ) {
this.setMissing( !exists );
}.bind( this ) );
/*jshint -W024*/
if ( !this.constructor.static.pageExistenceCaches[ this.apiUrl ] ) {
this.constructor.static.pageExistenceCaches[ this.apiUrl ] =
new PageExistenceCache( new mw.ForeignApi( this.apiUrl ) );
}
this.constructor.static.pageExistenceCaches[ this.apiUrl ]
.checkPageExistence( new ForeignTitle( this.title.getPrefixedText() ) )
.done( function ( exists ) {
this.setMissing( !exists );
}.bind( this ) );
/*jshint +W024*/
};
/* Setup */
OO.inheritClass( mw.widgets.CategoryCapsuleItemWidget, OO.ui.CapsuleItemWidget );
/* Static Properties */
/*jshint -W024*/
/**
* Map of API URLs to PageExistenceCache objects.
*
* @static
* @inheritable
* @property {Object}
*/
mw.widgets.CategoryCapsuleItemWidget.static.pageExistenceCaches = {
'': new PageExistenceCache()
};
/*jshint +W024*/
/* Methods */
/**
@ -125,13 +171,17 @@
* @param {boolean} missing Whether the page is missing (does not exist)
*/
mw.widgets.CategoryCapsuleItemWidget.prototype.setMissing = function ( missing ) {
var
title = new ForeignTitle( this.title.getPrefixedText() ), // HACK
prefix = this.apiUrl.replace( '/w/api.php', '' ); // HACK
if ( !missing ) {
this.$link
.attr( 'href', this.title.getUrl() )
.attr( 'href', prefix + title.getUrl() )
.removeClass( 'new' );
} else {
this.$link
.attr( 'href', this.title.getUrl( { action: 'edit', redlink: 1 } ) )
.attr( 'href', prefix + title.getUrl( { action: 'edit', redlink: 1 } ) )
.addClass( 'new' );
}
};

View file

@ -30,6 +30,7 @@
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {mw.Api} [api] Instance of mw.Api (or subclass thereof) to use for queries
* @cfg {number} [limit=10] Maximum number of results to load
* @cfg {mw.widgets.CategorySelector.SearchType[]} [searchTypes=[mw.widgets.CategorySelector.SearchType.OpenSearch]]
* Default search API to use when searching.
@ -61,7 +62,7 @@
this.$input.on( 'change input cut paste', OO.ui.debounce( this.updateMenuItems.bind( this ), 100 ) );
// Initialize
this.api = new mw.Api();
this.api = config.api || new mw.Api();
}
/* Setup */
@ -178,6 +179,7 @@
*/
CSP.createItemWidget = function ( data ) {
return new mw.widgets.CategoryCapsuleItemWidget( {
apiUrl: this.api.apiUrl || undefined,
title: mw.Title.newFromText( data, NS_CATEGORY )
} );
};

View file

@ -35,6 +35,21 @@
/* Uploading */
/**
* @inheritdoc
*/
mw.ForeignStructuredUpload.BookletLayout.prototype.initialize = function () {
mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this );
// Point the CategorySelector to the right wiki as soon as we know what the right wiki is
this.upload.apiPromise.done( function ( api ) {
// If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything
if ( api.apiUrl ) {
// Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance
this.categoriesWidget.api = new mw.ForeignApi( api.apiUrl );
}
}.bind( this ) );
};
/**
* Returns a {@link mw.ForeignStructuredUpload mw.ForeignStructuredUpload}
* with the {@link #cfg-target target} specified in config.
@ -150,6 +165,8 @@
mustBeBefore: moment().add( 1, 'day' ).locale( 'en' ).format( 'YYYY-MM-DD' ) // Tomorrow
} );
this.categoriesWidget = new mw.widgets.CategorySelector( {
// Can't be done here because we don't know the target wiki yet... done in #initialize.
// api: new mw.ForeignApi( ... ),
$overlay: this.$overlay
} );