האיגוד הישראלי לרפואת משפחה

הבדלים בין גרסאות בדף "מדיה ויקי:Gadget-Cat-a-lot.js"

מתוך ויקירפואה

מ (גרסה אחת: יבוא מויקיפדיה. http://he.wikipedia.org/w/index.php?title=%D7%9E%D7%93%D7%99%D7%94_%D7%95%D7%99%D7%A7%D7%99:Gadget-Cat-a-lot.js&action=history)
 
(updated version)
 
(גרסת ביניים אחת של אותו משתמש אינה מוצגת)
שורה 1: שורה 1:
// <source lang="javascript">
+
/**
//
+
* Cat-a-lot
// Cat-A-Lot
+
* Changes category of multiple files
// Changes category of multiple files (or pages)
+
*
//
+
* Originally by Magnus Manske
// Originally by Magnus Manske
+
* RegExes by Ilmari Karonen
// RegExes by Ilmari Karonen
+
* Completely rewritten by DieBuche
// Completely rewritten by DieBuche
+
*
//
+
* Requires [[MediaWiki:Gadget-SettingsManager.js]] and [[MediaWiki:Gadget-SettingsUI.js]] (properly registered) for per-user-settings
// Modified to work on regular pages instead of files, + Hebrew translation: קיפודנחש
+
*
//
+
* READ THIS PAGE IF YOU WANT TO TRANSLATE OR USE THIS ON ANOTHER SITE:
// READ THIS PAGE IF YOU WANT TO TRANSLATE OR USE THIS ON ANOTHER SITE:
+
* http://commons.wikimedia.org/wiki/MediaWiki:Gadget-Cat-a-lot.js/translating
// http://commons.wikimedia.org/wiki/MediaWiki_talk:Gadget-Cat-a-lot.js/translating
+
* <nowiki>
//
+
*/
var catALot = {
+
 
apiUrl: wgScriptPath + "/api.php",
+
/* global jQuery, mediaWiki, importStylesheet */
 +
/* eslint one-var: 0, vars-on-top: 0, no-underscore-dangle:0 */ // extends: wikimedia
 +
/* jshint unused:true, forin:false, smarttabs:true, loopfunc:true, browser:true */
 +
 
 +
( function( $, mw ) {
 +
'use strict';
 +
 
 +
var NS_CAT = 14,
 +
formattedNS = mw.config.get( 'wgFormattedNamespaces' ),
 +
nsIDs = mw.config.get( 'wgNamespaceIds' );
 +
 
 +
var msgs = {
 +
// Preferences
 +
// new: added 2012-09-19. Please translate.
 +
// Use user language for i18n
 +
'cat-a-lot-watchlistpref': 'Watchlist preference concerning files edited with Cat-a-lot',
 +
'cat-a-lot-watch_pref': 'According to your general preferences',
 +
'cat-a-lot-watch_nochange': 'Do not change watchstatus',
 +
'cat-a-lot-watch_watch': 'Watch pages edited with Cat-a-lot',
 +
'cat-a-lot-watch_unwatch': 'Remove pages while editing with Cat-a-lot from your watchlist',
 +
'cat-a-lot-minorpref': 'Mark edits as minor (if you generally mark your edits as minor, this won\'t change anything)',
 +
'cat-a-lot-editpagespref': 'Allow categorising pages (including categories) that are not files',
 +
'cat-a-lot-docleanuppref': 'Remove {{Check categories}} and other minor cleanup',
 +
'cat-a-lot-subcatcountpref': 'Sub-categories to show at most',
 +
'cat-a-lot-config-settings': 'Preferences',
 +
 
 +
// Progress
 +
'cat-a-lot-loading': 'Loading...',
 +
'cat-a-lot-editing': 'Editing page',
 +
'cat-a-lot-of': 'of ',
 +
'cat-a-lot-skipped-already': 'The following {{PLURAL:$1|1=page was|$1 pages were}} skipped, because the page was already in the category:',
 +
'cat-a-lot-skipped-not-found': 'The following {{PLURAL:$1|1=page was|$1 pages were}} skipped, because the old category could not be found:',
 +
'cat-a-lot-skipped-server': 'The following {{PLURAL:$1|1=page|$1 pages}} couldn\'t be changed, since there were problems connecting to the server:',
 +
'cat-a-lot-all-done': 'All pages are processed.',
 +
'cat-a-lot-done': 'Done!',
 +
'cat-a-lot-added-cat': 'Added category $1',
 +
'cat-a-lot-copied-cat': 'Copied to category $1',
 +
'cat-a-lot-moved-cat': 'Moved to category $1',
 +
'cat-a-lot-removed-cat': 'Removed from category $1',
 +
'cat-a-lot-return-to-page': 'Return to page',
 +
'cat-a-lot-cat-not-found': 'Category not found.',
 +
 
 +
// as in 17 files selected
 +
'cat-a-lot-files-selected': '{{PLURAL:$1|1=One file|$1 files}} selected.',
 +
 
 +
// Actions
 +
'cat-a-lot-copy': 'Copy',
 +
'cat-a-lot-move': 'Move',
 +
'cat-a-lot-add': 'Add',
 +
'cat-a-lot-remove-from-cat': 'Remove from this category',
 +
'cat-a-lot-enter-name': 'Enter category name',
 +
'cat-a-lot-select': 'Select',
 +
'cat-a-lot-all': 'all',
 +
'cat-a-lot-none': 'none',
 +
'cat-a-lot-none-selected': 'No files selected!',
 +
 
 +
// Summaries:
 +
'cat-a-lot-pref-save-summary': '[[c:Help:Gadget-Cat-a-lot|Cat-a-lot]]: updating user preferences',
 +
'cat-a-lot-summary-add': '[[c:Help:Cat-a-lot|Cat-a-lot]]: Adding [[Category:$1]]',
 +
'cat-a-lot-summary-copy': '[[c:Help:Cat-a-lot|Cat-a-lot]]: Copying from [[Category:$1]] to [[Category:$2]]',
 +
'cat-a-lot-summary-move': '[[c:Help:Cat-a-lot|Cat-a-lot]]: Moving from [[Category:$1]] to [[Category:$2]]',
 +
'cat-a-lot-summary-remove': '[[c:Help:Cat-a-lot|Cat-a-lot]]: Removing from [[Category:$1]]'
 +
};
 +
mw.messages.set( msgs );
 +
 
 +
function msg( /* params */ ) {
 +
var args = Array.prototype.slice.call( arguments, 0 );
 +
args[0] = 'cat-a-lot-' + args[0];
 +
return mw.message.apply( mw.message, args ).parse();
 +
}
 +
function msgPlain( key ) {
 +
return mw.message( 'cat-a-lot-' + key ).plain();
 +
}
 +
 
 +
// There is only one cat-a-lot on one page
 +
var $body, $container, $dataContainer, $searchInputContainer, $searchInput, $resultList, $markCounter,
 +
$selections, $selectAll, $selectNone, $settingsWrapper, $settingsLink, $head, $link;
 +
var commons_url = 'https://commons.wikimedia.org/w/index.php';
 +
 
 +
var catALot = window.catALot = {
 +
apiUrl: mw.util.wikiScript( 'api' ),
 +
origin: false,
 
searchmode: false,
 
searchmode: false,
version: 2.18,
+
version: 3.7,
 
setHeight: 450,
 
setHeight: 450,
init: function () {
+
settings: {},
$("body")
+
init: function() {
.append('<div id="cat_a_lot">' + '<div id="cat_a_lot_data"><div>' + '<input type="text" id="cat_a_lot_searchcatname" placeholder="' + this.i18n.enterName + '"/>'
+
this._initSettings();
+ '</div><div id="cat_a_lot_category_list"style="white-space:nowrap;"></div>' + '<div id="cat_a_lot_mark_counter"> </div>' + '<div id="cat_a_lot_selections">' + this.i18n.select
 
+ ' <a id="cat_a_lot_select_all">' + this.i18n.all + '</a> / ' + '<a id="cat_a_lot_select_none">' + this.i18n.none + '</a>'
 
+ '</div></div><div id="cat_a_lot_head">' + '<a id="cat_a_lot_toggle">Cat-a-lot</a></div></div>');
 
  
if (!this.searchmode) $('#cat_a_lot_selections').append('<br><a id="cat_a_lot_remove"><b>' + this.i18n.removeFromCat + '</b></a>');
+
$body = $( document.body );
$('#cat_a_lot_remove').click(function () {
+
$container = $( '<div>' )
catALot.remove();
+
.attr( 'id', 'cat_a_lot' )
});
+
.appendTo( $body );
$('#cat_a_lot_select_all').click(function () {
+
$dataContainer = $( '<div>' )
catALot.toggleAll(true);
+
.attr( 'id', 'cat_a_lot_data' )
});
+
.appendTo( $container );
$('#cat_a_lot_select_none').click(function () {
+
$searchInputContainer = $( '<div>' )
catALot.toggleAll(false);
+
.appendTo( $dataContainer );
});
+
$searchInput = $( '<input>' )
$('#cat_a_lot_toggle').click(function () {
+
.attr({
$(this).toggleClass('cat_a_lot_enabled');
+
id: 'cat_a_lot_searchcatname',
catALot.run();
+
placeholder: msgPlain( 'enter-name' ),
});
+
type: 'text'
$('#cat_a_lot_searchcatname')
 
.keypress(function (e) {
 
if (e.which == 13)
 
catALot.updateCats($(this).val());
 
 
})
 
})
.autocomplete({
+
.appendTo( $searchInputContainer );
source: function(request, response) {
+
$resultList = $( '<div>' )
catALot.doAPICall({action:'opensearch', search: request.term, namespace: 14},
+
.attr( 'id', 'cat_a_lot_category_list' )
function(data){
+
.appendTo( $dataContainer );
if(data[1])
+
$markCounter = $( '<div>' )
response($(data[1]).map(function(index,item){return item.replace(/.*:/, '');}));
+
.attr( 'id', 'cat_a_lot_mark_counter' )
 +
.appendTo( $dataContainer );
 +
$selections = $( '<div>' )
 +
.attr( 'id', 'cat_a_lot_selections' )
 +
.text( msgPlain( 'select' ) )
 +
.appendTo( $dataContainer );
 +
$selectAll = $( '<a>' )
 +
.attr( 'id', 'cat_a_lot_select_all' )
 +
.text( msgPlain( 'all' ) )
 +
.appendTo( $selections.append( ' ' ) );
 +
$selectNone = $( '<a>' )
 +
.attr( 'id', 'cat_a_lot_select_none' )
 +
.text( msgPlain( 'none' ) )
 +
.appendTo( $selections.append( ' • ' ) );
 +
$settingsWrapper = $( '<div>' )
 +
.attr( 'id', 'cat_a_lot_settings' )
 +
.appendTo( $dataContainer );
 +
$settingsLink = $( '<a>' )
 +
.attr( 'id', 'cat_a_lot_config_settings' )
 +
.text( msgPlain( 'config-settings' ) )
 +
.appendTo( $settingsWrapper );
 +
$head = $( '<div>' )
 +
.attr( 'id', 'cat_a_lot_head' )
 +
.appendTo( $container );
 +
$link = $( '<a>' )
 +
.attr( 'id', 'cat_a_lot_toggle' )
 +
.text( 'Cat-a-lot' )
 +
.appendTo( $head );
 +
$settingsWrapper.append( $( '<a>' )
 +
.attr( {
 +
href: commons_url + '?title=Special:MyLanguage/Help:Gadget-Cat-a-lot',
 +
target: '_blank',
 +
style: 'float:right',
 +
title: $( '#n-help' ).attr( 'title' )
 +
} ).text( '?' ) );
 +
 
 +
if ( this.origin ) {
 +
$( '<a>' )
 +
.attr( 'id', 'cat_a_lot_remove' )
 +
.html( msg( 'remove-from-cat' ) )
 +
.appendTo( $selections )
 +
.click( function() {
 +
catALot.remove();
 +
} );
 +
}
 +
 
 +
if ( ( mw.util.getParamValue( 'withJS' ) === 'MediaWiki:Gadget-Cat-a-lot.js' &&
 +
!mw.util.getParamValue( 'withCSS' ) ) ||
 +
mw.loader.getState( 'ext.gadget.Cat-a-lot' ) === 'registered' ) {
 +
importStylesheet( 'MediaWiki:Gadget-Cat-a-lot.css' );
 +
}
 +
 
 +
var reCat = new RegExp( '^\\s*' + catALot.localizedRegex( NS_CAT, 'Category' ) + ':', '' );
 +
 
 +
$searchInput.keypress( function( e ) {
 +
if ( e.which === 13 ) {
 +
catALot.updateCats( $.trim( $( this ).val() ) );
 +
}
 +
} )
 +
.bind( 'input keyup', function() {
 +
var oldVal = this.value,
 +
newVal = oldVal.replace( reCat, '' );
 +
if ( newVal !== oldVal ) {
 +
this.value = newVal;
 +
}
 +
} );
 +
if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Search' ) {
 +
$searchInput.val( mw.util.getParamValue( 'search' ) );
 +
}
 +
function initAutocomplete() {
 +
if ( catALot.autoCompleteIsEnabled ) {
 +
return;
 +
}
 +
catALot.autoCompleteIsEnabled = true;
 +
 
 +
$searchInput.autocomplete( {
 +
source: function( request, response ) {
 +
catALot.doAPICall( {
 +
action: 'opensearch',
 +
search: request.term,
 +
redirects: 'resolve',
 +
namespace: NS_CAT
 +
}, function( data ) {
 +
if ( data[ 1 ] ) {
 +
response( $( data[ 1 ] )
 +
.map( function( index, item ) {
 +
return item.replace( reCat, '' );
 +
} ) );
 
}
 
}
);
+
} );
 +
},
 +
open: function() {
 +
$( '.ui-autocomplete' )
 +
.position( {
 +
my: $( 'body' )
 +
.is( '.rtl' ) ? 'left bottom' : 'right bottom',
 +
at: $( 'body' )
 +
.is( '.rtl' ) ? 'left top' : 'right top',
 +
of: $searchInput
 +
} );
 
},
 
},
open:function(){
+
appendTo: '#cat_a_lot'
$(".ui-autocomplete")
+
} );
.position({
+
}
my: "left bottom",
+
 
at: "left top",
+
$selectAll
of: $('#cat_a_lot_searchcatname')
+
.click( function() {
});
+
catALot.toggleAll( true );
 +
} );
 +
$selectNone
 +
.click( function() {
 +
catALot.toggleAll( false );
 +
} );
 +
$link
 +
.click( function() {
 +
$( this ).toggleClass( 'cat_a_lot_enabled' );
 +
// Load autocomplete on demand
 +
mw.loader.using( [ 'jquery.ui.autocomplete' ], initAutocomplete );
 +
catALot.run();
 +
} );
 +
$settingsLink
 +
.click( function() {
 +
catALot.manageSettings();
 +
} );
 +
 
 +
this.localCatName = formattedNS[ NS_CAT ];
 +
},
 +
 
 +
findAllLabels: function( searchmode ) {
 +
// It's possible to allow any kind of pages as well but what happens if you click on "select all" and don't expect it
 +
switch ( searchmode ) {
 +
case 'search':
 +
this.labels = this.labels.add( $( 'table.searchResultImage' ).find( 'tr>td:eq(1)' ) );
 +
if ( this.settings.editpages ) {
 +
this.labels = this.labels.add( 'div.mw-search-result-heading' );
 +
}
 +
break;
 +
case 'category':
 +
this.findAllLabels( 'gallery' );
 +
this.labels = this.labels.add( $( 'div#mw-category-media' ).find( 'li[class!="gallerybox"]' ) );
 +
 
 +
if ( this.settings.editpages ) {
 +
this.labels = this.labels.add( $( 'div#mw-pages, div#mw-subcategories' ).find( 'li' ) );
 
}
 
}
});
+
break;
importStylesheet('MediaWiki:Gadget-Cat-a-lot.css');
+
case 'contribs':
this.localCatName = mw.config.get('wgFormattedNamespaces')[14];
+
this.labels = this.labels.add( $( 'ul.mw-contributions-list li' ) );
 +
// FIXME: Filter if !this.settings.editpages
 +
break;
 +
case 'prefix':
 +
this.labels = this.labels.add( $( 'ul.mw-prefixindex-list li' ) );
 +
break;
 +
case 'listfiles':
 +
// this.labels = this.labels.add( $( 'table.listfiles>tbody>tr' ).find( 'td:eq(1)' ) );
 +
this.labels = this.labels.add( $( '.TablePager_col_img_name' ) );
 +
break;
 +
case 'gallery':
 +
this.labels = this.labels.add( 'div.gallerytext' );
 +
break;
 +
}
 
},
 
},
findAllLabels: function () {
+
 
this.labels = $('#mw-pages').find('li');
+
getTitleFromLink: function( href ) {
var subCats =  $('#mw-subcategories').find('.CategoryTreeItem');
+
try {
for (var sub = 0; sub < subCats.length; sub++) {
+
return decodeURIComponent( href )
var a = $(subCats[sub]).find('a');
+
.match( /wiki\/(.+?)(?:#.+)?$/ )[ 1 ].replace( /_/g, ' ' );
if (a.length) {
+
} catch ( ex ) {
a[0]['title'] = catALot.localCatName + ":" + a[0].innerHTML;
+
return '';
this.labels.push(subCats[sub]);
 
}
 
 
}
 
}
$('li a, .CategoryTreeLabel', mw.util.$content).attr({href: '#noSuchAnchor'});
 
 
},
 
},
  
getMarkedLabels: function () {
+
getMarkedLabels: function() {
var marked = [];
+
this.selectedLabels = this.labels.filter( '.cat_a_lot_selected:visible' );
this.selectedLabels = this.labels.filter('.cat_a_lot_selected');
+
return this.selectedLabels.map( function() {
this.selectedLabels.each(function () {
+
var file = $( this ).find( 'a[title][class$="title"]' );
var file = $(this).find('a[title]');
+
file = file.length ? file : $( this ).find( 'a[title]' );
marked.push([file.attr('title'), $(this)]);
+
var title = file.attr( 'title' ) ||
});
+
catALot.getTitleFromLink( file.attr( 'href' ) ) ||
return marked;
+
catALot.getTitleFromLink( $( this ).find( 'a' )
 +
.attr( 'href' ) );
 +
 
 +
return [ [ title, $( this ) ] ];
 +
} );
 
},
 
},
  
updateSelectionCounter: function () {
+
updateSelectionCounter: function() {
this.selectedLabels = this.labels.filter('.cat_a_lot_selected');
+
this.selectedLabels = this.labels.filter( '.cat_a_lot_selected' );
$('#cat_a_lot_mark_counter').show().html(this.selectedLabels.length + this.i18n.filesSelected );
+
$markCounter
 +
.show()
 +
.html( msg( 'files-selected', this.selectedLabels.length ) );
 
},
 
},
  
makeClickable: function () {
+
makeClickable: function() {
this.findAllLabels();
+
this.labels = $();
this.labels.click(function () {
+
this.findAllLabels( this.searchmode );
$(this).toggleClass('cat_a_lot_selected');
+
this.labels.catALotShiftClick( function() {
 
catALot.updateSelectionCounter();
 
catALot.updateSelectionCounter();
});
+
} )
 +
.addClass( 'cat_a_lot_label' );
 
},
 
},
  
toggleAll: function (select) {
+
toggleAll: function( select ) {
this.labels.toggleClass('cat_a_lot_selected', select);
+
this.labels.toggleClass( 'cat_a_lot_selected', select );
 
this.updateSelectionCounter();
 
this.updateSelectionCounter();
 
},
 
},
  
getSubCats: function (cmcontinue) {
+
getSubCats: function() {
 
var data = {
 
var data = {
 
action: 'query',
 
action: 'query',
 
list: 'categorymembers',
 
list: 'categorymembers',
cmnamespace: 14,
+
cmtype: 'subcat',
cmlimit: 50,
+
cmlimit: this.settings.subcatcount,
cmtitle: this.localCatName + ':' + this.currentCategory
+
cmtitle: 'Category:' + this.currentCategory
 
};
 
};
if (cmcontinue)
 
data.cmcontinue = cmcontinue;
 
else
 
this.subCats = [];
 
 
this.doAPICall(data, function (result) {
 
  
 +
this.doAPICall( data, function( result ) {
 
var cats = result.query.categorymembers;
 
var cats = result.query.categorymembers;
 
+
for (var i = 0; i < cats.length; i++) {
+
catALot.subCats = [];
catALot.subCats.push(cats[i].title.replace(/^[^:]+:/, ""));
+
for ( var i = 0; i < cats.length; i++ ) {
 +
catALot.subCats.push( cats[ i ].title.replace( /^[^:]+:/, '' ) );
 
}
 
}
if (result['query-continue'])
+
catALot.catCounter++;
catALot.getSubCats(result['query-continue'].categorymembers.cmcontinue);
+
if ( catALot.catCounter === 2 ) {
else {
+
catALot.showCategoryList();
catALot.catCounter++;
 
if (catALot.catCounter == 2) catALot.showCategoryList();
 
 
}
 
}
});
+
} );
 
},
 
},
  
 
+
getParentCats: function() {
getParentCats: function () {
 
 
var data = {
 
var data = {
 
action: 'query',
 
action: 'query',
 
prop: 'categories',
 
prop: 'categories',
cllimit: 500,
+
titles: 'Category:' + this.currentCategory
titles: this.localCatName + ':' + this.currentCategory
 
 
};
 
};
this.doAPICall(data, function (result) {
+
this.doAPICall( data, function( result ) {
catALot.parentCats = new Array();
+
catALot.parentCats = [];
var pages = result.query.pages;
+
var cats, pages = result.query.pages;
if (pages[-1] && pages[-1].missing == '') {
+
if ( pages[ -1 ] && pages[ -1 ].missing === '' ) {
catALot.catlist.html('<span id="cat_a_lot_no_found">' + catALot.i18n.catNotFound + '</span>');
+
$resultList.html( '<span id="cat_a_lot_no_found">' + msg( 'cat-not-found' ) + '</span>' );
 
document.body.style.cursor = 'auto';
 
document.body.style.cursor = 'auto';
  
catALot.catlist.append('<ul></ul>');
+
$resultList.append( '<table>' );
catALot.createCatLinks("", [catALot.currentCategory]);
+
catALot.createCatLinks( '', [ catALot.currentCategory ] );
 
return;
 
return;
 
}
 
}
 
// there should be only one, but we don't know its ID
 
// there should be only one, but we don't know its ID
for (var id in pages) {
+
for ( var id in pages ) {
var cats = pages[id].categories;
+
cats = pages[ id ].categories;
 
}
 
}
for (var i = 0; i < cats.length; i++) {
+
for ( var i = 0; i < cats.length; i++ ) {
catALot.parentCats.push(cats[i].title.replace(/^[^:]+:/, ""));
+
catALot.parentCats.push( cats[ i ].title.replace( /^[^:]+:/, '' ) );
 
}
 
}
  
 
catALot.catCounter++;
 
catALot.catCounter++;
if (catALot.catCounter == 2) catALot.showCategoryList();
+
if ( catALot.catCounter === 2 ) {
});
+
catALot.showCategoryList();
 +
}
 +
} );
 
},
 
},
regexBuilder: function (category) {
+
 
var catname = ( this.localCatName == 'Category' ) ? this.localCatName: this.localCatName + '|Category';
+
localizedRegex: function( namespaceNumber, fallback ) {
catname = '(' + catname + ')';
+
// Copied from HotCat. Thanks Lupo.
 +
var wikiTextBlank = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+';
 +
var wikiTextBlankRE = new RegExp( wikiTextBlank, 'g' );
 +
 
 +
var createRegexStr = function( name ) {
 +
if ( !name || name.length === 0 ) {
 +
return '';
 +
}
 +
var regexName = '';
 +
for ( var i = 0; i < name.length; i++ ) {
 +
var initial = name.substr( i, 1 );
 +
var ll = initial.toLowerCase();
 +
var ul = initial.toUpperCase();
 +
if ( ll === ul ) {
 +
regexName += initial;
 +
} else {
 +
regexName += '[' + ll + ul + ']';
 +
}
 +
}
 +
return regexName.replace( /([\\\^\$\.\?\*\+\(\)])/g, '\\$1' )
 +
.replace( wikiTextBlankRE, wikiTextBlank );
 +
};
 +
 
 +
fallback = fallback.toLowerCase();
 +
var canonical = formattedNS[ namespaceNumber ].toLowerCase();
 +
var RegexString = createRegexStr( canonical );
 +
if ( fallback && canonical !== fallback ) {
 +
RegexString += '|' + createRegexStr( fallback );
 +
}
 +
for ( var catName in nsIDs ) {
 +
if ( typeof catName === 'string' && catName.toLowerCase() !== canonical && catName.toLowerCase() !== fallback && nsIDs[ catName ] === namespaceNumber ) {
 +
RegexString += '|' + createRegexStr( catName );
 +
}
 +
}
 +
return ( '(?:' + RegexString + ')' );
 +
},
 +
 
 +
regexBuilder: function( category ) {
 +
var catname = this.localizedRegex( NS_CAT, 'Category' );
  
 
// Build a regexp string for matching the given category:
 
// Build a regexp string for matching the given category:
 
// trim leading/trailing whitespace and underscores
 
// trim leading/trailing whitespace and underscores
category = category.replace(/^[\s_]+/, "").replace(/[\s_]+$/, "");
+
category = category.replace (/^[\s_]+|[\s_]+$/g, "");
  
 
// escape regexp metacharacters (= any ASCII punctuation except _)
 
// escape regexp metacharacters (= any ASCII punctuation except _)
category = category.replace(/([!-\/:-@\[-^`{-~])/g, '\\$1');
+
category = mw.RegExp.escape( category );
  
 
// any sequence of spaces and underscores should match any other
 
// any sequence of spaces and underscores should match any other
category = category.replace(/[\s_]+/g, '[\\s_]+');
+
category = category.replace( /[\s_]+/g, '[\\s_]+' );
  
 
// Make the first character case-insensitive:
 
// Make the first character case-insensitive:
var first = category.substr(0, 1);
+
var first = category.substr( 0, 1 );
if (first.toUpperCase() != first.toLowerCase()) category = '[' + first.toUpperCase() + first.toLowerCase() + ']' + category.substr(1);
+
if ( first.toUpperCase() !== first.toLowerCase() ) {
 +
category = '[' + first.toUpperCase() + first.toLowerCase() + ']' + category.substr( 1 );
 +
}
  
 
// Compile it into a RegExp that matches MediaWiki category syntax (yeah, it looks ugly):
 
// Compile it into a RegExp that matches MediaWiki category syntax (yeah, it looks ugly):
 
// XXX: the first capturing parens are assumed to match the sortkey, if present, including the | but excluding the ]]
 
// XXX: the first capturing parens are assumed to match the sortkey, if present, including the | but excluding the ]]
return new RegExp('\\[\\[[\\s_]*' + catname + '[\\s_]*:[\\s_]*' + category + '[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]', 'ig');
+
return new RegExp( '\\[\\[[\\s_]*' + catname + '[\\s_]*:[\\s_]*' + category + '[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]\\s*', 'g' );
 
},
 
},
  
getContent: function (file, targetcat, mode) {
+
getContent: function( file, targetcat, mode ) {
 
 
 
var data = {
 
var data = {
 
action: 'query',
 
action: 'query',
שורה 196: שורה 452:
 
rvprop: 'content|timestamp',
 
rvprop: 'content|timestamp',
 
intoken: 'edit',
 
intoken: 'edit',
titles: file[0]
+
titles: file[ 0 ]
 
};
 
};
  
this.doAPICall(data, function (result) {
+
this.doAPICall( data, function( result ) {
catALot.editCategories(result, file, targetcat, mode);
+
catALot.editCategories( result, file, targetcat, mode );
});
+
} );
 +
},
 +
 
 +
// Remove {{Uncategorized}}. No need to replace it with anything.
 +
removeUncat: function( text ) {
 +
return text.replace( /\{\{\s*[Uu]ncategorized\s*(\|?.*?)\}\}/, '' );
 
},
 
},
  
editCategories: function (result, file, targetcat, mode) {
+
doCleanup: function( text ) {
 +
if ( this.settings.docleanup ) {
 +
return text.replace( /\{\{\s*[Cc]heck categories\s*(\|?.*?)\}\}/, '' );
 +
} else {
 +
return text;
 +
}
 +
},
  
if (result == null) {
+
editCategories: function( result, file, targetcat, mode ) {
//Happens on unstable wifi connections..
+
var otext, starttimestamp, timestamp;
this.connectionError.push(file[0]);
+
if ( !result ) {
 +
// Happens on unstable wifi connections..
 +
this.connectionError.push( file[ 0 ] );
 
this.updateCounter();
 
this.updateCounter();
 
return;
 
return;
שורה 215: שורה 484:
  
 
// there should be only one, but we don't know its ID
 
// there should be only one, but we don't know its ID
for (var id in pages) {
+
for ( var id in pages ) {
 
// The edittoken only changes between logins
 
// The edittoken only changes between logins
this.edittoken = pages[id].edittoken;
+
this.edittoken = pages[ id ].edittoken;
var otext = pages[id].revisions[0]['*'];
+
otext = pages[ id ].revisions[ 0 ][ '*' ];
var starttimestamp = pages[id].starttimestamp;
+
starttimestamp = pages[ id ].starttimestamp;
var timestamp = pages[id].revisions[0]['timestamp'];
+
timestamp = pages[ id ].revisions[ 0 ].timestamp;
 
}
 
}
  
  
var sourcecat = wgTitle;
+
var sourcecat = this.origin;
 +
// Check if that file is already in that category
 +
if ( mode !== 'remove' && this.regexBuilder( targetcat ).test( otext ) ) {
  
// Check if that file is already in that category
+
// If the new cat is already there, just remove the old one.
if (mode != "remove" && this.regexBuilder(targetcat).test(otext)) {
+
if ( mode === 'move' ) {
//If the new cat is already there, just remove the old one.
+
mode = 'remove';
if (mode == 'move') {
 
mode='remove';
 
 
} else {
 
} else {
this.alreadyThere.push(file[0]);
+
this.alreadyThere.push( file[ 0 ] );
 
this.updateCounter();
 
this.updateCounter();
 
return;
 
return;
שורה 242: שורה 511:
  
 
// Fix text
 
// Fix text
switch (mode) {
+
switch ( mode ) {
case 'add':
+
case 'add':
text += "\n[[" + this.localCatName + ":" + targetcat + "]]\n";
+
text += '\n[[' + this.localCatName + ':' + targetcat + ']]\n';
comment = this.i18n.summaryAdd + targetcat + "]]";
+
comment = msgPlain( 'summary-add' ).replace( '$1', targetcat );
break;
+
break;
case 'copy':
+
case 'copy':
text = text.replace(this.regexBuilder(sourcecat), "[[" + this.localCatName + ":" + sourcecat + "$2]]\n[[" + this.localCatName + ":" + targetcat + "$2]]");
+
text = text.replace( this.regexBuilder( sourcecat ), '[[' + this.localCatName + ':' + sourcecat + '$1]]\n[[' + this.localCatName + ':' + targetcat + '$1]]\n' );
comment = this.i18n.summaryCopy + sourcecat + "]] " + this.i18n.to + targetcat + "]]";
+
comment = msgPlain( 'summary-copy' ).replace( '$1', sourcecat ).replace( '$2', targetcat );
break;
+
// If category is added through template:
case 'move':
+
if ( otext === text ) {
text = text.replace(this.regexBuilder(sourcecat), "[[" + this.localCatName + ":" + targetcat + "$2]]");
+
text += '\n[[' + this.localCatName + ':' + targetcat + ']]';
comment = this.i18n.summaryMove + sourcecat + "]] " + this.i18n.to + targetcat + "]]";
+
}
break;
+
break;
case 'remove':
+
case 'move':
text = text.replace(this.regexBuilder(sourcecat), "");
+
text = text.replace( this.regexBuilder( sourcecat ), '[[' + this.localCatName + ':' + targetcat + '$1]]\n' );
comment = this.i18n.summaryRemove + sourcecat + "]]";
+
comment = msgPlain( 'summary-move' ).replace( '$1', sourcecat ).replace( '$2', targetcat );
break;
+
break;
 +
case 'remove':
 +
text = text.replace( this.regexBuilder( sourcecat ), '' );
 +
comment = msgPlain( 'summary-remove' ).replace( '$1', sourcecat );
 +
break;
 
}
 
}
  
if (text == otext) {
+
if ( text === otext ) {
this.notFound.push(file[0]);
+
this.notFound.push( file[ 0 ] );
 
this.updateCounter();
 
this.updateCounter();
 
return;
 
return;
 
}
 
}
  
 +
// Remove uncat after we checked whether we changed the text successfully.
 +
// Otherwise we might fail to do the changes, but still replace {{uncat}}
 +
if ( mode !== 'remove' ) {
 +
text = this.doCleanup( this.removeUncat( text ) );
 +
}
 
var data = {
 
var data = {
 
action: 'edit',
 
action: 'edit',
 +
assert: 'user',
 
summary: comment,
 
summary: comment,
title: file[0],
+
title: file[ 0 ],
token: this.edittoken,
+
text: text,
 +
bot: true,
 
starttimestamp: starttimestamp,
 
starttimestamp: starttimestamp,
 
basetimestamp: timestamp,
 
basetimestamp: timestamp,
text: text
+
watchlist: this.settings.watchlist,
 +
token: this.edittoken
 
};
 
};
 +
if ( this.settings.minor ) {
 +
data.minor = true;
 +
}
  
var isBot=$.inArray('bot', wgUserGroups)>-1;
+
this.doAPICall( data, function() {
if(isBot)
 
data.bot = '1';
 
 
 
this.doAPICall(data, function (ret) {
 
 
catALot.updateCounter();
 
catALot.updateCounter();
});
+
} );
this.markAsDone(file[1], mode, targetcat);
+
this.markAsDone( file[ 1 ], mode, targetcat );
 
},
 
},
markAsDone: function (label, mode, targetcat) {
 
  
label.addClass('cat_a_lot_markAsDone');
+
markAsDone: function( label, mode, targetcat ) {
switch (mode) {
+
label.addClass( 'cat_a_lot_markAsDone' );
case 'add':
+
switch ( mode ) {
label.append('<br>' + this.i18n.addedCat + ' ' + targetcat);
+
case 'add':
break;
+
label.append( '<br>' + msg( 'added-cat', targetcat ) );
case 'copy':
+
break;
label.append('<br>' + this.i18n.copiedCat + ' ' + targetcat);
+
case 'copy':
break;
+
label.append( '<br>' + msg( 'copied-cat', targetcat ) );
case 'move':
+
break;
label.append('<br>' + this.i18n.movedCat + ' ' + targetcat);
+
case 'move':
break;
+
label.append( '<br>' + msg( 'moved-cat', targetcat ) );
case 'remove':
+
break;
label.append('<br>' + this.i18n.movedCat );
+
case 'remove':
break;
+
label.append( '<br>' + msg( 'removed-cat' ) );
 +
break;
 
}
 
}
 
},
 
},
updateCounter: function () {
 
  
 +
updateCounter: function() {
 
this.counterCurrent++;
 
this.counterCurrent++;
if (this.counterCurrent > this.counterNeeded) this.displayResult();
+
if ( this.counterCurrent > this.counterNeeded ) {
else this.domCounter.text(this.counterCurrent);
+
this.displayResult();
 +
} else {
 +
this.domCounter.text( this.counterCurrent );
 +
}
 
},
 
},
  
displayResult: function () {
+
displayResult: function() {
 
 
 
document.body.style.cursor = 'auto';
 
document.body.style.cursor = 'auto';
$('.cat_a_lot_feedback').addClass('cat_a_lot_done');
+
$( '.cat_a_lot_feedback' )
$('.ui-dialog-content').height('auto');
+
.addClass( 'cat_a_lot_done' );
 +
$( '.ui-dialog-content' )
 +
.height( 'auto' );
 
var rep = this.domCounter.parent();
 
var rep = this.domCounter.parent();
rep.html('<h3>' + this.i18n.done + '</h3>');
+
rep.html( '<h3>' + msg( 'done' ) + '</h3>' );
rep.append( this.i18n.allDone + '<br />');
+
rep.append( msg( 'all-done' ) + '<br />' );
  
var close = $('<a>').append( this.i18n.returnToPage );
+
var close = $( '<a>' )
close.click(function () {
+
.text( msgPlain( 'return-to-page' ) );
 +
close.click( function() {
 
catALot.progressDialog.remove();
 
catALot.progressDialog.remove();
catALot.toggleAll(false);
+
catALot.toggleAll( false );
});
+
} );
rep.append(close);
+
rep.append( close );
if (this.alreadyThere.length) {
+
if ( this.alreadyThere.length ) {
rep.append( this.i18n.skippedAlready );
+
rep.append( '<h5>' + msg( 'skipped-already', this.alreadyThere.length ) + '</h5>' );
rep.append(this.alreadyThere.join('<br>'));
+
rep.append( this.alreadyThere.join( '<br>' ) );
 
}
 
}
if (this.notFound.length) {
+
if ( this.notFound.length ) {
rep.append( this.i18n.skippedNotFound );
+
rep.append( '<h5>' + msg( 'skipped-not-found', this.notFound.length ) + '</h5>' );
rep.append(this.notFound.join('<br>'));
+
rep.append( this.notFound.join( '<br>' ) );
 
}
 
}
if (this.connectionError.length) {
+
if ( this.connectionError.length ) {
rep.append( this.i18n.skippedServer );
+
rep.append( '<h5>' + msg( 'skipped-server', this.connectionError.length ) + '</h5>' );
rep.append(this.connectionError.join('<br>'));
+
rep.append( this.connectionError.join( '<br>' ) );
 
}
 
}
  
 
},
 
},
  
moveHere: function (targetcat) {
+
moveHere: function( targetcat ) {
this.doSomething(targetcat, 'move');
+
this.doSomething( targetcat, 'move' );
 
},
 
},
  
copyHere: function (targetcat) {
+
copyHere: function( targetcat ) {
this.doSomething(targetcat, 'copy');
+
this.doSomething( targetcat, 'copy' );
 
},
 
},
 
+
addHere: function (targetcat) {
+
addHere: function( targetcat ) {
this.doSomething(targetcat, 'add');
+
this.doSomething( targetcat, 'add' );
 
},
 
},
  
remove: function () {
+
remove: function() {
this.doSomething('', 'remove');
+
this.doSomething( '', 'remove' );
 
},
 
},
  
doSomething: function (targetcat, mode) {
+
doSomething: function( targetcat, mode ) {
 
var files = this.getMarkedLabels();
 
var files = this.getMarkedLabels();
if (files.length == 0) {
+
if ( files.length === 0 ) {
alert( this.i18n.noneSelected );
+
alert( msgPlain( 'none-selected' ) );
 
return;
 
return;
 
}
 
}
שורה 368: שורה 653:
 
this.counterCurrent = 1;
 
this.counterCurrent = 1;
 
this.counterNeeded = files.length;
 
this.counterNeeded = files.length;
this.showProgress();
+
mw.loader.using( [ 'jquery.ui.dialog', 'mediawiki.RegExp' ], function() {
for (var i = 0; i < files.length; i++) {
+
catALot.showProgress();
this.getContent(files[i], targetcat, mode);
+
for ( var i = 0; i < files.length; i++ ) {
}
+
catALot.getContent( files[ i ], targetcat, mode );
 +
}
 +
} );
 
},
 
},
  
doAPICall: function (params, callback) {
+
doAPICall: function( params, callback ) {
 
 
 
params.format = 'json';
 
params.format = 'json';
$.ajax({
+
var i = 0,
url: this.apiUrl,
+
apiUrl = this.apiUrl,
cache: false,
+
doCall,
dataType: 'json',
+
handleError = function( jqXHR, textStatus, errorThrown ) {
data: params,
+
if ( window.console && $.isFunction( window.console.log ) ) {
type: 'POST',
+
window.console.log( 'Error: ', jqXHR, textStatus, errorThrown );
success: callback
+
}
});
+
if ( i < 4 ) {
 +
window.setTimeout( doCall, 300 );
 +
i++;
 +
} else if ( params.title ) {
 +
this.connectionError.push( params.title );
 +
this.updateCounter();
 +
return;
 +
}
 +
};
 +
doCall = function() {
 +
$.ajax( {
 +
url: apiUrl,
 +
cache: false,
 +
dataType: 'json',
 +
data: params,
 +
type: 'POST',
 +
success: callback,
 +
error: handleError
 +
} );
 +
};
 +
doCall();
 
},
 
},
  
createCatLinks: function (symbol, list) {
+
createCatLinks: function( symbol, list ) {
 
list.sort();
 
list.sort();
var domlist = this.catlist.find('ul');
+
var domlist = $resultList.find( 'table' );
for (var i = 0; i < list.length; i++) {
+
for ( var i = 0; i < list.length; i++ ) {
var li = $('<li></li>');
+
var $tr = $( '<tr>' );
  
var link = $('<a></a>');
+
var $link = $( '<a>' ),
link.text(list[i]);
+
$add, $move, $copy;
li.data('cat', list[i]);
 
link.click(function () {
 
catALot.updateCats($(this).parent().data('cat'));
 
});
 
  
if (this.searchmode) {
+
$link.text( list[ i ] );
var add = $('<a class="cat_a_lot_action"><b>' + this.i18n.add + '</b></a>');
+
$tr.data( 'cat', list[ i ] );
add.click(function () {
+
$link.click( function() {
catALot.addHere($(this).parent().data('cat'));
+
catALot.updateCats( $( this ).closest( 'tr' ).data( 'cat' ) );
});
+
} );
 +
 
 +
$tr.append( $( '<td>' ).text( symbol ) )
 +
.append( $( '<td>' ).append( $link ) );
 +
 
 +
if ( this.origin ) {
 +
// Can't move to source category
 +
if ( list[ i ] !== this.origin ) {
 +
$move = $( '<a>' )
 +
.addClass( 'cat_a_lot_move' )
 +
.text( msgPlain( 'move' ) )
 +
.click( function() {
 +
catALot.moveHere( $( this ).closest( 'tr' ).data( 'cat' ) );
 +
} );
 +
 
 +
$copy = $( '<a>' )
 +
.addClass( 'cat_a_lot_action' )
 +
.text( msgPlain( 'copy' ) )
 +
.click( function() {
 +
catALot.copyHere( $( this ).closest( 'tr' ).data( 'cat' ) );
 +
} );
 +
 
 +
$tr.append( $( '<td>' ).append( $move ), $( '<td>' ).append( $copy ) );
 +
}
 
} else {
 
} else {
var move = $('<a class="cat_a_lot_move"><b>' + this.i18n.move + '</b></a>');
+
$add = $( '<a>' )
move.click(function () {
+
.addClass( 'cat_a_lot_action' )
catALot.moveHere($(this).parent().data('cat'));
+
.text( msgPlain( 'add' ) )
});
+
.click( function() {
 +
catALot.addHere( $( this ).closest( 'tr' ).data( 'cat' ) );
 +
} );
  
var copy = $('<a class="cat_a_lot_action"><b>' + this.i18n.copy + '</b></a>');
+
$tr.append( $( '<td>' ).append( $add ) );
copy.click(function () {
 
catALot.copyHere($(this).parent().data('cat'));
 
});
 
 
}
 
}
  
// Can't move to source category
+
domlist.append( $tr );
if (list[i] != wgTitle && this.searchmode) li.append(' ').append(add);
 
else if (list[i] != wgTitle && !this.searchmode) li.append(' ').append(move).append(' ').append(copy);
 
li.append(symbol).append(' ').append(link);
 
 
 
domlist.append(li);
 
 
}
 
}
 
},
 
},
  
getCategoryList: function () {
+
getCategoryList: function() {
 
this.catCounter = 0;
 
this.catCounter = 0;
 
this.getParentCats();
 
this.getParentCats();
שורה 432: שורה 751:
 
},
 
},
  
showCategoryList: function () {
+
showCategoryList: function() {
var thiscat = [this.currentCategory];
+
var thiscat = [ this.currentCategory ];
  
this.catlist.empty();
+
$resultList.empty();
this.catlist.append('<ul></ul>');
+
$resultList.append( '<table>' );
  
this.createCatLinks("", this.parentCats);
+
this.createCatLinks( '', this.parentCats );
this.createCatLinks("", thiscat);
+
this.createCatLinks( '', thiscat );
this.createCatLinks("", this.subCats);
+
this.createCatLinks( '', this.subCats );
  
 
document.body.style.cursor = 'auto';
 
document.body.style.cursor = 'auto';
//Reset width
+
// Reset width
var cat = $('#cat_a_lot');
+
$container.width( '' );
cat.width('');
+
$container.height( '' );
cat.height('');
+
$container.width( Math.min( $container.width() * 1.1 + 15, $( window ).width() - 10 ) );
cat.width( cat.width() * 1.05 );
+
 
var list = $('#cat_a_lot_category_list');
+
$resultList.css( {
list.css({maxHeight: this.setHeight+'px', height: ''});
+
maxHeight: this.setHeight + 'px',
 +
height: ''
 +
} );
 
},
 
},
  
updateCats: function (newcat) {
+
updateCats: function( newcat ) {
 
document.body.style.cursor = 'wait';
 
document.body.style.cursor = 'wait';
  
 
this.currentCategory = newcat;
 
this.currentCategory = newcat;
this.catlist = $('#cat_a_lot_category_list');
+
$resultList.html( '<div class="cat_a_lot_loading">' + msgPlain( 'loading' ) + '</div>' );
this.catlist.html('<div class="cat_a_lot_loading">' + this.i18n.loading + '</div>');
 
 
this.getCategoryList();
 
this.getCategoryList();
 
},
 
},
showProgress: function () {
+
 +
showProgress: function() {
 
document.body.style.cursor = 'wait';
 
document.body.style.cursor = 'wait';
  
this.progressDialog = $('<div></div>')
+
this.progressDialog = $( '<div>' )
.html( this.i18n.editing + ' <span id="cat_a_lot_current">' + this.counterCurrent + '</span> ' + this.i18n.of + this.counterNeeded)
+
.html( msg( 'editing' ) + ' <span id="cat_a_lot_current">' + this.counterCurrent + '</span> ' + msg( 'of' ) + this.counterNeeded )
.dialog({
+
.dialog( {
width: 450,
+
width: 450,
height: 90,
+
height: 90,
minHeight: 90,
+
minHeight: 90,
modal: true,
+
modal: true,
resizable: false,
+
resizable: false,
draggable: false,
+
draggable: false,
closeOnEscape: false,
+
closeOnEscape: false,
dialogClass: "cat_a_lot_feedback"
+
dialogClass: 'cat_a_lot_feedback'
});
+
} );
$('.ui-dialog-titlebar').hide();
+
$( '.ui-dialog-titlebar' )
this.domCounter = $('#cat_a_lot_current');
+
.hide();
 +
this.domCounter = $( '#cat_a_lot_current' );
  
 
},
 
},
  
run: function () {
+
run: function() {
if ($('.cat_a_lot_enabled').length) {
+
if ( $( '.cat_a_lot_enabled' ).length ) {
 
this.makeClickable();
 
this.makeClickable();
$("#cat_a_lot_data").show();
+
$dataContainer
$('#cat_a_lot').resizable({
+
.show();
handles: 'n',  
+
$container
alsoResize: '#cat_a_lot_category_list',
+
.resizable( {
resize: function(event, ui) {
+
handles: 'n',
$(this).css({left:"", top:""});
+
alsoResize: '#cat_a_lot_category_list',
catALot.setHeight = $(this).height();
+
resize: function() {
$('#cat_a_lot_category_list').css({maxHeight: '', width: ''});
+
$( this )
}  
+
.css( {
});
+
left: '',
$('#cat_a_lot_category_list').css({maxHeight: '450px'});
+
top: ''
if (this.searchmode) this.updateCats("Pictures and images");
+
} );
else this.updateCats(wgTitle);
+
catALot.setHeight = $( this ).height();
 +
$resultList
 +
.css( {
 +
maxHeight: '',
 +
width: ''
 +
} );
 +
}
 +
} )
 +
/*.draggable( { // FIXME: Box get static if sametime resize
 +
cursor: 'move',
 +
start: function() {
 +
$( this ).css( 'height', $( this ).height() );
 +
}
 +
} )*/;
 +
$resultList
 +
.css( {
 +
maxHeight: '450px'
 +
} );
  
 +
this.updateCats( this.origin || 'Images' );
 +
$link.text( 'X' );
 
} else {
 
} else {
$("#cat_a_lot_data").hide();
+
$dataContainer
$("#cat_a_lot").resizable( "destroy" );
+
.hide();
//Unbind click handlers
+
$container
this.labels.unbind('click');
+
// .draggable( 'destroy' )
 +
.resizable( 'destroy' )
 +
.removeAttr( 'style' );
 +
// Unbind click handlers
 +
this.labels.unbind( 'click.catALot' );
 +
$link.text( 'Cat-a-lot' );
 
}
 
}
 
},
 
},
i18n: (wgUserLanguage == "he") ? {
 
//Progress
 
loading: 'טוען...',
 
editing: 'עורך דף',
 
of: 'מתוך ',
 
skippedAlready: '<h5>הדפים להלן לא שונו, משום שכבר הכילו את הקטגוריה:</h5>',
 
skippedNotFound: '<h5>הדפים להלן לא שונו, משום שהקטגוריה לא נמצאה:</h5>',
 
skippedServer: '<h5>Tהדפים להלן לא שונו, בגלל בעית תקשורת/h5>',
 
allDone: 'כל הדפים שונו.',
 
done: 'בוצע!',
 
addedCat: 'קטגוריה התווספה',
 
copiedCat: 'קטגוריה התווספה',
 
movedCat: 'הועברו לקטגוריה',
 
removedCat: 'הוסרו מקטגוריה',
 
returnToPage: 'חזור לדף',
 
catNotFound: 'קטגוריה לא נמצאה.',
 
  
 +
manageSettings: function() {
 +
mw.loader.using( [ 'ext.gadget.SettingsManager', 'ext.gadget.SettingsUI', 'jquery.ui.progressbar' ], function() {
 +
catALot._manageSettings();
 +
} );
 +
},
 +
 +
_manageSettings: function() {
 +
mw.libs.SettingsUI( this.defaults, 'Cat-a-lot' )
 +
.show()
 +
.done( function( s, verbose, loc, settingsOut, $dlg ) {
 +
var mustRestart = false,
 +
_restart = function() {
 +
if ( !mustRestart ) {
 +
return;
 +
}
  
//as in 17 files selected
+
$container.remove();
filesSelected: ' דפים מסומנים.',
+
catALot.labels.unbind( 'click.catALot' );
 +
catALot.init();
 +
},
 +
_saveToJS = function() {
 +
var opt = mw.libs.settingsManager.option( {
 +
optionName: 'catALotPrefs',
 +
value: catALot.settings,
 +
encloseSignature: 'catALot',
 +
encloseBlock: '////////// Cat-a-lot user preferences //////////\n',
 +
triggerSaveAt: /Cat.?A.?Lot/i,
 +
editSummary: msgPlain( 'pref-save-summary' )
 +
} ),
 +
oldHeight = $dlg.height(),
 +
$prog = $( '<div>' );
  
//Actions
+
$dlg.css( 'height', oldHeight )
copy: 'הוסף',
+
.html( '' );
move: 'העבר',
+
$prog.css( {
add: 'הוסף',
+
height: Math.round( oldHeight / 8 ),
removeFromCat: 'הסרה מקטגוריה זו',
+
'margin-top': Math.round( ( 7 * oldHeight ) / 16 )
enterName: 'שם קטגוריה להוספה לרשימה',
+
} )
select: 'בחירה',
+
.appendTo( $dlg );
all: 'כולם',
 
none: 'נקה',
 
  
noneSelected: 'אין דפים מסומנים!',
+
$dlg.parent()
 +
.find( '.ui-dialog-buttonpane button' )
 +
.button( 'option', 'disabled', true );
  
//Summaries:
+
opt.save()
summaryAdd: 'Cat-a-lot: הוסיף ל[[קטגוריה:',
+
.done( function( text, progress ) {
summaryCopy: 'Cat-a-lot: העתיק מ[[קטגוריה:',
+
$prog.progressbar( {
to: 'ל[[קטגוריה:',
+
value: progress
summaryMove: 'Cat-a-lot: העביר מ[[קטגוריה:',
+
} );
summaryRemove: 'Cat-a-lot: הסיר מ[[קטגוריה:'
+
$prog.fadeOut( function() {
}: {
+
$dlg.dialog( 'close' );
//Progress
+
_restart();
loading: 'Loading...',
+
} );
editing: 'Editing page',
+
} )
of: 'of ',
+
.progress( function( text, progress ) {
skippedAlready: '<h5>The following pages were skipped, because the page was already in the category:</h5>',
+
$prog.progressbar( {
skippedNotFound: '<h5>The following pages were skipped, because the old category could not be found:</h5>',
+
value: progress
skippedServer: '<h5>The following pages couldn\'t be changed, since there were problems connecting to the server:</h5>',
+
} );
allDone: 'All pages are processed.',
+
// TODO: Add "details" to progressbar
done: 'Done!',
+
} )
addedCat: 'Added category',
+
.fail( function( text ) {
copiedCat: 'Copied to category',
+
$prog.addClass( 'ui-state-error' );
movedCat: 'Moved to category',
+
$dlg.prepend( $( '<p>' )
removedCat: 'Removed from category',
+
.text( text ) );
returnToPage: 'Return to page',
+
} );
catNotFound: 'Category not found.',
+
};
 +
$.each( settingsOut, function( n, v ) {
 +
if ( v.forcerestart && catALot.settings[ v.name ] !== v.value ) {
 +
mustRestart = true;
 +
}
 +
catALot.settings[ v.name ] = v.value;
 +
window.catALotPrefs[ v.name ] = v.value;
 +
} );
 +
switch ( loc ) {
 +
case 'page':
 +
$dlg.dialog( 'close' );
 +
_restart();
 +
break;
 +
case 'account-publicly':
 +
_saveToJS();
 +
break;
 +
}
 +
} );
 +
},
 +
 +
_initSettings: function() {
 +
if ( this.settings.watchlist ) {
 +
return;
 +
}
 +
if ( !window.catALotPrefs ) {
 +
window.catALotPrefs = {};
 +
}
 +
$.each( this.defaults, function( n, v ) {
 +
v.value = catALot.settings[ v.name ] = ( window.catALotPrefs[ v.name ] || v[ 'default' ] );
 +
v.label = msgPlain( v.label_i18n );
 +
if ( v.select_i18n ) {
 +
v.select = {};
 +
$.each( v.select_i18n, function( i18nk, val ) {
 +
v.select[ msgPlain( i18nk ) ] = val;
 +
} );
 +
}
 +
} );
 +
},
 +
/* eslint-disable camelcase */
 +
defaults: [ {
 +
name: 'watchlist',
 +
'default': 'preferences',
 +
label_i18n: 'watchlistpref',
 +
select_i18n: {
 +
watch_pref: 'preferences',
 +
watch_nochange: 'nochange',
 +
watch_watch: 'watch',
 +
watch_unwatch: 'unwatch'
 +
}
 +
}, {
 +
name: 'minor',
 +
'default': false,
 +
label_i18n: 'minorpref'
 +
}, {
 +
name: 'editpages',
 +
'default': false,
 +
label_i18n: 'editpagespref',
 +
forcerestart: true
 +
}, {
 +
name: 'docleanup',
 +
'default': false,
 +
label_i18n: 'docleanuppref'
 +
}, {
 +
name: 'subcatcount',
 +
'default': 50,
 +
min: 5,
 +
max: 500,
 +
label_i18n: 'subcatcountpref',
 +
forcerestart: true
 +
} ]
 +
/* eslint-enable camelcase */
 +
};
  
 +
// The gadget is not immediately needed, so let the page load normally
 +
window.setTimeout( function () {
 +
var userGrp = mw.config.get('wgUserGroups');
 +
var trusted = ( $.inArray( 'sysop', userGrp ) > -1 ||
 +
$.inArray( 'autoconfirmed', userGrp ) > -1 ||
 +
mw.config.get( 'wgRelevantUserName' ) === mw.config.get( 'wgUserName' ) );
  
//as in 17 files selected
+
switch ( mw.config.get( 'wgNamespaceNumber' ) ) {
filesSelected: ' files selected.',
+
case NS_CAT:
 +
catALot.searchmode = 'category';
 +
catALot.origin = mw.config.get( 'wgTitle' );
 +
break;
 +
case -1:
 +
catALot.searchmode = {
 +
// list of accepted special page names mapped to search mode names
 +
Contributions: 'contribs',
 +
Listfiles: trusted ? 'listfiles' : null,
 +
Prefixindex: trusted ? 'prefix' : null,
 +
Search: 'search',
 +
Uncategorizedimages: 'gallery'
 +
}[ mw.config.get( 'wgCanonicalSpecialPageName' ) ];
 +
break;
 +
}
  
//Actions
+
if ( catALot.searchmode ) {
copy: 'Copy',
+
var loadingLocalizations = 1;
move: 'Move',
+
var loadLocalization = function( lang, cb ) {
add: 'Add',
+
loadingLocalizations++;
removeFromCat: 'Remove from this category',
+
switch ( lang ) {
enterName: 'Enter category name',
+
case 'zh-hk':
select: 'Select',
+
case 'zh-mo':
all: 'all',
+
case 'zh-tw':
none: 'none',
+
lang = 'zh-hant';
 +
break;
 +
case 'zh':
 +
case 'zh-cn':
 +
case 'zh-my':
 +
case 'zh-sg':
 +
lang = 'zh-hans';
 +
break;
 +
}
  
noneSelected: 'No files selected!',
+
$.ajax( {
 +
url: commons_url,
 +
dataType: 'script',
 +
data: {
 +
title: 'MediaWiki:Gadget-Cat-a-lot.js/' + lang,
 +
action: 'raw',
 +
ctype: 'text/javascript',
 +
// Allow caching for 28 days
 +
maxage: 2419200,
 +
smaxage: 2419200
 +
},
 +
cache: true,
 +
success: cb,
 +
error: cb
 +
} );
 +
};
 +
var maybeLaunch = function() {
 +
loadingLocalizations--;
 +
 
 +
function init() {
 +
$( function() {
 +
catALot.init();
 +
} );
 +
}
 +
if ( !loadingLocalizations )
 +
mw.loader.using( [ 'user' ], init, init );
 +
};
  
//Summaries:
+
if ( mw.config.get( 'wgUserLanguage' ) !== 'en' )
summaryAdd: 'Cat-a-lot: Adding [[Category:',
+
loadLocalization( mw.config.get( 'wgUserLanguage' ), maybeLaunch );
summaryCopy: 'Cat-a-lot: Copying from [[Category:',
+
if ( mw.config.get( 'wgContentLanguage' ) !== 'en' )
to: 'to [[Category:',
+
loadLocalization( mw.config.get( 'wgContentLanguage' ), maybeLaunch );
summaryMove: 'Cat-a-lot: Moving from [[Category:',
+
maybeLaunch();
summaryRemove: 'Cat-a-lot: Removing from [[Category:'
 
 
}
 
}
 +
}, 400);
 +
 +
/**
 +
*  Derivative work of
 +
*  (replace "checkboxes" with cat-a-lot labels in your mind)
 +
*/
 +
/**
 +
* jQuery checkboxShiftClick
 +
*
 +
* This will enable checkboxes to be checked or unchecked in a row by clicking one, holding shift and clicking another one
 +
*
 +
* @author Krinkle <krinklemail@gmail.com>
 +
* @license GPL v2
 +
*/
 +
$.fn.catALotShiftClick = function( cb ) {
 +
var prevCheckbox = null,
 +
$box = this;
 +
// When our boxes are clicked..
 +
$box.bind( 'click.catALot', function( e ) {
 +
 +
// Prevent following the link and text selection
 +
e.preventDefault();
 +
 +
// Highlight last selected
 +
$( '#cat_a_lot_last_selected' )
 +
.removeAttr( 'id' );
 +
var $thisControl = $( e.target ),
 +
method;
 +
if ( !$thisControl.hasClass( 'cat_a_lot_label' ) ) {
 +
$thisControl = $thisControl.parents( '.cat_a_lot_label' );
 +
}
 +
$thisControl.attr( 'id', 'cat_a_lot_last_selected' )
 +
.toggleClass( 'cat_a_lot_selected' );
 +
 +
// And one has been clicked before...
 +
if ( prevCheckbox !== null && e.shiftKey ) {
 +
method = $thisControl.hasClass( 'cat_a_lot_selected' ) ? 'addClass' : 'removeClass';
 +
 +
// Check or uncheck this one and all in-between checkboxes
 +
$box.slice(
 +
Math.min( $box.index( prevCheckbox ), $box.index( $thisControl ) ),
 +
Math.max( $box.index( prevCheckbox ), $box.index( $thisControl ) ) + 1
 +
)[ method ]( 'cat_a_lot_selected' );
 +
}
 +
// Either way, update the prevCheckbox variable to the one clicked now
 +
prevCheckbox = $thisControl;
 +
 +
if ( $.isFunction( cb ) ) {
 +
cb();
 +
}
 +
} );
 +
return $box;
 
};
 
};
  
 +
}( jQuery, mediaWiki ) );
  
if ((wgNamespaceNumber == -1 && wgCanonicalSpecialPageName == "Search") || wgNamespaceNumber == 14) {
+
// </nowiki>
if ( wgNamespaceNumber == -1 ) catALot.searchmode = true;
 
mediaWiki.loader.using(['jquery.ui.dialog', 'jquery.ui.autocomplete'], function () {
 
$(function () {
 
catALot.init();
 
});
 
});
 
}
 
 
 
// </source>
 

גרסה אחרונה מ־21:07, 23 ביולי 2017

/**
 * Cat-a-lot
 * Changes category of multiple files
 *
 * Originally by Magnus Manske
 * RegExes by Ilmari Karonen
 * Completely rewritten by DieBuche
 *
 * Requires [[MediaWiki:Gadget-SettingsManager.js]] and [[MediaWiki:Gadget-SettingsUI.js]] (properly registered) for per-user-settings
 *
 * READ THIS PAGE IF YOU WANT TO TRANSLATE OR USE THIS ON ANOTHER SITE:
 * http://commons.wikimedia.org/wiki/MediaWiki:Gadget-Cat-a-lot.js/translating
 * <nowiki>
 */

/* global jQuery, mediaWiki, importStylesheet */
/* eslint one-var: 0, vars-on-top: 0, no-underscore-dangle:0 */ // extends: wikimedia
/* jshint unused:true, forin:false, smarttabs:true, loopfunc:true, browser:true */

( function( $, mw ) {
'use strict';

var NS_CAT = 14,
	formattedNS = mw.config.get( 'wgFormattedNamespaces' ),
	nsIDs = mw.config.get( 'wgNamespaceIds' );

var msgs = {
	// Preferences
	// new: added 2012-09-19. Please translate.
	// Use user language for i18n
	'cat-a-lot-watchlistpref': 'Watchlist preference concerning files edited with Cat-a-lot',
	'cat-a-lot-watch_pref': 'According to your general preferences',
	'cat-a-lot-watch_nochange': 'Do not change watchstatus',
	'cat-a-lot-watch_watch': 'Watch pages edited with Cat-a-lot',
	'cat-a-lot-watch_unwatch': 'Remove pages while editing with Cat-a-lot from your watchlist',
	'cat-a-lot-minorpref': 'Mark edits as minor (if you generally mark your edits as minor, this won\'t change anything)',
	'cat-a-lot-editpagespref': 'Allow categorising pages (including categories) that are not files',
	'cat-a-lot-docleanuppref': 'Remove {{Check categories}} and other minor cleanup',
	'cat-a-lot-subcatcountpref': 'Sub-categories to show at most',
	'cat-a-lot-config-settings': 'Preferences',

	// Progress
	'cat-a-lot-loading': 'Loading...',
	'cat-a-lot-editing': 'Editing page',
	'cat-a-lot-of': 'of ',
	'cat-a-lot-skipped-already': 'The following {{PLURAL:$1|1=page was|$1 pages were}} skipped, because the page was already in the category:',
	'cat-a-lot-skipped-not-found': 'The following {{PLURAL:$1|1=page was|$1 pages were}} skipped, because the old category could not be found:',
	'cat-a-lot-skipped-server': 'The following {{PLURAL:$1|1=page|$1 pages}} couldn\'t be changed, since there were problems connecting to the server:',
	'cat-a-lot-all-done': 'All pages are processed.',
	'cat-a-lot-done': 'Done!',
	'cat-a-lot-added-cat': 'Added category $1',
	'cat-a-lot-copied-cat': 'Copied to category $1',
	'cat-a-lot-moved-cat': 'Moved to category $1',
	'cat-a-lot-removed-cat': 'Removed from category $1',
	'cat-a-lot-return-to-page': 'Return to page',
	'cat-a-lot-cat-not-found': 'Category not found.',

	// as in 17 files selected
	'cat-a-lot-files-selected': '{{PLURAL:$1|1=One file|$1 files}} selected.',

	// Actions
	'cat-a-lot-copy': 'Copy',
	'cat-a-lot-move': 'Move',
	'cat-a-lot-add': 'Add',
	'cat-a-lot-remove-from-cat': 'Remove from this category',
	'cat-a-lot-enter-name': 'Enter category name',
	'cat-a-lot-select': 'Select',
	'cat-a-lot-all': 'all',
	'cat-a-lot-none': 'none',
	'cat-a-lot-none-selected': 'No files selected!',

	// Summaries:
	'cat-a-lot-pref-save-summary': '[[c:Help:Gadget-Cat-a-lot|Cat-a-lot]]: updating user preferences',
	'cat-a-lot-summary-add': '[[c:Help:Cat-a-lot|Cat-a-lot]]: Adding [[Category:$1]]',
	'cat-a-lot-summary-copy': '[[c:Help:Cat-a-lot|Cat-a-lot]]: Copying from [[Category:$1]] to [[Category:$2]]',
	'cat-a-lot-summary-move': '[[c:Help:Cat-a-lot|Cat-a-lot]]: Moving from [[Category:$1]] to [[Category:$2]]',
	'cat-a-lot-summary-remove': '[[c:Help:Cat-a-lot|Cat-a-lot]]: Removing from [[Category:$1]]'
};
mw.messages.set( msgs );

function msg( /* params */ ) {
	var args = Array.prototype.slice.call( arguments, 0 );
	args[0] = 'cat-a-lot-' + args[0];
	return mw.message.apply( mw.message, args ).parse();
}
function msgPlain( key ) {
	return mw.message( 'cat-a-lot-' + key ).plain();
}

// There is only one cat-a-lot on one page
var $body, $container, $dataContainer, $searchInputContainer, $searchInput, $resultList, $markCounter,
	$selections, $selectAll, $selectNone, $settingsWrapper, $settingsLink, $head, $link;
var commons_url = 'https://commons.wikimedia.org/w/index.php';

var catALot = window.catALot = {
	apiUrl: mw.util.wikiScript( 'api' ),
	origin: false,
	searchmode: false,
	version: 3.7,
	setHeight: 450,
	settings: {},
	init: function() {
		this._initSettings();

		$body = $( document.body );
		$container = $( '<div>' )
			.attr( 'id', 'cat_a_lot' )
			.appendTo( $body );
		$dataContainer = $( '<div>' )
			.attr( 'id', 'cat_a_lot_data' )
			.appendTo( $container );
		$searchInputContainer = $( '<div>' )
			.appendTo( $dataContainer );
		$searchInput = $( '<input>' )
			.attr({
				id: 'cat_a_lot_searchcatname',
				placeholder: msgPlain( 'enter-name' ),
				type: 'text'
			})
			.appendTo( $searchInputContainer );
		$resultList = $( '<div>' )
			.attr( 'id', 'cat_a_lot_category_list' )
			.appendTo( $dataContainer );
		$markCounter = $( '<div>' )
			.attr( 'id', 'cat_a_lot_mark_counter' )
			.appendTo( $dataContainer );
		$selections = $( '<div>' )
			.attr( 'id', 'cat_a_lot_selections' )
			.text( msgPlain( 'select' ) )
			.appendTo( $dataContainer );
		$selectAll = $( '<a>' )
			.attr( 'id', 'cat_a_lot_select_all' )
			.text( msgPlain( 'all' ) )
			.appendTo( $selections.append( ' ' ) );
		$selectNone = $( '<a>' )
			.attr( 'id', 'cat_a_lot_select_none' )
			.text( msgPlain( 'none' ) )
			.appendTo( $selections.append( ' • ' ) );
		$settingsWrapper = $( '<div>' )
			.attr( 'id', 'cat_a_lot_settings' )
			.appendTo( $dataContainer );
		$settingsLink = $( '<a>' )
			.attr( 'id', 'cat_a_lot_config_settings' )
			.text( msgPlain( 'config-settings' ) )
			.appendTo( $settingsWrapper );
		$head = $( '<div>' )
			.attr( 'id', 'cat_a_lot_head' )
			.appendTo( $container );
		$link = $( '<a>' )
			.attr( 'id', 'cat_a_lot_toggle' )
			.text( 'Cat-a-lot' )
			.appendTo( $head );
		$settingsWrapper.append( $( '<a>' )
			.attr( {
				href: commons_url + '?title=Special:MyLanguage/Help:Gadget-Cat-a-lot',
				target: '_blank',
				style: 'float:right',
				title: $( '#n-help' ).attr( 'title' )
			} ).text( '?' ) );

		if ( this.origin ) {
			$( '<a>' )
				.attr( 'id', 'cat_a_lot_remove' )
				.html( msg( 'remove-from-cat' ) )
				.appendTo( $selections )
				.click( function() {
					catALot.remove();
				} );
		}

		if ( ( mw.util.getParamValue( 'withJS' ) === 'MediaWiki:Gadget-Cat-a-lot.js' &&
				!mw.util.getParamValue( 'withCSS' ) ) ||
				mw.loader.getState( 'ext.gadget.Cat-a-lot' ) === 'registered' ) {
			importStylesheet( 'MediaWiki:Gadget-Cat-a-lot.css' );
		}

		var reCat = new RegExp( '^\\s*' + catALot.localizedRegex( NS_CAT, 'Category' ) + ':', '' );

		$searchInput.keypress( function( e ) {
			if ( e.which === 13 ) {
				catALot.updateCats( $.trim( $( this ).val() ) );
			}
		} )
			.bind( 'input keyup', function() {
				var oldVal = this.value,
					newVal = oldVal.replace( reCat, '' );
				if ( newVal !== oldVal ) {
					this.value = newVal;
				}
			} );
		if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Search' ) {
			$searchInput.val( mw.util.getParamValue( 'search' ) );
		}
		function initAutocomplete() {
			if ( catALot.autoCompleteIsEnabled ) {
				return;
			}
			catALot.autoCompleteIsEnabled = true;

			$searchInput.autocomplete( {
				source: function( request, response ) {
					catALot.doAPICall( {
						action: 'opensearch',
						search: request.term,
						redirects: 'resolve',
						namespace: NS_CAT
					}, function( data ) {
						if ( data[ 1 ] ) {
							response( $( data[ 1 ] )
								.map( function( index, item ) {
									return item.replace( reCat, '' );
								} ) );
						}
					} );
				},
				open: function() {
					$( '.ui-autocomplete' )
						.position( {
							my: $( 'body' )
								.is( '.rtl' ) ? 'left bottom' : 'right bottom',
							at: $( 'body' )
								.is( '.rtl' ) ? 'left top' : 'right top',
							of: $searchInput
						} );
				},
				appendTo: '#cat_a_lot'
			} );
		}

		$selectAll
			.click( function() {
				catALot.toggleAll( true );
			} );
		$selectNone
			.click( function() {
				catALot.toggleAll( false );
			} );
		$link
			.click( function() {
				$( this ).toggleClass( 'cat_a_lot_enabled' );
				// Load autocomplete on demand
				mw.loader.using( [ 'jquery.ui.autocomplete' ], initAutocomplete );
				catALot.run();
			} );
		$settingsLink
			.click( function() {
				catALot.manageSettings();
			} );

		this.localCatName = formattedNS[ NS_CAT ];
	},

	findAllLabels: function( searchmode ) {
		// It's possible to allow any kind of pages as well but what happens if you click on "select all" and don't expect it
		switch ( searchmode ) {
			case 'search':
				this.labels = this.labels.add( $( 'table.searchResultImage' ).find( 'tr>td:eq(1)' ) );
				if ( this.settings.editpages ) {
					this.labels = this.labels.add( 'div.mw-search-result-heading' );
				}
				break;
			case 'category':
				this.findAllLabels( 'gallery' );
				this.labels = this.labels.add( $( 'div#mw-category-media' ).find( 'li[class!="gallerybox"]' ) );

				if ( this.settings.editpages ) {
					this.labels = this.labels.add( $( 'div#mw-pages, div#mw-subcategories' ).find( 'li' ) );
				}
				break;
			case 'contribs':
				this.labels = this.labels.add( $( 'ul.mw-contributions-list li' ) );
				// FIXME: Filter if !this.settings.editpages
				break;
			case 'prefix':
				this.labels = this.labels.add( $( 'ul.mw-prefixindex-list li' ) );
				break;
			case 'listfiles':
				// this.labels = this.labels.add( $( 'table.listfiles>tbody>tr' ).find( 'td:eq(1)' ) );
				this.labels = this.labels.add( $( '.TablePager_col_img_name' ) );
				break;
			case 'gallery':
				this.labels = this.labels.add( 'div.gallerytext' );
				break;
		}
	},

	getTitleFromLink: function( href ) {
		try {
			return decodeURIComponent( href )
				.match( /wiki\/(.+?)(?:#.+)?$/ )[ 1 ].replace( /_/g, ' ' );
		} catch ( ex ) {
			return '';
		}
	},

	getMarkedLabels: function() {
		this.selectedLabels = this.labels.filter( '.cat_a_lot_selected:visible' );
		return this.selectedLabels.map( function() {
			var file = $( this ).find( 'a[title][class$="title"]' );
			file = file.length ? file : $( this ).find( 'a[title]' );
			var title = file.attr( 'title' ) ||
					catALot.getTitleFromLink( file.attr( 'href' ) ) ||
					catALot.getTitleFromLink( $( this ).find( 'a' )
			.attr( 'href' ) );

			return [ [ title, $( this ) ] ];
		} );
	},

	updateSelectionCounter: function() {
		this.selectedLabels = this.labels.filter( '.cat_a_lot_selected' );
		$markCounter
			.show()
			.html( msg( 'files-selected', this.selectedLabels.length ) );
	},

	makeClickable: function() {
		this.labels = $();
		this.findAllLabels( this.searchmode );
		this.labels.catALotShiftClick( function() {
			catALot.updateSelectionCounter();
		} )
			.addClass( 'cat_a_lot_label' );
	},

	toggleAll: function( select ) {
		this.labels.toggleClass( 'cat_a_lot_selected', select );
		this.updateSelectionCounter();
	},

	getSubCats: function() {
		var data = {
			action: 'query',
			list: 'categorymembers',
			cmtype: 'subcat',
			cmlimit: this.settings.subcatcount,
			cmtitle: 'Category:' + this.currentCategory
		};

		this.doAPICall( data, function( result ) {
			var cats = result.query.categorymembers;
			
			catALot.subCats = [];
			for ( var i = 0; i < cats.length; i++ ) {
				catALot.subCats.push( cats[ i ].title.replace( /^[^:]+:/, '' ) );
			}
			catALot.catCounter++;
			if ( catALot.catCounter === 2 ) {
				catALot.showCategoryList();
			}
		} );
	},

	getParentCats: function() {
		var data = {
			action: 'query',
			prop: 'categories',
			titles: 'Category:' + this.currentCategory
		};
		this.doAPICall( data, function( result ) {
			catALot.parentCats = [];
			var cats, pages = result.query.pages;
			if ( pages[ -1 ] && pages[ -1 ].missing === '' ) {
				$resultList.html( '<span id="cat_a_lot_no_found">' + msg( 'cat-not-found' ) + '</span>' );
				document.body.style.cursor = 'auto';

				$resultList.append( '<table>' );
				catALot.createCatLinks( '→', [ catALot.currentCategory ] );
				return;
			}
			// there should be only one, but we don't know its ID
			for ( var id in pages ) {
				cats = pages[ id ].categories;
			}
			for ( var i = 0; i < cats.length; i++ ) {
				catALot.parentCats.push( cats[ i ].title.replace( /^[^:]+:/, '' ) );
			}

			catALot.catCounter++;
			if ( catALot.catCounter === 2 ) {
				catALot.showCategoryList();
			}
		} );
	},

	localizedRegex: function( namespaceNumber, fallback ) {
		// Copied from HotCat. Thanks Lupo.
		var wikiTextBlank = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+';
		var wikiTextBlankRE = new RegExp( wikiTextBlank, 'g' );

		var createRegexStr = function( name ) {
			if ( !name || name.length === 0 ) {
				return '';
			}
			var regexName = '';
			for ( var i = 0; i < name.length; i++ ) {
				var initial = name.substr( i, 1 );
				var ll = initial.toLowerCase();
				var ul = initial.toUpperCase();
				if ( ll === ul ) {
					regexName += initial;
				} else {
					regexName += '[' + ll + ul + ']';
				}
			}
			return regexName.replace( /([\\\^\$\.\?\*\+\(\)])/g, '\\$1' )
				.replace( wikiTextBlankRE, wikiTextBlank );
		};

		fallback = fallback.toLowerCase();
		var canonical = formattedNS[ namespaceNumber ].toLowerCase();
		var RegexString = createRegexStr( canonical );
		if ( fallback && canonical !== fallback ) {
			RegexString += '|' + createRegexStr( fallback );
		}
		for ( var catName in nsIDs ) {
			if ( typeof catName === 'string' && catName.toLowerCase() !== canonical && catName.toLowerCase() !== fallback && nsIDs[ catName ] === namespaceNumber ) {
				RegexString += '|' + createRegexStr( catName );
			}
		}
		return ( '(?:' + RegexString + ')' );
	},

	regexBuilder: function( category ) {
		var catname = this.localizedRegex( NS_CAT, 'Category' );

		// Build a regexp string for matching the given category:
		// trim leading/trailing whitespace and underscores
		category = category.replace (/^[\s_]+|[\s_]+$/g, "");

		// escape regexp metacharacters (= any ASCII punctuation except _)
		category = mw.RegExp.escape( category );

		// any sequence of spaces and underscores should match any other
		category = category.replace( /[\s_]+/g, '[\\s_]+' );

		// Make the first character case-insensitive:
		var first = category.substr( 0, 1 );
		if ( first.toUpperCase() !== first.toLowerCase() ) {
			category = '[' + first.toUpperCase() + first.toLowerCase() + ']' + category.substr( 1 );
		}

		// Compile it into a RegExp that matches MediaWiki category syntax (yeah, it looks ugly):
		// XXX: the first capturing parens are assumed to match the sortkey, if present, including the | but excluding the ]]
		return new RegExp( '\\[\\[[\\s_]*' + catname + '[\\s_]*:[\\s_]*' + category + '[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]\\s*', 'g' );
	},

	getContent: function( file, targetcat, mode ) {
		var data = {
			action: 'query',
			prop: 'info|revisions',
			rvprop: 'content|timestamp',
			intoken: 'edit',
			titles: file[ 0 ]
		};

		this.doAPICall( data, function( result ) {
			catALot.editCategories( result, file, targetcat, mode );
		} );
	},

	// Remove {{Uncategorized}}. No need to replace it with anything.
	removeUncat: function( text ) {
		return text.replace( /\{\{\s*[Uu]ncategorized\s*(\|?.*?)\}\}/, '' );
	},

	doCleanup: function( text ) {
		if ( this.settings.docleanup ) {
			return text.replace( /\{\{\s*[Cc]heck categories\s*(\|?.*?)\}\}/, '' );
		} else {
			return text;
		}
	},

	editCategories: function( result, file, targetcat, mode ) {
		var otext, starttimestamp, timestamp;
		if ( !result ) {
			// Happens on unstable wifi connections..
			this.connectionError.push( file[ 0 ] );
			this.updateCounter();
			return;
		}
		var pages = result.query.pages;

		// there should be only one, but we don't know its ID
		for ( var id in pages ) {
			// The edittoken only changes between logins
			this.edittoken = pages[ id ].edittoken;
			otext = pages[ id ].revisions[ 0 ][ '*' ];
			starttimestamp = pages[ id ].starttimestamp;
			timestamp = pages[ id ].revisions[ 0 ].timestamp;
		}


		var sourcecat = this.origin;
		// Check if that file is already in that category
		if ( mode !== 'remove' && this.regexBuilder( targetcat ).test( otext ) ) {

			// If the new cat is already there, just remove the old one.
			if ( mode === 'move' ) {
				mode = 'remove';
			} else {
				this.alreadyThere.push( file[ 0 ] );
				this.updateCounter();
				return;
			}
		}

		var text = otext;
		var comment;

		// Fix text
		switch ( mode ) {
			case 'add':
				text += '\n[[' + this.localCatName + ':' + targetcat + ']]\n';
				comment = msgPlain( 'summary-add' ).replace( '$1', targetcat );
				break;
			case 'copy':
				text = text.replace( this.regexBuilder( sourcecat ), '[[' + this.localCatName + ':' + sourcecat + '$1]]\n[[' + this.localCatName + ':' + targetcat + '$1]]\n' );
				comment = msgPlain( 'summary-copy' ).replace( '$1', sourcecat ).replace( '$2', targetcat );
				// If category is added through template:
				if ( otext === text ) {
					text += '\n[[' + this.localCatName + ':' + targetcat + ']]';
				}
				break;
			case 'move':
				text = text.replace( this.regexBuilder( sourcecat ), '[[' + this.localCatName + ':' + targetcat + '$1]]\n' );
				comment = msgPlain( 'summary-move' ).replace( '$1', sourcecat ).replace( '$2', targetcat );
				break;
			case 'remove':
				text = text.replace( this.regexBuilder( sourcecat ), '' );
				comment = msgPlain( 'summary-remove' ).replace( '$1', sourcecat );
				break;
		}

		if ( text === otext ) {
			this.notFound.push( file[ 0 ] );
			this.updateCounter();
			return;
		}

		// Remove uncat after we checked whether we changed the text successfully.
		// Otherwise we might fail to do the changes, but still replace {{uncat}}
		if ( mode !== 'remove' ) {
			text = this.doCleanup( this.removeUncat( text ) );
		}
		var data = {
			action: 'edit',
			assert: 'user',
			summary: comment,
			title: file[ 0 ],
			text: text,
			bot: true,
			starttimestamp: starttimestamp,
			basetimestamp: timestamp,
			watchlist: this.settings.watchlist,
			token: this.edittoken
		};
		if ( this.settings.minor ) {
			data.minor = true;
		}

		this.doAPICall( data, function() {
			catALot.updateCounter();
		} );
		this.markAsDone( file[ 1 ], mode, targetcat );
	},

	markAsDone: function( label, mode, targetcat ) {
		label.addClass( 'cat_a_lot_markAsDone' );
		switch ( mode ) {
			case 'add':
				label.append( '<br>' + msg( 'added-cat', targetcat ) );
				break;
			case 'copy':
				label.append( '<br>' + msg( 'copied-cat', targetcat ) );
				break;
			case 'move':
				label.append( '<br>' + msg( 'moved-cat', targetcat ) );
				break;
			case 'remove':
				label.append( '<br>' + msg( 'removed-cat' ) );
				break;
		}
	},

	updateCounter: function() {
		this.counterCurrent++;
		if ( this.counterCurrent > this.counterNeeded ) {
			this.displayResult();
		} else {
			this.domCounter.text( this.counterCurrent );
		}
	},

	displayResult: function() {
		document.body.style.cursor = 'auto';
		$( '.cat_a_lot_feedback' )
			.addClass( 'cat_a_lot_done' );
		$( '.ui-dialog-content' )
			.height( 'auto' );
		var rep = this.domCounter.parent();
		rep.html( '<h3>' + msg( 'done' ) + '</h3>' );
		rep.append( msg( 'all-done' ) + '<br />' );

		var close = $( '<a>' )
			.text( msgPlain( 'return-to-page' ) );
		close.click( function() {
			catALot.progressDialog.remove();
			catALot.toggleAll( false );
		} );
		rep.append( close );
		if ( this.alreadyThere.length ) {
			rep.append( '<h5>' + msg( 'skipped-already', this.alreadyThere.length ) + '</h5>' );
			rep.append( this.alreadyThere.join( '<br>' ) );
		}
		if ( this.notFound.length ) {
			rep.append( '<h5>' + msg( 'skipped-not-found', this.notFound.length ) + '</h5>' );
			rep.append( this.notFound.join( '<br>' ) );
		}
		if ( this.connectionError.length ) {
			rep.append( '<h5>' + msg( 'skipped-server', this.connectionError.length ) + '</h5>' );
			rep.append( this.connectionError.join( '<br>' ) );
		}

	},

	moveHere: function( targetcat ) {
		this.doSomething( targetcat, 'move' );
	},

	copyHere: function( targetcat ) {
		this.doSomething( targetcat, 'copy' );
	},
	
	addHere: function( targetcat ) {
		this.doSomething( targetcat, 'add' );
	},

	remove: function() {
		this.doSomething( '', 'remove' );
	},

	doSomething: function( targetcat, mode ) {
		var files = this.getMarkedLabels();
		if ( files.length === 0 ) {
			alert( msgPlain( 'none-selected' ) );
			return;
		}
		this.notFound = [];
		this.alreadyThere = [];
		this.connectionError = [];
		this.counterCurrent = 1;
		this.counterNeeded = files.length;
		mw.loader.using( [ 'jquery.ui.dialog', 'mediawiki.RegExp' ], function() {
			catALot.showProgress();
			for ( var i = 0; i < files.length; i++ ) {
				catALot.getContent( files[ i ], targetcat, mode );
			}
		} );
	},

	doAPICall: function( params, callback ) {
		params.format = 'json';
		var i = 0,
			apiUrl = this.apiUrl,
			doCall,
			handleError = function( jqXHR, textStatus, errorThrown ) {
				if ( window.console && $.isFunction( window.console.log ) ) {
					window.console.log( 'Error: ', jqXHR, textStatus, errorThrown );
				}
				if ( i < 4 ) {
					window.setTimeout( doCall, 300 );
					i++;
				} else if ( params.title ) {
					this.connectionError.push( params.title );
					this.updateCounter();
					return;
				}
			};
		doCall = function() {
			$.ajax( {
				url: apiUrl,
				cache: false,
				dataType: 'json',
				data: params,
				type: 'POST',
				success: callback,
				error: handleError
			} );
		};
		doCall();
	},

	createCatLinks: function( symbol, list ) {
		list.sort();
		var domlist = $resultList.find( 'table' );
		for ( var i = 0; i < list.length; i++ ) {
			var $tr = $( '<tr>' );

			var $link = $( '<a>' ),
				$add, $move, $copy;

			$link.text( list[ i ] );
			$tr.data( 'cat', list[ i ] );
			$link.click( function() {
				catALot.updateCats( $( this ).closest( 'tr' ).data( 'cat' ) );
			} );

			$tr.append( $( '<td>' ).text( symbol ) )
				.append( $( '<td>' ).append( $link ) );

			if ( this.origin ) {
				// Can't move to source category
				if ( list[ i ] !== this.origin ) {
					$move = $( '<a>' )
						.addClass( 'cat_a_lot_move' )
						.text( msgPlain( 'move' ) )
						.click( function() {
							catALot.moveHere( $( this ).closest( 'tr' ).data( 'cat' ) );
						} );

					$copy = $( '<a>' )
						.addClass( 'cat_a_lot_action' )
						.text( msgPlain( 'copy' ) )
						.click( function() {
							catALot.copyHere( $( this ).closest( 'tr' ).data( 'cat' ) );
						} );

					$tr.append( $( '<td>' ).append( $move ), $( '<td>' ).append( $copy ) );
				}
			} else {
				$add = $( '<a>' )
						.addClass( 'cat_a_lot_action' )
						.text( msgPlain( 'add' ) )
						.click( function() {
							catALot.addHere( $( this ).closest( 'tr' ).data( 'cat' ) );
						} );

				$tr.append( $( '<td>' ).append( $add ) );
			}

			domlist.append( $tr );
		}
	},

	getCategoryList: function() {
		this.catCounter = 0;
		this.getParentCats();
		this.getSubCats();
	},

	showCategoryList: function() {
		var thiscat = [ this.currentCategory ];

		$resultList.empty();
		$resultList.append( '<table>' );

		this.createCatLinks( '↑', this.parentCats );
		this.createCatLinks( '→', thiscat );
		this.createCatLinks( '↓', this.subCats );

		document.body.style.cursor = 'auto';
		// Reset width
		$container.width( '' );
		$container.height( '' );
		$container.width( Math.min( $container.width() * 1.1 + 15, $( window ).width() - 10 ) );

		$resultList.css( {
			maxHeight: this.setHeight + 'px',
			height: ''
		} );
	},

	updateCats: function( newcat ) {
		document.body.style.cursor = 'wait';

		this.currentCategory = newcat;
		$resultList.html( '<div class="cat_a_lot_loading">' + msgPlain( 'loading' ) + '</div>' );
		this.getCategoryList();
	},
	
	showProgress: function() {
		document.body.style.cursor = 'wait';

		this.progressDialog = $( '<div>' )
			.html( msg( 'editing' ) + ' <span id="cat_a_lot_current">' + this.counterCurrent + '</span> ' + msg( 'of' ) + this.counterNeeded )
			.dialog( {
				width: 450,
				height: 90,
				minHeight: 90,
				modal: true,
				resizable: false,
				draggable: false,
				closeOnEscape: false,
				dialogClass: 'cat_a_lot_feedback'
			} );
		$( '.ui-dialog-titlebar' )
			.hide();
		this.domCounter = $( '#cat_a_lot_current' );

	},

	run: function() {
		if ( $( '.cat_a_lot_enabled' ).length ) {
			this.makeClickable();
			$dataContainer
				.show();
			$container
				.resizable( {
					handles: 'n',
					alsoResize: '#cat_a_lot_category_list',
					resize: function() {
						$( this )
							.css( {
								left: '',
								top: ''
							} );
						catALot.setHeight = $( this ).height();
						$resultList
							.css( {
								maxHeight: '',
								width: ''
							} );
					}
				} )
				/*.draggable( { // FIXME: Box get static if sametime resize
					cursor: 'move',
					start: function() {
						$( this ).css( 'height', $( this ).height() );
					}
				} )*/;
			 $resultList
				.css( {
					maxHeight: '450px'
				} );

			this.updateCats( this.origin || 'Images' );
			$link.text( 'X' );
		} else {
			$dataContainer
				.hide();
			$container
				// .draggable( 'destroy' )
				.resizable( 'destroy' )
				.removeAttr( 'style' );
			// Unbind click handlers
			this.labels.unbind( 'click.catALot' );
			$link.text( 'Cat-a-lot' );
		}
	},

	manageSettings: function() {
		mw.loader.using( [ 'ext.gadget.SettingsManager', 'ext.gadget.SettingsUI', 'jquery.ui.progressbar' ], function() {
			catALot._manageSettings();
		} );
	},
	
	_manageSettings: function() {
		mw.libs.SettingsUI( this.defaults, 'Cat-a-lot' )
			.show()
			.done( function( s, verbose, loc, settingsOut, $dlg ) {
				var mustRestart = false,
					_restart = function() {
						if ( !mustRestart ) {
							return;
						}

						$container.remove();
						catALot.labels.unbind( 'click.catALot' );
						catALot.init();
					},
					_saveToJS = function() {
						var opt = mw.libs.settingsManager.option( {
								optionName: 'catALotPrefs',
								value: catALot.settings,
								encloseSignature: 'catALot',
								encloseBlock: '////////// Cat-a-lot user preferences //////////\n',
								triggerSaveAt: /Cat.?A.?Lot/i,
								editSummary: msgPlain( 'pref-save-summary' )
							} ),
							oldHeight = $dlg.height(),
							$prog = $( '<div>' );

						$dlg.css( 'height', oldHeight )
							.html( '' );
						$prog.css( {
							height: Math.round( oldHeight / 8 ),
							'margin-top': Math.round( ( 7 * oldHeight ) / 16 )
						} )
							.appendTo( $dlg );

						$dlg.parent()
							.find( '.ui-dialog-buttonpane button' )
							.button( 'option', 'disabled', true );

						opt.save()
							.done( function( text, progress ) {
								$prog.progressbar( {
									value: progress
								} );
								$prog.fadeOut( function() {
									$dlg.dialog( 'close' );
									_restart();
								} );
							} )
							.progress( function( text, progress ) {
								$prog.progressbar( {
									value: progress
								} );
								// TODO: Add "details" to progressbar
							} )
							.fail( function( text ) {
								$prog.addClass( 'ui-state-error' );
								$dlg.prepend( $( '<p>' )
									.text( text ) );
							} );
					};
				$.each( settingsOut, function( n, v ) {
					if ( v.forcerestart && catALot.settings[ v.name ] !== v.value ) {
						mustRestart = true;
					}
					catALot.settings[ v.name ] = v.value;
					window.catALotPrefs[ v.name ] = v.value;
				} );
				switch ( loc ) {
					case 'page':
						$dlg.dialog( 'close' );
						_restart();
						break;
					case 'account-publicly':
						_saveToJS();
						break;
				}
			} );
	},
	
	_initSettings: function() {
		if ( this.settings.watchlist ) {
			return;
		}
		if ( !window.catALotPrefs ) {
			window.catALotPrefs = {};
		}
		$.each( this.defaults, function( n, v ) {
			v.value = catALot.settings[ v.name ] = ( window.catALotPrefs[ v.name ] || v[ 'default' ] );
			v.label = msgPlain( v.label_i18n );
			if ( v.select_i18n ) {
				v.select = {};
				$.each( v.select_i18n, function( i18nk, val ) {
					v.select[ msgPlain( i18nk ) ] = val;
				} );
			}
		} );
	},
	/* eslint-disable camelcase */
	defaults: [ {
		name: 'watchlist',
		'default': 'preferences',
		label_i18n: 'watchlistpref',
		select_i18n: {
			watch_pref: 'preferences',
			watch_nochange: 'nochange',
			watch_watch: 'watch',
			watch_unwatch: 'unwatch'
		}
	}, {
		name: 'minor',
		'default': false,
		label_i18n: 'minorpref'
	}, {
		name: 'editpages',
		'default': false,
		label_i18n: 'editpagespref',
		forcerestart: true
	}, {
		name: 'docleanup',
		'default': false,
		label_i18n: 'docleanuppref'
	}, {
		name: 'subcatcount',
		'default': 50,
		min: 5,
		max: 500,
		label_i18n: 'subcatcountpref',
		forcerestart: true
	} ]
	/* eslint-enable camelcase */
};

// The gadget is not immediately needed, so let the page load normally
window.setTimeout( function () {
	var userGrp = mw.config.get('wgUserGroups');
	var trusted = ( $.inArray( 'sysop', userGrp ) > -1 ||
		$.inArray( 'autoconfirmed', userGrp ) > -1 ||
		mw.config.get( 'wgRelevantUserName' ) === mw.config.get( 'wgUserName' ) );

	switch ( mw.config.get( 'wgNamespaceNumber' ) ) {
		case NS_CAT:
			catALot.searchmode = 'category';
			catALot.origin = mw.config.get( 'wgTitle' );
			break;
		case -1:
			catALot.searchmode = {
				// list of accepted special page names mapped to search mode names
				Contributions: 'contribs',
				Listfiles: trusted ? 'listfiles' : null,
				Prefixindex: trusted ? 'prefix' : null,
				Search: 'search',
				Uncategorizedimages: 'gallery'
			}[ mw.config.get( 'wgCanonicalSpecialPageName' ) ];
			break;
	}

	if ( catALot.searchmode ) {
		var loadingLocalizations = 1;
		var loadLocalization = function( lang, cb ) {
			loadingLocalizations++;
			switch ( lang ) {
				case 'zh-hk':
				case 'zh-mo':
				case 'zh-tw':
					lang = 'zh-hant';
					break;
				case 'zh':
				case 'zh-cn':
				case 'zh-my':
				case 'zh-sg':
					lang = 'zh-hans';
					break;
			}

			$.ajax( {
				url: commons_url,
				dataType: 'script',
				data: {
					title: 'MediaWiki:Gadget-Cat-a-lot.js/' + lang,
					action: 'raw',
					ctype: 'text/javascript',
					// Allow caching for 28 days
					maxage: 2419200,
					smaxage: 2419200
				},
				cache: true,
				success: cb,
				error: cb
			} );
		};
		var maybeLaunch = function() {
			loadingLocalizations--;

			function init() { 
				$( function() {
					catALot.init();
				} );
			}
			if ( !loadingLocalizations )
				mw.loader.using( [ 'user' ], init, init );
		};

		if ( mw.config.get( 'wgUserLanguage' ) !== 'en' )
			loadLocalization( mw.config.get( 'wgUserLanguage' ), maybeLaunch );
		if ( mw.config.get( 'wgContentLanguage' ) !== 'en' )
			loadLocalization( mw.config.get( 'wgContentLanguage' ), maybeLaunch );
		maybeLaunch();
	}
}, 400);

/**
 *  Derivative work of
 *  (replace "checkboxes" with cat-a-lot labels in your mind)
 */
/**
 * jQuery checkboxShiftClick
 *
 * This will enable checkboxes to be checked or unchecked in a row by clicking one, holding shift and clicking another one
 *
 * @author Krinkle <krinklemail@gmail.com>
 * @license GPL v2
 */
$.fn.catALotShiftClick = function( cb ) {
	var prevCheckbox = null,
		$box = this;
	// When our boxes are clicked..
	$box.bind( 'click.catALot', function( e ) {

		// Prevent following the link and text selection
		e.preventDefault();

		// Highlight last selected
		$( '#cat_a_lot_last_selected' )
			.removeAttr( 'id' );
		var $thisControl = $( e.target ),
			method;
		if ( !$thisControl.hasClass( 'cat_a_lot_label' ) ) {
			$thisControl = $thisControl.parents( '.cat_a_lot_label' );
		}
		$thisControl.attr( 'id', 'cat_a_lot_last_selected' )
			.toggleClass( 'cat_a_lot_selected' );

		// And one has been clicked before...
		if ( prevCheckbox !== null && e.shiftKey ) {
			method = $thisControl.hasClass( 'cat_a_lot_selected' ) ? 'addClass' : 'removeClass';

			// Check or uncheck this one and all in-between checkboxes
			$box.slice(
				Math.min( $box.index( prevCheckbox ), $box.index( $thisControl ) ),
				Math.max( $box.index( prevCheckbox ), $box.index( $thisControl ) ) + 1
			)[ method ]( 'cat_a_lot_selected' );
		}
		// Either way, update the prevCheckbox variable to the one clicked now
		prevCheckbox = $thisControl;

		if ( $.isFunction( cb ) ) {
			cb();
		}
	} );
	return $box;
};

}( jQuery, mediaWiki ) );

// </nowiki>