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.
// <nowiki>
var afch2 = {
	script_home: 'User:Gryllida/DraftsReview/v2.js' // subst:FULLPAGENAME in double {}s
};
 
/*  ****************************************************************************
************* General Log functions ********************************************
******************************************************************************* */
 
function bugOut(){
	Log('Could not complete. Please see query error above. Sorry. :-(',0);
}
function finishOK(){
	Log('Done. :-)',0);
}
 
// Log (MESSAGE, [BUTTONS_DISABLED_STATE])
function Log(msg, propDisabled){
	$('#afch2Log').val($('#afch2Log').val() + msg + '\r\n');
	if(typeof propDisabled != 'undefined'){
		$('#afch2Box').prop('disabled',propDisabled);
		/*$('#afch2ApproveButton').prop('disabled',propDisabled);
		$('#afch2CancelButton').prop('disabled',propDisabled);
		$('#afch2DeclineButton').prop('disabled',propDisabled);*/
	}
}
 
/*  ****************************************************************************
************* Query functions **************************************************
******************************************************************************* */
 
// Gets meaningful content from the query.
function understandJson(data){
    var page, wikitext;
 
    if(data.edit && data.edit.result == 'Success'){ // An edit was ok
		return true;
    }
    if(data.move){ // A move was ok
		return true;
    }
 
    if(!data.query){ // We really need this for a read query
            return false;
    }
    for (page in data.query.pages) {
            if(!data.query.pages[page].revisions){ // We also really need this for a read query
                    return false;
            }
            res = data.query.pages[page].revisions[0]['*'];
            return res;
    }
}
 
/*  ****************************************************************************
************* Contributor functions ********************************************
******************************************************************************* */
 
function Contributor(name) {
   this.name = name;
}
 
Contributor.prototype.notify = function(page){
	var template = page.isApproved ? 'Afc talk' : 'Afc decline';
	var humanStatus = page.isApproved ? 'passed' : 'not ready';
 
	var pageName = page.name.replace(/_/g,' ');
	var subject = pageName.replace('Wikipedia talk:Articles for creation/','');
 
	var sectionTitle = '[['+pageName+']] (<span class="plainlinks">[http://en.wikipedia.org/w/index.php?oldid='+page.revid+' revision '+ page.revid+']</span>)';
 
	var talkPage = new Page('User talk:'+this.name);
 
	return talkPage.query(1,{
		content: '{{subst:'+template+'|'+subject+'|sig=yes}}',
		summary: '[['+page.name+']]: '+humanStatus,
		section: 'new',
		sectionTitle: sectionTitle
	});
};
 
/*  ****************************************************************************
************* Page functions ***************************************************
******************************************************************************* */
 
function Page(name) {
   this.name = name;
   this.revid = mw.config.get('wgCurRevisionId');
}
 
Page.prototype.query = function(actionType, info, stopNagging) {
	console.log('processing page ' + this.name);
	var dfd = $.Deferred(),
		self = this;
	var query;
	console.log('setting up query for this action type');
	switch (actionType){
		case 0:
			query = this.readQuery(info);
			break;
		case 1:
			query = this.editQuery(info);
			break;
		case 3:
			query = this.moveQuery(info);
			break;
 
	}
	console.log('done');
 
	// If the query fails, the check failed.
	query.fail( function(data){ 
		if(!stopNagging){
			Log("Couldn't query " + self.name + " (Action type " + actionType + ")");
		}
		dfd.reject();
	});
 
	query.then( function ( data ) {
		console.log('I got data.');
		console.log(data);
		var result = understandJson(data);
		if (!result) {
			if(!stopNagging){
				Log("Got error with query " + self.name + " (Action type " + actionType + ")");
				Log(JSON.stringify(data,null,4));
			}
			dfd.reject(data);
		} else { 
			dfd.resolve(result);
		}
	} );
 
	// calling .promise() makes the object 'read-only'; only we can resolve /
	// reject this deferred. it's not necessary to do but it's a nice way of
	// indicating which piece of code is responsible for some operation.
	return dfd.promise();
};
 
Page.prototype.readQuery = function(info){
        var hash = {
                format:  'json',
                action:  'query',
                prop:    'revisions',
                rvprop:  'content',
                rvlimit: 1,
                titles:  this.name,
        };
        if (info && typeof info.sectionNum != 'undefined'){
			hash.rvsection=info.sectionNum;
        }
 
        var promise = $.getJSON(mw.util.wikiScript('api'),hash);
 
        return promise;
};
 
