مدیاویکی:Gadget-Cat-a-lot.js: تفاوت بین نسخه‌ها

از بانک اسلامشناسان
پرش به: ناوبری، جستجو
جز (Adminm صفحهٔ در حال ویرایش مدیاویکی:Gadget-Cat-a-lot.js را بدون برجای‌گذاشتن تغییرمسیر به مدیاویکی:Gadget-Cat-a-lot.js منتقل کرد)
سطر ۱: سطر ۱:
// <source lang="javascript">
+
/**
//
+
* Cat-a-lot
// Cat-A-Lot
+
* Changes category of multiple files
// Changes category of multiple files (or pages)
+
*
//
+
* @rev 00:13, 10 February 2018 (UTC)
// Originally by Magnus Manske
+
* @author Originally by Magnus Manske (2007)
// RegExes by Ilmari Karonen
+
* @author RegExes by Ilmari Karonen (2010)
// Completely rewritten by DieBuche
+
* @author Completely rewritten by DieBuche (2010-2012)
//
+
* @author Rillke (2012-2014)
// Modified to work on regular pages instead of files, + Hebrew translation: קיפודנחש
+
* @author Perhelion (2017)
//
+
 
// READ THIS PAGE IF YOU WANT TO TRANSLATE OR USE THIS ON ANOTHER SITE:
+
* Requires [[MediaWiki:Gadget-SettingsManager.js]] and [[MediaWiki:Gadget-SettingsUI.js]] (properly registered) for per-user-settings
// http://commons.wikimedia.org/wiki/MediaWiki_talk:Gadget-Cat-a-lot.js/translating
+
*
//
+
* READ THIS PAGE IF YOU WANT TO TRANSLATE OR USE THIS ON ANOTHER SITE:
var catALot = {
+
* http://commons.wikimedia.org/wiki/MediaWiki:Gadget-Cat-a-lot.js/translating
apiUrl: mw.config.get('wgScriptPath') + "/api.php",
+
* <nowiki>
 +
*/
 +
 
 +
/* 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 formattedNS = mw.config.get( 'wgFormattedNamespaces' ),
 +
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-watchlistpref': 'Watchlist preference concerning files edited with Cat-a-lot',
 +
'cat-a-lot-watch_pref': 'According to your general preferences',
 +
'cat-a-lot-watch_nochange': 'Do not change watchstatus',
 +
'cat-a-lot-watch_watch': 'Watch pages edited with Cat-a-lot',
 +
'cat-a-lot-watch_unwatch': 'Remove pages while editing with Cat-a-lot from your watchlist',
 +
'cat-a-lot-minorpref': 'Mark edits as minor (if you generally mark your edits as minor, this won’t change anything)',
 +
'cat-a-lot-editpagespref': 'Allow categorising pages (including categories) that are not files',
 +
'cat-a-lot-docleanuppref': 'Remove {{Check categories}} and other minor cleanup',
 +
'cat-a-lot-uncatpref': 'Remove {{Uncategorized}}',
 +
'cat-a-lot-subcatcountpref': 'Sub-categories to show at most',
 +
'cat-a-lot-config-settings': 'Preferences',
 +
'cat-a-lot-buttonpref': 'Use buttons instead of text-links',
 +
'cat-a-lot-comment-label': 'Custom edit comment',
 +
'cat-a-lot-edit-question': 'Why is this change necessary?',
 +
 
 +
// Progress
 +
// 'cat-a-lot-loading': 'Loading …',
 +
'cat-a-lot-editing': 'Editing page',
 +
'cat-a-lot-of': 'of ',
 +
'cat-a-lot-skipped-already': 'The following {{PLURAL:$1|1=page was|$1 pages were}} skipped, because the page was already in the category:',
 +
'cat-a-lot-skipped-not-found': 'The following {{PLURAL:$1|1=page was|$1 pages were}} skipped, because the old category could not be found:',
 +
'cat-a-lot-skipped-server': 'The following {{PLURAL:$1|1=page|$1 pages}} couldn’t be changed, since there were problems connecting to the server:',
 +
'cat-a-lot-all-done': 'All pages are processed.',
 +
'cat-a-lot-done': 'Done!', // 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',
 +
// 'cat-a-lot-cat-not-found': 'Category not found.',
 +
 
 +
// as in 17 files selected
 +
'cat-a-lot-files-selected': '{{PLURAL:$1|1=One file|$1 files}} selected.',
 +
'cat-a-lot-pe_file': '$1 {{PLURAL:$1|page|pages}} of $2 affected',
 +
'cat-a-lot-parent-cat': 'Has parent-category: ',
 +
'cat-a-lot-sub-cat': 'Has sub-category: ',
 +
 
 +
// Actions
 +
'cat-a-lot-copy': 'Copy',
 +
'cat-a-lot-move': 'Move',
 +
'cat-a-lot-add': 'Add',
 +
// 'cat-a-lot-remove-from-cat': 'Remove from this category',
 +
'cat-a-lot-overcat': 'Check over-categorization',
 +
'cat-a-lot-enter-name': 'Enter category name',
 +
'cat-a-lot-select': 'Select',
 +
'cat-a-lot-all': 'all',
 +
'cat-a-lot-none': 'none',
 +
// 'cat-a-lot-none-selected': 'No files selected!', 'Ooui-selectfile-placeholder'
 +
 
 +
// Summaries (project language):
 +
'cat-a-lot-pref-save-summary': 'Updating user preferences',
 +
'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-using-summary': ' using [[c:Help:Cat-a-lot|Cat-a-lot]]'
 +
};
 +
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, $settingsWrapper, $settingsLink, $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,
 
searchmode: false,
version: 2.18,
+
version: '4.77',
 
setHeight: 450,
 
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: 'Disambiguation', // 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: 'Category redirects'
 +
 +
},
 +
 
init: function () {
 
init: function () {
$("body")
+
// Prevent historical double marker (maybe remove in future)
.append('<div id="cat_a_lot">' + '<div id="cat_a_lot_data"><div>' + '<input type="text" id="cat_a_lot_searchcatname" placeholder="' + this.i18n.enterName + '"/>'
+
if ( /Cat-?a-?lot/i.test( msgs[ 'cat-a-lot-pref-save-summary' ] ) ) { mw.messages.set( { 'cat-a-lot-prefix-summary': '', 'cat-a-lot-using-summary': '' } ); } else {
+ '</div><div id="cat_a_lot_category_list"style="white-space:nowrap;"></div>' + '<div id="cat_a_lot_mark_counter"> </div>' + '<div id="cat_a_lot_selections">' + this.i18n.select
+
mw.messages.set( {
+ ' <a id="cat_a_lot_select_all">' + this.i18n.all + '</a> / ' + '<a id="cat_a_lot_select_none">' + this.i18n.none + '</a>'
+
'cat-a-lot-pref-save-summary': msgs[ 'cat-a-lot-prefix-summary' ] + msgs[ 'cat-a-lot-pref-save-summary' ] + msgs[ 'cat-a-lot-using-summary' ]
+ '</div></div><div id="cat_a_lot_head">' + '<a id="cat_a_lot_toggle">Cat-a-lot</a></div></div>');
+
} );
 +
}
 +
 
 +
// 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 );
 +
$settingsWrapper = $( '<div>' )
 +
.attr( 'id', 'cat_a_lot_settings' )
 +
.appendTo( $dataContainer );
 +
$settingsLink = $( '<a>', {
 +
id: 'cat_a_lot_config_settings',
 +
title: 'Version ' + this.version,
 +
text: msg( 'config-settings' )
 +
} )
 +
.appendTo( $settingsWrapper );
 +
$head = $( '<div>' )
 +
.attr( 'id', 'cat_a_lot_head' )
 +
.appendTo( $container );
 +
$link = $( '<a>' )
 +
.attr( 'id', 'cat_a_lot_toggle' )
 +
.text( 'Cat-a-lot' )
 +
.appendTo( $head );
 +
$settingsWrapper.append( $( '<a>', {
 +
href: commonsURL + '?title=Special:MyLanguage/Help:Gadget-Cat-a-lot',
 +
target: '_blank',
 +
style: 'float:right',
 +
title: ( $( '#n-help a' ).attr( 'title' ) || '' ) + ' (v. ' + this.version + ')'
 +
} ).text( '?' ) );
 +
$container.one( 'mouseover', function () { // Try load on demand earliest as possible
 +
mw.loader.load( [ 'jquery.ui.resizable', 'jquery.ui.draggable' ] );
 +
} );
 +
 
 +
if ( this.origin && !non ) {
 +
$overcat = $( '<a>' )
 +
.attr( 'id', 'cat_a_lot_overcat' )
 +
.html( msg( 'overcat' ) )
 +
.on( 'click', function ( e ) {
 +
CAL.getOverCat( e );
 +
} )
 +
.insertBefore( $selections );
 +
}
 +
 
 +
if ( ( mw.util.getParamValue( 'withJS' ) === 'MediaWiki:Gadget-Cat-a-lot.js' &&
 +
!mw.util.getParamValue( 'withCSS' ) ) ||
 +
mw.loader.getState( 'ext.gadget.Cat-a-lot' ) === 'registered' ) {
 +
mw.loader.load( mw.config.get( 'wgServer' ) + '/w/index.php?title=MediaWiki:Gadget-Cat-a-lot.css&action=raw&ctype=text/css', 'text/css' );
 +
// importStylesheet( 'MediaWiki:Gadget-Cat-a-lot.css' );
 +
}
  
if (!this.searchmode) $('#cat_a_lot_selections').append('<br><a id="cat_a_lot_remove"><b>' + this.i18n.removeFromCat + '</b></a>');
+
reCat = new RegExp( '^\\s*' + CAL.localizedRegex( 14, 'Category' ) + ':', '' );
$('#cat_a_lot_remove').click(function () {
+
 
catALot.remove();
+
$searchInput.on( 'keypress', function ( e ) {
});
+
if ( e.which === 13 ) {
$('#cat_a_lot_select_all').click(function () {
+
CAL.updateCats( $.trim( $( this ).val().replace( /[\u200E\u200F\u202A-\u202E]/g, '' ) ) );
catALot.toggleAll(true);
+
mw.cookie.set( 'catAlot', CAL.currentCategory );
});
+
}
$('#cat_a_lot_select_none').click(function () {
+
} )
catALot.toggleAll(false);
+
.on( 'input keyup', function () {
});
+
var oldVal = this.value,
$('#cat_a_lot_toggle').click(function () {
+
newVal = oldVal.replace( reCat, '' );
$(this).toggleClass('cat_a_lot_enabled');
+
if ( newVal !== oldVal ) { this.value = newVal; }
catALot.run();
+
 
});
+
if ( !newVal ) { mw.cookie.set( 'catAlot', null ); }
$('#cat_a_lot_searchcatname')
+
} );
.keypress(function (e) {
+
 
if (e.which == 13)
+
function initAutocomplete() {
catALot.updateCats($(this).val());
+
if ( CAL.autoCompleteIsEnabled ) { return; }
})
+
 
.autocomplete({
+
CAL.autoCompleteIsEnabled = true;
source: function(request, response) {
+
 
catALot.doAPICall({action:'opensearch', search: request.term, namespace: 14},
+
if ( !$searchInput.val() && mw.cookie && mw.cookie.get( 'catAlot' ) ) { $searchInput.val( mw.cookie.get( 'catAlot' ) ); }
function(data){
+
 
if(data[1])
+
$searchInput.autocomplete( {
response($(data[1]).map(function(index,item){return item.replace(/.*:/, '');}));
+
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
 +
} );
 
},
 
},
open:function(){
+
appendTo: '#cat_a_lot'
$(".ui-autocomplete")
+
} );
.position({
+
}
my: "left bottom",
+
$( '<a>' )
at: "left top",
+
// .attr( 'id', 'cat_a_lot_select_all' )
of: $('#cat_a_lot_searchcatname')
+
.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>' )
 +
// .attr( 'id', 'cat_a_lot_select_none' )
 +
.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
 +
mw.loader.using( 'jquery.ui.autocomplete', initAutocomplete );
 +
 
 +
if ( !CAL.executed ) {
 +
$.when( mw.loader.using( [
 +
'jquery.ui.resizable',
 +
'jquery.ui.draggable',
 +
'jquery.ui.button',
 +
'mediawiki.api.messages',
 +
'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(); }
 +
} );
 +
$settingsLink
 +
.on( 'click', CAL.manageSettings );
 +
this.localCatName = formattedNS[ 14 ] + ':';
 +
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++; }
 
}
 
}
});
+
} );
importStylesheet('MediaWiki:Gadget-Cat-a-lot.css');
+
} else { mw.log( 'Api-fail', file, data ); }
this.localCatName = mw.config.get('wgFormattedNamespaces')[14];
+
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 () {
+
 
this.labels = $('#mw-pages').find('li');
+
findAllLabels: function ( searchmode ) {
var subCats = $('#mw-subcategories').find('.CategoryTreeItem');
+
// 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
for (var sub = 0; sub < subCats.length; sub++) {
+
switch ( searchmode ) {
var a = $(subCats[sub]).find('a');
+
case 'search':
if (a.length) {
+
this.labels = this.labels.add( $( 'table.searchResultImage' ).find( 'tr>td:eq(1)' ) );
a[0]['title'] = catALot.localCatName + ":" + a[0].innerHTML;
+
if ( this.settings.editpages ) { this.labels = this.labels.add( 'div.mw-search-result-heading' ); }
this.labels.push(subCats[sub]);
+
 
}
+
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( /wiki\/(.+?)(?:#.+)?$/ )[ 1 ].replace( /_/g, ' ' );
 +
} catch ( ex ) {
 +
return '';
 
}
 
}
$('li a, .CategoryTreeLabel', mw.util.$content).attr({href: '#noSuchAnchor'});
 
 
},
 
},
  
 +
/**
 +
*  @brief Get title from selected pages
 +
*  @return [array] touple of page title and $object
 +
*/
 
