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.
//<pre>wpPicmark.js
//By Gracenotes (http://en.wikipedia.org/wiki/User:Gracenotes)
//Released under the GPL

//Marks an image on Wikipedia (English) for something. 
//If you have suggestions for more templates, talk to Gracenotes
//Alpha version finished 21 April 2007

//global variables
var templateCase = 0; //which item was selected from drop-down
var templateStage = 0; //which page editing
var cpost; //asyncPost object
var uploader; //uploader of the image, scratched from page
var imForm; //abstract object representing pseudo-form
var imMessage = ''; //current message

//global array
var tempDate = new Date();
var tempDateString = tempDate.getUTCFullYear() + ' ' + ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][tempDate.getUTCMonth()] + ' ' + tempDate.getUTCDate();
var templateArray = {
    1: ['{{subst:nld}} No information on copyright', ' {{subst:nld}}', '{{subst:Image copyright request|Image:__IMAGE__}} ~~~~'],
    2: ['{{subst:nsd}} No source information', '{{subst:nsd}}', '{{subst:image source|Image:__IMAGE__}} ~~~~'],
    3: ['{{subst:nld}} + {{subst:nsd}}', '{{subst:nld}}\n{{subst:nsd}}', '{{subst:Image copyright request|Image:__IMAGE__}} ~~~~'],
    4: ['{{subst:ifd}} Nominate image for deletion', '{{subst:ifd}}', '{{subst:idw|1=Image:__IMAGE__}}', 'Wikipedia:Images and media for deletion/' + tempDateString, '{{subst:ifd2|__IMAGE__|Uploader=__AUTHOR__|Reason=<<Reason for deletion>>}} — ~~~~'],
    5: ['{{subst:orfud}} Orphaned fair use, not replaced', '{{subst:orfud}}', '{{subst:Orphaned|Image:__IMAGE__}} ~~~~'],
    6: ['{{subst:orfur}} Orphaned fair use, replaced', '{{subst:orfur|Image:<<Replaced by Image>>}}', '{{subst:Orphaned|Image:__IMAGE__}} ~~~~'],
    7: ['{{subst:rfu}} Fair use image considered replaceable', '{{subst:rfu}}', '{{subst:replaceable|__IMAGE__}} ~~~~'],
    8: ['{{copyrighted}} Used with permission, not including third party use', '{{copyrighted}}'],
    9: ['{{Db-redundantimage}} Reundant to another image (I1)', '{{Db-redundantimage|1=<<Redudant to Image>>}}'],
    10:['{{Db-noimage}} Corrupted or empty image (I2)', '{{Db-noimage}}'],
    11:['{{Db-attack}} Attack image (G10)', '{{Db-attack}}', '{{subst:uw-upload2|1=Image:__IMAGE__|subst=subst:}}'],
    12:['{{Db-vandalism}} Image meant for vandalism (G3)', '{{Db-vandalism}}', '{{subst:uw-upload2|1=Image:__IMAGE__|subst=subst:}}'],
    13:['{{PUIdisputed}} Information on source/license disputed', '{{PUIdisputed}}', '{{subst:idw-pui|1=Image:__IMAGE__}}', 'Wikipedia:Possibly unfree images', '*[[:Image:__IMAGE__]] <<Explanation>> ~~~~'],
    14:['{{PUInonfree}} Only available non-free', '{{PUInonfree}}', '{{subst:idw-pui|1=Image:__IMAGE__}}', 'Wikipedia:Possibly unfree images', '*[[:Image:__IMAGE__]] <<Explanation>> ~~~~'],
    15:['{{bsr}} Direct link to source, but no license context', '{{bsr}}', '{{subst:bsr-user|Image:__IMAGE__}} ~~~~']
}

var picmark = new Object();
picmark.popupBox = true;
picmark.minorEdit = [false, false, false];
picmark.watchThis = [false, false, false];
picmark.loadAll = true;