// content - target page content
// summary - target page summary
// section - section number
// sectionTitle - section title
Page.prototype.editQuery = function(info){
        var hash = {
                url: mw.util.wikiScript( 'api' ),
                type: 'POST',
                dataType: 'json',
                data: {
                        format: 'json',
                        action: 'edit',
                        title: this.name,
                        text: info.content, // will replace entire page content
                        summary: info.summary + ' ([[WP:AFC/S|see other submissions]]; [['+afch2.script_home+'|semi-automated]])',
                        token: mw.user.tokens.get( 'editToken' )
                }
        };
 
        if (typeof info.section != 'undefined'){
                hash.data.section=info.section;
        }
 
        if (typeof info.sectionTitle != 'undefined'){
                hash.data.sectiontitle=info.sectionTitle;
        }
 
        var promise = $.ajax(hash);
 
        return promise;
};
 
Page.prototype.moveQuery = function(info){
	fromName = this.name;
	toName = info.to;
	this.name=toName;
	return $.ajax({
		url: mw.util.wikiScript( 'api' ),
		type: 'POST',
		dataType: 'json',
		data: {
			format: 'json',
			action: 'move',
			from: fromName,
			to: toName,
			reason: 'ready ([[WP:AFC/S|see other submissions]])',
			token: mw.user.tokens.get( 'editToken' )
		}
	});
};
 
/*  ****************************************************************************
************* Page functions: review *******************************************
******************************************************************************* */
 
// Decline the page
Page.prototype.decline = function(){
	var self=this;
	Log('Declining... ',1);
	this.isApproved=0;
	// Grab the page markup and work on it
	this.query(0)
	.then(self.processMarkupToDecline.bind(self))
	.fail(bugOut);
};
 
Page.prototype.processMarkupToDecline=function(data){
	Log('... retrieved the page markup ...');
	var markup = new MarkUp(data);
 
	// Remove all review pending templates (or exit if there are none)
	if(!markup.clean()){
		Log('Article not in queue. Sorry. :-(',0);
		return false;
	}
 
	// Add review comment into the markup
	markup.decline();
	contributor = new Contributor(markup.submitterName);
 
	// Save the article with the processed markup (w/ review comment)
	Log('... processed markup for declining, trying to save the article ...');
	$.when(
		this.query(1, {
			title: this.name,
			content: markup.data,
			summary: 'draft not ready'
		}),
		contributor.notify(this)
	)
	.done(finishOK)
	.fail(bugOut);
};
 
// Comment on the page
Page.prototype.comment = function(){
	var self=this;
	Log('Commenting... ',1);
	// Grab the page markup and work on it
	this.query(0)
	.then(self.processMarkupToComment.bind(self))
	.fail(bugOut);
};
 
Page.prototype.processMarkupToComment=function(data){
	Log('... retrieved the page markup ...');
	var markup = new MarkUp(data,this);
	var summary = $('#afch2CommentSummary').val();
	markup.comment(); // uses $('#reviewBox').val()
 
	$.when(
		this.query(1, {
		title: this.name,
		content: markup.data,
		summary: '+comment (' + summary + ')'
		})
	)
	.done(finishOK)
	.fail(bugOut);
};
 
// Approve the page
Page.prototype.approve = function(){
	var self=this;
	Log('Approving... ',1);
	this.isApproved=1;
	// Grab the page markup and work on it
	this.query(0)
	.then(self.processMarkupToApprove.bind(self))
	.fail(bugOut);
};
 
Page.prototype.processMarkupToApprove=function(data){
	Log('... retrieved the page markup ...');
	var markup = new MarkUp(data,this);
	var targetName = $('#afch2TargetPageName').val();
	var self=this;
 
	// Remove all review pending templates (or exit if there are none)
	if(!markup.clean()){
		Log('Article not in queue. Sorry. :-(',0);
		return false;
	}
 
	// Add review comment into the markup
	markup.extractComments();
 
	this.query(3,{
			to: targetName
	}).then(self.postMoveProcess.bind(markup))
	.fail(bugOut);
};
 
