Merge "Added release notes for 'ContentHandler::runLegacyHooks' removal"
[mediawiki.git] / resources / src / mediawiki.widgets / mw.widgets.CategoryCapsuleItemWidget.js
blob488d9e099f5e415914f49cb9d5f2581ca64fa084
1 /*!
2  * MediaWiki Widgets - CategoryCapsuleItemWidget class.
3  *
4  * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
5  * @license The MIT License (MIT); see LICENSE.txt
6  */
7 ( function ( $, mw ) {
9         /**
10          * @class mw.widgets.PageExistenceCache
11          * @private
12          * @param {mw.Api} [api]
13          */
14         function PageExistenceCache( api ) {
15                 this.api = api || new mw.Api();
16                 this.processExistenceCheckQueueDebounced = OO.ui.debounce( this.processExistenceCheckQueue );
17                 this.currentRequest = null;
18                 this.existenceCache = {};
19                 this.existenceCheckQueue = {};
20         }
22         /**
23          * Check for existence of pages in the queue.
24          *
25          * @private
26          */
27         PageExistenceCache.prototype.processExistenceCheckQueue = function () {
28                 var queue, titles,
29                         cache = this;
30                 if ( this.currentRequest ) {
31                         // Don't fire off a million requests at the same time
32                         this.currentRequest.always( function () {
33                                 cache.currentRequest = null;
34                                 cache.processExistenceCheckQueueDebounced();
35                         } );
36                         return;
37                 }
38                 queue = this.existenceCheckQueue;
39                 this.existenceCheckQueue = {};
40                 titles = Object.keys( queue ).filter( function ( title ) {
41                         if ( cache.existenceCache.hasOwnProperty( title ) ) {
42                                 queue[ title ].resolve( cache.existenceCache[ title ] );
43                         }
44                         return !cache.existenceCache.hasOwnProperty( title );
45                 } );
46                 if ( !titles.length ) {
47                         return;
48                 }
49                 this.currentRequest = this.api.get( {
50                         formatversion: 2,
51                         action: 'query',
52                         prop: [ 'info' ],
53                         titles: titles
54                 } ).done( function ( response ) {
55                         var
56                                 normalized = {},
57                                 pages = {};
58                         $.each( response.query.normalized || [], function ( index, data ) {
59                                 normalized[ data.fromencoded ? decodeURIComponent( data.from ) : data.from ] = data.to;
60                         } );
61                         $.each( response.query.pages, function ( index, page ) {
62                                 pages[ page.title ] = !page.missing;
63                         } );
64                         $.each( titles, function ( index, title ) {
65                                 var normalizedTitle = title;
66                                 while ( normalized[ normalizedTitle ] ) {
67                                         normalizedTitle = normalized[ normalizedTitle ];
68                                 }
69                                 cache.existenceCache[ title ] = pages[ normalizedTitle ];
70                                 queue[ title ].resolve( cache.existenceCache[ title ] );
71                         } );
72                 } );
73         };
75         /**
76          * Register a request to check whether a page exists.
77          *
78          * @private
79          * @param {mw.Title} title
80          * @return {jQuery.Promise} Promise resolved with true if the page exists or false otherwise
81          */
82         PageExistenceCache.prototype.checkPageExistence = function ( title ) {
83                 var key = title.getPrefixedText();
84                 if ( !this.existenceCheckQueue[ key ] ) {
85                         this.existenceCheckQueue[ key ] = $.Deferred();
86                 }
87                 this.processExistenceCheckQueueDebounced();
88                 return this.existenceCheckQueue[ key ].promise();
89         };
91         /**
92          * @class mw.widgets.ForeignTitle
93          * @private
94          * @extends mw.Title
95          *
96          * @constructor
97          * @param {string} title
98          * @param {number} [namespace]
99          */
100         function ForeignTitle( title, namespace ) {
101                 // We only need to handle categories here... but we don't know the target language.
102                 // So assume that any namespace-like prefix is the 'Category' namespace...
103                 title = title.replace( /^(.+?)_*:_*(.*)$/, 'Category:$2' ); // HACK
104                 ForeignTitle.parent.call( this, title, namespace );
105         }
106         OO.inheritClass( ForeignTitle, mw.Title );
107         ForeignTitle.prototype.getNamespacePrefix = function () {
108                 // We only need to handle categories here...
109                 return 'Category:'; // HACK
110         };
112         /**
113          * Category selector capsule item widget. Extends OO.ui.CapsuleItemWidget with the ability to link
114          * to the given page, and to show its existence status (i.e., whether it is a redlink).
115          *
116          * @class mw.widgets.CategoryCapsuleItemWidget
117          * @uses mw.Api
118          * @extends OO.ui.CapsuleItemWidget
119          *
120          * @constructor
121          * @param {Object} config Configuration options
122          * @cfg {mw.Title} title Page title to use (required)
123          * @cfg {string} [apiUrl] API URL, if not the current wiki's API
124          */
125         mw.widgets.CategoryCapsuleItemWidget = function MWWCategoryCapsuleItemWidget( config ) {
126                 var widget = this;
127                 // Parent constructor
128                 mw.widgets.CategoryCapsuleItemWidget.parent.call( this, $.extend( {
129                         data: config.title.getMainText(),
130                         label: config.title.getMainText()
131                 }, config ) );
133                 // Properties
134                 this.title = config.title;
135                 this.apiUrl = config.apiUrl || '';
136                 this.$link = $( '<a>' )
137                         .text( this.label )
138                         .attr( 'target', '_blank' )
139                         .on( 'click', function ( e ) {
140                                 // CapsuleMultiselectWidget really wants to prevent you from clicking the link, don't let it
141                                 e.stopPropagation();
142                         } );
144                 // Initialize
145                 this.setMissing( false );
146                 this.$label.replaceWith( this.$link );
147                 this.setLabelElement( this.$link );
149                 if ( !this.constructor.static.pageExistenceCaches[ this.apiUrl ] ) {
150                         this.constructor.static.pageExistenceCaches[ this.apiUrl ] =
151                                 new PageExistenceCache( new mw.ForeignApi( this.apiUrl ) );
152                 }
153                 this.constructor.static.pageExistenceCaches[ this.apiUrl ]
154                         .checkPageExistence( new ForeignTitle( this.title.getPrefixedText() ) )
155                         .done( function ( exists ) {
156                                 widget.setMissing( !exists );
157                         } );
158         };
160         /* Setup */
162         OO.inheritClass( mw.widgets.CategoryCapsuleItemWidget, OO.ui.CapsuleItemWidget );
164         /* Static Properties */
166         /**
167          * Map of API URLs to PageExistenceCache objects.
168          *
169          * @static
170          * @inheritable
171          * @property {Object}
172          */
173         mw.widgets.CategoryCapsuleItemWidget.static.pageExistenceCaches = {
174                 '': new PageExistenceCache()
175         };
177         /* Methods */
179         /**
180          * Update label link href and CSS classes to reflect page existence status.
181          *
182          * @private
183          * @param {boolean} missing Whether the page is missing (does not exist)
184          */
185         mw.widgets.CategoryCapsuleItemWidget.prototype.setMissing = function ( missing ) {
186                 var
187                         title = new ForeignTitle( this.title.getPrefixedText() ), // HACK
188                         prefix = this.apiUrl.replace( '/w/api.php', '' ); // HACK
190                 this.missing = missing;
192                 if ( !missing ) {
193                         this.$link
194                                 .attr( 'href', prefix + title.getUrl() )
195                                 .attr( 'title', title.getPrefixedText() )
196                                 .removeClass( 'new' );
197                 } else {
198                         this.$link
199                                 .attr( 'href', prefix + title.getUrl( { action: 'edit', redlink: 1 } ) )
200                                 .attr( 'title', mw.msg( 'red-link-title', title.getPrefixedText() ) )
201                                 .addClass( 'new' );
202                 }
203         };
205 }( jQuery, mediaWiki ) );