var imForm = new Object();

imForm.setEnabled = function(enable) {
  var elems = ['imTextbox', 'imSummary', 'imWatchthis', 'imMinoredit', 'imSubmit', 'imSkip', 'imPageChange'];
  var enText = enable ? "" : "true";
  for (var i = 0; i < elems.length; i++)
    this[elems[i]].disabled = enText;
}

imForm.set = function(tb, sm, wl, me, pg) {
  this.imSummary.value = sm.imReplace();
  this.imWatchthis.checked = wl;
  this.imMinoredit.checked = me;
  this.imPage.firstChild.nodeValue = pg.replace(/_/g, ' ');
  this.imPage.title = 'Phase ' + (templateStage + 1);
  this.imPage.href = '/wiki/' + encodeURIComponent(pg.replace(/\s/g, '_'))
  this.imTextbox.value = fillInVars(tb.imReplace());
}

imForm.syncWithSync = function(sync) {
  sync.text = this.imTextbox.value.imReplace();
  sync.summary = this.imSummary.value.imReplace();
  sync.minor = this.imWatchthis.checked;
  sync.watch = this.imMinoredit.checked;
}

imForm.status = function(message) {
  var temp = imMessage.length == 0;
  imMessage += '...' + message;
  if (temp) {
    this.scroll();
  }
}

imForm.scroll = function() {
  var me = this;
  if (me.imStatus.nodeValue.length < 85)
    me.imStatus.nodeValue += imMessage.substring(0, 1);
  else
    me.imStatus.nodeValue = me.imStatus.nodeValue.substring(1) + imMessage.substring(0, 1);
  if (imMessage.length > 0) {
    imMessage = imMessage.substring(1);
    var temp = window.setTimeout('imForm.scroll()', 30);
  }
}

imForm.finish = function() {
  imMessage = '';
  this.imStatus.nodeValue = 'Done with application';
  //a more specific implementation of set()
  this.imTextbox.value = '';
  this.imSummary.value = '';
  this.imWatchthis.checked = false;
  this.imMinoredit.checked = false;
  imForm.imPage.parentNode.innerHTML = '(done)';
  this.setEnabled(false);
}

function imInit() {
  if (wgNamespaceNumber == 6 && document.getElementById('filehistory')) {
    var templateSelect = document.createElement('form');
    templateSelect.setAttribute("name", "templateSelect")
    //templateSelect using innerHTML: not elegant, but fast
    var templateString = ['<select name="imagetemp" onchange="imMain()">\n<option value="0" selected="selected">(Select a template)</option>'];
    for (var i in templateArray) {
      templateString.push('<option value="'+i+'">' + templateArray[i][0] + '</option>');
    }
    templateString.push('</select></form>');
    templateSelect.innerHTML = templateString.join('\n');
    document.getElementById('file').appendChild(templateSelect);
    imForm.form = templateSelect;
    uploader = getElementsByClassName(getElementsByClassName(document, 'table', 'filehistory')[0], 'a', 'mw-userlink')[0].firstChild.nodeValue;
  }
}