Page.prototype.postMoveProcess = function(){
	newTalk = new Page('Talk:' + this.page.name);
	contributor = new Contributor(this.submitterName);
	$.when(
		newTalk.query(1, {
			content: this.talk, 
			summary: 'creation from Draft discussion',
			section: 'new',
			sectionTitle: 'Draft discussion'
			}),
		this.page.query(1, {
			content: this.data,
			summary: 'move of discussion to the talk page',
		}),
		contributor.notify(this.page)
	).then(finishOK)
	.fail(bugOut);
};
 
/*  ****************************************************************************
************* MarkUp functions *************************************************
******************************************************************************* */
 
// Construct a new MarkUp object
function MarkUp(data,page){
	this.data = data;
	this.page=page;
}
 
MarkUp.prototype.comment = function(){
	var comment  = $('#reviewBox').val() ;
	comment = "{{afc comment|1=" + comment + " --[[User:Gryllida|Gryllida]] ([[User talk:Gryllida|talk]]) 08:21, 26 January 2014 (UTC)}}";
	this.data =  comment + "\r\n" + this.data;
	return true;
};
 
/*
 * Remove all 'review pending' tags from the MarkUp object
 * Also place the oldest (valuable! :) review-pending tag
 * into this.template_res, for later processing */
MarkUp.prototype.clean = function(){
	var data = this.data;
	// Find all 'review pending' templates on the page
	var templates = this.data.match(/{{AFC submission\|\|\|.*?}}/gmi);
 
	// Exit if no review pending is marked on the page
	if (templates == null){
		return false;
	}
 
	// Find the oldest 'review pending' teplace on the page; also, remove them all.
	var ts_min = Infinity;
	var template_res='';
	$(templates).each( function (index, template){ // template = '@FC submission|||ts=20140113103528|u=Daniels Wembley|ns=2'
		ts = template.replace('{{','').replace('}}','').split('|')[3].replace('ts=','');
		if (ts<ts_min){
			ts_min = ts;
			template_res = template;
		}
		data = data.replace(template, '');
	});
 
	// Remove boiletplate
	data = data.replace('== Request review at [[WP:AFC]] ==','');
	data = data.replace(/<!-- Just press the "Save page" button below without .*/,'');
 
	// Save template for processing
	this.data=data;
	this.template_res = template_res;
 
	// Break the submit template into pieces
	this.values = this.template_res.replace('{{','').replace('}}','').split('|');
 
	// Retrieve the username of the submitter
	this.submitterName = this.values[4].replace('u=','');
 
	// Exit
	return true;
};
 
MarkUp.prototype.extractComments = function(){
	var data = this.data;
	var talk = '';
	if($('#afch2PrjName').val()){
		talk += '{{WikiProject '+$('#afch2PrjName').val()+'}}\n';
	}
	// Find all AFC templates on the page and remove them
	var templates = this.data.match(/{{AFC[^]*?}}/gmi);
	$(templates).each( function (index, template){
		data = data.replace(template, '');
		talk += template;
		talk += '\r\n';
	});
	talk+="Approved. --~~~~\r\n";
 
	// Save template for processing
	this.data=data;
	this.talk = talk;
 
	// Exit
	return true;
};
 
MarkUp.prototype.decline = function(){
	// Submit template pieces
	var values = this.values;
 
	// 'd' as first value: Decline
	values[1]='d';
 
	// 'reason' as second value: Review type ('cv', 'npov', other types exist; not implemented)
	values[2]='reason';
 
	// Add 3=, declinets, decliner params - no place for them, kick around
	values.splice(3,0,
		'3 = ' + $('#reviewBox').val(),
		'declinets={{subst:REVISION'+'TIMESTAMP}}',
		'decliner=' + mw.config.get('wgUserName'));
	this.template_res = '{{' + values.join('|') + '}}';
 
	// Prepend the declined template to the page.
	this.data = this.template_res + '\r\n' + this.data;
	return true;
};
 
/*  ****************************************************************************
************* DOM functions ****************************************************
******************************************************************************* */
 
// Construct DOM object
function DOM(){
	// Load HTML from subpage
	Log('Making DOM...');
	this.subPage = new Page(afch2.script_home+'/text');
	this.page = new Page(mw.config.get('wgPageName'));
	this.query = this.subPage.query(0, {sectionNum: 0});
}
 
