LLWiki正在建设中,欢迎加入我们

“MediaWiki:Gadget-Cat-a-lot.js”的版本间差异

来自LLWiki
跳转到导航 跳转到搜索
 
(未显示同一用户的6个中间版本)
第23行: 第23行:
'use strict';
'use strict';


var formattedNS = mw.config.get( 'wgFormattedNamespaces' ),
var ns = mw.config.get( 'wgNamespaceNumber' ),
ns = mw.config.get( 'wgNamespaceNumber' ),
nsIDs = mw.config.get( 'wgNamespaceIds' ),
nsIDs = mw.config.get( 'wgNamespaceIds' ),
userGrp = mw.config.get( 'wgUserGroups' ),
userGrp = mw.config.get( 'wgUserGroups' ),
project = mw.config.get( 'wgDBname' );
project = mw.config.get( 'wgDBname' );


var msgs = {
var msgs = {
第33行: 第32行:
// new: added 2012-09-19. Please translate.
// new: added 2012-09-19. Please translate.
// Use user language for i18n
// Use user language for i18n
'cat-a-lot-uncatpref': '移除 {{Uncategorized}}',
'cat-a-lot-uncatpref': '移除 {{Uncategorized}}',
'cat-a-lot-comment-label': wgULS('自定义编辑摘要','自定義編輯摘要'),
'cat-a-lot-comment-label': wgULS('自定义编辑摘要','自定義編輯摘要'),
'cat-a-lot-edit-question': wgULS('请输入编辑摘要:','請輸入編輯摘要:'),
'cat-a-lot-edit-question': wgULS('请输入编辑摘要:','請輸入編輯摘要:'),


// Progress
// Progress
'cat-a-lot-editing': 'Editing page',
'cat-a-lot-editing': 'Editing page',
'cat-a-lot-of': 'of ',
'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-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-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-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-all-done': 'All pages are processed.',
'cat-a-lot-done': 'Done!', // mw.msg("Feedback-close")
'cat-a-lot-done': 'Done!', // mw.msg("Feedback-close")
'cat-a-lot-added-cat': 'Added category $1',
'cat-a-lot-added-cat': 'Added category $1',
'cat-a-lot-copied-cat': 'Copied to category $1',
'cat-a-lot-copied-cat': 'Copied to category $1',
'cat-a-lot-moved-cat': 'Moved to category $1',
'cat-a-lot-moved-cat': 'Moved to category $1',
'cat-a-lot-removed-cat': 'Removed from category $1',
'cat-a-lot-removed-cat': 'Removed from category $1',
'cat-a-lot-return-to-page': 'Return to page',
'cat-a-lot-return-to-page': 'Return to page',


// as in 17 files selected
// as in 17 files selected
'cat-a-lot-files-selected': '{{PLURAL:$1|1=One file|$1 files}} selected.',
'cat-a-lot-files-selected': '{{PLURAL:$1|1=One file|$1 files}} selected.',
'cat-a-lot-pe_file': '$1 {{PLURAL:$1|page|pages}} of $2 affected',
'cat-a-lot-pe_file': '$1 {{PLURAL:$1|page|pages}} of $2 affected',


// Actions
// Actions
'cat-a-lot-copy': 'Copy',
'cat-a-lot-copy': 'Copy',
'cat-a-lot-move': 'Move',
'cat-a-lot-move': 'Move',
'cat-a-lot-add': 'Add',
'cat-a-lot-add': 'Add',
'cat-a-lot-remove-from-cat': 'Remove',
'cat-a-lot-remove-from-cat': 'Remove',
'cat-a-lot-overcat': wgULS('检测过度分类','檢測過度分類'),
'cat-a-lot-overcat': wgULS('检测过度分类','檢測過度分類'),
'cat-a-lot-enter-name': 'Enter category name',
'cat-a-lot-enter-name': 'Enter category name',
'cat-a-lot-select': 'Select',
'cat-a-lot-select': 'Select',
'cat-a-lot-all': 'all',
'cat-a-lot-all': 'all',
'cat-a-lot-none': 'none',
'cat-a-lot-none': 'none',


// Summaries (project language):
// Summaries (project language):
'cat-a-lot-summary-add': 'Adding [[Category:$1]]',
'cat-a-lot-summary-add': 'Adding [[Category:$1]]',
'cat-a-lot-summary-copy': 'Copying from [[Category:$1]] to [[Category:$2]]',
'cat-a-lot-summary-copy': 'Copying from [[Category:$1]] to [[Category:$2]]',
'cat-a-lot-summary-move': 'Moving from [[Category:$1]] to [[Category:$2]]',
'cat-a-lot-summary-move': 'Moving from [[Category:$1]] to [[Category:$2]]',
'cat-a-lot-summary-remove': 'Removing from [[Category:$1]]',
'cat-a-lot-summary-remove': 'Removing from [[Category:$1]]',
'cat-a-lot-prefix-summary': '使用Cat-a-lot小工具',
'cat-a-lot-prefix-summary': '使用Cat-a-lot小工具',
'cat-a-lot-using-summary': ''
'cat-a-lot-using-summary': ''
};
};
mw.messages.set( msgs );
mw.messages.set( msgs );


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


// There is only one Cat-a-lot on one page
// There is only one Cat-a-lot on one page
var $body, $container, $dataContainer, $searchInputContainer, $searchInput, $resultList, $markCounter, $selections,
var $body, $container, $dataContainer, $searchInputContainer, $searchInput, $resultList, $markCounter, $selections,
$selectFiles, $selectPages, $selectNone, $selectInvert, $head, $link, $overcat,
$selectFiles, $selectPages, $selectNone, $selectInvert, $head, $link, $overcat,
commonsURL = 'https://commons.wikimedia.org/w/index.php',
commonsURL = 'https://commons.wikimedia.org/w/index.php',
is_rtl = $( 'body' ).hasClass( 'rtl' ),
is_rtl = $( 'body' ).hasClass( 'rtl' ),
reCat, // localized category search regexp
reCat, // localized category search regexp
non,
non,
r; // result file count for overcat
r; // result file count for overcat


var CAL = mw.libs.catALot = {
var CAL = mw.libs.catALot = {
apiUrl: mw.util.wikiScript( 'api' ),
apiUrl: mw.util.wikiScript( 'api' ),
origin: '',
origin: '',
searchmode: false,
searchmode: false,
version: '4.77',
version: '4.77',
setHeight: 450,
setHeight: 450,
changeTag: 'Cat-a-lot',
changeTag: 'Cat-a-lot',


settings: {
settings: {
/* Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
/* Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
any items, but that contains links to other categories where stuff should be categorized. If you don't have
any items, but that contains links to other categories where stuff should be categorized. If you don't have
that concept on your wiki, set it to null. Use blanks, not underscores. */
that concept on your wiki, set it to null. Use blanks, not underscores. */
disambig_category: null, // Commons and EnWP
disambig_category: null, // Commons and EnWP
/* Any category in this category is deemed a (soft) redirect to some other category defined by a link
/* Any category in this category is deemed a (soft) redirect to some other category defined by a link
* to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null.
* to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null.
* If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered
* If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered
* a disambiguation category instead. */
* a disambiguation category instead. */
redir_category: null
redir_category: null
},
},


init: function () {
init: function () {
// TODO: better extern project support for possible change-tag? (needs currently change after init)
// TODO: better extern project support for possible change-tag? (needs currently change after init)
if ( project === 'commonswiki' ) { mw.messages.set( { 'cat-a-lot-using-summary': '' } ); } else { // Reset
if ( project === 'commonswiki' ) { mw.messages.set( { 'cat-a-lot-using-summary': '' } ); } else { // Reset
this.changeTag = '';
this.changeTag = '';
this.settings.redir_category = '';
this.settings.redir_category = '';
}
}


this._initSettings();
this._initSettings();
$body = $( document.body );
$body = $( document.body );
$container = $( '<div>' )
$container = $( '<div>' )
.attr( 'id', 'cat_a_lot' )
.attr( 'id', 'cat_a_lot' )
.appendTo( $body );
.appendTo( $body );
$dataContainer = $( '<div>' )
$dataContainer = $( '<div>' )
.attr( 'id', 'cat_a_lot_data' )
.attr( 'id', 'cat_a_lot_data' )
.appendTo( $container );
.appendTo( $container );
$searchInputContainer = $( '<div>' )
$searchInputContainer = $( '<div>' )
.appendTo( $dataContainer );
.appendTo( $dataContainer );
$searchInput = $( '<input>', {
$searchInput = $( '<input>', {
id: 'cat_a_lot_searchcatname',
id: 'cat_a_lot_searchcatname',
placeholder: msg( 'enter-name' ),
placeholder: msg( 'enter-name' ),
type: 'text'
type: 'text'
} )
} )
.appendTo( $searchInputContainer );
.appendTo( $searchInputContainer );
$resultList = $( '<div>' )
$resultList = $( '<div>' )
.attr( 'id', 'cat_a_lot_category_list' )
.attr( 'id', 'cat_a_lot_category_list' )
.appendTo( $dataContainer );
.appendTo( $dataContainer );
$markCounter = $( '<div>' )
$markCounter = $( '<div>' )
.attr( 'id', 'cat_a_lot_mark_counter' )
.attr( 'id', 'cat_a_lot_mark_counter' )
.appendTo( $dataContainer );
.appendTo( $dataContainer );
$selections = $( '<div>' )
$selections = $( '<div>' )
.attr( 'id', 'cat_a_lot_selections' )
.attr( 'id', 'cat_a_lot_selections' )
.text( msg( 'select' ) + ':' )
.text( msg( 'select' ) + ':' )
.appendTo( $dataContainer );
.appendTo( $dataContainer );
$head = $( '<div>' )
$head = $( '<div>' )
.attr( 'id', 'cat_a_lot_head' )
.attr( 'id', 'cat_a_lot_head' )
.appendTo( $container );
.appendTo( $container );
$link = $( '<a>' )
$link = $( '<a>' )
.attr( 'id', 'cat_a_lot_toggle' )
.attr( 'id', 'cat_a_lot_toggle' )
.text( 'Cat-a-lot' )
.text( 'Cat-a-lot' )
.appendTo( $head );
.appendTo( $head );


if ( this.origin && !non ) {
if ( this.origin && !non ) {
$overcat = $( '<a>' )
$overcat = $( '<a>' )
.attr( 'id', 'cat_a_lot_overcat' )
.attr( 'id', 'cat_a_lot_overcat' )
.html( msg( 'overcat' ) )
.html( msg( 'overcat' ) )
.on( 'click', function ( e ) {
.on( 'click', function ( e ) {
CAL.getOverCat( e );
CAL.getOverCat( e );
} )
} )
.insertBefore( $selections );
.insertBefore( $selections );
}
}


reCat = new RegExp( '^\\s*' + CAL.localizedRegex( 14, 'Category' ) + ':', '' );
reCat = new RegExp( '^\\s*' + CAL.localizedRegex( 'Category' ) + ':', '' );


$searchInput.on( 'keypress', function ( e ) {
$searchInput.on( 'keypress', function ( e ) {
if ( e.which === 13 ) {
if ( e.which === 13 ) {
CAL.updateCats( $.trim( $( this ).val().replace( /[\u200E\u200F\u202A-\u202E]/g, '' ) ) );
CAL.updateCats( $.trim( $( this ).val().replace( /[\u200E\u200F\u202A-\u202E]/g, '' ) ) );
mw.cookie.set( 'catAlot', CAL.currentCategory );
mw.cookie.set( 'catAlot', CAL.currentCategory );
}
}
} )
} )
.on( 'input keyup', function () {
.on( 'input keyup', function () {
var oldVal = this.value,
var oldVal = this.value,
newVal = oldVal.replace( reCat, '' );
newVal = oldVal.replace( reCat, '' );
if ( newVal !== oldVal ) { this.value = newVal; }
if ( newVal !== oldVal ) { this.value = newVal; }


if ( !newVal ) { mw.cookie.set( 'catAlot', null ); }
if ( !newVal ) { mw.cookie.set( 'catAlot', null ); }
} );
} );


function initAutocomplete() {
function initAutocomplete() {
if ( CAL.autoCompleteIsEnabled ) { return; }
if ( CAL.autoCompleteIsEnabled ) { return; }


CAL.autoCompleteIsEnabled = true;
CAL.autoCompleteIsEnabled = true;


if ( !$searchInput.val() && mw.cookie && mw.cookie.get( 'catAlot' ) ) { $searchInput.val( mw.cookie.get( 'catAlot' ) ); }
if ( !$searchInput.val() && mw.cookie && mw.cookie.get( 'catAlot' ) ) { $searchInput.val( mw.cookie.get( 'catAlot' ) ); }


$searchInput.autocomplete( {
$searchInput.autocomplete( {
source: function ( request, response ) {
source: function ( request, response ) {
CAL.doAPICall( {
CAL.doAPICall( {
action: 'opensearch',
action: 'opensearch',
search: request.term,
search: request.term,
redirects: 'resolve',
redirects: 'resolve',
namespace: 14
namespace: 14
}, function ( data ) {
}, function ( data ) {
if ( data[ 1 ] ) {
if ( data[ 1 ] ) {
response( $( data[ 1 ] )
response( $( data[ 1 ] )
.map( function ( index, item ) {
.map( function ( index, item ) {
return item.replace( reCat, '' );
return item.replace( reCat, '' );
} ) );
} ) );
}
}
} );
} );
},
},
open: function () {
open: function () {
$( '.ui-autocomplete' )
$( '.ui-autocomplete' )
.position( {
.position( {
my: is_rtl ? 'left bottom' : 'right bottom',
my: is_rtl ? 'left bottom' : 'right bottom',
at: is_rtl ? 'left top' : 'right top',
at: is_rtl ? 'left top' : 'right top',
of: $searchInput
of: $searchInput
} );
} );
},
},
appendTo: '#cat_a_lot'
appendTo: '#cat_a_lot'
} );
} );
}
}
$( '<a>' )
$( '<a>' )
.text( msg( 'all' ) )
.text( msg( 'all' ) )
.on( 'click', function () {
.on( 'click', function () {
CAL.toggleAll( true );
CAL.toggleAll( true );
} )
} )
.appendTo( $selections.append( ' ' ) );
.appendTo( $selections.append( ' ' ) );
if ( this.settings.editpages ) {
if ( this.settings.editpages ) {
$selectFiles = $( '<a>' )
$selectFiles = $( '<a>' )
.on( 'click', function () {
.on( 'click', function () {
CAL.toggleAll( 'files' );
CAL.toggleAll( 'files' );
} );
} );
$selectPages = $( '<a>' )
$selectPages = $( '<a>' )
.on( 'click', function () {
.on( 'click', function () {
CAL.toggleAll( 'pages' );
CAL.toggleAll( 'pages' );
} );
} );
$selections.append( $( '<span>' ).hide().append( [ ' / ', $selectFiles, ' / ', $selectPages ] ) );
$selections.append( $( '<span>' ).hide().append( [ ' / ', $selectFiles, ' / ', $selectPages ] ) );
}
}
$selectNone = $( '<a>' )
$selectNone = $( '<a>' )
.text( msg( 'none' ) )
.text( msg( 'none' ) )
.on( 'click', function () {
.on( 'click', function () {
CAL.toggleAll( false );
CAL.toggleAll( false );
} );
} );
$selectInvert = $( '<a>' )
$selectInvert = $( '<a>' )
.on( 'click', function () {
.on( 'click', function () {
CAL.toggleAll( null );
CAL.toggleAll( null );
} );
} );
$selections.append( [ ' • ', $selectNone, ' • ', $selectInvert,
$selections.append( [ ' • ', $selectNone, ' • ', $selectInvert,
$( '<div>' ).append( [
$( '<div>' ).append( [
$( '<label>' )
$( '<label>' )
.attr( {
.attr( {
'for': 'cat_a_lot_comment',
'for': 'cat_a_lot_comment',
style: 'line-height:1.5em;vertical-align:bottom'
style: 'line-height:1.5em;vertical-align:bottom'
} )
} )
.text( msg( 'comment-label' ) ),
.text( msg( 'comment-label' ) ),
$( '<input>' )
$( '<input>' )
.attr( {
.attr( {
id: 'cat_a_lot_comment',
id: 'cat_a_lot_comment',
type: 'checkbox'
type: 'checkbox'
} )
} )
] )
] )
] );
] );


$link
$link
.on( 'click', function () {
.on( 'click', function () {
$( this ).toggleClass( 'cat_a_lot_enabled' );
$( this ).toggleClass( 'cat_a_lot_enabled' );
// Load autocomplete on demand
// Load autocomplete on demand
initAutocomplete ();
initAutocomplete ();


if ( !CAL.executed ) {
if ( !CAL.executed ) {
$.when( mw.loader.using( [
$.when( mw.loader.using( [
'mediawiki.api',
'mediawiki.api',
'mediawiki.jqueryMsg'
'mediawiki.jqueryMsg'
] ), $.ready )
] ), $.ready )
.then( function () {
.then( function () {
return new mw.Api().loadMessagesIfMissing( [
return new mw.Api().loadMessagesIfMissing( [
'Cancel',
'Cancel',
'Categorytree-not-found',
'Categorytree-not-found',
// 'Checkuser-all',
// 'Checkuser-all',
// 'Code-field-select',
// 'Code-field-select',
// 'Export-addcat',
// 'Export-addcat',
'Filerevert-submit',
'Filerevert-submit',
'Mobile-frontend-return-to-page',
'Mobile-frontend-return-to-page',
'Ooui-selectfile-placeholder',
'Ooui-selectfile-placeholder',
// 'Visualeditor-clipboard-copy',
// 'Visualeditor-clipboard-copy',
'Wikieditor-loading',
'Wikieditor-loading',
'Prefs-files',
'Prefs-files',
'Categories',
'Categories',
'Checkbox-invert',
'Checkbox-invert',
'Centralnotice-remove', // 'Ooui-item-remove'
'Centralnotice-remove', // 'Ooui-item-remove'
'Apifeatureusage-warnings'
'Apifeatureusage-warnings'
] );
] );
} ).then( function () {
} ).then( function () {
CAL.run();
CAL.run();
} );
} );
} else { CAL.run(); }
} else { CAL.run(); }
} );
} );
this.localCatName = formattedNS[ 14 ] + ':';
this.localCatName = 'Category:';
mw.loader.using( 'mediawiki.cookie', function () { // Let catAlot stay open
mw.loader.using( 'mediawiki.cookie', function () { // Let catAlot stay open
var val = mw.cookie.get( 'catAlotO' );
var val = mw.cookie.get( 'catAlotO' );
if ( val && Number( val ) === ns ) { $link.click(); }
if ( val && Number( val ) === ns ) { $link.click(); }
}
}
);
);
},
},


getOverCat: function ( e ) {
getOverCat: function ( e ) {
var files = [];
var files = [];
r = 0; // result counter
r = 0; // result counter
if ( e ) {
if ( e ) {
e.preventDefault();
e.preventDefault();
this.files = this.getMarkedLabels(); // .toArray() not working
this.files = this.getMarkedLabels(); // .toArray() not working
for ( var f = 0; f < this.files.length; f++ ) { files.push( this.files[ f ] ); }
for ( var f = 0; f < this.files.length; f++ ) { files.push( this.files[ f ] ); }


}
}
if ( !files.length || !( files instanceof Array ) ) { return alert( mw.msg( 'Ooui-selectfile-placeholder' ) ); }
if ( !files.length || !( files instanceof Array ) ) { return alert( mw.msg( 'Ooui-selectfile-placeholder' ) ); }
this.files = files;
this.files = files;
mw.loader.using( [ 'jquery.spinner' ], function () {
mw.loader.using( [ 'jquery.spinner' ], function () {
$markCounter.injectSpinner( 'overcat' );
$markCounter.injectSpinner( 'overcat' );
CAL.getFileCats();
CAL.getFileCats();
} );
} );
},
},


getFileCats: function () {
getFileCats: function () {
var aLen = this.files.length;
var aLen = this.files.length;
var bLen = this.selectedLabels.length;
var bLen = this.selectedLabels.length;
var file = this.files[ aLen - 1 ][ 0 ];
var file = this.files[ aLen - 1 ][ 0 ];
$overcat.text( '…' + aLen + '\/' + bLen );
$overcat.text( '…' + aLen + '\/' + bLen );
if ( file ) {
if ( file ) {
this.doAPICall( {
this.doAPICall( {
prop: 'categories',
prop: 'categories',
titles: file
titles: file
}, this.checkFileCats
}, this.checkFileCats
);
);
}
}


},
},


checkFileCats: function ( data ) {
checkFileCats: function ( data ) {
var cc = 0; // current cat counter;
var cc = 0; // current cat counter;
var file = CAL.files.pop();
var file = CAL.files.pop();
if ( data.query && data.query.pages ) {
if ( data.query && data.query.pages ) {
$.each( data.query.pages, function ( id, page ) {
$.each( data.query.pages, function ( id, page ) {
if ( page.categories ) {
if ( page.categories ) {
var target = file[ 1 ].removeClass( 'cat_a_lot_selected' );
var target = file[ 1 ].removeClass( 'cat_a_lot_selected' );
$.each( page.categories, function ( c, cat ) {
$.each( page.categories, function ( c, cat ) {
var title = cat.title.replace( reCat, '' ),
var title = cat.title.replace( reCat, '' ),
color = 'orange',
color = 'orange',
mark = function ( kind ) { // kind of category
mark = function ( kind ) { // kind of category
// TODO: store data to use this for special remove function
// TODO: store data to use this for special remove function
if ( kind === 'sub' ) { color = 'green'; }
if ( kind === 'sub' ) { color = 'green'; }
var border = '3px dotted ';
var border = '3px dotted ';
if ( $.inArray( title, CAL[ kind + 'Cats' ] ) !== -1 ) {
if ( $.inArray( title, CAL[ kind + 'Cats' ] ) !== -1 ) {
cc++;
cc++;
target = target.parents( '.gallerybox' );
target = target.parents( '.gallerybox' );
target = target[ 0 ] ? target : file[ 1 ];
target = target[ 0 ] ? target : file[ 1 ];
target.css( {
target.css( {
border: border + color
border: border + color
} ).prop( 'title', msg( kind + '-cat' ) + title );
} ).prop( 'title', msg( kind + '-cat' ) + title );
color = 'red';
color = 'red';
return false;
return false;
}
}
};
};
mark( 'sub' );
mark( 'sub' );
return mark( 'parent' );
return mark( 'parent' );
} );
} );
if ( cc ) { r++; }
if ( cc ) { r++; }
}
}
} );
} );
} else { mw.log( 'Api-fail', file, data ); }
} else { mw.log( 'Api-fail', file, data ); }
if ( CAL.files[ 0 ] ) { return setTimeout( function () { CAL.getFileCats(); }, 100 ); } // Api has bad performance here, so we can get only each file separately
if ( CAL.files[ 0 ] ) { return setTimeout( function () { CAL.getFileCats(); }, 100 ); } // Api has bad performance here, so we can get only each file separately
$overcat.text( msg( 'pe_file', r, CAL.selectedLabels.length ) );
$overcat.text( msg( 'pe_file', r, CAL.selectedLabels.length ) );
$.removeSpinner( 'overcat' );
$.removeSpinner( 'overcat' );
},
},


findAllLabels: function ( searchmode ) {
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
// 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 ) {
switch ( searchmode ) {
case 'search':
case 'search':
this.labels = this.labels.add( $( 'table.searchResultImage' ).find( 'tr>td:eq(1)' ) );
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' ); }
if ( this.settings.editpages ) { this.labels = this.labels.add( 'div.mw-search-result-heading' ); }
break;
break;
case 'category':
case 'category':
this.findAllLabels( 'gallery' );
this.findAllLabels( 'gallery' );
this.labels = this.labels.add( $( '#mw-category-media' ).find( 'li[class!="gallerybox"]' ) );
this.labels = this.labels.add( $( '#mw-category-media' ).find( 'li[class!="gallerybox"]' ) );
if ( this.settings.editpages ) {
if ( this.settings.editpages ) {
this.pageLabels = $( '#mw-pages, #mw-subcategories' ).find( 'li' );
this.pageLabels = $( '#mw-pages, #mw-subcategories' ).find( 'li' );
// this.files = this.labels;
// this.files = this.labels;
this.labels = this.labels.add( this.pageLabels );
this.labels = this.labels.add( this.pageLabels );
}
}
break;
break;
case 'contribs':
case 'contribs':
this.labels = this.labels.add( $( 'ul.mw-contributions-list li' ) );
this.labels = this.labels.add( $( 'ul.mw-contributions-list li' ) );
// FIXME: Filter if !this.settings.editpages
// FIXME: Filter if !this.settings.editpages
break;
break;
case 'prefix':
case 'prefix':
this.labels = this.labels.add( $( 'ul.mw-prefixindex-list li' ) );
this.labels = this.labels.add( $( 'ul.mw-prefixindex-list li' ) );
break;
break;
case 'listfiles':
case 'listfiles':
// this.labels = this.labels.add( $( 'table.listfiles>tbody>tr' ).find( 'td:eq(1)' ) );
// this.labels = this.labels.add( $( 'table.listfiles>tbody>tr' ).find( 'td:eq(1)' ) );
this.labels = this.labels.add( $( '.TablePager_col_img_name' ) );
this.labels = this.labels.add( $( '.TablePager_col_img_name' ) );
break;
break;
case 'gallery':
case 'gallery':
// this.labels = this.labels.add( '.gallerybox' ); // TODO incombatible with GalleryDetails
// this.labels = this.labels.add( '.gallerybox' ); // TODO incombatible with GalleryDetails
this.labels = this.labels.add( '.gallerytext' );
this.labels = this.labels.add( '.gallerytext' );
break;
break;
}
}
},
},


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


