User:Ahecht/sandbox/Scripts/potd-helper.js

Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//jshint maxerr:512
// <nowiki>
mw.loader.using( [ 'mediawiki.util', 'oojs-ui' ], function () {
	var potdScriptLongTitle = "Picture of the Day Helper (sandbox)",
		potdScriptShortTitle = "POTDHelper (sandbox)",
		potdScriptLocation = "User:Ahecht/sandbox/Scripts/potd-helper.js",
		potdFPCategory = 'Featured pictures';
		
	const urlParams = new URLSearchParams(window.location.search);
	const debug = urlParams.get('debug') || false;
	
	function POTDHelper() {
		var config = {
			fpCategory: potdFPCategory,
			fpcPrefix: 'Wikipedia:Featured picture candidates/',
			potdPrefix: 'Template:POTD/',
			editSummaryPattern: '[[File:%0]] scheduled for [[WP:POTD|POTD]] on [[Template:POTD/%1|%1]]',
			editSummarySuffix: ' ([[' + potdScriptLocation + '|' + potdScriptShortTitle + ']])',
			panoWidth: 500,
			defaultWidth: 350,
			landscapeWidth: 300,
			squareWidth: 275,
			portraitWidth: 250,
			tallWidth: 225,
			maxWidth: 650,
			suggestHeight: 350,
			maxHeight: 450,
			aspectRatio: 0,
			submitMessageHeader: '<strong>Submitting Picture of the Day:</strong><ul><li>',
			submitMessageFooter: '</li></ul>',
			submitMessages: [],
			query: {
				action: 'query',
				formatversion: '2'
			},
			revQuery: {
				prop: 'revisions',
				rvprop: 'content',
				rvslots: 'main',
				rvlimit: '1',
				rvsection: '0',
			},
			imageQuery: {
				prop: 'imageinfo|revisions|categories|fileusage',
				iiprop: 'user|dimensions',
				iilimit: 'max',
				cllimit: 'max',
				funamespace: '0',
				fulimit: 'max'
			},
			nomQuery: {
				rvprop: 'user',
				rvdir: 'newer'
			},
			titleQuery: {
				prop: 'revisions|pageimages|images',
				piprop: 'name',
				imlimit: 'max'
			},
			edit: {
				action: 'edit',
				watchlist: 'preferences',
				recreate: 1
			}
		};
		
		function wikiLink(message) {
			message = message.replace(/\[\[(.*?)\]\]/g, function(s,v) {
				v = v.split("|");
				var url = mw.config.get("wgServer") + mw.config.get("wgArticlePath").replace("$1", encodeURI(v[0]));
				return '<a href="'+ url + '">' + (v[1] || v[0]) + "</a>";
			} );
			return message;
		}
		
		function showImageMessage(label, type='error') {
			imageMessage.setType(type);
			imageMessage.setLabel(label);
			imageMessage.toggle(true);
			imageMessage.scrollElementIntoView();
		}

		function showImageError(error) {
			error = "The script " + error + " Check that the image name [[File:" + imageInput.value + "]] is correct.";
			showImageMessage(new OO.ui.HtmlSnippet(wikiLink(error)));
		}
		
		function showTitleError(error) {
			error = "The script " + error + " Check that the article name [[" + titleInput.value + "]] is correct.";
			titleMessage.setLabel(new OO.ui.HtmlSnippet(wikiLink(error)));
			titleMessage.toggle(true);
			titleMessage.scrollElementIntoView();
		}
		
		function showDateError(error) {
			dateMessage.setLabel(new OO.ui.HtmlSnippet(wikiLink(error)));
			dateMessage.toggle(true);
			dateMessage.scrollElementIntoView();
		}
		
		function showSubmitMessage(message, type) {
			type = type ? type : 'notice';
			config.submitMessages.push(wikiLink(message));
			var label = new OO.ui.HtmlSnippet( config.submitMessageHeader + config.submitMessages.join("</li><li>") + config.submitMessageFooter );
			submitMessage.setLabel(label);
			var types = ['notice', 'success', 'warning', 'error'];
			if (types.indexOf(type) > types.indexOf(submitMessage.type)) {
				submitMessage.setType(type);
			}
			submitMessage.toggle(true);
			submitMessage.scrollElementIntoView();
		}
		
		function fetchImageData(doneFunction = function() {return;}, errorFunction = function() {return;}){
			var imageName = 'File:' + imageInput.value;
			if (imageName != "File:" && typeof config.imageData === 'undefined') {
				var fn = "fetchImageData("+doneFunction+","+errorFunction;
				console.log('Fetching image data...');
				new mw.Api().get(
					Object.assign( {titles: imageName}, config.query, config.revQuery, config.imageQuery )
				).fail(function(code, error) {
					console.warn(fn+") encountered an API error retrieving info for "+imageName+":");
					console.warn(error);
					showImageError("got an API error '" + code + "' when querying image [[" + imageName + "]]: " + error.error.info);
					errorFunction();
				}).done( function(data) {
					if (data && data.query && data.query.pages && data.query.pages[0]) {
						if (data.query.pages[0].missing) {
							if (data.query.pages[0].known) {
								errorMsg = "was unable find a local page on Wikipedia for this image (although one exists on Commons), which means it is likely not a Wikipedia Featured Picture.";
							} else {
								errorMsg = "was unable to find the image.";
							}
							showImageError(errorMsg);
							console.warn(fn+") did not find a page for "+imageName+".");
							console.log(data);
							errorFunction();
						} else {
							console.log('Caching image data...');
							config.imageData = data.query.pages[0];
							doneFunction();
						}
					} else {
						console.warn(fn+") encountered an error parsing the API respose for "+imageName+":");
						console.log(data);
						showImageError("was unable to parse the API response when trying get info on the image.");
						errorFunction();
					}
				} );
			} else {
				console.log('Using cached image data...');
				doneFunction();
			}
		}
		
		function fillImage(titleData, imageInput) {
			if (titleData.images && titleData.images.length) {
				var options = [];
				titleData.images.forEach( function(item) {return options.push({data: item.title.replace(/^File:/i, '')});} );
				if (options.length) {
					imageInput.setOptions(options);
				} else {
					console.log("No images found on " + titleData.title);
					console.log(imageData);
				}
			} else {
				console.warn("Images not found in API response:");
				console.log(imageData);
			}
			if (titleData.pageimage) {
				if (!imageInput.value.length) {
					imageInput.setValue(titleData.pageimage);
					checkSizeInput();
				} else {
					console.log("Page image " + titleData.pageimage + " found, but imageInput is not empty.");
				}
			} else {
				console.warn("Page Image not found for article " + titleData.title);
				console.log(titleData);
			}
		}
		
		function fillUsage(imageData, titleInput) {
			if (imageData.fileusage && imageData.fileusage.length) {
				var options = [];
				imageData.fileusage.forEach( function(item) {return options.push({data: item.title});} );
				if (options.length) {
					titleInput.setOptions(options);
				} else {
					console.log("No usage of " + imageData.title + " found.");
					console.log(imageData);
				}
			} else {
				console.warn("Fileusage not found in API response:");
				console.log(imageData);
			}
		}
			
		function getImageInfo() {
			function getNominator(nom) {
				if (nom && nom.query && nom.query.pages && nom.query.pages[0] && nom.query.pages[0].revisions) {
					if (nom.query.pages[0].revisions[0] && nom.query.pages[0].revisions[0].user && nom.query.pages[0].revisions[0].user.length) {
						nominatorInput.setValue("User:" + nom.query.pages[0].revisions[0].user);
						console.log("Nominated by " + nominatorInput.value);
						if (nominatorInput.value == uploaderInput.value) {
							nominatorInput.setValue();
						}
					} else {
						showImageError("was unable to parse API data on nomination page [[" + nomination + "]].");
					}
				} else {
					showImageError("was unable to find the nomination page [[" + nomination + "]].");
				}
			}
			
			function getNomination(imageData) {
				if (imageData.revisions && imageData.revisions.length && imageData.categories && imageData.categories.length) {
					var cats=[];
					imageData.categories.forEach( function(cat) {return cats.push(cat.title);} );
					if (cats.includes('Category:' + config.fpCategory)) {
						if (imageData.revisions[0] && imageData.revisions[0].slots && imageData.revisions[0].slots.main && imageData.revisions[0].slots.main.content) {
								var content = imageData.revisions[0].slots.main.content,
									nominations = [];
								try {
									nominations = XRegExp.matchRecursive(content, "\\{\\{featured\\s*picture\\s*\\|", "\\}}", 'i');
								} catch(error) {
									console.warn(error + " when parsing local file description.");
									console.log(content);
									showImageError("was unable to parse the {{Featured picture}} template on [[" + imageInput.value + "]]: " + error + ".");
								}
								if (nominations.length && nominations[0].length) {
									var nomination = config.fpcPrefix + nominations[0];
									console.log("Nominated at " + nomination);
									new mw.Api().get(
											Object.assign( {titles: nomination}, config.query, config.revQuery, config.nomQuery )
										).fail(function(code, error) {
											console.warn("API error retrieving nomination:");
											console.warn(error);
											showImageError("got an API error '" + code + "' when retrieving the nomination page [[" + nomination + "]]: " + error.error.info);
										}).done( getNominator );
								} else {
									showImageError("was unable to find the featured picture template in the local description. Are you sure this is a featured image on the English Wikipedia?");
								}
						} else {
							showImageError("was unable to parse API data on the image.");
						}
					} else {
						showImageError("was unable to find [[Category:" + config.fpCategory + "]] on that image. Are you sure this is a featured image on the English Wikipedia?");
					}
				} else {
					showImageError("was unable to find a local file description. Are you sure this is a featured image on the English Wikipedia?");
				}
			}
			
			function getUploader(imageData) {
				if (imageData.imageinfo && imageData.imageinfo.length) {
					info = imageData.imageinfo;
					info = info[info.length - 1];
					if (info.user) {
						uploaderInput.setValue("User:" + info.user);
						console.log("Uploaded by " + uploaderInput.value);
						getNomination(imageData);
					} else {
						console.warn("Error parsing API respose for image uploader:");
						console.log(data);
						showImageError("was unable to parse the API response when trying to determine who uploaded the image.");
					}
				} else {
					console.warn("Error parsing API respose for image uploader:");
					console.log(data);
					showImageError("was unable to parse the API response when trying to determine who uploaded the image.");
				}
			}
			
			imageMessage.toggle(false);
			fetchImageData(function() {
				getUploader(config.imageData);
				fillUsage(config.imageData, titleInput);
				checkSizeInput();
			} );
		}
		
		function getCaption() {
			function extractCaption(titleData) {
				var extract = "";
				
				if (titleData.revisions && titleData.revisions[0] && titleData.revisions[0].slots &&
					titleData.revisions[0].slots.main && titleData.revisions[0].slots.main.content) {
					var content = titleData.revisions[0].slots.main.content,
						xValues = [];
					
					content = content.replace(/(\[\[File:.*\]\][\s\n]*)*/ig, '');
					
					try {
						xValues = XRegExp.matchRecursive(content, "\\{{", "\\}}[\s\n]*", 'gi', {valueNames: ['b', 'l', 'm', 'r']});
					} catch(error) {
						console.warn(error + " when parsing linked article.");
						console.log(content);
						showTitleError("encountered an error when parsing the article's lead section: " + error + ".");
					}
					if (xValues.length) {
						var startSub = 0;

						for(var i = 0; i < xValues.length; i++){
    						var x = xValues[i];
							if (x.name && x.name=='b' && (typeof x.start === 'number')) {
								startSub=x.start;
								break;
							}
						}

						extract = content.substr(startSub).split('\n\n')[0];
						
						if (extract && extract.length) {
							extract = extract.replace(/<ref.*?>(.*?)<\/ref>/g, "");
							extract = extract.replace(/<ref.*?\/>/g, "");
							re = new RegExp(/'''('')?(.*?)\1''' /);
							titleMatch=extract.match(re);
							if (titleMatch) {
								if (titleMatch[2]==titleInput.value) {
									captionInput.setValue(extract.replace(re, "'''$1[[$2]]$1''' "));
								} else {
									captionInput.setValue(extract.replace(re, "'''[["+titleInput.value+"|$1$2$1]]''' "));
								}
							} else {
								captionInput.setValue("'''[["+titleInput.value+"]]:''' "+extract);
							}
						} else {
							showTitleError("was unable to find non-template content in lead section.");
							extract = "";
						}
					} else {
						showTitleError("was unable to parse the article's lead section.");
					}
				} else if (titleData.missing) {
					console.warn("Could not find revisions for Title:");
					console.log(titleData);
					showTitleError("was unable to find an article with that title.");
				} else {
					console.warn("Error parsing API respose when fetching lead section:");
					console.log(titleData);
					showTitleError("encountered an error parsing the API response when fetching the article's lead section.");
				}
			}
			
			titleMessage.toggle(false);
			if (typeof config.titleData === 'undefined') {
				console.log('Fetching title data...');
				new mw.Api().get(
					Object.assign( {titles: titleInput.value}, config.query, config.revQuery, config.titleQuery )
				).fail(function(code, error) {
					console.warn("API error retrieving linked article:");
					console.warn(error);
					showTitleError("encountered an API error '" + code + "' when retrieving the article: " + error.error.info);
				} ).done( function(data) {
					if (data && data.query && data.query.pages && data.query.pages[0]) {
						console.log('Caching title data...');
						config.titleData = data.query.pages[0];
						extractCaption(config.titleData);
						fillImage(config.titleData, imageInput);
					} else {
						console.warn("Error parsing API respose when fetching lead section:");
						console.log(data);
						showTitleError("encountered an error parsing the API response when fetching the article's lead section.");
					}
				} );
			} else {
				console.log('Using cached title data...');
				extractCaption(config.titleData);
				fillImage(config.titleData, imageInput);
			}
		}
		
		function finishSubmit(done) {
			done = done ? done : false;
			if (done) {showSubmitMessage("Done!", 'success');}
			while (closeButton.isPending()) {closeButton.popPending();}
			closeButton.toggleFramed(true).setDisabled(false);
			while (submitButton.isPending()) {submitButton.popPending();}
			submitButton.toggleFramed(true);
			checkButtons();
		}
		
		function writeUserTalk(user) {
			var templateText = "\n{{subst:" + "NotifyPOTD|1=" + 
				"|2=File:" + imageInput.value +
				"|3=" + dateInput.value +
				(commentsInput.value.length ? ("|4=" + commentsInput.value) : ""),
				userTalk = "";
			
			if (user == 'uploader') {
				userTalk = uploaderInput.value;
				templateText += "|action=uploaded}}";
			} else if ((user == 'nominator') && (nominatorInput.value != uploaderInput.value)) {
				userTalk = nominatorInput.value;
				templateText += "|action=nominated}}";
			}
			
			if (userTalk.search(/^User:./i) == 0) { //Value starts with "User:"
				console.log(templateText);
				userTalk = userTalk.replace(/^User:/i, 'User talk:');
				var params = Object.assign( {
					title: userTalk,
					appendtext: templateText,
					summary: config.editSummary,
					redirect: 1,
					nocreate: 1
				}, config.edit );
				
				if (debug) {
					console.log(params);
					showSubmitMessage("Simulated message to [[" + userTalk + "]].");
					if (user == 'uploader') {
						writeUserTalk('nominator');
					} else {
						finishSubmit(true);
					}
				} else {
					new mw.Api().postWithEditToken(params).fail( function(code, error) {
						console.error("API error when creating article talk page message: ");
						console.error(error);
						showSubmitMessage("ERROR '" + code + "' when creating message on [[" + userTalk + "]]: " + error.error.info, 'warning');
						if (user == 'uploader') {
							writeUserTalk('nominator');
						} else {
							finishSubmit(true);
						}
					} ).done( function() {
						showSubmitMessage("Added message to [[" + userTalk + "]].");
						if (user == 'uploader') {
							writeUserTalk('nominator');
						} else {
							finishSubmit(true);
						}
					} );
				}
			} else if (user == 'uploader') {
				writeUserTalk('nominator');
			} else {
				finishSubmit(true);
			}
		}
		
		function writeArticleTalk(){
			var templateText = "\n{{subst:" + "UpcomingPOTD" + 
				"|1=File:" + imageInput.value +
				"|2=" + dateInput.value +
				(commentsInput.value.length ? ("|3=" + commentsInput.value) : "") +
				"}}",
				titleTalk = 'Talk:' + titleInput.value;
			
			console.log(templateText);
			
			var params = Object.assign( {
				title: titleTalk,
				appendtext: templateText,
				summary: config.editSummary,
				redirect: 1
			}, config.edit );
			
			if (debug) {
				console.log(params);
				showSubmitMessage("Simulated message to [[" + titleTalk + "]].");
				writeUserTalk('uploader');
			} else {
				new mw.Api().postWithEditToken(params).fail( function(code, error) {
					console.error("API error when creating article talk page message: ");
					console.error(error);
					showSubmitMessage("ERROR '" + code + "' when creating message on [[" + titleTalk + "]]: " + error.error.info, 'warning');
					writeUserTalk('uploader');
				} ).done( function() {
					showSubmitMessage("Added message to [[" + titleTalk + "]].");
					writeUserTalk('uploader');
				} );
			}
		}
		
		function checkSize() {
			config.wide = false;
			config.tall = false;
			if (config.imageData && config.imageData.imageinfo && config.imageData.imageinfo[0] && config.imageData.imageinfo[0].width > 0) {
				var imageWidth = config.imageData.imageinfo[0].width;
				var imageHeight = config.imageData.imageinfo[0].height || 0;
				config.aspectRatio = imageWidth / imageHeight;
				console.log("Original image dimensions: " + imageWidth + "×" + imageHeight + " (" + config.aspectRatio + ":1).");
				
				if (!sizeInput.value.length) {
					width =	config.aspectRatio > 2	? config.panoWidth
						: config.aspectRatio > 1.1 ? config.landscapeWidth
						: config.aspectRatio >= 0.9 ? config.squareWidth
						: config.aspectRatio >= 0.5 ? config.portraitWidth
						: config.aspectRatio > 0 ? config.tallWidth
						: config.defaultWidth;
				} else {
					width = sizeInput.value;
				}
				if (width > config.maxWidth) {
					console.log("Wide image detected.");
					config.wide = true;
				}
				
				var height = width * imageHeight / imageWidth;
				console.log("Template image dimensions: " + width + "×" + height);
				if (!config.wide && height > config.maxHeight) {
					console.log("Tall image detected.");
					if (!sizeInput.value.length) {
						width = Math.floor(config.suggestHeight*config.aspectRatio / 10) * 10;
						height = width * imageHeight / imageWidth;
						console.log("Changing default value to: " + width + "×" + height);
					} else {
						config.tall = true;
					}
				}
				if (!sizeInput.value.length) {
					sizeInput.setValue(width);
				}
			} else {
				console.warn("Error reading or parsing image data when trying to determine dimensions.");
				console.log(config.imageData);
			}
		}
		
		function writePOTDTemplate() {
			var templateName = config.potdPrefix + dateInput.value;
			if (!texttitleInput.value.length) {
				texttitleInput.setValue(titleInput.value);
			}
			if (!sizeInput.value.length) {
				sizeInput.setValue(config.defaultWidth);
			}
			checkSize();
			if (!captionInput.value.length) {
				captionInput.setValue("'''[[<!--article-->]]'''");
			}
			var templateText = "{{POTD {{{1|{{{style|default}}}}}}" +
				"\n|image=" + imageInput.value +
				"\n|size=" + sizeInput.value +
				( (config.wide && config.aspectRatio >= 1.5) ? "\n|wide=yes" : "" ) +
				( (config.tall && config.aspectRatio < 0.67) ? "\n|tall=yes" : "" ) +
				"\n|title=[[" + titleInput.value + "]]" +
				"\n|texttitle=" + texttitleInput.value +
				( alttextInput.value.length ? ("\n|alttext=" + alttextInput.value) : "" ) +
				"\n|caption=\n\n" + captionInput.value +
				"\n\n|credit=" + creditInput.value + ( secondaryCreditInput.value.length ? ("; " + secondaryCreditInput.value) : "" ) +
				"\n}}<noinclude>[[Category:Wikipedia Picture of the day {{#time:F Y|{{SUBPAGENAME}}}}]]" + 
				"\n\n== See also ==" + 
				"\n* [[Template:POTD{{#ifeq:{{BASEPAGENAME}}|POTD protected||&#32;protected}}/{{SUBPAGENAME}}]]</noinclude>";
			
			console.log(templateText);
			
			var params = Object.assign( {
				createonly: 1,
				title: templateName,
				text: templateText,
				summary: 'Creating a [[WP:POTD|POTD]] template for [[File:' + imageInput.value + ']]' + config.editSummarySuffix
			}, config.edit );
			
			if (debug) {
				console.log(params);
				showSubmitMessage("Simulated [[" + templateName + "]].");
				writeArticleTalk();
			} else {
				new mw.Api().postWithEditToken(params).fail( function(code, error) {
					console.error("API error when creating template: ");
					console.error(error);
					showSubmitMessage("ERROR '" + code + "' when creating template [[" + templateName + "]]: " + error.error.info, 'error');
					finishSubmit(true);
				} ).done( function() {
					showSubmitMessage("Created [[" + templateName + "]].");
					writeArticleTalk();
				} );
			}
		}
		
		function writeLocalDesc() {
			var params = Object.assign( {
				title: 'File:' + imageInput.value,
				appendtext: '\n{{Picture of the day|' + dateInput.value + '}}',
				summary: config.editSummary,
				redirect: 1,
				nocreate: 1
			}, config.edit );
			
			if (debug) {
				console.log(params);
				showSubmitMessage("Simulated message to [[File:" + imageInput.value + "]].");
				writePOTDTemplate();
			} else {
				new mw.Api().postWithEditToken(params).fail( function(code, error) {
						console.error("API error when adding template to local file description: ");
						console.error(error);
						showSubmitMessage("ERROR '" + code + "' when adding template to [[File:" + imageInput.value + "]]: " + error.error.info, 'warning');
						finishSubmit(true);
					} ).done( function() {
						showSubmitMessage("Added message to [[File:" + imageInput.value + "]].");
						writePOTDTemplate();
					} );
			}
		}
		
		function checkButtons() {
			if (!titleInput.value.length) {
				autofillTitleButton.setDisabled(true);
			} else {
				autofillTitleButton.setDisabled(false);
			}
			
			if (!imageInput.value.length) {
				autofillImageButton.setDisabled(true);
			} else {
				autofillImageButton.setDisabled(false);
			}
			
			if (titleInput.value.length && imageInput.value.length && dateInput.value.length && creditInput.value.length && !submitButton.isPending()) {
				submitButton.setDisabled(false);
			} else {
				submitButton.setDisabled(true);
			}
		}
		
		function preCheck() {
			var imageData = config.imageData;
			if (imageData.categories && imageData.categories.length){
				var cats=[];
				imageData.categories.forEach( function(cat) {return cats.push(cat.title);} );
				if (cats.includes('Category:' + config.fpCategory)) {
					var templateName = config.potdPrefix + dateInput.value;
					new mw.Api().get(
						Object.assign( {titles: templateName}, config.query )
					).fail(function(code, error) {
						console.warn("API error checking if POTD template exists:");
						console.warn(error);
						showDateError("got an API error '" + code + "' when checking if [[" + templateName + "]] exists: " + error.error.info);
						finishSubmit();
					}).done( function(data) {
						if (data && data.query && data.query.pages && data.query.pages[0]) {
							if (data.query.pages[0].missing) {
								console.log(templateName + " does not yet exist.");
								if (typeof config.titleData === 'undefined') {
									console.log('Fetching title data...');
									new mw.Api().get(
										Object.assign( {titles: titleInput.value}, config.query, config.revQuery, config.titleQuery )
									).fail(function(code, error) {
										console.warn("API error checking if article exists:");
										console.warn(error);
										showDateError("got an API error '" + code + "' when checking if [[" + titleInput.value + "]] exists: " + error.error.info);
										finishSubmit();
									}).done( function(data) {
										if (data && data.query && data.query.pages && data.query.pages[0]) {
											if (data.query.pages[0].missing) {
												showTitleError("was unable to find that article.");
											} else {
												console.log('Caching title data...');
												config.titleData = data.query.pages[0];
												writeLocalDesc();
											}
										} else {
											console.warn("Error parsing API respose for article:");
											console.log(data);
											showTitleError("was unable to parse the API response when trying get info on [[" + titleInput.value + "]].");
											finishSubmit();
										}
									});
								} else {
									console.log('Using cached title data...');
									if (config.titleData.missing) {
										showTitleError("was unable to find that article.");
									} else {
										writeLocalDesc();
									}
								}
							} else {
								showDateError("A Picture of the Day template already exists at [[" + templateName + "]].");
								finishSubmit();
							}
						} else {
							console.warn("Error parsing API respose for template:");
							console.log(data);
							showDateError("The script was unable to parse the API response when trying get info on the [[" + templateName + "]] template.");
							finishSubmit();
						}
					} );
				} else {
					showImageError("was unable to find [[Category:" + config.fpCategory + "]] on that image.");
					finishSubmit();
				}
			} else {
				showImageError("was unable to find any categories on that image.");
				finishSubmit();
			}
		}
		
		function submitClick() {
			titleMessage.toggle(false);
			imageMessage.toggle(false);
			dateMessage.toggle(false);
			submitMessage.toggle(false);
			if (uploaderInput.value != "" && uploaderInput.value.search(/^User:/i) == -1) {
				showImageMessage("Uploader name must begin with \"User:\".");
				uploaderInput.select();
			} else if (nominatorInput.value != "" && nominatorInput.value.search(/^User:/i) == -1) {
				showImageMessage("Nominator name must begin with \"User:\".");
				nominatorInput.select();
			} else if (creditInput.value.includes(creditDefault) || creditInput.value.includes(nameDefault)) {
				showImageMessage("Please enter a real image credit.");
				creditInput.select();
			} else if (secondaryCreditInput.value.includes(secondaryCreditDefault) || secondaryCreditInput.value.includes(nameDefault) ) {
				showImageMessage("Please enter a real secondary credit or leave it blank.");
				secondaryCreditInput.select();
			} else if (isNaN(Date.parse(dateInput.value))) {
				showDateError("Invalid date.");
				dateInput.select();
			} else {
				submitButton.setDisabled(true).pushPending().toggleFramed(false);
				closeButton.setDisabled(true).pushPending().toggleFramed(false);
				
				dateInput.setValue(new Date(dateInput.value).toISOString().split('T')[0]);
				config.editSummary = config.editSummaryPattern.replace(/%(\d+)/g, function(_, n) {return [imageInput.value, dateInput.value][n];}) + config.editSummarySuffix;
				
				fetchImageData(preCheck, finishSubmit);
			}
		}
		
		function filePrefill(imageInput, titleInput) {
			imageInput.setValue(mw.config.get('wgTitle'));
			fetchImageData(function() {
				fillUsage(config.imageData, titleInput);
				if (!uploaderInput.value.length && !nominatorInput.value.length) { getImageInfo(); }
			} );
		}
		
		function articlePrefill(imageInput, titleInput) {
			titleInput.setValue(mw.config.get('wgTitle'));
			if (typeof config.titleData === 'undefined') {
				console.log('Fetching title data...');
				new mw.Api().get(
					Object.assign( {titles: titleInput.value}, config.query, config.revQuery, config.titleQuery )
				).fail(function(code, error) {
					console.warn("API error getting Page Image:");
					console.warn(error);
				}).done( function(data) {
					if (data && data.query && data.query.pages && data.query.pages[0]) {
						console.log('Caching title data...');
						config.titleData = data.query.pages[0];
						fillImage(config.titleData, imageInput);
						if (!captionInput.value.length) { getCaption(); }
					} else {
						console.warn("Error parsing API respose for title:");
						console.log(data);
					}
				} );
			} else {
				console.log('Using cached title data...');
				fillImage(config.titleData, imageInput);
				if (!captionInput.value.length) { getCaption(); }
			}
		}
		
		function checkSizeInput() {
			fetchImageData(function() {
				checkSize();
				if (config.wide) {
					showImageMessage("Image will be wider than "+config.maxWidth+"px. "
					+(config.aspectRatio >= 1.5 
						? "The wide=yes parameter will be set in the POTD template unless the size is reduced" 
						: "Consider reducing the size")
					+" to "+config.maxWidth+" or less.", "warning");
				} else if (config.tall) {
					var maxSizeMsg = (config.aspectRatio ?  " to "+Math.floor(config.maxHeight*config.aspectRatio)+" or less ("+Math.floor(config.suggestHeight*config.aspectRatio)+" for an image height of "+config.suggestHeight+"px)" : "");
					showImageMessage("Image will be taller than "+config.maxHeight+"px. "
					+(config.aspectRatio < 0.67
						? "The tall=yes parameter will be set in the POTD template unless the size is reduced"
						: "Consider reducing the size")
					+maxSizeMsg+".", "warning");
				} else if (typeof imageMessage.getLabel() == 'string' && imageMessage.getLabel().startsWith("Image will be")) {
					imageMessage.toggle(false);
				}
			} );
		}
		
		if (typeof POTDFieldset === 'undefined') {
			var creditDefault = 'Photograph/Painting/etc.',
				secondaryCreditDefault = 'restored/photographed/etc.',
				nameDefault = '[[<!--name-->]]';
			
			var titleMessage = new OO.ui.MessageWidget( {type: 'error', showClose: true} ),
				titleInput = new OO.ui.ComboBoxInputWidget( { placeholder: 'Article the image represents', indicator: 'required', validate: 'non-empty'} ),
				autofillTitleButton = new OO.ui.ButtonWidget( { label: 'Autofill caption', icon: 'search', flags: ['progressive'] } ),
				captionInput = new OO.ui.MultilineTextInputWidget({placeholder: '\'\'\'[[Article title]]\'\'\' is...', autosize: true, }),
				texttitleInput = new OO.ui.TextInputWidget( { placeholder: 'Short caption. Leave blank to use Title' } ),
				alttextInput = new OO.ui.TextInputWidget( { placeholder: 'Leave blank to use Text Title' } ),
				creditInput = new OO.ui.TextInputWidget( { value: creditDefault + ' credit: ' + nameDefault, indicator: 'required', validate: function(v) {return (v != "" && !v.includes(creditDefault) && !v.includes(nameDefault));} } ),
				secondaryCreditInput = new OO.ui.TextInputWidget( { value: secondaryCreditDefault + ' by ' + nameDefault, validate: function(v) {return (!v.includes(secondaryCreditDefault) && !v.includes(nameDefault));} } ),
				sizeInput = new OO.ui.TextInputWidget( { placeholder: 'Size in pixels (defaults to '+config.defaultWidth.toString()+')', type: 'number' } ),
				imageMessage = new OO.ui.MessageWidget( {type: 'error', showClose: true} ),
				imageInput = new OO.ui.ComboBoxInputWidget( { placeholder: 'File name without "File:"', indicator: 'required', validate: 'non-empty' } ),
				autofillImageButton = new OO.ui.ButtonWidget( { label: 'Autofill image details', icon: 'search', flags: ['progressive'] } ),
				uploaderInput = new OO.ui.TextInputWidget( { placeholder: 'User:', validate: function(v){return (v == "" || v.search(/^User:/i) != -1); } } ),
				nominatorInput = new OO.ui.TextInputWidget( { placeholder: 'User:', validate: function(v){return (v == "" || v.search(/^User:/i) != -1); } } ),
				dateMessage = new OO.ui.MessageWidget( {type: 'error', showClose: true} ),
				dateInput = new OO.ui.TextInputWidget( { placeholder: 'YYYY-MM-DD', indicator: 'required', validate: function(v) {return !isNaN(Date.parse(v));} } ),
				commentsInput = new OO.ui.TextInputWidget( { placeholder: '[optional]' } ),
				submitMessage = new OO.ui.MessageWidget( {type: 'notice', showClose: true} ),
				submitButton = new OO.ui.ActionWidget( { label: 'Submit'+(debug ? " (debug)" : ""), disabled: true, framed: true, flags: ['primary','progressive']} ),
				closeButton = new OO.ui.ActionWidget( { label: 'Close', framed: true, flags: ['primary','destructive']} );
				
			var POTDFieldset = new OO.ui.FieldsetLayout( { label: potdScriptLongTitle, classes: [ 'container' ] } ),
				titleFieldset = new OO.ui.FieldsetLayout( { label: 'Linked article', classes: [ 'container' ] } ),
				imageFieldset = new OO.ui.FieldsetLayout( { label: 'Image information', classes: [ 'container' ] } ),
				templateFieldset = new OO.ui.FieldsetLayout( { label: 'Template information', classes: [ 'container' ] } ),
				submitFieldset = new OO.ui.FieldsetLayout( { classes: [ 'container' ] } );
			
			titleFieldset.addItems( [
				titleMessage,
				new OO.ui.ActionFieldLayout(titleInput, autofillTitleButton, {align: 'top', label: 'Title'}),
				new OO.ui.FieldLayout(captionInput, {align: 'top', label: 'Caption'}),
				new OO.ui.FieldLayout(texttitleInput, {align: 'top', label: 'Text title'})
			] );
			
			imageFieldset.addItems( [
				imageMessage,
				new OO.ui.ActionFieldLayout(imageInput, autofillImageButton, {align: 'top', label: 'Image'}),
				new OO.ui.FieldLayout(uploaderInput, {align: 'top', label: 'Original uploader'}),
				new OO.ui.FieldLayout(nominatorInput, {align: 'top', label: 'Featured Picture nominator'}),
				new OO.ui.FieldLayout(creditInput, {align: 'top', label: 'Credit (use full name, not user name, if known)'}),
				new OO.ui.FieldLayout(secondaryCreditInput, {align: 'top', label: 'Secondary credit (may be blank)'}),
				new OO.ui.FieldLayout(alttextInput, {align: 'top', label: 'Alt text'}),
				new OO.ui.FieldLayout(sizeInput, {align: 'top', label: 'Size'})
			] );
				
			templateFieldset.addItems( [
				dateMessage,
				new OO.ui.FieldLayout(dateInput, {align: 'top', label: 'Date'}),
				new OO.ui.FieldLayout(commentsInput, {align: 'top', label: 'Additional comments for talk page messages'}),
			] );
			
			submitFieldset.addItems( [
				new OO.ui.FieldLayout(submitMessage, {}),
				new OO.ui.FieldLayout(new OO.ui.ButtonGroupWidget( { items: [ submitButton, closeButton] } ))
			] );
			
			POTDFieldset.addItems( [ 
				titleFieldset,
				imageFieldset,
				templateFieldset,
				submitFieldset
			] );
			
			if (mw.config.get('wgNamespaceNumber') == 6) {
				filePrefill(imageInput, titleInput);
			} else if (mw.config.get('wgNamespaceNumber') == 0) {
				articlePrefill(imageInput, titleInput);
			}
			
			checkButtons();

			titleInput.inputFilter = function(value){return value.replace(/^\[\[/, '').replace(/\]\]$/, '').replace('_', ' ');};
			imageInput.inputFilter = function(value){return value.replace(/^File:/, '').replace('_', ' ');};
			creditInput.on( 'change', checkButtons );
			titleInput.on( 'change', function() {checkButtons();delete config.titleData;});
			titleInput.on( 'enter', getCaption);
			dateInput.on( 'change', checkButtons);
			imageInput.on( 'change', function() {checkButtons();delete config.imageData;fetchImageData(function(){imageMessage.toggle(false);checkSizeInput();});});
			imageInput.on( 'enter', getImageInfo );
			autofillTitleButton.on( 'click', getCaption);
			autofillImageButton.on( 'click', getImageInfo);
			sizeInput.on ( 'change', checkSizeInput);
			submitButton.on( 'click', submitClick);
			closeButton.on( 'click', function() {POTDFieldset.toggle(false);} );
			submitMessage.on( 'close', function() {
					config.submitMessages = [];
					submitMessage.setType('notice');
				} ).on( 'toggle', function() {
					if (!submitMessage.isVisible()) {
						config.submitMessages = [];
						submitMessage.setType('notice');
					}
				} );

			titleMessage.toggle(false);
			imageMessage.toggle(false);
			dateMessage.toggle(false);
			submitMessage.toggle(false);
			
			$( POTDFieldset.$element ).insertBefore( "#mw-content-text" );
		} else {
			POTDFieldset.toggle(true);
		}
	}
	
	function loadPOTDHelper() {
		mw.loader.getScript(
			'https://tools-static.wmflabs.org/cdnjs/ajax/libs/xregexp/3.2.0/xregexp-all.js'
		).then( function () {
			console.log('XRegExp loaded.');
			POTDHelper();
		}, function ( e ) {
			errorMessage = "Error: Cannot load XRegExp: " + e.message;
			console.error( errorMessage );
			mw.notify( errorMessage, {type: 'error'} );
		} );
	}
	
	if ((mw.config.get('wgNamespaceNumber') == 6 && mw.config.get('wgCategories').find( function(cat) {return cat == potdFPCategory;} )) || mw.config.get('wgNamespaceNumber') == 0) {
		var portletLink = mw.util.addPortletLink("p-cactions", "#", potdScriptShortTitle,
		"ca-potdhelper", "Make image a Picture of the Day");
		$( portletLink ).click(function(e) {
			e.preventDefault();
			return loadPOTDHelper();
		} );
	} else if (mw.config.get('wgNamespaceNumber') == -1 && mw.config.get('wgTitle').toLowerCase() === "potdhelper") {
		document.title = "Special:" + potdScriptShortTitle + ' - Wikipedia';
		document.getElementsByTagName("h1")[0].textContent = "Special:" + potdScriptShortTitle;
		document.getElementById("mw-content-text").innerHTML="";
		loadPOTDHelper();
	}
} );
// </nowiki>