Content deleted Content added
Fix for "mw" not being immediately available. |
massive update |
||
Line 1:
/*
Nominations Viewer
Description: Compact nominations for [[WP:FAC]], [[WP:FAR]], [[WP:FLC]],
[[WP:FLRC]], [[WP:FPC]], and [[WP:PR]].
Documentation: [[Wikipedia:Nominations Viewer]]
===
Begin settings
---
Default:
NominationsViewer =
{
Line 18:
'nominationData': ['images', 'age', 'nominators', 'participants', 'votes'],
}
window.NominationsViewer == null ? {} : window.NominationsViewer;
if (NominationsViewer.enabledPages == null) {
NominationsViewer.enabledPages = {
// 'User:Gary/Sandbox': 'nominations'
'Wikipedia:Featured article candidates': 'nominations',
'Wikipedia:Featured article review': 'reviews',
'Wikipedia:Featured list candidates': 'nominations',
'Wikipedia:Featured list removal candidates': 'reviews',
'Wikipedia:Featured picture candidates': 'pictures',
'Wikipedia:Peer review': 'peer reviews',
};
}
if (NominationsViewer.nominationData == null) {
NominationsViewer.nominationData = [
'images', 'age', 'nominators', 'participants', 'votes' ];
}
/*
End settings
class NominationsViewerClass
/**
* Add empty nomination data holders for a nomination.
* @param {string} pageName Name of the nomination page.
* @param {jQuery} $parentNode Parent node containing the entire nomination.
* @param {Array} ids The ID names to create.
*/
static addNominationData(pageName, $parentNode, ids) {
return ids.map((id) => {
const $span = $(
`<span id="${id}-${this.simplifyPageName(pageName)}"></span>`,
);
return $parentNode
.children()
.last()
.before($span);
});
}
static addOverallNominationInformation($headings) {
const data = { allH3Length: $headings.length };
const $expandAllLink = $(
'<a href="#" id="expand-all-link">expand all</a>',
).on('click', data, this.expandAllNoms);
const $collapseAllLink = $(
'<a href="#" id="collapse-all-link">collapse all</a>',
).on('click', data, this.collapseAllNoms);
const $info = $('<span class="overall-controls"></span>')
.append($expandAllLink)
.append(' / ')
.append($collapseAllLink)
.append(')');
return $headings
.first()
.prevUntil('h2')
.prev()
.append($info);
}
const apiUrl = `https:${window.mw.config.get(
'wgServer',
)}${window.mw.config.get(
'wgScriptPath',
)}/api.php?callback=${callback}&format=json&${parameters}`;
mw.loader.load(apiUrl);
};
const pageName = pageNameParam;
// Participants
appendNominationData(
'NominationsViewerClass.allRevisionsCallback',
`action=query&prop=revisions&rvdir=newer&rvlimit=500&titles=${pageName}`,
);
// Images, nominators, votes
appendNominationData(
'NominationsViewerClass.currentRevisionCallback',
`action=query&prop=revisions&rvdir=older&rvlimit=1&rvprop=content&titles=\
${pageName}`,
);
// Age
return appendNominationData(
'NominationsViewerClass.firstRevisionCallback',
`action=query&prop=revisions&rvdir=newer&rvlimit=1&titles=${pageName}`,
);
}
static appendGeneratedNominationData(
pageName,
data,
id,
abbrTitle,
isFirstData,
) {
if (!data) {
return false;
}
// Select the element we want to add values to.
const $id = $(`#${id}-${this.simplifyPageName(pageName)}`);
const $newChild = $('<span></span>');
if (!isFirstData) {
$newChild.append(' · ');
}
const $abbr = $(
$newChild.append($abbr);
return $id.append($newChild);
}
/**
* Create the data that appears next to the nomination's listing.
* @param {string} pageName Page name of the nomination page.
*/
static createData(pageName) {
const $newSpan = $('<span class="nomination-data"></span>').append(
const matchArchiveNumber = pageName.match(/([0-9]+)$/);
const conditions = matchArchiveNumber && matchArchiveNumber[1] > 1;
const matchArchiveNumberPrint = (() => {
if (conditions)
const number = parseInt(matchArchiveNumber[1], 10);
const ordinalSuffix = (() => {
switch (number) {
case 2:
return 'nd';
case 3:
return 'rd';
default:
return 'th';
}
})();
return `: ${number}${ordinalSuffix}`;
}
return '';
})();
const $viewLink = $(
`<span><a href="/https/en.m.wikipedia.org/wiki/${pageName}">nomination</a>\
${matchArchiveNumberPrint}</span>`,
);
return $newSpan.append($viewLink).append('<tt>)</tt>');
}
const $newNode = $(`<div id="nom-title-${index}"></div>`).append(
);
const $heading = $newNode.children().first(); $heading
.prepend(`<span class="nomination-order">${index + 1}.</span> `)
.append(' ')
.append(showHideLink)
.append(newSpan);
return $newNode;
}
/**
* Replace a nomination with a new and improved one.
* @param {jQuery} $h3 The h3 heading of the nomination.
* @param {number} index The index of the nomination among the others.
static createNomination({ $h3, index }) {
const $editLink = $h3.find('.mw-editsection-bracket').next('a');
// This nomination is missing an edit link, so something is wrong with it.
// Skip it.
if (!$editLink.length) {
return true;
}
// Get the name of the nomination page.
const pageName = decodeURIComponent(
$editLink
.attr('href')
.match(/title=(.*?)&/)[1]
.replace(/\s/g, '_'),
);
// Create the [show] / [hide] link
const showHideLink = this.createShowHideLink(index);
// Create the spot to put the data that we will retrieve via the Wikipedia
// API
const newSpan = this.createData(pageName);
// Move the nomination into a hidden node
this.hideNomination($h3, index);
// Add placeholders for the data that we will retrieve for the API
this.addNominationData(pageName, newSpan, NominationsViewer.nominationData);
// Create the nomination's title line
const newNode = this.createNewNode($h3, showHideLink, newSpan, index);
// Create the actual nomination
const nomDiv = this.generateNomination(index, newNode, $h3);
// Replace this nomination with the new one we created
$h3.replaceWith(nomDiv);
// Ask the API to add data to our placeholders
return this.appendAllNominationData(pageName);
}
const link = $(`<a href="#" id="nom-button-${index}">show</a>`).on(
'click',
);
const span = $('<span class="plus-minus-button"></span>'); return span
.append('[') .append(link) .append(']'); }
return $(
.append(newNode.clone(true)) .append($(oldNode[0].nextSibling).clone(true)); }
// This function MUST stay in JavaScript, rather than switch to jQuery, for
// optmization reasons.
//
// The jQuery version slowed the page down by about 28%. This version slows
// the page down by about 11%, so it is about 17% faster.
static hideNomination($h3, index) {
// Re-create all nodes between this H3 node, and the next one,
// then place it into a new node.
const hiddenNode = document.createElement('div');
hiddenNode.className = 'nomination-body';
hiddenNode.id =
hiddenNode.style.display = 'none';
let nomNextSibling = $h3[0].nextSibling;
// Continue to the next node, as long as the next node still exists,
// it isn't an H2 or H3, and it doesn't have the class "printfooter"
while (
nomNextSibling !== null &&
['H2', 'H3'].indexOf(nomNextSibling.nodeName) === -1 &&
!(
nomNextSibling.classList &&
nomNextSibling.classList.contains('printfooter')
)
) {
const nomNextSiblingTemp = nomNextSibling.nextSibling;
// Move the node, if it isn't a text node
if (nomNextSibling.nodeType !== 3) {
hiddenNode.appendChild(nomNextSibling);
}
nomNextSibling = nomNextSiblingTemp;
}
// Insert hidden content
return $h3.after(hiddenNode);
}
static init() {
let currentPageIsASubpage;
let currentPageIsEnabled;
const pageName = window.mw.config.get('wgPageName');
// Check if enabled on this page
Object.keys(NominationsViewer.enabledPages).forEach((page) => {
// const type = NominationsViewer.enabledPages[page];
if (pageName === page.replace(/\s/g, '_')) {
currentPageIsEnabled = true;
} else if (pageName.indexOf(page.replace(/\s/g, '_')) === 0) {
currentPageIsASubpage = true;
}
});
if (
currentPageIsEnabled == null ||
window.mw.config.get('wgAction') !== 'view' ||
window.location.href.indexOf('&oldid=') !== -1 ||
!(currentPageIsASubpage == null)
) {
return false;
}
// Only append the CSS if we are definitely running the script on this page.
this.nominationsViewerCss();
const $
const $h3s = $parentNode.find('h3');
this.addOverallNominationInformation($h3s);
// loop through each nomination
$h3s.each((index, element) =>
this.createNomination({
$h3: $(element),
index,
}),
);
// Fix any conflicts with collapsed comments (using the special template).
return $('.collapseButton').each((index, element) => {
const $link = $(element)
.children()
.first();
const newIndex = $link
.attr('id')
.substring(
$link.attr('id').indexOf('collapseButton') + 'collapseButton'.length,
$link.attr('id').length,
);
return $link
.attr('href', '#')
.on('click', { newIndex }, this.collapseTable);
});
}
/*
Helpers
*/
static collapseTable(event) {
event.preventDefault();
const tableIndex = event.data.index;
const collapseCaption = 'hide';
const expandCaption = 'show';
const $
if (!$table.length || !$button.length) {
return false;
}
const $rows = $table.find('> tbody > tr');
if ($button.text() === collapseCaption) {
$rows.each(
if (index === 0) {
return true;
Line 256 ⟶ 394:
});
return $button.text(expandCaption);
}
$rows.each((index, element) => {
if (index === 0) {
return true;
}
return $(element).show();
});
return $button.text(collapseCaption);
}
// CSS
return window.mw.util.addCSS(
// eslint-disable-next-line no-multi-str
'\
html { overflow-y: scroll; } \
\
#content .nomination h3 { margin-bottom: 0; padding-top: 0; } \
.nomination-data, .nomination-order, .overall-controls { \
font-size: 75%; font-weight: normal; } \
\
.nomination-order { display: inline-block; width: 25px; } \
.plus-minus-button { font-size: 13px; font-weight: normal; \
display: inline-block; width: 40px; }\
',
);
}
return NominationsViewerClass.toggleAllNoms(event, 'expand');
}
return NominationsViewerClass.toggleAllNoms(event, 'collapse');
}
static toggleAllNoms(event, actionParam) {
let action = actionParam;
if (action == null) {
action = 'expand';
}
event.preventDefault();
const { allH3Length } = event.data
Array(allH3Length)
.fill()
.forEach((value, index) => {
NominationsViewerClass.toggleNom(index, action);
});
}
static toggleNom(id, actionParam) {
let action = actionParam;
if (action == null) {
action = '';
}
const toggleHideNom =
$node.hide();
return $nomButton.text('show');
};
const toggleShowNom = ($node, $nomButton) => {
$node.show();
return $nomButton.text('hide');
};
const $
const $nomButton = $(`#nom-button-${id}`);
// These are actions that override the status for all nominations.
if (action === 'collapse') {
return toggleHideNom($node, $nomButton);
} else if (action === 'expand') {
return toggleShowNom($node, $nomButton);
// These have to be separate from the above because they have a lower
// priority.
} else if ($node.is(':visible')) {
return toggleHideNom($node, $nomButton);
Line 317 ⟶ 477:
return toggleShowNom($node, $nomButton);
}
return null;
}
static toggleNomClick(event) {
event.preventDefault();
const { index } = event.data
return NominationsViewerClass.toggleNom(index);
}
/*
static allRevisionsCallback(obj) {
const vars = this.formatJSON(obj);
if (!vars) {
return null;
}
//
if (this.dataIsEnabled('participants') && vars.revisions) {
let userCount = 0;
// revision = { 'revid', 'paretnid', 'user', 'timestamp', 'comment' }
if (!revision.user) {
return;
}
if
users[revision.user] += 1;
} else {
users[revision
userCount +
}
});
const moreThan = obj['query-continue'] ? 'more than ' : '';
const
parseInt(users[user], 10),
]);
usersArray.sort((a, b) => {
if (a[1] < b[1]) {
return 1;
} else if (a[1] > b[1]) {
return -1;
}
return 0;
});
return this.appendGeneratedNominationData(
'participants', edits": );
}
return null;
}
static currentRevisionCallback(obj) {
let content;
const vars = this.formatJSON(obj);
if (!vars) {
return null;
}
if
content = vars.firstRevision['*'];
} else {
return false;
}
// 'images'
if (this.nomType('pictures') && this.dataIsEnabled('images')) {
// Determine number of images in the nomination
const matches = matches1 || (matches2 || []);
const
const filename = $.trim(split[0].replace(/^\[\[/, ''));
});
this.appendGeneratedNominationData(
vars.pageName,
`${matches.length} ${this.pluralize('image', matches.length)}`,
'images',
`Images (in order of appearance): ${images.join(' ')}`,
);
}
// 'nominators'
if (this.dataIsEnabled('nominators') && !this.nomType('peer reviews')) {
let nomTypeText = '';
let listOfNominators = {};
switch (this.nomType()) {
case 'nominations':
nomTypeText = 'nominator';
listOfNominators = this.findNominators(
content, /Nominator(\(s\))?:.*/ );
if ($.isEmptyObject(listOfNominators)) {
listOfNominators = this.findNominators(content, /:<small>''.*/);
Line 416 ⟶ 604:
case 'reviews':
nomTypeText = 'notification';
listOfNominators = this.findNominators(
content, /(Notified|Notifying):.*/ );
break;
case 'pictures':
nomTypeText = 'nominator';
listOfNominators = this.findNominators(
content, /\* '''Support as nominator''' – .*/ }
const allNominators = Object.keys(listOfNominators).map((n) => n);
allNominators.sort();
/* eslint-disable indent */
this.appendGeneratedNominationData(
vars.pageName,
allNominators.length > 0
? `${allNominators.length} ${this.pluralize(
nomTypeText,
allNominators.length,
)}`
: `? ${this.pluralize(nomTypeText, 0)}`,
'nominators',
`${this.pluralize(
this.capitalize(nomTypeText),
allNominators.length,
)} (sorted alphabetically): ${allNominators.join(' ')}`,
);
/* eslint-enable indent */
}
// 'votes'
if (this.dataIsEnabled('votes') && !this.nomType('peer reviews')) {
let opposePattern;
let supportPattern;
const showOpposesForNominations = false;
const showOpposesForReviews = true;
let showSupportsAndOpposes = false;
const voteBuffer = 25;
let basicPatternOpen = `'''(.{0,${voteBuffer}})?`;
let basicPatternClose = `(.{0,${voteBuffer}})?'''`;
if (this.nomType('nominations')) {
supportText = 'support';
opposeText = 'oppose';
`${basicPatternOpen}support${basicPatternClose}`,
'gi',
);
opposePattern = new RegExp(
`${basicPatternOpen}oppose${basicPatternClose}`,
'gi',
);
} else if (this.nomType('reviews')) {
supportText = 'keep';
opposeText = 'delist';
`${basicPatternOpen}keep${basicPatternClose}`,
'gi',
);
opposePattern = new RegExp(
`${basicPatternOpen}delist${basicPatternClose}`,
'gi',
);
} else if (this.nomType('pictures')) {
supportText = 'support';
opposeText = 'oppose';
basicPatternOpen = "\\*(\\s)?'''.*?";
basicPatternClose = ".*?'''";
`${basicPatternOpen}Support${basicPatternClose}`,
'gi',
);
opposePattern = new RegExp(
`${basicPatternOpen}Oppose${basicPatternClose}`,
'gi',
);
}
let dataText = '';
? content.match(supportPattern)
: [];
const opposeMatches = content.match(opposePattern)
? content.match(opposePattern)
: [];
const supports = `${supportMatches.length} ${this.pluralize(
supportText,
supportMatches.length,
)}`;
const opposes = `, ${opposeMatches.length} ${this.pluralize(
opposeText,
opposeMatches.length,
)}`;
if (
((this.nomType('nominations') || this.nomType('pictures')) &&
showOpposesForNominations) ||
(this.nomType('reviews') && showOpposesForReviews)
) {
showSupportsAndOpposes = true;
}
// Only show dataText if sum(votes) > 0
if (
(showSupportsAndOpposes === false && supportMatches.length === 0) ||
supportMatches.length + opposeMatches.length === 0
) {
return null;
}
dataText = showSupportsAndOpposes ? supports + opposes : supports;
const toolTip = supports + opposes;
return this.appendGeneratedNominationData(
vars.pageName,
dataText,
'votes',
toolTip,
);
}
return null;
}
/**
* The callback after getting the first revision of a page.
* @param {Object} obj The response object from the Wikipedia API.
*/
static firstRevisionCallback(obj) {
const vars = this.formatJSON(obj);
if (!vars) {
return null;
}
// Nomination age
if (this.dataIsEnabled('age') && vars.firstRevision) {
const { timestamp } = vars.firstRevision;
const matches = timestamp.match(
/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z/,
);
const today = new Date(
origToday.getFullYear(),
origToday.getMonth(),
origToday.getDate(),
);
const origThen = new Date(
Date.UTC(
matches[1],
matches[2] - 1,
matches[3],
matches[4],
matches[5],
matches[6],
),
);
const thenDate = new Date(
origThen.getFullYear(),
origThen.getMonth(),
origThen.getDate(),
);
const difference = new Date(today.valueOf() - thenDate.valueOf());
let timeAgo = '';
if (difference.valueOf() !== 0) {
const differenceYears = difference.getYear() - 70;
const differenceMonths = difference.getMonth();
const differenceDays = difference.getDate();
/* eslint-disable indent, no-nested-ternary */
timeAgo =
differenceYears > 0
? `${differenceYears} ${this.pluralize(
'year',
differenceYears,
)} old`
: differenceMonths > 0
? `${differenceMonths} ${this.pluralize(
'month',
differenceMonths,
)} old`
: `${differenceDays} ${this.pluralize(
'day',
differenceDays,
)} old`;
/* eslint-enable indent, no-nested-ternary */
} else if (difference.valueOf() === 0) {
timeAgo = 'today';
}
return this.appendGeneratedNominationData(
vars.pageName,
timeAgo,
'age',
`Creation date (local time): ${origThen}`,
);
}
return null;
}
/*
static capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
return NominationsViewer.nominationData.some((data) => dataName === data);
}
const nominatorMatches = content.match(pattern);
const listOfNominators = {};
if (!nominatorMatches) {
return listOfNominators;
}
// [[User:Example|Example]], [[Wikipedia talk:WikiProject Example]]
let nominators = nominatorMatches[0].match(
/\[\[(User|Wikipedia)([ _]talk)?:.*?\]\]/gi,
);
if (nominators) {
nominators.forEach((nominator) => {
.replace(/\|.*/, '') .replace(']]', ''); // Does 'username' have a '/' that we have to strip?
if (username.indexOf('/') !== -1) {
username = username.substring(0, username.indexOf('/'));
}
listOfNominators[username] += 1;
});
}
// {{user|Example}} and similar variants
const userTemplatePattern = /\{\{user.*?\|(.*?)\}\}/gi;
nominators = nominatorMatches[0].match(userTemplatePattern);
if (nominators) {
nominators.forEach((singleNominator) => {
});
}
return listOfNominators;
}
if (!obj.query || !obj.query.pages) {
return false;
}
const vars
vars
vars.page = Object.keys(vars
if (vars
return false;
}
vars
if (!vars.page.revisions) {
return false;
}
[vars
vars.revisions = vars.page.revisions;
return vars;
}
static nomType(type) {
const pageName = window.mw.config.get('wgPageName').replace(/_/g, ' ');
const pageType = NominationsViewer.enabledPages[pageName];
if (type != null) {
if (type === pageType) {
return true;
}
}
return pageType;
}
static pluralize(string, count) {
const plural = `${string}s`;
if (count === 1) {
return string;
}
return plural;
}
static simplifyPageName(pageName) {
return pageName.replace(/\W/g, '');
}
}
$(
// Check the URL to determine if this script should be disabled.
if (window.location.href.indexOf('&disable=nomviewer') > -1) {
return
}
NominationsViewerClass.init();
});
|