/**
/**
* @brief Get title from selected pages
* @brief Get title from selected pages
* @return [array] touple of page title and $object
* @return [array] touple of page title and $object
*/
*/
getMarkedLabels: function () {
getMarkedLabels: function () {
this.selectedLabels = this.labels.filter( '.cat_a_lot_selected:visible' );
this.selectedLabels = this.labels.filter( '.cat_a_lot_selected:visible' );
return this.selectedLabels.map( function () {
return this.selectedLabels.map( function () {
var label = $( this ), file = label.find( 'a[title][class$="title"]' );
var label = $( this ), file = label.find( 'a[title][class$="title"]' );
file = file.length ? file : label.find( 'a[title]' );
file = file.length ? file : label.find( 'a[title]' );
var title = file.attr( 'title' ) ||
var title = file.attr( 'title' ) ||
CAL.getTitleFromLink( file ) ||
CAL.getTitleFromLink( file ) ||
CAL.getTitleFromLink( label.find( 'a' ) ) ||
CAL.getTitleFromLink( label.find( 'a' ) ) ||
CAL.getTitleFromLink( label.parent().find( 'a' ) ); // TODO needs optimization
CAL.getTitleFromLink( label.parent().find( 'a' ) ); // TODO needs optimization
if ( title.indexOf( formattedNS[ 2 ] + ':' ) ) { return [ [ title, label ] ]; }
if ( title.indexOf( 'User:' ) ) { return [ [ title, label ] ]; }
} );
} );
},
},


updateSelectionCounter: function () {
updateSelectionCounter: function () {
this.selectedLabels = this.labels.filter( '.cat_a_lot_selected:visible' );
this.selectedLabels = this.labels.filter( '.cat_a_lot_selected:visible' );
var first = $markCounter.is( ':hidden' );
var first = $markCounter.is( ':hidden' );
$markCounter
$markCounter
.html( msg( 'files-selected', this.selectedLabels.length ) )
.html( msg( 'files-selected', this.selectedLabels.length ) )
.show();
.show();
if ( first && !$dataContainer.is( ':hidden' ) ) { // Workaround to fix position glitch
if ( first && !$dataContainer.is( ':hidden' ) ) { // Workaround to fix position glitch
first = $markCounter.innerHeight();
first = $markCounter.innerHeight();
$container
$container
.offset( { top: $container.offset().top - first } )
.offset( { top: $container.offset().top - first } )
.height( $container.height() + first );
.height( $container.height() + first );
$( window ).on( 'beforeunload', function () {
$( window ).on( 'beforeunload', function () {
if ( CAL.labels.filter( '.cat_a_lot_selected:visible' )[ 0 ] ) { return 'You have pages selected!'; } // There is a default message in the browser
if ( CAL.labels.filter( '.cat_a_lot_selected:visible' )[ 0 ] ) { return 'You have pages selected!'; } // There is a default message in the browser
} );
} );
}
}
},
},


makeClickable: function () {
makeClickable: function () {
this.labels = $();
this.labels = $();
this.pageLabels = $(); // only for distinct all selections
this.pageLabels = $(); // only for distinct all selections
this.findAllLabels( this.searchmode );
this.findAllLabels( this.searchmode );
this.labels.catALotShiftClick( function () {
this.labels.catALotShiftClick( function () {
CAL.updateSelectionCounter();
CAL.updateSelectionCounter();
} )
} )
.addClass( 'cat_a_lot_label' );
.addClass( 'cat_a_lot_label' );
},
},


toggleAll: function ( select ) {
toggleAll: function ( select ) {
if ( typeof select === 'string' && select === 'files' ) {
if ( typeof select === 'string' && select === 'files' ) {
if ( this.pageLabels[ 0 ] ) // pages remain unchanged
if ( this.pageLabels[ 0 ] ) // pages remain unchanged
{ this.pageLabels.toggleClass( 'cat_a_lot_selected' ); }
{ this.pageLabels.toggleClass( 'cat_a_lot_selected' ); }
this.labels.toggleClass( 'cat_a_lot_selected' );
this.labels.toggleClass( 'cat_a_lot_selected' );
} else if ( typeof select === 'string' && select === 'pages' ) {
} else if ( typeof select === 'string' && select === 'pages' ) {
if ( this.pageLabels[ 0 ] ) // files remain unchanged
if ( this.pageLabels[ 0 ] ) // files remain unchanged
{ this.pageLabels.toggleClass( 'cat_a_lot_selected' ); }
{ this.pageLabels.toggleClass( 'cat_a_lot_selected' ); }
} else {
} else {
// invert / none / all
// invert / none / all
this.labels.toggleClass( 'cat_a_lot_selected', select );
this.labels.toggleClass( 'cat_a_lot_selected', select );
}
}
this.updateSelectionCounter();
this.updateSelectionCounter();
},
},


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


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


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


} );
} );
},
},


getParentCats: function () {
getParentCats: function () {
var data = {
var data = {
prop: 'categories',
prop: 'categories',
titles: 'Category:' + this.currentCategory
titles: 'Category:' + this.currentCategory
};
};
this.doAPICall( data, function ( result ) {
this.doAPICall( data, function ( result ) {
CAL.parentCats = [];
CAL.parentCats = [];
var cats,
var cats,
pages = result.query.pages,
pages = result.query.pages,
table = $( '<table>' );
table = $( '<table>' );


if ( pages[ -1 ] && pages[ -1 ].missing === '' ) {
if ( pages[ -1 ] && pages[ -1 ].missing === '' ) {
$resultList.html( '<span id="cat_a_lot_no_found">' + mw.msg( 'Categorytree-not-found', this.currentCategory ) + '</span>' );
$resultList.html( '<span id="cat_a_lot_no_found">' + mw.msg( 'Categorytree-not-found', this.currentCategory ) + '</span>' );
document.body.style.cursor = 'auto';
document.body.style.cursor = 'auto';
CAL.createCatLinks( '→', [ CAL.currentCategory ], table );
CAL.createCatLinks( '→', [ CAL.currentCategory ], table );
$resultList.append( table );
$resultList.append( table );
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 ) { cats = pages[ id ].categories || []; }
for ( var id in pages ) { cats = pages[ id ].categories || []; }


for ( var i = 0; i < cats.length; i++ ) { CAL.parentCats.push( cats[ i ].title.replace( /^[^:]+:/, '' ) ); }
for ( var i = 0; i < cats.length; i++ ) { CAL.parentCats.push( cats[ i ].title.replace( /^[^:]+:/, '' ) ); }


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


} );
} );
},
},


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


var regexName = '';
var regexName = '';
for ( var i = 0; i < name.length; i++ ) {
for ( var i = 0; i < name.length; i++ ) {
var ii = name[ i ];
var ii = name[ i ];
var ll = ii.toLowerCase();
var ll = ii.toLowerCase();
var ul = ii.toUpperCase();
var ul = ii.toUpperCase();
regexName += ( ll === ul ) ? ii : '[' + ll + ul + ']';
regexName += ll === ul ? ii : '[' + ll + ul + ']';
}
}
return regexName.replace( /([\\\^\$\.\?\*\+\(\)])/g, '\\$1' )
return regexName.replace( /([\\\^\$\.\?\*\+\(\)])/g, '\\$1' )
.replace( wikiTextBlankRE, wikiTextBlank );
.replace( wikiTextBlankRE, wikiTextBlank );
};
};


fallback = fallback.toLowerCase();
fallback = fallback.toLowerCase();
var canonical = formattedNS[ namespaceNumber ].toLowerCase();
var canonical = 'category';
var RegexString = createRegexStr( canonical );
var RegexString = createRegexStr( canonical );
if ( fallback && canonical !== fallback ) { RegexString += '|' + createRegexStr( fallback ); }
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 ); } }
for ( var catName in nsIDs ) { if ( typeof catName === 'string' && catName.toLowerCase() !== canonical && catName.toLowerCase() !== fallback && nsIDs[ catName ] === ns ) { RegexString += '|' + createRegexStr( catName ); } }


return ( '(?:' + RegexString + ')' );
return '(?:' + RegexString + ')';
},
},


