Jump to content

User:Terasail/Edit Request Tool.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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>
	Edit Request Tool
	Created by: Terasail
*/
var dataERT;
var userSignauteERT = "";
var editRequestBoxes = $('.editrequest');
var editRequests = [];
for (let i = 0; i < editRequestBoxes.length; i++) {
	if (typeof(editRequestBoxes[i].attributes['data-origlevel']) != 'undefined') {
		if (editRequestBoxes[i].id == "") {
			$(editRequestBoxes[i].children[0].children[0].children[0]).append('<div class="response-cell-ert" style="text-align:center;"></div>');
		} else {
			$(editRequestBoxes[i].children[0]).append('<tr><td colspan="2" class="response-cell-ert" style="text-align:center;"></td></tr>');
		}
		editRequests.push(editRequestBoxes[i]);
	}
}

if (editRequests.length > 0) {
	mw.loader.using(["oojs-ui-core", "oojs-ui-widgets", "oojs-ui-windows"]).done(function() {
		mw.loader.load(["oojs-ui.styles.icons-interactions", "oojs-ui.styles.icons-moderation", "oojs-ui.styles.icons-user", "oojs-ui.styles.icons-content", "oojs-ui.styles.icons-editing-core", "oojs-ui.styles.icons-editing-advanced"]);
		loadERTool();
		$.getJSON("https://en.wikipedia.org/w/index.php?title=User:Terasail/Edit_Request_Tool.json&action=raw&ctype=text/json", function (newData) {
			dataERT = newData;
		});
		ApiGetERT({
			action: "parse",
			title: mw.config.get("wgPageName"),
			text: "~~~~",
			pst: "true",
			disablelimitreport: "true",
			disableeditsection: "true",
			preview: "true"
		}).then(function(data){
			userSignauteERT = data.parse.text["*"].replaceAll(/([^]+(?=<p>)<p>|<\/div>)/g, " ");
		});
	});
}