// Hide things and set events as necessary in the DOM
DOM.prototype.setUp = function(){
	var page = this.page;
	// Hide everything
	$('afch2Log').hide();
	$('#afch2Box').hide();        
	$('#afch2ApproveBox').hide();        
	$('#afch2DeclineBox').hide();
	$('#afch2CommentBox').hide(); 
 
	// Set events
	//$("#reviewCans").change( function() {afch2.dom.insertCannedResponse();});
	$('#afch2DeclineButton').click(page.decline.bind(page));
	$('#afch2ApproveButton').click(page.approve.bind(page));
	$('#afch2CommentButton').click(page.comment.bind(page));
	$('#afch2ApprovePageCheck').click(pageCheck);
	$("#reviewCans").change(insertCannedResponse);
 
	$('#afch2CancelButton').click(function(){
		$('#afch2Box').hide();
	});
};
 
function pageCheck(){
	$('#afch2ApprovePageCheck').prop('disabled',true);
	var targetPage = new Page($('#afch2TargetPageName').val());
	targetPage.query(0,{},1)
	.then(function(){
		$('#afch2ApproveButton').prop('disabled',true);
	}).fail(function(){
		$('#afch2ApproveButton').prop('disabled',false);
	}).always(function(){
		$('#afch2ApprovePageCheck').prop('disabled',false);
	});
}
 
DOM.prototype.fillInReviewBox = function(data){
	var lines = data.split('\n');
	lines.shift();
	$('#reviewBox').val(lines.join('\n'));
	Log('... filled in the review box. :-)');
};
 
DOM.prototype.fillInCannedResponses = function(data){
	var lines = data.split('\n');
	lines.shift();
	// Process group, name, and content from each line
	$(lines).each(function( i, line ) {
		group = line.split(':')[0];
		name = line.split(':')[1];
		content = line.split(':').slice(2).join(':');
		// Create optgroup if it doesn't yet exist
		if($('#reviewCans optgroup[label="'+group+'"]').html() == null){
			$('<optgroup/>', {
				label: group
			}).appendTo('#reviewCans');
		}
		// Add the option to the relevant optgroup
		$('<option/>', {
			html: name,
			value: content
		}).prependTo('#reviewCans optgroup[label="'+group+'"]');
	});
	Log('... filled in the canned responses. :-)');
};
 
DOM.prototype.fillIn = function(){
	var self=this;
	Log('... filling in DOM ...');
	this.subPage.query(0, {sectionNum: 1})
		.then(self.fillInReviewBox)
		.fail(bugOut);
 
	this.subPage.query(0, {sectionNum: 2})
		.then(self.fillInCannedResponses)
		.fail(bugOut);
};
 
function insertCannedResponse (){
	var caretPos = document.getElementById("reviewBox").selectionStart;
	var textAreaTxt = $("#reviewBox").val();
	var txtToAdd = $( "#reviewCans" ).val();
	$("#reviewBox").val(textAreaTxt.substring(0, caretPos) + txtToAdd + textAreaTxt.substring(caretPos) );
}
 
DOM.prototype.only_show = function(id){
	$('#afch2Box').toggle();
	$('#afch2ApproveBox').hide();	
	$('#afch2DeclineBox').hide(); 
	//$('#afch2CommentBox').hide(); 
	$('#' + id).show();
};
 
/*  ****************************************************************************
************* Document onload routine ******************************************
******************************************************************************* */
 
// Wait for the page to be parsed
$(document).ready( function() { 
	// Adds review tab
	var linkA = mw.util.addPortletLink( 'p-cactions', '#', 'Approve', 'ca-afch2-approve', 'AFC Approve');
	var linkD = mw.util.addPortletLink( 'p-cactions', '#', 'Decline or comment', 'ca-afch2-decline', 'AFC Decline');
	//var linkC = mw.util.addPortletLink( 'p-cactions', '#', 'Comment', 'ca-afch2-comment', 'AFC Comment');
 
	console.log('Making dom object');
	var dom = new DOM();
	console.log('Made dom object');
	dom.query.then(function(data){
		console.log('Added in the html');
		$(data).prependTo('#content');
		dom.setUp();
		console.log('dom set up');
		dom.fillIn();
		console.log('dom filled in');
	}).fail(bugOut);
 
	// Assigns actions to the tabs
	$( linkA ).click( function ( event ) {
		event.preventDefault();
		dom.only_show('afch2ApproveBox');
	} );
	$( linkD ).click( function ( event ) {
		event.preventDefault();
		dom.only_show('afch2DeclineBox');
	} );
	//$( linkC ).click( function ( event ) {
	//	event.preventDefault();
	//	dom.only_show('afch2CommentBox');
	//} );
} );
// </nowiki>