regexCatBuilder: function ( category ) {
regexCatBuilder: function ( category ) {
var catname = this.localizedRegex( 14, 'Category' );
var catname = this.localizedRegex( 'Category' );
// 繁简转换
if ( window.CatALotSourceCat && window.CatALotSourceCat.includes( category.replace(/_/g, ' ') ) ) {
category = window.CatALotSourceCat;
} else { category = [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_]+|[\s_]+$/g, '' );
category = category.map(ele => ele.replace( /^[\s_]+|[\s_]+$/g, '' ));


// escape regexp metacharacters (= any ASCII punctuation except _)
// escape regexp metacharacters (= any ASCII punctuation except _)
category = mw.util.escapeRegExp( category );
category = category.map(ele => mw.util.escapeRegExp( ele ));


// 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.map(ele => ele.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):
// 繁简转换
// XXX: the first capturing parens are assumed to match the sortkey, if present, including the | but excluding the ]]
if ( window.CatALotSourceCat && window.CatALotSourceCat.includes(category) ) {
return new RegExp( '\\[\\[[\\s_]*' + catname + '[\\s_]*:[\\s_]*(?:' + category.join('|') + ')[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]\\s*', 'gi' );
category = '(?:' + window.CatALotSourceCat.join('|') + ')';
},
}


getContent: function ( page, targetcat, mode ) {
// Compile it into a RegExp that matches MediaWiki category syntax (yeah, it looks ugly):
if ( !this.cancelled ) {
// XXX: the first capturing parens are assumed to match the sortkey, if present, including the | but excluding the ]]
this.doAPICall( {
return new RegExp( '\\[\\[[\\s_]*' + catname + '[\\s_]*:[\\s_]*' + category + '[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]\\s*', 'gi' );
curtimestamp: 1,
},
// meta: 'tokens',
prop: 'revisions',
rvprop: 'content|timestamp',
titles: page[ 0 ]
}, function ( result ) {
CAL.editCategories( result, page, targetcat, mode );
} );
}


},
getContent: function ( page, targetcat, mode ) {
if ( !this.cancelled ) {
this.doAPICall( {
curtimestamp: 1,
// meta: 'tokens',
prop: 'revisions',
rvprop: 'content|timestamp',
titles: page[ 0 ]
}, function ( result ) {
CAL.editCategories( result, page, targetcat, mode );
} );
}


getTargetCat: function ( pages, targetcat, mode ) {
},
if ( !this.cancelled ) {
this.doAPICall( {
meta: 'tokens',
prop: 'categories|categoryinfo',
titles: 'Category:' + targetcat
}, function ( result ) {
if ( !result || !result.query ) { return; }
CAL.edittoken = result.query.tokens.csrftoken;
result = CAL._getPageQuery( result );
CAL.checkTargetCat( result );
for ( var i = 0; i < pages.length; i++ ) { CAL.getContent( pages[ i ], targetcat, mode ); }


} );
getTargetCat: function ( pages, targetcat, mode ) {
}
if ( !this.cancelled ) {
this.doAPICall( {
meta: 'tokens',
prop: 'categories|categoryinfo',
titles: 'Category:' + targetcat
}, function ( result ) {
if ( !result || !result.query ) { return; }
CAL.edittoken = result.query.tokens.csrftoken;
result = CAL._getPageQuery( result );
CAL.checkTargetCat( result );
for ( var i = 0; i < pages.length; i++ ) { CAL.getContent( pages[ i ], targetcat, mode ); }


},
} );
}


checkTargetCat: function ( page ) {
},
var is_dab = false; // disambiguation
var is_redir = typeof page.redirect === 'string'; // Hard redirect?
if ( typeof page.missing === 'string' ) { return alert( mw.msg( 'Apifeatureusage-warnings', mw.msg( 'Categorytree-not-found', page.title ) ) ); }
var cats = page.categories;
this.is_hidden = page.categoryinfo && typeof page.categoryinfo.hidden === 'string';


if ( !is_redir && cats && ( CAL.disambig_category || CAL.redir_category ) ) {
checkTargetCat: function ( page ) {
for ( var c = 0; c < cats.length; c++ ) {
var is_dab = false; // disambiguation
var cat = cats[ c ].title;
var is_redir = typeof page.redirect === 'string'; // Hard redirect?
if ( cat ) { // Strip namespace prefix
if ( typeof page.missing === 'string' ) { return alert( mw.msg( 'Apifeatureusage-warnings', mw.msg( 'Categorytree-not-found', page.title ) ) ); }
cat = cat.substring( cat.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
var cats = page.categories;
if ( cat === CAL.disambig_category ) {
this.is_hidden = page.categoryinfo && typeof page.categoryinfo.hidden === 'string';
is_dab = true; break;
} else if ( cat === CAL.redir_category ) {
is_redir = true; break;
}
}
}
}


if ( !is_redir && cats && ( CAL.disambig_category || CAL.redir_category ) ) {
if ( !is_redir && !is_dab ) { return; }
alert( mw.msg( 'Apifeatureusage-warnings', page.title + ' is a ' + CAL.disambig_category ) );
for ( var c = 0; c < cats.length; c++ ) {
},
var cat = cats[ c ].title;
if ( cat ) { // Strip namespace prefix
cat = cat.substring( cat.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
if ( cat === CAL.disambig_category ) {
is_dab = true; break;
} else if ( cat === CAL.redir_category ) {
is_redir = true; break;
}
}
}
}


// Remove {{Uncategorized}} (also with comment). No need to replace it with anything.
if ( !is_redir && !is_dab ) { return; }
removeUncat: function ( text ) {
alert( mw.msg( 'Apifeatureusage-warnings', page.title + ' is a ' + CAL.disambig_category ) );
return this.settings.uncat ? text.replace( /\{\{\s*[Uu]ncategorized\s*[^}]*\}\}\s*(<!--.*?-->\s*)?/, '' ) : text;
},
},


doCleanup: function ( text ) {
// Remove {{Uncategorized}} (also with comment). No need to replace it with anything.
return this.settings.docleanup ? text.replace( /\{\{\s*[Cc]heck categories\s*(\|?.*?)\}\}/, '' ) : text;
removeUncat: function ( text ) {
},
return ( this.settings.uncat ? text.replace( /\{\{\s*[Uu]ncategorized\s*[^}]*\}\}\s*(<!--.*?-->\s*)?/, '' ) : text );
},


editCategories: function ( result, file, targetcat, mode ) {
doCleanup: function ( text ) {
if ( !result || !result.query ) {
return ( this.settings.docleanup ? text.replace( /\{\{\s*[Cc]heck categories\s*(\|?.*?)\}\}/, '' ) : text );
// Happens on unstable wifi connections..
},
this.connectionError.push( file[ 0 ] );
this.updateCounter();
return;
}
var otext,
timestamp,
page = CAL._getPageQuery( result );
if ( page.ns === 2 ) { return; }
var id = page.revisions[ 0 ],
catNS = this.localCatName; // canonical cat-name


this.starttimestamp = result.curtimestamp;
editCategories: function ( result, file, targetcat, mode ) {
otext = id[ '*' ];
if ( !result || !result.query ) {
timestamp = id.timestamp;
// Happens on unstable wifi connections..
this.connectionError.push( file[ 0 ] );
this.updateCounter();
return;
}
var otext,
timestamp,
page = CAL._getPageQuery( result );
if ( page.ns === 2 ) { return; }
var id = page.revisions[ 0 ],
catNS = this.localCatName; // canonical cat-name


var sourcecat = this.origin;
this.starttimestamp = result.curtimestamp;
// Check if that file is already in that category
otext = id[ '*' ];
if ( mode !== 'remove' && this.regexCatBuilder( targetcat ).test( otext ) ) {
timestamp = id.timestamp;
// If the new cat is already there, just remove the old one
if ( mode === 'move' ) {
mode = 'remove';
targetcat = sourcecat;
} else {
this.alreadyThere.push( file[ 0 ] );
this.updateCounter();
return;
}
}


// Text modification (following 3 functions are partialy taken from HotCat)
var sourcecat = this.origin;
var wikiTextBlankOrBidi = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200B\\u200E\\u200F\\u2028-\\u202F\\u205F\\u3000]*';
// Check if that file is already in that category
// Whitespace regexp for handling whitespace between link components. Including the horizontal tab, but not \n\r\f\v:
if ( mode !== 'remove' && this.regexCatBuilder( targetcat ).test( otext ) ) {
// a link must be on one single line.
// If the new cat is already there, just remove the old one
// MediaWiki also removes Unicode bidi override characters in page titles (and namespace names) completely.
if ( mode === 'move' ) {
// This is *not* handled, as it would require us to allow any of [\u200E\u200F\u202A-\u202E] between any two
mode = 'remove';
// characters inside a category link. It _could_ be done though... We _do_ handle strange spaces, including the
targetcat = sourcecat;
// zero-width space \u200B, and bidi overrides between the components of a category link (adjacent to the colon,
} else {
// or adjacent to and inside of "[[" and "]]").
this.alreadyThere.push( file[ 0 ] );
var findCatsRE = new RegExp( '\\[\\[' + wikiTextBlankOrBidi + this.localizedRegex( 'Category' ) + wikiTextBlankOrBidi + ':[^\\]]+\\]\\]', 'g' );
this.updateCounter();
return;
}
}


function replaceByBlanks( match ) {
// Text modification (following 3 functions are partialy taken from HotCat)
return match.replace( /(\s|\S)/g, ' ' ); // /./ doesn't match linebreaks. /(\s|\S)/ does.
var wikiTextBlankOrBidi = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200B\\u200E\\u200F\\u2028-\\u202F\\u205F\\u3000]*';
}
// Whitespace regexp for handling whitespace between link components. Including the horizontal tab, but not \n\r\f\v:
// a link must be on one single line.
// MediaWiki also removes Unicode bidi override characters in page titles (and namespace names) completely.
// This is *not* handled, as it would require us to allow any of [\u200E\u200F\u202A-\u202E] between any two
// characters inside a category link. It _could_ be done though... We _do_ handle strange spaces, including the
// zero-width space \u200B, and bidi overrides between the components of a category link (adjacent to the colon,
// or adjacent to and inside of "[[" and "]]").
var findCatsRE = new RegExp( '\\[\\[' + wikiTextBlankOrBidi + this.localizedRegex( 14, 'Category' ) + wikiTextBlankOrBidi + ':[^\\]]+\\]\\]', 'g' );


function replaceByBlanks( match ) {
function find_insertionpoint( wikitext ) {
var copiedtext = wikitext
return match.replace( /(\s|\S)/g, ' ' ); // /./ doesn't match linebreaks. /(\s|\S)/ does.
.replace( /<!--(\s|\S)*?-->/g, replaceByBlanks )
}
.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, replaceByBlanks );
// Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element".
var index = -1;
findCatsRE.lastIndex = 0;
while ( findCatsRE.exec( copiedtext ) !== null ) { index = findCatsRE.lastIndex; }


return index;
function find_insertionpoint( wikitext ) {
}
var copiedtext = wikitext
.replace( /<!--(\s|\S)*?-->/g, replaceByBlanks )
.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, replaceByBlanks );
// Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element".
var index = -1;
findCatsRE.lastIndex = 0;
while ( findCatsRE.exec( copiedtext ) !== null ) { index = findCatsRE.lastIndex; }


/**
return index;
}

/**
* @brief Adds the new Category by searching the right insert point,
* @brief Adds the new Category by searching the right insert point,
* if there is text after the category section
* if there is text after the category section
* @param [string] wikitext
* @param [string] wikitext
* @param [string] toAdd
* @param [string] toAdd
* @return Return wikitext
* @return Return wikitext
*/
*/
function addCategory( wikitext, toAdd ) {
function addCategory( wikitext, toAdd ) {
if ( toAdd && toAdd[ 0 ] ) {
if ( toAdd && toAdd[ 0 ] ) {
// TODO: support sort key
// TODO: support sort key
var cat_point = find_insertionpoint( wikitext ); // Position of last category
var cat_point = find_insertionpoint( wikitext ); // Position of last category
var newcatstring = '[[' + catNS + toAdd + ']]';
var newcatstring = '[[' + catNS + toAdd + ']]';
if ( cat_point > -1 ) {
if ( cat_point > -1 ) {
var suffix = wikitext.substring( cat_point );
var suffix = wikitext.substring( cat_point );
wikitext = wikitext.substring( 0, cat_point ) + ( cat_point ? '\n' : '' ) + newcatstring;
wikitext = wikitext.substring( 0, cat_point ) + ( cat_point ? '\n' : '' ) + newcatstring;
if ( suffix[ 0 ] && suffix.substr( 0, 1 ) !== '\n' ) { wikitext += '\n'; }
if ( suffix[ 0 ] && suffix.substr( 0, 1 ) !== '\n' ) { wikitext += '\n'; }
wikitext += suffix;
wikitext += suffix;
} else {
} else {
if ( wikitext[ 0 ] && wikitext.substr( wikitext.length - 1, 1 ) !== '\n' ) { wikitext += '\n'; }
if ( wikitext[ 0 ] && wikitext.substr( wikitext.length - 1, 1 ) !== '\n' ) { wikitext += '\n'; }


wikitext += ( wikitext[ 0 ] ? '\n' : '' ) + newcatstring;
wikitext += ( wikitext[ 0 ] ? '\n' : '' ) + newcatstring;
}
}
}
}
return wikitext;
return wikitext;
}
}
// End HotCat functions
// End HotCat functions


var text = otext,
var text = otext,
arr = is_rtl ? '\u2190' : '\u2192', // left and right arrows. Don't use ← and → in the code.
arr = is_rtl ? '\u2190' : '\u2192', // left and right arrows. Don't use ← and → in the code.
sumCmt, // summary comment
sumCmt, // summary comment
sumCmtShort;
sumCmtShort;
// Fix text
// Fix text
switch ( mode ) {
switch ( mode ) {
case 'add':
case 'add':
text = addCategory( text, targetcat );
text = addCategory( text, targetcat );
sumCmt = msg( 'summary-add' ).replace( /\$1/g, targetcat );
sumCmt = msg( 'summary-add' ).replace( /\$1/g, targetcat );
sumCmtShort = '+[[' + catNS + targetcat + ']]';
sumCmtShort = '+[[' + catNS + targetcat + ']]';
break;
break;
case 'copy':
case 'copy':
text = text.replace( this.regexCatBuilder( sourcecat ), '[[' + catNS + sourcecat + '$1]]\n[[' + catNS + targetcat + '$1]]\n' );
text = text.replace( this.regexCatBuilder( sourcecat ), '[[' + catNS + sourcecat + '$1]]\n[[' + catNS + targetcat + '$1]]\n' );
sumCmt = msg( 'summary-copy' ).replace( /\$1/g, sourcecat ).replace( /\$2/g, targetcat );
sumCmt = msg( 'summary-copy' ).replace( /\$1/g, sourcecat ).replace( /\$2/g, targetcat );
sumCmtShort = '+[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]';
sumCmtShort = '+[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]';
// If category is added through template:
// If category is added through template:
if ( otext === text ) { text = addCategory( text, targetcat ); }
if ( otext === text ) { text = addCategory( text, targetcat ); }


break;
break;
case 'move':
case 'move':
text = text.replace( this.regexCatBuilder( sourcecat ), '[[' + catNS + targetcat + '$1]]\n' );
text = text.replace( this.regexCatBuilder( sourcecat ), '[[' + catNS + targetcat + '$1]]\n' );
sumCmt = msg( 'summary-move' ).replace( /\$1/g, sourcecat ).replace( /\$2/g, targetcat );
sumCmt = msg( 'summary-move' ).replace( /\$1/g, sourcecat ).replace( /\$2/g, targetcat );
sumCmtShort = '±[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]';
sumCmtShort = '±[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]';
break;
break;
case 'remove':
case 'remove':
text = text.replace( this.regexCatBuilder( targetcat ), '' );
text = text.replace( this.regexCatBuilder( targetcat ), '' );
sumCmt = msg( 'summary-remove' ).replace( /\$1/g, targetcat );
sumCmt = msg( 'summary-remove' ).replace( /\$1/g, targetcat );
sumCmtShort = '-[[' + catNS + targetcat + ']]';
sumCmtShort = '-[[' + catNS + targetcat + ']]';
break;
break;
}
}


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


// Remove {{uncat}} after we checked whether we changed the text successfully.
// Remove {{uncat}} after we checked whether we changed the text successfully.
// Otherwise we might fail to do the changes, but still replace {{uncat}}
// Otherwise we might fail to do the changes, but still replace {{uncat}}
if ( mode !== 'remove' && ( !non || userGrp.indexOf( 'autoconfirmed' ) > -1 ) ) {
if ( mode !== 'remove' && ( !non || userGrp.indexOf( 'autoconfirmed' ) > -1 ) ) {
if ( !this.is_hidden ) {
if ( !this.is_hidden ) {
text = this.removeUncat( text );
text = this.removeUncat( text );
if ( text.length !== otext.length ) { sumCmt += '; ' + msg( 'uncatpref' ); }
if ( text.length !== otext.length ) { sumCmt += '; ' + msg( 'uncatpref' ); }
}
}
text = this.doCleanup( text );
text = this.doCleanup( text );
}
}


