Jump to content

User:Ingenuity/NeedingImprovement.js

From Wikipedia, the free encyclopedia
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.
const allWikiprojects = {};
const allColors = [ "#ff2424", "#ff8e24", "#d1d100", "#5c9e00", "#26b000", "#04b09c", "#005bd1", "#3d00bf", "#a000c4", "#6a00ff" ];
let currentColor = 0;
let parsedData = [];
const selectedProjects = [];
const settings = {
	minQuality: 0,
	maxQuality: Infinity,
	minViews: 0,
	maxViews: Infinity
};

let suggestedProjects, selectedProjectsDiv, tableDiv;

function maxSplit(str, max) {
	const split = str.split(" ");
	const first = split.slice(0, max);
	const last = split.slice(max);

	return [...first, last.join(" ")];
}

function parseData(data) {
	const lines = data
		.split("\n")
		.filter(e => e.length > 2)
		.map(e => maxSplit(e, 3))
		.filter(e => e.length === 4);
	
	lines.forEach(e => {
			e[3]
				.split(",")
				.forEach(proj => {
					proj = proj.toLowerCase();
					if (!(proj in allWikiprojects) && proj.length > 0) {
						allWikiprojects[proj] = [ allColors[currentColor], 0 ];
						currentColor = (currentColor + 1) % allColors.length;
					}

					if (proj.length > 0) {
						allWikiprojects[proj][1] += 1;
					}
				});
		});

	return lines;
}

function renderSelectedPages(pages, container, limit=100) {
	container.innerHTML = "";
	let allHTML = `
		<table>
			<tr>
				<th>Page</th>
				<th class="titleHelp" title="Rough page quality, determined by multiple metrics" class="">Quality</th>
				<th class="titleHelp" title="Approximate number of monthly views">Views</th>
				<th class="titleHelp" title="WikiProjects the article is a part of">Projects</th>
			</tr>`;

	for (const page of pages.splice(0, limit)) {
		let wikiProjectText = "";
		for (let proj of page[3].split(",")) {
			if (proj.length > 0) {
				wikiProjectText += `<span class="wikiproject" style="background-color: ${allWikiprojects[proj.toLowerCase()][0]}">${proj.replaceAll("_", " ")}</span>`;
			}
		}
		allHTML += `
			<tr>
				<td><a href="https://en.wikipedia.org/wiki/${page[0]}" target="_blank">${page[0].replaceAll("_", " ")}</a></td>
				<td>${page[1]}</td>
				<td>${page[2] * 30}</td>
				<td>${wikiProjectText}</td>
			</tr>
		`;
	}

	allHTML += "</table>";
	container.innerHTML = allHTML;
}

function findProjectsMatching(str) {
	const matchingProjects = Object.keys(allWikiprojects).filter(e => e.toLowerCase().includes(str.toLowerCase()));
	return matchingProjects;
}

function matchingPages() {
	return parsedData.filter(page => {
		let matchesAll = true;
		for (let proj of selectedProjects) {
			if (!page[3].toLowerCase().includes(proj.replaceAll("_", " "))) {
				matchesAll = false;
				break;
			}
		}
		if (page[1] < settings.minQuality || page[1] > settings.maxQuality) {
			matchesAll = false;
		}
		if (page[2] * 30 < settings.minViews || page[2] * 30 > settings.maxViews) {
			matchesAll = false;
		}
		return matchesAll;
	});
}

function addProject(project) {
	project = project.replaceAll(" ", "_");
	if (selectedProjects.includes(project)) {
		selectedProjects.splice(selectedProjects.indexOf(project), 1);
	} else {
		selectedProjects.push(project);
	}

	selectedProjectsDiv.innerHTML = selectedProjects.map(e => `
		<span class="wikiproject" style="background-color: ${allWikiprojects[e.replaceAll("_", " ")][0]};" onclick="addProject(this.innerText)">${e.replaceAll("_", " ")}</span>
	`).join(" ");
	
	renderSelectedPages(matchingPages(), tableDiv);
}

async function getPageContent(title) {
	const api = new mw.Api();
	const data = await api.get({
		"action": "query",
		"prop": "revisions",
		"titles": title,
		"rvslots": "*",
		"rvprop": "content"
	});
	const page = Object.values(data.query.pages)[0];
	return page.revisions[0].slots.main["*"];
}

function applyViews() {
	const minViews = document.getElementById("minViews").value;
	const maxViews = document.getElementById("maxViews").value;

	settings.minViews = minViews.length > 0 ? Number(minViews) : 0;
	settings.maxViews = maxViews.length > 0 ? Number(maxViews) : Infinity;

	renderSelectedPages(matchingPages(), tableDiv);
}

function applyQuality() {
	const minQuality = document.getElementById("minQuality").value;
	const maxQuality = document.getElementById("maxQuality").value;

	settings.minQuality = minQuality.length > 0 ? Number(minQuality) : 0;
	settings.maxQuality = maxQuality.length > 0 ? Number(maxQuality) : Infinity;

	renderSelectedPages(matchingPages(), tableDiv);
}

