User:Terasail/COI Request Tool.js

/*

COI Request Tool

Created by: Terasail

  • /

var nonResponseCOI = [

{label: "Close", title: "Close request", summary: "Closed edit request", parameter: "answered=yes", response: "", icon: "unFlag", flags: "", text: ""},

{label: "Open", title: "Reopen request", summary: "Reopened edit request", parameter: "answered=no", response: "", icon: "flag", flags: "", text: ""},

{label: "Remove", title: "Remove entire section", summary: "Removed COI request", parameter: "", response: "", icon: "trash", flags: ["primary", "destructive"], text: ""}

];

var responseCOI = [

{label: "Done", title: "Mark request as done", summary: "Marked COI request as done", parameter: "answered=yes", response: "d", icon: "checkAll", flags: ["primary", "progressive"], text: "Done"},

{label: "Partly done", title: "Mark request as partly done", summary: "Marked COI request as partly done", parameter: "P", response: "pd", icon: "check", flags: "", text: "Partly done:"},

{label: "Already done", title: "Mark request as already done", summary: "Marked COI request as already done", parameter: "answered=yes", response: "a", icon: "clock", flags: "", text: "Already done:"},

{label: "Note", title: "Add a note", summary: "Added a note", parameter: "", response: "note", icon: "ellipsis", flags: "", text: "Note:"},

{label: "Question", title: "Add a question", summary: "Added a question", parameter: "", response: "q", icon: "helpNotice", flags: "", text: "Question:"},

{label: "Go ahead", title: "Go ahead", summary: "User may go ahead and edit themselves", parameter: "G", response: "g", icon: "edit", flags: "", text: "Go ahead: I have reviewed these proposed changes and suggest that you go ahead and make the proposed changes to the page."},

{label: "Not done", title: "Decline request", summary: "Declined COI request", parameter: "D", response: "n", icon: "notice", flags: "", text: "Not done:"},

{label: "Not done for now", title: "Decline request for now", summary: "Declined request for now", parameter: "D", response: "nfn", icon: "notice", flags: "", text: "Not done for now:"},

{label: "Promotional", title: "Decline promotional request", summary: "Declined promotional request", parameter: "D|ADV", response: "mpro", icon: "signature", flags: "", text: "Not done: A majority of the requested changes are currently written in a promotional tone. Please review WP:Neutral point of view and ensure you follow this before submitting any edit requests."},

{label: "No consensus", title: "No consensus for the change", summary: "Declined request with no consensus for the change", parameter: "D|C", response: "nc", icon: "userGroup", flags: "", text: "Not done: No consensus could be obtained for making the requested change."},

{label: "Needs reliable sources", title: "Close request pending reliable sources", summary: "COI request declined: Change requires reliable sources", parameter: "D|V", response: "rs", icon: "quotes", flags: "", text: "Not done: please provide reliable sources that support the change you want to be made."},

{label: "Removing content", title: "Decline request removing well-cited content", summary: "Declined request removing well-cited content", parameter: "D|R", response: "rm", icon: "restore", flags: "", text: "Not done: The proposed changes are removing content that is well-cited or where sources exist."},

{label: "Partly promotional", title: "Decline partly promotional request for now", summary: "Declined partly promotional request for now", parameter: "D|ADV", response: "pro", icon: "signature", flags: "", text: "Not done for now: Some of the requested changes are currently written in a promotional tone. Please review WP:Neutral point of view and make changes where appropriate to follow this before reopening the request."},

{label: "Needs consensus", title: "Close request pending consensus", summary: "COI request declined: Change requires consensus first", parameter: "D|D", response: "c", icon: "userGroup", flags: "", text: "Please establish a consensus with editors engaged in the subject area before using the {{Edit COI}} template for this proposed change."},

{label: "Unclear request", title: "Decline and mark as unclear", summary: "COI request closed as it is unclear what change is requested", parameter: "D|Unclear request", response: "xy", icon: "helpNotice", flags: "", text: "it's not clear what changes you want to be made. Please mention the specific changes in a \"change X to Y\" format."},

{label: "Unspecific", title: "Decline unspecific request", summary: "Declined unspecific request", parameter: "D|S", response: "s", icon: "speechBubbles", flags: "", text: "Not done for now: The current request is not specific enough to make changes to the page. Consider developing changes in a new talk section or visit the conflict of interest noticeboard for serious issues."},

{label: "Balance issues", title: "Decline request with balance issues", summary: "Declined request with balance issues", parameter: "D|O", response: "b", icon: "notice", flags: "", text: "Not done for now: The proposed changes create some balance issues with the article. These will need to be addressed before any changes can be made."},

{label: "Partly undo", title: "Partly undo request", summary: "COI request has been partly undone", parameter: "P|The requested edit has been partially undone", response: "udp", icon: "undo", flags: "", text: "Undone: This request has been partially undone."},

{label: "Undo", title: "Undo request", summary: "COI request has been undone", parameter: "D|The requested edit has been undone", response: "ud", icon: "undo", flags: "", text: "Undone: This request has been undone."}

];

var editRequests = $('.editrequest');

var COIRequests = [];

for (let i = 0; i < editRequests.length; i++) {

if (typeof(editRequests[i].attributes['data-origlevel']) == 'undefined') {

$(editRequests[i].children[0]).append('');

COIRequests.push(editRequests[i]);

}

}

if (COIRequests.length > 0) {

mw.loader.using(["oojs-ui-core", "oojs-ui-widgets", "oojs-ui-windows"]).done(function() {

mw.loader.load(["oojs-ui.styles.icons-alerts", "oojs-ui.styles.icons-interactions", "oojs-ui.styles.icons-moderation", "oojs-ui.styles.icons-editing-core", "oojs-ui.styles.icons-editing-advanced", "oojs-ui.styles.icons-user"]);

loadCOITool();

});

}

async function loadCOITool() {

// Get page watchers, visitors and user watch status.

let watchStatus = [];

let watchQuery = await ApiGetCOI({

action: "query",

prop: "info",

pageids: mw.config.get("wgArticleId"),

inprop: "watchers|visitingwatchers|watched",

format: "json"

});

let watchData = watchQuery.query.pages[mw.config.get("wgArticleId")];

let watched = watchData.watched;

let expiry = watchData.watchlistexpiry;

if (expiry) {

watched = Math.ceil((new Date(expiry).getTime() - Date.now()) / 1000 / 60 / 60 / 24) + " days";

}

if (watched == undefined && typeof(autoWatchRequests) != "undefined" && autoWatchRequests == true) {

watched = '';

}

watchStatus.push(watchData.watchers || "less than 30", watchData.visitingwatchers || "<30", watched);

//Increment through all COI requests & add respond button

for (let i = 0; i < COIRequests.length; i++) {

let respondButton = new OO.ui.ButtonWidget({

icon: "edit",

label: "Respond",

flags: "progressive",

title: "Open the response menu for this request"

}).on("click", function() {

loadCOIResponse(COIRequests[i], respondButton, watchStatus);

respondButton.setDisabled(true);

});

respondButton.$element[0].style = "margin:5px";

$($('.response-cell')[i]).append(respondButton.$element);

}

}

function loadCOIResponse(COIRequest, respondButton, watchStatus) {

let responseBoxHTML = '

There are currently ' + watchStatus[0] + ' users watching this page (' + watchStatus[1] + ' have viewed recent edits).
Quick options:
Custom response:
';

$(responseBoxHTML).insertAfter(COIRequest, respondButton);

let responseBox = COIRequest.nextElementSibling;

let responseQuick = $(responseBox).find('.response-quick')[0];

let responseCustom = $(responseBox).find('.response-custom')[0];

let responsePreview = $(responseBox).find('.response-preview')[0];

let responseControls = $(responseBox).find('.response-controls')[0];

//Quick Responses

//Create a HorizontalLayout & Fieldset for quick responses

let quickLayout = new OO.ui.HorizontalLayout();

let quickFieldset = new OO.ui.FieldsetLayout();

quickFieldset.addItems([new OO.ui.FieldLayout(new OO.ui.Widget({content: [quickLayout]}), {align: 'top'})]);

$(responseQuick).append(quickFieldset.$element);

let quickNonResponses = [2];//Remove button

if ($(COIRequest).find('hr').length > 0) {//If request is closed

quickNonResponses.push(0);//Close button

} else {

quickNonResponses.push(1);//Open button

}

for (let i = 0; i < quickNonResponses.length; i++) {

let tempVal = quickNonResponses[i];

let tempButton = new OO.ui.ButtonWidget({

flags: nonResponseCOI[tempVal].flags,

icon: nonResponseCOI[tempVal].icon,

title: nonResponseCOI[tempVal].title,

invisibleLabel: true

}).on("click", function () {

saveResponseCOI([COIRequest, responseQuick, responsePreview, responseControls], nonResponseCOI[tempVal], "", "nochange", undefined);

});

quickLayout.addItems([tempButton]);

}

let quickResponses = [0, 5, 8, 10];//Done, Go ahead, Consensus, Unclear

for (let i = 0; i < quickResponses.length; i++) {

let tempVal = quickResponses[i];

let tempButton = new OO.ui.ButtonWidget({

flags: responseCOI[tempVal].flags,

label: responseCOI[tempVal].label,

title: responseCOI[tempVal].title

}).on("click", function () {

saveResponseCOI([COIRequest, responseQuick, responsePreview, responseControls], responseCOI[tempVal], "", "nochange", undefined);

});

quickLayout.addItems([tempButton]);

}

//Custom Responses

//Response dropdown

let responseDropdown = new OO.ui.DropdownWidget({

label: "Select reply option - Add additional text below",

menu: {items: []}

}).on("labelChange", function () {

submitButton.setDisabled(false);

previewCOI(responseDropdown.menu.findSelectedItem().getData(), responseText.value, responsePreview);

});

for (let i = 0; i < responseCOI.length; i++) {

let tempWidget = new OO.ui.MenuOptionWidget({

label: responseCOI[i].text,

icon: responseCOI[i].icon,

data: responseCOI[i]

});

responseDropdown.menu.addItems([tempWidget]);

}

responseDropdown.$element[0].style = "margin:auto; text-align:left;";

$(responseCustom).append(responseDropdown.$element);

//Response text

var responseText = new OO.ui.MultilineTextInputWidget({

autosize: true, rows: 4, label: "Additional text"

}).on("change", function () {

if (responseDropdown.menu.findSelectedItem()) {

previewCOI(responseDropdown.menu.findSelectedItem().getData(), responseText.value, responsePreview);

}

});

responseText.$element[0].style = "margin:5px auto;";

$(responseCustom).append(responseText.$element);

//Response Controls

//Create a HorizontalLayout & Fieldset for response controls

let controlsLayout = new OO.ui.HorizontalLayout();

let controlsFieldset = new OO.ui.FieldsetLayout();

controlsFieldset.addItems([new OO.ui.FieldLayout(new OO.ui.Widget({content: [controlsLayout]}), {align: 'top'})]);

$(responseControls).append(controlsFieldset.$element);

//Cancel Button

let cancelButton = new OO.ui.ButtonWidget({

icon: "cancel",

flags: "destructive",

label: "Cancel",

framed: false,

title: "Cancel the response & close this menu"

}).on("click", function () {

respondButton.setDisabled(false);

responseBox.remove();

});

controlsLayout.addItems([cancelButton]);

//Watchlist dropdown

let watchOptions = [{data: "infinite", label: "Permanent"}, {data: "1 day", label: "1 day"}, {data: "3 days", label: "3 days"}, {data: "1 week", label: "1 week"}, {data: "1 month", label: "1 month"}];

let watchValue = "infinite";

if (!!watchStatus[2]) {

watchOptions.unshift({data: "nochange", label: watchStatus[2]});

watchValue = "nochange";

}

let watchlistLayout = new OO.ui.HorizontalLayout();

let watchlistDropdown = new OO.ui.DropdownInputWidget({

value: watchValue,

options: watchOptions,

disabled: (watchStatus[2] == undefined)

});

watchlistLayout.addItems([watchlistDropdown]);

//Watchlist checkbox & label

let watchlistCheckbox = new OO.ui.CheckboxInputWidget({

selected: (watchStatus[2] != undefined)

}).on("change", function (newStatus) {

watchlistDropdown.setDisabled(!newStatus);

});

let watchlistLabel = new OO.ui.LabelWidget({label: "Watch this page"});

//Submit Button

let submitButton = new OO.ui.ButtonWidget({

icon: "checkAll",

flags: ["primary", "progressive"],

label: "Submit",

title: "Submit the response",

disabled: true

}).on("click", function () {

saveResponseCOI([COIRequest, responseQuick, responsePreview, responseControls], responseDropdown.menu.findSelectedItem().getData(), responseText.value, watchlistCheckbox.selected, watchlistDropdown.value);

});

controlsLayout.addItems([cancelButton, watchlistCheckbox, watchlistLabel, watchlistLayout, submitButton]);

}

function previewCOI(responseOption, responseText, tableCell) {

let restTransform = "https://en.wikipedia.org/api/rest_v1/transform/wikitext/to/html/" + encodeURI(mw.config.get("wgPageName"));

if (responseOption.response != "") {

responseText = "{{ECOI|" + responseOption.response + "}} " + responseText;

}

if (responseText != "") {

let nickname = " " + mw.user.options.values.nickname;

if (nickname == " ") {//Create default signature if no nickname

nickname = mw.user.getName();

nickname = " " + nickname + " (talk)";

}

let dateObj = new Date();

let dateNow = dateObj.toLocaleDateString('en-GB', {

timeZone: 'UTC',

year: 'numeric',

month: 'long',

day: 'numeric'

});

let timeNow = dateObj.toLocaleTimeString('en-GB', {timeZone: 'UTC', hour: '2-digit', minute: '2-digit'});

responseText = responseText + nickname + " " + timeNow + ", " + dateNow + " (UTC)";

responseText = responseText.replaceAll(/{{subst:/gi, "{{");

responseText = responseText.replaceAll(/\s*~~~~\s*/g, "");