sumCmt += this.summary ? ' ' + this.summary : '';
sumCmt += this.summary ? ' ' + this.summary : '';


var preM = msg( 'prefix-summary' );
var preM = msg( 'prefix-summary' );
var usgM = msg( 'using-summary' );
var usgM = msg( 'using-summary' );
// Try shorten summary
// Try shorten summary
if ( preM || usgM ) {
if ( preM || usgM ) {
sumCmt = ( sumCmt.length > 250 - preM.length - usgM.length ) ?
sumCmt = sumCmt.length > 250 - preM.length - usgM.length ?
sumCmt + ' (CatAlot)' : preM + sumCmt + usgM;
sumCmt + ' (CatAlot)' : preM + sumCmt + usgM;
}
}


if ( sumCmt.length > 254 ) // Try short summary
if ( sumCmt.length > 254 ) // Try short summary
{ sumCmt = sumCmtShort; }
{ sumCmt = sumCmtShort; }


var data = {
var data = {
action: 'edit',
action: 'edit',
assert: 'user',
assert: 'user',
summary: sumCmt,
summary: sumCmt,
title: file[ 0 ],
title: file[ 0 ],
text: text,
text: text,
bot: true,
bot: true,
starttimestamp: this.starttimestamp,
starttimestamp: this.starttimestamp,
basetimestamp: timestamp,
basetimestamp: timestamp,
watchlist: this.settings.watchlist,
watchlist: this.settings.watchlist,
minor: this.settings.minor,
minor: this.settings.minor,
tags: this.changeTag,
tags: this.changeTag,
token: this.edittoken
token: this.edittoken
};
};


this.doAPICall( data, function ( r ) {
this.doAPICall( data, function ( r ) {
delete CAL.XHR[ file[ 0 ] ];
delete CAL.XHR[ file[ 0 ] ];
return CAL.updateUndoCounter( r );
return CAL.updateUndoCounter( r );
} );
} );
this.markAsDone( file[ 1 ], mode, targetcat );
this.markAsDone( file[ 1 ], mode, targetcat );
},
},


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


updateUndoCounter: function ( r ) {
updateUndoCounter: function ( r ) {
this.updateCounter();
this.updateCounter();
if ( !r.edit || r.edit.result !== 'Success' ) { return; }
if ( !r.edit || r.edit.result !== 'Success' ) { return; }
r = r.edit;
r = r.edit;


this.undoList.push( {
this.undoList.push( {
title: r.title,
title: r.title,
id: r.newrevid,
id: r.newrevid,
timestamp: r.newtimestamp
timestamp: r.newtimestamp
} );
} );
},
},


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


displayResult: function () {
displayResult: function () {
document.body.style.cursor = 'auto';
document.body.style.cursor = 'auto';
$.removeSpinner( 'fb-dialog' );
$.removeSpinner( 'fb-dialog' );
this.progressDialog.parent()
this.progressDialog.parent()
.addClass( 'cat_a_lot_done' )
.addClass( 'cat_a_lot_done' )
.find( '.ui-dialog-buttonpane button span' ).eq( 0 )
.find( '.ui-dialog-buttonpane button span' ).eq( 0 )
.text( mw.msg( 'cat-a-lot-return-to-page' ) );
.text( mw.msg( 'cat-a-lot-return-to-page' ) );
var rep = this.domCounter.parent()
var rep = this.domCounter.parent()
.height( 'auto' )
.height( 'auto' )
.html( '<h3>' + msg( 'done' ) + '</h3>' )
.html( '<h3>' + msg( 'done' ) + '</h3>' )
.append( msg( 'all-done' ) + '<br>' );
.append( msg( 'all-done' ) + '<br>' );
if ( this.alreadyThere.length ) {
if ( this.alreadyThere.length ) {
rep.append( '<h5>' + msg( 'skipped-already', this.alreadyThere.length ) + '</h5>' )
rep.append( '<h5>' + msg( 'skipped-already', this.alreadyThere.length ) + '</h5>' )
.append( this.alreadyThere.join( '<br>' ) );
.append( this.alreadyThere.join( '<br>' ) );
}
}


if ( this.notFound.length ) {
if ( this.notFound.length ) {
rep.append( '<h5>' + msg( 'skipped-not-found', this.notFound.length ) + '</h5>' )
rep.append( '<h5>' + msg( 'skipped-not-found', this.notFound.length ) + '</h5>' )
.append( this.notFound.join( '<br>' ) );
.append( this.notFound.join( '<br>' ) );
}
}


if ( this.connectionError.length ) {
if ( this.connectionError.length ) {
rep.append( '<h5>' + msg( 'skipped-server', this.connectionError.length ) + '</h5>' )
rep.append( '<h5>' + msg( 'skipped-server', this.connectionError.length ) + '</h5>' )
.append( this.connectionError.join( '<br>' ) );
.append( this.connectionError.join( '<br>' ) );
}
}


},
},


/**
/**
* @brief set parameters for API call,
* @brief set parameters for API call,
* convert targetcat to string, get selected pages/files
* convert targetcat to string, get selected pages/files
* @param [dom object] targetcat with data
* @param [dom object] targetcat with data
* @param [string] mode action
* @param [string] mode action
* @return Return API call getTargetCat with pages
* @return Return API call getTargetCat with pages
*/
*/
doSomething: function ( targetcat, mode ) {
doSomething: function ( targetcat, mode ) {
var pages = this.getMarkedLabels();
var pages = this.getMarkedLabels();
if ( !pages.length ) { return alert( mw.msg( 'Ooui-selectfile-placeholder' ) ); }
if ( !pages.length ) { return alert( mw.msg( 'Ooui-selectfile-placeholder' ) ); }
targetcat = $( targetcat ).closest( 'tr' ).data( 'cat' );
targetcat = $( targetcat ).closest( 'tr' ).data( 'cat' );


this.notFound = [];
this.notFound = [];
this.alreadyThere = [];
this.alreadyThere = [];
this.connectionError = [];
this.connectionError = [];
this.counterCurrent = 1;
this.counterCurrent = 1;
this.counterNeeded = pages.length;
this.counterNeeded = pages.length;
this.undoList = [];
this.undoList = [];
this.XHR = {};
this.XHR = {};
this.cancelled = 0;
this.cancelled = 0;
this.summary = '';
this.summary = '';


if ( $( '#cat_a_lot_comment' ).prop( 'checked' ) ) { this.summary = window.prompt( msg( 'edit-question' ), '' ); } // TODO custom pre-value
if ( $( '#cat_a_lot_comment' ).prop( 'checked' ) ) { this.summary = window.prompt( msg( 'edit-question' ), '' ); } // TODO custom pre-value
if ( this.summary !== null ) {
if ( this.summary !== null ) {
mw.loader.using( 'jquery.spinner', function () {
mw.loader.using( 'jquery.spinner', function () {
CAL.showProgress();
CAL.showProgress();
CAL.getTargetCat( pages, targetcat, mode );
CAL.getTargetCat( pages, targetcat, mode );
} );
} );
}
}


},
},


doAPICall: function ( params, callback ) {
doAPICall: function ( params, callback ) {
params = $.extend( {
params = $.extend( {
action: 'query',
action: 'query',
format: 'json'
format: 'json'
}, params );
}, params );


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


if ( params.action === 'edit' && !CAL.cancelled ) { CAL.XHR[ params.title ] = xhr; }
if ( params.action === 'edit' && !CAL.cancelled ) { CAL.XHR[ params.title ] = xhr; }
};
};
doCall();
doCall();
},
},


createCatLinks: function ( symbol, list, table ) {
createCatLinks: function ( symbol, list, table ) {
list.sort();
list.sort();
var button = this.settings.button ? 1 : 0;
var button = this.settings.button ? 1 : 0;
for ( var c = 0; c < list.length; c++ ) {
for ( var c = 0; c < list.length; c++ ) {
var $tr = $( '<tr>' ),
var $tr = $( '<tr>' ),
$link = $( '<a>', {
$link = $( '<a>', {
href: mw.util.getUrl( CAL.localCatName + list[ c ] ),
href: mw.util.getUrl( CAL.localCatName + list[ c ] ),
text: list[ c ]
text: list[ c ]
} ),
} ),
$buttons = [];
$buttons = [];
$tr.data( 'cat', list[ c ] );
$tr.data( 'cat', list[ c ] );
$link.on( 'click', function ( e ) {
$link.on( 'click', function ( e ) {
if ( !e.ctrlKey ) {
if ( !e.ctrlKey ) {
e.preventDefault();
e.preventDefault();
CAL.updateCats( $( this ).closest( 'tr' ).data( 'cat' ) );
CAL.updateCats( $( this ).closest( 'tr' ).data( 'cat' ) );
}
}
} );
} );


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


$buttons.push( $( '<a>' )
$buttons.push( $( '<a>' )
.text( msg( 'remove-from-cat' ) )
.text( msg( 'remove-from-cat' ) )
.on( 'click', function () {
.on( 'click', function () {
CAL.doSomething( this, 'remove' );
CAL.doSomething( this, 'remove' );
} )
} )
.addClass( 'cat_a_lot_move' )
.addClass( 'cat_a_lot_move' )
);
);
if ( button ) {
if ( button ) {
$buttons.slice( -1 )[ 0 ].button( {
$buttons.slice( -1 )[ 0 ].button( {
icons: { primary: 'ui-icon-minusthick' },
icons: { primary: 'ui-icon-minusthick' },
showLabel: false,
showLabel: false,
text: false
text: false
} );
} );
}
}


if ( this.origin ) {
if ( this.origin ) {
// Can't move to source category
// Can't move to source category
if ( list[ c ] !== this.origin ) {
if ( list[ c ] !== this.origin ) {
$buttons.push( $( '<a>' )
$buttons.push( $( '<a>' )
.text( msg( 'move' ) )
.text( msg( 'move' ) )
.on( 'click', function () {
.on( 'click', function () {
CAL.doSomething( this, 'move' );
CAL.doSomething( this, 'move' );
} )
} )
.addClass( 'cat_a_lot_move' )
.addClass( 'cat_a_lot_move' )
);
);
if ( button ) {
if ( button ) {
$buttons.slice( -1 )[ 0 ].button( {
$buttons.slice( -1 )[ 0 ].button( {
icons: { primary: 'ui-icon-arrowthick-1-e' },
icons: { primary: 'ui-icon-arrowthick-1-e' },
showLabel: false,
showLabel: false,
text: false
text: false
} );
} );
}
}


$buttons.push( $( '<a>' )
$buttons.push( $( '<a>' )
.text( msg( 'copy' ) )
.text( msg( 'copy' ) )
.on( 'click', function () {
.on( 'click', function () {
CAL.doSomething( this, 'copy' );
CAL.doSomething( this, 'copy' );
} )
} )
.addClass( 'cat_a_lot_action' )
.addClass( 'cat_a_lot_action' )
);
);
if ( button ) {
if ( button ) {
$buttons.slice( -1 )[ 0 ].button( {
$buttons.slice( -1 )[ 0 ].button( {
icons: { primary: 'ui-icon-plusthick' },
icons: { primary: 'ui-icon-plusthick' },
showLabel: false,
showLabel: false,
text: false
text: false
} );
} );
}
}


}
}
} else {
} else {
$buttons.push( $( '<a>' )
$buttons.push( $( '<a>' )
.text( msg( 'add' ) )
.text( msg( 'add' ) )
.on( 'click', function () {
.on( 'click', function () {
CAL.doSomething( this, 'add' );
CAL.doSomething( this, 'add' );
} )
} )
.addClass( 'cat_a_lot_action' )
.addClass( 'cat_a_lot_action' )
);
);
if ( button ) {
if ( button ) {
$buttons.slice( -1 )[ 0 ].button( {
$buttons.slice( -1 )[ 0 ].button( {
icons: { primary: 'ui-icon-plusthick' },
icons: { primary: 'ui-icon-plusthick' },
showLabel: false,
showLabel: false,
text: false
text: false
} );
} );
}
}


}
}
// TODO CSS may extern
// TODO CSS may extern
var css = button ? { fontSize: '.6em', margin: '0', width: '2.5em' } : {};
var css = button ? { fontSize: '.6em', margin: '0', width: '2.5em' } : {};
for ( var b = 0; b < $buttons.length; b++ ) { $tr.append( $( '<td>' ).append( $buttons[ b ].css( css ) ) ); }
for ( var b = 0; b < $buttons.length; b++ ) { $tr.append( $( '<td>' ).append( $buttons[ b ].css( css ) ) ); }


table.append( $tr );
table.append( $tr );
}
}
},
},


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


_getPageQuery: function ( data ) {
_getPageQuery: function ( data ) {
// There should be only one, but we don't know its ID
// There should be only one, but we don't know its ID
if ( data && data.query && data.query.pages ) {
if ( data && data.query && data.query.pages ) {
data = data.query.pages;
data = data.query.pages;
for ( var p in data ) { return data[ p ]; }
for ( var p in data ) { return data[ p ]; }
}
}
},
},


/**
/**
* @brief takes this.currentCategory if redir_category is configured
* @brief takes this.currentCategory if redir_category is configured
** Cat pages with more than one cat link are still not supported for sure
** Cat pages with more than one cat link are still not supported for sure
* @return soft redirected cat
* @return soft redirected cat
*/
*/
solveSoftRedirect: function () {
solveSoftRedirect: function () {
this.doAPICall( {
this.doAPICall( {
prop: 'links', // TODO: For more accuracy the revisions could be checked
prop: 'links', // TODO: For more accuracy the revisions could be checked
titles: 'Category:' + this.currentCategory,
titles: 'Category:' + this.currentCategory,
// 'rvprop': 'content',
// 'rvprop': 'content',
// 'pllimit': 'max',
// 'pllimit': 'max',
plnamespace: 14
plnamespace: 14
}, function ( page ) {
}, function ( page ) {
page = CAL._getPageQuery( page );
page = CAL._getPageQuery( page );
if ( page ) {
if ( page ) {
var lks = page.links;
var lks = page.links;
if ( lks && lks.length === 1 && lks[ 0 ].title ) {
if ( lks && lks.length === 1 && lks[ 0 ].title ) {
CAL.currentCategory = lks[ 0 ].title.replace( reCat, '' );
CAL.currentCategory = lks[ 0 ].title.replace( reCat, '' );
$searchInput.val( CAL.currentCategory );
$searchInput.val( CAL.currentCategory );
return CAL.getCategoryList();
return CAL.getCategoryList();
} else {
} else {
// TODO? better translatable warning message: "Please solve the category soft redirect manually!"
// TODO? better translatable warning message: "Please solve the category soft redirect manually!"
$resultList.html( '<span id="cat_a_lot_no_found">' + mw.msg( 'Apifeatureusage-warnings', mw.msg( 'Categorytree-not-found', CAL.currentCategory ) ) + '</span>' );
$resultList.html( '<span id="cat_a_lot_no_found">' + mw.msg( 'Apifeatureusage-warnings', mw.msg( 'Categorytree-not-found', CAL.currentCategory ) ) + '</span>' );
}
}
}
}
} );
} );
},
},


showCategoryList: function () {
showCategoryList: function () {
if ( this.settings.redir_category && this.settings.redir_category === this.parentCats[ 0 ] ) { return this.solveSoftRedirect(); }
if ( this.settings.redir_category && this.settings.redir_category === this.parentCats[ 0 ] ) { return this.solveSoftRedirect(); }


var table = $( '<table>' );
var table = $( '<table>' );


this.createCatLinks( '↑', this.parentCats, table );
this.createCatLinks( '↑', this.parentCats, table );
this.createCatLinks( '→', [ this.currentCategory ], table );
this.createCatLinks( '→', [ this.currentCategory ], table );
// Show on soft-redirect
// Show on soft-redirect
if ( $searchInput.val() === this.currentCategory && this.origin !== this.currentCategory ) { this.createCatLinks( '→', [ this.origin ], table ); }
if ( $searchInput.val() === this.currentCategory && this.origin !== this.currentCategory ) { this.createCatLinks( '→', [ this.origin ], table ); }
this.createCatLinks( '↓', this.subCats, table );
this.createCatLinks( '↓', this.subCats, table );


$resultList.empty();
$resultList.empty();
$resultList.append( table );
$resultList.append( table );


document.body.style.cursor = 'auto';
document.body.style.cursor = 'auto';


// Reset width
// Reset width
$container.width( '' );
$container.width( '' );
$container.height( '' );
$container.height( '' );
$container.width( Math.min( table.width() * 1.1 + 15, $( window ).width() - 10 ) );
$container.width( Math.min( table.width() * 1.1 + 15, $( window ).width() - 10 ) );


$resultList.css( {
$resultList.css( {
maxHeight: Math.min( this.setHeight, $( window ).height() - $container.position().top - $selections.outerHeight() - 15 ),
maxHeight: Math.min( this.setHeight, $( window ).height() - $container.position().top - $selections.outerHeight() - 15 ),
height: ''
height: ''
} );
} );
table.width( '100%' );
table.width( '100%' );
$container.height( Math.min( $container.height(), $head.offset().top - $container.offset().top + 10 ) );
$container.height( Math.min( $container.height(), $head.offset().top - $container.offset().top + 10 ) );
$container.offset( { left: $( window ).width() - $container.outerWidth() } ); // Fix overlap
$container.offset( { left: $( window ).width() - $container.outerWidth() } ); // Fix overlap
},
},


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


