From bf672c8bc5edfaf573dd465326d5fc8d67dcb8ed Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bartosz=20Dziewo=C5=84ski?= Date: Thu, 24 Sep 2015 20:45:36 +0200 Subject: [PATCH] mw.widgets.CategorySelector: Link to category page and display existence status This contains a silly amount of code to avoid firing separate requests for each CategoryCapsuleItemWidget that is created, which really should be a separate class shared with TitleInputWidget but isn't now. The real widget code here is quite simple. Change-Id: I66e603fd45b8d3fd61618f9115cd031f6fa01e9d --- resources/Resources.php | 1 + .../mw.widgets.CategoryCapsuleItemWidget.js | 139 ++++++++++++++++++ .../mw.widgets.CategorySelector.js | 19 ++- 3 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js diff --git a/resources/Resources.php b/resources/Resources.php index b35ebadc1c..a7212ef3ca 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1897,6 +1897,7 @@ return array( 'resources/src/mediawiki.widgets/mw.widgets.ComplexTitleInputWidget.js', 'resources/src/mediawiki.widgets/mw.widgets.TitleOptionWidget.js', 'resources/src/mediawiki.widgets/mw.widgets.UserInputWidget.js', + 'resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js', 'resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js', ), 'skinStyles' => array( diff --git a/resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js b/resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js new file mode 100644 index 0000000000..f1c4f6f21b --- /dev/null +++ b/resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js @@ -0,0 +1,139 @@ +/*! + * MediaWiki Widgets - CategoryCapsuleItemWidget class. + * + * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ +( function ( $, mw ) { + + /** + * @class mw.widgets.CategoryCapsuleItemWidget + */ + + var processExistenceCheckQueueDebounced, + api = new mw.Api(), + currentRequest = null, + existenceCache = {}, + existenceCheckQueue = {}; + + // The existence checking code really could be refactored into a separate class. + + /** + * @private + */ + function processExistenceCheckQueue() { + var queue, titles; + if ( currentRequest ) { + // Don't fire off a million requests at the same time + currentRequest.always( function () { + currentRequest = null; + processExistenceCheckQueueDebounced(); + } ); + return; + } + queue = existenceCheckQueue; + existenceCheckQueue = {}; + titles = Object.keys( queue ).filter( function ( title ) { + if ( existenceCache.hasOwnProperty( title ) ) { + queue[ title ].resolve( existenceCache[ title ] ); + } + return !existenceCache.hasOwnProperty( title ); + } ); + if ( !titles.length ) { + return; + } + currentRequest = api.get( { + action: 'query', + prop: [ 'info' ], + titles: titles + } ).done( function ( response ) { + 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 ] ); + } + } ); + } + + processExistenceCheckQueueDebounced = OO.ui.debounce( processExistenceCheckQueue ); + + /** + * Register a request to check whether a page exists. + * + * @private + * @param {mw.Title} title + * @return {jQuery.Promise} Promise resolved with true if the page exists or false otherwise + */ + function checkPageExistence( title ) { + var key = title.getPrefixedText(); + if ( !existenceCheckQueue[ key ] ) { + existenceCheckQueue[ key ] = $.Deferred(); + } + processExistenceCheckQueueDebounced(); + return existenceCheckQueue[ key ].promise(); + } + + /** + * 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). + * + * @uses mw.Api + * @extends OO.ui.CapsuleItemWidget + * + * @constructor + * @param {Object} config Configuration options + * @cfg {mw.Title} title Page title to use (required) + */ + mw.widgets.CategoryCapsuleItemWidget = function MWWCategoryCapsuleItemWidget( config ) { + // Parent constructor + mw.widgets.CategoryCapsuleItemWidget.parent.call( this, $.extend( { + data: config.title.getMainText(), + label: config.title.getMainText() + }, config ) ); + + // Properties + this.title = config.title; + this.$link = $( '' ) + .text( this.label ) + .attr( 'target', '_blank' ) + .on( 'click', function ( e ) { + // CapsuleMultiSelectWidget really wants to prevent you from clicking the link, don't let it + e.stopPropagation(); + } ); + + // Initialize + this.setMissing( false ); + this.$label.replaceWith( this.$link ); + this.setLabelElement( this.$link ); + checkPageExistence( this.title ).done( function ( exists ) { + this.setMissing( !exists ); + }.bind( this ) ); + }; + + /* Setup */ + + OO.inheritClass( mw.widgets.CategoryCapsuleItemWidget, OO.ui.CapsuleItemWidget ); + + /* Methods */ + + /** + * Update label link href and CSS classes to reflect page existence status. + * + * @private + * @param {boolean} missing Whether the page is missing (does not exist) + */ + mw.widgets.CategoryCapsuleItemWidget.prototype.setMissing = function ( missing ) { + if ( !missing ) { + this.$link + .attr( 'href', this.title.getUrl() ) + .removeClass( 'new' ); + } else { + this.$link + .attr( 'href', this.title.getUrl( { action: 'edit', redlink: 1 } ) ) + .addClass( 'new' ); + } + }; + +}( jQuery, mediaWiki ) ); diff --git a/resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js b/resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js index 995561ec6f..e40caaa856 100644 --- a/resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js +++ b/resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js @@ -5,7 +5,8 @@ * @license The MIT License (MIT); see LICENSE.txt */ ( function ( $, mw ) { - var CSP; + var CSP, + NS_CATEGORY = mw.config.get( 'wgNamespaceIds' ).category; /** * Category selector widget. Displays an OO.ui.CapsuleMultiSelectWidget @@ -57,7 +58,6 @@ this.$input.on( 'change input cut paste', OO.ui.debounce( this.updateMenuItems.bind( this ), 100 ) ); // Initialize - this.catNsId = mw.config.get( 'wgNamespaceIds' ).category; this.api = new mw.Api(); } @@ -137,7 +137,7 @@ // Get titles categoryNames = categories.map( function ( name ) { - return mw.Title.newFromText( name, this.catNsId ).getMainText(); + return mw.Title.newFromText( name, NS_CATEGORY ).getMainText(); } ); deferred.resolve( categoryNames ); @@ -147,6 +147,15 @@ return deferred.promise(); }; + /** + * @inheritdoc + */ + CSP.createItemWidget = function ( data ) { + return new mw.widgets.CategoryCapsuleItemWidget( { + title: mw.Title.newFromText( data, NS_CATEGORY ) + } ); + }; + /** * Validates the values in `this.searchType`. * @@ -211,7 +220,7 @@ case CategorySelector.SearchType.OpenSearch: this.api.get( { action: 'opensearch', - namespace: this.catNsId, + namespace: NS_CATEGORY, limit: this.limit, search: input } ).done( function ( res ) { @@ -224,7 +233,7 @@ this.api.get( { action: 'query', list: 'allpages', - apnamespace: this.catNsId, + apnamespace: NS_CATEGORY, aplimit: this.limit, apfrom: input, apprefix: input -- 2.20.1