function imMain() {
  templateCase = document.forms.templateSelect.imagetemp.selectedIndex;
  document.forms.templateSelect.imagetemp.disabled = true;
  if (templateCase == 0) return false;
  var imDiv = document.createElement('div');
  imDiv.setAttribute('style', 'border:solid red 2px; padding: 5px;');
  imDiv.setAttribute('id', 'imBox');
  //convert text to DOM, and /then/ attach
  imDiv.innerHTML = '<span id="imStatus" style="color: brown; text-align:center;"> </span><br />Post the following to <span id="imPage" style="background-color: lightgray; font-family: monospace; font-size:120%; font-weight: bold;"><a href="#" title="">Page name</a></span> <input type="button" value="(change)" name="imPageChange" id="imPageChange" onClick="var k = prompt(\'Enter the page name.\', cpost.page); cpost.page = k; cpost.stage = 0; imUpdate();" />\n\n<textarea cols="80" rows="10" name="imTextbox" id="imTextbox" >Text to send</textarea><br /><label for="imSummary">Edit summary (__IMAGE__ → image name, __AUTHOR__ → uploader username):</label><input type="text" size="30" maxlength="250" name="imSummary" id="imSummary" value="" /><br /><input type="checkbox" name="imMinoredit" id="imMinoredit" /><label for="imMinoredit">Minor edit</label><input type="checkbox" name="imWatchthis" id="imWatchthis" /><label for="imWatchthis">Watch this page</label> <input type="button" value="Submit" name="imSubmit" id="imSubmit" onClick="cpost.stage++; imForm.syncWithSync(cpost); imForm.setEnabled(false); imUpdate();" /> <input type="button" value="Skip" name="imSkip" id="imSkip" onClick="cpost = new asyncPost(); templateStage++; imUpdate();" />';
  document.forms.templateSelect.appendChild(imDiv);
  imForm.imBox = document.getElementById('imDiv');
  imForm.imTextbox = document.getElementById('imTextbox');
  imForm.imSummary = document.getElementById('imSummary');
  imForm.imWatchthis = document.getElementById('imWatchthis');
  imForm.imMinoredit = document.getElementById('imMinoredit');
  imForm.imSubmit = document.getElementById('imSubmit');
  imForm.imSkip = document.getElementById('imSkip');
  imForm.imPageChange = document.getElementById('imPageChange');
  imForm.imStatus = document.getElementById('imStatus').firstChild;
  imForm.imPage = document.getElementById('imPage').getElementsByTagName('a')[0];
  imForm.setEnabled(false);
  cpost = new asyncPost();
  imUpdate();
}

function imUpdate() {
  switch (cpost.stage) {
    case 0:
    switch(templateStage) {
      case 0:
        if (!cpost.page) cpost.page = wgPageName;
        imForm.set(templateArray[templateCase][1], 'Tagging [[Image:__IMAGE__]]', picmark.watchThis[0], picmark.minorEdit[0], cpost.page)
        break;
      case 1:
        if (templateArray[templateCase][2]) {
          if (!cpost.page) cpost.page = 'User talk:' + uploader;
          imForm.set(templateArray[templateCase][2], 'Notifying uploader', picmark.watchThis[1], picmark.minorEdit[1], cpost.page);
        }
        else {
          templateStage = 3;
        }
        break;
      case 2:
        if (templateArray[templateCase][3]) {
          if (!cpost.page) cpost.page = templateArray[templateCase][3];
          imForm.set(templateArray[templateCase][4], 'Posting to process page', picmark.watchThis[2], picmark.minorEdit[2], cpost.page);
        }
        else {
          templateStage = 3;
        }
        break;
      }
      if (templateStage < 3) {
        imForm.status('Getting edit box');
        imForm.setEnabled(false);
        cpost.getFormbox();
      }
      else
        imForm.finish();
      break;  
    case 1:
      cpost.setFormbox();
      break;
    case 2:
      cpost.submitFormbox();
      break;
    case 3:
      if (templateStage < 3) {
        cpost = new asyncPost();
        imUpdate();
      }
      else {
        imForm.finish();
      } 
      break;
  }
}