doUndo: function () {
doUndo: function () {
this.cancelled = 0;
this.cancelled = 0;
this.doAbort();
this.doAbort();
if ( !this.undoList.length ) { return; }
if ( !this.undoList.length ) { return; }


$( '.cat_a_lot_feedback' ).removeClass( 'cat_a_lot_done' );
$( '.cat_a_lot_feedback' ).removeClass( 'cat_a_lot_done' );
this.counterNeeded = this.undoList.length;
this.counterNeeded = this.undoList.length;
this.counterCurrent = 1;
this.counterCurrent = 1;


document.body.style.cursor = 'wait';
document.body.style.cursor = 'wait';


var query = {
var query = {
action: 'edit',
action: 'edit',
user: mw.config.get( 'wgUserName' ),
user: mw.config.get( 'wgUserName' ),
bot: true,
bot: true,
minor: this.settings.minor,
minor: this.settings.minor,
starttimestamp: this.starttimestamp,
starttimestamp: this.starttimestamp,
watchlist: this.settings.watchlist,
watchlist: this.settings.watchlist,
tags: this.changeTag,
tags: this.changeTag,
token: this.edittoken
token: this.edittoken
};
};
for ( var i = 0; i < this.undoList.length; i++ ) {
for ( var i = 0; i < this.undoList.length; i++ ) {
var uID = this.undoList[ i ];
var uID = this.undoList[ i ];
query.title = uID.title;
query.title = uID.title;
query.undo = uID.id;
query.undo = uID.id;
query.basetimestamp = uID.timestamp;
query.basetimestamp = uID.timestamp;
this.doAPICall( query, function ( r ) {
this.doAPICall( query, function ( r ) {
// TODO: Add "details" to progressbar?
// TODO: Add "details" to progressbar?
// $resultList.append( [mw.msg('Filerevert-submit') + " done " + r.edit.title, '<br>' ] );
// $resultList.append( [mw.msg('Filerevert-submit') + " done " + r.edit.title, '<br>' ] );
if ( r && r.edit ) { mw.log( 'Revert done', r.edit.title ); }
if ( r && r.edit ) { mw.log( 'Revert done', r.edit.title ); }
CAL.updateCounter();
CAL.updateCounter();
} );
} );
}
}
},
},


doAbort: function () {
doAbort: function () {
for ( var t in this.XHR ) { this.XHR[ t ].abort(); }
for ( var t in this.XHR ) { this.XHR[ t ].abort(); }


if ( this.cancelled ) { // still not for undo
if ( this.cancelled ) { // still not for undo
this.progressDialog.remove();
this.progressDialog.remove();
this.toggleAll( false );
this.toggleAll( false );
$head.last().show();
$head.last().show();
}
}
this.cancelled = 1;
this.cancelled = 1;
},
},


showProgress: function () {
showProgress: function () {
document.body.style.cursor = 'wait';
document.body.style.cursor = 'wait';
this.progressDialog = $( '<div>' )
this.progressDialog = $( '<div>' )
.html( ' ' + msg( 'editing' ) + ' <span id="cat_a_lot_current">' + CAL.counterCurrent + '</span> ' + msg( 'of' ) + CAL.counterNeeded )
.html( ' ' + msg( 'editing' ) + ' <span id="cat_a_lot_current">' + CAL.counterCurrent + '</span> ' + msg( 'of' ) + CAL.counterNeeded )
.prepend( $.createSpinner( { id: 'fb-dialog', size: 'large' } ) )
.prepend( $.createSpinner( { id: 'fb-dialog', size: 'large' } ) )
.dialog( {
.dialog( {
width: 450,
width: 450,
height: 180,
height: 180,
minHeight: 90,
minHeight: 90,
modal: true,
modal: true,
resizable: false,
resizable: false,
draggable: false,
draggable: false,
// closeOnEscape: true,
// closeOnEscape: true,
dialogClass: 'cat_a_lot_feedback',
dialogClass: 'cat_a_lot_feedback',
buttons: [ {
buttons: [ {
text: mw.msg( 'Cancel' ), // Stops all actions
text: mw.msg( 'Cancel' ), // Stops all actions
click: function () {
click: function () {
$( this ).dialog( 'close' );
$( this ).dialog( 'close' );
}
}
} ],
} ],
close: function () {
close: function () {
CAL.cancelled = 1;
CAL.cancelled = 1;
CAL.doAbort();
CAL.doAbort();
$( this ).remove();
$( this ).remove();
},
},
open: function ( event, ui ) { // Workaround modify
open: function ( event, ui ) { // Workaround modify
ui = $( this ).parent();
ui = $( this ).parent();
ui.find( '.ui-dialog-titlebar' ).hide();
ui.find( '.ui-dialog-titlebar' ).hide();
ui.find( '.ui-dialog-buttonpane.ui-widget-content' )
ui.find( '.ui-dialog-buttonpane.ui-widget-content' )
.removeClass( 'ui-widget-content' );
.removeClass( 'ui-widget-content' );
/* .find( 'span' ).css( { fontSize: '90%' } )*/
/* .find( 'span' ).css( { fontSize: '90%' } )*/
}
}
} );
} );
if ( $head.children().length < 3 ) {
if ( $head.children().length < 3 ) {
$( '<span>' )
$( '<span>' )
.css( {
.css( {
'float': 'right',
'float': 'right',
fontSize: '75%'
fontSize: '75%'
} )
} )
.append( [ '[ ',
.append( [ '[ ',
$( '<a>', { title: 'Revert all last done edits' } ) // TODO i18n
$( '<a>', { title: 'Revert all last done edits' } ) // TODO i18n
.on( 'click', function () {
.on( 'click', function () {
if ( window.confirm( mw.msg( 'Apifeatureusage-warnings', this.title + '⁉' ) ) ) {
if ( window.confirm( mw.msg( 'Apifeatureusage-warnings', this.title + '⁉' ) ) ) {
CAL.doUndo();
CAL.doUndo();
$( this ).parent().remove();
$( this ).parent().remove();
}
}
return false;
return false;
} )
} )
.addClass( 'new' )
.addClass( 'new' )
.text( mw.msg( 'Filerevert-submit' ) ),
.text( mw.msg( 'Filerevert-submit' ) ),
' ]'
' ]'
] ).insertAfter( $link );
] ).insertAfter( $link );
}
}


this.domCounter = $( '#cat_a_lot_current' );
this.domCounter = $( '#cat_a_lot_current' );
},
},


minimize: function ( e ) {
minimize: function ( e ) {
CAL.top = Math.max( 0, $container.position().top );
CAL.top = Math.max( 0, $container.position().top );
CAL.height = $container.height();
CAL.height = $container.height();
$dataContainer.hide();
$dataContainer.hide();
$container.animate( {
$container.animate( {
height: $head.height(),
height: $head.height(),
top: $( window ).height() - $head.height() * 1.4
top: $( window ).height() - $head.height() * 1.4
}, function () {
}, function () {
$( e.target ).one( 'click', CAL.maximize );
$( e.target ).one( 'click', CAL.maximize );
} );
} );
},
},


maximize: function ( e ) {
maximize: function ( e ) {
$dataContainer.show();
$dataContainer.show();
$container.animate( {
$container.animate( {
top: CAL.top,
top: CAL.top,
height: CAL.height
height: CAL.height
}, function () {
}, function () {
$( e.target ).one( 'click', CAL.minimize );
$( e.target ).one( 'click', CAL.minimize );
} );
} );
},
},


run: function () {
run: function () {
if ( $( '.cat_a_lot_enabled' )[ 0 ] ) {
if ( $( '.cat_a_lot_enabled' )[ 0 ] ) {
this.makeClickable();
this.makeClickable();
if ( !this.executed ) { // only once
if ( !this.executed ) { // only once
$selectInvert.text( mw.msg( 'Checkbox-invert' ) );
$selectInvert.text( mw.msg( 'Checkbox-invert' ) );
if ( this.settings.editpages && this.pageLabels[ 0 ] ) {
if ( this.settings.editpages && this.pageLabels[ 0 ] ) {
$selectFiles.text( mw.msg( 'Prefs-files' ) );
$selectFiles.text( mw.msg( 'Prefs-files' ) );
$selectPages.text( mw.msg( 'Categories' ) ).parent().show();
$selectPages.text( mw.msg( 'Categories' ) ).parent().show();
}
}
$link.after( $( '<a>' )
$link.after( $( '<a>' )
.text( '–' )
.text( '–' )
.css( { fontWeight: 'bold', marginLeft: '.7em' } )
.css( { fontWeight: 'bold', marginLeft: '.7em' } )
.one( 'click', this.minimize )
.one( 'click', this.minimize )
);
);
}
}
$dataContainer.show();
$dataContainer.show();
$container.one( 'mouseover', function () {
$container.one( 'mouseover', function () {
$( this )
$( this )
.resizable( {
.resizable( {
handles: 'n',
handles: 'n',
alsoResize: '#cat_a_lot_category_list',
alsoResize: '#cat_a_lot_category_list',
resize: function () {
resize: function () {
$resultList
$resultList
.css( {
.css( {
maxHeight: '',
maxHeight: '',
width: ''
width: ''
} );
} );
},
},
start: function ( e, ui ) { // Otherwise box get static if sametime resize with draggable
start: function ( e, ui ) { // Otherwise box get static if sametime resize with draggable
ui.helper.css( {
ui.helper.css( {
top: ui.helper.offset().top - $( window ).scrollTop(),
top: ui.helper.offset().top - $( window ).scrollTop(),
position: 'fixed'
position: 'fixed'
} );
} );
},
},
stop: function () {
stop: function () {
CAL.setHeight = $resultList.height();
CAL.setHeight = $resultList.height();
}
}
} )
} )
.draggable( {
.draggable( {
cursor: 'move',
cursor: 'move',
start: function ( e, ui ) {
start: function ( e, ui ) {
ui.helper.on( 'click.prevent',
ui.helper.on( 'click.prevent',
function ( e ) { e.preventDefault(); }
function ( e ) { e.preventDefault(); }
);
);
ui.helper.css( 'height', ui.helper.height() );
ui.helper.css( 'height', ui.helper.height() );
},
},
stop: function ( e, ui ) {
stop: function ( e, ui ) {
setTimeout(
setTimeout(
function () {
function () {
ui.helper.off( 'click.prevent' );
ui.helper.off( 'click.prevent' );
}, 300
}, 300
);
);
}
}
} )
} )
.one( 'mousedown', function () {
.one( 'mousedown', function () {
$container.height( $container.height() ); // Workaround to calculate
$container.height( $container.height() ); // Workaround to calculate
} );
} );
$resultList
$resultList
.css( { maxHeight: 450 } );
.css( { maxHeight: 450 } );
} );
} );
this.updateCats( this.origin || 'Images' );
this.updateCats( this.origin || 'Images' );


$link.html( $( '<span>' )
$link.html( $( '<span>' )
.text( '×' )
.text( '×' )
.css( { font: 'bold 2em monospace', lineHeight: '.75em' } )
.css( { font: 'bold 2em monospace', lineHeight: '.75em' } )
);
);
$link.next().show();
$link.next().show();
if ( this.cancelled ) { $head.last().show(); }
if ( this.cancelled ) { $head.last().show(); }
mw.cookie.set( 'catAlotO', ns ); // Let stay open on new window
mw.cookie.set( 'catAlotO', ns ); // Let stay open on new window
} else { // Reset
} else { // Reset
$dataContainer.hide();
$dataContainer.hide();
$container
$container
.draggable( 'destroy' )
.draggable( 'destroy' )
.resizable( 'destroy' )
.resizable( 'destroy' )
.removeAttr( 'style' );
.removeAttr( 'style' );
// Unbind click handlers
// Unbind click handlers
this.labels.off( 'click.catALot' );
this.labels.off( 'click.catALot' );
this.setHeight = 450;
this.setHeight = 450;
$link.text( 'Cat-a-lot' )
$link.text( 'Cat-a-lot' )
.nextAll().hide();
.nextAll().hide();
this.executed = 1;
this.executed = 1;
mw.cookie.set( 'catAlotO', null );
mw.cookie.set( 'catAlotO', null );
}
}
},
},


_initSettings: function () {
_initSettings: function () {
if ( this.settings.watchlist ) { return; }
if ( this.settings.watchlist ) { return; }
this.catALotPrefs = window.catALotPrefs || {};
this.catALotPrefs = window.catALotPrefs || {};
for ( var i = 0; i < this.defaults.length; i++ ) {
for ( var i = 0; i < this.defaults.length; i++ ) {
var v = this.defaults[ i ];
var v = this.defaults[ i ];
v.value = this.settings[ v.name ] = ( this.catALotPrefs[ v.name ] || v['default'] );
v.value = this.settings[ v.name ] = this.catALotPrefs[ v.name ] || v['default'];
v.label = msg( v.label_i18n );
v.label = msg( v.label_i18n );
if ( v.select_i18n ) {
if ( v.select_i18n ) {
v.select = {};
v.select = {};
$.each( v.select_i18n, function ( i18nk, val ) {
$.each( v.select_i18n, function ( i18nk, val ) {
v.select[ msg( i18nk ) ] = val;
v.select[ msg( i18nk ) ] = val;
} );
} );
}
}
}
}
},
},
/* eslint-disable camelcase */
/* eslint-disable camelcase */
defaults: [ {
defaults: [ {
name: 'watchlist',
name: 'watchlist',
'default': 'preferences'
'default': 'preferences'
}, {
}, {
name: 'minor',
name: 'minor',
'default': false
'default': false
}, {
}, {
name: 'editpages',
name: 'editpages',
'default': project !== 'commonswiki', // on Commons false
'default': project !== 'commonswiki', // on Commons false
forcerestart: true
forcerestart: true
}, {
}, {
name: 'docleanup',
name: 'docleanup',
'default': false
'default': false
}, {
}, {
name: 'subcatcount',
name: 'subcatcount',
'default': 50,
'default': 50,
min: 5,
min: 5,
max: 500,
max: 500,
forcerestart: true
forcerestart: true
}, {
}, {
name: 'uncat',
name: 'uncat',
'default': false
'default': false
}, {
}, {
name: 'button',
name: 'button',
'default': true
'default': true
} ]
} ]
/* eslint-enable camelcase */
/* eslint-enable camelcase */
};
};
第1,364行: 第1,363行:
// The gadget is not immediately needed, so let the page load normally
// The gadget is not immediately needed, so let the page load normally
$( function () {
$( function () {
non = mw.config.get( 'wgUserName' );
non = mw.config.get( 'wgUserName' );
if ( non ) {
if ( non ) {
if ( mw.config.get( 'wgRelevantUserName' ) === non ) { non = 0; } else {
if ( mw.config.get( 'wgRelevantUserName' ) === non ) { non = 0; } else {
$.each( [ 'sysop', 'filemover', 'editor', 'rollbacker', 'patroller', 'autopatrolled', 'image-reviewer', 'reviewer', 'extendedconfirmed' ], function ( i, v ) {
$.each( [ 'sysop', 'filemover', 'editor', 'rollbacker', 'patroller', 'autopatrolled', 'image-reviewer', 'reviewer', 'extendedconfirmed' ], function ( i, v ) {
non = $.inArray( v, userGrp ) === -1;
non = $.inArray( v, userGrp ) === -1;
return non;
return non;
} );
} );
}
}
} else { non = 1; }
} else { non = 1; }


switch ( ns ) {
switch ( ns ) {
case 14:
case 14:
CAL.searchmode = 'category';
CAL.searchmode = 'category';
CAL.origin = mw.config.get( 'wgTitle' );
CAL.origin = mw.config.get( 'wgTitle' );
break;
break;
/* 其他namespace无法正常使用
/* 其他namespace无法正常使用
case -1:
case -1:
CAL.searchmode = {
CAL.searchmode = {
// list of accepted special page names mapped to search mode names
// list of accepted special page names mapped to search mode names
Contributions: 'contribs',
Contributions: 'contribs',
Listfiles: non ? null : 'listfiles',
Listfiles: non ? null : 'listfiles',
Prefixindex: non ? null : 'prefix',
Prefixindex: non ? null : 'prefix',
Search: 'search',
Search: 'search',
Uncategorizedimages: 'gallery'
Uncategorizedimages: 'gallery'
}[ mw.config.get( 'wgCanonicalSpecialPageName' ) ];
}[ mw.config.get( 'wgCanonicalSpecialPageName' ) ];
break;
break;
case 2:
case 2:
case 0:
case 0:
CAL.searchmode = 'gallery';
CAL.searchmode = 'gallery';
var parents = $( '#mw-normal-catlinks ul' ).find( 'a[title]' ), n;
var parents = $( '#mw-normal-catlinks ul' ).find( 'a[title]' ), n;
parents.each( function ( i ) {
parents.each( function ( i ) {
if ( new RegExp( mw.config.get( 'wgTitle' ), 'i' ).test( $( this ).text() ) ) {
if ( new RegExp( mw.config.get( 'wgTitle' ), 'i' ).test( $( this ).text() ) ) {
n = i;
n = i;
return false;
return false;
}
}
} );
} );
CAL.origin = parents.eq( n || 0 ).text();
CAL.origin = parents.eq( n || 0 ).text();
*/
*/
}
}


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


$.ajax( {
$.ajax( {
url: commonsURL,
url: commonsURL,
dataType: 'script',
dataType: 'script',
data: {
data: {
title: 'MediaWiki:Gadget-Cat-a-lot.js/' + lang,
title: 'MediaWiki:Gadget-Cat-a-lot.js/' + lang,
action: 'raw',
action: 'raw',
ctype: 'text/javascript',
ctype: 'text/javascript',
// Allow caching for 28 days
// Allow caching for 28 days
maxage: 2419200,
maxage: 2419200,
smaxage: 2419200
smaxage: 2419200
},
},
cache: true,
cache: true,
success: cb,
success: function() {
mw.messages.set({
error: cb
'cat-a-lot-summary-add': wgULS('加入分类', '加入分類') + '[[Category:$1]]',
} );
'cat-a-lot-summary-copy': wgULS('分类间复制:从', '分類間複製:從') + '[[Category:$1]]到[[Category:$2]]',
};
'cat-a-lot-summary-move': wgULS('分类间移动:从', '分類間移動:從') + '[[Category:$1]]到[[Category:$2]]',
var maybeLaunch = function () {
'cat-a-lot-summary-remove': wgULS('从分类', '從分類') + '移除:[[Category:$1]]'
loadingLocalizations--;
});
if(loadingLocalizations) {
cb();
mw.messages.set({
},
'cat-a-lot-summary-add': wgULS('加入分类', '加入分類') + '[[Category:$1]]',
error: cb
'cat-a-lot-summary-copy': wgULS('分类间复制:从', '分類間複製:從') + '[[Category:$1]]到[[Category:$2]]',
} );
'cat-a-lot-summary-move': wgULS('分类间移动:从', '分類間移動:從') + '[[Category:$1]]到[[Category:$2]]',
};
'cat-a-lot-summary-remove': wgULS('从分类', '從分類') + '移除:[[Category:$1]]'
var maybeLaunch = function () {
});
loadingLocalizations--;
}
function init() {
function init() {
$( function () {
$( function () {
CAL.init();
CAL.init();
} );
} );
}
}
if ( !loadingLocalizations ) { init(); }
if ( !loadingLocalizations ) { init(); }
};
};


var userlang = mw.config.get( 'wgUserLanguage' );
var userlang = mw.config.get( 'wgUserLanguage' );
if ( userlang !== 'en' ) { loadLocalization( userlang, maybeLaunch ); }
if ( userlang !== 'en' ) { loadLocalization( userlang, maybeLaunch ); }
// if ( $.inArray( contlang, [ 'en', userlang ] ) === -1 ) { loadLocalization( contlang, maybeLaunch ); }
// if ( $.inArray( contlang, [ 'en', userlang ] ) === -1 ) { loadLocalization( contlang, maybeLaunch ); }
maybeLaunch();
maybeLaunch();
}
}
} );
} );