getMarkedLabels: function () {
 
getMarkedLabels: function () {
var marked = [];
+
this.selectedLabels = this.labels.filter( '.cat_a_lot_selected:visible' );
this.selectedLabels = this.labels.filter('.cat_a_lot_selected');
+
return this.selectedLabels.map( function () {
this.selectedLabels.each(function () {
+
var label = $( this ), file = label.find( 'a[title][class$="title"]' );
var file = $(this).find('a[title]');
+
file = file.length ? file : label.find( 'a[title]' );
marked.push([file.attr('title'), $(this)]);
+
var title = file.attr( 'title' ) ||
});
+
CAL.getTitleFromLink( file ) ||
return marked;
+
CAL.getTitleFromLink( label.find( 'a' ) ) ||
 +
CAL.getTitleFromLink( label.parent().find( 'a' ) ); // TODO needs optimization
 +
if ( title.indexOf( formattedNS[ 2 ] + ':' ) ) { return [ [ title, label ] ]; }
 +
} );
 
},
 
},
  
 
updateSelectionCounter: function () {
 
updateSelectionCounter: function () {
this.selectedLabels = this.labels.filter('.cat_a_lot_selected');
+
this.selectedLabels = this.labels.filter( '.cat_a_lot_selected:visible' );
$('#cat_a_lot_mark_counter').show().html(this.selectedLabels.length + this.i18n.filesSelected );
+
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 () {
 
makeClickable: function () {
this.findAllLabels();
+
this.labels = $();
this.labels.click(function () {
+
this.pageLabels = $(); // only for distinct all selections
$(this).toggleClass('cat_a_lot_selected');
+
this.findAllLabels( this.searchmode );
catALot.updateSelectionCounter();
+
this.labels.catALotShiftClick( function () {
});
+
CAL.updateSelectionCounter();
 +
} )
 +
.addClass( 'cat_a_lot_label' );
 
},
 
},
  
toggleAll: function (select) {
+
toggleAll: function ( select ) {
this.labels.toggleClass('cat_a_lot_selected', select);
+
if ( typeof select === 'string' && this.pageLabels[ 0 ] ) {
 +
this.pageLabels.toggleClass( 'cat_a_lot_selected', true );
 +
if ( select === 'files' ) // pages get deselected
 +
{ this.labels.toggleClass( 'cat_a_lot_selected' ); }
 +
} else {
 +
// invert / none / all
 +
this.labels.toggleClass( 'cat_a_lot_selected', select );
 +
}
 
this.updateSelectionCounter();
 
this.updateSelectionCounter();
 
},
 
},
  
getSubCats: function (cmcontinue) {
+
getSubCats: function () {
 
var data = {
 
var data = {
action: 'query',
 
 
list: 'categorymembers',
 
list: 'categorymembers',
cmnamespace: 14,
+
cmtype: 'subcat',
cmlimit: 50,
+
cmlimit: this.settings.subcatcount,
cmtitle: this.localCatName + ':' + this.currentCategory
+
cmtitle: 'Category:' + this.currentCategory
 
};
 
};
if (cmcontinue)
 
data.cmcontinue = cmcontinue;
 
else
 
this.subCats = [];
 
  
this.doAPICall(data, function (result) {
+
this.doAPICall( data, function ( result ) {
 +
var cats = result.query.categorymembers;
 +
CAL.subCats = [];
 +
for ( var i = 0; i < cats.length; i++ ) { CAL.subCats.push( cats[ i ].title.replace( /^[^:]+:/, '' ) ); }
  
var cats = result.query.categorymembers;
+
CAL.catCounter++;
 +
if ( CAL.catCounter === 2 ) { CAL.showCategoryList(); }
  
for (var i = 0; i < cats.length; i++) {
+
} );
catALot.subCats.push(cats[i].title.replace(/^[^:]+:/, ""));
 
}
 
if (result['query-continue'])
 
catALot.getSubCats(result['query-continue'].categorymembers.cmcontinue);
 
else {
 
catALot.catCounter++;
 
if (catALot.catCounter == 2) catALot.showCategoryList();
 
}
 
});
 
 
},
 
},
 
  
 
getParentCats: function () {
 
getParentCats: function () {
 
var data = {
 
var data = {
action: 'query',
 
 
prop: 'categories',
 
prop: 'categories',
cllimit: 500,
+
titles: 'Category:' + this.currentCategory
titles: this.localCatName + ':' + this.currentCategory
 
 
};
 
};
this.doAPICall(data, function (result) {
+
this.doAPICall( data, function ( result ) {
catALot.parentCats = new Array();
+
CAL.parentCats = [];
var pages = result.query.pages;
+
var cats,
if (pages[-1] && pages[-1].missing == '') {
+
pages = result.query.pages,
catALot.catlist.html('<span id="cat_a_lot_no_found">' + catALot.i18n.catNotFound + '</span>');
+
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';
 
document.body.style.cursor = 'auto';
 
+
CAL.createCatLinks( '', [ CAL.currentCategory ], table );
catALot.catlist.append('<ul></ul>');
+
$resultList.append( table );
catALot.createCatLinks("→", [catALot.currentCategory]);
 
 
return;
 
return;
 
}
 
}
 
// there should be only one, but we don't know its ID
 
// there should be only one, but we don't know its ID
for (var id in pages) {
+
for ( var id in pages ) { cats = pages[ id ].categories || []; }
var 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++) {
+
 
catALot.parentCats.push(cats[i].title.replace(/^[^:]+:/, ""));
+
CAL.catCounter++;
 +
if ( CAL.catCounter === 2 ) { CAL.showCategoryList(); }
 +
 
 +
} );
 +
},
 +
 
 +
localizedRegex: function ( namespaceNumber, fallback ) {
 +
// Copied from HotCat, thanks Lupo.
 +
var wikiTextBlank = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+';
 +
var wikiTextBlankRE = new RegExp( wikiTextBlank, 'g' );
 +
var createRegexStr = function ( name ) {
 +
if ( !name || !name.length ) { 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 = formattedNS[ namespaceNumber ].toLowerCase();
 +
var RegexString = createRegexStr( canonical );
 +
if ( fallback && canonical !== fallback ) { RegexString += '|' + createRegexStr( fallback ); }
 +
 +
for ( var catName in nsIDs ) { if ( typeof catName === 'string' && catName.toLowerCase() !== canonical && catName.toLowerCase() !== fallback && nsIDs[ catName ] === namespaceNumber ) { RegexString += '|' + createRegexStr( catName ); } }
  
catALot.catCounter++;
+
return ( '(?:' + RegexString + ')' );
if (catALot.catCounter == 2) catALot.showCategoryList();
 
});
 
 
},
 
},
regexBuilder: function (category) {
+
 
var catname = ( this.localCatName == 'Category' ) ? this.localCatName: this.localCatName + '|Category';
+
regexCatBuilder: function ( category ) {
catname = '(' + catname + ')';
+
var catname = this.localizedRegex( 14, 'Category' );
  
 
// Build a regexp string for matching the given category:
 
// Build a regexp string for matching the given category:
 
// trim leading/trailing whitespace and underscores
 
// trim leading/trailing whitespace and underscores
category = category.replace(/^[\s_]+/, "").replace(/[\s_]+$/, "");
+
category = category.replace( /^[\s_]+|[\s_]+$/g, '' );
  
 
// escape regexp metacharacters (= any ASCII punctuation except _)
 
// escape regexp metacharacters (= any ASCII punctuation except _)
category = category.replace(/([!-\/:-@\[-^`{-~])/g, '\\$1');
+
category = mw.RegExp.escape( category );
  
 
// any sequence of spaces and underscores should match any other
 
// any sequence of spaces and underscores should match any other
category = category.replace(/[\s_]+/g, '[\\s_]+');
+
category = category.replace( /[\s_]+/g, '[\\s_]+' );
  
 
// Make the first character case-insensitive:
 
// Make the first character case-insensitive:
var first = category.substr(0, 1);
+
var first = category.substr( 0, 1 );
if (first.toUpperCase() != first.toLowerCase()) category = '[' + first.toUpperCase() + first.toLowerCase() + ']' + category.substr(1);
+
if ( first.toUpperCase() !== first.toLowerCase() ) { category = '[' + first.toUpperCase() + first.toLowerCase() + ']' + category.substr( 1 ); }
  
 
// Compile it into a RegExp that matches MediaWiki category syntax (yeah, it looks ugly):
 
// Compile it into a RegExp that matches MediaWiki category syntax (yeah, it looks ugly):
 
// XXX: the first capturing parens are assumed to match the sortkey, if present, including the | but excluding the ]]
 
// XXX: the first capturing parens are assumed to match the sortkey, if present, including the | but excluding the ]]
return new RegExp('\\[\\[[\\s_]*' + catname + '[\\s_]*:[\\s_]*' + category + '[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]', 'ig');
+
return new RegExp( '\\[\\[[\\s_]*' + catname + '[\\s_]*:[\\s_]*' + category + '[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]\\s*', 'g' );
 +
},
 +
 
 +
getContent: function ( 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 );
 +
} );
 +
}
 +
 
 
},
 
},
  
getContent: function (file, 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;
 +
}
 +
}
 +
}
 +
}
  
var data = {
+
if ( !is_redir && !is_dab ) { return; }
action: 'query',
+
alert( mw.msg( 'Apifeatureusage-warnings', page.title + ' is a ' + CAL.disambig_category ) );
prop: 'info|revisions',
+
},
rvprop: 'content|timestamp',
 
intoken: 'edit',
 
titles: file[0]
 
};
 
  
this.doAPICall(data, function (result) {
+
// Remove {{Uncategorized}} (also with comment). No need to replace it with anything.
catALot.editCategories(result, file, targetcat, mode);
+
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 ) {
 +
return ( this.settings.docleanup ? text.replace( /\{\{\s*[Cc]heck categories\s*(\|?.*?)\}\}/, '' ) : text );
 +
},
  
if (result == null) {
+
editCategories: function ( result, file, targetcat, mode ) {
//Happens on unstable wifi connections..
+
if ( !result || !result.query ) {
this.connectionError.push(file[0]);
+
// Happens on unstable wifi connections..
 +
this.connectionError.push( file[ 0 ] );
 
this.updateCounter();
 
this.updateCounter();
 
return;
 
return;
 
}
 
}
var pages = result.query.pages;
+
var otext,
 
+
timestamp,
// there should be only one, but we don't know its ID
+
page = CAL._getPageQuery( result );
for (var id in pages) {
+
if ( page.ns === 2 ) { return; }
// The edittoken only changes between logins
+
var id = page.revisions[ 0 ],
this.edittoken = pages[id].edittoken;
+
catNS = this.localCatName; // canonical cat-name
var otext = pages[id].revisions[0]['*'];
 
var starttimestamp = pages[id].starttimestamp;
 
var timestamp = pages[id].revisions[0]['timestamp'];
 
}
 
 
 
  
var sourcecat = mw.config.get('wgTitle');
+
this.starttimestamp = result.curtimestamp;
 +
otext = id[ '*' ];
 +
timestamp = id.timestamp;
  
 +
var sourcecat = this.origin;
 
// Check if that file is already in that category
 
// Check if that file is already in that category
if (mode != "remove" && this.regexBuilder(targetcat).test(otext)) {
+
if ( mode !== 'remove' && this.regexCatBuilder( targetcat ).test( otext ) ) {
//If the new cat is already there, just remove the old one.
+
// If the new cat is already there, just remove the old one
if (mode == 'move') {
+
if ( mode === 'move' ) {
mode='remove';
+
mode = 'remove';
 +
targetcat = sourcecat;
 
} else {
 
} else {
this.alreadyThere.push(file[0]);
+
this.alreadyThere.push( file[ 0 ] );
 
this.updateCounter();
 
this.updateCounter();
 
return;
 
return;
سطر ۲۳۸: سطر ۷۲۴:
 
}
 
}
  
var text = otext;
+
// Text modification (following 3 functions are partialy taken from HotCat)
var comment;
+
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 ) {
 +
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
 
// Fix text
switch (mode) {
+
switch ( mode ) {
case 'add':
+
case 'add':
text += "\n[[" + this.localCatName + ":" + targetcat.replace("رده:","") + "]]\n";
+
text = addCategory( text, targetcat );
                        text=text.replace("رده:رده:","رده:");
+
sumCmt = msg( 'summary-add' ).replace( '$1', targetcat );
comment = this.i18n.summaryAdd + targetcat.replace("رده:","") + "]]";
+
sumCmtShort = '+[[' + catNS + targetcat + ']]';
break;
+
break;
case 'copy':
+
case 'copy':
text = text.replace(this.regexBuilder(sourcecat), "[[" + this.localCatName + ":" + sourcecat + "$2]]\n[[" + this.localCatName + ":" + targetcat.replace("رده:","") + "$2]]");
+
text = text.replace( this.regexCatBuilder( sourcecat ), '[[' + catNS + sourcecat + '$1]]\n[[' + catNS + targetcat + '$1]]\n' );
                        text=text.replace("رده:رده:","رده:");
+
sumCmt = msg( 'summary-copy' ).replace( '$1', sourcecat ).replace( '$2', targetcat );
comment = this.i18n.summaryCopy + sourcecat + "]] " + this.i18n.to + targetcat.replace("رده:","") + "]]";
+
sumCmtShort = '+[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]';
break;
+
// If category is added through template:
case 'move':
+
if ( otext === text ) { text = addCategory( text, targetcat ); }
text = text.replace(this.regexBuilder(sourcecat), "[[" + this.localCatName + ":" + targetcat.replace("رده:","") + "$2]]");
+
 
                        text=text.replace("رده:رده:","رده:");
+
break;
comment = this.i18n.summaryMove + sourcecat + "]] " + this.i18n.to + targetcat.replace("رده:","") + "]]";
+
case 'move':
break;
+
text = text.replace( this.regexCatBuilder( sourcecat ), '[[' + catNS + targetcat + '$1]]\n' );
case 'remove':
+
sumCmt = msg( 'summary-move' ).replace( '$1', sourcecat ).replace( '$2', targetcat );
text = text.replace(this.regexBuilder(sourcecat), "");
+
sumCmtShort = '±[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]';
                        text=text.replace("رده:رده:","رده:");
+
break;
comment = this.i18n.summaryRemove + sourcecat + "]]";
+
case 'remove':
break;
+
text = text.replace( this.regexCatBuilder( targetcat ), '' );
 +
sumCmt = msg( 'summary-remove' ).replace( '$1', targetcat );
 +
sumCmtShort = '-[[' + catNS + targetcat + ']]';
 +
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;
 +
 +
// 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 = {
 
var data = {
 
action: 'edit',
 
action: 'edit',
summary: comment,
+
assert: 'user',
title: file[0],
+
summary: sumCmt,
token: this.edittoken,
+
title: file[ 0 ],
starttimestamp: starttimestamp,
+
text: text,
 +
bot: true,
 +
starttimestamp: this.starttimestamp,
 
basetimestamp: timestamp,
 
basetimestamp: timestamp,
text: text
+
watchlist: this.settings.watchlist,
 +
minor: this.settings.minor,
 +
tags: this.changeTag,
 +
token: this.edittoken
 
};
 
};
  
var isBot=$.inArray('bot', mw.config.get('wgUserGroups'))>-1;
+
this.doAPICall( data, function ( r ) {
if(isBot)
+
delete CAL.XHR[ file[ 0 ] ];
data.bot = '1';
+
return CAL.updateUndoCounter( r );
 +
} );
 +
this.markAsDone( file[ 1 ], mode, targetcat );
 +
},
  
this.doAPICall(data, function (ret) {
+
markAsDone: function ( label, mode, targetcat ) {
catALot.updateCounter();
+
mode = ( function ( m ) {
});
+
switch ( m ) {
this.markAsDone(file[1], mode, targetcat);
+
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 ) );
 
},
 
},
markAsDone: function (label, mode, targetcat) {
 
  
label.addClass('cat_a_lot_markAsDone');
+
updateUndoCounter: function ( r ) {
switch (mode) {
+
this.updateCounter();
case 'add':
+
if ( !r.edit || r.edit.result !== 'Success' ) { return; }
label.append('<br>' + this.i18n.addedCat + ' ' + targetcat);
+
r = r.edit;
break;
+
 
case 'copy':
+
this.undoList.push( {
label.append('<br>' + this.i18n.copiedCat + ' ' + targetcat);
+
title: r.title,
break;
+
id: r.newrevid,
case 'move':
+
timestamp: r.newtimestamp
label.append('<br>' + this.i18n.movedCat + ' ' + targetcat);
+
} );
break;
 
case 'remove':
 
label.append('<br>' + this.i18n.movedCat );
 
break;
 
}
 
 
},
 
},
 +
 
updateCounter: function () {
 
updateCounter: function () {
 
 
this.counterCurrent++;
 
this.counterCurrent++;
if (this.counterCurrent > this.counterNeeded) this.displayResult();
+
if ( this.counterCurrent > this.counterNeeded ) { this.displayResult(); } else { this.domCounter.text( this.counterCurrent ); }
else this.domCounter.text(this.counterCurrent);
 
 
},
 
},
  
 
displayResult: function () {
 
displayResult: function () {
 
 
document.body.style.cursor = 'auto';
 
document.body.style.cursor = 'auto';
$('.cat_a_lot_feedback').addClass('cat_a_lot_done');
+
$.removeSpinner( 'fb-dialog' );
$('.ui-dialog-content').height('auto');
+
this.progressDialog.parent()
var rep = this.domCounter.parent();
+
.addClass( 'cat_a_lot_done' )
rep.html('<h3>' + this.i18n.done + '</h3>');
+
.find( '.ui-dialog-buttonpane button span' ).eq( 0 )
rep.append( this.i18n.allDone + '<br />');
+
.text( mw.msg( 'Mobile-frontend-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>' ) );
 +
}
  
var close = $('<a>').append( this.i18n.returnToPage );
+
if ( this.notFound.length ) {
close.click(function () {
+
rep.append( '<h5>' + msg( 'skipped-not-found', this.notFound.length ) + '</h5>' )
catALot.progressDialog.remove();
+
.append( this.notFound.join( '<br>' ) );
catALot.toggleAll(false);
 
});
 
rep.append(close);
 
if (this.alreadyThere.length) {
 
rep.append( this.i18n.skippedAlready );
 
rep.append(this.alreadyThere.join('<br>'));
 
 
}
 
}
if (this.notFound.length) {
+
 
rep.append( this.i18n.skippedNotFound );
+
if ( this.connectionError.length ) {
rep.append(this.notFound.join('<br>'));
+
rep.append( '<h5>' + msg( 'skipped-server', this.connectionError.length ) + '</h5>' )
 +
.append( this.connectionError.join( '<br>' ) );
 
}
 
}
if (this.connectionError.length) {
 
rep.append( this.i18n.skippedServer );
 
rep.append(this.connectionError.join('<br>'));
 
}
 
 
},
 
 
moveHere: function (targetcat) {
 
this.doSomething(targetcat, 'move');
 
},
 
 
copyHere: function (targetcat) {
 
this.doSomething(targetcat, 'copy');
 
},
 
  
addHere: function (targetcat) {
 
this.doSomething(targetcat, 'add');
 
 
},
 
},
  
remove: function () {
+
/**
this.doSomething('', 'remove');
+
*  @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' );
  
doSomething: function (targetcat, mode) {
 
var files = this.getMarkedLabels();
 
if (files.length == 0) {
 
alert( this.i18n.noneSelected );
 
return;
 
}
 
 
this.notFound = [];
 
this.notFound = [];
 
this.alreadyThere = [];
 
this.alreadyThere = [];
 
this.connectionError = [];
 
this.connectionError = [];
 
this.counterCurrent = 1;
 
this.counterCurrent = 1;
this.counterNeeded = files.length;
+
this.counterNeeded = pages.length;
this.showProgress();
+
this.undoList = [];
for (var i = 0; i < files.length; i++) {
+
this.XHR = {};
this.getContent(files[i], targetcat, mode);
+
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.ui.dialog', 'jquery.spinner', 'mediawiki.RegExp' ], function () {
 +
CAL.showProgress();
 +
CAL.getTargetCat( pages, targetcat, mode );
 +
} );
 
}
 
}
 +
 
},
 
},
  
doAPICall: function (params, callback) {
+
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
 +
} );
  
params.format = 'json';
+
if ( params.action === 'edit' && !CAL.cancelled ) { CAL.XHR[ params.title ] = xhr; }
$.ajax({
+
};
url: this.apiUrl,
+
doCall();
cache: false,
 
dataType: 'json',
 
data: params,
 
type: 'POST',
 
success: callback
 
});
 
 
},
 
},
  
createCatLinks: function (symbol, list) {
+
createCatLinks: function ( symbol, list, table ) {
 
list.sort();
 
list.sort();
var domlist = this.catlist.find('ul');
+
var button = ( this.settings.button && mw.loader.getState( 'jquery.ui.button' ) === 'ready' ) ? 1 : 0;
for (var i = 0; i < list.length; i++) {
+
for ( var c = 0; c < list.length; c++ ) {
var li = $('<li></li>');
+
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( mw.msg( 'Centralnotice-remove' ) )
 +
.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
 +
} );
 +
}
  
var link = $('<a></a>');
+
if ( this.origin ) {
link.text(list[i]);
+
// Can't move to source category
li.data('cat', list[i]);
+
if ( list[ c ] !== this.origin ) {
link.click(function () {
+
$buttons.push( $( '<a>' )
catALot.updateCats($(this).parent().data('cat'));
+
.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
 +
} );
 +
}
  
if (this.searchmode) {
+
$buttons.push( $( '<a>' )
var add = $('<a class="cat_a_lot_action"><b>' + this.i18n.add + '</b></a>');
+
.text( msg( 'copy' ) )
add.click(function () {
+
.on( 'click', function () {
catALot.addHere($(this).parent().data('cat'));
+
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 {
 
} else {
var move = $('<a class="cat_a_lot_move"><b>' + this.i18n.move + '</b></a>');
+
$buttons.push( $( '<a>' )
move.click(function () {
+
.text( msg( 'add' ) )
catALot.moveHere($(this).parent().data('cat'));
+
.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
 +
} );
 +
}
  
var copy = $('<a class="cat_a_lot_action"><b>' + this.i18n.copy + '</b></a>');
 
copy.click(function () {
 
catALot.copyHere($(this).parent().data('cat'));
 
});
 
 
}
 
}
 +
// 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 ) ) ); }
  
// Can't move to source category
+
table.append( $tr );
if (list[i] != mw.config.get('wgTitle') && this.searchmode) li.append(' ').append(add);
 
else if (list[i] != mw.config.get('wgTitle') && !this.searchmode) li.append(' ').append(move).append(' ').append(copy);
 
li.append(symbol).append(' ').append(link);
 
 
 
domlist.append(li);
 
 
}
 
}
 
},
 
},
سطر ۴۳۴: سطر ۱٬۰۸۵:
 
this.getParentCats();
 
this.getParentCats();
 
this.getSubCats();
 
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 () {
 
showCategoryList: function () {
var thiscat = [this.currentCategory];
+
if ( this.settings.redir_category && this.settings.redir_category === this.parentCats[ 0 ] ) { return this.solveSoftRedirect(); }
  
this.catlist.empty();
+
var table = $( '<table>' );
this.catlist.append('<ul></ul>');
 
  
this.createCatLinks("", this.parentCats);
+
this.createCatLinks( '', this.parentCats, table );
this.createCatLinks("", thiscat);
+
this.createCatLinks( '→', [ this.currentCategory ], table );
this.createCatLinks("", this.subCats);
+
// 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';
 
document.body.style.cursor = 'auto';
//Reset width
+
 
var cat = $('#cat_a_lot');
+
// Reset width
cat.width('');
+
$container.width( '' );
cat.height('');
+
$container.height( '' );
cat.width( cat.width() * 1.05 );
+
$container.width( Math.min( table.width() * 1.1 + 15, $( window ).width() - 10 ) );
var list = $('#cat_a_lot_category_list');
+
 
list.css({maxHeight: this.setHeight+'px', height: ''});
+
$resultList.css( {
 +
maxHeight: Math.min( this.setHeight, $( window ).height() - $container.position().top - $settingsLink.outerHeight() - $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) {
+
updateCats: function ( newcat ) {
 
document.body.style.cursor = 'wait';
 
document.body.style.cursor = 'wait';
 
 
this.currentCategory = newcat;
 
this.currentCategory = newcat;
this.catlist = $('#cat_a_lot_category_list');
+
$resultList.html( '<div class="cat_a_lot_loading">' + mw.msg( 'Wikieditor-loading' ) + '</div>' );
this.catlist.html('<div class="cat_a_lot_loading">' + this.i18n.loading + '</div>');
 
 
this.getCategoryList();
 
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 () {
 
showProgress: function () {
 
document.body.style.cursor = 'wait';
 
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.progressDialog = $('<div></div>')
+
this.domCounter = $( '#cat_a_lot_current' );
.html( this.i18n.editing + ' <span id="cat_a_lot_current">' + this.counterCurrent + '</span> ' + this.i18n.of + this.counterNeeded)
+
},
.dialog({
+
 
width: 450,
+
minimize: function ( e ) {
height: 90,
+
CAL.top = Math.max( 0, $container.position().top );
minHeight: 90,
+
CAL.height = $container.height();
modal: true,
+
$dataContainer.hide();
resizable: false,
+
$container.animate( {
draggable: false,
+
height: $head.height(),
closeOnEscape: false,
+
top: $( window ).height() - $head.height() * 1.4
dialogClass: "cat_a_lot_feedback"
+
}, function () {
});
+
$( e.target ).one( 'click', CAL.maximize );
$('.ui-dialog-titlebar').hide();
+
} );
this.domCounter = $('#cat_a_lot_current');
+
},
  
 +
maximize: function ( e ) {
 +
$dataContainer.show();
 +
$container.animate( {
 +
top: CAL.top,
 +
height: CAL.height
 +
}, function () {
 +
$( e.target ).one( 'click', CAL.minimize );
 +
} );
 
},
 
},
  
 
run: function () {
 
run: function () {
if ($('.cat_a_lot_enabled').length) {
+
if ( $( '.cat_a_lot_enabled' )[ 0 ] ) {
 
this.makeClickable();
 
this.makeClickable();
$("#cat_a_lot_data").show();
+
if ( !this.executed ) { // only once
$('#cat_a_lot').resizable({
+
$selectInvert.text( mw.msg( 'Checkbox-invert' ) );
handles: 'n',  
+
if ( this.settings.editpages && this.pageLabels[ 0 ] ) {
alsoResize: '#cat_a_lot_category_list',
+
$selectFiles.text( mw.msg( 'Prefs-files' ) );
resize: function(event, ui) {
+
$selectPages.text( mw.msg( 'Categories' ) ).parent().show();
$(this).css({left:"", top:""});
+
}
catALot.setHeight = $(this).height();
+
$link.after( $( '<a>' )
$('#cat_a_lot_category_list').css({maxHeight: '', width: ''});
+
.text( '–' )
}  
+
.css( { fontWeight: 'bold', marginLeft: '.7em' } )
});
+
.one( 'click', this.minimize )
$('#cat_a_lot_category_list').css({maxHeight: '450px'});
+
);
if (this.searchmode) this.updateCats("Pictures and images");
+
}
else this.updateCats(mw.config.get('wgTitle'));
+
$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' );
  
} else {
+
$link.html( $( '<span>' )
$("#cat_a_lot_data").hide();
+
.text( '×' )
$("#cat_a_lot").resizable( "destroy" );
+
.css( { font: 'bold 2em monospace', lineHeight: '.75em' } )
//Unbind click handlers
+
);
this.labels.off('click');
+
$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 );
 
}
 
}
 
},
 
},
i18n: (mw.config.get('wgUserLanguage') == "fa") ? {
 
                loading: 'بارگیری...',
 
editing: 'ویرایش صفحه',
 
of: ' ',
 
skippedAlready: '<h5>صفحهٔ انتخاب شده رها شد، چون صفحه در همان رده قرار دارد.:</h5>',
 
skippedNotFound: '<h5>صفحهٔ انتخاب شده رها شد چون رده قدیمی پیدا نشد!:</h5>',
 
skippedServer: '<h5>صفحه‌های انتخاب شده را نمی‌توان تغییر داد چون ارتباط با سرور فعلا برقرار نیست:</h5>',
 
allDone: 'درخواست برای تمام صفحه‌ها انجام شدند.',
 
done: 'انجام شد!',
 
addedCat: 'افزودن رده',
 
copiedCat: 'کپی به ردهٔ',
 
movedCat: 'انتقال به ردهٔ',
 
removedCat: 'حذف از ردهٔ',
 
returnToPage: 'بازگشت به صفحه',
 
catNotFound: 'رده یافت نشد.',
 
  
 +
manageSettings: function () {
 +
mw.loader.using( [ 'ext.gadget.SettingsManager', 'ext.gadget.SettingsUI', 'jquery.ui.progressbar' ], CAL._manageSettings );
 +
},
 +
 +
_manageSettings: function () {
 +
mw.libs.SettingsUI( CAL.defaults, 'Cat-a-lot' )
 +
.show()
 +
.done( function ( s, verbose, loc, settingsOut, $dlg ) {
 +
var mustRestart = false,
 +
_restart = function () {
 +
if ( !mustRestart ) { return; }
 +
$container.remove();
 +
CAL.labels.off( 'click.catALot' );
 +
CAL.init();
 +
},
 +
_saveToJS = function () {
 +
var opt = mw.libs.settingsManager.option( {
 +
optionName: 'catALotPrefs',
 +
value: CAL.settings,
 +
encloseSignature: 'catALot',
 +
encloseBlock: '////////// Cat-a-lot user preferences //////////\n',
 +
triggerSaveAt: /Cat.?A.?Lot/i,
 +
editSummary: msg( 'pref-save-summary' )
 +
} ),
 +
oldHeight = $dlg.height(),
 +
$prog = $( '<div>' );
  
//as in 17 files selected
+
$dlg.css( 'height', oldHeight )
filesSelected: ' صفحه انتخاب شده.',
+
.html( '' );
 +
$prog.css( {
 +
height: Math.round( oldHeight / 8 ),
 +
'margin-top': Math.round( ( 7 * oldHeight ) / 16 )
 +
} )
 +
.appendTo( $dlg );
  
//Actions
+
$dlg.parent()
copy: 'کپی',
+
.find( '.ui-dialog-buttonpane button' )
move: 'انتقال',
+
.button( 'option', 'disabled', true );
add: 'افزودن',
 
removeFromCat: 'حذف از رده',
 
enterName: 'نام رده مورد نظر را بنویسید',
 
select: 'انتخاب',
 
all: 'همه',
 
none: 'هیچکدام',
 
  
noneSelected: 'صفحه‌ای انتخاب نشده‌است!',
+
opt.save()
 +
.done( function ( text, progress ) {
 +
$prog.progressbar( {
 +
value: progress
 +
} );
 +
$prog.fadeOut( function () {
 +
$dlg.dialog( 'close' );
 +
_restart();
 +
} );
 +
} )
 +
.progress( function ( text, progress ) {
 +
$prog.progressbar( {
 +
value: progress
 +
} );
 +
// TODO: Add "details" to progressbar
 +
} )
 +
.fail( function ( text ) {
 +
$prog.addClass( 'ui-state-error' );
 +
$dlg.prepend( $( '<p>' )
 +
.text( text ) );
 +
} );
 +
};
 +
$.each( settingsOut, function ( n, v ) {
 +
if ( v.forcerestart && CAL.settings[ v.name ] !== v.value ) { mustRestart = true; }
 +
CAL.settings[ v.name ] = CAL.catALotPrefs[ v.name ] = v.value;
 +
} );
 +
switch ( loc ) {
 +
case 'page':
 +
$dlg.dialog( 'close' );
 +
_restart();
 +
break;
 +
case 'account-publicly':
 +
_saveToJS();
 +
break;
 +
}
 +
} );
 +
},
  
//Summaries:
+
_initSettings: function () {
summaryAdd: '[[وپ:رده انبوه|رده‌انبوه]]: افزودن [[رده:',
+
if ( this.settings.watchlist ) { return; }
summaryCopy: '[[وپ:رده انبوه|رده‌انبوه]]: کپی از [[رده:',
+
this.catALotPrefs = window.catALotPrefs || {};
to: 'به [[رده:',
+
for ( var i = 0; i < this.defaults.length; i++ ) {
summaryMove: '[[وپ:رده انبوه|رده‌انبوه]]: انتقال از [[رده:',
+
var v = this.defaults[ i ];
summaryRemove: '[[وپ:رده انبوه|رده‌انبوه]]: حذف از [[رده:'
+
v.value = this.settings[ v.name ] = ( this.catALotPrefs[ v.name ] || v['default'] );
}: {
+
v.label = msg( v.label_i18n );
//Progress
+
if ( v.select_i18n ) {
loading: 'Loading...',
+
v.select = {};
editing: 'Editing page',
+
$.each( v.select_i18n, function ( i18nk, val ) {
of: 'of ',
+
v.select[ msg( i18nk ) ] = val;
skippedAlready: '<h5>The following pages were skipped, because the page was already in the category:</h5>',
+
} );
skippedNotFound: '<h5>The following pages were skipped, because the old category could not be found:</h5>',
+
}
skippedServer: '<h5>The following pages couldn\'t be changed, since there were problems connecting to the server:</h5>',
+
}
allDone: 'All pages are processed.',
+
},
done: 'Done!',
+
/* eslint-disable camelcase */
addedCat: 'Added category',
+
defaults: [ {
copiedCat: 'Copied to category',
+
name: 'watchlist',
movedCat: 'Moved to category',
+
'default': 'preferences',
removedCat: 'Removed from category',
+
label_i18n: 'watchlistpref',
returnToPage: 'Return to page',
+
select_i18n: {
catNotFound: 'Category not found.',
+
watch_pref: 'preferences',
 +
watch_nochange: 'nochange',
 +
watch_watch: 'watch',
 +
watch_unwatch: 'unwatch'
 +
}
 +
}, {
 +
name: 'minor',
 +
'default': false,
 +
label_i18n: 'minorpref'
 +
}, {
 +
name: 'editpages',
 +
'default': project !== 'commonswiki', // on Commons false
 +
label_i18n: 'editpagespref',
 +
forcerestart: true
 +
}, {
 +
name: 'docleanup',
 +
'default': false,
 +
label_i18n: 'docleanuppref'
 +
}, {
 +
name: 'subcatcount',
 +
'default': 50,
 +
min: 5,
 +
max: 500,
 +
label_i18n: 'subcatcountpref',
 +
forcerestart: true
 +
}, {
 +
name: 'uncat',
 +
'default': project === 'commonswiki', // on Commons true
 +
label_i18n: 'uncatpref'
 +
}, {
 +
name: 'button',
 +
'default': true,
 +
label_i18n: 'buttonpref'
 +
} ]
 +
/* eslint-enable camelcase */
 +
};
  
 +
// The gadget is not immediately needed, so let the page load normally
 +
window.setTimeout( 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; }
  
//as in 17 files selected
+
switch ( ns ) {
filesSelected: ' files selected.',
+
case 14:
 +
CAL.searchmode = 'category';
 +
CAL.origin = mw.config.get( 'wgTitle' );
 +
break;
 +
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();
 +
}
  
//Actions
+
if ( CAL.searchmode ) {
copy: 'Copy',
+
var loadingLocalizations = 1;
move: 'Move',
+
var loadLocalization = function ( lang, cb ) {
add: 'Add',
+
loadingLocalizations++;
removeFromCat: 'Remove from this category',
+
switch ( lang ) {
enterName: 'Enter category name',
+
case 'zh-hk':
select: 'Select',
+
case 'zh-mo':
all: 'all',
+
case 'zh-tw':
none: 'none',
+
lang = 'zh-hant';
 +
break;
 +
case 'zh':
 +
case 'zh-cn':
 +
case 'zh-my':
 +
case 'zh-sg':
 +
lang = 'zh-hans';
 +
break;
 +
}
  
noneSelected: 'No files selected!',
+
$.ajax( {
 +
url: 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: cb,
 +
error: cb
 +
} );
 +
};
 +
var maybeLaunch = function () {
 +
loadingLocalizations--;
 +
function init() {
 +
$( function () {
 +
CAL.init();
 +
} );
 +
}
 +
if ( !loadingLocalizations ) { mw.loader.using( [ 'user' ], init, init ); }
 +
};
  
//Summaries:
+
var userlang = mw.config.get( 'wgUserLanguage' ),
summaryAdd: 'Cat-a-lot: Adding [[Category:',
+
contlang = mw.config.get( 'wgContentLanguage' );
summaryCopy: 'Cat-a-lot: Copying from [[Category:',
+
if ( userlang !== 'en' ) { loadLocalization( userlang, maybeLaunch ); }
to: 'to [[Category:',
+
if ( $.inArray( contlang, [ 'en', userlang ] ) === -1 ) { loadLocalization( contlang, maybeLaunch ); }
summaryMove: 'Cat-a-lot: Moving from [[Category:',
+
maybeLaunch();
summaryRemove: 'Cat-a-lot: Removing from [[Category:'
 
 
}
 
}
};
+
}, 400 );
  
 +
/**
 +
*  Derivative work of
 +
*  (replace "checkboxes" with cat-a-lot labels in your mind)
 +
**
 +
* jQuery checkboxShiftClick
 +
*
 +
* This will enable checkboxes to be checked or unchecked in a row by clicking one, holding shift and clicking another one
 +
*
 +
* @author Krinkle <krinklemail@gmail.com>
 +
* @license GPL v2
 +
*/
 +
$.fn.catALotShiftClick = function ( cb ) {
 +
var prevCheckbox = null,
 +
$box = this;
 +
// When our boxes are clicked..
 +
$box.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' ); }
  
if ((mw.config.get('wgNamespaceNumber') == -1 && mw.config.get('wgCanonicalSpecialPageName') == "Search") || mw.config.get('wgNamespaceNumber') == 14) {
+
$thisControl.attr( 'id', 'cat_a_lot_last_selected' )
if ( mw.config.get('wgNamespaceNumber') == -1 ) catALot.searchmode = true;
+
.toggleClass( 'cat_a_lot_selected' );
mediaWiki.loader.using(['jquery.ui.dialog', 'jquery.ui.autocomplete'], function () {
+
// And one has been clicked before…
$(function () {
+
if ( prevCheckbox !== null && e.shiftKey ) {
catALot.init();
+
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;
 +
};
  
// </source>
+
}( jQuery, mediaWiki ) );
 +
// </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)
  • Requires MediaWiki:Gadget-SettingsManager.js and MediaWiki:Gadget-SettingsUI.js (properly registered) for per-user-settings
  • READ THIS PAGE IF YOU WANT TO TRANSLATE OR USE THIS ON ANOTHER SITE:
  • http://commons.wikimedia.org/wiki/MediaWiki:Gadget-Cat-a-lot.js/translating
  • */ /* 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 formattedNS = mw.config.get( 'wgFormattedNamespaces' ), 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-watchlistpref': 'Watchlist preference concerning files edited with Cat-a-lot', 'cat-a-lot-watch_pref': 'According to your general preferences', 'cat-a-lot-watch_nochange': 'Do not change watchstatus', 'cat-a-lot-watch_watch': 'Watch pages edited with Cat-a-lot', 'cat-a-lot-watch_unwatch': 'Remove pages while editing with Cat-a-lot from your watchlist', 'cat-a-lot-minorpref': 'Mark edits as minor (if you generally mark your edits as minor, this won’t change anything)', 'cat-a-lot-editpagespref': 'Allow categorising pages (including categories) that are not files', 'cat-a-lot-docleanuppref': 'Remove {{Check categories}} and other minor cleanup', 'cat-a-lot-uncatpref': 'Remove {{Uncategorized}}', 'cat-a-lot-subcatcountpref': 'Sub-categories to show at most', 'cat-a-lot-config-settings': 'Preferences', 'cat-a-lot-buttonpref': 'Use buttons instead of text-links', 'cat-a-lot-comment-label': 'Custom edit comment', 'cat-a-lot-edit-question': 'Why is this change necessary?', // Progress // 'cat-a-lot-loading': 'Loading …', 'cat-a-lot-editing': 'Editing page', 'cat-a-lot-of': 'of ', 'cat-a-lot-skipped-already': 'The following {{PLURAL:$1|1=page was|$1 pages were}} skipped, because the page was already in the category:', 'cat-a-lot-skipped-not-found': 'The following {{PLURAL:$1|1=page was|$1 pages were}} skipped, because the old category could not be found:', 'cat-a-lot-skipped-server': 'The following {{PLURAL:$1|1=page|$1 pages}} couldn’t be changed, since there were problems connecting to the server:', 'cat-a-lot-all-done': 'All pages are processed.', 'cat-a-lot-done': 'Done!', // 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', // 'cat-a-lot-cat-not-found': 'Category not found.', // as in 17 files selected 'cat-a-lot-files-selected': '{{PLURAL:$1|1=One file|$1 files}} selected.', 'cat-a-lot-pe_file': '$1 {{PLURAL:$1|page|pages}} of $2 affected', 'cat-a-lot-parent-cat': 'Has parent-category: ', 'cat-a-lot-sub-cat': 'Has sub-category: ', // Actions 'cat-a-lot-copy': 'Copy', 'cat-a-lot-move': 'Move', 'cat-a-lot-add': 'Add', // 'cat-a-lot-remove-from-cat': 'Remove from this category', 'cat-a-lot-overcat': 'Check over-categorization', 'cat-a-lot-enter-name': 'Enter category name', 'cat-a-lot-select': 'Select', 'cat-a-lot-all': 'all', 'cat-a-lot-none': 'none', // 'cat-a-lot-none-selected': 'No files selected!', 'Ooui-selectfile-placeholder' // Summaries (project language): 'cat-a-lot-pref-save-summary': 'Updating user preferences', '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-using-summary': ' using [[c:Help:Cat-a-lot|Cat-a-lot]]' }; 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, $settingsWrapper, $settingsLink, $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: 'Disambiguation', // 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: 'Category redirects' }, init: function () { // Prevent historical double marker (maybe remove in future) if ( /Cat-?a-?lot/i.test( msgs[ 'cat-a-lot-pref-save-summary' ] ) ) { mw.messages.set( { 'cat-a-lot-prefix-summary': '', 'cat-a-lot-using-summary': '' } ); } else { mw.messages.set( { 'cat-a-lot-pref-save-summary': msgs[ 'cat-a-lot-prefix-summary' ] + msgs[ 'cat-a-lot-pref-save-summary' ] + msgs[ 'cat-a-lot-using-summary' ] } ); } // 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 ); $settingsWrapper = $( '<div>' ) .attr( 'id', 'cat_a_lot_settings' ) .appendTo( $dataContainer ); $settingsLink = $( '<a>', { id: 'cat_a_lot_config_settings', title: 'Version ' + this.version, text: msg( 'config-settings' ) } ) .appendTo( $settingsWrapper ); $head = $( '<div>' ) .attr( 'id', 'cat_a_lot_head' ) .appendTo( $container ); $link = $( '<a>' ) .attr( 'id', 'cat_a_lot_toggle' ) .text( 'Cat-a-lot' ) .appendTo( $head ); $settingsWrapper.append( $( '<a>', { href: commonsURL + '?title=Special:MyLanguage/Help:Gadget-Cat-a-lot', target: '_blank', style: 'float:right', title: ( $( '#n-help a' ).attr( 'title' ) || '' ) + ' (v. ' + this.version + ')' } ).text( '?' ) ); $container.one( 'mouseover', function () { // Try load on demand earliest as possible mw.loader.load( [ 'jquery.ui.resizable', 'jquery.ui.draggable' ] ); } ); if ( this.origin && !non ) { $overcat = $( '<a>' ) .attr( 'id', 'cat_a_lot_overcat' ) .html( msg( 'overcat' ) ) .on( 'click', function ( e ) { CAL.getOverCat( e ); } ) .insertBefore( $selections ); } if ( ( mw.util.getParamValue( 'withJS' ) === 'MediaWiki:Gadget-Cat-a-lot.js' && !mw.util.getParamValue( 'withCSS' ) ) || mw.loader.getState( 'ext.gadget.Cat-a-lot' ) === 'registered' ) { mw.loader.load( mw.config.get( 'wgServer' ) + '/w/index.php?title=MediaWiki:Gadget-Cat-a-lot.css&action=raw&ctype=text/css', 'text/css' ); // importStylesheet( 'MediaWiki:Gadget-Cat-a-lot.css' ); } reCat = new RegExp( '^\\s*' + CAL.localizedRegex( 14, '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>' ) // .attr( 'id', 'cat_a_lot_select_all' ) .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>' ) // .attr( 'id', 'cat_a_lot_select_none' ) .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 mw.loader.using( 'jquery.ui.autocomplete', initAutocomplete ); if ( !CAL.executed ) { $.when( mw.loader.using( [ 'jquery.ui.resizable', 'jquery.ui.draggable', 'jquery.ui.button', 'mediawiki.api.messages', '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(); } } ); $settingsLink .on( 'click', CAL.manageSettings ); this.localCatName = formattedNS[ 14 ] + ':'; 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( /wiki\/(.+?)(?:#.+)?$/ )[ 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( formattedNS[ 2 ] + ':' ) ) { 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' && this.pageLabels[ 0 ] ) { this.pageLabels.toggleClass( 'cat_a_lot_selected', true ); if ( select === 'files' ) // pages get deselected { this.labels.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 ( namespaceNumber, fallback ) { // Copied from HotCat, thanks Lupo. var wikiTextBlank = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+'; var wikiTextBlankRE = new RegExp( wikiTextBlank, 'g' ); var createRegexStr = function ( name ) { if ( !name || !name.length ) { 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 = formattedNS[ namespaceNumber ].toLowerCase(); var RegexString = createRegexStr( canonical ); if ( fallback && canonical !== fallback ) { RegexString += '|' + createRegexStr( fallback ); } for ( var catName in nsIDs ) { if ( typeof catName === 'string' && catName.toLowerCase() !== canonical && catName.toLowerCase() !== fallback && nsIDs[ catName ] === namespaceNumber ) { RegexString += '|' + createRegexStr( catName ); } } return ( '(?:' + RegexString + ')' ); }, regexCatBuilder: function ( category ) { var catname = this.localizedRegex( 14, 'Category' ); // Build a regexp string for matching the given category: // trim leading/trailing whitespace and underscores category = category.replace( /^[\s_]+|[\s_]+$/g, '' ); // escape regexp metacharacters (= any ASCII punctuation except _) category = mw.RegExp.escape( category ); // any sequence of spaces and underscores should match any other category = category.replace( /[\s_]+/g, '[\\s_]+' ); // Make the first character case-insensitive: var first = category.substr( 0, 1 ); if ( first.toUpperCase() !== first.toLowerCase() ) { category = '[' + first.toUpperCase() + first.toLowerCase() + ']' + category.substr( 1 ); } // Compile it into a RegExp that matches MediaWiki category syntax (yeah, it looks ugly): // XXX: the first capturing parens are assumed to match the sortkey, if present, including the | but excluding the ]] return new RegExp( '\\[\\[[\\s_]*' + catname + '[\\s_]*:[\\s_]*' + category + '[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]\\s*', 'g' ); }, getContent: function ( 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( 14, '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', 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', sourcecat ).replace( '$2', 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', sourcecat ).replace( '$2', targetcat ); sumCmtShort = '±[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]'; break; case 'remove': text = text.replace( this.regexCatBuilder( targetcat ), '' ); sumCmt = msg( 'summary-remove' ).replace( '$1', 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( 'Mobile-frontend-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.ui.dialog', 'jquery.spinner', 'mediawiki.RegExp' ], 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 && mw.loader.getState( 'jquery.ui.button' ) === 'ready' ) ? 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( mw.msg( 'Centralnotice-remove' ) ) .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 - $settingsLink.outerHeight() - $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 ); } }, manageSettings: function () { mw.loader.using( [ 'ext.gadget.SettingsManager', 'ext.gadget.SettingsUI', 'jquery.ui.progressbar' ], CAL._manageSettings ); }, _manageSettings: function () { mw.libs.SettingsUI( CAL.defaults, 'Cat-a-lot' ) .show() .done( function ( s, verbose, loc, settingsOut, $dlg ) { var mustRestart = false, _restart = function () { if ( !mustRestart ) { return; } $container.remove(); CAL.labels.off( 'click.catALot' ); CAL.init(); }, _saveToJS = function () { var opt = mw.libs.settingsManager.option( { optionName: 'catALotPrefs', value: CAL.settings, encloseSignature: 'catALot', encloseBlock: '////////// Cat-a-lot user preferences //////////\n', triggerSaveAt: /Cat.?A.?Lot/i, editSummary: msg( 'pref-save-summary' ) } ), oldHeight = $dlg.height(), $prog = $( '<div>' ); $dlg.css( 'height', oldHeight ) .html( '' ); $prog.css( { height: Math.round( oldHeight / 8 ), 'margin-top': Math.round( ( 7 * oldHeight ) / 16 ) } ) .appendTo( $dlg ); $dlg.parent() .find( '.ui-dialog-buttonpane button' ) .button( 'option', 'disabled', true ); opt.save() .done( function ( text, progress ) { $prog.progressbar( { value: progress } ); $prog.fadeOut( function () { $dlg.dialog( 'close' ); _restart(); } ); } ) .progress( function ( text, progress ) { $prog.progressbar( { value: progress } ); // TODO: Add "details" to progressbar } ) .fail( function ( text ) { $prog.addClass( 'ui-state-error' ); $dlg.prepend( $( '<p>' ) .text( text ) ); } ); }; $.each( settingsOut, function ( n, v ) { if ( v.forcerestart && CAL.settings[ v.name ] !== v.value ) { mustRestart = true; } CAL.settings[ v.name ] = CAL.catALotPrefs[ v.name ] = v.value; } ); switch ( loc ) { case 'page': $dlg.dialog( 'close' ); _restart(); break; case 'account-publicly': _saveToJS(); break; } } ); }, _initSettings: function () { if ( this.settings.watchlist ) { return; } 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', label_i18n: 'watchlistpref', select_i18n: { watch_pref: 'preferences', watch_nochange: 'nochange', watch_watch: 'watch', watch_unwatch: 'unwatch' } }, { name: 'minor', 'default': false, label_i18n: 'minorpref' }, { name: 'editpages', 'default': project !== 'commonswiki', // on Commons false label_i18n: 'editpagespref', forcerestart: true }, { name: 'docleanup', 'default': false, label_i18n: 'docleanuppref' }, { name: 'subcatcount', 'default': 50, min: 5, max: 500, label_i18n: 'subcatcountpref', forcerestart: true }, { name: 'uncat', 'default': project === 'commonswiki', // on Commons true label_i18n: 'uncatpref' }, { name: 'button', 'default': true, label_i18n: 'buttonpref' } ] /* eslint-enable camelcase */ }; // The gadget is not immediately needed, so let the page load normally window.setTimeout( 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; 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: cb, error: cb } ); }; var maybeLaunch = function () { loadingLocalizations--; function init() { $( function () { CAL.init(); } ); } if ( !loadingLocalizations ) { mw.loader.using( [ 'user' ], init, init ); } }; var userlang = mw.config.get( 'wgUserLanguage' ), contlang = mw.config.get( 'wgContentLanguage' ); if ( userlang !== 'en' ) { loadLocalization( userlang, maybeLaunch ); } if ( $.inArray( contlang, [ 'en', userlang ] ) === -1 ) { loadLocalization( contlang, maybeLaunch ); } maybeLaunch(); } }, 400 ); /** * Derivative work of * (replace "checkboxes" with cat-a-lot labels in your mind) ** * jQuery checkboxShiftClick * * This will enable checkboxes to be checked or unchecked in a row by clicking one, holding shift and clicking another one * * @author Krinkle <krinklemail@gmail.com> * @license GPL v2 */ $.fn.catALotShiftClick = function ( cb ) { var prevCheckbox = null, $box = this; // When our boxes are clicked.. $box.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 ) ); //