મીડિયાવિકિ:Gadget-VisualFileChange.js/ui.js

શાશ્વત સંદેશ માંથી
દિશાશોધન પર જાઓ શોધ પર જાઓ

નોંધ: પાનું પ્રકાશિત કર્યા પછી, તમારે તમારા બ્રાઉઝરની કૅશ બાયપાસ કરવાની આવશ્યકતા પડી શકે છે.

  • ફાયરફોક્સ / સફારી: શીફ્ટ દબાવેલી રાખીને રિલોડ પર ક્લિક કરો, અથવા તો Ctrl-F5 કે Ctrl-R દબાવો (મેક પર ⌘-R)
  • ગુગલ ક્રોમ: Ctrl-Shift-R દબાવો (મેક પર ⌘-Shift-R)
  • ઈન્ટરનેટ એક્સપ્લોરર/એજ: Ctrl દબાવેલી રાખીને રિફ્રેશ પર ક્લિક કરો, અથવા Ctrl-F5 દબાવો
  • Opera: Ctrl-F5 દબાવો
// Main script is at [[MediaWiki:VisualFileChange.js]]
// These are the delayed loaded display components (UI) in order to speed up loading of the first
// <nowiki>
/* global jQuery:false, mediaWiki:false*/
/* eslint one-var:0, vars-on-top:0, no-underscore-dangle:0, no-multi-str:0, indent:0, valid-jsdoc:0, no-bitwise:0, camelcase:0, curly:0, space-in-parens:0, computed-property-spacing:0,array-bracket-spacing:0 */
/* jshint curly:false, multistr:true*/
(function ($, mw) {
'use strict';

// Return if Main Script is not loaded
if (!window.VisualFileChange)
	return;
// Return if this script is already loaded
if (window.VisualFileChange.displayComponents)
	return;

var vfc = window.VisualFileChange,
	i18n = vfc.i18n,
	$doc = $(document),
	$win = $(window),
	css = '.rgallery{background-color:#FFF !important;}\n\
		#AjaxMdContainer{position:relative;}\n\
		.md-toggle-pane{position:absolute;}\n\
		.md-nav-button{display:inline-block;background-color:white;border:2px solid #f3f3f3;border-radius:10px}\
		.md-nav-button:hover,.md-nav-button:focus{background-color:#EEF;}\
		.md-nav-button:active{border-color:#ccc }\
		.md-nav-button-container{position:absolute;top:3em;right:1.5em;max-width:18px }\n\
		.md-nav-button-wrap{display:inline-block;background:none!important;border:none!important;}\n\
		.numbersOnly{text-align:right;}\n\
		.md-deleted-uploads{width:99%;display:none;background:#FCC url(//upload.wikimedia.org/wikipedia/commons/d/d6/User-trash.png) no-repeat scroll center;}\n\
		a:link.md-loglink{color:#BB6!important;display:inline-block;}\n\
		a.md-talklink{color:#888!important;border-bottom:1px dotted #555;white-space:nowrap;font-style:italic;font-weight:bold;margin-right:4px;display:inline-block;text-decoration:none!important;}\n\
		.tipsycategory{border-bottom:1px dotted #888;font-style:italic;margin-right:4px;display:inline-block;cursor:help }\n\
		.tipsymetadata{color:#68B;border-bottom:1px dotted #57B;white-space:nowrap;font-style:italic;font-weight:bold;margin-right:4px;display:inline-block;cursor:help }\n\
		.jFileTitle{display:block;text-align:center;padding-right:0 !important;background:#EEE !important;margin-top:3px;border-radius:5px;}\n\
		.jFileTime{color:gray;font-size:0.9em;line-height:1.1em;}\n\
		.jFileSize{color:green;font-size:0.9em;}\n\
		li.rgallerybox{width:155px;background:#f8f8f8;border:#FFF 2px solid;}\n\
		li.md-selected{background-color:#BBE;}\n\
		li.md-selected div.thumb{background-color:#DDE;border:#FFF 1px solid;}\n\
		div.jImage{position:relative }\n\
		div.jProgress{position:absolute;top:0;left:0;height:16px;width:16px }\n\
		div.jImage.progress-doing div.jProgress{background:url(\'' + vfc.icons.current + '\') no-repeat center;}\n\
		div.jImage.progress-done div.jProgress{background:url(\'' + vfc.icons.done + '\') no-repeat center;}\n\
		div.jImage.progress-failed div.jProgress{background:url(\'' + vfc.icons.failed + '\') no-repeat center;}\n\
		div.jImage.progress-nochange div.jProgress{background:url(\'' + vfc.icons.nochange + '\') no-repeat center;}\n\
		div.jGU{position:absolute;right:0;bottom:0;height:0;width:0;overflow:visible;cursor:pointer }\n\
		li.rgallerybox div.jImage.progress-done{border:2px solid #393;}\n\
		li.rgallerybox div.jImage.progress-failed{background-color:#ebb;border:2px solid #933;}\n\
		li.rgallerybox div.jImage.progress-nochange{background-color:#eeb;border:2px solid #993;}\n\
		.md-re-escape-helper{position:absolute;z-index:1002;color:#FFF;cursor:pointer;padding:3px;\
		border-top-left-radius:8px;border-bottom-left-radius:8px;background:rgb(97,196,25);background:\
		-webkit-gradient(radial,center center,0,center center,100%,color-stop(0%,rgb(97,196,25)),color-stop(100%,rgb(180,227,145)));\
		background:radial-gradient(center,ellipse cover,rgb(97,196,25),rgb(180,227,145));}\n\
		.md-examine-contents{display:inline-block;width:49%;white-space:pre-wrap;font-family:monospace;border:1px solid #333;text-align:left;word-wrap:break-word;}\n\
		li#mdLastSelected{border:#447 2px solid;}\n';
// Fix https://bugzilla.wikimedia.org/show_bug.cgi?id=32687 and some vector readability improvements
if (mw.config.get('skin') === 'vector') {
	css += '.ui-buttonset .ui-button{margin-left:0!important;margin-right:-.3em!important;}\n\
		.ui-buttonset .ui-button-text-only > .ui-button-text{padding-left:0.5em !important;padding-right:0.5em !important;}\n\
		.ui-buttonset > label.ui-state-active{background:#EEE !important;}\n\
		.md-examine-contents{font-size:1.2em;}';
}
mw.util.addCSS(css);

$.extend(window.VisualFileChange, {
	displayComponents: true,
/**
**  Called by nextTask after the fill-in-user-dialog and by mdQueryFileDone (follows later)
**  Calls the appropriate method (uploads of cats)
**/
	mdCreateList: function () {
		var si = vfc.startInput;

		if (vfc.mdNumberOfExecs) {
			$doc.triggerHandler('vFC', ['filelist query aborted due to exection of edit-tasks', vfc]);
			return true;
		}
		if (vfc.mdListUploadsPending > 0) {
			$doc.triggerHandler('vFC', ['filelist will queried later due to a pending query', vfc]);
			setTimeout(function () {
				vfc.mdCreateList();
			}, 200); // See me later ...
			return true;
		}
		$doc.triggerHandler('vFC', ['preparing querying the filelist', vfc]);
		vfc.pb.setCurrentTaskDone();
		vfc.pb.setTaskState('list', 'md-doing');
		if (vfc.mdFirstRun) { // First time only
			vfc.pb.setHelp(i18n.mdPleaseWait);
		}

		if (vfc.internalState !== 'md')
			return;
		vfc.mdListUploadsPending++;

		var method;
		if (si.modeCat)
			method = 'mdFindCatMembers';
		if (si.modeUser)
			method = 'mdFindUploads';
		if (si.modePage)
			method = 'mdFindFiles';
		if (si.modeSearch)
			method = 'mdFindViaSearch';
		vfc.secureCall(method);
	},
	mdSaveContinueKey: function (key) {
		vfc.lastContinues.vals.push(key);
		if (vfc.lastContinues.vals.length > 2)
			vfc.lastContinues.vals.shift();
	},
	mdFindUploads: function () {
		var qp = vfc.queryParams;
		var query = {
			action: 'query',
			list: 'logevents',
			rawcontinue: 1,
			leprop: 'title|timestamp|type',
			leaction: 'upload/upload',
			leuser: qp.target,
			lelimit: vfc.mdSettings.loadBatchSize,
			ledir: qp.ledir
		};
		if (qp.lecontinue)
			query.lecontinue = qp.lecontinue;
		if (qp.lestart)
			query.lestart = qp.lestart;
		$doc.triggerHandler('vFC', ['useruploads', vfc, query]);
		vfc.queryAPI(query, 'mdFindUploadsCB');
	},
	mdFindUploadsCB: function (result) {
		vfc.mdListUploadsPending--;
		$doc.triggerHandler('vFC', ['got useruploads', vfc, result]);
		vfc.pb.setCurrentTaskDone();

		var allFileChanges = result.query.logevents,
			qp = vfc.queryParams;

		$.each(allFileChanges, function (id, fc) { // loop through all filechanges (upload-events) (array)
			// This file was initially uploaded by the user.
			if (fc.action === 'upload') {
				// Do not mess-up numbers when a file was deleted and re-uploaded
				if (fc.title in vfc.iUploads)
					return;
				vfc.iUploads[fc.title] = {
					uploader: [],
					iId: vfc.iiUploads,
					time: fc.timestamp
				};

				vfc.iiUploads++;
				// User overwrote file.
			} else if (fc.action === 'overwrite') {
				vfc.oUploads[fc.title] = {
					iId: vfc.ioUploads,
					time: fc.timestamp
				};
				vfc.ioUploads++;
			}
		});

		if (!result['query-continue'])
			vfc.mdNoMoreFiles = true;

		if (!vfc.mdNoMoreFiles) {
			qp.lecontinue = result['query-continue'].logevents.lecontinue;
			qp.lestart = result['query-continue'].logevents.lestart;
			vfc.mdSaveContinueKey(qp.lecontinue);
		}
		if (vfc.mdFirstRun) { // Call nextTask the first time only
			vfc.mdFirstRun = false;
			vfc.nextTask();
		} else {
			if (!vfc.mdActualResultCount)
				vfc.secureCall('mdQueryMore');
		}
	},
	mdFindCatMembers: function () {
		var qp = vfc.queryParams;
		var query = {
			action: 'query',
			list: 'categorymembers',
			rawcontinue: 1,
			cmtitle: qp.target,
			cmtype: 'file',
			cmprop: 'title|type|timestamp',
			cmlimit: vfc.mdSettings.loadBatchSize,
			cmdir: qp.cmdir,
			cmsort: qp.cmsort
		};
		// "[cmtype is] Ignored when cmsort=timestamp is set"
		if (qp.cmsort === 'timestamp')
			query.cmnamespace = 6;
		// Prevent passing empty params
		if (qp.cmcontinue)
			query.cmcontinue = qp.cmcontinue;
		if (qp.cmstart)
			query.cmstart = qp.cmstart;
		if (qp.cmstartsortkey)
			query.cmstartsortkey = qp.cmstartsortkey;
		$doc.triggerHandler('vFC', ['catmembers', vfc, query]);

		vfc.queryAPI(query, 'mdFindCatMembersCB');
	},
	mdFindCatMembersCB: function (result) {
		vfc.mdListUploadsPending--;
		$doc.triggerHandler('vFC', ['got catmembers', vfc, result]);
		vfc.pb.setCurrentTaskDone();

		var qp = vfc.queryParams;

		// loop through all category-members (object)
		$.each(result.query.categorymembers, function (id, fc) {
			vfc.iUploads[fc.title] = {
				uploader: [],
				comments: [],
				iId: vfc.iiUploads,
				time: fc.timestamp
			};
			vfc.iiUploads++;
		});
		if (!result['query-continue'])
			vfc.mdNoMoreFiles = true;

		if (!vfc.mdNoMoreFiles) {
			qp.cmcontinue = result['query-continue'].categorymembers.cmcontinue || result['query-continue'].categorymembers.cmstart;
			if (result['query-continue'].categorymembers.cmstart)
				qp.cmstart = result['query-continue'].categorymembers.cmstart;
			vfc.mdSaveContinueKey(qp.cmcontinue || qp.cmstart);
		}
		if (vfc.mdFirstRun) { // Call nextTask the first time only
			vfc.mdFirstRun = false;
			vfc.nextTask();
		}
	},
	mdFindFiles: function () {
		var qp = vfc.queryParams;

		var query = {
			action: 'query',
			prop: 'images',
			rawcontinue: 1,
			titles: qp.target,
			imlimit: vfc.mdSettings.loadBatchSize,
			imdir: qp.imdir
		};
		// Prevent passing empty params
		if (qp.imcontinue)
			query.imcontinue = qp.imcontinue;
		$doc.triggerHandler('vFC', ['imagesonpage', vfc, query]);

		vfc.queryAPI(query, 'mdFindFilesCB');
	},
	mdFindFilesCB: function (result) {
		vfc.mdListUploadsPending--;
		$doc.triggerHandler('vFC', ['got imagesonpage', vfc, result]);
		vfc.pb.setCurrentTaskDone();

		var qp = vfc.queryParams;

		// loop through all pages-members (there should be only one)
		$.each(result.query.pages, function (pgId, pg) {
			if (!pg.images) {
				if (pg.missing !== undefined)
					throw new Error('Page does not exist. Enter full page name!');
				if (pg.invalid)
					throw new Error('Invalid page name specified.');
				pg.images = {};
			}
			$.each(pg.images, function (imNr, im) {
				vfc.iUploads[im.title] = {
					uploader: [],
					comments: [],
					iId: vfc.iiUploads
				};
				vfc.iiUploads++;
			});
		});
		if (!result['query-continue'])
			vfc.mdNoMoreFiles = true;

		if (!vfc.mdNoMoreFiles) {
			qp.imcontinue = result['query-continue'].images.imcontinue;
			vfc.mdSaveContinueKey(qp.imcontinue);
		}
		if (vfc.mdFirstRun) { // Call nextTask the first time only
			vfc.mdFirstRun = false;
			vfc.nextTask();
		}
	},
	mdFindViaSearch: function () {
		var qp = vfc.queryParams;

		var query = {
			action: 'query',
			list: 'search',
			rawcontinue: 1,
			srsearch: qp.target,
			srlimit: vfc.mdSettings.loadBatchSize,
			srnamespace: 6
		};
		// Prevent passing empty params
		if (qp.sroffset)
			query.sroffset = qp.sroffset;
		$doc.triggerHandler('vFC', ['imagesonpage', vfc, query]);

		vfc.queryAPI(query, 'mdFindViaSearchCB');
	},
	mdFindViaSearchCB: function (result) {
		vfc.mdListUploadsPending--;
		$doc.triggerHandler('vFC', ['got imagesonpage', vfc, result]);
		vfc.pb.setCurrentTaskDone();

		var qp = vfc.queryParams;

		// loop through all search results (array)
		$.each(result.query.search, function (id, sr) {
			vfc.iUploads[sr.title] = {
				uploader: [],
				comments: [],
				iId: vfc.iiUploads,
				time: sr.timestamp
			};
			vfc.iiUploads++;
		});
		if (!result['query-continue'])
			vfc.mdNoMoreFiles = true;

		if (!vfc.mdNoMoreFiles) {
			qp.sroffset = result['query-continue'].search.sroffset;
			vfc.mdSaveContinueKey(qp.sroffset);
		}
		if (vfc.mdFirstRun) { // Call nextTask the first time only
			vfc.mdFirstRun = false;
			vfc.nextTask();
		}
	},
	mdRegExpFromString: function (st) {
		var match,
			m = st.match(vfc.mdRegExpPattern);
		if (m && m[0] && m[1])
			match = new RegExp(m[1], m[2]);
		else
			match = '';

		return match;
	},
	mdVarReplacements: function (replacetext, uploadTitle, uploadObject) {
		if (!/%.+%/.test(replacetext))
			return replacetext;

		replacetext = replacetext.replace('%PAGENAME%', uploadTitle.replace(/^File:/, ''))
			.replace('%FULLPAGENAME%', uploadTitle)
			.replace('%FULLPAGENAMEE%', encodeURIComponent(uploadTitle));
		$.each(uploadObject.metadata, function (i, el) {
			if ($.inArray(typeof el.value, ['string', 'number']) !== -1)
				replacetext = replacetext.replace('%' + el.name + '%', el.value);
		});
		return replacetext;
	},
	mdCreateExamineNode: function () {
		var $examineInput,
			$examineButton,
			$loadDivButton,
			$examineFirstDiv,
			$examineSecondDiv,
			$examineDiffDiv,
			$examineOuterDiv;

		var _getRenderLink = function (text, title, $node) {
			var rl,
				blockCSS = {
					width: '99%'
				},
				__doUnblock = function () {
					$node.unblock();
					return false;
				},
				$blockNode = $('<div>', {
					style: 'text-align:left;',
					title: 'Click to hide preview',
					click: __doUnblock
				}),
				$blockContent = $('<div>').appendTo($blockNode);
			/* $closeButton = */
			$.createIcon('ui-icon-close').css({
				cursor: 'pointer',
				'float': 'right'
			}).click(__doUnblock).appendTo($blockNode);

			var _gotParsedText = function (t) {
				$blockContent.html(t);
				// Block again in order to vertically center text
				$node.block({
					css: blockCSS,
					message: $blockNode
				});
			};
			rl = $('<a>', {
				href: '#render',
				text: 'Render preview',
				style: 'display:inline-block; float:right; font-family:sans-serif;'
			}).button().click(function (e) {
				e.preventDefault();
				$blockContent.html('').append($('<div>', {
					style: 'text-align:center; font-size:2em; line-height:1.5em',
					text: 'Server is parsing wiki-markup'
				}));
				$node.block({
					css: blockCSS,
					message: $blockNode
				});
				mw.libs.commons.api.parse(text, mw.config.get('wgUserLanguage'), title, _gotParsedText);
			});
			return rl;
		};
		$examineInput = $('<input>').attr({
			type: 'text',
			'class': 'numbersOnly',
			maxlength: 4,
			size: 11,
			placeholder: 'file number'
		});
		$examineButton = $('<button>', {
			text: 'Compare!'
		}).click(function () {
			$examineFirstDiv.text('');
			$examineSecondDiv.text('');
			if (!$examineInput.val())
				return;

			var examineInputVal = Number($examineInput.val()) - 1;
			var replaceSecurityLevel = vfc.$selPreserve.val();

			$.each(vfc.iUploads, function (upload, curUpld) {
				if (!curUpld.content || (examineInputVal !== curUpld.iId))
					return;

				var newText,
					oldText = curUpld.content,
					replaceContainer = mw.libs.wikiDOM.nowikiEscaper(oldText);

				replaceContainer.alsoPreserve(vfc.$alsoPreserve.val());

				$.each(vfc.mdReplacementMatrix, function (i, rObj) {
					var match,
						replace;
					match = rObj.match.val();
					replace = rObj.replace.val();

					if (rObj.regex[0].checked && match)
						match = vfc.mdRegExpFromString(match);

					if (!match)
						return;

					if (rObj.vars[0].checked)
						replace = vfc.mdVarReplacements(replace, upload, curUpld);

					switch (replaceSecurityLevel) {
						case 'secure':
							replaceContainer.secureReplace(match, replace);
							break;
						case 'placeholder':
							replaceContainer.replace(match, replace);
							break;
						case 'none':
							replaceContainer.ordinaryReplace(match, replace);
							break;
					}
				});

				newText = replaceContainer.doCleanUp();

				// Side by side
				$examineFirstDiv.text(oldText);
				$examineSecondDiv.text(newText);
				$examineSecondDiv.prepend(_getRenderLink(newText, upload, $examineOuterDiv));

				// Diff
				if (mw.libs.schnarkDiff && mw.libs.schnarkDiff.htmlDiff)
					$examineDiffDiv.html('<a target="_blank" href="//de.wikipedia.org/wiki/Benutzer:Schnark/js/diff/core" style="display:inline-block; float:right; font-family:sans-serif;">Powered by SchnarkDiff</a>' + mw.libs.schnarkDiff.htmlDiff(oldText, newText, true));

			});
		}).button({
			icons: {
				primary: 'ui-icon-search'
			}
		});
		$loadDivButton = $('<button>', {
			text: 'Diff'
		}).click(function () {
			var $btn = $(this);
			$btn.button({
				disabled: true,
				label: 'Loading from [[:de:Benutzer:Schnark/js/diff.js/core.js]]'
			});
			vfc.loadModule('diff', function () {
				$btn.button({
					label: 'Diff loaded'
				});
				$examineButton.click();
			});
		}).button({
			icons: {
				primary: 'ui-icon-shuffle'
			}
		});

		$examineFirstDiv = $('<div>', {
			'class': 'md-examine-contents'
		});
		$examineSecondDiv = $('<div>', {
			'class': 'md-examine-contents'
		});
		$examineDiffDiv = $('<div>', {
			'class': 'md-examine-contents',
			style: 'width:98%'
		});
		$examineOuterDiv = $('<div>', {
			style: 'text-align:center;'
		}).append($examineInput, ' ', $examineButton, ' ', $loadDivButton, ' ', $('<div>').append($examineDiffDiv, $examineFirstDiv, $examineSecondDiv));

		$('.numbersOnly').keyup(function () {
			this.value = this.value.replace(/[^0-9]/g, '');
		});

		return vfc.$createToggler('Examine scheduled changes', $examineOuterDiv.hide());
	},
	mdEscapeSpecial: function (string) {
		var specials = ['t', 'n', 'v', '0', 'f'];
		$.each(specials, function (i, s) {
			var rx = new RegExp('\\' + s, 'g');
			string = string.replace(rx, '\\' + s);
		});
		return string;
	},
	mdCreateReplaceNode: function () {
		var rowCount = 0;
		var $lastBigArea = 0;
		var $desc = $('<p>', {
			text: 'This is a very powerful tool, and it is strongly suggested that you test your edits before running a batch task. You are fully responsible for your edits. Replacement is done row-by-row from top. Rows with empty or invalid pattern fields are skipped. When not using RegExp-replace only the first occurrence of the pattern in each file will be replaced.'
		});
		var $regExpEscapeHelper = $('<div>', {
			style: 'display:none;',
			'class': 'md-re-escape-helper',
			html: 'R. → /R\\./g',
			title: 'Convert pattern to a \'match-all-occurrences-RegExp\''
		}).click(function () {
			var $rREH = $(this),
				$serviceFor = $rREH.data('mdServiceFor'),
				$serviceForEnable = $rREH.data('mdServiceForEnable');

			$serviceFor.val('/' + vfc.mdEscapeSpecial(mw.RegExp.escape($serviceFor.val())) + '/g').keyup();
			if (!$serviceForEnable[0].checked)
				$serviceForEnable.click();

			$(this).hide();
		});
		var $table = $('<table>').attr({
			style: 'border:1px solid #BBB',
			width: '100%',
			cellspacing: 0,
			cellpadding: 0
		});
		/* var $thead = */
		$('<thead>').append(
			$('<th>').append($('<abbr>', {
				title: 'Replacing is done for each selected file from top to bottom',
				text: 'Nr '
			})),
			$('<th>').append($('<a>', {
				title: 'Flags (=options) control how to treat your input.',
				text: 'Flags',
				target: '_blank',
				href: mw.util.getUrl('Help:VisualFileChange.js') + '#Custom_replace:_Flags'
			})),
			$('<th>').append($('<abbr>', {
				title: 'Simple string or a /Regular Expression/ \nSimple strings are only replaced once per file',
				text: 'Pattern to match'
			})),
			$('<th>').append($('<abbr>', {
				title: 'Text, %variables% and/or submatches of a RegExp ($1-$9) like /abc(.+?)ghi/',
				text: 'Text to insert instead'
			}))).appendTo($table);
		var $selPreserve = vfc.$selPreserve = $('<select>').attr({
			id: 'selPreserve',
			size: 1
		}).append(
			$('<option>', {
				text: 'Preserve nowikis, comments. Do this as secure as possible (internal split into array). (recommended)',
				value: 'secure'
			}),
			$('<option>', {
				text: 'Preserve nowikis, comments. Allow usage of substring and $1 (internal usage of placeholders (%v%f%c%\\d+)).',
				value: 'placeholder'
			}),
			$('<option>', {
				text: 'Do not preserve nowikis and comments.',
				value: 'none'
			}));
		var $alsoPreserveL = vfc.$alsoPreserve = $('<label>', {
			'for': 'alsoPreserve',
			text: 'Also preserve the following areas from being replaced.'
		});
		var $alsoPreserve = vfc.$alsoPreserve = $('<input>').attr({
			type: 'text',
			id: 'alsoPreserve',
			placeholder: 'RegExp without flags enclosed in (): /(toPreserve)/ Example: /(<gallery>(?:.|\\n)*?<\\/gallery>)/',
			style: 'width:99%'
		});
		var $tbody = $('<tbody>', {
			id: 'mdRTableBody'
		}).appendTo($table);
		var $rn = $('<tr>', {
			id: 'mdReplaceTextNode'
		}).append($('<td>').append($desc, $regExpEscapeHelper, $table, $selPreserve, $('<br>'), $alsoPreserveL, $alsoPreserve, $('<br>'), vfc.mdCreateExamineNode()));
		var $newRow;
		var regExTester = function ($text, $cb, $replace) {
			var testConditions = function () {
				var val = $text.val(),
					isRE = vfc.mdRegExpPattern.test(val);
				$cb.parent().css('background', '');
				$text.css('background', '');
				if (isRE && !$cb[0].checked)
					$cb.parent().css('background', '#FCC');
				else if (!isRE && $cb[0].checked)
					$text.css('background', '#FCC');

				if (!val || isRE)
					$regExpEscapeHelper.hide();
				else
					$regExpEscapeHelper.show();

			};
			var testEmptyReplace = function () {
				$replace.css('background', '');
				if ($text.val() && !$replace.val())
					$replace.css('background', '#FEB');

			};
			$text.on('input keyup change', testConditions);
			$text.on('input keyup change', testEmptyReplace);
			$cb.change(testConditions);
			$replace.on('input keyup change', testEmptyReplace);
		};
		var resizeHandler = function ($el) {
			$el.focus(function () {
				if ($lastBigArea)
					$lastBigArea.css('height', '');
				$el.css('height', '5em');
				$lastBigArea = $el;
			});
			return $el;
		};
		var $newMatchArea = function () {
			return resizeHandler($('<textarea>').attr({
				id: 'mdMatchText' + rowCount,
				rows: 1,
				cols: 50,
				style: 'width:95%',
				placeholder: 'text or pattern to be replaced'
			}).on('input.resize keyup.resize change.resize', function (/* e */) {
				var $el = $(this);
				if (!$el.val())
					return;
				$el.off('input.resize keyup.resize change.resize');
				$newRow();
			})).focus(function () {
				var $el = $(this),
					val = $el.val(),
					pos = $el.offset();

				$regExpEscapeHelper.show().offset({top: pos.top + 5, left: pos.left - $regExpEscapeHelper.width() - 6});
				$regExpEscapeHelper.data('mdServiceFor', $el);
				$regExpEscapeHelper.data('mdServiceForEnable', $el.data('mdServiceForEnable'));

				if (!val || vfc.mdRegExpPattern.test(val))
					$regExpEscapeHelper.hide();

			});
		};
		var $newReplaceArea = function () {
			return resizeHandler($('<textarea>').attr({
				id: 'mdReplaceText' + rowCount,
				rows: 1,
				cols: 50,
				style: 'width:98%',
				placeholder: 'text or variables to be inserted instead'
			}));
		};
		var $newRegexCheckBox = function () {
			return $('<input>').attr({
				type: 'checkbox',
				id: 'mdRRegEx' + rowCount
			});
		};
		var $newRegexLabel = function () {
			return $('<label>', {
				'for': 'mdRRegEx' + rowCount,
				text: '/R/',
				title: 'Select if Regular Expression. Use full JavaScript RegExp-Syntax like /find.+/g'
			});
		};
		var $newVarCheckBox = function () {
			return $('<input type="checkbox" checked="checked">').attr({
				id: 'mdRVar' + rowCount
			});
		};
		var $newVarLabel = function () {
			return $('<label>', {
				'for': 'mdRVar' + rowCount,
				text: '%V%',
				title: 'Select to allow inserting variables like %FULLPAGENAME%'
			});
		};
		$newRow = function () {
			rowCount++;
			var $mA = $newMatchArea(),
				$rA = $newReplaceArea(),
				$rE = $newRegexCheckBox(),
				$rEL = $newRegexLabel(),
				$v = $newVarCheckBox(),
				$vL = $newVarLabel();

			vfc.mdReplacementMatrix.push({
				match: $mA,
				replace: $rA,
				regex: $rE,
				vars: $v
			});

			$mA.data('mdServiceForEnable', $rE);
			$rE.click(function (e) {
				e.stopPropagation();
			});
			$rEL.click(function (e) {
				e.stopPropagation();
			});
			regExTester($mA, $rE, $rA);
			$('<tr>', {
				align: 'center'
			}).append(
				$('<td>').append(rowCount + '.'),
				$('<td>', {
					style: 'max-width:15%'
				}).append($rE, $rEL, $v, $vL).buttonset(),
				$('<td>').append($mA),
				$('<td>').append($rA)).appendTo($tbody);
		};
		$newRow();
		return $rn;
	},
	mdMwRTACache: {},
	mdMwReasonToAutocomplete: function (title, $el, saveKey) {
		var mwRTAtoken = vfc.mdMwRTAtoken = new Date();
		if (!title) {
			// Disable autocomplete
			$el.autocomplete({
				disabled: true
			});
			// Discard pending query
			vfc.mdMwRTAtoken = 0;
			return;
		}
		var __gotResult = function (r) {
			var $r = $(r),
				suggestions = [],
				lastSummaries = mw.storage.get(vfc.summaryChageKey);
			// Add last used summaries
			if (lastSummaries) {
				lastSummaries = JSON.parse(lastSummaries);
				if (lastSummaries[saveKey]) {
					suggestions = $.map(lastSummaries[saveKey], function (txt /* , i */) {
						return {
							value: txt
						};
					});
				}
			}

			// There is http://jqueryui.com/demos/autocomplete/#categories
			// But since there are only two groups, is seems to be unnecessary
			// to group the result
			$r.find('li > ul > li').each(function (i, li) {
				var $li = $(li),
					wikitext = '';
				$li.contents().each(function (x, n) {
					var $n = $(n),
						href = $n.attr('href');
					if (href) {
						href = href.match(vfc.mdWikipageRegExp);
						if (href && href[1]) {
							wikitext += '[[' + decodeURI(href[1]).replace(/^Commons:/, 'COM:').replace(/%27/g, '\'').replace(/%22/g, '"') + '|' + $n.text() + ']]';
							return;
						}
					}
					wikitext += $n.text();
				});
				suggestions.push({
					label: $li.text(),
					value: wikitext
				});
			});
			// Add to cache
			vfc.mdMwRTACache[title] = suggestions;
			// Upadate if the token matches
			if (mwRTAtoken === vfc.mdMwRTAtoken) {
				$el.autocomplete({
					disabled: false,
					source: suggestions
				});
			}
		};
		if (title in vfc.mdMwRTACache) {
			// If a query for this page is pending, return
			var rtaItem = vfc.mdMwRTACache[title];
			if (rtaItem instanceof Date) {
				// Allows updating on callback (__gotResult)
				vfc.mdMwRTAtoken = rtaItem;
				return;
			}
			// Use the cached value
			$el.autocomplete({
				disabled: false,
				source: rtaItem
			});
		} else {
			vfc.mdMwRTACache[title] = mwRTAtoken;
			mw.libs.commons.api.parse('{{' + title + '}}', mw.config.get('wgUserLanguage'), 'File:A.png', __gotResult, true);
		}
	},

/**
**  Mammoth method to set up and manage the UserInterface (UI)
**/
	mdGenIGallery: function () {
		$doc.triggerHandler('vFC', ['preparing initial uploads dialog', vfc]);
		vfc.mdUploadCt = 0; // Number of pending API queries
		vfc.mdActualResultCount = 0; // Doesn't include reverts and deleted media
		vfc.mdPendingBatchQueries = 0; // Number of batches currently on query; should be max. 1
		vfc.mdThumbsOnDlg = 0; // How many thumbs on the dialog?
		vfc.mdReplacementMatrix = []; // Contains pointers to jQuery-Array-Inputfields for the custom replace
		vfc.mdDateOldestDataFetched = new Date();
		vfc.$ctrs = {};

		// Tipsy-tooltips for metadata and categories
		$('.tipsymetadata').off();
		$('.tipsycategory').off();

		/*  The UI  */
		var _callExec = function (action) {
			// Save suggestions:
			if (i18n.reasonAutoSuggest[action]) {
				// FIXME: JSON.stringify/parse seems sometimes not working here
				var s = mw.storage.get(vfc.summaryChageKey);
				if (s) {
					try {
						s = JSON.parse(s);
					} catch (e) {
						mw.log.warn(e);
						s = {};
					}
				} else {
					s = {};
				}
				var ss = s[action] || [],
					nv = vfc.$ctrs.editSummary.val();
				while (ss.length > vfc.mdSettings.summaryChacheLen)
					ss.pop();

				if (nv && $.inArray(nv, ss) === -1) {
					if (ss.length === vfc.mdSettings.summaryChacheLen && ss.length)
						ss.pop();
					ss.unshift(nv);
				}
				s[action] = ss;
				mw.storage.set(vfc.summaryChageKey, JSON.stringify(s));
			}
			vfc.secureCall('mdExecute');
		};
		var confirmDlg = function (title, text, cancel, ignore, icon, action) {
			var dlg2Btns = {};
			var dlgWidth = Math.min(600, $win.width() - 250);
			dlg2Btns[cancel] = function () {
				$(this).dialog('close');
			};
			dlg2Btns[ignore] = function () {
				$(this).dialog('close');
				_callExec(action);
			};
			$('<div>').append($('<img>', {
				src: icon,
				height: 128,
				width: 128,
				style: 'float:left;'
			}), $('<div>', {
				style: 'margin-top: 30px'
			}).text(text))
				.dialog({
					title: title,
					buttons: dlg2Btns,
					modal: true,
					position: [($win.width() - dlgWidth - 250) / 2 + 250, ($win.height() - 200) / 2],
					close: function () {
						$(this).dialog('destroy');
						$(this).remove();
					}
				});
		};

		var dlgButtons = {};
		dlgButtons[i18n.submitButtonLabel] = function () {
			var action = vfc.$ctrs.ajaxMdType.val(),
				pp = i18n.mdPotentialProblems,
				cf = vfc.mdCommandsExec[action].confirm;

			if ($('input[name="mdCheckDelete"]:checked').length === 0) {
				confirmDlg(pp.titleNf, pp.textNf, pp.back, pp.proceed, vfc.icons.info, action);
			} else if (cf) {
				var cfl = i18n.mdConfirm;
				confirmDlg(cfl[cf + 'Title'], cfl[cf], cfl[cf + 'Cancel'], cfl[cf + 'Ignore'], vfc.icons.question, action);
			} else {
				_callExec(action);
			}
		};
		dlgButtons[i18n.cancelButtonLabel] = function () {
			$(this).dialog('close');
		};

		var getSelect = function () {
			var sel = '<select size="1" id="AjaxMdType">';
			$.each(vfc.mdOpt, function (id /* , opt*/) {
				var ugs = vfc.mdCommandsExec[id].userGroups,
					disabled = '';
				if ($.isArray(ugs)) {
					var ugsIntersect = $.map(mw.config.get('wgUserGroups'), function (a) {
						return $.inArray(a, ugs) < 0 ? null : a;
					});
					if (!ugsIntersect.length)
						disabled = 'disabled="disabled"';
				}
				sel += '<option value="' + id + '" ' + disabled + '>' + mw.html.escape(i18n.mdOptions[id]) + '</option>';
			});
			sel += '</select>';
			return sel;
		};

		var dlgResizeTimeout = 0,
			$AjaxMdContainer = vfc.$AjaxMdContainer.text(''),
			si = vfc.startInput;

		vfc.dlg.dialog('widget').stop(true);
		if ($AjaxMdContainer.$banner)
			$AjaxMdContainer.$banner.fadeOut();
		$AjaxMdContainer.fadeIn();
		vfc.dlg.dialog({
			modal: false,
			title: vfc.mdHelpNode +	' ' + i18n.action + ':&nbsp;' +	getSelect() + '<label for="AjaxMdIa">' +
				i18n.mdDisselectAll + '</label><input type="checkbox" id="AjaxMdIa" value="1"> &nbsp;',
			width: $win.width() - 250,
			height: $win.height(),
			buttons: dlgButtons,
			resizeStop: function (/* evt, ui*/) {
				clearTimeout(dlgResizeTimeout);
				dlgResizeTimeout = setTimeout(function () {
					$('.ui-dialog > .ui-dialog-content').eq(0).scroll();
				}, 500);
			} // Fire scroll evt on resize for (continue query if btn gets visible)
		});
		vfc.dlg.dialog('option', 'position', 'right').dialog('option', 'modal', 'false').css('padding', '3px 6px');
		var windowRezizeTimeout = 0;
		$win.resize(function () {
			clearTimeout(windowRezizeTimeout);
			windowRezizeTimeout = setTimeout(function () {
				if (vfc && vfc.dlg)
					vfc.dlg.dialog('option', 'width', $win.width() - (vfc.pb.isHidden() ? 0 : 250));
			}, 1000);
		});

		var subHeading,
			target = vfc.queryParams.target,
			targetHref = target,
			defaultHeading;

		if (si.modeCat) {
			subHeading = i18n.filesIn + ' -';
			defaultHeading = 'Files in [[:' + target + ']] ';
		}
		if (si.modeUser) {
			subHeading = i18n.filesBy + ' -';
			targetHref = vfc.mdUserTalkPrefix + targetHref;
			defaultHeading = 'Files uploaded by [[' + vfc.mdUserPrefix + target + '|' + target + ']] ([[' +
				vfc.mdUserTalkPrefix + target + '|talk]] · [[' + vfc.mdContribPrefix + target + '|contribs]])';
		}
		if (si.modePage) {
			subHeading = i18n.filesOn + ' -';
			defaultHeading = 'Files on [[' + (/^(?:File|Category)/.test(target) ? ':' : '') + target + ']] ';
		}
		targetHref = mw.util.getUrl(targetHref);
		if (si.modeSearch) {
			subHeading = i18n.filesWith + ' -';
			targetHref = document.location.href;
			defaultHeading = 'Files found with [[Special:Search/' + target + ']] ';
		}

		// Build the input-ui
		// Save the controls into a var that's reference we need later
		$.extend(vfc.$ctrs, {
			ajaxMdType: $('#AjaxMdType'),
			ajaxMdIa: $('#AjaxMdIa'),
			taskForUser: $('<label>', {
				text: i18n.mdInsertDeleteReasen
			}).attr({
				id: 'mdTaskForUser',
				'for': 'mdDeleteReason'
			}),
			deleteReason: $('<textarea>').attr({
				id: 'mdDeleteReason',
				style: 'width: 99%; height: 40px;'
			}),
			editSummaryL: $('<label>', {
				text: i18n.mdInsertEditSummary
			}).attr({
				'for': 'mdEditSummary'
			}),
			editSummary: $('<input>').attr({
				type: 'text',
				id: 'mdEditSummary',
				style: 'width: 70%;',
				maxlength: 255,
				placeholder: '+Edit summary or reason'
			}),
			replacePermission: $('<input>').attr({
				type: 'checkbox',
				id: 'mdReplacePermission'
			}),
			deleteHeading: $('<input>', {
				value: defaultHeading
			}).attr({
				type: 'text',
				id: 'mdDeleteHeading',
				style: 'width: 99%;',
				placeholder: 'Heading for deletion request'
			}),
			talkNote: $('<input>', {
				value: vfc.mdSettings.userNote
			}).attr({
				type: 'text',
				id: 'mdTalkNote',
				style: 'width: 99%;',
				placeholder: 'Comments for the uploader'
			}),
			ajaxDeletedUploads: $('<ul>').attr({
				id: 'AjaxDeletedUploads',
				'class': 'md-deleted-uploads'
			}),
			ajaxMdNotReady: $('<div>', {
				text: 'Please wait while enumerating uploads…'
			}).attr({
				id: 'AjaxMdNotReady'
			}),
			ajaxMdUlContainer: $('<ul>').attr({
				id: 'AjaxMdUlContainer',
				'class': 'gallery rgallery'
			}),
			ajaxMdActionConfirm: $('<span>').attr({
				id: 'AjaxMdActionConfirm'
			})
		});

		vfc.$ctrs.container = $('<div>', {
			id: 'mdControlContainer',
			'class': 'ui-widget-content'
		}).css('padding', '6px 10px');

		var $toTop = $('<div>', {
				'class': 'md-nav-button-wrap ui-helper-reset'
			}).append($('<a>', {
				'class': 'md-nav-button ui-icon ui-icon-arrowstop-1-n',
				href: '#goToTop',
				text: '↑',
				title: 'To top'
			})),
			$toBottom = $('<div>', {
				'class': 'md-nav-button-wrap ui-helper-reset'
			}).append($('<a>', {
				'class': 'md-nav-button ui-icon ui-icon-arrowstop-1-s',
				href: '#goToBottom',
				text: '↓',
				title: 'To bottom'
			})),
			__onHover = function () {
				$(this).addClass('ui-state-hover');
			},
			__onOut = function () {
				$(this).removeClass('ui-state-hover');
			},
			__onDown = function () {
				var $i = $(this);
				$(this).addClass('ui-state-active');
				$doc.one('mouseup', function () {
					$i.removeClass('ui-state-active');
				});
			};
		/* $navButtonContainer = */
		$('<div>', {
			'class': 'md-nav-button-container'
		}).append($toTop, ' ', $toBottom).appendTo(vfc.dlg.dialog('widget'));

		$toTop.on('mouseenter', __onHover).on('mouseleave', __onOut)
			.focus(__onHover).blur(__onOut).mousedown(__onDown).click(function (e) {
				e.preventDefault();
				vfc.dlg.scrollTop(0);
			});
		$toBottom.on('mouseenter', __onHover).on('mouseleave', __onOut)
			.focus(__onHover).blur(__onOut).mousedown(__onDown).click(function (e) {
				e.preventDefault();
				vfc.dlg.scrollTop($AjaxMdContainer.height());
				vfc.dlg.triggerHandler('scroll');
			});

		if (!vfc.dlg)
			return 0;

		var updateSelectedCount = function () {
			var $inputs = $('input[name="mdCheckDelete"]');
			var $checkedInputs = $inputs.filter(':checked');
			vfc.pb.setHelp3(vfc._msg('selected-count', $checkedInputs.length));
			$inputs.closest('li.gallerybox').removeClass('md-selected');
			$checkedInputs.closest('li.gallerybox').addClass('md-selected');
		};

		$('<a>', {
			href: '#',
			text: i18n.mdInvertSelection
		}).button().css('margin', '0').click(function (e) {
			e.preventDefault();
			var $inputs = $('input[name="mdCheckDelete"]'),
				$checkedInputs = $inputs.filter(':checked').prop('checked', false);
			$inputs.not($checkedInputs).prop('checked', true); // $uncheckedInputs
			updateSelectedCount();
		}).appendTo(vfc.$ctrs.ajaxMdIa.parent());

		$('<a>', {
			href: '#',
			text: i18n.mdCuteSelectLabel
		}).button().css('margin', '0').click(function (e) {
			e.preventDefault();
			vfc.mdCuteSelectDlg.dialog('open');
		}).appendTo(vfc.$ctrs.ajaxMdIa.parent());

		// Change the title of the browser tab/window
		document.title = 'VisualFileChange: ' + subHeading + vfc.queryParams.target + '- ';

		// Append controls to the dialog
		vfc.$ctrs.container
			.append(
				subHeading, $('<a>', {
					href: targetHref,
					target: '_blank',
					text: vfc.queryParams.target
				}), '-. ',
				vfc.$ctrs.taskForUser, ': ',
				vfc.$ctrs.deleteReason, $('<br>'),
				vfc.$ctrs.editSummaryL, ': ',
				vfc.$ctrs.editSummary,
				$('<span>', {
					id: 'mdReplacePermissionWrapper'
				}).append(
					vfc.$ctrs.replacePermission,
					$('<label>', {
						'for': 'mdReplacePermission',
						text: ' ' + i18n.mdReplacePermissionText
					})))
			.append(
				$('<table>', {
					cellspacing: 0,
					cellpadding: 0,
					style: 'position:relative;',
					width: '100%'
				})
					.append($('<tr>', {
						id: 'mdRequestPageTitle'
					}).append(
						$('<td>').append($('<label>', {
							'for': 'mdDeleteHeading',
							text: i18n.mdInsertDeleteHeading
						})),
						$('<td>', {
							width: '70%'
						}).append(vfc.$ctrs.deleteHeading))).append($('<tr>', {
						id: 'mdTalkNoteNode'
					}).append(
						$('<td>').append($('<label>', {
							'for': 'mdTalkNote',
							text: i18n.mdInsertTalkNote
						})),
						$('<td>', {
							width: '70%'
						}).append(vfc.$ctrs.talkNote))).append(vfc.mdCreateReplaceNode()))
			.appendTo($AjaxMdContainer);

		$AjaxMdContainer
			.append(vfc.$createToggler(i18n.mdDelContribsButtonLabel, vfc.$ctrs.ajaxDeletedUploads))
			.append(vfc.$ctrs.ajaxMdNotReady, vfc.$ctrs.ajaxMdUlContainer);

		// Add label to the button
		var $buttons = vfc.dlg.parent().find('.ui-dialog-buttonpane button'),
			$submitButton = $buttons.eq(0).specialButton('proceed').button({
				label: $('<span>', {
					text: i18n.submitButtonLabel
				}).append(vfc.$ctrs.ajaxMdActionConfirm)
			});
		$buttons.eq(1).specialButton('cancel'); // $cancelButton =

		var keyTimout = 0;
		var __handleCriticalKey = function (/* event*/) {
			var reason = vfc.$ctrs.deleteReason.val(),
				summary = vfc.$ctrs.editSummary.val();

			if ((reason.length < vfc.minLenReq) || (summary.length < vfc.summaryMinLen)) {
				$submitButton.button('option', 'disabled', true);
				if (vfc.minLenReq)
					vfc.pb.setHelp2(vfc._msg('enter-reason', vfc.minLenReq), true);

			} else {
				$submitButton.button('option', 'disabled', false);
				if (vfc.minLenReq) {
					vfc.pb.setHelp2(reason, true);
					mw.libs.commons.api.parse(reason, mw.config.get('wgUserLanguage'), '', $.proxy(vfc.pb.setHelp2, vfc.pb));
				}
			}
		};
		var _handleCriticalKey = function () {
			clearTimeout(keyTimout);
			keyTimout = setTimeout(__handleCriticalKey, 85);
		};
		vfc.$ctrs.deleteReason.on('input keyup change autocompleteselect', _handleCriticalKey);
		vfc.$ctrs.editSummary.on('input keyup change autocompleteselect', _handleCriticalKey);

		// Binding enter-key event.
		var __submitOnEnter = function (e) {
			if (e.which === 13 && (vfc.$ctrs.deleteReason.val().length >= vfc.minLenReq) && vfc.$ctrs.editSummary.val().length >= vfc.summaryMinLen)
				$submitButton.click();
		};
		vfc.$ctrs.deleteHeading.keyup(__submitOnEnter);
		vfc.$ctrs.talkNote.keyup(__submitOnEnter);
		vfc.$ctrs.editSummary.keyup(__submitOnEnter);

		var timoutID = 0;
		updateSelectedCount();
		vfc.$ctrs.ajaxMdIa.change(function (/* event*/) {
			var tmpChecked = this.checked;
			$('input[name="mdCheckDelete"]').each(function (/* index*/) {
				this.checked = tmpChecked;
			});
			clearTimeout(timoutID);
			timoutID = setTimeout(updateSelectedCount, 200);
		});
		$(document).off('change', 'input[name="mdCheckDelete"]');
		$(document).on('change', 'input[name="mdCheckDelete"]', function (/* e*/) {
			clearTimeout(timoutID);
			timoutID = setTimeout(updateSelectedCount, 200);
		});
		$(document).off('mouseup', 'input[name="mdCheckDelete"]');
		$(document).on('mouseup', 'input[name="mdCheckDelete"]', function (e) {
			var $ls,
				$this,
				newVal,
				$lsBox;
			$ls = $('#mdLastSelected');
			$lsBox = $ls.find('input[name="mdCheckDelete"]');
			$this = $(this).closest('li.gallerybox');

			if ($ls.length && e.shiftKey && this !== $lsBox[0]) {
				newVal = !this.checked;
				if ($this.nextAll('#mdLastSelected').length) {
					$this.nextUntil('#mdLastSelected').find('input[name="mdCheckDelete"]').each(function () {
						this.checked = newVal;
					});
				} else {
					$this.prevUntil('#mdLastSelected').find('input[name="mdCheckDelete"]').each(function () {
						this.checked = newVal;
					});
				}
				$lsBox[0].checked = newVal;
				clearTimeout(timoutID);
				timoutID = setTimeout(updateSelectedCount, 200);
			}
			$ls.removeAttr('id');
			$this.attr('id', 'mdLastSelected');
		});

		//
		// Event handler for the selection box (task to perform)
		//
		vfc.$ctrs.ajaxMdType.click(function () {
			if (mw.user.isAnon()) {
				var $anonInfo = $('<div>');
				$anonInfo.html(vfc._msg_parsed('anon-info'));
				$anonInfo.dialog({
					modal: true,
					resize: false,
					title: vfc._msg('anon-info-heading'),
					close: function () {
						$anonInfo.remove();
					}
				});
				return false;
			}
		});
		vfc.$ctrs.ajaxMdType.on('change', function (/* event*/) {
			var action = $(this).val(),
				opt = vfc.mdOpt[action],
				uTags = vfc.mdUserTags[action];

			if (!opt)
				return;
			if (action === 'del')
				$('#mdRequestPageTitle').show();
			else
				$('#mdRequestPageTitle').hide();

			// minLenReq
			vfc.minLenReq = opt.minLenReq || 0;
			vfc.summaryMinLen = opt.summaryMinLen || 0;
			vfc.$ctrs.deleteReason.keyup();

			// byte limit
			// PlugIn has a bug: It attaches multiple handlers
			// So start unbinding them.
			vfc.$ctrs.editSummary.off('keypress');
			vfc.$ctrs.editSummary.byteLimit(opt.bLimit);
			var summary = vfc.$ctrs.editSummary.val();
			var mwString = require('mediawiki.String');
			while (mwString.byteLength(summary) > opt.bLimit)
				summary = $.trim(summary.slice(0, summary.length - 1));

			vfc.$ctrs.editSummary.val(summary);

			// acceptReason
			if (opt.reasonText) {
				vfc.$ctrs.taskForUser.text(i18n[opt.reasonText]);
				vfc.$ctrs.taskForUser.show();
				vfc.$ctrs.deleteReason.show();
			} else {
				vfc.$ctrs.taskForUser.hide();
				vfc.$ctrs.deleteReason.hide();
			}
			// prefill
			if (opt.prefill) {
				if (!vfc.$ctrs.deleteReason.val())
					vfc.$ctrs.deleteReason.val(vfc[opt.prefill]);
				// For some reason RegExp.$1 will not work in the following line
				if (/(\d{16})/g.test(vfc.$ctrs.deleteReason.val()))
					vfc.$ctrs.deleteReason.val(vfc[opt.prefill].replace(/%ID/g, /(\d{16})/g.exec(vfc.$ctrs.deleteReason.val())[0]));
				if (vfc.mdURLPattern.test(vfc.$ctrs.deleteReason.val()))
					vfc.$ctrs.deleteReason.val(vfc.mdOTRSTicketPrefill.replace(/%URL/g, vfc.$ctrs.deleteReason.val()));
			}
			// confirmation
			if (opt.reasonParse) {
				vfc.pb.setHelp(i18n[opt.reasonParse]);
			} else {
				vfc.pb.setHelp2('');
				vfc.pb.setHelp('');
			}
			// talk note
			if (uTags.summary)
				$('#mdTalkNoteNode').show();
			else
				$('#mdTalkNoteNode').hide();

			// replace node
			if (opt.replaceNode)
				$('#mdReplaceTextNode').show();
			else
				$('#mdReplaceTextNode').hide();

			// OTRS replace node
			if (opt.permissionWrapper)
				$('#mdReplacePermissionWrapper').show();
			else
				$('#mdReplacePermissionWrapper').hide();

			vfc.$ctrs.ajaxMdActionConfirm.text(' (' + i18n.mdOptions[action] + ')');

			// Summary / Reason input
			vfc.$ctrs.editSummaryL.text(i18n[(opt.addSummary || 'mdInsertEditSummary')]);

			// Reason autocomplete
			vfc.mdMwReasonToAutocomplete(i18n.reasonAutoSuggest[action], vfc.$ctrs.editSummary, action);
		});
		vfc.$ctrs.ajaxMdType.val(vfc.mdSettings.defaultAction);
		vfc.$ctrs.ajaxMdType.change();
		vfc.$ctrs.ajaxMdType.on('click mousedown mouseup', function (e) {
			e.stopPropagation();
		});
		vfc.$ctrs.ajaxMdIa.on('click mousedown mouseup', function (e) {
			e.stopPropagation();
		});
		vfc.$ctrs.deleteReason.keyup();

		// cute-selection dialog
		var $metaSelect = $('<select>').attr({
				style: 'width:10em;',
				size: 1
			}),
			$uploaderSelect = $('<select>').attr({
				type: 'text',
				id: 'txtAjaxMdSelectUploader',
				size: 1,
				placeholder: i18n.cuteSelect.uploader
			});

		var dlgSelectButtons = {};
		dlgSelectButtons[i18n.cuteSelect.button] = function () {
			var $curThmb = '';
			var newVal = $('#chkAjaxMdSelect')[0].checked;
			var v_txtAjaxMdSelectCat = $.trim($('#txtAjaxMdSelectCat').val().replace(/^Category:/, '').replace(/_/g, ''));
			var v_txtAjaxMdSelectTitle = $.trim($('#txtAjaxMdSelectTitle').val().replace(/^File:/, '').replace(/_/g, ''));
			if (v_txtAjaxMdSelectTitle)
				v_txtAjaxMdSelectTitle = new RegExp(v_txtAjaxMdSelectTitle, '');
			var v_txtAjaxMdSelectWikitext = $('#txtAjaxMdSelectWikitext').val();
			if (v_txtAjaxMdSelectWikitext)
				v_txtAjaxMdSelectWikitext = new RegExp(v_txtAjaxMdSelectWikitext, '');
			var v_txtAjaxMdSelectSize = $('#txtAjaxMdSelectSize').val();
			var vl_selAjaxMdSelectSize = ((String($('#selAjaxMdSelectSize').val())) === 'l');
			var vl_selAjaxMdSelectUploader = $uploaderSelect.val();
			var v_txtAjaxMdSelectMeta = new RegExp($.trim($('#txtAjaxMdSelectMeta').val()), '');
			var vl_selAjaxMdSelectMeta = $metaSelect.val();
			var v_txtAjaxMdSelectDate0 = $('#txtAjaxMdSelectDate0').val();
			var dateFromString = function (st) {
				try {
					/(\d{4})-(\d\d?)-(\d\d?)(?: (\d\d?):(\d\d?):(\d\d?))?/.exec(st);
					return RegExp.$4 ? (new Date(RegExp.$1, (RegExp.$2 - 1), RegExp.$3, RegExp.$4, RegExp.$5, RegExp.$6)) : (new Date(RegExp.$1, (RegExp.$2 - 1), RegExp.$3));
				} catch (ex) {
					return 0;
				}
			};
			if (v_txtAjaxMdSelectDate0) {
				v_txtAjaxMdSelectDate0 = dateFromString(v_txtAjaxMdSelectDate0);
				if (!v_txtAjaxMdSelectDate0) {
					alert('Invalid start-date');
					return;
				}
			}
			var v_txtAjaxMdSelectDate1 = $('#txtAjaxMdSelectDate1').val();
			if (v_txtAjaxMdSelectDate1 !== '') {
				v_txtAjaxMdSelectDate1 = dateFromString(v_txtAjaxMdSelectDate1);
				if (!v_txtAjaxMdSelectDate1) {
					alert('Invalid end-date');
					return;
				}
			}

			$.each(vfc.iUploads, function (upload, curUpld) {
				try {
					var isMatch = {};
					// yyyy    mm     dd
					/(\d{4})-(\d\d)-(\d\d)T(?:(\d\d):(\d\d):(\d\d))Z/.exec(curUpld.time);
					var curUpldDate = new Date(RegExp.$1, (RegExp.$2 - 1), RegExp.$3, RegExp.$4, RegExp.$5, RegExp.$6);
					$curThmb = $('input[value="' + upload.replace('"', '\\"') + '"]');

					if (v_txtAjaxMdSelectCat) {
						for (var ct in curUpld.categories) {
							if (curUpld.categories.hasOwnProperty(ct) && (curUpld.categories[ct].title.replace('Category:', '') === v_txtAjaxMdSelectCat))
								isMatch.cat = true;

						}
					} else {
						isMatch.cat = true;
					}

					if (typeof (v_txtAjaxMdSelectTitle) === 'object') {
						if (v_txtAjaxMdSelectTitle.test(upload.replace('File:', '')))
							isMatch.title = true;

					} else {
						isMatch.title = true;
					}

					if (typeof (v_txtAjaxMdSelectWikitext) === 'object' && curUpld.content) {
						if (v_txtAjaxMdSelectWikitext.test(curUpld.content))
							isMatch.content = true;

					} else {
						isMatch.content = true;
					}

					if (v_txtAjaxMdSelectSize !== '') {
						if ((vl_selAjaxMdSelectSize && (curUpld.size < v_txtAjaxMdSelectSize)) || (!vl_selAjaxMdSelectSize && (curUpld.size > v_txtAjaxMdSelectSize)))
							isMatch.size = true;
					} else {
						isMatch.size = true;
					}

					if (vl_selAjaxMdSelectUploader) {
						if ($.inArray(vl_selAjaxMdSelectUploader, curUpld.uploader) !== -1)
							isMatch.uploder = true;
					} else {
						isMatch.uploder = true;
					}

					if (typeof (v_txtAjaxMdSelectDate0) === 'object') { // between -> start date -> must be in the past
						if (curUpldDate >= v_txtAjaxMdSelectDate0)
							isMatch.date0 = true;
					} else {
						isMatch.date0 = true;
					}

					if (typeof (v_txtAjaxMdSelectDate1) === 'object') { // and -> end date -> must be in the future
						if (curUpldDate <= v_txtAjaxMdSelectDate1)
							isMatch.date1 = true;
					} else {
						isMatch.date1 = true;
					}

					if (vl_selAjaxMdSelectMeta) {
						for (var md in curUpld.metadata) {
							if (curUpld.metadata.hasOwnProperty(md) && curUpld.metadata[md].name === vl_selAjaxMdSelectMeta && v_txtAjaxMdSelectMeta.test(curUpld.metadata[md].value))
								isMatch.meta = true;

						}
					} else {
						isMatch.meta = true;
					}

					if (isMatch.cat && isMatch.title && isMatch.content && isMatch.size && isMatch.meta && isMatch.uploder && isMatch.date0 && isMatch.date1 && ($curThmb.length === 1))
						$curThmb[0].checked = newVal;
				} catch (ex) {
					vfc.log('Error: ' + ex);
				}
			});
			updateSelectedCount();
		};

		var cuteI18n = i18n.cuteSelect,
			$AjaxMdCuteSelect = $('<div>', {
				id: 'AjaxMdCuteSelect'
			});
		$AjaxMdCuteSelect
			.append(cuteI18n.intro)
			.append($('<table>').append(
				$('<tr>').append(
					$('<td>').append($('<label>', {
						'for': 'chkAjaxMdSelect',
						text: cuteI18n.select
					})),
					$('<td>').append($('<input>', {
						type: 'checkbox',
						id: 'chkAjaxMdSelect',
						value: 1,
						checked: ''
					}))),
				$('<tr>').append(
					$('<td>').append($('<label>', {
						'for': 'txtAjaxMdSelectCat',
						text: cuteI18n.inCat
					})),
					$('<td>').append($('<input>', {
						type: 'text',
						id: 'txtAjaxMdSelectCat',
						size: 50
					}), cuteI18n.and)),
				$('<tr>').append(
					$('<td>').append($('<label>', {
						'for': 'txtAjaxMdSelectTitle',
						html: cuteI18n.title + ' <abbr title="Regular Expression. Example: Tower.+ will select Towers or TowersInSpain but not Tower">(RegExpr)</abbr>'
					})),
					$('<td>').append($('<input>').attr({
						type: 'text',
						id: 'txtAjaxMdSelectTitle',
						size: 50,
						placeholder: cuteI18n.titleplaceholder
					}), cuteI18n.and)),
				$('<tr>').append(
					$('<td>').append($('<label>', {
						'for': 'txtAjaxMdSelectWikitext',
						html: cuteI18n.wikitext + ' <abbr title="Regular Expression. Example: \\{\\{[Tt]emplate\\}\\} would match {{template}} and {{Template}}">(RegExpr)</abbr>'
					})),
					$('<td>').append($('<input>').attr({
						type: 'text',
						id: 'txtAjaxMdSelectWikitext',
						size: 50,
						placeholder: cuteI18n.wikitextplaceholder
					}), cuteI18n.and)),
				$('<tr>').append(
					$('<td>').append(cuteI18n.size + ' <select size="1" id="selAjaxMdSelectSize"><option value="l">&lt</option><option value="g">&gt</option></select>'),
					$('<td>').append($('<input>', {
						type: 'text',
						'class': 'numbersOnly',
						id: 'txtAjaxMdSelectSize',
						size: 15
					}),
					$('<label>', {
						'for': 'txtAjaxMdSelectSize',
						text: cuteI18n.kibibyte
					}), ' ', cuteI18n.and)),
				!vfc.startInput.modeUser ? $('<tr>').append(
					$('<td>').append($('<label>', {
						'for': 'txtAjaxMdSelectUploader',
						text: cuteI18n.uploader
					})),
					$('<td>').append($uploaderSelect, cuteI18n.and)) : '',
				$('<tr>').append(
					$('<td>').append($metaSelect),
					$('<td>').append($('<label>', {
						'for': 'txtAjaxMdSelectMeta',
						text: cuteI18n.matches
					}),
					$('<input>').attr({
						type: 'text',
						placeholder: cuteI18n.titleplaceholder,
						id: 'txtAjaxMdSelectMeta',
						size: 41
					}), cuteI18n.and)),
				$('<tr>').append(
					$('<td>').append(cuteI18n.date),
					$('<td>').append(
						$('<label>', {
							'for': 'txtAjaxMdSelectDate0'
						}).append($('<abbr>', {
							text: cuteI18n.between,
							title: cuteI18n.after
						})),
						$('<input>')
							.attr({
								type: 'text',
								id: 'txtAjaxMdSelectDate0',
								size: 20,
								'class': 'dateOnly',
								maxlength: 19,
								placeholder: cuteI18n.dateplaceholder
							})
							.datepicker({
								changeYear: true,
								changeMonth: true,
								dateFormat: 'yy-mm-dd 00:00:00',
								showWeek: true,
								firstDay: 1
							})
							.tipsy({
								trigger: 'focus',
								gravity: 's',
								html: true,
								title: function () {
									return i18n.optStartAtHowTo;
								}
							}),
						$('<label>', {
							'for': 'txtAjaxMdSelectDate1'
						}).append($('<abbr>', {
							text: cuteI18n.and,
							title: cuteI18n.before
						})),
						$('<input>')
							.attr({
								type: 'text',
								id: 'txtAjaxMdSelectDate1',
								size: 20,
								'class': 'dateOnly',
								maxlength: 19,
								placeholder: cuteI18n.dateplaceholder
							})
							.datepicker({
								changeYear: true,
								changeMonth: true,
								dateFormat: 'yy-mm-dd 23:59:59',
								showWeek: true,
								firstDay: 1
							})
							.tipsy({
								trigger: 'focus',
								gravity: 's',
								html: true,
								title: function () {
									return i18n.optStartAtHowTo;
								}
							})))));

		var pbWidth = (vfc.pb.isHidden() ? 0 : 250);
		var dlgWidth = Math.min(600, $win.width() - pbWidth);
		vfc.mdCuteSelectDlg = $('<div>').append($AjaxMdCuteSelect).dialog({
			modal: true,
			resizable: false,
			closeOnEscape: true,
			position: [($win.width() - dlgWidth - pbWidth) / 2 + pbWidth, 0],
			title: cuteI18n.heading,
			height: 'auto',
			width: dlgWidth,
			buttons: dlgSelectButtons,
			open: function (/* evt, ui*/) {
				$metaSelect.children().remove();
				$uploaderSelect.children().remove();
				$('<option>', {
					text: ' ',
					value: ''
				}).appendTo($metaSelect).clone().appendTo($uploaderSelect);
				vfc.metaKeys.sort();
				vfc.allUploaders.sort();
				$.each(vfc.metaKeys, function (k, val) {
					$('<option>', {
						text: val
					}).appendTo($metaSelect);
				});

				var isRtl = $uploaderSelect.css('direction') === 'rtl',
					bidiMark = isRtl ? '\u200f' : '\u200e'; // rlm : lrm

				$.each(vfc.allUploaders, function (k, val) {
					$('<option>', {
						value: val,
						text: val + ' ' + bidiMark + '[' + vfc.uploadsByUser[val] + ']'
					}).appendTo($uploaderSelect);
				});
			},
			autoOpen: false
		});
		this.$dialogsToClose.push(this.mdCuteSelectDlg);

		$('.numbersOnly').keyup(function () {
			this.value = this.value.replace(/[^0-9]/g, '');
		});
		$('.dateOnly').keyup(function () {
			this.value = this.value.replace(/[^0-9\-: ]/g, '');
		});
		// End of cute-selection dialog

		var $queryMore = $('<div>', {
			id: 'mdQueryMore',
			align: 'center'
		});
		vfc.$mdQueryMoreBtn = $('<button>', {
			id: 'mdQueryMoreBtn',
			text: i18n.mdMore
		})
			.button({
				disabled: true,
				icons: {
					primary: 'ui-icon-arrowthick-1-s',
					secondary: 'ui-icon-arrowthick-1-s'
				}
			})
			.click(function (/* event*/) {
				vfc.mdQueryMore();
			})
			.appendTo($queryMore);

		$AjaxMdContainer.append($queryMore).append('<div style="height:100px">&nbsp;</div>');
		/* End of UI */

		$doc.triggerHandler('vFC', ['initial uploads dialog', vfc, vfc.dlg]);
		// Temporary fix
		$('.vFCConfigRequired').removeClass('ui-state-disabled');

		vfc.iCurrentIId = -1; // Last queried file to continue obtaining detail-info
		vfc.secureCall('mdSendNextQueries');
	},

/**
**  Send the next batch of queries. Called by mdGenIGallery (mammoth method above) and mdQueryMore (Query on demand)
**/
	mdSendNextQueries: function () {
		$doc.triggerHandler('vFC', ['querying detail-info', this]);
		this.pb.setTaskState('datails', 'md-doing');

		if (!this.mdPendingBatchQueries) {
			this.mdPendingBatchQueries++;
			var collectedFiles = [],
				collectedCount = 0,
				si = vfc.startInput;

			var sendReq = function (files) {
				if (!files.length)
					return;

				var query = {
					action: 'query',
					prop: 'imageinfo|info|revisions|categories',
					rvprop: 'timestamp',
					inprop: 'talkid',
					iiprop: 'url|size|metadata',
					titles: files.join('|'),
					clprop: 'hidden',
					cllimit: 500,
					redirects: 1
				};
				if (si.loadWikitext)
					query.rvprop += '|content';

				if (!si.modeUser) {
					query.iiprop += '|user|sha1|comment';
					query.iilimit = 500;
				}

				if (si.loadThumbs)
					query.iiurlwidth = query.iiurlheight = 120;

				$doc.triggerHandler('vFC', ['detail-info', vfc, query]);

				vfc.mdUploadCt++;
				vfc.failQueriedAgain = '';
				vfc.queryAPI(query, 'mdQueriedFile', 'failQueriedFile'); // Dont use task queue because this is a loop
			};

			$.each(this.iUploads, function (upload, upli) {

				if ((vfc.iCurrentIId + 1) > upli.iId) return;
				collectedFiles.push(upload);
				collectedCount++;
				if (collectedCount > 9) { // only 10 at once
					collectedCount = 0;
					sendReq(collectedFiles);
					collectedFiles = [];
				}
				vfc.iCurrentIId = upli.iId;
				if ((vfc.mdUploadCt * 10 + collectedCount) >= vfc.mdSettings.loadBatchSize)
					return false;

			});
			sendReq(collectedFiles);
		}

		if (!this.mdUploadCt)
			this.nextTask();
		this.$mdQueryMoreBtn.button('option', 'disabled', true);
	},
	/* Helper function: Creating a gallery box for gallery view */
	mdCreateGalleryBox: function (img, txt, height, style, imgTitle, imgH, addGU) {
		var $ih = $('<div>', {
				style: 'margin:' + (imgH ? (Math.round((height - imgH) / 2) + 'px auto;') : '15px auto;')
			}),
			$th = $('<div>', {
				'class': 'gallerytext',
				'tipsy-title': imgTitle
			}),
			$gbProgress = $('<div>', {
				'class': 'jProgress'
			}),
			$gbGU = $('<div>', {
				'class': 'jGU',
				title: 'GlobalUsage'
			}),
			$thumb = $('<div>', {
				style: 'width: 150px;',
				'class': 'thumb jImage'
			}).css('height', height).append($ih, $gbProgress, $gbGU),
			$galleryBox = $('<li>', {
				'class': 'gallerybox rgallerybox'
			}).append($('<div>', {
				style: 'width: 155px'
			}).append($thumb).append($th));
		if (typeof style === 'string')
			$th.attr('style', style);

		$.each(img, function (id, imgi) {
			$ih.append(imgi);
		});
		$.each(txt, function (id, txti) {
			$th.append(txti);
		});
		$galleryBox.$thumb = $thumb;
		if (addGU) {
			$gbGU.badge('?');
			$galleryBox.$gbGU = $gbGU;
		}
		return $galleryBox;
	},
	fillGlobalUsage: function () {
		mw.loader.using('ext.gadget.GlobalUsage', function () {
			vfc.secureCall('_fillGlobalUsage');
		});
	},
	_fillGlobalUsage: function () {
		var gu = window.mw.libs.GlobalUsage(5, 15, 3, true);
		gu.tipsyGravity = 'se';
		gu.query(vfc.gbu).done(function () {
			$.each(vfc.gbu, function (i, $el) {
				$el.off('click');
			});
			vfc.gbu = {};
		});
	},
	mdLargerGallery: function () {
		if ( !mw.libs.largerGallery) {
			mw.loader.using('ext.gadget.LargerGallery', function () {
				vfc.secureCall('_mdLargerGallery');
			});
		} else
			vfc.secureCall('_mdLargerGallery');
	},
	_mdLargerGallery: function () {
		if (!$('#largerGallery2')[0])
			mw.libs.largerGallery.init('AjaxDeletedUploads', vfc.$ctrs.ajaxMdUlContainer);
		vfc.nextTask();
	},

/** Helper function: Creating a link to the log of a deleted item **/
	mdCreateDelUploadItem: function (img) {
		return $('<li>').append($('<a>', {
			href: mw.config.get('wgServer') + mw.config.get('wgScript') + '?title=Special:Log&page=' + img,
			target: '_blank'
		}).append(mw.html.escape(img)))
			.append($('<a>', {
				href: mw.config.get('wgServer') + mw.config.get('wgArticlePath').replace('$1', 'Special:Undelete/' + img),
				target: '_blank'
			}).append(' (undel)'));
	},
/** Called by Tipsy on hovering. Get a list of cats for a specific file.
@param {string} revision-identifier
@param {object} reference to JSONListUploads
@param {boolean} render as flickr-like tags?
**/
	mdGetCatTable: function (rv, o, asTags) {
		var cats = o.iUploads[rv].categories,
			$catWrap = $('<div>'),
			$catsList = $('<ul>');

		if (asTags)
			$catsList.addClass('j-cat-wrap');
		else
			$catWrap.html('<b><i>Categories:</i></b><br><br>');

		$catWrap.append($catsList);

		$.each(cats, function (id, tCat) {
			if (undefined === cats[id].hidden) {
				$catsList.append(
					$('<li>', {
						'class': (asTags ? ' j-cat-label' : '')
					}).append($('<a>', {
						'class': (asTags ? ' j-cat-label' : ''),
						href: mw.util.getUrl(tCat.title),
						target: '_blank'
					}).text(tCat.title.replace('Category:', '')), ' '));
			}
		});
		return $catWrap;
	},
	/* Helper function: Called by QueryIICB. Get model info from Image-Metadata */
	mdGetCamModel: function (uItem) {
		if (!uItem || !uItem.metadata)
			return '';

		var model = '';
		var mdata = uItem.metadata;
		if (typeof mdata !== 'object')
			return '';
		$.each(mdata, function (id, mdatai) {
			if (mdatai.name === 'Model') {
				model = $.trim(mdatai.value) + ' ';
				return false;
			}
		});
		return model;
	},
	/* Called by Tipsy on hovering. Get a list of cats for a specific file */
	mdGetMetaTable: function (rv, o) {
		var val,
			mdata = o.iUploads[rv].metadata,
			fRestr = (mdata && mdata.length > 6),
			stMdat = '<b><i>File-Metadata:</i></b><br><br>',
			restrArr = ['ImageDescription', 'Make', 'Model', 'Copyright', 'DateTime', 'Artist', 'Title', 'Author', 'Creator', 'CreationDate'];

		$.each(mdata, function (id, mdatai) {
			if (typeof mdatai.value !== 'string')
				return;
			try {
				val = mw.html.escape(mdatai.value).slice(0, 200);
			} catch (ex) {
				val = mdatai.value;
			}
			stMdat += ((fRestr && ($.inArray(mdatai.name, restrArr) === -1)) ? '' : '<i>' + mdatai.name + '</i><br>' + val + '<br><br>');
		});
		return stMdat;
	},
	/* Helper function: Extract categories from the list of those. */
	mdExtractTags: function (mdCats) {
		var thiscat,
			mdTags = '',
			_this = this,
			skip = false;

		if (typeof mdCats !== 'object')
			return mdTags;
		mdTags += '<i>';

		// First the licenses (hidden):
		$.each(mdCats, function (i, cati) {
			if (typeof cati.hidden === 'string') { // it is an empty string if true
				thiscat = cati.title;
				skip = false;
				$.each(_this.mdLicenseRecognization, function (id, recogi) {
					if (recogi[0].test(thiscat)) {
						if (recogi[1] !== '')
							mdTags += '<abbr title="' + thiscat.replace('Category:', '') + '">' + recogi[1] + '</abbr> ';

						skip = true;
						return false;
					}
				});
				if (skip)
					return;

				// This will be only executed if there was no match
				mdTags += '<abbr title="' + thiscat.replace('Category:', '') + '">U</abbr> ';
			}
		});

		mdTags += '</i> <span style="background-color:#FC9;">';

		// Then, deletion tags:
		$.each(mdCats, function (i, cati) {
			thiscat = cati.title;
			skip = false;
			$.each(_this.mdTagRecognization, function (id, thistag) {
				if (thistag[0].test(thiscat)) {
					if (thistag[1] !== '')
						mdTags += (thistag[2] ? '<span style="background-color:#' + thistag[2] + ';">' : '') + '<abbr title="' + thiscat.replace('Category:', '') + '">' + thistag[1] + '</abbr>' + (thistag[2] ? '</span>' : '');

					skip = true;
					return false;
				}
			});
			if (skip)
				return;
		});

		mdTags += '</span>';

		return mdTags;
	},
	/* Format a number > 1 (1000 --> 1 000) */
	mdFormattNumber: function (iNr) {
		iNr = String(iNr);
		var rx = /(\d+)(\d{3})/;
		while (rx.test(iNr))
			iNr = iNr.replace(rx, '$1<span style="font-size:40%;font-weight:100">&nbsp;</span>$2');

		return iNr;
	},

/**
*  API-Call-back method to eval the queried file and add a thumb
*/
	mdQueriedFile: function (result) {
		vfc.mdUploadCt--;
		// If some failed, try again
		if (vfc.failQueriedAgain) {
			vfc.queryAPI( vfc.failQueriedAgain, 'mdQueriedFile' );
			vfc.failQueriedAgain = '';
		}

		$doc.triggerHandler('vFC', ['got detail-info', vfc, result]);

		if (({
			err: 1,
			done: 1
		})[vfc.internalState])
			return; // don't try to add something if there was an error.
		result = result.query;
		var pages = result.pages,
			redirects = result.redirects,
			loadThumb = vfc.startInput.loadThumbs;

		vfc.mdBusy = true;

		$.each(pages, function (id, pg) {
			var dirtyTitle = pg.title,
				uItem = vfc.iUploads[dirtyTitle],
				i,
				ii,
				seenHash = {};
			if (!uItem && redirects) { // Resolve redirect
				for (var r = 0; r < redirects.length; r++) {
					var redirect = redirects[r];
					if (redirect && dirtyTitle === redirect.to) {
						uItem = vfc.iUploads[dirtyTitle] = vfc.iUploads[redirect.from];
						delete vfc.iUploads[redirect.from];
						redirects.splice(r, 1);
						break;
					}
				}
				// uItem = uItem || {}; // should never happen
			}

			vfc.$ctrs.ajaxMdNotReady.text(vfc._msg('query-progress', vfc.mdUploadCt, vfc.iiUploads, dirtyTitle));

			if (!pg.revisions) { // The file has been deleted
				try {
					vfc.$ctrs.ajaxDeletedUploads.append(vfc.secureCall('mdCreateDelUploadItem', dirtyTitle));
				} catch (e) {}
				return;
			}
			// No thumb-image, create dummy
			if ( loadThumb && (!pg.imageinfo || $.isEmptyObject( pg.imageinfo[0] ) || !pg.imageinfo[0].thumbwidth )) {
				var fileext = dirtyTitle.slice(-4).toLowerCase();
				fileext = $.inArray(fileext, ['djvu', '.mov', 'mpga', '.svg', '.ogg', '.pdf', '.psd', '.xcf']) !== -1 ? '-' + fileext.replace(/^\./, '') : '';

				pg.imageinfo = [{
					thumburl: '/w/resources/assets/file-type-icons/fileicon' + fileext + '.png',
					size: 0,
					width: 0,
					height: 0,
					thumbwidth: 120,
					thumbheight: 120,
					descriptionurl: '/wiki/' + dirtyTitle
				}];
			}

			/*  Adding a thumbnail to the dialog  */
			vfc.mdActualResultCount++;

			if (!vfc.startInput.modeUser) {
				for (i = (pg.imageinfo || []).length - 1; i >= 0; i--) {
					ii = pg.imageinfo[i];
					if (ii.sha1 && !seenHash[ii.sha1]) {
						seenHash[ii.sha1] = true;
						// Admins can hide usernames. Make sure not pushing undefined.
						if (ii.user) {
							uItem.uploader.push(ii.user);
							uItem.comments.push(ii.comment || '');
							vfc.uploadsByUser[ii.user] = vfc.uploadsByUser[ii.user] || 0;
							vfc.uploadsByUser[ii.user]++;
							if ($.inArray(ii.user, vfc.allUploaders) === -1)
								vfc.allUploaders.push(ii.user);
						}
					}
				}
			}

			ii = (pg.imageinfo || [])[0] || {};
			ii.metadata = ii.metadata || []; // not all images have metadata
			pg.categories = pg.categories || []; // not all images have categories

			if (pg.revisions[0]) {
				uItem.content = pg.revisions[0]['*'] || ''; // save the content for OTRS usage, replace and find-the-real-uploader
				uItem.time = uItem.time || pg.revisions[0].timestamp;
				uItem.basetimestamp = pg.revisions[0].timestamp; // timestamp to detect edit conflicts
			} else {
				mw.log.warn('API response for ' + id + ' has empty revisions list (w.wiki/Njw)'); // TODO properly handle this case (there should be some rvcontinue in the response, follow that)
			}
			uItem.categories = pg.categories; // save cats for cute-selection
			uItem.metadata = ii.metadata; // save Exif and other for cute-selection
			uItem.size = (ii.size >> 10); // for cute-selection
			uItem.pageId = id;
			uItem.talkId = pg.talkid;

			// List all Meta-Keys
			if (ii.metadata.length) {
				$.each(ii.metadata, function (i, m) {
					var tp = typeof m.value;
					if (tp === 'string' || tp === 'number' || tp === 'boolean') {
						if ($.inArray(m.name, vfc.metaKeys) === -1)
							vfc.metaKeys.push(m.name);
					}

				});
			}

			var camModel = vfc.secureCall('mdGetCamModel', uItem),
				loglink = mw.config.get('wgServer') + mw.config.get('wgScript') + '?title=Special:Log&page=' + mw.util.wikiUrlencode(dirtyTitle);

			var $galleryBox = vfc.mdCreateGalleryBox(
				[$('<a>', {
					href: ii.descriptionurl,
					target: '_blank',
					'class': 'image'
				})
					.append(loadThumb ? $('<img>').attr({ // attr is needed to not convert to CSS
						src: ii.thumburl,
						width: ii.thumbwidth,
						height: ii.thumbheight,
						alt: dirtyTitle
					}) : '')],
				[('<i>' + (uItem.iId + 1) + ': </i>'),
					$('<input>', {
						type: 'checkbox',
						name: 'mdCheckDelete',
						value: dirtyTitle
					}),
					$('<span>', {
						'class': 'tipsycategory'
					}).text('c').tipsy({
						title: function () {
							$('.tipsy').remove();
							return vfc.secureCall('mdGetCatTable', $(this).parent().attr('tipsy-title'), vfc, false).html();
						},
						html: true,
						delayOut: 1000,
						gravity: $.fn.tipsy.autoNS
					}), ' ',
					(($.isArray(ii.metadata) && !!ii.metadata.length) ?
						$('<span>', {
							'class': 'tipsymetadata'
						}).text(camModel || 'm').tipsy({
							title: function () {
								$('.tipsy').remove();
								return vfc.secureCall('mdGetMetaTable', $(this).parent().attr('tipsy-title'), vfc);
							},
							html: true,
							gravity: $.fn.tipsy.autoNS
						}) :
						''),
					(uItem.talkId ?
						$('<a>', {
							'class': 'md-talklink',
							href: mw.util.getUrl(dirtyTitle.replace(/^File/, 'File_talk')),
							target: '_blank',
							title: i18n.hasTalk
						}).text('t') :
						''),
					$('<a>', {
						href: loglink,
						target: '_blank',
						text: 'log',
						'class': 'md-loglink'
					}), '<br>',
					(vfc.secureCall('mdExtractTags', pg.categories)), ' ',
					$('<a>', {
						href: ii.descriptionurl,
						target: '_blank',
						'class': 'image jFileTitle'
					})
						.text(dirtyTitle),
					$('<div>', {
						'class': 'jFileTime'
					}).text(uItem.time.replace(/[T|Z]/g, ' ')),
					$('<div>', {
						'class': 'jFileSize'
					}).html(
						vfc.secureCall('mdFormattNumber', uItem.size) +
			'&nbsp;' + i18n.kibibyte + '&nbsp; ' +
			(vfc.secureCall('mdFormattNumber', ii.width) + '&nbsp;×&nbsp;' + vfc.secureCall('mdFormattNumber', ii.height)) +
			'px')
				],
				150,
				'',
				dirtyTitle,
				ii.thumbheight,
				true);
			vfc.gbs[dirtyTitle] = $galleryBox;
			vfc.gbu[dirtyTitle] = $galleryBox.$gbGU.click(vfc.fillGlobalUsage);
		});
		vfc.mdBusy = false;
		if (!vfc.mdUploadCt)
			vfc.nextTask();
	},

/**
**  Called when a batch of files has been queried
**/
	mdQueryFileDone: function () {
		// Append all queried files to the screen-container
		var largerGallery = $('#largerGallery2')[0] ? mw.libs.largerGallery.run : function () {};
		$.each(vfc.iUploads, function (id, uItem) {
			if (!uItem.gbInUse) {
				var $galleryBox = vfc.gbs[id];
				if ($galleryBox) {
					largerGallery($galleryBox, 1);
					uItem.gbInUse = true;
					vfc.$ctrs.ajaxMdUlContainer.append($galleryBox);
					vfc.mdThumbsOnDlg++;
				}
			}
		});

		vfc.mdBusy = false;
		vfc.mdPendingBatchQueries--;
		vfc.$ctrs.ajaxMdNotReady.hide();
		vfc.pb.setCurrentTaskDone();

		$doc.triggerHandler('vFC', ['detail-info processed', vfc]);

		if ((vfc.iiUploads > (vfc.iCurrentIId + 1)) || !vfc.mdNoMoreFiles) {
			vfc.$mdQueryMoreBtn.button('option', 'disabled', false);

			var __onScroll = function () {
				if (vfc.$mdQueryMoreBtn.inView().length > 0)
					vfc.secureCall('mdQueryMore');

			};
			vfc.dlg.off('scroll', __onScroll)
				.on('scroll', __onScroll);
		}
		if (!vfc.mdNoMoreFiles) { // silently run the next upload-list-query in background
			vfc.secureCall('mdCreateList');
		}

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

		var si = vfc.startInput;

		if (!vfc.mdActualResultCount) { // If there are no thumbs on the dialog, continue
			if (vfc.internalState !== 'revert') { // Don't shedule further tasks if called from revert part
				if (vfc.internalState === 'md') {
					if (si.modeUser && !vfc.mdNoMoreFiles) { // When no files in cat/page-mode are found, it's clear there are no more files
						return;
					} else {
						vfc.infoTextToEndDlg.push(i18n.mdNoResult.replace('%TARGET%', vfc.queryParams.target));
					}
				}
				if (si.modeUser) {
					$.each(vfc.mdCommandsPostExec[vfc.mdSettings.defaultAction], function (id, task) {
						vfc.addTask(task);
						if (task in i18n.task)
							vfc.pb.addTask(task);
					});
				}
				vfc.addTask('mdExecuteReady');
			}
			vfc.nextTask();
		}
	},

/**
**  Called by scroll-event when the button "more" is scrolled to view (mdQueryFileDone binds the evt) or onClick on the button
**/
	mdQueryMore: function () {
		if (!this.mdPendingBatchQueries && !this.mdNumberOfExecs) {
			this.mdUploadCt = 0;
			this.addTask('mdQueryFileDone');
			this.secureCall('mdSendNextQueries');
		}
	},

/**
** II. REVERT PART
**
**/
	mdRvGenOGallery: function () {
		this.pb.setCurrentTaskDone();
		if ('mdRvGenOGallery' in this.pb.t)
			this.pb.setTaskState('mdRvGenOGallery', 'md-doing');

		this.pb.setHelp('We are working to re-add the possibility to rollback overwritten files to combat vandalism');

		// /////////////////////////////////////////
		//   SKIP THIS - TEMPORARY NOT WORKING   //
		// /////////////////////////////////////////
		this.nextTask();
		return;
	// /////////////////////////////////////////
	},

	mdExecuteReady: function () {
		this.internalState = 'ready';
		this.pb.setCurrentTaskDone();

		var i18nED = i18n.endDlg,
			$dlgNode = $('<div>', {
				id: 'mdWhereToGoDlg',
				title: i18nED.heading,
				text: i18nED.intro
			})
				.append($('<br>'), $('<a>', {
					href: '#',
					text: i18nED.saveInProfile
				}).button().click(function (e) {
					e.preventDefault();
					vfc.mdProfile();
				}))
				.append($('<br>'), mw.html.escape(i18nED.aboutLastExec));
		var $ulNode = $('<ul>');

		var addLink = function (href, text, target) {
			$('<li>').append($('<a>', {
				style: 'font-size:2em; line-height:1.8em',
				href: mw.util.getUrl(href),
				target: target,
				text: text
			})).appendTo($ulNode);
		};

		if (this.api.wasError) {
			$dlgNode.append(
				$.createNotifyArea(i18nED.errorDuringTask, 'ui-icon-alert', 'ui-state-error'));
		}
		$.each(this.infoTextToEndDlg, function (i, text) {
			$dlgNode.append($.createNotifyArea(text, 'ui-icon-info', 'ui-state-highlight'));
		});
		if (this.mdTaskToPerform === 'aDelete')
			addLink('Special:Log/delete/' + this.username.replace(/ /g, '_'), i18nED.checkDeletions);
		else
			addLink('Special:MyContributions', i18nED.checkContribs, 'contribswindow');

		if (this.mdInsertedTag) {
			// var encTitle;
			if (this.mdTaskToPerform === 'del') {
				// allow navigating to the new created deletion request
				addLink(this.requestPage, i18nED.goToRequestPage);
			}
			if (this.startInput.modeUser && ($.inArray(this.mdTaskToPerform, ['c_replace', 'OTRS', 'other']) === -1)) {
				addLink(this.mdUserTalkPrefix + vfc.queryParams.target, i18nED.goUserTalk);
				addLink(this.mdContribPrefix + vfc.queryParams.target, i18nED.goUserContribs);
				// Revert files overwritten by this user ...
			}
			addLink(mw.config.get('wgPageName'), i18nED.reload, '_self');
		}
		$dlgNode.append($ulNode).dialog({
			show: {
				effect: 'highlight',
				duration: 1800
			},
			width: Math.min(450, $win.width())
		});
		document.title = i18nED.title;
		vfc.$dialogsToClose.push($dlgNode);
	}
});

mw.loader.using([// TODO
	'jquery.ui', // deprecated
	'jquery.ui', // deprecated
	'jquery.tipsy', // deprecated
	'jquery.lengthLimit',
	'mediawiki.String',
	'mediawiki.storage', // former jquery.jStorage
	'mediawiki.util',
	'mediawiki.page.gallery.styles',
	'ext.gadget.libAPI',
	'ext.gadget.libJQuery',
	'ext.gadget.jquery.badge',
	'ext.gadget.jquery.blockUI',
	'ext.gadget.libWikiDOM'
], function () {
	$doc.triggerHandler('scriptLoaded', ['VisualFileChange', 'displayComponents']);
});

}(jQuery, mediaWiki));
// </nowiki>