diff --git a/modules/twinklearv.js b/modules/twinklearv.js
index 81019b349..f08426c2f 100644
--- a/modules/twinklearv.js
+++ b/modules/twinklearv.js
@@ -379,9 +379,8 @@ Twinkle.arv.callback.changeCategory = function (e) {
const $input = $('', {
type: 'checkbox',
name: 's_' + field,
- value: rev.revid
+ value: JSON.stringify(rev)
});
- $input.data('revinfo', rev);
$input.appendTo($entry);
let comment = '';
// revdel/os
@@ -472,8 +471,6 @@ Twinkle.arv.callback.evaluate = function(e) {
let reason = '';
const input = Morebits.QuickForm.getInputData(form);
- const uid = form.uid.value;
-
switch (input.category) {
// Report user for vandalism
@@ -591,7 +588,7 @@ Twinkle.arv.callback.evaluate = function(e) {
Morebits.wiki.actionCompleted.redirect = reportpage;
Morebits.wiki.actionCompleted.notice = 'Reporting complete';
- var spiPage = new Morebits.wiki.page(reportpage, 'Retrieving discussion page');
+ var spiPage = new Morebits.wiki.Page(reportpage, 'Retrieving discussion page');
spiPage.setFollowRedirect(true);
spiPage.setEditSummary('Adding new report for [[Special:Contributions/' + reportData.sockmaster + '|' + reportData.sockmaster + ']].');
spiPage.setChangeTags(Twinkle.changeTags);
@@ -603,129 +600,47 @@ Twinkle.arv.callback.evaluate = function(e) {
break;
case 'an3':
- var diffs = $.map($('input:checkbox[name=s_diffs]:checked', form), (o) => $(o).data('revinfo'));
+ // prepare the AN3 report, then post and notify
+ Twinkle.arv.callback.getAn3ReportData(input).then((data) => {
+ // If there are any reasons why the user might want to cancel the report, check with them about each reason and cancel if they choose to
+ for (const confirmation of data.confirmations) {
+ if (!confirm(confirmation)) {
+ return;
+ }
+ }
- if (diffs.length < 3 && !confirm('You have selected fewer than three offending edits. Do you wish to make the report anyway?')) {
- return;
- }
+ Morebits.SimpleWindow.setButtonsEnabled(false);
+ Morebits.Status.init(form);
- var warnings = $.map($('input:checkbox[name=s_warnings]:checked', form), (o) => $(o).data('revinfo'));
+ Morebits.wiki.addCheckpoint(); // prevent notification events from causing an erronous "action completed"
- if (!warnings.length && !confirm('You have not selected any edits where you warned the offender. Do you wish to make the report anyway?')) {
- return;
- }
+ const reportpage = 'Wikipedia:Administrators\' noticeboard/Edit warring';
- var resolves = $.map($('input:checkbox[name=s_resolves]:checked', form), (o) => $(o).data('revinfo'));
- var free_resolves = $('input[name=s_resolves_free]').val();
+ Morebits.wiki.actionCompleted.redirect = reportpage;
+ Morebits.wiki.actionCompleted.notice = 'Reporting complete';
- var an3_next = function(free_resolves) {
- if (!resolves.length && !free_resolves && !confirm('You have not selected any edits where you tried to resolve the issue. Do you wish to make the report anyway?')) {
- return;
- }
+ const an3Page = new Morebits.wiki.Page(reportpage, 'Retrieving discussion page');
+ an3Page.setFollowRedirect(true);
+ an3Page.setEditSummary('Adding new report for [[Special:Contributions/' + data.uid + '|' + data.uid + ']].');
+ an3Page.setChangeTags(Twinkle.changeTags);
+ an3Page.setAppendText(data.reportWikitext);
+ an3Page.append();
- const an3Parameters = {
- uid: uid,
- page: form.page.value.trim(),
- comment: form.comment.value.trim(),
- diffs: diffs,
- warnings: warnings,
- resolves: resolves,
- free_resolves: free_resolves
- };
+ // notify user
- Morebits.SimpleWindow.setButtonsEnabled(false);
- Morebits.Status.init(form);
- Twinkle.arv.processAN3(an3Parameters);
- };
-
- if (free_resolves) {
- let query;
- let diff, oldid;
- const specialDiff = /Special:Diff\/(\d+)(?:\/(\S+))?/i.exec(free_resolves);
- if (specialDiff) {
- if (specialDiff[2]) {
- oldid = specialDiff[1];
- diff = specialDiff[2];
- } else {
- diff = specialDiff[1];
- }
- } else {
- diff = mw.util.getParamValue('diff', free_resolves);
- oldid = mw.util.getParamValue('oldid', free_resolves);
- }
- const title = mw.util.getParamValue('title', free_resolves);
- const diffNum = /^\d+$/.test(diff); // used repeatedly
-
- // rvdiffto in prop=revisions is deprecated, but action=compare doesn't return
- // timestamps ([[phab:T247686]]) so we can't rely on it unless necessary.
- // Likewise, we can't rely on a meaningful comment for diff=cur.
- // Additionally, links like Special:Diff/123/next, Special:Diff/123/456, or ?diff=next&oldid=123
- // would each require making use of rvdir=newer in the revisions API.
- // That requires a title parameter, so we have to use compare instead of revisions.
- if (oldid && (diff === 'cur' || (!title && (diff === 'next' || diffNum)))) {
- query = {
- action: 'compare',
- fromrev: oldid,
- prop: 'ids|title',
- format: 'json'
- };
- if (diffNum) {
- query.torev = diff;
- } else {
- query.torelative = diff;
- }
- } else {
- query = {
- action: 'query',
- prop: 'revisions',
- rvprop: 'ids|timestamp|comment',
- format: 'json',
- indexpageids: true
- };
-
- if (diff && oldid) {
- if (diff === 'prev') {
- query.revids = oldid;
- } else {
- query.titles = title;
- query.rvdir = 'newer';
- query.rvstartid = oldid;
-
- if (diff === 'next' && title) {
- query.rvlimit = 2;
- } else if (diffNum) {
- // Diffs may or may not be consecutive, no limit
- query.rvendid = diff;
- }
- }
- } else {
- // diff=next|prev|cur with no oldid
- // Implies title= exists otherwise it's not a valid diff link (well, it is, but to the Main Page)
- if (diff && /^\D+$/.test(diff)) {
- query.titles = title;
- } else {
- query.revids = diff || oldid;
- }
- }
- }
+ const notifyText = '\n\n{{subst:an3-notice|1=' + mw.util.wikiUrlencode(data.uid) + '|auto=1}} ~~~~';
- new mw.Api().get(query).done((data) => {
- let page;
- if (data.compare && data.compare.fromtitle === data.compare.totitle) {
- page = data;
- } else if (data.query) {
- const pageid = data.query.pageids[0];
- page = data.query.pages[pageid];
- } else {
- return;
- }
- an3_next(page);
- }).fail((data) => {
- console.log('API failed :(', data); // eslint-disable-line no-console
- });
- } else {
- an3_next();
- }
+ const talkPage = new Morebits.wiki.Page('User talk:' + data.uid, 'Notifying edit warrior');
+ talkPage.setFollowRedirect(true);
+ talkPage.setEditSummary('Notifying about edit warring noticeboard discussion.');
+ talkPage.setChangeTags(Twinkle.changeTags);
+ talkPage.setAppendText(notifyText);
+ talkPage.append();
+ Morebits.wiki.removeCheckpoint(); // all page updates have been started
+ }).catch((error) => {
+ console.error('Error occurred while preparing AN3 report.', error); // eslint-disable-line no-console
+ alert('Error occurred while preparing AN3 report: ' + error.message);
+ });
break;
}
};
@@ -854,49 +769,171 @@ Twinkle.arv.callback.getSpiReportData = function(input) {
};
};
-Twinkle.arv.processAN3 = function(params) {
- // prepare the AN3 report
- let minid;
- for (let i = 0; i < params.diffs.length; ++i) {
- if (params.diffs[i].parentid && (!minid || params.diffs[i].parentid < minid)) {
- minid = params.diffs[i].parentid;
- }
+Twinkle.arv.callback.getAn3ReportData = function(input) {
+ let data;
+ const confirmations = [];
+
+ const diffs = input.s_diffs ? input.s_diffs.map(JSON.parse) : [];
+
+ if (diffs.length < 3) {
+ confirmations.push('You have selected fewer than three offending edits. Do you wish to make the report anyway?');
}
- new mw.Api().get({
- action: 'query',
- prop: 'revisions',
- format: 'json',
- rvprop: 'sha1|ids|timestamp|comment',
- rvlimit: 100, // intentionally limited
- rvstartid: minid,
- rvexcludeuser: params.uid,
- indexpageids: true,
- titles: params.page
- }).done((data) => {
- Morebits.wiki.addCheckpoint(); // prevent notification events from causing an erronous "action completed"
+ const warnings = input.s_warnings ? input.s_warnings.map(JSON.parse) : [];
+
+ if (!warnings.length) {
+ confirmations.push('You have not selected any edits where you warned the offender. Do you wish to make the report anyway?');
+ }
+
+ const resolves = input.s_resolves ? input.s_resolves.map(JSON.parse) : [];
+ const free_resolves = input.s_resolves_free;
+
+ return new Promise((resolve, reject) => {
+ if (free_resolves) {
+ let query;
+ let diff, oldid;
+ const specialDiff = /Special:Diff\/(\d+)(?:\/(\S+))?/i.exec(free_resolves);
+ if (specialDiff) {
+ if (specialDiff[2]) {
+ oldid = specialDiff[1];
+ diff = specialDiff[2];
+ } else {
+ diff = specialDiff[1];
+ }
+ } else {
+ diff = mw.util.getParamValue('diff', free_resolves);
+ oldid = mw.util.getParamValue('oldid', free_resolves);
+ }
+ const title = mw.util.getParamValue('title', free_resolves);
+ const diffNum = /^\d+$/.test(diff); // used repeatedly
+
+ // rvdiffto in prop=revisions is deprecated, but action=compare doesn't return
+ // timestamps ([[phab:T247686]]) so we can't rely on it unless necessary.
+ // Likewise, we can't rely on a meaningful comment for diff=cur.
+ // Additionally, links like Special:Diff/123/next, Special:Diff/123/456, or ?diff=next&oldid=123
+ // would each require making use of rvdir=newer in the revisions API.
+ // That requires a title parameter, so we have to use compare instead of revisions.
+ if (oldid && (diff === 'cur' || (!title && (diff === 'next' || diffNum)))) {
+ query = {
+ action: 'compare',
+ fromrev: oldid,
+ prop: 'ids|title',
+ format: 'json'
+ };
+ if (diffNum) {
+ query.torev = diff;
+ } else {
+ query.torelative = diff;
+ }
+ } else {
+ query = {
+ action: 'query',
+ prop: 'revisions',
+ rvprop: 'ids|timestamp|comment',
+ format: 'json',
+ indexpageids: true
+ };
+
+ if (diff && oldid) {
+ if (diff === 'prev') {
+ query.revids = oldid;
+ } else {
+ query.titles = title;
+ query.rvdir = 'newer';
+ query.rvstartid = oldid;
+
+ if (diff === 'next' && title) {
+ query.rvlimit = 2;
+ } else if (diffNum) {
+ // Diffs may or may not be consecutive, no limit
+ query.rvendid = diff;
+ }
+ }
+ } else {
+ // diff=next|prev|cur with no oldid
+ // Implies title= exists otherwise it's not a valid diff link (well, it is, but to the Main Page)
+ if (diff && /^\D+$/.test(diff)) {
+ query.titles = title;
+ } else {
+ query.revids = diff || oldid;
+ }
+ }
+ }
+ new mw.Api().get(query).then((queryResponse) => {
+ let page;
+ if (queryResponse.compare && queryResponse.compare.fromtitle === queryResponse.compare.totitle) {
+ page = queryResponse;
+ } else if (queryResponse.query) {
+ const pageIds = queryResponse.query.pageids;
+ if (!Array.isArray(pageIds) || pageIds.length !== 1) reject({ message: 'Error parsing diff.', data: queryResponse });
+ page = queryResponse.query.pages[pageIds[0]];
+ } else {
+ reject({ message: 'Could not find any diff associated with the URL provided.', data: queryResponse });
+ }
+ resolve(page);
+ }).catch((queryResponse) => {
+ reject({ message: 'Call to MediaWiki API failed.', data: queryResponse });
+ });
+ } else {
+ resolve();
+ }
+ }).then((free_resolves_data) => {
+ if (!resolves.length && !free_resolves_data) {
+ confirmations.push('You have not selected any edits where you tried to resolve the issue. Do you wish to make the report anyway?');
+ }
+
+ data = {
+ uid: input.uid,
+ page: input.page,
+ comment: input.comment,
+ diffs: diffs,
+ warnings: warnings,
+ resolves: resolves,
+ free_resolves: free_resolves_data,
+ confirmations: confirmations
+ };
+
+ let minid;
+ for (let i = 0; i < data.diffs.length; ++i) {
+ if (data.diffs[i].parentid && (!minid || data.diffs[i].parentid < minid)) {
+ minid = data.diffs[i].parentid;
+ }
+ }
+
+ return new mw.Api().get({
+ action: 'query',
+ prop: 'revisions',
+ format: 'json',
+ rvprop: 'sha1|ids|timestamp|comment',
+ rvlimit: 100, // intentionally limited
+ rvstartid: minid,
+ rvexcludeuser: data.uid,
+ indexpageids: true,
+ titles: data.page
+ }).catch((queryResponse) => Promise.reject({ message: 'Call to MediaWiki API failed.', data: queryResponse }));
+ }).then((queryResponse) => {
// In case an edit summary was revdel'd
const hasHiddenComment = function(rev) {
if (!rev.comment && typeof rev.commenthidden === 'string') {
return '(comment hidden)';
}
- return '"' + rev.comment + '"';
-
+ // swap curly braces for HTML entities to avoid templates being rendered if they were included in an edit summary
+ return '"' + rev.comment.replace(/\{/g, '{').replace(/\}/g, '}') + '"';
};
let orig;
- if (data.length) {
- const sha1 = data[0].sha1;
- for (let i = 1; i < data.length; ++i) {
- if (data[i].sha1 === sha1) {
- orig = data[i];
+ if (queryResponse.length) {
+ const sha1 = queryResponse[0].sha1;
+ for (let i = 1; i < queryResponse.length; ++i) {
+ if (queryResponse[i].sha1 === sha1) {
+ orig = queryResponse[i];
break;
}
}
if (!orig) {
- orig = data[0];
+ orig = queryResponse[0];
}
}
@@ -908,8 +945,8 @@ Twinkle.arv.processAN3 = function(params) {
const grouped_diffs = {};
let parentid, lastid;
- for (let j = 0; j < params.diffs.length; ++j) {
- const cur = params.diffs[j];
+ for (let j = 0; j < data.diffs.length; ++j) {
+ const cur = data.diffs[j];
if ((cur.revid && cur.revid !== parentid) || lastid === null) {
lastid = cur.revid;
grouped_diffs[lastid] = [];
@@ -927,21 +964,23 @@ Twinkle.arv.processAN3 = function(params) {
ret = '# {{diff|oldid=' + first.parentid + '|diff=' + last.revid + '|label=' + label + '}}\n';
}
ret += sub.reverse().map((v) => (sub.length >= 2 ? '#' : '') + '# {{diff2|' + v.revid + '|' + new Morebits.Date(v.timestamp).format('HH:mm, D MMMM YYYY', 'utc') + ' (UTC)}} ' + hasHiddenComment(v)).join('\n');
+
return ret;
}).reverse().join('\n');
- const warningtext = params.warnings.reverse().map((v) => '# {{diff2|' + v.revid + '|' + new Morebits.Date(v.timestamp).format('HH:mm, D MMMM YYYY', 'utc') + ' (UTC)}} ' + hasHiddenComment(v)).join('\n');
- let resolvetext = params.resolves.reverse().map((v) => '# {{diff2|' + v.revid + '|' + new Morebits.Date(v.timestamp).format('HH:mm, D MMMM YYYY', 'utc') + ' (UTC)}} ' + hasHiddenComment(v)).join('\n');
+ const warningtext = data.warnings.reverse().map((v) => '# {{diff2|' + v.revid + '|' + new Morebits.Date(v.timestamp).format('HH:mm, D MMMM YYYY', 'utc') + ' (UTC)}} ' + hasHiddenComment(v)).join('\n');
- if (params.free_resolves) {
- const page = params.free_resolves;
+ let resolvetext = data.resolves.reverse().map((v) => '# {{diff2|' + v.revid + '|' + new Morebits.Date(v.timestamp).format('HH:mm, D MMMM YYYY', 'utc') + ' (UTC)}} ' + hasHiddenComment(v)).join('\n');
+
+ if (data.free_resolves) {
+ const page = data.free_resolves;
if (page.compare) {
- resolvetext += '\n# {{diff|oldid=' + page.compare.fromrevid + '|diff=' + page.compare.torevid + '|label=Consecutive edits on ' + page.compare.totitle + '}}';
+ resolvetext += '\n# {{diff|oldid=' + page.compare.fromrevid + '|diff=' + page.compare.torevid + '|label=Consecutive edits on ' + page.compare.totitle + '}}';
} else if (page.revisions) {
const revCount = page.revisions.length;
let rev;
if (revCount < 3) { // diff=prev or next
rev = revCount === 1 ? page.revisions[0] : page.revisions[1];
- resolvetext += '\n# {{diff2|' + rev.revid + '|' + new Morebits.Date(rev.timestamp).format('HH:mm, D MMMM YYYY', 'utc') + ' (UTC) on ' + page.title + '}} ' + hasHiddenComment(rev);
+ resolvetext += '\n# {{diff2|' + rev.revid + '|' + new Morebits.Date(rev.timestamp).format('HH:mm, D MMMM YYYY', 'utc') + ' (UTC) on ' + page.title + '}} ' + hasHiddenComment(rev);
} else { // diff and oldid are nonconsecutive
rev = page.revisions[0];
const revLatest = page.revisions[revCount - 1];
@@ -951,39 +990,25 @@ Twinkle.arv.processAN3 = function(params) {
}
}
- let comment = params.comment.replace(/~*$/g, '').trim();
+ let comment = data.comment.replace(/~*$/g, '').trim();
if (comment) {
comment += ' ~~~~';
}
- const text = '\n\n{{subst:AN3 report|diffs=' + difftext + '|warnings=' + warningtext + '|resolves=' + resolvetext + '|pagename=' + params.page + '|orig=' + origtext + '|comment=' + comment + '|uid=' + params.uid + '}}';
-
- const reportpage = 'Wikipedia:Administrators\' noticeboard/Edit warring';
+ const reportWikitext = '\n\n{{subst:AN3 report|diffs=' + difftext + '|warnings=' + warningtext + '|resolves=' + resolvetext + '|pagename=' + data.page + '|orig=' + origtext + '|comment=' + comment + '|uid=' + data.uid + '}}';
- Morebits.wiki.actionCompleted.redirect = reportpage;
- Morebits.wiki.actionCompleted.notice = 'Reporting complete';
-
- const an3Page = new Morebits.wiki.Page(reportpage, 'Retrieving discussion page');
- an3Page.setFollowRedirect(true);
- an3Page.setEditSummary('Adding new report for [[Special:Contributions/' + params.uid + '|' + params.uid + ']].');
- an3Page.setChangeTags(Twinkle.changeTags);
- an3Page.setAppendText(text);
- an3Page.append();
-
- // notify user
-
- const notifyText = '\n\n{{subst:an3-notice|1=' + mw.util.wikiUrlencode(params.uid) + '|auto=1}} ~~~~';
+ return {
+ uid: data.uid,
+ reportWikitext: reportWikitext,
+ confirmations: data.confirmations
+ };
+ }).catch((errorData) => {
+ if (typeof errorData !== 'object' || !Object.prototype.hasOwnProperty.call(errorData, 'message')) {
+ return Promise.reject({ message: 'An unknown error occurred while generating the report.', data: errorData});
+ }
- const talkPage = new Morebits.wiki.Page('User talk:' + params.uid, 'Notifying edit warrior');
- talkPage.setFollowRedirect(true);
- talkPage.setEditSummary('Notifying about edit warring noticeboard discussion.');
- talkPage.setChangeTags(Twinkle.changeTags);
- talkPage.setAppendText(notifyText);
- talkPage.append();
- Morebits.wiki.removeCheckpoint(); // all page updates have been started
- }).fail((data) => {
- console.log('API failed :(', data); // eslint-disable-line no-console
+ return Promise.reject(errorData);
});
};