第1,467行: 第1,466行:
*/
*/
$.fn.catALotShiftClick = function ( cb ) {
$.fn.catALotShiftClick = function ( cb ) {
var prevCheckbox = null,
var prevCheckbox = null,
$box = this;
$box = this;
// When our boxes are clicked..
// When our boxes are clicked..
$box.on( 'click.catALot', function ( e ) {
$box.on( 'click.catALot', function ( e ) {
// Prevent following the link and text selection
// Prevent following the link and text selection
if ( !e.ctrlKey ) { e.preventDefault(); }
if ( !e.ctrlKey ) { e.preventDefault(); }
// Highlight last selected
// Highlight last selected
$( '#cat_a_lot_last_selected' )
$( '#cat_a_lot_last_selected' )
.removeAttr( 'id' );
.removeAttr( 'id' );
var $thisControl = $( e.target ),
var $thisControl = $( e.target ),
method;
method;
if ( !$thisControl.hasClass( 'cat_a_lot_label' ) ) { $thisControl = $thisControl.parents( '.cat_a_lot_label' ); }
if ( !$thisControl.hasClass( 'cat_a_lot_label' ) ) { $thisControl = $thisControl.parents( '.cat_a_lot_label' ); }


$thisControl.attr( 'id', 'cat_a_lot_last_selected' )
$thisControl.attr( 'id', 'cat_a_lot_last_selected' )
.toggleClass( 'cat_a_lot_selected' );
.toggleClass( 'cat_a_lot_selected' );
// And one has been clicked before…
// And one has been clicked before…
if ( prevCheckbox !== null && e.shiftKey ) {
if ( prevCheckbox !== null && e.shiftKey ) {
method = $thisControl.hasClass( 'cat_a_lot_selected' ) ? 'addClass' : 'removeClass';
method = $thisControl.hasClass( 'cat_a_lot_selected' ) ? 'addClass' : 'removeClass';
// Check or uncheck this one and all in-between checkboxes
// Check or uncheck this one and all in-between checkboxes
$box.slice(
$box.slice(
Math.min( $box.index( prevCheckbox ), $box.index( $thisControl ) ),
Math.min( $box.index( prevCheckbox ), $box.index( $thisControl ) ),
Math.max( $box.index( prevCheckbox ), $box.index( $thisControl ) ) + 1
Math.max( $box.index( prevCheckbox ), $box.index( $thisControl ) ) + 1
)[ method ]( 'cat_a_lot_selected' );
)[ method ]( 'cat_a_lot_selected' );
}
}
// Either way, update the prevCheckbox variable to the one clicked now
// Either way, update the prevCheckbox variable to the one clicked now
prevCheckbox = $thisControl;
prevCheckbox = $thisControl;
if ( $.isFunction( cb ) ) { cb(); }
if ( $.isFunction( cb ) ) { cb(); }
} );
} );
return $box;
return $box;
};
};



2022年4月5日 (二) 01:06的最新版本

//<nowiki>
/**
* Cat-a-lot
* Changes category of multiple files
*
* @rev 00:13, 10 February 2018 (UTC)
* @author Originally by Magnus Manske (2007)
* @author RegExes by Ilmari Karonen (2010)
* @author Completely rewritten by DieBuche (2010-2012)
* @author Rillke (2012-2014)
* @author Perhelion (2017)

* 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]
*/

/* global jQuery, mediaWiki */
/* eslint one-var:0, vars-on-top:0, no-underscore-dangle:0, valid-jsdoc:0,
curly:0, camelcase:0, no-useless-escape:0, no-alert:0 */ // extends: wikimedia
/* jshint unused:true, forin:false, smarttabs:true, loopfunc:true, browser:true */

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

var ns = mw.config.get( 'wgNamespaceNumber' ),
	nsIDs = mw.config.get( 'wgNamespaceIds' ),
	userGrp = mw.config.get( 'wgUserGroups' ),
	project = mw.config.get( 'wgDBname' );

var msgs = {
// Preferences
// new: added 2012-09-19. Please translate.
// Use user language for i18n
	'cat-a-lot-uncatpref': '移除 {{Uncategorized}}',
	'cat-a-lot-comment-label': wgULS('自定义编辑摘要','自定義編輯摘要'),
	'cat-a-lot-edit-question': wgULS('请输入编辑摘要:','請輸入編輯摘要:'),

	// Progress
	'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!', // mw.msg("Feedback-close")
	'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',

	// as in 17 files selected
	'cat-a-lot-files-selected': '{{PLURAL:$1|1=One file|$1 files}} selected.',
	'cat-a-lot-pe_file': '$1 {{PLURAL:$1|page|pages}} of $2 affected',

	// Actions
	'cat-a-lot-copy': 'Copy',
	'cat-a-lot-move': 'Move',
	'cat-a-lot-add': 'Add',
	'cat-a-lot-remove-from-cat': 'Remove',
	'cat-a-lot-overcat': wgULS('检测过度分类','檢測過度分類'),
	'cat-a-lot-enter-name': 'Enter category name',
	'cat-a-lot-select': 'Select',
	'cat-a-lot-all': 'all',
	'cat-a-lot-none': 'none',

	// Summaries (project language):
	'cat-a-lot-summary-add': 'Adding [[Category:$1]]',
	'cat-a-lot-summary-copy': 'Copying from [[Category:$1]] to [[Category:$2]]',
	'cat-a-lot-summary-move': 'Moving from [[Category:$1]] to [[Category:$2]]',
	'cat-a-lot-summary-remove': 'Removing from [[Category:$1]]',
	'cat-a-lot-prefix-summary': '使用Cat-a-lot小工具',
	'cat-a-lot-using-summary': ''
};
mw.messages.set( msgs );

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

// There is only one Cat-a-lot on one page
var $body, $container, $dataContainer, $searchInputContainer, $searchInput, $resultList, $markCounter, $selections,
	$selectFiles, $selectPages, $selectNone, $selectInvert, $head, $link, $overcat,
	commonsURL = 'https://commons.wikimedia.org/w/index.php',
	is_rtl = $( 'body' ).hasClass( 'rtl' ),
	reCat, // localized category search regexp
	non,
	r; // result file count for overcat

