MediaWiki:Commander.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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>