87410a0d47a126be344bdf2761fa973ced6df000
3 // * The edit summary should contain the added/removed category name too.
4 // Something like: "Category:Foo added. Reason"
5 // Requirement: Be able to get msg with lang option.
6 // * Handle uneditable cats. Needs serverside changes!
7 // * Add Hooks for change, delete, add
8 // * Add Hooks for soft redirect
9 // * Handle normal redirects
10 // * api.php / api.php5
11 // * Simple / MultiEditMode
14 var catLinkWrapper
= '<li/>'
15 var $container
= $( '.catlinks' );
17 var categoryLinkSelector
= '#mw-normal-catlinks li a';
20 var _catElements
= {};
21 var _otherElements
= {};
23 var namespaceIds
= mw
.config
.get( 'wgNamespaceIds' )
24 var categoryNamespaceId
= namespaceIds
['category'];
25 var categoryNamespace
= mw
.config
.get( 'wgFormattedNamespaces' )[categoryNamespaceId
];
26 var wgScriptPath
= mw
.config
.get( 'wgScriptPath' );
28 function _fetchSuggestions ( query
) {
31 // ignore bad characters, they will be stripped out
32 var catName
= _stripIllegals( $( this ).val() );
33 var request
= $.ajax( {
34 url
: wgScriptPath
+ '/api.php',
38 'apnamespace': categoryNamespaceId
,
43 success: function( data
) {
44 // Process data.query.allpages into an array of titles
45 var pages
= data
.query
.allpages
;
48 $.each( pages
, function( i
, page
) {
49 var title
= page
.title
.split( ':', 2 )[1];
50 titleArr
.push( title
);
53 $( _this
).suggestions( 'suggestions', titleArr
);
60 function _stripIllegals( cat
) {
61 return cat
.replace( /[\x00-\x1f\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f]+/g, '' );
64 function _insertCatDOM( cat
, isHidden
) {
65 // User can implicitely state a sort key.
66 // Remove before display
67 cat
= cat
.replace(/\|.*/, '');
69 // strip out bad characters
70 cat
= _stripIllegals ( cat
);
72 if ( $.isEmpty( cat
) || _containsCat( cat
) ) {
76 var $catLinkWrapper
= $( catLinkWrapper
);
77 var $anchor
= $( '<a/>' ).append( cat
);
78 $catLinkWrapper
.append( $anchor
);
79 $anchor
.attr( { target
: "_blank", href
: _catLink( cat
) } );
81 $container
.find( '#mw-hidden-catlinks ul' ).append( $catLinkWrapper
);
83 $container
.find( '#mw-normal-catlinks ul' ).append( $catLinkWrapper
);
85 _createCatButtons( $anchor
.get(0) );
88 function _makeSuggestionBox( prefill
, callback
, buttonVal
) {
89 // Create add category prompt
90 var promptContainer
= $( '<div class="mw-addcategory-prompt"/>' );
91 var promptTextbox
= $( '<input type="text" size="45" class="mw-addcategory-input"/>' );
92 if ( prefill
!== '' ) {
93 promptTextbox
.val( prefill
);
95 var addButton
= $( '<input type="button" class="mw-addcategory-button"/>' );
96 addButton
.val( buttonVal
);
98 addButton
.click( callback
);
100 promptTextbox
.suggestions( {
101 'fetch':_fetchSuggestions
,
102 'cancel': function() {
104 // XMLHttpRequest.abort is unimplemented in IE6, also returns nonstandard value of "unknown" for typeof
105 if ( req
&& ( typeof req
.abort
!== 'unknown' ) && ( typeof req
.abort
!== 'undefined' ) && req
.abort
) {
111 promptTextbox
.suggestions();
113 promptContainer
.append( promptTextbox
);
114 promptContainer
.append( addButton
);
116 return promptContainer
;
119 // Create a valid link to the category.
120 function _catLink ( cat
) {
122 return mw
.util
.wikiGetlink( categoryNamespace
+ ':' + $.ucFirst( cat
) );
125 function _getCats() {
126 return $container
.find( categoryLinkSelector
).map( function() { return $.trim( $( this ).text() ); } );
129 function _containsCat( cat
) {
131 return _getCats().filter( function() { return $.ucFirst(this) == $.ucFirst(cat
); } ).length
!== 0;
134 function _confirmEdit ( page
, fn
, actionSummary
, doneFn
) {
136 // Produce a confirmation dialog
137 var dialog
= $( '<div/>' );
139 dialog
.addClass( 'mw-ajax-confirm-dialog' );
140 dialog
.attr( 'title', mw
.msg( 'ajax-confirm-title' ) );
143 var confirmIntro
= $( '<p/>' );
144 confirmIntro
.text( mw
.msg( 'ajax-confirm-prompt' ) );
145 dialog
.append( confirmIntro
);
147 // Summary of the action to be taken
148 var summaryHolder
= $( '<p/>' );
149 var summaryLabel
= $( '<strong/>' );
150 summaryLabel
.text( mw
.msg( 'ajax-confirm-actionsummary' ) + " " );
151 summaryHolder
.text( actionSummary
);
152 summaryHolder
.prepend( summaryLabel
);
153 dialog
.append( summaryHolder
);
156 var reasonBox
= $( '<input type="text" size="45" />' );
157 reasonBox
.addClass( 'mw-ajax-confirm-reason' );
158 dialog
.append( reasonBox
);
161 var submitButton
= $( '<input type="button"/>' );
162 submitButton
.val( mw
.msg( 'ajax-confirm-save' ) );
164 var submitFunction = function() {
165 _addProgressIndicator( dialog
);
172 dialog
.dialog( 'close' );
173 _removeProgressIndicator( dialog
);
179 buttons
[mw
.msg( 'ajax-confirm-save' )] = submitFunction
;
180 var dialogOptions
= {
186 $( '#catlinks' ).prepend( dialog
);
187 dialog
.dialog( dialogOptions
);
190 function _doEdit ( page
, fn
, summary
, doneFn
) {
191 // Get an edit token for the page.
194 'prop':'info|revisions',
197 'rvprop':'content|timestamp',
201 $.get( wgScriptPath
+ '/api.php', getTokenVars
,
203 var infos
= reply
.query
.pages
;
206 function( pageid
, data
) {
207 var token
= data
.edittoken
;
208 var timestamp
= data
.revisions
[0].timestamp
;
209 var oldText
= data
.revisions
[0]['*'];
211 var newText
= fn( oldText
);
213 if ( newText
=== false ) return;
221 'basetimestamp':timestamp
,
225 $.post( wgScriptPath
+ '/api.php', postEditVars
, doneFn
, 'json' );
232 function _addProgressIndicator ( elem
) {
233 var indicator
= $( '<div/>' );
235 indicator
.addClass( 'mw-ajax-loader' );
237 elem
.append( indicator
);
240 function _removeProgressIndicator ( elem
) {
241 elem
.find( '.mw-ajax-loader' ).remove();
244 function _makeCaseInsensitiv( string
) {
246 for (var i
=0; i
< string
.length
; i
++) {
247 newString
+= '[' + string
[i
].toUpperCase() + string
[i
].toLowerCase() + ']';
251 function _buildRegex ( category
) {
252 // Build a regex that matches legal invocations of that category.
253 var categoryNSFragment
= '';
254 $.each( namespaceIds
, function( name
, id
) {
256 // The parser accepts stuff like cATegORy,
257 // we need to do the same
258 categoryNSFragment
+= '|' + _makeCaseInsensitiv ( $.escapeRE(name
) );
261 categoryNSFragment
= categoryNSFragment
.substr( 1 ); // Remove leading |
264 var titleFragment
= $.escapeRE(category
);
266 firstChar
= category
.charAt( 0 );
267 firstChar
= '[' + firstChar
.toUpperCase() + firstChar
.toLowerCase() + ']';
268 titleFragment
= firstChar
+ category
.substr( 1 );
269 var categoryRegex
= '\\[\\[(' + categoryNSFragment
+ '):' + titleFragment
+ '(\\|[^\\]]*)?\\]\\]';
271 return new RegExp( categoryRegex
, 'g' );
274 function _handleEditLink ( e
) {
276 var $this = $( this );
277 var $link
= $this.parent().find( 'a:not(.icon)' );
278 var category
= $link
.text();
280 var $input
= _makeSuggestionBox( category
, _handleCategoryEdit
, mw
.msg( 'ajax-confirm-save' ) );
281 $link
.after( $input
).hide();
282 _catElements
[category
].editButton
.hide();
283 _catElements
[category
].deleteButton
.unbind('click').click( function() {
286 _catElements
[category
].editButton
.show();
287 $( this ).unbind('click').click( _handleDeleteLink
);
291 function _handleAddLink ( e
) {
294 $container
.find( '#mw-normal-catlinks>.mw-addcategory-prompt' ).toggle();
297 function _handleDeleteLink ( e
) {
300 var $this = $( this );
301 var $link
= $this.parent().find( 'a:not(.icon)' );
302 var category
= $link
.text();
304 categoryRegex
= _buildRegex( category
);
306 var summary
= mw
.msg( 'ajax-remove-category-summary', category
);
309 mw
.config
.get('wgPageName'),
310 function( oldText
) {
311 //TODO Cleanup whitespace safely?
312 var newText
= oldText
.replace( categoryRegex
, '' );
314 if ( newText
== oldText
) {
315 var error
= mw
.msg( 'ajax-remove-category-error' );
317 _removeProgressIndicator( $( '.mw-ajax-confirm-dialog' ) );
318 $( '.mw-ajax-confirm-dialog' ).dialog( 'close' );
326 $this.parent().remove();
331 function _handleCategoryAdd ( e
) {
332 // Grab category text
333 var category
= $( this ).parent().find( '.mw-addcategory-input' ).val();
334 category
= $.ucFirst( category
);
336 if ( _containsCat(category
) ) {
337 // TODO add info alert
340 var appendText
= "\n[[" + categoryNamespace
+ ":" + category
+ "]]\n";
341 var summary
= mw
.msg( 'ajax-add-category-summary', category
);
344 mw
.config
.get( 'wgPageName' ),
345 function( oldText
) { return oldText
+ appendText
},
348 _insertCatDOM( category
, false );
353 function _handleCategoryEdit ( e
) {
356 // Grab category text
357 var categoryNew
= $( this ).parent().find( '.mw-addcategory-input' ).val();
358 categoryNew
= $.ucFirst( categoryNew
);
360 var $this = $( this );
361 var $link
= $this.parent().parent().find( 'a:not(.icon)' );
362 var category
= $link
.text();
364 // User didn't change anything. Just close the box
365 if ( category
== categoryNew
) {
366 $this.parent().remove();
370 categoryRegex
= _buildRegex( category
);
372 var summary
= mw
.msg( 'ajax-edit-category-summary', category
, categoryNew
);
375 mw
.config
.get( 'wgPageName' ),
376 function( oldText
) {
377 var matches
= oldText
.match( categoryRegex
);
379 //Old cat wasn't found, likely to be transcluded
380 if ( !$.isArray( matches
) ) {
381 var error
= mw
.msg( 'ajax-edit-category-error' );
383 _removeProgressIndicator( $( '.mw-ajax-confirm-dialog' ) );
384 $( '.mw-ajax-confirm-dialog' ).dialog( 'close' );
387 var sortkey
= matches
[0].replace( categoryRegex
, '$2' );
388 var newCategoryString
= "[[" + categoryNamespace
+ ":" + categoryNew
+ sortkey
+ ']]';
390 if (matches
.length
> 1) {
391 // The category is duplicated.
392 // Remove all but one match
393 for (var i
= 1; i
< matches
.length
; i
++) {
394 oldText
= oldText
.replace( matches
[i
], '');
397 var newText
= oldText
.replace( categoryRegex
, newCategoryString
);
403 // Remove input box & button
404 $this.parent().remove();
406 // Update link text and href
407 $link
.show().text( categoryNew
).attr( 'href', _catLink( categoryNew
) );
411 function _showError ( str
) {
412 var dialog
= $( '<div/>' );
415 $( '#bodyContent' ).append( dialog
);
418 buttons
[mw
.msg( 'ajax-error-dismiss' )] = function( e
) {
419 dialog
.dialog( 'close' );
421 var dialogOptions
= {
424 'title' : mw
.msg( 'ajax-error-title' )
427 dialog
.dialog( dialogOptions
);
430 function _createButton ( icon
, title
, category
, text
){
431 var button
= $( '<a>' ).addClass( category
|| '' )
432 .attr('title', title
);
435 var icon
= $( '<a>' ).addClass( 'icon ' + icon
);
436 button
.addClass( 'icon-parent' ).append( icon
).append( text
);
438 button
.addClass( 'icon ' + icon
);
442 function _createCatButtons ( element
) {
443 // Create remove & edit buttons
444 var deleteButton
= _createButton('icon-close', mw
.msg( 'ajax-remove-category' ) );
445 var editButton
= _createButton('icon-edit', mw
.msg( 'ajax-edit-category' ) );
448 var saveButton
= _createButton('icon-tick', mw
.msg( 'ajax-confirm-save' ) ).hide();
450 deleteButton
.click( _handleDeleteLink
);
451 editButton
.click( _handleEditLink
);
453 $( element
).after( deleteButton
).after( editButton
);
455 //Save references to all links and buttons
456 _catElements
[$( element
).text()] = {
458 parent
: $( element
).parent(),
459 saveButton
: saveButton
,
460 deleteButton
: deleteButton
,
461 editButton
: editButton
465 // Could be set by gadgets like HotCat etc.
466 if ( mw
.config
.get('disableAJAXCategories') ) {
469 // Only do it for articles.
470 if ( !mw
.config
.get( 'wgIsArticle' ) ) return;
472 var clElement
= $( '#mw-normal-catlinks' );
474 // Unhide hidden category holders.
475 $('#mw-hidden-catlinks').show();
478 // Create [Add Category] link
479 var addLink
= _createButton('icon-add',
480 mw
.msg( 'ajax-add-category' ),
481 'mw-ajax-addcategory',
482 mw
.msg( 'ajax-add-category' )
484 addLink
.click( _handleAddLink
);
485 clElement
.append( addLink
);
487 // Create add category prompt
488 var promptContainer
= _makeSuggestionBox( '', _handleCategoryAdd
, mw
.msg( 'ajax-add-category-submit' ) );
489 promptContainer
.hide();
491 // Create edit & delete link for each category.
492 $( '#catlinks li a' ).each( function( e
) {
493 _createCatButtons( this );
496 clElement
.append( promptContainer
);
498 function _teardown() {
504 add : function( obj
) {
505 this.list
.push( obj
);
508 var task
= this.list
.shift();
510 this.executed
.push( task
);
513 $(document
).ready( function() {_setup()});
515 } )( jQuery
, mediaWiki
);