MediaWiki:Gadget-QICvote.js
Jump to navigation
Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
This user script seems to have a documentation page at MediaWiki:Gadget-QICvote and an accompanying .css page at MediaWiki:Gadget-QICvote.css. |
- Report page listing warnings and errors.
/*
@authors:
* [[User:Wilfredor]]
* [[User:Trougnouf]] (remove the blue backgrounds and fix signature.)
This script make easy the review process on [[Commons:Quality_images_candidates]]
It reduce edit conflicts and you can vote with a simple click (selecting the vote type in the combobox below of each image)
Please, feel free to contact me to improve this tool. Thanks
*
* Section editing is incomplete. See some issues with the current implementation in T341736#9243771
* Finding the filename in the source in _addVote is, at present, best effort. See T341736#9247790
*/
mw.loader.using( 'oojs-ui-core' ).done( function () {
$(function() {
"use strict";
mw.loader.load( '/w/index.php?title=MediaWiki:Gadget-QICvote.css&action=raw&ctype=text/css', 'text/css' );
var CONFIRM_BAR =
"<div class='loading_overly'>"+
"<div><img src='https://upload.wikimedia.org/wikipedia/commons/c/cd/Vector_Loading_fallback.gif'> Sending</div>"+
"</div>"+
"<div class='voting_bar'>" +
" <span class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-buttonElement oo-ui-buttonElement-framed oo-ui-labelElement oo-ui-flaggedElement-progressive oo-ui-flaggedElement-primary oo-ui-buttonInputWidget' >" +
" <input type='button' style='margin:auto !important;' tabindex='6' aria-disabled='false' id='confirm_review' value='Confirm reviews' class='oo-ui-inputWidget-input oo-ui-buttonElement-button'>" +
" </span>" +
" <div id='loading_'></div>" +
" <div id='confirm_'></div>" +
" <p style='padding-right:20%;float:right;margin:0'>" +
" <label for='filterNominations'>Show Only</label>" +
" <select style='display:inline !important;width:auto;' id='filterNominations' class='submit ui-button ui-widget ui-state-default ui-corner-all ui-button-text-icon-primary ui-button-large'>" +
" <option value='Discuss'>Discuss</option>" +
" <option value='Promotion' style='color:DarkGreen;' >Promotion</option>" +
" <option value='Decline' style='color:DarkRed;' >Decline</option>" +
" <option value='Review'>Reviewed</option>" +
" <option value='ToReview'>To Review</option>" +
" <option value='' selected>All</option>" +
" </select>" +
" </p>" +
"</div>";
var COMBO_REVIEW =
"<select style='width:100%; margin:0px !important;' class='submit ui-button ui-widget ui-state-default ui-corner-all ui-button-text-icon-primary ui-button-large'>" +
" <option value='Promotion' style='color:DarkGreen;'>Promotion</option>" +
" <option value='Decline' style='color:DarkRed;'>Decline</option>" +
" <option value='Discuss' style='color:DarkGoldenRod;' >Discuss</option>" +
" <option value='Nomination'>Comment</option>" +
"</select>";
var NOTIFICATION_LAYOUT = '<div class="mw-notification-content">'+
'<span class="ui-button-icon-primary ui-icon-green ui-icon ui-icon-check" style="display:inline-block">'+
'</span> <span style="font-size: 1em !important;color:green;">Your ';
var USERNAME = mw.config.get("wgUserName");
var TITLE = mw.config.get("wgPageName");
var QIC_PAGE_DESTINY = "Commons:Quality_images_candidates/candidate_list";
var NOMINATION_CONTAINER = "div.qi";
var REVIEW_MESSAGE = "Dear " + USERNAME + ", please enter your review comment";
if (TITLE == "Commons:Quality_images_candidates") {
TITLE = QIC_PAGE_DESTINY;
}
var api = new mw.Api();
var opt = {
"Nomination": {
color: "#0000FF",
message: "",
type: "Nomination"
},
"Promotion": {
color: "lime",
message: "Good quality.",
type: "Promotion"
},
"Decline": {
color: "red",
message: "Please fill in a reason why the image does not meet the guidelines.",
type: "Decline"
},
"Comment": {
color: "#0000FF",
message: "",
type: "Comment"
},
"Discuss": {
color: "#EEEE00",
message: "I disagree.",
type: "Discuss"
}
};
var votes = {};
var reviewCount = 0;
/**
* Set message of loading.
*
* Set the messawe while the reviews
* are being sent
*
* @param {boolean} show Indicates whether the message should be defined or not
*/
function _loading(show) {
if (show) {
$("#confirm_").text("");
$("#loading_").text("Sending reviews...");
} else {
$("#loading_").text("");
}
}
/**
* Set confirm messaage.
*
* Set the message of review waiting to be sent
*/
function _confirmMessage() {
if (reviewCount > 0) {
$("#confirm_").text(reviewCount + " pending review confirmations");
}
}
/**
* Get the button background color.
*
* Get the button background color from a
* button type
*
* @param {string} cmbVal button type.
* @return {string} background color
*/
function getButtonBg(cmbVal) {
var buttonBg = null;
if (cmbVal == "Decline") {
buttonBg = "red";
} else {
if (cmbVal == "Promotion") {
buttonBg = "green";
} else {
if (cmbVal == "Discuss") {
buttonBg = "yellow";
}
}
}
return buttonBg;
}
/**
* Set the combobox class.
*
* Set the class for the combobox typeo
* that define the background color of the combobox
*
* @param {object} cmb combobox object.
* @param {string} className part of the class name to set.
*/
function setComboClass(cmb) {
var className = getButtonBg($(cmb).val());
if (className !== null) {
$(cmb).addClass("ui-button-" + className);
} else {
$(cmb).removeClass("ui-button-green ui-button-red ui-button-yellow");
}
}
/**
* Get the section html content
*
* Get the section conten from the API
*
* @param {number} sectionNumber idenfitication of the section.
* @return {object} section content.
*/
function getSectionContent(sectionNumber) {
_loading(true);
return api.get({
action: "query",
prop: "revisions",
rvprop: ["content", "timestamp"],
titles: [TITLE],
//section: sectionNumber, // Future support of section edition
formatversion: "2",
curtimestamp: true
})
.then(function(data) {
var page, revision;
page = data.query.pages[0];
revision = page.revisions[0];
return revision.content;
});
}
/**
* Set new content for a section
*
* Update the section content with the new reviews
*
* @param {number} sectionNumber idenfitication of the section.
* @param {object} content new content of the section.
*/
function editSection(sectionNumber, content) {
$(".loading_overly").toggle(500);
$.ajax({
url: mw.util.wikiScript("api"),
data: {
format: "json",
action: "edit",
title: TITLE,
summary: "Reviewing " + reviewCount + " nomination(s) with [[Special:MyLanguage/Help:Gadget-QICvote|QICvote]]",
text: content,
//section: sectionNumber, // Future support of section edition
token: mw.user.tokens.get("csrfToken")
},
dataType: "json",
type: "POST",
success: function(data) {
if (data && data.edit && data.edit.result == "Success") {
$(".loading_overly").toggle(500);
_loading(false);
votes = {};
$("#confirm_").text("");
mw.notify($(NOTIFICATION_LAYOUT + reviewCount + " review(s) have been saved." + "</span></div>"));
reviewCount = 0;
} else if (data && data.error) {
mw.notify("Error: API returned error code '" + data.error.code + "' : " + data.error.info);
} else {
mw.notify("Error: Unknown result from API.");
}
},
error: function(xhr) {
alert("Error: Request failed.");
}
});
}
/**
* Get section identification
*
* Get section identification from the current nomination
*
* @param {object} element a element of nomination on QIC with the class 'gallerybox'.
* @return {number} id of the section.
*/
function getSectionId(element) {
var url = $(element).parent().parent().parent().find("h2 span a:last").attr("href");
var results = new RegExp("[\?&]section=([^&#]*)").exec(url);
return (results && results[1]) || 0;
}
/**
* Get image name
*
* Get image name of the current nomination
* using the url of the image how reference
*
* @param {object} aElement a element of nomination on QIC with the class 'gallerybox'.
* @return {string} name of the image.
*/
function _getImageName(aElement) {
var imageName = null;
var imageNode = $(aElement).parent().find("a.image, a.mw-file-description").last().attr("href").split(":");
if (imageNode.length > 0) {
if (imageNode[1].indexOf(".") !== -1) {
imageName = imageNode[1];
}
}
return imageName;
}
function _remplaceLast(nomination,vote) {
var newNomination = getNominationType(nomination, vote.type);
var newWord = getEOL(newNomination) + getVoteTemplate(vote.type) + vote.comment + _getSignature() + "}}";
var n = newNomination.lastIndexOf("}}");
newNomination = newNomination.slice(0, n) + newNomination.slice(n).replace("}}", newWord);
return newNomination;
}
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function getEOL(newNomination) {
return (newNomination.slice(-3) === "|}}") ? "" : "<br />";
}
function getVoteTemplate(type) {
var voteTemplate = "";
if (type == "Promotion") {
voteTemplate = "{{s}} ";
} else if (type == "Decline") {
voteTemplate = "{{o}} ";
}
return voteTemplate;
}
function _getSignature() {
// Signature function adapted from MediaWiki:QICSigs.js
var month = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
var now = new Date();
var minute = now.getUTCMinutes();
if (minute < 10) {
minute = "0" + minute;
}
var hour = now.getUTCHours();
if (hour < 10) {
hour = "0" + hour;
}
return " --[[User:" + USERNAME + "|" + USERNAME + "]] " + hour + ":" + minute + ", " + now.getUTCDate() + " " +
month[now.getUTCMonth()] + " " + now.getUTCFullYear() + " (UTC)";
}
function _addVote(content, vote) {
var reg = escapeRegExp(vote.image) + "(.*)\\n";
var match = content.match(new RegExp(reg, "g"));
if (!match) {
// vote.image is the normalized filename found in the href,
// however, it may not be that way in the content. If we failed
// to match, try another variation and hope for the best
reg = reg.replace(/_/g, ' ');
match = content.match(new RegExp(reg, "g"));
}
if (!match) {
// Still no match
console.log('Failed to find in the content: ', vote.image);
return;
}
var nomination = match[0];
var newNomination = _remplaceLast(nomination,vote);
return content.replace(nomination, newNomination);
}
function scapeFilenames(filesname) {
function removeSpaces(match) {
return match.replace(/\ /g, "_");
}
return filesname.replace(/File:(.*?)\|\{\{+/g, removeSpaces);
}
function isDiscuss(oldType, newType) {
return ((oldType === "Promotion" && newType == "Decline") || (newType === "Promotion" && oldType == "Decline") || oldType == "Discuss");
}
function getNominationType(nomination, newType) {
function setType(oldType) {
oldType = oldType.replace("{{/", "").replace("|", "");
if (isDiscuss(oldType, newType)) {
newType = "Discuss";
}
return ("{{/" + newType + "|");
}
return nomination.replace(/\{\{\/(Nomination|Promotion|Decline|Discuss)\|/g, setType);
}
function _addVotes() {
$.each(votes, function(sectionNumber, votesSection) {
if ((typeof votesSection !== "undefined") && (votesSection.length > 0)) {
getSectionContent(sectionNumber - 1).then(function(content) {
content = scapeFilenames(content);
$.each(votesSection, function(i, vote) {
content = _addVote(content, vote);
});
editSection(sectionNumber, content);
});
}
});
}
function _getNominationContainer(e) {
return $(e).parent().find(NOMINATION_CONTAINER);
}
function _nullVote(cmb) {
var nullVote = false;
if ($(cmb).val() === "") {
_getNominationContainer(cmb).find("ul li i").text("Review needed");
_getNominationContainer(cmb).css("border-color", "#0000FF");
$(cmb).val("");
nullVote = true;
}
return nullVote;
}
function _getSelection(cmb) {
var oldType = $(cmb).parent().find("div.qi ul li:nth-child(2)").children(":first").text();
var newType = $(cmb).val();
if (isDiscuss(oldType, newType)) {
newType = "Discuss";
}
return opt[newType];
}
function _removePreviusVote(cmb) {
_getNominationContainer(cmb).find("ul li > .coment").remove();
}
function _setBoderColorVote(cmb, newColor) {
_getNominationContainer(cmb).css("border-color", newColor);
}
function getOldType(cmb) {
_getNominationContainer(cmb).find("ul li:nth-child(2)").children(":first").text();
}
function _setVoteTitle(cmb, newTitle) {
_getNominationContainer(cmb).find("ul li:nth-child(2)").children(":first").text(newTitle);
}
function _getCommentElement(comment) {
return $("<li />", {
text: comment,
"class": "coment"
});
}
function _getSignatureElement() {
return $("<a />", {
href: String("/wiki/User:" + USERNAME),
text: USERNAME
});
}
function _getDateElement() {
return $("<p />", {
text: String(new Date().toUTCString())
});
}
function _filterNominations(cmb) {
$(".gallerybox").each(function() {
$(this).show();
if ($(cmb).val() !== "") {
if ($(this).find(NOMINATION_CONTAINER).find("ul:first li:last i").text().trim() !== "Review needed") {
if ($(this).find(NOMINATION_CONTAINER).find("ul:first li:last b:first").text() !== $(cmb).val()) {
$(this).hide();
} else {
$(this).show();
}
} else if ($(cmb).val() !== "ToReview") {
$(this).hide();
}
}
});
}
function _onChangeVote(cmb, section) {
if (_nullVote(cmb)) {
return;
}
var selection, imageName;
var comment = "";
selection = _getSelection(cmb);
//_removePreviusVote(cmb);
var textI = {
placeholder: selection.message
};
if (selection.type == "Promotion") {
textI = {
value: selection.message
};
}
OO.ui.prompt(REVIEW_MESSAGE, {
textInput: textI,
size: 'medium',
escapable: true,
}).done(function(result) {
if (result === null || $.trim(result) === "") {
$(cmb).val("");
return;
}
_setVoteTitle(cmb, selection.type);
_setBoderColorVote(cmb, selection.color);
setComboClass(cmb);
var $comment = _getCommentElement(result);
var $signature = _getSignatureElement();
var $date = _getDateElement();
$(cmb).parent().find(NOMINATION_CONTAINER).
find("ul").append($comment).
append($signature).
append($date);
imageName = _getImageName(cmb);
if (typeof votes[section] === "undefined") {
votes[section] = [];
}
votes[section].push({
image: decodeURIComponent(imageName),
comment: result,
type: $(cmb).val()
});
reviewCount++;
_confirmMessage();
});
}
if (TITLE == QIC_PAGE_DESTINY) {
var $cmb;
$("#content").append(CONFIRM_BAR);
$("#confirm_review").on("click", function() {
_addVotes();
});
$(".gallerybox").each(function() {
$cmb = $(COMBO_REVIEW).val("");
$(this).append($cmb);
var section = getSectionId(this);
$cmb.on("change", function() {
_onChangeVote(this, section);
});
});
$("#filterNominations").change(function() {
_filterNominations(this);
setComboClass(this);
});
}
});
});