async function loadERTool() {
	// Get page watchers, visitors and user watch status.
	let watchStatus = [];
	let watchQuery = await ApiGetERT({
		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";
	}
	watchStatus.push(watchData.watchers || "less than 30", watchData.visitingwatchers || "<30", watched);
	//Increment through all edit requests & add respond button
	for (let i = 0; i < editRequests.length; i++) {
		let responseCell = $('.response-cell-ert')[i];
		let smallButton = false;
		if (responseCell.tagName == "DIV") {
			smallButton = true;
		}
		let respondButton = new OO.ui.ButtonWidget({
			icon: "edit",
			label: "Respond",
			flags: "progressive",
			title: "Open the response menu for this request",
			invisibleLabel: smallButton,
		}).on("click", function() {
			loadERTResponse(editRequests[i], respondButton, watchStatus);
			respondButton.setDisabled(true);
		});
		respondButton.$element[0].style = "margin:5px";
		$(responseCell).append(respondButton.$element);
	}
}

function loadERTResponse(editRequest, respondButton, watchStatus) {
	let boxType = editRequest.dataset.origlevel;
	boxType = boxType.replace("full", "fully");
	$('<table style="border:1px solid #A2A9B1; border-radius:2px; padding:10px 16px 0; margin:auto; max-width:55em; width:100%; clear:both;"><tr><td><div style="font-style:italic; margin-left:1em;">There are currently ' + watchStatus[0] + ' users watching this page (' + watchStatus[1] + ' have viewed recent edits).</div><div>Quick options:</div></td></tr><tr style="display: flex; justify-content: center;"><td class="response-quick"></td></tr><tr><td>Custom response:</td></tr><tr style="text-align:center;"><td class="response-custom"></td></tr><tr style="background:#F6F6F6;"><td class="response-preview" style="display:none;"><div>Preview:</div><div></div></td></tr><tr style="display: flex; justify-content: right;"><td class="response-controls"></td></tr></table>').insertAfter(editRequest);
	let responseBox = editRequest.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];
	let protections = Object.entries(dataERT.protections);
	let responses = Object.entries(dataERT.response);
	let quickResponses = Object.entries(dataERT.quickResponse);
	let nonResponses = dataERT.nonResponse;
	//Create type change dropdown
	let tcOptions = [];
	for (let i = 0; i < protections.length; i++) {
		tcOptions.push({data: protections[i][0], label: protections[i][1][0]});
	}
	let typeChange = new OO.ui.DropdownInputWidget({
		value: boxType,
		options: tcOptions
	});
	typeChange.on("change", function () {
		submitB.setDisabled(false);
	});
	typeChange.$element[0].style = "text-align:left; margin:auto";
	$(responseCustom).append(typeChange.$element);
	//Create target page list
	let boxLinks = editRequest.getElementsByClassName("mbox-text")[0].getElementsByClassName("external text");
	let pageTargets = [];
	if (boxLinks.length > 0) {//Open request
		for (let c1 = 0; c1 < boxLinks.length; c1++) {
			if (boxLinks[c1].parentElement.tagName == "LI" || boxLinks[c1].parentElement.tagName == "B") {
				pageTargets[pageTargets.length] = boxLinks[c1].innerHTML;
			}
		}
	} else {//Closed request
		boxLinks = editRequest.getElementsByClassName("mbox-text")[0].getElementsByTagName("A");
		for (let c2 = 1; c2 < boxLinks.length; c2++) {
			pageTargets[pageTargets.length] = boxLinks[c2].title;
		}
		if (pageTargets.length == 0) {
			pageTargets = mw.config.get("wgPageName").replace(/(_talk|Talk:)/,"").replaceAll("_", " ");
		}
	}
	let targetPages = new OO.ui.TagMultiselectWidget({
		placeholder: 'Target Pages',
		allowArbitrary: true,
		selected: pageTargets
	});
	targetPages.on("change", function () {
		submitB.setDisabled(false);
	});
	targetPages.$element[0].style = "text-align:left; margin:5px auto";
	$(responseCustom).append(targetPages.$element);
	//Create dropdown menu
	let dropMenu = new OO.ui.DropdownWidget({
		label: "Select reply option - Add additional text below",
		menu: {items: []}
	});
	for (let count = 0; count < responses.length; count++) {
		let newOption = new OO.ui.MenuOptionWidget({
			label: responses[count][0],
			icon: responses[count][1][1]
		});
		dropMenu.menu.addItems([newOption]);
	}
	responses = dataERT.response;
	dropMenu.$element[0].style = "text-align:left; margin:0px";
	$(responseCustom).append(dropMenu.$element);
	dropMenu.on("labelChange", function () {
		submitB.setDisabled(false);
		previewERT(inputText, responses[dropMenu.getLabel()], responsePreview, typeChange.value);
	});
	//Create input box
	let inputText = new OO.ui.MultilineTextInputWidget({autosize: true, rows: 4, label: "Additional text"});
	inputText.$element[0].style = "margin:5px auto";
	$(responseCustom).append(inputText.$element);
	inputText.on("change", function (newText) {
		previewERT(inputText, responses[dropMenu.getLabel()], responsePreview, typeChange.value);
	});
	//Create top horizontal layout
	let hzLayoutT = new OO.ui.HorizontalLayout();
	//Create firstrow fieldset
	let fieldsetT = new OO.ui.FieldsetLayout();
	fieldsetT.addItems([new OO.ui.FieldLayout(new OO.ui.Widget({content: [hzLayoutT]}), {align: 'top'})]);
	$(responseQuick).append(fieldsetT.$element);
	//Remove button
	let remove = new OO.ui.ButtonWidget({
		icon: "trash",
		flags: ["primary", "destructive"],
		invisibleLabel: true,
		title: "Remove the section!"
	});
	remove.on("click", function () {
		$(responseBox).find("tr").each(function(_, row) {
			if ($(row).find('.response-quick').length == 0) {
				row.remove();
			}
		});
		hzLayoutT.clearItems();
		//Create deletion options
		let remSec = new OO.ui.ButtonWidget({//RemoveSection
			icon: "trash",
			flags: ["primary", "destructive"],
			label: "Remove section",
			title: "Remove the entire section!"
		});
		remSec.on("click", function () {
			saveResponseERT([editRequest, responseQuick, responsePreview, responseControls], nonResponses.Remove, "", null, typeChange.defaultValue, targetPages.getValue(), "nochange", "");
		});
		hzLayoutT.addItems([remSec]);
		hzLayoutT.addItems([cancelB]);
	});
	hzLayoutT.addItems([remove]);
	//Open & Close button
	if (editRequest.attributes[2].localName != "data-origlevel") {
		let closeB = new OO.ui.ButtonWidget({
			icon: "unFlag",
			invisibleLabel: true,
			title: "Mark as answered"
		});
		closeB.on("click", function () {
			saveResponseERT([editRequest, responseQuick, responsePreview, responseControls], nonResponses.Close, "", true, typeChange.defaultValue, targetPages.getValue(), "nochange", "");
		});
		hzLayoutT.addItems([closeB]);
	} else {
		let openB = new OO.ui.ButtonWidget({
			icon: "flag",
			invisibleLabel: true,
			title: "Mark as unanswered"
		});
		openB.on("click", function () {
			saveResponseERT([editRequest, responseQuick, responsePreview, responseControls], nonResponses.Open, "", false, typeChange.defaultValue, targetPages.getValue(), "nochange", "");
		});
		hzLayoutT.addItems([openB]);
	}
	//Create quick response buttons
	for (let i = 0; i < quickResponses.length; i++) {
		let newButton = new OO.ui.ButtonWidget({
			label: quickResponses[i][1][0],
			flags: quickResponses[i][1][1],
			title: quickResponses[i][1][2]
		});
		newButton.on("click", function () {
			saveResponseERT([editRequest, responseQuick, responsePreview, responseControls], responses[quickResponses[i][0]], "", toggleAns.selected, typeChange.defaultValue, targetPages.getValue(), "nochange", "");
		});
		hzLayoutT.addItems([newButton]);
	}
	//Toggle answer button
	let toggleAns = new OO.ui.CheckboxInputWidget({selected: true});
	hzLayoutT.addItems([toggleAns, new OO.ui.LabelWidget({label: "Answered"})]);
	//Create lastrow horizontal layout
	let hzLayoutB = new OO.ui.HorizontalLayout();
	//Create lastrow fieldset
	let fieldsetB = new OO.ui.FieldsetLayout();
	fieldsetB.addItems([new OO.ui.FieldLayout(new OO.ui.Widget({content: [hzLayoutB]}), {align: 'top'})]);
	$(responseControls).append(fieldsetB.$element);
	//Cancel response button
	let cancelB = new OO.ui.ButtonWidget({
		icon: "cancel",
		flags: ["destructive"],
		label: "Cancel",
		framed: false,
		title: "Cancel the response & close menu"
	});
	cancelB.on("click", function () {
		respondButton.setDisabled(false);
		responseBox.remove();
	});
	hzLayoutB.addItems([cancelB]);
	//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"}).on("change", function (newStatus) {

	});
	hzLayoutB.addItems([watchlistCheckbox, watchlistLabel, watchlistLayout]);
	//Submit response button
	let submitB = new OO.ui.ButtonWidget({
		icon: "checkAll",
		flags: ["primary", "progressive"],
		label: "Submit",
		title: "Submit the response",
		disabled: true
	});
	submitB.on("click", function () {
		let newResponse = responses[dropMenu.getLabel()];
		let newText = inputText.value;
		if (typeof(newResponse) == "undefined") {
			newText = "";
			newResponse = nonResponses.ChangeLevel; //Assume that it is a template change
			let newPageTargets = []; 
			targetPages.items.forEach(function(item) {
				newPageTargets.push(item.label);
			});
			if (pageTargets.toString() != newPageTargets.toString()) { //Check if the page targets were changed instead
				newResponse = nonResponses.ChangeTarget;
			}
		}
		saveResponseERT([editRequest, responseQuick, responsePreview, responseControls], newResponse, newText, toggleAns.selected, typeChange.value, targetPages.getValue(), watchlistCheckbox.selected, watchlistDropdown.value);
	});
	hzLayoutB.addItems([submitB]);
}

