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