$.post(restTransform, 'wikitext=' + encodeURIComponent(responseText) + '&body_only=true',

function (html) {

tableCell.style = "padding:8px 1em 2px;";

tableCell.children[1].innerHTML = html;

}

);

} else {

tableCell.style = "display:none;";

}

}

async function saveResponseCOI(requestBox, responseOption, responseText, watchPage, watchValue) {

await new Promise(function(resolve) {

OO.ui.confirm("Confirm in order to reply to this edit request.").done(function(confirmed) { if (confirmed) {

resolve();

} else {

return;

}});

});

//Create label box & remove quick actions

requestBox[1].innerHTML = "";

requestBox[3].remove();

let statusMessage = new OO.ui.MessageWidget({

icon: 'pageSettings',

type: 'notice',

label: 'Processing request — Edit request starting, getting section data to edit.'

});

statusMessage.$element[0].style = "margin:5px 0; max-width:50em";

$(requestBox[1]).append(statusMessage.$element);

//Create progress bar

let progressBar = new OO.ui.ProgressBarWidget({

progress: false

});

$(requestBox[1]).append(progressBar.$element);

//Set preview for output

previewCOI(responseOption, responseText, requestBox[2]);

// Find header

let header = "";

let sectionIndex = 0;

let tempElement = requestBox[0];

let sectionQuery = await ApiGetCOI({

action: "parse",

page: mw.config.get("wgPageName"),

prop: "sections"

});

let sections = sectionQuery.parse.sections;

do {

tempElement = tempElement.previousElementSibling;

if (tempElement.classList.contains("mw-heading")) {

if (tempElement.parentElement.tagName == "SECTION") { //Need to support both while new parser is being implemented

header = $(tempElement).find("h1,h2,h3,h4,h5,h6")[0].id;

sectionIndex = parseInt(tempElement.parentElement.dataset.mwSectionId);

} else {

if (tempElement.getElementsByClassName("mw-headline").length > 0) { //Vector 2022

header = tempElement.getElementsByClassName("mw-headline")[0].id;

} else { //Vector Legacy

header = $(tempElement).find("h1,h2,h3,h4,h5,h6")[0].id;

}

for (let i = 0; i < sections.length; i++) {

if (sections[i].anchor == header) {

sectionIndex = parseInt(sections[i].index);

}

}

}

}

}

while (header == "");

statusMessage.setLabel("Processing request — Making changes to the edit request");

let editSummary = "/* " + header.replaceAll("_", " ") + " */ " + responseOption.summary + " (COI Request Tool)";

let wikitextQuery = await ApiGetCOI({

action: "parse",

page: mw.config.get("wgPageName"),

section: sectionIndex,

prop: "wikitext|revid"

});

let wikitext = wikitextQuery.parse.wikitext["*"];

let latestRevision = wikitextQuery.parse.revid;

if (responseOption.parameter != "") {

let template = "{{Edit COI|" + responseOption.parameter + "}}";

wikitext = wikitext.replace(/{{ *(Edit[ _])?COI(-protected|([ _](edit|request)){2})?( *\| *([=A-Z])*)* *}}/i, template);

}

if (responseOption.response != "") {

wikitext += "\n:{{subst:ECOI|" + responseOption.response + "}}";

wikitext += responseText.replaceAll(/\s*~~~~\s*/g, "") + " ~~~~";

}

if (responseOption.label == "Remove") {

wikitext = "";

editSummary = editSummary.replace(/[^]+\*\/ /, "");

}

statusMessage.setType("success");

statusMessage.setLabel("Processing request — Saving changes to the talk page.");

if (latestRevision != mw.config.values.wgRevisionId) {

await new Promise(function(resolve) {

OO.ui.confirm("There has been a new revision to the page, do you wish to continue?").done(function(confirmed) { if (confirmed) {

resolve();

} else {

return;

}});

});

}

if (watchPage) {

if (watchPage != "nochange") {

watchPage = "watch";

}

} else {

watchPage = "unwatch";

}

let apiParams = {

action: 'edit',

title: mw.config.get("wgPageName"),

text: wikitext,

section: sectionIndex,

summary: editSummary,

watchlist: watchPage

};

if (watchPage == "watch") {

apiParams.watchlistexpiry = watchValue;

}

let reloadURL = "/w/index.php?title=" + encodeURI(mw.config.get("wgPageName")) + "&type=revision&diff=cur&oldid=prev";

new mw.Api().postWithEditToken(apiParams).done(function () {

window.location = reloadURL;

});

}

function ApiGetCOI(params) {

return new Promise(function(resolve) {

new mw.Api().get(params)

.done(function (data) {resolve(data);})

.fail(function (data) {console.error(data);});

});

}

//Category:Wikipedia scripts