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

//jshint maxerr:512

//

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

api = new mw.Api( { userAgent: potdScriptShortTitle + '/0.0.1' } );

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 POTD on %1',

editSummarySuffix: ' (' + potdScriptShortTitle + ')',

panoWidth: 500,

defaultWidth: 350,

landscapeWidth: 300,

squareWidth: 275,

portraitWidth: 250,

tallWidth: 225,

maxWidth: 650,

suggestHeight: 350,

maxHeight: 450,

aspectRatio: 0,

submitMessageHeader: 'Submitting Picture of the Day:

  • ',

    submitMessageFooter: '

',

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 '' + (v[1] || v[0]) + "";

} );

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("

  • ") + 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...');

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

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

    extract = extract.replace(//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, "$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...');

    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 {

    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 {

    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("");

    }

    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}}{{SUBPAGENAME}}}}" +

    "\n\n== See also ==" +

    "\n* Template:POTD{{#ifeq:{{BASEPAGENAME}}";

    console.log(templateText);

    var params = Object.assign( {

    createonly: 1,

    title: templateName,

    text: templateText,

    summary: 'Creating a POTD template for File:' + imageInput.value + '' + config.editSummarySuffix

    }, config.edit );

    if (debug) {

    console.log(params);

    showSubmitMessage("Simulated " + templateName + ".");

    writeArticleTalk();

    } else {

    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 {

    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;

    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...');

    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...');

    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 = '';

    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();

    }

    } );

    //