async function runNeedingImprovement() {
	const body = document.getElementById("bodyContent");

	const pagesToFetch = [
		"000", "001", "002", "003", "004", "005", "006", "007", "008", "009",
		"010", "011", "012", "013", "014", "015", "016", "017", "018", "019",
		"020", "021", "022", "023", "024", "025", "026", "027", "028", "029",
		"030", "031", "032", "033", "034", "035", "036", "037", "038", "039",
		"040", "041", "042", "043", "044", "045", "046", "047", "048", "049",
		"050", "051", "052", "053", "054", "055", "056", "057", "058", "059",
		"060", "061", "062", "063", "064", "065", "066", "067", "068", "069",
		"070", "071", "072", "073", "074", "075", "076", "077", "078", "079",
		"080", "081", "082", "083", "084", "085", "086", "087", "088", "089",
		"090", "091", "092", "093", "094", "095", "096", "097", "098", "099",
	];
	body.innerHTML = "Loading... (<span id='complete'></span>)";

	let numComplete = 0;
	const allData = [];
	for (let page of pagesToFetch) {
		allData.push(await getPageContent(`User:Ingenuity/ArticleData/${page}.txt`));
		document.getElementById("complete").innerText = `${++numComplete} / ${pagesToFetch.length}`;
	}
	parsedData = parseData(allData.join("\n"));

	document.head.innerHTML += `
		<style>
			.wikiproject {
				padding: 2px 7px;
				border-radius: 5px;
				color: white;
				display: inline-block;
				margin: 3px 2px;
			}
	
			td, th {
				padding: 5px;
				background: #eee;
				min-width: 100px;
				max-width: 800px;
			}
	
			th {
				text-align: left;
				background: #ddd;
			}
	
			#suggestedProjects, #selectedProjects {
				margin-top: 10px;
				white-space: nowrap;
				overflow-x: auto;
			}

			#selectedProjects {
				margin: 0 !important;
			}
	
			input {
				display: block;
				padding: 5px 10px;
				margin-top: 5px;
				outline: none !important;
				width: 250px;
			}

			.titleHelp {
				cursor: help;
				text-decoration: dotted underline;
			}

			.smallInput {
				width: 75px;
			}

			.filterSection {
				margin: 5px;
				background: #eee;
				padding: 10px;
			}

			.applyButton {
				margin-top: 5px;
				padding: 5px 10px;
				cursor: pointer;
			}
		</style>
	`;
	
	body.innerHTML = `
		<div id="filterContainer" style="display: flex; width: min-content;">
			<div class="filterSection" style="width: 275px;">
				<span>Select projects:</span>
				<input type="text" id="wikiprojectInput">
				<div id="suggestedProjects"></div>
			</div>
			<div class="filterSection" style="display: flex;">
				<div style="margin-right: 10px;">
					<span>Min. quality</span>
					<input type="number" class="smallInput" id="minQuality">
					<button class="applyButton" onclick="applyQuality()">Apply</button>
				</div>
				<div>
					<span>Max. quality</span>
					<input type="number" class="smallInput" id="maxQuality">
				</div>
			</div>
			<div class="filterSection" style="display: flex;">
				<div style="margin-right: 10px;">
					<span>Min. views</span>
					<input type="number" class="smallInput" id="minViews">
					<button class="applyButton" onclick="applyViews()">Apply</button>
				</div>
				<div>
					<span>Max. views</span>
					<input type="number" class="smallInput" id="maxViews">
				</div>
			</div>
		</div>
		<span>Selected projects:</span>
		<div id="selectedProjects"></div>
		<div id="tableDiv"></div>
	`;

	suggestedProjects = document.getElementById("suggestedProjects");
	selectedProjectsDiv = document.getElementById("selectedProjects");
	tableDiv = document.getElementById("tableDiv");
	
	const input = document.getElementById("wikiprojectInput");
	input.addEventListener("keyup", () => {
		const matches = findProjectsMatching(input.value)
			.sort((a, b) => allWikiprojects[b][1] - allWikiprojects[a][1]);
		window.inputval = input.value;
	
		suggestedProjects.innerHTML = "";
		for (const match of matches.splice(0, 10)) {
			suggestedProjects.innerHTML += `
				<span class="wikiproject" style="background-color: ${allWikiprojects[match][0]}" onclick="addProject(this.innerText)">${match.replaceAll("_", " ")}</span>
			`;
		}
	});
}

if (mw.config.values.wgPageName === "Special:BlankPage/NeedingImprovement") {
	runNeedingImprovement();
} else {
	addPortletLink(
		"p-tb",
		`/wiki/Special:BlankPage/NeedingImprovement`,
		"Needing improvement",
		"ca-needing-improvement",
		"Needing improvement",
		"n"
	);
}