//we'll be needing 3 of these
//possible: add parameter for minor edit or not
function asyncPost(page, text, summary, minor, watch) {
  //properties: may change with methods
  this.page = page || '';
  this.text = text || '';
  this.summary = summary || '';
  this.minor = minor || false;
  this.watch = watch || false;
  this.stage = 0;
  this.formbox = undefined;

  //adapted from code by Quarl
  this.getFormbox = function() {
    var me = this;
    var req = sajax_init_object();

    req.open("GET", mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?title='+encodeURIComponent(this.page.replace(/\s/g, '_'))+'&action=edit', true);
    req.overrideMimeType('text/xml');
    req.onload = function(event) {
      var req = event.target;
      if (req.readyState == 4) {
        if (req.status != 200) {
          alert('Can\'t download page!');
          return;
        }
        me.formbox = req.responseXML.getElementById('editform');
        imForm.status('Form received');
        var temp = me.formbox['wpTextbox1'].value;
        if (picmark.loadAll) {
          imForm.imTextbox.value = temp + imForm.imTextbox.value;
          if (imForm.imTextbox.setSelectionRange)
            imForm.imTextbox.setSelectionRange(temp.length - 1, imForm.imTextbox.value.length);
        }
        imForm.setEnabled(true);

        //basic regexes from [[User:Quarl/redirector.js]], but some altered

        if (new RegExp('^[\\s\\n]*$').test(temp)) {
          alert('Note: page is empty; this could mean that it doesn\'t exist.');
        }

        if (temp.match(/^\s*#REDIRECT \[\[([^\]]+)\]\]/i)) {
          var temp2 = RegExp.$1;
          var temp3 = confirm(me.page + ' redirects to ' + temp2 + '. Edit that page instead?');
          if(temp3) {
            me.page = temp2;
            me.stage = 0;
            imUpdate();
          }
        }
      };
    };
    req.send(null);
    return req;
  };

  this.setFormbox = function() {
    imForm.status('Setting text');
    if (picmark.loadAll)
      this.formbox['wpTextbox1'].value = this.text;
    else
      this.formbox['wpTextbox1'].value += this.text;
    this.formbox['wpSummary'].value = this.summary;
    this.formbox['wpMinoredit'].checked = this.minor;
    this.formbox['wpWatchthis'].checked = this.watch;
    this.stage++;
    imUpdate();
  };

  this.submitFormbox = function() {
    var fields = [
      'wpSection', 'wpStarttime', 'wpEdittime', 'wpScrolltop',
    'wpTextbox1', 'wpSummary', 'wpMinoredit', 'wpWatchthis',
    'wpEditToken' ];
    var e;
    var contquery = '';

    for (var i in fields) {
      e = this.formbox[fields[i]];
      if (e && (e.type != 'checkbox' || e.checked)) {
        contquery += '&' + fields[i] + '=' + encodeURIComponent(e.value);
      }
    }
    contquery = contquery.substring(1) + '&wpIgnoreBlankSummary';

    var url = this.formbox.action;
    var req = sajax_init_object();

    var me = this;
    imForm.status('Submitting form');
    req.open("POST", url, true);
    req.overrideMimeType('text/xml');
    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.setRequestHeader("Content-length", contquery.length);
    req.setRequestHeader("Connection", "close");
    req.onload = function(event) {
        var req = event.target;
        if (req.readyState == 4) {
          imForm.status('Edit has been made');
          me.stage++;
          templateStage++;
          imUpdate();
        }
    };
    req.send(contquery);
  };
}

//getTemplateText() and getConfirmedText() removed from here
//former was too complicated
//latter was replaced by the form, textbox and such

function fillInVars(text) {
  var tickerArr = [];
  var tickerText;
  while (true) {
    tickerArr = [text.indexOf('<<'), text.indexOf('>>')];
    if (tickerArr[0] == -1 || tickerArr[1] == -1) {
      break;
    }
    else {
      tickerText = text.substring(tickerArr[0], tickerArr[1] + 2)
      if (picmark.popupBox) {
        text = text.replace(tickerText, prompt(tickerText.substring(2, tickerText.length - 2) + ':', '') || '')
      }
      else {
        text = text.replace(tickerText, '');
      }
    }
  }
  return text;
}

String.prototype.imReplace = function(message) {
  return this.replace('__IMAGE__', wgTitle).replace('__AUTHOR__', uploader) 
}

addOnloadHook(imInit);
//necessary so templates don't subst in source → </pre>