function previewERT(inputText, replyOption, tableRow, template) {
	var restTransform = "https://en.wikipedia.org/api/rest_v1/transform/wikitext/to/html/" + encodeURIComponent(mw.config.get('wgPageName'));
	let preview = inputText.value;
	template = dataERT.protections[template][1];
	if (typeof (replyOption) != "undefined") {
		preview = "{{" + template + replyOption[0] + "}} " + preview;
	}
	if (preview != "") {
		preview = preview.replaceAll(/{{subst:/gi, "{{");
		$.post(restTransform, 'wikitext=' + encodeURIComponent(preview) + '&body_only=true',
			function (html) {
				if (inputText.value != "" || typeof (replyOption) != "undefined") {//Stops preview appearing with empty input box
					tableRow.style = "padding:8px 1em 2px;";
					tableRow.children[1].innerHTML = html.replace("</p>", userSignauteERT);
				}
			}
		);
	} else {
		tableRow.style = "display:none;";
	}
}

async function saveResponseERT(requestBox, responseOption, responseText, answered, requestType, targets, 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 action buttons
	requestBox[1].innerHTML = "";
	requestBox[3].remove();
	let infoBox = new OO.ui.MessageWidget({
		icon: 'pageSettings',
		type: 'notice',
		label: 'Processing request — Edit request starting, getting section data to edit.'
	});
	infoBox.$element[0].style = "margin:5px 0; max-width:50em";
	$(requestBox[1]).append(infoBox.$element);
	//Create loading bar
	let progressBar = new OO.ui.ProgressBarWidget({
		progress: false
	});
	$(requestBox[1]).append(progressBar.$element);
	//Set preview for output
	if (responseOption[0] != "") {//Don't preview a non-response
		let tempValue = {value:responseText};
		previewERT(tempValue, responseOption, requestBox[2], requestType);
	}
	//Find header
	let header = "";
	let sectionIndex = 0;
	let tempElement = requestBox[0];
	let sectionQuery = await ApiGetERT({
		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 == "");
	infoBox.setLabel("Processing request — Making changes to the edit request");
	let editSummary = "/* " + header.replaceAll("_", " ") + " */ " + responseOption[2] + " ([[User:Terasail/Edit_Request_Tool|Edit Request Tool]])";
	let wikitextQuery = await ApiGetERT({
		action: "parse",
		page: mw.config.get("wgPageName"),
		section: sectionIndex,
		prop: "wikitext|revid"
	});
	let wikitext = wikitextQuery.parse.wikitext["*"];
	let latestRevision = wikitextQuery.parse.revid;
	if (responseOption[1] != "Remove") {
		let editTemplate = "{{Edit " + requestType + "-protected";
		for (let c3 = 0; c3 < targets.length; c3++) {
			editTemplate += "|" + targets[c3];
		}
		if (answered) {
			editTemplate += "|answered=yes";
		} else {
			editTemplate += "|answered=no";
		}
		wikitext = wikitext.replace(/{{ *([SETFI]PER|Edit([ -]?[A-Z]+[ -]?|[- ])Protected|Sudo)\s*[^}}]*/i, editTemplate);
		if (responseOption[1] != "Close") {
			wikitext = wikitext.trim() + "\n:";
			if (responseOption[0] != "") {
				wikitext += "{{subst:" + dataERT.protections[requestType][1] + responseOption[0] + "}} ";
			}
			if (responseText != "") {
				wikitext += responseText.replaceAll(/\s*~~~~\s*/g, "") + " ";
			}
			wikitext += "~~~~";
		}
	} else {
		wikitext = "";
		editSummary = editSummary.replace(/[^]+\*\/ /, "");
	}
	infoBox.setType("success");
	infoBox.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;
	}
	new mw.Api().postWithEditToken(apiParams).done(function () {
		window.location = "/w/index.php?title=" + encodeURI(mw.config.get("wgPageName")) + "&type=revision&diff=cur&oldid=prev";
	});
}

function ApiGetERT(params) {
	return new Promise(function(resolve) {
		new mw.Api().get(params)
		.done(function (data) {resolve(data);})
		.fail(function (data) {console.error(data);});
	});
}
//</nowiki>[[Category:Wikipedia scripts]]