From 86dedeea7f7cae5452acf5162672f34c32b593fd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bartosz=20Dziewo=C5=84ski?= Date: Wed, 21 Oct 2015 00:32:19 +0200 Subject: [PATCH] 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 --- resources/Resources.php | 2 + .../mw.widgets.CategoryCapsuleItemWidget.js | 128 ++++++++++++------ .../mw.widgets.CategorySelector.js | 4 +- ...i.ForeignStructuredUpload.BookletLayout.js | 17 +++ 4 files changed, 111 insertions(+), 40 deletions(-) diff --git a/resources/Resources.php b/resources/Resources.php index fee1e7c412..5ed82dbd81 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -2031,6 +2031,8 @@ return array( 'dependencies' => array( 'oojs-ui', 'mediawiki.api', + 'mediawiki.ForeignApi', + 'mediawiki.Title', ), 'targets' => array( 'desktop', 'mobile' ), ), diff --git a/resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js b/resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js index f1c4f6f21b..24b0e72b9a 100644 --- a/resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js +++ b/resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js @@ -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 = $( '' ) .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' ); } }; diff --git a/resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js b/resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js index 89fcc0b8f0..59f1d507db 100644 --- a/resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js +++ b/resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js @@ -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 ) } ); }; diff --git a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js index 5a7e62eaa3..3e573bb05e 100644 --- a/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js +++ b/resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js @@ -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 } ); -- 2.20.1