MediaWiki:Gadget-Cat-a-lot-dev.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
// <source lang="javascript">
var catALot = {
	apiUrl: mw.util.wikiScript( 'api' ),
	searchmode: false,

	init: function() {
		$('#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();
		});
		if ( mw.config.get( 'skin' ) === 'vector' ) {
			mw.util.addCSS( '#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 = [];
			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 = [];
			var	cats, id, i,
				pages = result.query.pages;
			// there should be only one, but we don't know its ID
			for ( id in pages) {
				cats = pages[id].categories;
			}
			for ( 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,
			indexpageids: 1
		};

		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;
		}

		// The edittoken only changes between logins
		var	page = result.query.pages[result.query.pageids[0]],
			otext = page.revisions[0]['*'],
			starttimestamp = page.starttimestamp,
			timestamp = page.revisions[0].timestamp,
			sourcecat = mw.config.get('wgTitle');

		this.edittoken = page.edittoken;


		// 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,
			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>>');

			var link = $('<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] != mw.config.get('wgTitle') && this.searchmode) li.append(' ').append(copy);
			else if (list[i] != mw.config.get('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>');

		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>').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(mw.config.get('wgTitle'));

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

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

if (mw.config.get('wgNamespaceNumber') == -1 && wgCanonicalSpecialPageName === 'Search') {
	catALot.searchmode = true;
}

if (mw.config.get('wgNamespaceNumber') === 14 || catALot.searchmode) {
	$(document).ready( catALot.init );
}

// </source>