MediaWiki:Commander.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.
/**
* Commons Commander
* (c) 2011 by Magnus Manske
* Released under GPL V2 or higher
**/

// <nowiki>

String.prototype.ucFirst = function() {
    return this[0].toUpperCase() + this.substring(1);
};

String.prototype.lcFirst = function() {
    return this[0].toLowerCase() + this.substring(1);
};

var commander = {
	
	pagetitle : 'Commons Commander' ,
	moving_text : 'MOVING $$ files<br/>(press ALT to copy)' ,
	copying_text : 'COPYING $$ files<br/>(release ALT to move)' ,
	tabs : [ 'cat', 'user' , 'rc' ] , //, 'fs' , 'cs' ) ,
	api : '' ,
	idx : '' ,
	params : { pane1 : 'cat' , pane2 : 'rc' , run1 : 0 , run2 : 0 } ,
	panes : [],
	thumb_max : 120 ,
	scroll_lookahead : 6 ,
	auto_refresh_delay : 5 , // sec.
	batch_size : 60 ,
	throwaray_id : 0 ,
	
	init : function () {
		var wgScriptPath = mw.config.get('wgScriptPath');
		$.contextMenu.theme = 'osx' ;
		commander.api = mw.config.get('wgServer') + wgScriptPath + "/api.php" ;
		commander.idx = mw.config.get('wgServer') + wgScriptPath + "/index.php" ;
		commander.content_id = 'bodyContent' ;
		mw.loader.load('//tools.wmflabs.org/magnus-toolserver/commcomm/wpinclude.css', 'text/css');

		document.title = commander.pagetitle ;
		$('#firstHeading').html(commander.pagetitle);
		$('#'+commander.content_id).html('');
		
		var h = parseInt($(window).height());
		h -= parseInt($('#'+commander.content_id).offset().top);
		h -= parseInt($('#footer').height());
		$('#'+commander.content_id).css('min-height',h+'px');

		$('#footer').hide() ;

		// Vector hack
		$('#'+commander.content_id).width($('#'+commander.content_id).width()) ;
		$('#'+commander.content_id).css('position','absolute') ;
		$('#content').width($('#content').width()).height($('#content').height()+$('#'+commander.content_id).height()-5);
		
		
		$('#'+commander.content_id).append(commander.getMainHTML(1));
		$('#'+commander.content_id).append(commander.getMainHTML(2));
//		$('#'+commander.content_id).append(commander.getCenterBar());
		
		commander.setTopBox ( 1 ) ;
		commander.setTopBox ( 2 ) ;
		
		// Init site params
		$.each ( commander.tabs , function ( k , v ) {
			if ( typeof commander.params[v+'1'] == 'undefined' ) commander.params[v+'1'] = '' ;
			if ( typeof commander.params[v+'2'] == 'undefined' ) commander.params[v+'2'] = '' ;
		} ) ;
		commander.params.user1 = mediaWiki.config.get ( 'wgUserName' ) ;
		commander.params = commander.getUrlVars ( commander.params ) ;
		
		if ( commander.params.testing == 1 ) $('#firstHeading').append ( ' [TEST MODE]' ) ;

		// Action params

		$('#catname_1').val ( commander.params.cat1 ) ;
		$('#catname_2').val ( commander.params.cat2 ) ;
		
		$('#username_1').val ( commander.params.user1 ) ;
		$('#username_2').val ( commander.params.user2 ) ;
		
		$.each ( commander.tabs , function ( k , v ) {
			
			for ( var i = 1 ; i <= 2 ; i++ ) {
				if ( v != commander.params['pane'+i] ) continue ;
				$('#tab_'+i).tabs( 'select' , k ) ;
				if ( commander.params['run'+i] != 1 ) continue ;
				if ( v == 'cat' ) $('#cat_button_'+i).click();
				else if ( v == 'user' ) $('#user_button_'+i).click();
			}
		} ) ;
		
		commander.updateSelectionStatus();
		
/*		
		mw.loader.load('http://toolserver.org/~magnus/commcomm/main.css', 'text/css');
		mw.loader.load('http://toolserver.org/~magnus/commcomm/jquery.contextMenu/jquery.contextMenu.css', 'text/css');

		mw.loader.load("http://toolserver.org/~magnus/commcomm/JSONSuggestBox/jquery.jsonSuggest.js");
		mw.loader.load("http://toolserver.org/~magnus/commcomm/JSONSuggestBox/json2.js");
		mw.loader.load("http://toolserver.org/~magnus/commcomm/jquery.contextMenu/jquery.contextMenu.js");
		mw.loader.load("http://toolserver.org/~magnus/commcomm/main.js");
*/	} ,

/*
	// Obsolete by drag'n'drop
	getCenterBar : function () {
		var border = 5 ;
		var top = 2 ;
		var x1 = parseInt($('#cc_top1').position().left) + $('#cc_top1').width() + border ;
		var x2 = parseInt($('#cc_top2').position().left) - border ;
		var w = x2 - x1 ;
		
		var h = '<div id="cc_center_bar" style="position:absolute;top:'+top+'px;left:'+x1+'px;width:'+w+'px;bottom:'+top+'px;border:1px solid #DDDDDD;vertical-align:middle;text-align:center">' ;
		h += '<input id="cc_button_move_12" type="button" value="&rarr;" title="Move file from left to right category" onclick="commander.doCopyMove(1,2,false)" /><br/><br/>' ;
		h += '<input id="cc_button_copy_12" type="button" value="+&rarr;" title="Copy file into right category" onclick="commander.doCopyMove(1,2,true)" /><br/><br/>' ;
		h += '<input id="cc_button_move_21" type="button" value="&larr;" title="Move file from right to left category" onclick="commander.doCopyMove(2,1,false)" /><br/><br/>' ;
		h += '<input id="cc_button_copy_21" type="button" value="&larr;+" title="Copy file into left category" onclick="commander.doCopyMove(2,1,true)" />' ;
		h += '</div>' ;
		return h ;
	} ,
*/

	doCopyMove : function ( source_pane , target_pane , do_copy , target_cat ) {
		if ( typeof target_cat == 'undefined' ) {
			if ( commander.panes[target_pane].mode !== 0 || commander.panes[target_pane].modes[0].category === '' ) {
				alert ( "Target is not a category!" ) ;
				return ;
			}
			target_cat = commander.panes[target_pane].modes[0].category ;
		}
		
		var source_cat = '' ;
		if ( !do_copy && commander.panes[source_pane].mode === 0 && commander.panes[source_pane].modes[0].category !== '' ) {
			source_cat = commander.panes[source_pane].modes[0].category ;
		}
		
		
		var mode = commander.panes[source_pane].mode ;
		var baseid = '#cc_'+commander.tabs[mode]+'_'+source_pane ;
		commander.createCopyMoveStatusDialog ( baseid , [] ) ;
		$(baseid+' .cc_thumb_selected').each ( function ( k , v ) {
			var pagename = decodeURI ( $(v).attr('filename') ) ;
			commander.doCopyMoveFile ( pagename , target_cat , source_cat , do_copy , source_pane , target_pane ) ;
		} ) ;
	} ,
	
	createCopyMoveStatusDialog : function ( baseid , thumbs ) {
		var did = 'cc_copy_move_status_dialog' ;
		if ( $('#'+did).length > 0 ) $('#'+did).remove() ;
		var h = '<div id="' + did + '">' ;
		h += '<div id="' + did + '_content" class="cc_scrollpane">' ;
		
		if ( thumbs.length == '' ) thumbs = $(baseid+' .cc_thumb_selected') ;
		
		h += '<table border=0 cellspacing=1 cellpadding=1 style="width:100%"><tbody>' ;
		$.each ( thumbs , function ( k , v ) {
			var pagename = $(v).attr('filename') ;
			var url = commander.idx + '?title=' + pagename ;
			h += '<tr class="cc_cmsd_row" filename="' + pagename + '">' ;
			h += '<td class="cc_cmsd_elm" style="width:15px;height:15px;background-color:#999999;color:white;padding:2px">Working...</td>' ;
			h += '<td class="cc_cmsd_elm"><a href="' + url + '" target="_blank">' + decodeURI ( pagename ) + '</a></td>' ;
			h += '</td>' ;
		} ) ;
		h += '</tbody></table>' ;
		h += 'This dialog will automatically close if all files are processed correctly' ;
		
		h += '</div>' ;
		h += '</div>' ;
		$('#'+commander.content_id).append ( h ) ;
		$('#'+did).dialog ( {
			title : 'File operation status' ,
			dialogClass : 'cc_dialog' ,
			width : 600 ,
			maxHeight : 400 , // default:auto
//			buttons: { "Close": function() { $(this).dialog("close"); } }
		} ) ;
	} ,
	
	updateCopyMoveStatusDialogEntry : function ( pagename , status , text ) {
		var did = 'cc_copy_move_status_dialog' ;
		var tr = $('#'+did+' .cc_cmsd_row[filename="'+encodeURI(pagename)+'"]') ;
		if ( tr.length == 0 ) return ;
		
		var td = $(tr.find('td')[0]) ;
		if ( status == 'ok' ) {
			var col = 'green' ;
			td.html('OK!').css ( 'background-color' , col ) ;
			tr.fadeOut ( 1000 , function () {
				tr.remove() ;
				if ( $('#'+did+' .cc_cmsd_row').length == 0 ) $('#'+did).dialog("close") ;
			} ) ;
		} else if ( status == 'warning' ) {
			var col = '#FFFF84' ;
			td.html('Warning').css('background-color',col).css('color','black') ;
		} else if ( status == 'error' ) {
			var col = 'red' ;
			td.html('ERROR').css ( 'background-color' , col ) ;
		} else {
			alert ( 'Unknown status ' + status ) ;
		}
		
		if ( typeof text != 'undefined' && text != '' ) td.next().append('<br/><i>'+text+'</i>');
	} ,

	removeCategoryFromWikitext : function ( s , nwt ) {
		// TODO maybe some escaping of special chars in the category name "s" so they won't be mistaken for regexp
		var space = "[ _]" ;
		s = "\\[\\[category:\\s*" + s.replace(/[ _]/g,space).replace(/\(/g,'\\(').replace(/\)/g,'\\)') ;
		console.log ( s ) ;
		var r1 = new RegExp ( s+"\\s*\\]\\]\\s*" , 'gi' ) ;
		var r2 = new RegExp ( s+"\\|.+?\\s*\\]\\]\\s*" , 'gi' ) ;
		nwt = nwt.replace(r1,'').replace(r2,'') ;
		return nwt ;
	} ,

	doCopyMoveFile : function ( pagename , cat , oldcat , do_copy , source_pane , target_pane ) {
//		console.log ( pagename + " | " + cat + " | " + oldcat + " | " + do_copy ) ; //return ;
		$.getJSON ( commander.api + '?action=parse&prop=wikitext&format=json&callback=?' , {
			page : pagename
		} , function ( data ) {
			var wikitext = data.parse.wikitext['*'] ;
			var success = true ;
			var error_text = '' ;
			
//			console.log ( wikitext + "\n---\n" ) ;
			
			// Here we edit the wikitext
			var nwt = wikitext ;
			
			// Remove oldcat if !do_copy
			if ( !do_copy && oldcat != '' ) {
				nwt = commander.removeCategoryFromWikitext ( oldcat , nwt ) ;
				if ( nwt == wikitext ) { success = false ; error_text = 'Cannot remove category '+oldcat ; } // Cannot move
			}
			
			// Copy
			if ( cat != '' ) {
				var owt = nwt ;
				nwt = commander.removeCategoryFromWikitext ( cat , nwt ) ;
				nwt = $.trim(nwt) + "\n[[Category:" + cat.ucFirst().replace(/_/g,' ') + "]]" ;
			}
			
			if ( !success ) {
//				commander.copyMoveThumb ( pagename , source_pane , target_pane , do_copy || !success ) ;
				commander.updateCopyMoveStatusDialogEntry ( pagename , 'error' , error_text ) ;
				return ;
			}
			
			if ( commander.params.testing == 1 ) { // TESTING, will not actually edit the file description page
				console.log ( nwt + "\n\n" ) ;
				commander.copyMoveThumb ( pagename , source_pane , target_pane , do_copy || !success ) ;
				commander.updateCopyMoveStatusDialogEntry ( pagename , nwt == wikitext ? 'warning' : 'ok' , nwt == wikitext ? 'No change in wikitext' : '' ) ;
				return ;
			}
			
			if ( nwt == wikitext ) {
				commander.copyMoveThumb ( pagename , source_pane , target_pane , do_copy || !success ) ;
				commander.updateCopyMoveStatusDialogEntry ( pagename , 'warning' , 'No change in wikitext - operation not performed' ) ;
				return ;
			}

			$.post ( commander.api , {
				action : 'query' ,
				prop : 'info' ,
				intoken : 'edit' ,
				format : 'json' ,
				titles : pagename ,
			} , function ( data2 ) {
				var d ;
				$.each ( data2.query.pages , function ( k2 , v2 ) { d = v2 } ) ;
				if ( typeof d == 'undefined' ) { // Paranoia
					success = false ;
					commander.copyMoveThumb ( pagename , source_pane , target_pane , do_copy || !success ) ;
					commander.updateCopyMoveStatusDialogEntry ( pagename , ( success ? 'ok' : 'error' ) ) ;
					return ;
				}
				
				var sum = [];
				if ( oldcat != '' ) sum.push ( 'removing [[Category:'+oldcat.ucFirst()+']]' ) ;
				if ( cat != '' ) sum.push ( 'adding [[Category:'+cat.ucFirst()+']]' ) ;
				sum.push ( 'using Commons Commander' ) ;
				sum = sum.join ( '; ' ) ;

				$.post ( commander.api , {
					action : 'edit' ,
					title : pagename ,
					minor : 1 ,
					starttimestamp : d.starttimestamp ,
//					basetimestamp : d.touched ,
					text : nwt ,
					summary : sum ,
					token : d.edittoken ,
					format : 'json'
				} , function ( data3 ) {
//					console.log ( JSON.stringify ( data3 ) ) ;
					if ( data3.edit.result != 'Success' ) success = false ;
					commander.copyMoveThumb ( pagename , source_pane , target_pane , do_copy || !success ) ;
					commander.updateCopyMoveStatusDialogEntry ( pagename , ( success ? 'ok' : 'error' ) ) ;
				} , 'json' ) ;
			} ) ;
		} ) ;
	} ,
	
	copyMoveThumb : function ( pagename , source_pane , target_pane , do_copy ) {
		var o = $('#cc_'+commander.tabs[commander.panes[source_pane].mode]+'_'+source_pane+' .cc_thumb_wrapper[filename="'+encodeURI(pagename)+'"]') ;
		if ( o.length == 0 ) {
			console.log ( "Cannot find thumbnail for " + pagename + " in pane " + source_pane ) ;
			return ;
		}

		var o_target = $('#cc_'+commander.tabs[commander.panes[target_pane].mode]+'_'+target_pane+' .cc_thumb_wrapper[filename="'+encodeURI(pagename)+'"]') ;
		if ( o_target.length > 0 && source_pane != target_pane ) { // Target thumb already exists
			if ( !do_copy ) o.remove() ; // ..remove this one in moving
		} else if ( do_copy || commander.panes[source_pane].mode != 0 ) { // Copy
			if ( source_pane == target_pane ) { // ..unless same pane
			} else { // duplicate
				var n = o.clone() ;
				$('#cc_cat_'+target_pane).append ( n ) ;
				n = commander.makeThumbnailDraggable ( n , target_pane ) ;
				commander.onClickThumbnail(n);
				commander.onClickThumbnail(n);
			}
		} else { // Move
			if ( source_pane == target_pane ) { // ..unless same pane
				commander.onClickThumbnail(o);
				o.remove() ; // delete
			} else { // detach/attach
				o = o.detach() ;
				$('#cc_cat_'+target_pane).append ( o ) ;
				o = commander.makeThumbnailDraggable ( o , target_pane ) ;
				commander.onClickThumbnail(o);
				commander.onClickThumbnail(o);
			}
		}
		commander.updateSelectionStatus() ;
	} ,
	
	updateSelectionStatus : function () { // Still needed, even if center bar is obsolete
		var m1 = commander.panes[1].mode ;
		var m2 = commander.panes[2].mode ;
		
		commander.updateCategoryLinks ( 1 ) ;
		commander.updateCategoryLinks ( 2 ) ;
		
		commander.updateSelector ( 1 ) ;
		commander.updateSelector ( 2 ) ;
/*		
		// Obsolete updates
		var sel1 = $('#cc_'+commander.tabs[m1]+'_1 .cc_thumb_selected').length > 0 ;
		var sel2 = $('#cc_'+commander.tabs[m2]+'_2 .cc_thumb_selected').length > 0 ;
		
		var is_valid_target1 = commander.panes[1].mode == 0 && commander.panes[1].modes[0].category != '' ;
		var is_valid_target2 = commander.panes[2].mode == 0 && commander.panes[2].modes[0].category != '' ;
		
		var enable12m = is_valid_target2 && sel1 && m1 == 0 ? '' : 'disabled' ;
		var enable12c = is_valid_target2 && sel1 ? '' : 'disabled' ;
		var enable21m = is_valid_target1 && sel2 && m2 == 0 ? '' : 'disabled' ;
		var enable21c = is_valid_target1 && sel2 ? '' : 'disabled' ;
		
		$('#cc_button_move_12').attr ( 'disabled' , enable12m ) ;
		$('#cc_button_copy_12').attr ( 'disabled' , enable12c ) ;
		$('#cc_button_move_21').attr ( 'disabled' , enable21m ) ;
		$('#cc_button_copy_21').attr ( 'disabled' , enable21c ) ;*/
	} ,
	
	updateCategoryLinks : function ( pane ) {
		if ( commander.panes[pane].mode != 0 ) return ;
		var sel = $('#cc_'+commander.tabs[0]+'_'+pane+' .cc_thumb_selected').length > 0 ;
		if ( sel ) $('#cc_cat_sidebar'+pane+' .cc_move_copy_link').show() ;
		else $('#cc_cat_sidebar'+pane+' .cc_move_copy_link').hide() ;
	} ,

	getMainHTML : function ( n ) {
		var border = 5 ;
		var h_top = 100 ;
		var h_bottom = 30 ;
		var h = '' ;
		var factor_between_panes = 1 ;
		
		var w = Math.floor($('#'+commander.content_id).width()/2) - border * factor_between_panes ;
		var x = n == 1 ? 2 : w + border * 2 * factor_between_panes ;
		var top = 2 ;
		var bot = h_bottom + border*3 ;
		h += '<div id="cc_top'+n+'" style="position:absolute;top:'+top+'px;left:'+x+'px;width:'+w+'px;bottom:'+bot+'px;border:1px solid #DDDDDD"></div>' ;
		top = $('#'+commander.content_id).height() - h_bottom - border*2 ;
		h += '<div id="cc_bot'+n+'" style="position:absolute;top:'+top+'px;left:'+x+'px;width:'+w+'px;bottom:2px;border:1px solid #DDDDDD">' ;
		
		h += '<div id="cc_selector_'+n+'" class="cc_selector">' ;
		h += '<span id="cc_selector_'+n+'_count"></span> files selected<br/>' ;
		h += 'Select ' ;
		h += '<a href="#" onclick="commander.selectThumbs('+n+',\'all\');return false">all</a>, ' ;
		h += '<a href="#" onclick="commander.selectThumbs('+n+',\'toggle\');return false">toggle</a>, ' ;
		h += '<a href="#" onclick="commander.selectThumbs('+n+',\'none\');return false">none</a>' ;
		h += '</div>' ;
		
		h += '<div id="cc_loading'+n+'" class="cc_loading">Updating...</div>' ;
		h += '<div id="cc_bot_content'+n+'"></div>' ;
		h += '</div>' ;
		return h ;
	} ,
	
	selectThumbs : function ( pane , selmode ) {
		var mode = commander.panes[pane].mode ;
		var root = '#cc_'+commander.tabs[mode]+'_'+pane ;
		if ( selmode == 'all' ) $(root+' .cc_thumb_wrapper').addClass('cc_thumb_selected');
		else if ( selmode == 'toggle' ) $(root+' .cc_thumb_wrapper').toggleClass('cc_thumb_selected');
		else if ( selmode == 'none' ) $(root+' .cc_thumb_wrapper').removeClass('cc_thumb_selected');
		commander.updateSelectionStatus();
	} ,
	
	updateSelector : function ( pane ) {
		var id = '#cc_selector_'+pane+'_count' ;
		var mode = commander.panes[pane].mode ;
		var root = '#cc_'+commander.tabs[mode]+'_'+pane ;
		var num = $(root+' .cc_thumb_selected:not(.ui-draggable-dragging)').length ;
		$(id).html(num) ;
	} ,
	
	setTopBox : function ( pane ) {
		commander.panes[pane] = new Object ;
		var id = "tab_" + pane ;
		commander.panes[pane].tabid = id ;
		commander.panes[pane].modes = new Object ;
		$.each ( commander.tabs , function ( k , v ) {
			commander.panes[pane].modes[k] = new Object ;
			commander.panes[pane].modes[k].files = new Object ;
		} ) ;
		commander.panes[pane].mode = 0 ;
		commander.panes[pane].modes[0].category = '' ;
		commander.panes[pane].loading = 0 ;
		
		var links = [];
		var divs = [];
		
		// Categories
		header_div = "<div id='cat_header_"+pane+"'><table border='0' style='width:100%'><tr><td style='width:100%;padding-right:5px'><input type='text' name='catname_"+pane+"' id='catname_"+pane+"' style='width:100%'/></td>" ;
		header_div += "<td><input pane='"+pane+"' id='cat_button_"+pane+"' type='button' value='Show category'/></td></tr></table></div>" ;
		
		content_div = '' ;
		content_div += "<table id='tab_cat_"+pane+"' style='width:100%' border='0'>" ;
		content_div += "<tr><td valign='top' style='width:210px'><div id='cc_cat_sidebar"+pane+"' class='cc_scrollpane'></div></td>" ;
		content_div += "<td valign='top'>" ;
		content_div += '<div id="cc_cat_'+pane+'" class="cc_scrollpane"></div>' ;
		content_div += "</td></tr></table>" ;
		
//		content_div += "<div id='cc_cat_sidebar"+pane+"' class='cc_scrollpane' style='position:absolute;left:0px;top:0px;width:200px'></div>" ;
//		content_div += '<div id="cc_cat_'+pane+'" class="cc_scrollpane" style="position:absolute;width:300px;top:0px;right:0px"></div>' ;
		
		
		links.push ( "<li><a href='#cat_"+pane+"'>Categories</a></li>" ) ;
		divs.push ( "<div id='cat_"+pane+"' style=''>"+header_div+content_div+"</div>" ) ;
		
		// User uploads
		header_div = "<div id='user_header_"+pane+"'><table border='0' style='width:100%'><tr><td style='width:100%;padding-right:5px'><input type='text' name='username_"+pane+"' id='username_"+pane+"' style='width:100%' value='" ;
//		if ( pane == 2 ) header_div += mediaWiki.config.get ( 'wgUserName' ) ;
		header_div += "'/></td>" ;
		header_div += "<td><input pane='"+pane+"' id='user_button_"+pane+"' type='button' value='Show user files'/></td></tr></table></div>" ;

		content_div = '<div id="cc_user_'+pane+'" class="cc_scrollpane"></div>' ;
	
		links.push ( "<li><a href='#user_"+pane+"'>User uploads</a></li>" ) ;
		divs.push ( "<div id='user_"+pane+"'>"+header_div+content_div+"</div>" ) ;
		
		
		// Recent Changes
		content_div = '<div id="cc_rc_'+pane+'" class="cc_scrollpane"></div>' ;
	
		links.push ( "<li><a href='#rc_"+pane+"'>Recent uploads</a></li>" ) ;
		divs.push ( "<div id='rc_"+pane+"'>"+content_div+"</div>" ) ;
/*		
		// File search
		header_div = "<div id='fs_header_"+pane+"'><table border='0' style='width:100%'><tr><td style='width:100%'><input type='text' name='fs_query_"+pane+"' id='fs_query_"+pane+"' style='width:100%'/></td>" ;
		header_div += "<td><input pane='"+pane+"' id='fs_button_"+pane+"'type='button' value='Search files'/></td></tr></table></div>" ;
	
		content_div = "<table id='tab_fs_"+pane+"' style='width:100%;position:absolute;top:100px;left:0px;right:0px;bottom:0px;overflow:hidden' border='0'>" ;
		content_div += "<tr><td width='100%' valign='top'><div id='fspics_"+pane+"' style='background:#DDDDDD;overflow:auto'></div></td></tr></table>" ;
	
		links.push ( "<li><a href='#fs_"+pane+"'>File search</a></li>" ) ;
		divs.push ( "<div id='fs_"+pane+"'>"+header_div+content_div+"</div>" ) ;
	
		// Category search
		header_div = "<div id='cs_header_"+pane+"'><table border='0' style='width:100%'><tr><td style='width:100%'><input type='text' name='cs_query_"+pane+"' id='cs_query_"+pane+"' style='width:100%'/></td>" ;
		header_div += "<td><input pane='"+pane+"' id='cs_button_"+pane+"'type='button' value='Search categpanees'/></td></tr></table></div>" ;
	
		content_div = "<table id='tab_cs_"+pane+"' style='width:100%;position:absolute;top:100px;left:0px;right:0px;bottom:0px;overflow:hidden' border='0'>" ;
		content_div += "<tr><td width='100%' valign='top'><div id='cscandidates_"+pane+"' style='background:#DDDDDD;height:100px;border:1px solid black;overflow:auto;'></div></td></tr></table>" ;
		content_div += "<tr><td width='100%' valign='top'><div id='cspics_"+pane+"' style='background:#DDDDDD;overflow:auto;margin-top: 120px;'></div></td></tr></table>" ;
	
		links.push ( "<li><a href='#cs_"+pane+"'>Category search</a></li>" ) ;
		divs.push ( "<div id='cs_"+pane+"'>"+header_div+content_div+"</div>" ) ;
*/		
	
		var html = "<div style='position:absolute;top:0px;bottom:0px;right:0px;left:0px;' id='"+id+"'><ul>"+links.join('')+"</ul>"+divs.join('')+"</div>" ;
		var t = $(html);
		t.appendTo('#cc_top'+pane) ;
		t.tabs();
		t.on('tabsselect', function(event, ui) {
			commander.tabClicked ( pane , event , ui ) ;
	   	});
	   	
	   	$.each ( commander.tabs , function ( k , v ) {
	   		var ct = commander.tabs[k]+'_'+pane ;
	   		$('#'+ct).css('padding','0px') ;
	   		$('#'+ct).css('max-height',$('#cc_top'+pane).height()-$('#cc_top'+pane+' ul.ui-tabs-nav').height()/2);
	   		$('#'+ct).css('min-height',$('#'+ct).css('max-height'));
	   		if ( k == 1 ) { // HACK FIXME
	   			$('#cc_'+ct).css('max-height',$('#cc_'+commander.tabs[0]+'_'+pane).css('max-height'));
	   		} else {
				$('#cc_'+ct).css('max-height',parseInt($('#'+ct).css('max-height'))-$('#'+commander.tabs[k]+'_header_'+pane).height()-20);
			}
	   	} ) ;
		
		// 0


		$('#cc_cat_sidebar'+pane).css ( 'max-height' , $('#cc_'+commander.tabs[0]+'_'+pane).css('max-height') ) ;
		$('#cat_button_'+pane).click ( function () {
			commander.showCategory ( pane ) ;
		} ) ;
		$('#catname_'+pane).keypress ( function ( event ) {
			if ( event.which != '13' ) return ;
			$('#cat_button_'+pane).click() ;
	   	} ) ;

		$('#cc_cat_'+pane).scroll(function(){
			if ( commander.panes[pane].loading > 0 ) return ;
			if ( commander.panes[pane].modes[1].ended ) return ;
//			console.log ( ( commander.thumb_max * commander.scroll_lookahead ) + " / " + $('#cc_cat_'+pane).attr("scrollHeight") + " / " + $(this).outerHeight() ) ;
//		return $(obj).scrollTop() >= $(obj).attr("scrollHeight") - $(obj).outerHeight() - before ;
			if ( commander.isScrollBottom ( this , commander.thumb_max * commander.scroll_lookahead ) ) {
				commander.updateFromCategory ( pane ) ;
			}
		}); 
		
		var cid = '#cat_'+pane ;
		$(cid).droppable ( {
			accept : '.cc_thumb_wrapper' ,

			activate : function( event, ui ) {
				if ( commander.panes[pane].modes[0].category == '' ) return ; // No drop on blank category
				if ( ui.draggable.parents(cid).length > 0 ) return ; // No drop on self
				$(cid).addClass('cc_activate_drop_target') ;
			} ,

			deactivate : function( event, ui ) {
				$(cid).removeClass('cc_activate_drop_target') ;
			} ,

			over : function( event, ui ) {
				if ( commander.panes[pane].modes[0].category == '' ) return ; // No drop on blank category
				if ( ui.draggable.parents(cid).length > 0 ) return ; // No drop on self
				$(cid).removeClass('cc_activate_drop_target') ;
				$(cid).addClass('cc_highlight_drop_target') ;
			} ,

			out : function( event, ui ) {
				if ( commander.panes[pane].modes[0].category == '' ) return ; // No drop on blank category
				if ( ui.draggable.parents(cid).length > 0 ) return ; // No drop on self
				$(cid).addClass('cc_activate_drop_target') ;
				$(cid).removeClass('cc_highlight_drop_target') ;
			} ,
			
			drop : function( event, ui ) {
				if ( commander.panes[pane].modes[0].category == '' ) return ; // No drop on blank category
				if ( ui.draggable.parents(cid).length > 0 ) return ; // No drop on self
				$(cid).removeClass('cc_highlight_drop_target') ;
				ui.draggable.draggable({ revert: false });
				ui.helper.remove();
				var do_copy = event.altKey ;
				var cat = commander.panes[pane].modes[0].category ;
				
				commander.doCopyMove ( 3-pane , pane , do_copy , cat ) ;
/*				
				var filename = decodeURI ( ui.draggable.attr('filename') ) ;
				var oldcat = commander.panes[3-pane].modes[0].category ;
				commander.doCopyMoveFile ( filename , cat , oldcat , do_copy , 3-pane , pane ) ;*/
			}
			
		} ) ;

		// 1	
		
	   	$('#username_'+pane).keypress ( function ( event ) {
			if ( event.which != '13' ) return ;
			$('#user_button_'+pane).click() ;
	   	} ) ;
	   	$('#user_button_'+pane).click ( function () {
	   		commander.showUserImages ( pane ) ;
	   	} ) ;
	   	
		$('#cc_user_'+pane).scroll(function(){
			if ( commander.panes[pane].loading > 0 ) return ;
			if ( commander.panes[pane].modes[1].ended ) return ;
			if ( commander.isScrollBottom ( this , commander.thumb_max * commander.scroll_lookahead ) ) {
				commander.updateFromUploadLog ( pane ) ;
			}
		}); 
	} ,
	
	showCategory : function ( pane ) {
		$('#tab_'+pane).tabs('select',0);
		commander.updateSelectionStatus();
		var cat = $('#catname_'+pane).val() ;
		if ( cat == '' ) return ;
		
		commander.panes[pane].modes[0].files = new Object ;
		commander.panes[pane].modes[0].category = cat ;
		commander.panes[pane].modes[0].ended = false ;
		commander.updateSelectionStatus();

		// Cleanup
		var h = '' ;
		h += 'Parent categories:<div class="cc_parentcats"></div>' ;
		h += 'Sub-categories:<div class="cc_subcats"></div>' ;
		$('#cc_cat_sidebar'+pane).html ( h ) ;
		$('#cc_cat_'+pane).html ( '' ) ;
		
		// Files, first batch
		commander.panes[pane].modes[0].lestart = undefined ;
		commander.updateFromCategory ( pane ) ;
		
		// Category information
		$.getJSON ( commander.api + '?action=query&prop=categoryinfo&format=json&callback=?' , {
			titles : "Category:" + cat
		} , function ( data ) {
			var d ;
			$.each ( data.query.pages , function ( k , v ) { d = v } ) ;
			if ( typeof d == 'undefined' || typeof d.categoryinfo == 'undefined' ) {
				$('#cc_bot_content'+pane).html ( 'Category "' + cat + '" is empty or does not exist' ) ;
				return ;
			}
			var h = '<b>' + cat + '</b><br/>' ;
			h += commander.prettyNumber ( d.categoryinfo.pages ) + ' pages, ' ;
			h += commander.prettyNumber ( d.categoryinfo.files ) + ' files, ' ;
			h += commander.prettyNumber ( d.categoryinfo.subcats ) + ' sub-categories.' ;
			$('#cc_bot_content'+pane).html ( h ) ;
		} ) ;
		
		// Parent categories
		$.getJSON ( commander.api + '?action=query&prop=categories&cllimit=500&format=json&callback=?' , {
			titles : "Category:" + cat
		} , function ( data ) {
			if ( typeof data.query.pages == 'undefined' ) return ;
			var d ;
			$.each ( data.query.pages , function ( k , v ) { d = v } ) ;
			if ( typeof d == 'undefined' ) return ;
			if ( typeof d.categories == 'undefined' ) return ;
			
			var h = '' ;
			$.each ( d.categories , function ( k , v ) {
				h += commander.getCategoryLink ( pane , v.title ) ;
//				$('#cc_cat_sidebar'+pane+' .cc_parentcats').append ( h ) ;
			} ) ;
			if ( h != '' ) $('#cc_cat_sidebar'+pane+' .cc_parentcats').html ( '<table class="cc_catlink">' + h + '</table>' ) ;
		} ) ;
		
		// Sub-categories
		$.getJSON ( commander.api + '?action=query&list=categorymembers&cmtype=subcat&cmlimit=500&format=json&callback=?' , {
			cmtitle : "Category:" + cat
		} , function ( data ) {
			if ( typeof data.query.categorymembers == 'undefined' ) return ;
			var h = '' ;
			$.each ( data.query.categorymembers , function ( k , v ) {
				h += commander.getCategoryLink ( pane , v.title ) ;
			} ) ;
			if ( h != '' ) $('#cc_cat_sidebar'+pane+' .cc_subcats').html ( '<table class="cc_catlink">' + h + '</table>' ) ;
		} ) ;
	} ,
	
	getCategoryLink : function ( pane , cat ) {
		var cat2 = commander.removeFilePrefix ( cat ) ;
		var h = '<tr><td nowrap>' ;
		h += '<a class="cc_move_copy_link" href="#" onclick="commander.moveOrCopyInternally('+pane+',\''+encodeURI(cat)+'\',false);return false" title="Move selected files in this box to this category">&rarr;</a> ' ;
		h += '<a class="cc_move_copy_link" href="#" onclick="commander.moveOrCopyInternally('+pane+',\''+encodeURI(cat)+'\',true);return false" title="Add selected files in this box to this category">+</a> ' ;
		h += '</td><td class="cc_category_link_td">' ;
		h += '<a href="#" onclick="commander.showCategoryFromLink('+pane+',\''+encodeURI(cat2)+'\');return false">' + cat2 + '</a>' ;
		h += ' <a href="#" onclick="commander.openCategoryInPane('+(3-pane)+',\''+encodeURI(cat2)+'\');return false" title="Open in other pane">' ;
		h += pane == 1 ? '&rArr;' : '&lArr;' ;
		h += '</a>' ;
		h += '</tr>' ;
		return h ;
	} ,
	
	openCategoryInPane : function ( pane , cat ) {
		$('#catname_'+pane).val ( decodeURI(cat) ) ;
		$('#tab_'+pane).tabs( 'select' , 0 ) ;
		commander.showCategory ( pane ) ;
	} ,
	
	showCategoryFromLink : function ( pane , cat ) {
		$('#catname_'+pane).val ( decodeURI ( cat ) ) ;
		commander.showCategory ( pane ) ;
//		$('#cat_button_'+pane).click() ;
		return false ;
	} ,
	
	moveOrCopyInternally : function ( pane , cat , do_copy ) {
		cat = commander.removeFilePrefix ( decodeURI ( cat ) ) ;
		commander.doCopyMove ( pane , pane , do_copy , cat ) ;
	} ,
	
	isScrollBottom :function ( obj , before ) {
		return $(obj).scrollTop() >= $(obj).prop("scrollHeight") - $(obj).outerHeight() - before ;
	} ,

	
	showUserImages : function ( pane ) {
		var username = $('#username_'+pane).val() ;
		$('#cc_'+commander.tabs[1]+'_'+pane).html('');
		commander.panes[pane].mode = 1 ;
		commander.panes[pane].modes[1].username = username ;
		commander.panes[pane].modes[1].files = new Object ;
		commander.panes[pane].modes[1].ended = false ;
		commander.updateFromUploadLog ( pane ) ;
		commander.updateSelectionStatus();
	} ,
	
	tabClicked : function ( pane , event , ui ) {
		
		// Cache footer
		if ( typeof commander.panes[pane].mode != 'undefined' ) {
			commander.panes[pane].modes[commander.panes[pane].mode].bot_cache = $('#cc_bot_content'+pane).html() ;
		}
		
		// Kill timers
		$.each ( commander.panes[pane].modes , function ( k , v ) {
			if ( typeof v.timer == 'undefined' ) return ;
			clearTimeout ( v.timer ) ;
			commander.panes[pane].modes[k].timer = undefined ;
		} ) ;
		commander.panes[pane].mode = ui.index ;
		
		// Restore footer
		var bot = commander.panes[pane].modes[commander.panes[pane].mode].bot_cache ;
		if ( typeof bot == 'undefined' ) bot = '' ;
		$('#cc_bot_content'+pane).html(bot);
		
		commander.updateSelectionStatus();
		
		if ( ui.index == 2 ) { // RC
			commander.updateRecentChanges ( pane ) ;
		}
	} ,
	
	updateRecentChanges : function ( pane ) {
		commander.updateFromUploadLog ( pane ) ;
		commander.updateSelectionStatus();
	} ,
	
	loading : function ( pane , cnt ) {
		if ( cnt == 0 ) return ;
		if ( commander.panes[pane].loading == 0 && cnt > 0 ) $('#cc_loading'+pane).show() ;
		if ( commander.panes[pane].loading == -cnt ) $('#cc_loading'+pane).hide() ;
		commander.panes[pane].loading += cnt ;
	} ,
	
	updateFromUploadLog : function ( pane ) {
		var mode = commander.panes[pane].mode ;
		if ( commander.panes[pane].modes[mode].ended && mode != 2 ) return ;

		var ls = commander.panes[pane].modes[mode].lestart ;
		var params = {
//			rand : Math.random() , // Hack around caching; may be unnecessary
			lelimit : commander.batch_size
		} ;
		if ( mode == 1 ) {
			params.leuser = commander.panes[pane].modes[mode].username ;
			params.ledir = 'older' ;
			if ( typeof ls != 'undefined' ) params.lestart = ls ;
		}
		if ( typeof ls != 'undefined' && mode != 2 ) params.lestart = ls ;
		
		commander.loading ( pane , 3 ) ;
		$.getJSON ( commander.api + '?action=query&list=logevents&rawcontinue=&letype=upload&leprop=title|user|userid|timestamp&format=json&callback=?' , params ,function ( data ) {
			commander.loading ( pane , -1 ) ;
			if ( typeof data.query == 'undefined' ) return ;
			var images = [];
			if ( data['query-continue'] !== undefined ) commander.panes[pane].modes[mode].lestart = data['query-continue'].logevents.lestart ;
			var ended = true ;
			$.each ( data.query.logevents , function ( k , v ) { 
				if ( typeof commander.panes[pane].modes[mode].files[v.title] != 'undefined' ) return ;
				ended = false ;
				images.push ( v.title ) ;
			} ) ;
			commander.panes[pane].modes[mode].ended = ended ;
			commander.completeFileInfo ( images , pane , true ) ;
			if ( mode == 2 ) commander.panes[pane].modes[mode].timer = setTimeout ( 'commander.updateRecentChanges('+pane+')' , 1000 * commander.auto_refresh_delay ) ;
		} ) ;
	} ,

	updateFromCategory : function ( pane ) {
		var mode = commander.panes[pane].mode ;
		if ( commander.panes[pane].modes[mode].ended ) return ;

		var ls = commander.panes[pane].modes[mode].lestart ;
		var params = {
//			rand : Math.random() , // Hack around caching; may be unnecessary
			cmlimit : commander.batch_size ,
			cmtitle : "Category:" + commander.panes[pane].modes[mode].category
		} ;
		if ( typeof ls != 'undefined' ) params.cmcontinue = ls ;
		
		commander.loading ( pane , 3 ) ;
		$.getJSON ( commander.api + '?action=query&list=categorymembers&rawcontinue=&cmtype=file&format=json&callback=?' , params ,function ( data ) {
			commander.loading ( pane , -1 ) ;
			if ( typeof data.query == 'undefined' ) return ;
			var images = [];
			var ended = true ;
			if ( data['query-continue'] !== undefined ) {
				commander.panes[pane].modes[mode].lestart = data['query-continue'].categorymembers.cmcontinue ;
			}
			$.each ( data.query.categorymembers , function ( k , v ) { 
				if ( typeof commander.panes[pane].modes[mode].files[v.title] != 'undefined' ) return ;
				ended = false ;
				images.push ( v.title ) ;
			} ) ;
			commander.panes[pane].modes[mode].ended = ended ;
			commander.completeFileInfo ( images , pane , true ) ;
		} ) ;
	} ,
		
	completeFileInfo : function ( titles , pane , auto_append ) {
		var mode = commander.panes[pane].mode ;
		
		// Get image data
		$.post ( commander.api + '?action=query&prop=imageinfo&iiprop=timestamp|user|userid|size|url&format=json' , {
			titles : titles.join ( '|' ) ,
			iiurlwidth : commander.thumb_max ,
			iiurlheight : commander.thumb_max
		} , function ( data ) {
			commander.loading ( pane , -1 ) ;
			if ( typeof data.query == 'undefined' ) { commander.loading ( pane , -1 ) ; return ; }
			if ( typeof data.query.pages == 'undefined' ) { commander.loading ( pane , -1 ) ; return ; }
			$.each ( data.query.pages , function ( id , v ) {
				if ( typeof commander.panes[pane].modes[mode].files[v.title] != 'undefined' ) return ;
				if ( typeof v.imageinfo == 'undefined' ) return ;
				commander.panes[pane].modes[mode].files[v.title] = v ;
				commander.panes[pane].modes[mode].files[v.title].fresh = true ;
				commander.panes[pane].modes[mode].files[v.title].complete = true ;
				commander.panes[pane].modes[mode].files[v.title].categories = [] ;
			} ) ;
			
			// Get categories
			$.post ( commander.api + '?action=query&prop=categories&clshow=!hidden&cllimit=500&format=json' , {
				titles : titles.join ( '|' ) ,
			} , function ( data2 ) {

				commander.loading ( pane , -1 ) ;
				if ( typeof data2.query == 'undefined' ) return ;
				if ( typeof data2.query.pages == 'undefined' ) return ;
				$.each ( data2.query.pages , function ( id2 , v2 ) {
					if ( typeof v2.categories == 'undefined' ) return ;
					$.each ( v2.categories , function ( k3 , v3 ) {
						if ( typeof commander.panes[pane].modes[mode].files[v2.title] == 'undefined' ) return ;
						commander.panes[pane].modes[mode].files[v2.title].categories.push ( v3.title ) ;
					} ) ;
				} ) ;
			
				if ( auto_append ) commander.autoAppend ( pane ) ;
				
			} , 'json' ) ;
			
		} , 'json' ) ;
	} ,
	
	sortByNameAsc : function ( a , b ) {
		if ( a.title < b.title ) return -1 ;
		if ( a.title > b.title ) return 1 ;
		return 0 ;
	} ,
	
	sortByDateAsc : function ( a , b ) {
		if ( a.imageinfo[0].timestamp < b.imageinfo[0].timestamp ) return -1 ;
		if ( a.imageinfo[0].timestamp > b.imageinfo[0].timestamp ) return 1 ;
		return 0 ;
	} ,
	
	sortByDateDesc : function ( a , b ) {
		if ( a.imageinfo[0].timestamp < b.imageinfo[0].timestamp ) return 1 ;
		if ( a.imageinfo[0].timestamp > b.imageinfo[0].timestamp ) return -1 ;
		return 0 ;
	} ,
	
	autoAppend : function ( pane ) {
		var mode = commander.panes[pane].mode ;
		var f = [];
		$.each ( commander.panes[pane].modes[mode].files , function ( k , v ) {
			if ( !v.fresh ) return ;
			commander.panes[pane].modes[mode].files[k].fresh = false ;
			f.push ( v ) ;
		} ) ;
		if ( mode == 0 ) f = f.sort ( commander.sortByNameAsc ) ;
		if ( mode == 1 ) f = f.sort ( commander.sortByDateDesc ) ;
		if ( mode == 2 ) f = f.sort ( commander.sortByDateAsc ) ;
		var op = $('#cc_'+commander.tabs[mode]+'_'+pane) ; // Output pane
		var was_blank = op.html() == '' ;
		$.each ( f , function ( k , v ) {
			var h = commander.renderThumbnail(v);
			h = commander.makeThumbnailDraggable ( h , pane ) ;
			commander.addThumbContextMenu ( h , pane ) ;
			op.append ( h ) ;
		} ) ;
		if ( was_blank && mode == 2 ) {
//			op.scrollTop = op.scrollHeight; // FIXME initial scroll to bottom
			op.animate({scrollTop: op.height()*2}, 1000);
		}
	} ,
	
	addThumbContextMenu : function ( o , pane ) {
		var mode = commander.panes[pane].mode ;
		var file = decodeURI ( $(o).attr('filename') ) ;
		
		var arr = [
			
			{ 'Open' :
				{
					onclick:function(menuItem,menu) {
						var url = commander.idx + '?title=' + encodeURI(file) ;
						window.open(url,'_blank');
					} ,
					title : 'Open file in new tab'
				}
			} ,
			

			{ 'Rename' :
				{
					onclick:function(menuItem,menu) {
//						var file = decodeURI ( $(this).attr('filename') ) ;
						var newfile = prompt ( "Rename file" , commander.removeFilePrefix(file) ) ;
						if ( newfile == null ) return ; // Canceled
						if ( newfile == commander.removeFilePrefix(file) ) return ; // Same name
						newfile = 'File:' + newfile ;
						commander.renameFile ( file , newfile ) ;
					} ,
					title : 'Rename this file'
				}
			} ,
			
/*
			{ 'Test' : {  onclick:function(menuItem,menu) { alert("You clicked me!"); },
//	  className:'menu3-custom-item',
//	  hoverClassName:'menu3-custom-item-hover',
	  title:'This is the hover title'
	}
  },
  {'Delete':{
      onclick:function(menuItem,menu) { if(confirm('Are you sure?')){$(this).remove();} },
//	  icon:'delete_icon.gif',
	  disabled:false
	}
  }
*/
		] ;
		
		
		
		if ( mode == 0 ) {
			var cat = commander.panes[pane].modes[mode].category ;
			
			arr.push ( { 'Remove this file from category' :
				{
					onclick:function(menuItem,menu) {
						commander.removeFileFromCategory ( this , pane ) ;
					} ,
					title : commander.escapeDoubleQuotes ( 'Remove this file from category "'+cat+'"' )
				}
			} ) ;

			arr.push ( { 'Remove selected files from category' :
				{
					onclick:function(menuItem,menu) {
						commander.doCopyMove ( pane , pane , false , '' ) ;
					} ,
					title : commander.escapeDoubleQuotes ( 'Remove all selected files from category "'+cat+'"' )
				}
			} ) ;
		}
		
//		console.log ( JSON.stringify(commander.panes[pane].modes[mode]) ) ;
//		console.log ( file ) ;
		
		if ( commander.panes[pane].modes[mode].files[file].categories.length > 0 ) {

			var cat = '' ;
			if ( mode == 0 ) {
				var cat = commander.panes[pane].modes[mode].category ;
			}

			var first = true ;
			$.each ( commander.panes[pane].modes[mode].files[file].categories.sort() , function ( k , v ) {
				if ( cat == commander.removeFilePrefix(v) ) return ; // Do not show this same category in list...
				if ( first ) {
					arr.push ( $.contextMenu.separator ) ;
					first = false ;
				}
				var name = commander.escapeDoubleQuotes ( 'Show category "'+commander.removeFilePrefix(v)+'"' ) ;
				var obj = new Object ;
				obj[name] = {
					onclick:function(menuItem,menu) {
						commander.showCategoryFromLink ( 3-pane , encodeURI(commander.removeFilePrefix(v)) ) ;
					} ,
					title : name + ' in opposite pane'
				} ;
				arr.push ( obj ) ;
			} ) ;
		}
		
		$(o).contextMenu ( arr ) ;
	} ,
	
	removeFileFromCategory : function ( o , pane ) {
		o = $(o) ;
		var file = decodeURI ( $(o).attr('filename') ) ;
		var mode = commander.panes[pane].mode ;
		var cat = commander.panes[pane].modes[mode].category ;
//		alert ( 'Removing ' + cat + ' from ' + file ) ;
		var baseid = '#cc_'+commander.tabs[mode]+'_'+pane ;
		commander.createCopyMoveStatusDialog ( baseid , [ o ] ) ;
		commander.doCopyMoveFile ( file , '' , cat , false , pane , pane ) ;
//		o.remove() ;
	} ,
	
	renameFile : function ( oldfile , newfile ) {
		
		$.post ( commander.api , {
			action : 'query' ,
			prop : 'info' ,
			intoken : 'move' ,
			format : 'json' ,
			titles : oldfile ,
		} , function ( data ) {
			var d ;
			$.each ( data.query.pages , function ( k2 , v2 ) { d = v2 } ) ;
			if ( typeof d == 'undefined' ) { // Paranoia
				alert ( "Error getting move token : " + JSON.stringify(data) ) ;
				return ;
			}
			
			if ( commander.params.testing == 1 ) { // TESTING, will not actually move the page
				$('.cc_thumb_wrapper[filename="'+encodeURI(oldfile)+'"] .cc_thumb_caption').html ( commander.removeFilePrefix(newfile) ) ;
				$('.cc_thumb_wrapper[filename="'+encodeURI(oldfile)+'"]').attr ( 'filename' , encodeURI(newfile) ) ;
				return ;
			}

			$.post ( commander.api , {
				action : 'move' ,
				from : oldfile ,
				to : newfile ,
				token : d.movetoken ,
				reason : 'Renamed using Commons Commander' ,
				movetalk : 1 ,
				format : 'json'
			} , function ( data2 ) {

				if ( typeof data2.error != 'undefined' ) {
					alert ( "Error moving file : " + JSON.stringify(data2) ) ;
					return ;
				}

				// Adjust thumbnails
				$('.cc_thumb_wrapper[filename="'+encodeURI(oldfile)+'"] .cc_thumb_caption').html ( commander.removeFilePrefix(newfile) ) ;
				$('.cc_thumb_wrapper[filename="'+encodeURI(oldfile)+'"]').attr ( 'filename' , encodeURI(newfile) ) ;

			} ) ;
			
		} , 'json' ) ;
	} ,

	makeThumbnailDraggable : function ( h , pane ) {
		h = $(h) ;
		h = h.draggable ( { 
			zIndex : 100 ,
			helper: 'clone' , 
			revert: true , 
			containment : 'document' , 
			scroll : 'false' ,
			start : function(event, ui) {
				$(h).addClass('cc_thumb_selected'); // Always select dragged thumbnail
				ui.helper.append('<div id="cc_draghelper"></div>') ;
//				commander.updateSelectionStatus();
				setTimeout ( "commander.updateSelectionStatus();" , 10 ) ;
			} ,
			drag: function(event, ui) {
				var do_copy = event.altKey ;
				if ( commander.panes[pane].mode != 0 ) do_copy = true ; // All except categories MUST copy!
				var n = do_copy ? commander.copying_text : commander.moving_text ;
				n  = n.replace ( '$$' , h.parent().find('.cc_thumb_selected:not(.ui-draggable-dragging)').length ) ;
//				n += '<br/>' + h.parent().attr('id') ;
//				h.parent().find('.cc_thumb_selected:not(.ui-draggable-dragging)').each(function(x,a){n+='<br/>'+$(a).attr('filename');});
				$('#cc_draghelper').html(n);
			}
		} );
		return h ;
	} ,
	
	renderThumbnail : function ( i ) {
		var ctm = commander.thumb_max ;
		var h = '<div class="cc_thumb_wrapper" ' ;
		h += 'onclick="commander.onClickThumbnail(this);" ' ;
		h += 'ondblclick="commander.onDoubleClickThumbnail(this);" ' ;
		h += 'filename="' + encodeURI ( i.title ) + '"' ;
		h += '>' ;
		
		var padsize = Math.floor ( ( ctm - i.imageinfo[0].thumbheight ) / 2 ) ;
		var vpad = padsize == 0 ? '' : '<div style="height:' + padsize + 'px"></div>' ;
		
		var alt = commander.removeFilePrefix ( i.title ) + "\n" ;
		alt += "Uploaded " + i.imageinfo[0].timestamp + "\n" ;
		alt += "by " + i.imageinfo[0].user + "\n" ;
		alt += commander.prettyNumber(i.imageinfo[0].width) + " &times; " + commander.prettyNumber(i.imageinfo[0].height) + " px\n" ;
		alt += commander.prettyNumber(i.imageinfo[0].size) + " bytes" ;
		
		if ( i.categories.length > 0 ) {
			alt += "\n\n" + i.categories.sort().join("\n") ;
		}
		
		// Thumbnail
		var img_param = '' ;
		var thumb_url = i.imageinfo[0].thumburl ;
		if ( thumb_url == false ) thumb_url = '//upload.wikimedia.org/wikipedia/commons/thumb/4/47/Sound-icon.svg/'+commander.thumb_max+'px-Sound-icon.svg.png' ;
		else if ( i.title.match(/\.og.$/i) ) img_param = ' width="'+i.imageinfo[0].thumbwidth+'"' ;
		h += '<div class="cc_thumbnail" style="min-width:'+ctm+'px;min-height:'+ctm+'px" title="'+commander.escapeDoubleQuotes(alt)+'">' ;
		h += vpad ;
		h += '<img src="' + thumb_url + '" ' + img_param + '/>' ;
		h += vpad ;
		h += '</div>' ;
		
		// Legend
		h += '<div class="cc_thumb_caption" style="max-width:'+ctm+'px">' ;
		h += commander.removeFilePrefix ( i.title ) ;
		h += '</div>' ;
		
		h += '</div>' ;
		return h ;
	} ,
	
	onClickThumbnail : function ( obj ) {
		$(obj).toggleClass('cc_thumb_selected');
		commander.updateSelectionStatus();
	} ,
	
	onDoubleClickThumbnail : function ( obj ) {
		var file = $(obj).attr('filename') ;
		var url = commander.idx + '?title=' + file ;
		window.open(url,'_blank');
//		$(obj).toggleClass('cc_thumb_selected');
	} ,
	
	removeFilePrefix : function ( s ) {
		var ret = s.split(':') ;
		ret.shift() ;
		ret = ret.join(':');
		return ret ;
	} ,
	
	prettyNumber : function ( v ) {
		var val = v.toString();
		var result = "";
		var len = val.length;
		while (len > 3){
		result = ","+val.substr(len-3,3)+result;
		len -=3;
		}
		return val.substr(0,len)+result;
	} ,
	
	getUrlVars : function ( def ) {
		var vars = def , hash;
		var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
		$.each ( hashes , function ( i , j ) {
			var hash = j.split('=');
			hash[1] += '' ;
			vars[hash[0]] = decodeURI(hash[1]).replace(/_/g,' ');
		} ) ;
		return vars;
	} ,
	
	escapeDoubleQuotes : function ( s ) {
		return s.replace ( /"/g , '&quot;' ) ;
	} ,
	
	dummy : ''
} ;

// See http://www.javascripttoolbox.com/lib/contextmenu/
mw.loader.load('//tools.wmflabs.org/magnus-toolserver/commcomm/jquery.contextmenu.css','text/css');
mw.loader.load("//tools.wmflabs.org/magnus-toolserver/commcomm/jquery.contextMenu.js",'text/javascript',false);

$.when( $(document).ready, mw.loader.using('mediawiki.user')).then( function () {
	var wgScriptPath = mw.config.get('wgScriptPath');
	
	function avoidRaceCondition() {
		if ( typeof $.contextMenu == 'undefined' ) {
			setTimeout ( avoidRaceCondition , 100 ) ;
			return ;
		}
		// Pre-init code
		if ( mw.user.isAnon() ) {
			// No candy for you!
		} else if ( mediaWiki.config.get('wgNamespaceNumber') == -1 && mediaWiki.config.get('wgTitle') == 'Commander' ) {
			mw.loader.using( ['jquery.ui'], 
			function(){
			  $(document).ready( commander.init );
			} ) ;
		} else if ( mediaWiki.config.get('wgNamespaceNumber') == 2 ) { // User
			var url = mw.config.get('wgServer') + wgScriptPath + "/index.php?title=Special:Commander&pane1=user&user1=" + encodeURI(mw.config.get('wgTitle')) + "&run1=1&pane2=cat" ;
			mw.util.addPortletLink('p-tb', url, 'CommComm','t-commcomm', 'Show this category in Commons Commander ', '', '#t-print') ;
		} else if ( mediaWiki.config.get('wgNamespaceNumber') == 14 ) { // Category
			var url = mw.config.get('wgServer') + wgScriptPath + "/index.php?title=Special:Commander&pane1=cat&cat1=" + encodeURI(mw.config.get('wgTitle')) + "&run1=1&pane2=cat" ;
			mw.util.addPortletLink('p-tb', url, 'CommComm','t-commcomm', 'Show this category in Commons Commander ', '', '#t-print') ;
		}
	}
	
	avoidRaceCondition();
} ) ;
// </nowiki>