// ==UserScript==
// @name WikiGender
// @author Taylor Robinson
// @description a user script for showing statistics on Wikipedia articles about the gender of those linked in the article
// @match *://*.wikipedia.org/wiki/*
// ==/UserScript==
/**
* @typedef {HTMLDivElement} GenderBar
* @typedef {'male' | 'female' | 'other' | 'none'} Gender
* @typedef {{gender: string, title: string}} GenderedLink
* @typedef {{gender: string, num_links: number, pct_links: number}} GenderSummary
* @typedef {{article: string, details: GenderedLink[], num_outlinks: number, summary: GenderSummary[]}} GenderData
*/
(async() => {
const GENDERDATA_API = "https://article-gender-data.wmcloud.org/api/v1/details";
/**
* Requests the gender data for the current page.
* @returns {Promise<GenderData>} The analysed gender data for the page.
*/
async function fetchData() {
let lang = location.host.split(".")[0];
let article = location.pathname.split("/")[2];
let response = await fetch(GENDERDATA_API + "?lang=" + lang + "&title=" + article);
let data = await response.json();
return data;
}
/**
* Creates an instance of a gender bar on the current page.
* @returns {GenderBar}
*/
function createGenderBar() {
let element = document.createElement("div");
element.id = "genderbar";
element.style.maxHeight = "5px";
element.style.width = "100%";
element.style.marginTop = "-0.5em";
element.style.overflow = "hidden";
element.onmouseenter = () => element.style.maxHeight = "1000px";
element.onmouseleave = () => element.style.maxHeight = "5px";
document.querySelector("#firstHeading").insertAdjacentElement("afterend", element);
return element;
}
/**
* Takes a gender and gives back a hex colour code
* @param {string} name
* @returns {string}
*/
function getGenderColour(name) {
switch (name) {
case "N/A":
return "#EEEEEE";
case "male":
return "#517FC1";
case "transgender male":
case "transmasculine":
return "#3d5f8f";
case "female":
return "#F19359";
case "transgender female":
case "transfeminine":
return "#cf7d4a";
default:
return "#FAD965";
}
}
/* Order the genders so it looks nicer on the graph */
const ORDER = [
"female", "transgender female", "transfeminine",
"male", "transgender male", "transmasculine",
"N/A"
];
/**
* Make rows for each gender category for the table
* @param {GenderSummary[]} literals
* @returns {string}
*/
function makeRows(list) {
return `<td>${list.map((o) => `<p style="margin: 0;padding-left:0.5em;border-left: 4px solid ${getGenderColour(o.gender)}"> ${o.gender}</p>`).join("")}</td>
<td style="text-align:right;">${list.map((o) => o.num_links.toLocaleString() + " link" + (o.num_links != 1 ? "s" : "")).join("<br>")}</td>
<td style="text-align:right;">${list.map((o) => Math.round(o.pct_links * 100) + "%").join("<br>")}</td>`;
}
/**
* Fills a genderbar with a gender summary.
* @param {GenderBar} genderbar The gender bar to fill.
* @param {GenderSummary[]} summaries The summary of the genders.
*/
function fillGenderBar(genderbar, summaries) {
genderbar.innerHTML = ""; // Clear the gender bar of any previous elements.
let x = 0;
let gradientParts = [];
for (let summary of summaries.sort((a, b) => ORDER.indexOf(a.gender) - ORDER.indexOf(b.gender))) {
gradientParts.push(getGenderColour(summary.gender) + " " + x + "%");
x += summary.pct_links * 100;
gradientParts.push(getGenderColour(summary.gender) + " " + x + "%");
}
genderbar.style.backgroundImage = "linear-gradient(to right, " + gradientParts.join(", ") + ")";
// Fill table
let maleList = [];
let femaleList = [];
let otherList = [];
for (let summary of summaries) {
switch (summary.gender) {
case "N/A":
break;
case "male":
case "transgender male":
case "transmasculine":
maleList.push(summary);
break;
case "female":
case "transgender female":
case "transfeminine":
femaleList.push(summary);
break;
default:
otherList.push(summary);
break;
}
}
let table = document.createElement("table");
table.style.width = "100%";
table.style.background = "#eee";
table.style.paddingTop = "5px";
table.style.backgroundClip = "content-box";
table.style.borderSpacing = "0.5em";
// I will admit the following code is **not** pretty.
// I am less than proud.
table.innerHTML = `
<tr>
<th>Other Genders</th>
<td style="text-align:right;">${otherList.reduce((previous, value) => previous + value.num_links, 0).toLocaleString()} links</td>
<td style="text-align:right;">${Math.round(otherList.reduce((previous, value) => previous + value.pct_links, 0) * 100)}%</td>
<th>Female</th>
<td style="text-align:right;">${femaleList.reduce((previous, value) => previous + value.num_links, 0).toLocaleString()} links</td>
<td style="text-align:right;">${Math.round(femaleList.reduce((previous, value) => previous + value.pct_links, 0) * 100)}%</td>
<th>Male</th>
<td style="text-align:right;">${maleList.reduce((previous, value) => previous + value.num_links, 0).toLocaleString()} links</td>
<td style="text-align:right;">${Math.round(maleList.reduce((previous, value) => previous + value.pct_links, 0) * 100)}%</td>
</tr>
<tr style="vertical-align: top">
${ makeRows(otherList)}
${makeRows(femaleList) }
${makeRows(maleList) }
</tr>
`;
genderbar.appendChild(table);
}
/**
* Applies colour to links on the page
* @param {GenderedLink[]} links The links to colour.
*/
function applyLinkColours(links) {
let pageLinks = document.querySelector(".mw-parser-output").querySelectorAll("a[href^=\"/wiki/\"]");
// Create an object of all the links on the page.
let linksOnPage = {};
for (let link of pageLinks) {
let linkText = decodeURIComponent(link.href.split("/")[4].toLowerCase().replace(/_/g, " "));
// Build array of all instances of a link appearing on page
if (!(linkText in linksOnPage)) {
linksOnPage[linkText] = [];
}
linksOnPage[linkText].push(link);
}
// Colour the links.
for (let link of links) {
let linkOnPage = linksOnPage[link.title];
if (!linkOnPage) continue;
for (let l of linkOnPage) {
l.style.backgroundColor = getGenderColour(link.gender) + "88";
}
}
}
let mwIndicator = document.createElement("div");
mwIndicator.className = "mw-indicator";
mwIndicator.id = "mw-indicator-gender";
document.querySelector(".mw-indicators").appendChild(mwIndicator);
let link = document.createElement("a");
link.href = "javascript:void";
link.onclick = async() => {
link.remove();
let data = await fetchData();
fillGenderBar(createGenderBar(), data.summary);
applyLinkColours(data.details);
};
link.title = "Analyse the gender of the links on the page";
mwIndicator.appendChild(link);
let img = document.createElement("img");
img.alt = "Gender equality icon";
img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Gender_equality.png/20px-Gender_equality.png";
img.srcset = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Gender_equality.png/30px-Gender_equality.png 1.5x, https://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Gender_equality.png/40px-Gender_equality.png 2x";
img.decoding = "async";
img.height = 20;
img.width = 20;
link.appendChild(img);
})();