var CAL = mw.libs.catALot = {
	apiUrl: mw.util.wikiScript( 'api' ),
	origin: '',
	searchmode: false,
	version: '4.77',
	setHeight: 450,
	changeTag: 'Cat-a-lot',

	settings: {
	/* Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
any items, but that contains links to other categories where stuff should be categorized. If you don't have
that concept on your wiki, set it to null. Use blanks, not underscores. */
		disambig_category: null, // Commons and EnWP
		/* Any category in this category is deemed a (soft) redirect to some other category defined by a link
* to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null.
* If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered
* a disambiguation category instead. */
		redir_category: null
	},

	init: function () {
		// TODO: better extern project support for possible change-tag? (needs currently change after init)
		if ( project === 'commonswiki' ) { mw.messages.set( { 'cat-a-lot-using-summary': '' } ); } else { // Reset
			this.changeTag = '';
			this.settings.redir_category = '';
		}

		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>', {
			id: 'cat_a_lot_searchcatname',
			placeholder: msg( '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( msg( 'select' ) + ':' )
			.appendTo( $dataContainer );
		$head = $( '<div>' )
			.attr( 'id', 'cat_a_lot_head' )
			.appendTo( $container );
		$link = $( '<a>' )
			.attr( 'id', 'cat_a_lot_toggle' )
			.text( 'Cat-a-lot' )
			.appendTo( $head );

		if ( this.origin && !non ) {
			$overcat = $( '<a>' )
				.attr( 'id', 'cat_a_lot_overcat' )
				.html( msg( 'overcat' ) )
				.on( 'click', function ( e ) {
					CAL.getOverCat( e );
				} )
				.insertBefore( $selections );
		}

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

		$searchInput.on( 'keypress', function ( e ) {
			if ( e.which === 13 ) {
				CAL.updateCats( $.trim( $( this ).val().replace( /[\u200E\u200F\u202A-\u202E]/g, '' ) ) );
				mw.cookie.set( 'catAlot', CAL.currentCategory );
			}
		} )
			.on( 'input keyup', function () {
				var oldVal = this.value,
					newVal = oldVal.replace( reCat, '' );
				if ( newVal !== oldVal ) { this.value = newVal; }

				if ( !newVal ) { mw.cookie.set( 'catAlot', null ); }
			} );

		function initAutocomplete() {
			if ( CAL.autoCompleteIsEnabled ) { return; }

			CAL.autoCompleteIsEnabled = true;

			if ( !$searchInput.val() && mw.cookie && mw.cookie.get( 'catAlot' ) ) { $searchInput.val( mw.cookie.get( 'catAlot' ) ); }

			$searchInput.autocomplete( {
				source: function ( request, response ) {
					CAL.doAPICall( {
						action: 'opensearch',
						search: request.term,
						redirects: 'resolve',
						namespace: 14
					}, function ( data ) {
						if ( data[ 1 ] ) {
							response( $( data[ 1 ] )
								.map( function ( index, item ) {
									return item.replace( reCat, '' );
								} ) );
						}
					} );
				},
				open: function () {
					$( '.ui-autocomplete' )
						.position( {
							my: is_rtl ? 'left bottom' : 'right bottom',
							at: is_rtl ? 'left top' : 'right top',
							of: $searchInput
						} );
				},
				appendTo: '#cat_a_lot'
			} );
		}
		$( '<a>' )
			.text( msg( 'all' ) )
			.on( 'click', function () {
				CAL.toggleAll( true );
			} )
			.appendTo( $selections.append( ' ' ) );
		if ( this.settings.editpages ) {
			$selectFiles = $( '<a>' )
				.on( 'click', function () {
					CAL.toggleAll( 'files' );
				} );
			$selectPages = $( '<a>' )
				.on( 'click', function () {
					CAL.toggleAll( 'pages' );
				} );
			$selections.append( $( '<span>' ).hide().append( [ ' / ', $selectFiles, ' / ', $selectPages ] ) );
		}
		$selectNone = $( '<a>' )
			.text( msg( 'none' ) )
			.on( 'click', function () {
				CAL.toggleAll( false );
			} );
		$selectInvert = $( '<a>' )
			.on( 'click', function () {
				CAL.toggleAll( null );
			} );
		$selections.append( [ ' • ', $selectNone, ' • ', $selectInvert,
			$( '<div>' ).append( [
				$( '<label>' )
					.attr( {
						'for': 'cat_a_lot_comment',
						style: 'line-height:1.5em;vertical-align:bottom'
					} )
					.text( msg( 'comment-label' ) ),
				$( '<input>' )
					.attr( {
						id: 'cat_a_lot_comment',
						type: 'checkbox'
					} )
			] )
		] );

		$link
			.on( 'click', function () {
				$( this ).toggleClass( 'cat_a_lot_enabled' );
				// Load autocomplete on demand
				initAutocomplete ();

				if ( !CAL.executed ) {
					$.when( mw.loader.using( [
						'mediawiki.api',
						'mediawiki.jqueryMsg'
					] ), $.ready )
						.then( function () {
							return new mw.Api().loadMessagesIfMissing( [
								'Cancel',
								'Categorytree-not-found',
								// 'Checkuser-all',
								// 'Code-field-select',
								// 'Export-addcat',
								'Filerevert-submit',
								'Mobile-frontend-return-to-page',
								'Ooui-selectfile-placeholder',
								// 'Visualeditor-clipboard-copy',
								'Wikieditor-loading',
								'Prefs-files',
								'Categories',
								'Checkbox-invert',
								'Centralnotice-remove', // 'Ooui-item-remove'
								'Apifeatureusage-warnings'
							] );
						} ).then( function () {
							CAL.run();
						} );
				} else { CAL.run(); }
			} );
		this.localCatName = 'Category:';
		mw.loader.using( 'mediawiki.cookie', function () { // Let catAlot stay open
			var val = mw.cookie.get( 'catAlotO' );
			if ( val && Number( val ) === ns ) { $link.click(); }
		}
		);
	},

	getOverCat: function ( e ) {
		var files = [];
		r = 0; // result counter
		if ( e ) {
			e.preventDefault();
			this.files = this.getMarkedLabels(); // .toArray() not working
			for ( var f = 0; f < this.files.length; f++ ) { files.push( this.files[ f ] ); }

		}
		if ( !files.length || !( files instanceof Array ) ) { return alert( mw.msg( 'Ooui-selectfile-placeholder' ) ); }
		this.files = files;
		mw.loader.using( [ 'jquery.spinner' ], function () {
			$markCounter.injectSpinner( 'overcat' );
			CAL.getFileCats();
		} );
	},

	getFileCats: function () {
		var aLen = this.files.length;
		var bLen = this.selectedLabels.length;
		var file = this.files[ aLen - 1 ][ 0 ];
		$overcat.text( '…' + aLen + '\/' + bLen );
		if ( file ) {
			this.doAPICall( {
				prop: 'categories',
				titles: file
			}, this.checkFileCats
			);
		}

	},

	checkFileCats: function ( data ) {
		var cc = 0; // current cat counter;
		var file = CAL.files.pop();
		if ( data.query && data.query.pages ) {
			$.each( data.query.pages, function ( id, page ) {
				if ( page.categories ) {
					var target = file[ 1 ].removeClass( 'cat_a_lot_selected' );
					$.each( page.categories, function ( c, cat ) {
						var title = cat.title.replace( reCat, '' ),
							color = 'orange',
							mark = function ( kind ) { // kind of category
							// TODO: store data to use this for special remove function
								if ( kind === 'sub' ) { color = 'green'; }
								var border = '3px dotted ';
								if ( $.inArray( title, CAL[ kind + 'Cats' ] ) !== -1 ) {
									cc++;
									target = target.parents( '.gallerybox' );
									target = target[ 0 ] ? target : file[ 1 ];
									target.css( {
										border: border + color
									} ).prop( 'title', msg( kind + '-cat' ) + title );
									color = 'red';
									return false;
								}
							};
						mark( 'sub' );
						return mark( 'parent' );
					} );
					if ( cc ) { r++; }
				}
			} );
		} else { mw.log( 'Api-fail', file, data ); }
		if ( CAL.files[ 0 ] ) { return setTimeout( function () { CAL.getFileCats(); }, 100 ); } // Api has bad performance here, so we can get only each file separately
		$overcat.text( msg( 'pe_file', r, CAL.selectedLabels.length ) );
		$.removeSpinner( 'overcat' );
	},

	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( $( '#mw-category-media' ).find( 'li[class!="gallerybox"]' ) );
				if ( this.settings.editpages ) {
					this.pageLabels = $( '#mw-pages, #mw-subcategories' ).find( 'li' );
					// this.files = this.labels;
					this.labels = this.labels.add( this.pageLabels );
				}
				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( '.gallerybox' ); // TODO incombatible with GalleryDetails
				this.labels = this.labels.add( '.gallerytext' );
				break;
		}
	},

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

	/**
*  @brief Get title from selected pages
*  @return [array] touple of page title and $object
*/
	getMarkedLabels: function () {
		this.selectedLabels = this.labels.filter( '.cat_a_lot_selected:visible' );
		return this.selectedLabels.map( function () {
			var label = $( this ), file = label.find( 'a[title][class$="title"]' );
			file = file.length ? file : label.find( 'a[title]' );
			var title = file.attr( 'title' ) ||
			CAL.getTitleFromLink( file ) ||
			CAL.getTitleFromLink( label.find( 'a' ) ) ||
			CAL.getTitleFromLink( label.parent().find( 'a' ) ); // TODO needs optimization
			if ( title.indexOf( 'User:' ) ) { return [ [ title, label ] ]; }
		} );
	},

	updateSelectionCounter: function () {
		this.selectedLabels = this.labels.filter( '.cat_a_lot_selected:visible' );
		var first = $markCounter.is( ':hidden' );
		$markCounter
			.html( msg( 'files-selected', this.selectedLabels.length ) )
			.show();
		if ( first && !$dataContainer.is( ':hidden' ) ) { // Workaround to fix position glitch
			first = $markCounter.innerHeight();
			$container
				.offset( { top: $container.offset().top - first } )
				.height( $container.height() + first );
			$( window ).on( 'beforeunload', function () {
				if ( CAL.labels.filter( '.cat_a_lot_selected:visible' )[ 0 ] ) { return 'You have pages selected!'; } // There is a default message in the browser
			} );
		}
	},

	makeClickable: function () {
		this.labels = $();
		this.pageLabels = $(); // only for distinct all selections
		this.findAllLabels( this.searchmode );
		this.labels.catALotShiftClick( function () {
			CAL.updateSelectionCounter();
		} )
			.addClass( 'cat_a_lot_label' );
	},

	toggleAll: function ( select ) {
		if ( typeof select === 'string' && select === 'files' ) {
			if ( this.pageLabels[ 0 ] ) // pages remain unchanged
			{ this.pageLabels.toggleClass( 'cat_a_lot_selected' ); }
			this.labels.toggleClass( 'cat_a_lot_selected' );
		} else if ( typeof select === 'string' && select === 'pages' ) {
			if ( this.pageLabels[ 0 ] ) // files remain unchanged
			{ this.pageLabels.toggleClass( 'cat_a_lot_selected' ); }
		} else {
		// invert / none / all
			this.labels.toggleClass( 'cat_a_lot_selected', select );
		}
		this.updateSelectionCounter();
	},

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

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

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

		} );
	},

	getParentCats: function () {
		var data = {
			prop: 'categories',
			titles: 'Category:' + this.currentCategory
		};
		this.doAPICall( data, function ( result ) {
			CAL.parentCats = [];
			var cats,
				pages = result.query.pages,
				table = $( '<table>' );

			if ( pages[ -1 ] && pages[ -1 ].missing === '' ) {
				$resultList.html( '<span id="cat_a_lot_no_found">' + mw.msg( 'Categorytree-not-found', this.currentCategory ) + '</span>' );
				document.body.style.cursor = 'auto';
				CAL.createCatLinks( '→', [ CAL.currentCategory ], table );
				$resultList.append( table );
				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++ ) { CAL.parentCats.push( cats[ i ].title.replace( /^[^:]+:/, '' ) ); }

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

		} );
	},

	localizedRegex: function ( 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 ) { return ''; }

			var regexName = '';
			for ( var i = 0; i < name.length; i++ ) {
				var ii = name[ i ];
				var ll = ii.toLowerCase();
				var ul = ii.toUpperCase();
				regexName +=  ll === ul ? ii : '[' + ll + ul + ']';
			}
			return regexName.replace( /([\\\^\$\.\?\*\+\(\)])/g, '\\$1' )
				.replace( wikiTextBlankRE, wikiTextBlank );
		};

		fallback = fallback.toLowerCase();
		var canonical = 'category';
		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 ] === ns ) { RegexString += '|' + createRegexStr( catName ); } }

		return '(?:' + RegexString + ')';
	},

	regexCatBuilder: function ( category ) {
		var catname = this.localizedRegex( 'Category' );
		
		// 繁简转换
		if ( window.CatALotSourceCat && window.CatALotSourceCat.includes( category.replace(/_/g, ' ') ) ) {
			category = window.CatALotSourceCat;
		} else { category = [category]; }

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

		// escape regexp metacharacters (= any ASCII punctuation except _)
		category = category.map(ele => mw.util.escapeRegExp( ele ));

		// any sequence of spaces and underscores should match any other
		category = category.map(ele => ele.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.join('|') + ')[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]\\s*', 'gi' );
	},

	getContent: function ( page, targetcat, mode ) {
		if ( !this.cancelled ) {
			this.doAPICall( {
				curtimestamp: 1,
				// meta: 'tokens',
				prop: 'revisions',
				rvprop: 'content|timestamp',
				titles: page[ 0 ]
			}, function ( result ) {
				CAL.editCategories( result, page, targetcat, mode );
			} );
		}

	},

	getTargetCat: function ( pages, targetcat, mode ) {
		if ( !this.cancelled ) {
			this.doAPICall( {
				meta: 'tokens',
				prop: 'categories|categoryinfo',
				titles: 'Category:' + targetcat
			}, function ( result ) {
				if ( !result || !result.query ) { return; }
				CAL.edittoken = result.query.tokens.csrftoken;
				result = CAL._getPageQuery( result );
				CAL.checkTargetCat( result );
				for ( var i = 0; i < pages.length; i++ ) { CAL.getContent( pages[ i ], targetcat, mode ); }

			} );
		}

	},

	checkTargetCat: function ( page ) {
		var is_dab = false; // disambiguation
		var is_redir = typeof page.redirect === 'string'; // Hard redirect?
		if ( typeof page.missing === 'string' ) { return alert( mw.msg( 'Apifeatureusage-warnings', mw.msg( 'Categorytree-not-found', page.title ) ) ); }
		var cats = page.categories;
		this.is_hidden = page.categoryinfo && typeof page.categoryinfo.hidden === 'string';

		if ( !is_redir && cats && ( CAL.disambig_category || CAL.redir_category ) ) {
			for ( var c = 0; c < cats.length; c++ ) {
				var cat = cats[ c ].title;
				if ( cat ) { // Strip namespace prefix
					cat = cat.substring( cat.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
					if ( cat === CAL.disambig_category ) {
						is_dab = true; break;
					} else if ( cat === CAL.redir_category ) {
						is_redir = true; break;
					}
				}
			}
		}

		if ( !is_redir && !is_dab ) { return; }
		alert( mw.msg( 'Apifeatureusage-warnings', page.title + ' is a ' + CAL.disambig_category ) );
	},

	// Remove {{Uncategorized}} (also with comment). No need to replace it with anything.
	removeUncat: function ( text ) {
		return this.settings.uncat ? text.replace( /\{\{\s*[Uu]ncategorized\s*[^}]*\}\}\s*(<!--.*?-->\s*)?/, '' ) : text;
	},

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

	editCategories: function ( result, file, targetcat, mode ) {
		if ( !result || !result.query ) {
		// Happens on unstable wifi connections..
			this.connectionError.push( file[ 0 ] );
			this.updateCounter();
			return;
		}
		var otext,
			timestamp,
			page = CAL._getPageQuery( result );
		if ( page.ns === 2 ) { return; }
		var id = page.revisions[ 0 ],
			catNS = this.localCatName; // canonical cat-name

		this.starttimestamp = result.curtimestamp;
		otext = id[ '*' ];
		timestamp = id.timestamp;

		var sourcecat = this.origin;
		// Check if that file is already in that category
		if ( mode !== 'remove' && this.regexCatBuilder( targetcat ).test( otext ) ) {
			// If the new cat is already there, just remove the old one
			if ( mode === 'move' ) {
				mode = 'remove';
				targetcat = sourcecat;
			} else {
				this.alreadyThere.push( file[ 0 ] );
				this.updateCounter();
				return;
			}
		}

		// Text modification (following 3 functions are partialy taken from HotCat)
		var wikiTextBlankOrBidi = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200B\\u200E\\u200F\\u2028-\\u202F\\u205F\\u3000]*';
		// Whitespace regexp for handling whitespace between link components. Including the horizontal tab, but not \n\r\f\v:
		// a link must be on one single line.
		// MediaWiki also removes Unicode bidi override characters in page titles (and namespace names) completely.
		// This is *not* handled, as it would require us to allow any of [\u200E\u200F\u202A-\u202E] between any two
		// characters inside a category link. It _could_ be done though... We _do_ handle strange spaces, including the
		// zero-width space \u200B, and bidi overrides between the components of a category link (adjacent to the colon,
		// or adjacent to and inside of "[[" and "]]").
		var findCatsRE = new RegExp( '\\[\\[' + wikiTextBlankOrBidi + this.localizedRegex( 'Category' ) + wikiTextBlankOrBidi + ':[^\\]]+\\]\\]', 'g' );

		function replaceByBlanks( match ) {
			return match.replace( /(\s|\S)/g, ' ' ); // /./ doesn't match linebreaks. /(\s|\S)/ does.
		}

		function find_insertionpoint( wikitext ) {
			var copiedtext = wikitext
				.replace( /<!--(\s|\S)*?-->/g, replaceByBlanks )
				.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, replaceByBlanks );
			// Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element".
			var index = -1;
			findCatsRE.lastIndex = 0;
			while ( findCatsRE.exec( copiedtext ) !== null ) { index = findCatsRE.lastIndex; }

			return index;
		}

		/**
*  @brief Adds the new Category by searching the right insert point,
*		 if there is text after the category section
*  @param [string] wikitext
*  @param [string] toAdd
*  @return Return wikitext
*/
		function addCategory( wikitext, toAdd ) {
			if ( toAdd && toAdd[ 0 ] ) {
			// TODO: support sort key
				var cat_point = find_insertionpoint( wikitext ); // Position of last category
				var newcatstring = '[[' + catNS + toAdd + ']]';
				if ( cat_point > -1 ) {
					var suffix = wikitext.substring( cat_point );
					wikitext = wikitext.substring( 0, cat_point ) + ( cat_point ? '\n' : '' ) + newcatstring;
					if ( suffix[ 0 ] && suffix.substr( 0, 1 ) !== '\n' ) { wikitext += '\n'; }
					wikitext += suffix;
				} else {
					if ( wikitext[ 0 ] && wikitext.substr( wikitext.length - 1, 1 ) !== '\n' ) { wikitext += '\n'; }

					wikitext += ( wikitext[ 0 ] ? '\n' : '' ) + newcatstring;
				}
			}
			return wikitext;
		}
		// End HotCat functions

		var text = otext,
			arr = is_rtl ? '\u2190' : '\u2192', // left and right arrows. Don't use ← and → in the code.
			sumCmt, // summary comment
			sumCmtShort;
		// Fix text
		switch ( mode ) {
			case 'add':
				text = addCategory( text, targetcat );
				sumCmt = msg( 'summary-add' ).replace( /\$1/g, targetcat );
				sumCmtShort = '+[[' + catNS + targetcat + ']]';
				break;
			case 'copy':
				text = text.replace( this.regexCatBuilder( sourcecat ), '[[' + catNS + sourcecat + '$1]]\n[[' + catNS + targetcat + '$1]]\n' );
				sumCmt = msg( 'summary-copy' ).replace( /\$1/g, sourcecat ).replace( /\$2/g, targetcat );
				sumCmtShort = '+[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]';
				// If category is added through template:
				if ( otext === text ) { text = addCategory( text, targetcat ); }

				break;
			case 'move':
				text = text.replace( this.regexCatBuilder( sourcecat ), '[[' + catNS + targetcat + '$1]]\n' );
				sumCmt = msg( 'summary-move' ).replace( /\$1/g, sourcecat ).replace( /\$2/g, targetcat );
				sumCmtShort = '±[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]';
				break;
			case 'remove':
				text = text.replace( this.regexCatBuilder( targetcat ), '' );
				sumCmt = msg( 'summary-remove' ).replace( /\$1/g, targetcat );
				sumCmtShort = '-[[' + catNS + targetcat + ']]';
				break;
		}

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

		// 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' && ( !non || userGrp.indexOf( 'autoconfirmed' ) > -1 ) ) {
			if ( !this.is_hidden ) {
				text = this.removeUncat( text );
				if ( text.length !== otext.length ) { sumCmt += '; ' + msg( 'uncatpref' ); }
			}
			text = this.doCleanup( text );
		}

		sumCmt += this.summary ? ' ' + this.summary : '';

		var preM = msg( 'prefix-summary' );
		var usgM = msg( 'using-summary' );
		// Try shorten summary
		if ( preM || usgM )	{
			sumCmt = sumCmt.length > 250 - preM.length - usgM.length ?
				sumCmt + ' (CatAlot)' : preM + sumCmt + usgM;
		}

		if ( sumCmt.length > 254 ) // Try short summary
		{ sumCmt = sumCmtShort; }

		var data = {
			action: 'edit',
			assert: 'user',
			summary: sumCmt,
			title: file[ 0 ],
			text: text,
			bot: true,
			starttimestamp: this.starttimestamp,
			basetimestamp: timestamp,
			watchlist: this.settings.watchlist,
			minor: this.settings.minor,
			tags: this.changeTag,
			token: this.edittoken
		};

		this.doAPICall( data, function ( r ) {
			delete CAL.XHR[ file[ 0 ] ];
			return CAL.updateUndoCounter( r );
		} );
		this.markAsDone( file[ 1 ], mode, targetcat );
	},

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

	updateUndoCounter: function ( r ) {
		this.updateCounter();
		if ( !r.edit || r.edit.result !== 'Success' ) { return; }
		r = r.edit;

		this.undoList.push( {
			title: r.title,
			id: r.newrevid,
			timestamp: r.newtimestamp
		} );
	},

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

	displayResult: function () {
		document.body.style.cursor = 'auto';
		$.removeSpinner( 'fb-dialog' );
		this.progressDialog.parent()
			.addClass( 'cat_a_lot_done' )
			.find( '.ui-dialog-buttonpane button span' ).eq( 0 )
			.text( mw.msg( 'cat-a-lot-return-to-page' ) );
		var rep = this.domCounter.parent()
			.height( 'auto' )
			.html( '<h3>' + msg( 'done' ) + '</h3>' )
			.append( msg( 'all-done' ) + '<br>' );
		if ( this.alreadyThere.length ) {
			rep.append( '<h5>' + msg( 'skipped-already', this.alreadyThere.length ) + '</h5>' )
				.append( this.alreadyThere.join( '<br>' ) );
		}

		if ( this.notFound.length ) {
			rep.append( '<h5>' + msg( 'skipped-not-found', this.notFound.length ) + '</h5>' )
				.append( this.notFound.join( '<br>' ) );
		}

		if ( this.connectionError.length ) {
			rep.append( '<h5>' + msg( 'skipped-server', this.connectionError.length ) + '</h5>' )
				.append( this.connectionError.join( '<br>' ) );
		}

	},

	/**
*  @brief set parameters for API call,
*	  convert targetcat to string, get selected pages/files
*  @param [dom object] targetcat with data
*  @param [string] mode action
*  @return Return API call getTargetCat with pages
*/
	doSomething: function ( targetcat, mode ) {
		var pages = this.getMarkedLabels();
		if ( !pages.length ) { return alert( mw.msg( 'Ooui-selectfile-placeholder' ) ); }
		targetcat = $( targetcat ).closest( 'tr' ).data( 'cat' );

		this.notFound = [];
		this.alreadyThere = [];
		this.connectionError = [];
		this.counterCurrent = 1;
		this.counterNeeded = pages.length;
		this.undoList = [];
		this.XHR = {};
		this.cancelled = 0;
		this.summary = '';

		if ( $( '#cat_a_lot_comment' ).prop( 'checked' ) ) { this.summary = window.prompt( msg( 'edit-question' ), '' ); } // TODO custom pre-value
		if ( this.summary !== null ) {
			mw.loader.using( 'jquery.spinner', function () {
				CAL.showProgress();
				CAL.getTargetCat( pages, targetcat, mode );
			} );
		}

	},

	doAPICall: function ( params, callback ) {
		params = $.extend( {
			action: 'query',
			format: 'json'
		}, params );

		var i = 0,
			apiUrl = this.apiUrl,
			doCall,
			handleError = function ( jqXHR, textStatus, errorThrown ) {
				mw.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 () {
			var xhr = $.ajax( {
				url: apiUrl,
				cache: false,
				dataType: 'json',
				data: params,
				type: 'POST',
				success: callback,
				error: handleError
			} );

			if ( params.action === 'edit' && !CAL.cancelled ) { CAL.XHR[ params.title ] = xhr; }
		};
		doCall();
	},

	createCatLinks: function ( symbol, list, table ) {
		list.sort();
		var button = this.settings.button ? 1 : 0;
		for ( var c = 0; c < list.length; c++ ) {
			var $tr = $( '<tr>' ),
				$link = $( '<a>', {
					href: mw.util.getUrl( CAL.localCatName + list[ c ] ),
					text: list[ c ]
				} ),
				$buttons = [];
			$tr.data( 'cat', list[ c ] );
			$link.on( 'click', function ( e ) {
				if ( !e.ctrlKey ) {
					e.preventDefault();
					CAL.updateCats( $( this ).closest( 'tr' ).data( 'cat' ) );
				}
			} );

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

			$buttons.push( $( '<a>' )
				.text( msg( 'remove-from-cat' ) )
				.on( 'click', function () {
					CAL.doSomething( this, 'remove' );
				} )
				.addClass( 'cat_a_lot_move' )
			);
			if ( button ) {
				$buttons.slice( -1 )[ 0 ].button( {
					icons: { primary: 'ui-icon-minusthick' },
					showLabel: false,
					text: false
				} );
			}

			if ( this.origin ) {
			// Can't move to source category
				if ( list[ c ] !== this.origin ) {
					$buttons.push( $( '<a>' )
						.text( msg( 'move' ) )
						.on( 'click', function () {
							CAL.doSomething( this, 'move' );
						} )
						.addClass( 'cat_a_lot_move' )
					);
					if ( button ) {
						$buttons.slice( -1 )[ 0 ].button( {
							icons: { primary: 'ui-icon-arrowthick-1-e' },
							showLabel: false,
							text: false
						} );
					}

					$buttons.push( $( '<a>' )
						.text( msg( 'copy' ) )
						.on( 'click', function () {
							CAL.doSomething( this, 'copy' );
						} )
						.addClass( 'cat_a_lot_action' )
					);
					if ( button ) {
						$buttons.slice( -1 )[ 0 ].button( {
							icons: { primary: 'ui-icon-plusthick' },
							showLabel: false,
							text: false
						} );
					}

				}
			} else {
				$buttons.push( $( '<a>' )
					.text( msg( 'add' ) )
					.on( 'click', function () {
						CAL.doSomething( this, 'add' );
					} )
					.addClass( 'cat_a_lot_action' )
				);
				if ( button ) {
					$buttons.slice( -1 )[ 0 ].button( {
						icons: { primary: 'ui-icon-plusthick' },
						showLabel: false,
						text: false
					} );
				}

			}
			// TODO CSS may extern
			var css = button ? { fontSize: '.6em', margin: '0', width: '2.5em' } : {};
			for ( var b = 0; b < $buttons.length; b++ ) { $tr.append( $( '<td>' ).append( $buttons[ b ].css( css ) ) ); }

			table.append( $tr );
		}
	},

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

	_getPageQuery: function ( data ) {
	// There should be only one, but we don't know its ID
		if ( data && data.query && data.query.pages ) {
			data = data.query.pages;
			for ( var p in data ) { return data[ p ]; }
		}
	},

	/**
*  @brief takes this.currentCategory if redir_category is configured
** Cat pages with more than one cat link are still not supported for sure
*  @return soft redirected cat
*/
	solveSoftRedirect: function () {
		this.doAPICall( {
			prop: 'links', // TODO: For more accuracy the revisions could be checked
			titles: 'Category:' + this.currentCategory,
			// 'rvprop': 'content',
			// 'pllimit': 'max',
			plnamespace: 14
		}, function ( page ) {
			page = CAL._getPageQuery( page );
			if ( page ) {
				var lks = page.links;
				if ( lks && lks.length === 1 && lks[ 0 ].title ) {
					CAL.currentCategory = lks[ 0 ].title.replace( reCat, '' );
					$searchInput.val( CAL.currentCategory );
					return CAL.getCategoryList();
				} else {
				// TODO? better translatable warning message: "Please solve the category soft redirect manually!"
					$resultList.html( '<span id="cat_a_lot_no_found">' + mw.msg( 'Apifeatureusage-warnings', mw.msg( 'Categorytree-not-found', CAL.currentCategory ) ) + '</span>' );
				}
			}
		} );
	},

	showCategoryList: function () {
		if ( this.settings.redir_category && this.settings.redir_category === this.parentCats[ 0 ] ) { return this.solveSoftRedirect(); }

		var table = $( '<table>' );

		this.createCatLinks( '↑', this.parentCats, table );
		this.createCatLinks( '→', [ this.currentCategory ], table );
		// Show on soft-redirect
		if ( $searchInput.val() === this.currentCategory && this.origin !== this.currentCategory ) { this.createCatLinks( '→', [ this.origin ], table ); }
		this.createCatLinks( '↓', this.subCats, table );

		$resultList.empty();
		$resultList.append( table );

		document.body.style.cursor = 'auto';

		// Reset width
		$container.width( '' );
		$container.height( '' );
		$container.width( Math.min( table.width() * 1.1 + 15, $( window ).width() - 10 ) );

		$resultList.css( {
			maxHeight: Math.min( this.setHeight, $( window ).height() - $container.position().top - $selections.outerHeight() - 15 ),
			height: ''
		} );
		table.width( '100%' );
		$container.height( Math.min( $container.height(), $head.offset().top - $container.offset().top + 10 ) );
		$container.offset( { left: $( window ).width() - $container.outerWidth() } ); // Fix overlap
	},

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

	doUndo: function () {
		this.cancelled = 0;
		this.doAbort();
		if ( !this.undoList.length ) { return; }

		$( '.cat_a_lot_feedback' ).removeClass( 'cat_a_lot_done' );
		this.counterNeeded = this.undoList.length;
		this.counterCurrent = 1;

		document.body.style.cursor = 'wait';

		var query = {
			action: 'edit',
			user: mw.config.get( 'wgUserName' ),
			bot: true,
			minor: this.settings.minor,
			starttimestamp: this.starttimestamp,
			watchlist: this.settings.watchlist,
			tags: this.changeTag,
			token: this.edittoken
		};
		for ( var i = 0; i < this.undoList.length; i++ ) {
			var uID = this.undoList[ i ];
			query.title = uID.title;
			query.undo = uID.id;
			query.basetimestamp = uID.timestamp;
			this.doAPICall( query, function ( r ) {
			// TODO: Add "details" to progressbar?
			// $resultList.append( [mw.msg('Filerevert-submit') + " done " + r.edit.title, '<br>' ] );
				if ( r && r.edit ) { mw.log( 'Revert done', r.edit.title ); }
				CAL.updateCounter();
			} );
		}
	},

	doAbort: function () {
		for ( var t in this.XHR ) { this.XHR[ t ].abort(); }

		if ( this.cancelled ) { // still not for undo
			this.progressDialog.remove();
			this.toggleAll( false );
			$head.last().show();
		}
		this.cancelled = 1;
	},

	showProgress: function () {
		document.body.style.cursor = 'wait';
		this.progressDialog = $( '<div>' )
			.html( ' ' + msg( 'editing' ) + ' <span id="cat_a_lot_current">' + CAL.counterCurrent + '</span> ' + msg( 'of' ) + CAL.counterNeeded )
			.prepend( $.createSpinner( { id: 'fb-dialog', size: 'large' } ) )
			.dialog( {
				width: 450,
				height: 180,
				minHeight: 90,
				modal: true,
				resizable: false,
				draggable: false,
				// closeOnEscape: true,
				dialogClass: 'cat_a_lot_feedback',
				buttons: [ {
					text: mw.msg( 'Cancel' ), // Stops all actions
					click: function () {
						$( this ).dialog( 'close' );
					}
				} ],
				close: function () {
					CAL.cancelled = 1;
					CAL.doAbort();
					$( this ).remove();
				},
				open: function ( event, ui ) { // Workaround modify
					ui = $( this ).parent();
					ui.find( '.ui-dialog-titlebar' ).hide();
					ui.find( '.ui-dialog-buttonpane.ui-widget-content' )
						.removeClass( 'ui-widget-content' );
				/* .find( 'span' ).css( { fontSize: '90%' } )*/
				}
			} );
		if ( $head.children().length < 3 ) {
			$( '<span>' )
				.css( {
					'float': 'right',
					fontSize: '75%'
				} )
				.append( [ '[ ',
					$( '<a>', { title: 'Revert all last done edits' } ) // TODO i18n
						.on( 'click', function () {
							if ( window.confirm( mw.msg( 'Apifeatureusage-warnings', this.title + '⁉' ) ) ) {
								CAL.doUndo();
								$( this ).parent().remove();
							}
							return false;
						} )
						.addClass( 'new' )
						.text( mw.msg( 'Filerevert-submit' ) ),
					' ]'
				] ).insertAfter( $link );
		}

		this.domCounter = $( '#cat_a_lot_current' );
	},

	minimize: function ( e ) {
		CAL.top = Math.max( 0, $container.position().top );
		CAL.height = $container.height();
		$dataContainer.hide();
		$container.animate( {
			height: $head.height(),
			top: $( window ).height() - $head.height() * 1.4
		}, function () {
			$( e.target ).one( 'click', CAL.maximize );
		} );
	},

	maximize: function ( e ) {
		$dataContainer.show();
		$container.animate( {
			top: CAL.top,
			height: CAL.height
		}, function () {
			$( e.target ).one( 'click', CAL.minimize );
		} );
	},

	run: function () {
		if ( $( '.cat_a_lot_enabled' )[ 0 ] ) {
			this.makeClickable();
			if ( !this.executed ) { // only once
				$selectInvert.text( mw.msg( 'Checkbox-invert' ) );
				if ( this.settings.editpages && this.pageLabels[ 0 ] ) {
					$selectFiles.text( mw.msg( 'Prefs-files' ) );
					$selectPages.text( mw.msg( 'Categories' ) ).parent().show();
				}
				$link.after( $( '<a>' )
					.text( '–' )
					.css( { fontWeight: 'bold', marginLeft: '.7em' } )
					.one( 'click', this.minimize )
				);
			}
			$dataContainer.show();
			$container.one( 'mouseover', function () {
				$( this )
					.resizable( {
						handles: 'n',
						alsoResize: '#cat_a_lot_category_list',
						resize: function () {
							$resultList
								.css( {
									maxHeight: '',
									width: ''
								} );
						},
						start: function ( e, ui ) { // Otherwise box get static if sametime resize with draggable
							ui.helper.css( {
								top: ui.helper.offset().top - $( window ).scrollTop(),
								position: 'fixed'
							} );
						},
						stop: function () {
							CAL.setHeight = $resultList.height();
						}
					} )
					.draggable( {
						cursor: 'move',
						start: function ( e, ui ) {
							ui.helper.on( 'click.prevent',
								function ( e ) { e.preventDefault(); }
							);
							ui.helper.css( 'height', ui.helper.height() );
						},
						stop: function ( e, ui ) {
							setTimeout(
								function () {
									ui.helper.off( 'click.prevent' );
								}, 300
							);
						}
					} )
					.one( 'mousedown', function () {
						$container.height( $container.height() ); // Workaround to calculate
					} );
				$resultList
					.css( { maxHeight: 450 } );
			} );
			this.updateCats( this.origin || 'Images' );

			$link.html( $( '<span>' )
				.text( '×' )
				.css( { font: 'bold 2em monospace', lineHeight: '.75em' } )
			);
			$link.next().show();
			if ( this.cancelled ) { $head.last().show(); }
			mw.cookie.set( 'catAlotO', ns ); // Let stay open on new window
		} else { // Reset
			$dataContainer.hide();
			$container
				.draggable( 'destroy' )
				.resizable( 'destroy' )
				.removeAttr( 'style' );
			// Unbind click handlers
			this.labels.off( 'click.catALot' );
			this.setHeight = 450;
			$link.text( 'Cat-a-lot' )
				.nextAll().hide();
			this.executed = 1;
			mw.cookie.set( 'catAlotO', null );
		}
	},

	_initSettings: function () {
		if ( this.settings.watchlist ) { return; }
		this.catALotPrefs = window.catALotPrefs || {};
		for ( var i = 0; i < this.defaults.length; i++ ) {
			var v = this.defaults[ i ];
			v.value = this.settings[ v.name ] = this.catALotPrefs[ v.name ] || v['default'];
			v.label = msg( v.label_i18n );
			if ( v.select_i18n ) {
				v.select = {};
				$.each( v.select_i18n, function ( i18nk, val ) {
					v.select[ msg( i18nk ) ] = val;
				} );
			}
		}
	},
	/* eslint-disable camelcase */
	defaults: [ {
		name: 'watchlist',
		'default': 'preferences'
	}, {
		name: 'minor',
		'default': false
	}, {
		name: 'editpages',
		'default': project !== 'commonswiki', // on Commons false
		forcerestart: true
	}, {
		name: 'docleanup',
		'default': false
	}, {
		name: 'subcatcount',
		'default': 50,
		min: 5,
		max: 500,
		forcerestart: true
	}, {
		name: 'uncat',
		'default': false
	}, {
		name: 'button',
		'default': true
	} ]
/* eslint-enable camelcase */
};

