לדלג לתוכן

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

מתוך צפונות ויקי
simplify cat_a_lot_get_current_utc_timestamp()
Rewrite completely: Smaller size, use API for editing, progress indicator, unobstrusive js, closurize etc.
שורה 1: שורה 1:
// <source lang="javascript">
// <source lang="javascript">
// Variables
//
var cat_a_lot_mark_color = "#3399FF" ;
// Cat-A-Lot
var cat_a_lot_api_url = wgScriptPath + "/api.php" ;
// Changes category of multiple files
var cat_a_lot_edit_url = wgScript + "?title=" ;
//
var cat_a_lot_divtype = 'div' ;
// Originally by Magnus Manske
var cat_a_lot_classname = 'gallerytext' ;
// RegExes by Ilmari Karonen
var cat_a_lot_searchmode = false ;
// Completely rewritten by DieBuche
var cat_a_lot_parent;


var catALot = {
apiUrl: wgScriptPath + "/api.php",
searchmode: false,
version: 2.0,
init: function () {
//jQuery UI is not loaded on all pages:
if (typeof jQuery.ui == 'undefined') {
importStylesheetURI(wgServer + '/w/extensions/UsabilityInitiative/css/vector/jquery-ui-1.7.2.css');
importScriptURI(wgServer + '/w/extensions/UsabilityInitiative/js/js2stopgap/jui.combined.min.js');
}
$("#column-one, #mw-panel").append('<div id="cat_a_lot">' + '<div id="cat_a_lot_data"><div>' + '<input type="text" input="8" id="cat_a_lot_searchcatname" />' + '<input type="button" id="cat_a_lot_go" value="Go" />' + '</div><div id="cat_a_lot_category_list"></div>' + '<div id="cat_a_lot_mark_counter"> </div>' + '<div id="cat_a_lot_selections">Select <a  id="cat_a_lot_select_all">all</a> / ' + '<a  id="cat_a_lot_select_none">none</a>' + '<br><a id="cat_a_lot_remove"><b>Remove from this category</b></a>' + '</div></div><div id="cat_a_lot_head">' + '<a id="cat_a_lot_toggle">Cat-a-lot</a></div></div>');


// Add this to URLs to get around cache issues
$('#cat_a_lot_go').click(function () {
function cat_a_lot_dummy_time() {
catALot.updateCats($('#cat_a_lot_searchcatname').val());
  var t = new Date () ;
});
  return "&dummytime=" + t.getTime() ;
$('#cat_a_lot_remove').click(function () {
}
catALot.remove();
});
$('#cat_a_lot_select_all').click(function () {
catALot.toggleAll(true);
});
$('#cat_a_lot_select_none').click(function () {
catALot.toggleAll(false);
});
$('#cat_a_lot_toggle').click(function () {
$(this).toggleClass('cat_a_lot_enabled');
catALot.run();
});
 
importStylesheet('MediaWiki:Gadget-Cat-a-lot.css');
if (skin == "vector") appendCSS("#cat_a_lot {font-size: .75em};");
},
findAllLabels: function () {
if (this.searchmode) this.labels = $('table.searchResultImage').find('tr>td:eq(1)');
else this.labels = $('div.gallerytext');
},
 
getMarkedLabels: function () {
var marked = [];
this.selectedLabels = this.labels.filter('.cat_a_lot_selected');
this.selectedLabels.each(function () {
var file = $(this).find('a[title]');
marked.push(file.attr('title'));
});
return marked;
},
 
updateSelectionCounter: function () {
this.selectedLabels = this.labels.filter('.cat_a_lot_selected');
$('#cat_a_lot_mark_counter').html(this.selectedLabels.length + " files selected.");
},
 
makeClickable: function () {
this.findAllLabels();
this.labels.click(function () {
$(this).toggleClass('cat_a_lot_selected');
catALot.updateSelectionCounter();
});
},
toggleAll: function (select) {
 
this.labels.toggleClass('cat_a_lot_selected', select);
this.updateSelectionCounter();
},
 
getSubCats: function () {
var data = {
action: 'query',
list: 'categorymembers',
cmnamespace: 14,
cmlimit: 50,
cmtitle: 'Category:' + this.currentCategory
};
 
this.doAPICall(data, function (result) {
 
var cats = result.query.categorymembers;
 
catALot.subCats = new Array();
for (var i = 0; i < cats.length; i++) {
catALot.subCats.push(cats[i].title.split(":", 2)[1]);
}
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 = new Array();
var pages = result.query.pages;
// there should be only one, but we don't know its ID
for (var id in pages) {
var cats = pages[id].categories;
}
for (var i = 0; i < cats.length; i++) {
catALot.parentCats.push(cats[i].title.split(":", 2)[1]);
}
 
catALot.catCounter++;
if (catALot.catCounter == 2) catALot.showCategoryList();
});
},
regexBuilder: function (category) {
// Build a regexp string for matching the given category:
// trim leading/trailing whitespace and underscores
category = category.replace(/^[\s_]+/, "").replace(/[\s_]+$/, "");
 
// escape regexp metacharacters (= any ASCII punctuation except _)
category = category.replace(/([!-\/:-@\[-^`{-~])/g, '\\$1');
 
// 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_]*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy][\\s_]*:[\\s_]*' + category + '[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]', 'g');
},


function init_cat_a_lot () {
getContent: function (filename, targetcat, mode) {
  cat_a_lot_parent = document.getElementById( "mw-category-media" ) || getElementsByClassName( document, "ul", "mw-search-results" )[0];
  if( !cat_a_lot_parent) return;


  var c1_div = document.getElementById("column-one") ;
var data = {
  if (!c1_div) c1_div = document.getElementById("mw-panel");
action: 'query',
  if (!c1_div) return;
prop: 'info|revisions',
  c1_div.innerHTML += '<div id="cat_a_lot" style="z-index:9999;display:block;background-color:white; position:fixed; bottom:0; right:0;">' +
rvprop: 'content|timestamp',
    '<div class="cat_a_lot_data" id="cat_a_lot_data" style="display:none;border:1px solid black">' +
intoken: 'edit',
titles: filename
};


    '<div style="border-bottom:1px solid black">' +
this.doAPICall(data, function (result) {
    '<form name="cat_a_lot_searchcat" method="post" onSubmit="return false;">' +
catALot.editCategories(result, filename, targetcat, mode);
    '<input type="text" input="8" name="cat_a_lot_searchcatname" />' +
});
    '<input type="submit" name="cat_a_lot_go" value="Go" onclick="cat_a_lot_search_cat();"/>' +
},
    '</form>' +
    '</div>' +


    '<div class="cat_a_lot_category_list" id="cat_a_lot_category_list" style="border-bottom:1px solid black;max-height:500px;overflow:auto"> </div>' +
editCategories: function (result, filename, targetcat, mode) {
    '<div class="cat_a_lot_mark_counter" id="cat_a_lot_mark_counter"> </div>' +
    '<div class="cat_a_lot_selections" id="cat_a_lot_selections" style="border-top:1px solid black">Select ' +
    '<a href="#" onClick="cat_a_lot_mark_all(' + "'2'" + ');">all</a> / ' +
    '<a href="#" onClick="cat_a_lot_mark_all(' + "'3'" + ');">none</a>' +
    '</div>' +
    '<div>' +
    '<a href="#" onclick ="cat_a_lot_remove()"><b>Remove from this category</b></a>' +
    '</div>' +
    '<form name="cat_a_lot_edit" enctype="multipart/form-data" method="post" target="_blank">' +
    '<input type="hidden" name="wpTextbox1" />' +
    '<input type="hidden" name="wpSummary" />' +
    '<input type="hidden" name="wpDiff" value="wpDiff" />' +
    '<input type="hidden" name="wpStarttime" />' +
    '<input type="hidden" name="wpEdittime" />' +
    '</form>' +
    '</div>' +
    '<div id="cat_a_lot_head" class="pBody">' +
    '<form><input id="cat_a_lot_mode" type="checkbox" onchange="run_cat_a_lot();"/><label for="cat_a_lot_mode">Cat-a-lot mode</label></form>' +
    '</div></div>' ;
if( skin == "vector" )
  appendCSS( "#cat_a_lot {font-size: .75em};");
}


function cat_a_lot_get_marked_labels () {
if (result == null) {
  var ret = new Array () ;
//Happens on unstable wifi connections..
  var divs = getElementsByClassName( cat_a_lot_parent, cat_a_lot_divtype, cat_a_lot_classname);
this.updateCounter();
  for ( var i = 0 ; i < divs.length ; i++ ) {
this.connectionError.push(filename);
    if ( divs[i].style.backgroundColor == "" ) continue ;
return;
    var files = divs[i].getElementsByTagName ( "a" ) ;
}
    var t = cat_a_lot_gettitle(files) ;
var pages = result.query.pages;
    ret.push ( t ) ;
  }
  return ret ;
}


function cat_a_lot_update_selection_counter () {
// there should be only one, but we don't know its ID
  var cat_a_lot_mark_counter = document.getElementById("cat_a_lot_mark_counter") ;
for (var id in pages) {
  var l = cat_a_lot_get_marked_labels () ;
// The edittoken only changes between logins
  cat_a_lot_mark_counter.innerHTML = l.length + " files selected." ;
this.edittoken = pages[id].edittoken;
}
var otext = pages[id].revisions[0]['*'];
var starttimestamp = pages[id].starttimestamp;
var timestamp = pages[id].revisions[0]['timestamp'];
}


function cat_a_lot_on_click_label ( title ) {
  cat_a_lot_alter_label ( title , "onclick" ) ;
}


function cat_a_lot_invalidate_label ( title , mode ) {
var sourcecat = wgTitle;
  cat_a_lot_alter_label ( title , mode ) ;
}


function cat_a_lot_alter_label ( title , mode ) {
// Check if that file is already in that category
  var divs = getElementsByClassName( cat_a_lot_parent, cat_a_lot_divtype, cat_a_lot_classname ) ;
if (mode != "remove" && this.regexBuilder(targetcat).test(otext)) {
  for ( var i = 0 ; i < divs.length ; i++ ) {
    var files = divs[i].getElementsByTagName ( "a" ) ;
    if ( files.length == 0 ) continue ;
    if ( cat_a_lot_gettitle(files) != title ) continue ;


    if ( mode == "move" ) {
this.updateCounter();
      divs[i].innerHTML += "<div style=\"width:100%;color:white;background-color:red;text-align:center;\">Moved to new category</div>" ;
//Still remove old one if mode=move?
      divs[i].style.backgroundColor = "#FF0000" ;
this.alreadyThere.push(filename);
    } else if ( mode == "remove" ) {
      divs[i].innerHTML += "<div style=\"width:100%;color:white;background-color:red;text-align:center;\">Removed from category</div>" ;
      divs[i].style.backgroundColor = "#FF0000" ;
    } else if ( mode == "copy" ) {
      divs[i].innerHTML += "<div style=\"width:100%;font-weight:bold;\">Copied to new category!</div>" ;
    }


    // Clicked on this element
return;
    if ( divs[i].style.backgroundColor != "" ) {
}
      divs[i].style.backgroundColor = "" ;
    } else {
      divs[i].style.backgroundColor = cat_a_lot_mark_color ;
    }
    cat_a_lot_update_selection_counter () ;
    return ;
  }
}


function cat_a_lot_fix_quotes ( text , quote ) {
// Fix text
  return text.split(quote).join("\\"+quote) ;
var text = otext;
}
var comment;


function cat_a_lot_gettitle( files ) {
switch (mode) {
  var ret = "";
case 'copy':
  if( files.length > 0 ) {
text += "\n[[" + "Category:" + targetcat + "]]\n";
      var flag = ( cat_a_lot_searchmode && files.length > 1 ) ? 1 : 0;
comment = "Moving from [[" + "Category:" + sourcecat + "]] to [[" + "Category:" + targetcat + "]]";
      ret = files[flag].title;
if (this.searchmode) comment = "Adding [[" + "Category:" + targetcat + "]]";
      if( files[flag].originalTitle ) // navpopups mode
break;
        ret = files[flag].originalTitle;
case 'move':
  }
text = text.replace(this.regexBuilder(sourcecat), "[[Category:" + targetcat + "$1]]");
  return ret;
comment = "Copying from [[" + "Category:" + sourcecat + "]] to [[" + "Category:" + targetcat + "]]";
}
break;
case 'remove':
text = text.replace(this.regexBuilder(sourcecat), "");
comment = "Removing from [[" + "Category:" + sourcecat + "]]";
break;
}


function cat_a_lot_mark_all ( how ) {
if (text == otext) {
  var divs = getElementsByClassName( cat_a_lot_parent, cat_a_lot_divtype, cat_a_lot_classname ) ;
this.updateCounter();
  for ( var i = 0 ; i < divs.length ; i++ ) {
this.notFound.push(filename);
    var files = divs[i].getElementsByTagName ( "a" ) ;
return;
    if ( files.length == 0 ) continue ;
}


    divs[i].style.backgroundColor = "" ; // Deselect
var data = {
    if ( how == 1 ) { // Activate
action: 'edit',
      var title = cat_a_lot_gettitle( files );
summary: comment,
      if ( cat_a_lot_searchmode )
title: filename,
        divs[i] = divs[i].getElementsByTagName('div')[0] ;
token: this.edittoken,
starttimestamp: starttimestamp,
basetimestamp: timestamp,
text: text
};


      eval("divs[i].onclick = function () { cat_a_lot_on_click_label('" + cat_a_lot_fix_quotes ( title , "'" ) + "'); };") ;
this.doAPICall(data, function () {
    } else if ( how == 2 ) { // Select
catALot.updateCounter();
      divs[i].style.backgroundColor = cat_a_lot_mark_color ;
});
    } else if ( how == 0 ) { // Deactivate
},
      divs[i].onclick = "" ;
    }
  }
  cat_a_lot_update_selection_counter () ;
}


function cat_a_lot_get_subcategories () {
updateCounter: function () {
  var url = cat_a_lot_api_url + "?action=query&format=xml&list=categorymembers&cmnamespace=14&cmlimit=50&cmtitle=Category:" + encodeURIComponent (cat_a_lot_current_category) + cat_a_lot_dummy_time() ;
  var xmlreq = sajax_init_object() ;
  xmlreq.open ( "GET" , url , false ) ;
  xmlreq.send ( null ) ;
  xml = xmlreq.responseXML ;
  var cls = xml.getElementsByTagName('cm') ;
  var ret = new Array () ;
  for ( var i = 0 ; i < cls.length ; i++ ) {
    if (cls[i].getAttribute('ns') != 14) continue;  // XXX: partial workaround for MW API bug (cmnamespace parameter doesn't work)
    var title = cls[i].getAttribute('title');
    title = title.split(":",2)[1] ;
    ret.push ( title ) ;
  }
  return ret ;
}


function cat_a_lot_get_parent_categories () {
this.counterCurrent++;
  var url = cat_a_lot_api_url + "?action=query&format=xml&prop=categories&titles=Category:" + encodeURIComponent (cat_a_lot_current_category) + cat_a_lot_dummy_time() ;
this.domCounter.text(this.counterCurrent);
  var xmlreq = sajax_init_object() ;
if (this.counterCurrent == this.counterNeeded) this.displayResult();
  xmlreq.open ( "GET" , url , false ) ;
},
  xmlreq.send ( null ) ;
  xml = xmlreq.responseXML ;
  var cls = xml.getElementsByTagName('cl') ;
  var ret = new Array () ;
  for ( var i = 0 ; i < cls.length ; i++ ) {
    var title = cls[i].attributes.getNamedItem("title").value ;
    title = title.split(":",2)[1] ;
    ret.push ( title ) ;
  }
  return ret ;
}


function cat_a_lot_get_article_timestamp ( title ) {
displayResult: function () {
  var ret ;
  var url = cat_a_lot_api_url + '?action=query&format=xml&prop=revisions&rvprop=timestamp&titles=' + title ;


  var xmlreq = sajax_init_object() ;
document.body.style.cursor = 'auto';
  xmlreq.open ( "GET" , url , false ) ;
$('.cat_a_lot_feedback').addClass('cat_a_lot_done');
  xmlreq.send ( null ) ;
$('.ui-dialog-content').height('auto');
  var xml = xmlreq.responseXML ;
var rep = this.domCounter.parent();
  var rev = xml.getElementsByTagName('rev') ;
rep.html('<h3>Done!</h3>');
  if ( rev.length == 0 ) return cat_a_lot_get_current_utc_timestamp () ;
//FIXME Click here to reload the page
rep.append('All pages are processed. You can now reload the page');


  ret = rev.item(0).getAttribute('timestamp').replace( /[: \-ZT]/g, "" ) ;
if (this.alreadyThere.length) {
  return ret
rep.append('<h5>The following pages were skipped, because the page was already in the category:</h5>');
}
rep.append(this.alreadyThere.join('<br>'));
}
if (this.notFound.length) {
rep.append('<h5>The following pages were skipped, because the old category could not be found:</h5>');
rep.append(this.notFound.join('<br>'));
}
if (this.connectionError.length) {
rep.append('<h5>The following pages couldn\'t be changed, since there were problems connecting to the server:</h5>');
rep.append(this.connectionError.join('<br>'));
}


function cat_a_lot_build_regexp ( category ) {
},
  // Build a regexp string for matching the given category:
  category = category.replace( /^[\s_]+/, "" ).replace( /[\s_]+$/, "" );  // trim leading/trailing whitespace and underscores
  category = category.replace( /([!-\/:-@\[-^`{-~])/g, '\\$1' );          // escape regexp metacharacters (= any ASCII punctuation except _)
  category = category.replace( /[\s_]+/g, '[\\s_]+' );                    // any sequence of spaces and underscores should match any other


  // Make the first character case-insensitive:
moveHere: function (targetcat) {
  var first = category.substr( 0, 1 );
this.doSomething(targetcat, 'move');
  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):
copyHere: function (targetcat) {
  // XXX: the first capturing parens are assumed to match the sortkey, if present, including the | but excluding the ]]
this.doSomething(targetcat, 'copy');
  return new RegExp ( '\\[\\[[\\s_]*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy][\\s_]*:[\\s_]*' + category + '[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]', 'g' );
},
}


function cat_a_lot_edit_move ( filename , targetcat , mode ) {
remove: function () {
  var sourcecat = wgTitle ;
this.doSomething('', 'remove');
  var url = cat_a_lot_edit_url + encodeURIComponent (filename) + "&action=raw" + cat_a_lot_dummy_time() ;
},
  var xmlreq = sajax_init_object() ;
  xmlreq.open ( "GET" , url , false ) ;
  xmlreq.send ( null ) ;
  var otext = xmlreq.responseText.replace( /^\s+/, "" ).replace( /\s+$/, "" ) ;


  // Check if that file is already in that category
doSomething: function (targetcat, mode) {
  if ( mode != "remove" && cat_a_lot_build_regexp( targetcat ).test( otext ) ) {
//Paranoia
    alert ( filename + " is already in category " + targetcat + " - skipping." ) ;
if (this.lock) return alert('Please reload page');
    return ;
  }


  // Fix text
var files = this.getMarkedLabels();
  var text = otext ;
if (files.length == 0) {
  if ( mode == 'copy' )
alert("No files selected.");
    text += "\n[[Category:" + targetcat + "]]\n" ;
return;
  else if ( mode == 'move' )
}
    text = text.replace ( cat_a_lot_build_regexp( sourcecat ), "[[Category:" + targetcat + "$1]]" );
this.lock = true;
  else if ( mode == 'remove' )
this.notFound = [];
    text = text.replace ( cat_a_lot_build_regexp( sourcecat ), "" );
this.alreadyThere = [];
this.connectionError = [];
this.counterCurrent = 0;
this.counterNeeded = files.length;
this.showProgress();
for (var i = 0; i < files.length; i++) {
this.getContent(files[i], targetcat, mode);
}
},


  if ( text == otext ) {
doAPICall: function (params, callback) {
    alert ( "Could not find an occurrence of category " + sourcecat + " in description of file " + filename + " - skipping." ) ;
    return ;
  }


  var time = new Date () ;
params.format = 'json';
  var last_version_timestamp = cat_a_lot_get_article_timestamp ( filename ) ;
$.ajax({
  var starttime = cat_a_lot_get_current_utc_timestamp () ;
url: this.apiUrl,
cache: false,
dataType: 'json',
data: params,
type: 'POST',
success: callback
});
},


  var comment ;
createCatLinks: function (symbol, list) {
  if ( mode == "move" ) comment = "Moving from [[" + "Category:" + sourcecat + "]] to [[" + "Category:" + targetcat + "]]" ;
list.sort();
  if ( mode == "copy" ) comment = "Copying from [[" + "Category:" + sourcecat + "]] to [[" + "Category:" + targetcat + "]]" ;
var domlist = this.catlist.find('ul');
  if ( mode == "remove" ) comment = "Removing from [[" + "Category:" + sourcecat + "]]" ;
for (var i = 0; i < list.length; i++) {
  url = cat_a_lot_edit_url + encodeURIComponent(filename) + "&action=edit" ;
var li = $('<li></li>');
  document.cat_a_lot_edit.action = url ;
  document.cat_a_lot_edit.wpSummary.value = comment ;
  document.cat_a_lot_edit.wpTextbox1.value = text ;
  document.cat_a_lot_edit.wpStarttime.value = starttime ;
  document.cat_a_lot_edit.wpEdittime.value = last_version_timestamp ;
  document.cat_a_lot_edit.submit () ;


  cat_a_lot_invalidate_label ( filename , mode ) ;
var link = $('<a></a>');
}
link.text(list[i]);
li.data('cat', list[i]);
link.click(function () {
catALot.updateCats($(this).parent().data('cat'));
});


// Generates current timestamp
var move = $('<a class="cat_a_lot_action"><b> Move</b></a>');
function cat_a_lot_get_current_utc_timestamp () {
move.click(function () {
  var today = new Date() ;
catALot.moveHere($(this).parent().data('cat'));
  function pad0 ( n ) { return (n < 10 ? "0" + n : n); }
});
  return [ today.getUTCFullYear(),
          pad0( today.getUTCMonth() + 1 ),
          pad0( today.getUTCDate() ),
          pad0( today.getUTCHours() ),
          pad0( today.getUTCMinutes() ),
          pad0( today.getUTCSeconds() ) ].join( "" ) ;
}


var copy = $('<a class="cat_a_lot_action"><b>Copy</b></a>');
copy.click(function () {
catALot.copyHere($(this).parent().data('cat'));
});


li.append(symbol).append(' ').append(link);


function cat_a_lot_move_here ( targetcat ) {
// Can't move to source category
  cat_a_lot_do_something ( cat_a_lot_decode (targetcat) , 'move' ) ;
if (list[i] != wgTitle && this.searchmode) li.append(' ').append(copy);
}
else if (list[i] != wgTitle && !this.searchmode) li.append(' ').append(move).append(' ').append(copy);


function cat_a_lot_copy_here ( targetcat ) {
domlist.append(li);
  cat_a_lot_do_something ( cat_a_lot_decode (targetcat) , 'copy' ) ;
}
}
},
getCategoryList: function () {
this.catCounter = 0;
this.getParentCats();
this.getSubCats();
},
showCategoryList: function () {
var thiscat = [this.currentCategory];


function cat_a_lot_remove () {
this.catlist.empty();
  cat_a_lot_do_something ( '' , 'remove' ) ;
this.catlist.append('<ul></ul>');
}


function cat_a_lot_do_something ( targetcat , mode ) {
this.createCatLinks("↑", this.parentCats);
  var files = cat_a_lot_get_marked_labels () ;
this.createCatLinks("→", thiscat);
  if ( files.length == 0 ) {
this.createCatLinks("↓", this.subCats);
    alert ( "No files selected." ) ;
    return ;
  }
  for ( var i = 0 ; i < files.length ; i++ ) {
    cat_a_lot_edit_move ( files[i] , targetcat , mode ) ;
  }
}


function cat_a_lot_htmlize (str) {
document.body.style.cursor = 'auto';
  return str.replace (/&/g,    '&amp;')
},
            .replace (/\xa0/g, '&nbsp;')
            .replace (/</g,    '&lt;')
            .replace (/>/g,    '&gt;')
            .replace (/\"/g,   '&quot;') // " // Fix syntax coloring
            .replace (/\'/g,  '&apos;') // ' // Fix syntax coloring
  ;
}


function cat_a_lot_encode (str) {
updateCats: function (newcat) {
  return encodeURIComponent (str).replace (/'/g, '%27').replace (/"/g, '%22');
document.body.style.cursor = 'wait';
}


function cat_a_lot_decode (str) {
this.currentCategory = newcat;
  return decodeURIComponent (str.replace (/%27/g, "'").replace (/%22/g, '"'));
this.catlist = $('#cat_a_lot_category_list');
}
this.catlist.html('<div class="cat_a_lot_loading">Loading...</div>');
this.getCategoryList();
},
showProgress: function () {


function cat_a_lot_get_cat_links ( symbol , list ) {
document.body.style.cursor = 'wait';
  var ret = "" ;
  list.sort () ;
  for ( var i = 0 ; i < list.length ; i++ ) {
    ret += "<tr style='border:1px solid #AAAAAA'><td>" + symbol + "</td><td>" ;
    ret += '<a href="#" onclick ="cat_a_lot_update_cats(\'' + cat_a_lot_encode (list[i]) + '\', true)">' + cat_a_lot_htmlize (list[i]) + '</a>' ;
    ret += "</td><td>" ;
    if ( list[i] != wgTitle ) { // Can't move to source category
      ret += '<a href="#" onclick ="cat_a_lot_move_here(\'' + cat_a_lot_encode (list[i]) + '\')"><b>Move</b></a> ' ;
      ret += '<a href="#" onclick ="cat_a_lot_copy_here(\'' + cat_a_lot_encode (list[i]) + '\')"><b>Copy</b></a>' ;
    }
    ret += "</td></tr>" ;
  }
  return ret ;
}


function cat_a_lot_show_category_list () {
this.progressDialog = $('<div></div>').html('Editing page <span id="cat_a_lot_current">' + this.counterCurrent + '</span> of ' + this.counterNeeded).dialog({
  var parcats = cat_a_lot_get_parent_categories () ;
width: 450,
  var subcats = cat_a_lot_get_subcategories () ;
height: 90,
  var thiscat = new Array () ;
minHeight: 90,
  thiscat.push ( cat_a_lot_current_category ) ;
modal: true,
 
resizable: false,
  var out = "<table border='0'>" ;
draggable: false,
  out += cat_a_lot_get_cat_links ( "&uarr;" , parcats ) ;
closeOnEscape: false,
  out += cat_a_lot_get_cat_links ( "&rarr;" , thiscat ) ;
dialogClass: "cat_a_lot_feedback"
  out += cat_a_lot_get_cat_links ( "&darr;" , subcats ) ;
});
  out += "</table>" ;
$('.ui-dialog-titlebar').hide();
this.domCounter = $('#cat_a_lot_current');


  catlist = document.getElementById("cat_a_lot_category_list") ;
},
  catlist.innerHTML = out ;
  document.body.style.cursor = 'auto' ;
}


function cat_a_lot_update_cats ( newcat, encoded ) {
run: function () {
  document.body.style.cursor = 'wait' ;
if ($('.cat_a_lot_enabled').length) {
  if (encoded) newcat = cat_a_lot_decode (newcat);
this.makeClickable();
  cat_a_lot_current_category = newcat ;
$("#cat_a_lot_data").show();
  catlist = document.getElementById("cat_a_lot_category_list") ;
  catlist.innerHTML = '<div style="background-color:red;color:white">Loading...</div>' ;
  window.setTimeout("cat_a_lot_show_category_list()", 0);
}


function cat_a_lot_search_cat() {
if (this.searchmode) this.updateCats("Pictures and images");
  var newcat = document.cat_a_lot_searchcat.cat_a_lot_searchcatname.value ;
else this.updateCats(wgTitle);
  cat_a_lot_update_cats ( newcat ) ;
}


function run_cat_a_lot () {
} else {
  cat_a_lot_data = document.getElementById("cat_a_lot_data") ;
$("#cat_a_lot_data").hide();
  cat_a_lot_mode = document.getElementById("cat_a_lot_mode").checked ;
  if ( cat_a_lot_mode == false ) {
    cat_a_lot_data.style.display = "none" ;
    cat_a_lot_mark_all ( 0 ) ;
  } else {
    cat_a_lot_mark_all ( 1 ) ;
    cat_a_lot_data.style.display = "block" ;
    if ( cat_a_lot_searchmode ) cat_a_lot_update_cats ( "Pictures and images" ) ;
    else cat_a_lot_update_cats ( wgTitle ) ;
  }
}


//Unbind click handlers
this.labels.unbind('click');
}
}
};


if ( wgNamespaceNumber == 14 ) {
if (wgNamespaceNumber == 14) {
  $j(document).ready ( init_cat_a_lot ) ;
$j(document).ready(catALot.init);
}
}


if ( wgNamespaceNumber == -1 && wgCanonicalSpecialPageName == "Search" ) {
if (wgNamespaceNumber == -1 && wgCanonicalSpecialPageName == "Search") {
  cat_a_lot_divtype = 'table' ;
catALot.searchmode = true;
  cat_a_lot_classname = 'searchResultImage' ;
$j(document).ready(catALot.init);
  cat_a_lot_searchmode = true ;
  $j(document).ready ( init_cat_a_lot ) ;
}
}


// </source>
// </source>

גרסה מ־00:31, 7 בנובמבר 2010

// <source lang="javascript">
//
// Cat-A-Lot
// Changes category of multiple files
//
// Originally by Magnus Manske
// RegExes by Ilmari Karonen
// Completely rewritten by DieBuche

var catALot = {
	apiUrl: wgScriptPath + "/api.php",
	searchmode: false,
	version: 2.0,
	
	init: function () {
		//jQuery UI is not loaded on all pages:
		if (typeof jQuery.ui == 'undefined') {
			importStylesheetURI(wgServer + '/w/extensions/UsabilityInitiative/css/vector/jquery-ui-1.7.2.css');
			importScriptURI(wgServer + '/w/extensions/UsabilityInitiative/js/js2stopgap/jui.combined.min.js');
		}
		$("#column-one, #mw-panel").append('<div id="cat_a_lot">' + '<div id="cat_a_lot_data"><div>' + '<input type="text" input="8" id="cat_a_lot_searchcatname" />' + '<input type="button" id="cat_a_lot_go" value="Go" />' + '</div><div id="cat_a_lot_category_list"></div>' + '<div id="cat_a_lot_mark_counter"> </div>' + '<div id="cat_a_lot_selections">Select <a  id="cat_a_lot_select_all">all</a> / ' + '<a  id="cat_a_lot_select_none">none</a>' + '<br><a id="cat_a_lot_remove"><b>Remove from this category</b></a>' + '</div></div><div id="cat_a_lot_head">' + '<a id="cat_a_lot_toggle">Cat-a-lot</a></div></div>');

		$('#cat_a_lot_go').click(function () {
			catALot.updateCats($('#cat_a_lot_searchcatname').val());
		});
		$('#cat_a_lot_remove').click(function () {
			catALot.remove();
		});
		$('#cat_a_lot_select_all').click(function () {
			catALot.toggleAll(true);
		});
		$('#cat_a_lot_select_none').click(function () {
			catALot.toggleAll(false);
		});
		$('#cat_a_lot_toggle').click(function () {
			$(this).toggleClass('cat_a_lot_enabled');
			catALot.run();
		});

		importStylesheet('MediaWiki:Gadget-Cat-a-lot.css');
		if (skin == "vector") appendCSS("#cat_a_lot {font-size: .75em};");
	},
	findAllLabels: function () {
		if (this.searchmode) this.labels = $('table.searchResultImage').find('tr>td:eq(1)');
		else this.labels = $('div.gallerytext');
	},

	getMarkedLabels: function () {
		var marked = [];
		this.selectedLabels = this.labels.filter('.cat_a_lot_selected');
		this.selectedLabels.each(function () {
			var file = $(this).find('a[title]');
			marked.push(file.attr('title'));
		});
		return marked;
	},

	updateSelectionCounter: function () {
		this.selectedLabels = this.labels.filter('.cat_a_lot_selected');
		$('#cat_a_lot_mark_counter').html(this.selectedLabels.length + " files selected.");
	},

	makeClickable: function () {
		this.findAllLabels();
		this.labels.click(function () {
			$(this).toggleClass('cat_a_lot_selected');
			catALot.updateSelectionCounter();
		});
	},
	toggleAll: function (select) {

		this.labels.toggleClass('cat_a_lot_selected', select);
		this.updateSelectionCounter();
	},

	getSubCats: function () {
		var data = {
			action: 'query',
			list: 'categorymembers',
			cmnamespace: 14,
			cmlimit: 50,
			cmtitle: 'Category:' + this.currentCategory
		};

		this.doAPICall(data, function (result) {

			var cats = result.query.categorymembers;

			catALot.subCats = new Array();
			for (var i = 0; i < cats.length; i++) {
				catALot.subCats.push(cats[i].title.split(":", 2)[1]);
			}
			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 = new Array();
			var pages = result.query.pages;
			// there should be only one, but we don't know its ID
			for (var id in pages) {
				var cats = pages[id].categories;
			}
			for (var i = 0; i < cats.length; i++) {
				catALot.parentCats.push(cats[i].title.split(":", 2)[1]);
			}

			catALot.catCounter++;
			if (catALot.catCounter == 2) catALot.showCategoryList();
		});
	},
	regexBuilder: function (category) {
		// Build a regexp string for matching the given category:
		// trim leading/trailing whitespace and underscores
		category = category.replace(/^[\s_]+/, "").replace(/[\s_]+$/, "");

		// escape regexp metacharacters (= any ASCII punctuation except _) 
		category = category.replace(/([!-\/:-@\[-^`{-~])/g, '\\$1');

		// 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_]*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy][\\s_]*:[\\s_]*' + category + '[\\s_]*(\\|[^\\]]*(?:\\][^\\]]+)*)?\\]\\]', 'g');
	},

	getContent: function (filename, targetcat, mode) {

		var data = {
			action: 'query',
			prop: 'info|revisions',
			rvprop: 'content|timestamp',
			intoken: 'edit',
			titles: filename
		};

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

	editCategories: function (result, filename, targetcat, mode) {

		if (result == null) {
			//Happens on unstable wifi connections..
			this.updateCounter();
			this.connectionError.push(filename);
			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;
			var otext = pages[id].revisions[0]['*'];
			var starttimestamp = pages[id].starttimestamp;
			var timestamp = pages[id].revisions[0]['timestamp'];
		}


		var sourcecat = wgTitle;

		// Check if that file is already in that category
		if (mode != "remove" && this.regexBuilder(targetcat).test(otext)) {

			this.updateCounter();
			//Still remove old one if mode=move?
			this.alreadyThere.push(filename);

			return;
		}

		// Fix text
		var text = otext;
		var comment;

		switch (mode) {
		case 'copy':
			text += "\n[[" + "Category:" + targetcat + "]]\n";
			comment = "Moving from [[" + "Category:" + sourcecat + "]] to [[" + "Category:" + targetcat + "]]";
			if (this.searchmode) comment = "Adding [[" + "Category:" + targetcat + "]]";
			break;
		case 'move':
			text = text.replace(this.regexBuilder(sourcecat), "[[Category:" + targetcat + "$1]]");
			comment = "Copying from [[" + "Category:" + sourcecat + "]] to [[" + "Category:" + targetcat + "]]";
			break;
		case 'remove':
			text = text.replace(this.regexBuilder(sourcecat), "");
			comment = "Removing from [[" + "Category:" + sourcecat + "]]";
			break;
		}

		if (text == otext) {
			this.updateCounter();
			this.notFound.push(filename);
			return;
		}

		var data = {
			action: 'edit',
			summary: comment,
			title: filename,
			token: this.edittoken,
			starttimestamp: starttimestamp,
			basetimestamp: timestamp,
			text: text
		};

		this.doAPICall(data, function () {
			catALot.updateCounter();
		});
	},

	updateCounter: function () {

		this.counterCurrent++;
		this.domCounter.text(this.counterCurrent);
		if (this.counterCurrent == this.counterNeeded) this.displayResult();
	},

	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>Done!</h3>');
		//FIXME Click here to reload the page
		rep.append('All pages are processed. You can now reload the page');

		if (this.alreadyThere.length) {
			rep.append('<h5>The following pages were skipped, because the page was already in the category:</h5>');
			rep.append(this.alreadyThere.join('<br>'));
		}
		if (this.notFound.length) {
			rep.append('<h5>The following pages were skipped, because the old category could not be found:</h5>');
			rep.append(this.notFound.join('<br>'));
		}
		if (this.connectionError.length) {
			rep.append('<h5>The following pages couldn\'t be changed, since there were problems connecting to the server:</h5>');
			rep.append(this.connectionError.join('<br>'));
		}

	},

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

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

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

	doSomething: function (targetcat, mode) {
		//Paranoia
		if (this.lock) return alert('Please reload page');

		var files = this.getMarkedLabels();
		if (files.length == 0) {
			alert("No files selected.");
			return;
		}
		this.lock = true;
		this.notFound = [];
		this.alreadyThere = [];
		this.connectionError = [];
		this.counterCurrent = 0;
		this.counterNeeded = files.length;
		this.showProgress();
		for (var i = 0; i < files.length; i++) {
			this.getContent(files[i], targetcat, mode);
		}
	},

	doAPICall: function (params, callback) {

		params.format = 'json';
		$.ajax({
			url: this.apiUrl,
			cache: false,
			dataType: 'json',
			data: params,
			type: 'POST',
			success: callback
		});
	},

	createCatLinks: function (symbol, list) {
		list.sort();
		var domlist = this.catlist.find('ul');
		for (var i = 0; i < list.length; i++) {
			var li = $('<li></li>');

			var link = $('<a></a>');
			link.text(list[i]);
			li.data('cat', list[i]);
			link.click(function () {
				catALot.updateCats($(this).parent().data('cat'));
			});

			var move = $('<a class="cat_a_lot_action"><b> Move</b></a>');
			move.click(function () {
				catALot.moveHere($(this).parent().data('cat'));
			});

			var copy = $('<a class="cat_a_lot_action"><b>Copy</b></a>');
			copy.click(function () {
				catALot.copyHere($(this).parent().data('cat'));
			});

			li.append(symbol).append(' ').append(link);

			// Can't move to source category
			if (list[i] != wgTitle && this.searchmode) li.append(' ').append(copy);
			else if (list[i] != wgTitle && !this.searchmode) li.append(' ').append(move).append(' ').append(copy);

			domlist.append(li);
		}
	},
	getCategoryList: function () {
		this.catCounter = 0;
		this.getParentCats();
		this.getSubCats();
	},
	showCategoryList: function () {
		var thiscat = [this.currentCategory];

		this.catlist.empty();
		this.catlist.append('<ul></ul>');

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

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

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

		this.currentCategory = newcat;
		this.catlist = $('#cat_a_lot_category_list');
		this.catlist.html('<div class="cat_a_lot_loading">Loading...</div>');
		this.getCategoryList();
	},
	showProgress: function () {

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

		this.progressDialog = $('<div></div>').html('Editing page <span id="cat_a_lot_current">' + this.counterCurrent + '</span> 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();
			$("#cat_a_lot_data").show();

			if (this.searchmode) this.updateCats("Pictures and images");
			else this.updateCats(wgTitle);

		} else {
			$("#cat_a_lot_data").hide();

			//Unbind click handlers
			this.labels.unbind('click');
		}
	}
};

if (wgNamespaceNumber == 14) {
	$j(document).ready(catALot.init);
}

if (wgNamespaceNumber == -1 && wgCanonicalSpecialPageName == "Search") {
	catALot.searchmode = true;
	$j(document).ready(catALot.init);
}

// </source>