第1行:
第1行:
/**
/**
−
* Cat-a-lot
+
* Cat-a-lot - changes category of multiple files
−
* Changes category of multiple files
+
**/
−
*
+
if (mw.config.get('wgNamespaceNumber') === 14) {
−
* @rev 00:13, 10 February 2018 (UTC)
+
window.catALotPrefs = { editpages: true, subcatcount: 100 };
−
* @author Originally by Magnus Manske (2007)
+
mw.loader.using(['jquery.ui', 'mediawiki.util']).done(function () {
−
* @author RegExes by Ilmari Karonen (2010)
+
mw.util.addCSS("#cat_a_lot_settings { display:none !important;}"); // Preferences depend on some Commons gadgets, not available locally
−
* @author Completely rewritten by DieBuche (2010-2012)
+
mw.loader.load('//commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-Cat-a-lot.js&action=raw&ctype=text/javascript');
−
* @author Rillke (2012-2014)
+
mw.loader.load('//commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-Cat-a-lot.css&action=raw&ctype=text/css', 'text/css');
−
* @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
−
* <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,
−
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'] );
−
} );
−
−
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', initAutocomplete );
−
−
if ( !CAL.executed ) {
−
$.when( mw.loader.using( [
−
'jquery.ui',
−
'jquery.ui',
−
'jquery.ui',
−
'mediawiki.api',
−
'mediawiki.jqueryMsg'
−
] ), $.ready )
−
.then( function () {
−
return new mw.Api().loadMessagesIfMissing( [
−
'Cancel',
−
'Categorytree-not-found',
−
// 'Checkuser-all',
−
// 'Code-field-select',
−
// 'Export-addcat',
−
'Filerevert-submit',
−
'Mobile-frontend-return-to-page',
−
'Ooui-selectfile-placeholder',
−
// 'Visualeditor-clipboard-copy',
−
'Wikieditor-loading',
−
'Prefs-files',
−
'Categories',
−
'Checkbox-invert',
−
'Centralnotice-remove', // 'Ooui-item-remove'
−
'Apifeatureusage-warnings'
−
] );
−
} ).then( function () {
−
CAL.run();
−
} );
−
} else { CAL.run(); }
−
} );
−
$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.util.escapeRegExp( 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/g, targetcat );
−
sumCmtShort = '+[[' + catNS + targetcat + ']]';
−
break;
−
case 'copy':
−
text = text.replace( this.regexCatBuilder( sourcecat ), '[[' + catNS + sourcecat + '$1]]\n[[' + catNS + targetcat + '$1]]\n' );
−
sumCmt = msg( 'summary-copy' ).replace( /\$1/g, sourcecat ).replace( /\$2/g, targetcat );
−
sumCmtShort = '+[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]';
−
// If category is added through template:
−
if ( otext === text ) { text = addCategory( text, targetcat ); }
−
−
break;
−
case 'move':
−
text = text.replace( this.regexCatBuilder( sourcecat ), '[[' + catNS + targetcat + '$1]]\n' );
−
sumCmt = msg( 'summary-move' ).replace( /\$1/g, sourcecat ).replace( /\$2/g, targetcat );
−
sumCmtShort = '±[[' + catNS + sourcecat + ']]' + arr + '[[' + catNS + targetcat + ']]';
−
break;
−
case 'remove':
−
text = text.replace( this.regexCatBuilder( targetcat ), '' );
−
sumCmt = msg( 'summary-remove' ).replace( /\$1/g, targetcat );
−
sumCmtShort = '-[[' + catNS + targetcat + ']]';
−
break;
−
}
−
−
if ( text === otext ) {
−
this.notFound.push( file[ 0 ] );
−
this.updateCounter();
−
return;
−
}
−
otext = text;
−
−
// Remove {{uncat}} after we checked whether we changed the text successfully.
−
// Otherwise we might fail to do the changes, but still replace {{uncat}}
−
if ( mode !== 'remove' && ( !non || userGrp.indexOf( 'autoconfirmed' ) > -1 ) ) {
−
if ( !this.is_hidden ) {
−
text = this.removeUncat( text );
−
if ( text.length !== otext.length ) { sumCmt += '; ' + msg( 'uncatpref' ); }
−
}
−
text = this.doCleanup( text );
−
}
−
−
sumCmt += this.summary ? ' ' + this.summary : '';
−
−
var preM = msg( 'prefix-summary' );
−
var usgM = msg( 'using-summary' );
−
// Try shorten summary
−
if ( preM || usgM ) {
−
sumCmt = ( sumCmt.length > 250 - preM.length - usgM.length ) ?
−
sumCmt + ' (CatAlot)' : preM + sumCmt + usgM;
−
}
−
−
if ( sumCmt.length > 254 ) // Try short summary
−
{ sumCmt = sumCmtShort; }
−
−
var data = {
−
action: 'edit',
−
assert: 'user',
−
summary: sumCmt,
−
title: file[ 0 ],
−
text: text,
−
bot: true,
−
starttimestamp: this.starttimestamp,
−
basetimestamp: timestamp,
−
watchlist: this.settings.watchlist,
−
minor: this.settings.minor,
−
tags: this.changeTag,
−
token: this.edittoken
−
};
−
−
this.doAPICall( data, function ( r ) {
−
delete CAL.XHR[ file[ 0 ] ];
−
return CAL.updateUndoCounter( r );
−
} );
−
this.markAsDone( file[ 1 ], mode, targetcat );
−
},
−
−
markAsDone: function ( label, mode, targetcat ) {
−
mode = ( function ( m ) {
−
switch ( m ) {
−
case 'add': return 'added-cat';
−
case 'copy': return 'copied-cat';
−
case 'move': return 'moved-cat';
−
case 'remove': return 'removed-cat';
−
}
−
}( mode ) );
−
label.addClass( 'cat_a_lot_markAsDone' ).append( '<br>' + msg( mode, targetcat ) );
−
},
−
−
updateUndoCounter: function ( r ) {
−
this.updateCounter();
−
if ( !r.edit || r.edit.result !== 'Success' ) { return; }
−
r = r.edit;
−
−
this.undoList.push( {
−
title: r.title,
−
id: r.newrevid,
−
timestamp: r.newtimestamp
−
} );
−
},
−
−
updateCounter: function () {
−
this.counterCurrent++;
−
if ( this.counterCurrent > this.counterNeeded ) { this.displayResult(); } else { this.domCounter.text( this.counterCurrent ); }
−
},
−
−
displayResult: function () {
−
document.body.style.cursor = 'auto';
−
$.removeSpinner( 'fb-dialog' );
−
this.progressDialog.parent()
−
.addClass( 'cat_a_lot_done' )
−
.find( '.ui-dialog-buttonpane button span' ).eq( 0 )
−
.text( mw.msg( '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', 'jquery.spinner', 'mediawiki.util' ], 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' ) === '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' ], 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 );
−
−
/**
−
* When clicking a cat-a-lot label with Shift pressed, select all labels between the current and last-clicked one.
−
*/
−
$.fn.catALotShiftClick = function ( cb ) {
−
var prevCheckbox = null,
−
$box = this;
−
// When our boxes are clicked..
−
$box.on( 'click.catALot', function ( e ) {
−
// Prevent following the link and text selection
−
if ( !e.ctrlKey ) { e.preventDefault(); }
−
// Highlight last selected
−
$( '#cat_a_lot_last_selected' )
−
.removeAttr( 'id' );
−
var $thisControl = $( e.target ),
−
method;
−
if ( !$thisControl.hasClass( 'cat_a_lot_label' ) ) { $thisControl = $thisControl.parents( '.cat_a_lot_label' ); }
−
−
$thisControl.attr( 'id', 'cat_a_lot_last_selected' )
−
.toggleClass( 'cat_a_lot_selected' );
−
// And one has been clicked before…
−
if ( prevCheckbox !== null && e.shiftKey ) {
−
method = $thisControl.hasClass( 'cat_a_lot_selected' ) ? 'addClass' : 'removeClass';
−
// Check or uncheck this one and all in-between checkboxes
−
$box.slice(
−
Math.min( $box.index( prevCheckbox ), $box.index( $thisControl ) ),
−
Math.max( $box.index( prevCheckbox ), $box.index( $thisControl ) ) + 1
−
)[ method ]( 'cat_a_lot_selected' );
−
}
−
// Either way, update the prevCheckbox variable to the one clicked now
−
prevCheckbox = $thisControl;
−
if ( $.isFunction( cb ) ) { cb(); }
−
} );
−
return $box;
−
};
−
−
}( jQuery, mediaWiki ) );
−
// </nowiki>