// The gadget is not immediately needed, so let the page load normally
$( function () {
	non = mw.config.get( 'wgUserName' );
	if ( non ) {
		if ( mw.config.get( 'wgRelevantUserName' ) === non ) { non = 0; } else {
			$.each( [ 'sysop', 'filemover', 'editor', 'rollbacker', 'patroller', 'autopatrolled', 'image-reviewer', 'reviewer', 'extendedconfirmed' ], function ( i, v ) {
				non = $.inArray( v, userGrp ) === -1;
				return non;
			} );
		}
	} else { non = 1; }

	switch ( ns ) {
		case 14:
			CAL.searchmode = 'category';
			CAL.origin = mw.config.get( 'wgTitle' );
			break;
		/* 其他namespace无法正常使用
		case -1:
			CAL.searchmode = {
			// list of accepted special page names mapped to search mode names
				Contributions: 'contribs',
				Listfiles: non ? null : 'listfiles',
				Prefixindex: non ? null : 'prefix',
				Search: 'search',
				Uncategorizedimages: 'gallery'
			}[ mw.config.get( 'wgCanonicalSpecialPageName' ) ];
			break;
		case 2:
		case 0:
			CAL.searchmode = 'gallery';
			var parents = $( '#mw-normal-catlinks ul' ).find( 'a[title]' ), n;
			parents.each( function ( i ) {
				if ( new RegExp( mw.config.get( 'wgTitle' ), 'i' ).test( $( this ).text() ) ) {
					n = i;
					return false;
				}
			} );
			CAL.origin = parents.eq( n || 0 ).text();
		*/
	}

	if ( CAL.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: commonsURL,
				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: function() {
					mw.messages.set({
						'cat-a-lot-summary-add': wgULS('加入分类', '加入分類') + '[[Category:$1]]',
						'cat-a-lot-summary-copy': wgULS('分类间复制:从', '分類間複製:從') + '[[Category:$1]]到[[Category:$2]]',
						'cat-a-lot-summary-move': wgULS('分类间移动:从', '分類間移動:從') + '[[Category:$1]]到[[Category:$2]]',
						'cat-a-lot-summary-remove': wgULS('从分类', '從分類') + '移除:[[Category:$1]]'
					});
					cb();
				},
				error: cb
			} );
		};
		var maybeLaunch = function () {
			loadingLocalizations--;
			function init() {
				$( function () {
					CAL.init();
				} );
			}
			if ( !loadingLocalizations ) { init(); }
		};

		var userlang = mw.config.get( 'wgUserLanguage' );
		if ( userlang !== 'en' ) { loadLocalization( userlang, maybeLaunch ); }
		// if ( $.inArray( contlang, [ 'en', userlang ] ) === -1 ) { loadLocalization( contlang, maybeLaunch ); }
		maybeLaunch();
	}
} );

/**
 * When clicking a cat-a-lot label with Shift pressed, select all labels between the current and last-clicked one.
 */
$.fn.catALotShiftClick = function ( cb ) {
	var prevCheckbox = null,
		$box = this;
	// When our boxes are clicked..
	$box.on( 'click.catALot', function ( e ) {
	// Prevent following the link and text selection
		if ( !e.ctrlKey ) { 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>