Merge "objectcache: Remove broken cas() method from WinCacheBagOStuff"
[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 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 };
74
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 };
90
91 /**
92 * @class mw.widgets.ForeignTitle
93 * @private
94 * @extends mw.Title
95 *
96 * @constructor
97 * @inheritdoc
98 */
99 function ForeignTitle( title, namespace ) {
100 // We only need to handle categories here... but we don't know the target language.
101 // So assume that any namespace-like prefix is the 'Category' namespace...
102 title = title.replace( /^(.+?)_*:_*(.*)$/, 'Category:$2' ); // HACK
103 ForeignTitle.parent.call( this, title, namespace );
104 }
105 OO.inheritClass( ForeignTitle, mw.Title );
106 ForeignTitle.prototype.getNamespacePrefix = function () {
107 // We only need to handle categories here...
108 return 'Category:'; // HACK
109 };
110
111 /**
112 * @class mw.widgets.CategoryCapsuleItemWidget
113 *
114 * Category selector capsule item widget. Extends OO.ui.CapsuleItemWidget with the ability to link
115 * to the given page, and to show its existence status (i.e., whether it is a redlink).
116 *
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 ) );
132
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 } );
143
144 // Initialize
145 this.setMissing( false );
146 this.$label.replaceWith( this.$link );
147 this.setLabelElement( this.$link );
148
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 };
159
160 /* Setup */
161
162 OO.inheritClass( mw.widgets.CategoryCapsuleItemWidget, OO.ui.CapsuleItemWidget );
163
164 /* Static Properties */
165
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 };
176
177 /* Methods */
178
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
189
190 this.missing = missing;
191
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 };
204
205 }( jQuery, mediaWiki ) );