Merge "Allow titles with falsy title text in suggestions"
[lhc/web/wiklou.git] / resources / src / mediawiki.widgets / mw.widgets.CategoryCapsuleItemWidget.js
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 ) {
8
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 }
21
22 /**
23 * Check for existence of pages in the queue.
24 *
25 * @private
26 */
27 PageExistenceCache.prototype.processExistenceCheckQueue = function () {
28 var queue, titles;
29 if ( this.currentRequest ) {
30 // Don't fire off a million requests at the same time
31 this.currentRequest.always( function () {
32 this.currentRequest = null;
33 this.processExistenceCheckQueueDebounced();
34 }.bind( this ) );
35 return;
36 }
37 queue = this.existenceCheckQueue;
38 this.existenceCheckQueue = {};
39 titles = Object.keys( queue ).filter( function ( title ) {
40 if ( this.existenceCache.hasOwnProperty( title ) ) {
41 queue[ title ].resolve( this.existenceCache[ title ] );
42 }
43 return !this.existenceCache.hasOwnProperty( title );
44 }.bind( this ) );
45 if ( !titles.length ) {
46 return;
47 }
48 this.currentRequest = this.api.get( {
49 formatversion: 2,
50 action: 'query',
51 prop: [ 'info' ],
52 titles: titles
53 } ).done( function ( response ) {
54 $.each( response.query.pages, function ( index, page ) {
55 var title = new ForeignTitle( page.title ).getPrefixedText();
56 this.existenceCache[ title ] = !page.missing;
57 queue[ title ].resolve( this.existenceCache[ title ] );
58 }.bind( this ) );
59 }.bind( this ) );
60 };
61
62 /**
63 * Register a request to check whether a page exists.
64 *
65 * @private
66 * @param {mw.Title} title
67 * @return {jQuery.Promise} Promise resolved with true if the page exists or false otherwise
68 */
69 PageExistenceCache.prototype.checkPageExistence = function ( title ) {
70 var key = title.getPrefixedText();
71 if ( !this.existenceCheckQueue[ key ] ) {
72 this.existenceCheckQueue[ key ] = $.Deferred();
73 }
74 this.processExistenceCheckQueueDebounced();
75 return this.existenceCheckQueue[ key ].promise();
76 };
77
78 /**
79 * @class mw.widgets.ForeignTitle
80 * @private
81 * @extends mw.Title
82 *
83 * @constructor
84 * @inheritdoc
85 */
86 function ForeignTitle() {
87 ForeignTitle.parent.apply( this, arguments );
88 }
89 OO.inheritClass( ForeignTitle, mw.Title );
90 ForeignTitle.prototype.getNamespacePrefix = function () {
91 // We only need to handle categories here...
92 return 'Category:'; // HACK
93 };
94
95 /**
96 * @class mw.widgets.CategoryCapsuleItemWidget
97 *
98 * Category selector capsule item widget. Extends OO.ui.CapsuleItemWidget with the ability to link
99 * to the given page, and to show its existence status (i.e., whether it is a redlink).
100 *
101 * @uses mw.Api
102 * @extends OO.ui.CapsuleItemWidget
103 *
104 * @constructor
105 * @param {Object} config Configuration options
106 * @cfg {mw.Title} title Page title to use (required)
107 * @cfg {string} [apiUrl] API URL, if not the current wiki's API
108 */
109 mw.widgets.CategoryCapsuleItemWidget = function MWWCategoryCapsuleItemWidget( config ) {
110 // Parent constructor
111 mw.widgets.CategoryCapsuleItemWidget.parent.call( this, $.extend( {
112 data: config.title.getMainText(),
113 label: config.title.getMainText()
114 }, config ) );
115
116 // Properties
117 this.title = config.title;
118 this.apiUrl = config.apiUrl || '';
119 this.$link = $( '<a>' )
120 .text( this.label )
121 .attr( 'target', '_blank' )
122 .on( 'click', function ( e ) {
123 // CapsuleMultiSelectWidget really wants to prevent you from clicking the link, don't let it
124 e.stopPropagation();
125 } );
126
127 // Initialize
128 this.setMissing( false );
129 this.$label.replaceWith( this.$link );
130 this.setLabelElement( this.$link );
131
132 /*jshint -W024*/
133 if ( !this.constructor.static.pageExistenceCaches[ this.apiUrl ] ) {
134 this.constructor.static.pageExistenceCaches[ this.apiUrl ] =
135 new PageExistenceCache( new mw.ForeignApi( this.apiUrl ) );
136 }
137 this.constructor.static.pageExistenceCaches[ this.apiUrl ]
138 .checkPageExistence( new ForeignTitle( this.title.getPrefixedText() ) )
139 .done( function ( exists ) {
140 this.setMissing( !exists );
141 }.bind( this ) );
142 /*jshint +W024*/
143 };
144
145 /* Setup */
146
147 OO.inheritClass( mw.widgets.CategoryCapsuleItemWidget, OO.ui.CapsuleItemWidget );
148
149 /* Static Properties */
150
151 /*jshint -W024*/
152 /**
153 * Map of API URLs to PageExistenceCache objects.
154 *
155 * @static
156 * @inheritable
157 * @property {Object}
158 */
159 mw.widgets.CategoryCapsuleItemWidget.static.pageExistenceCaches = {
160 '': new PageExistenceCache()
161 };
162 /*jshint +W024*/
163
164 /* Methods */
165
166 /**
167 * Update label link href and CSS classes to reflect page existence status.
168 *
169 * @private
170 * @param {boolean} missing Whether the page is missing (does not exist)
171 */
172 mw.widgets.CategoryCapsuleItemWidget.prototype.setMissing = function ( missing ) {
173 var
174 title = new ForeignTitle( this.title.getPrefixedText() ), // HACK
175 prefix = this.apiUrl.replace( '/w/api.php', '' ); // HACK
176
177 this.missing = missing;
178
179 if ( !missing ) {
180 this.$link
181 .attr( 'href', prefix + title.getUrl() )
182 .attr( 'title', title.getPrefixedText() )
183 .removeClass( 'new' );
184 } else {
185 this.$link
186 .attr( 'href', prefix + title.getUrl( { action: 'edit', redlink: 1 } ) )
187 .attr( 'title', mw.msg( 'red-link-title', title.getPrefixedText() ) )
188 .addClass( 'new' );
189 }
190 };
191
192 }( jQuery, mediaWiki ) );