mw.loader.using('oojs-ui-core').done(async function() {
"use strict";
if (!(mw.config.get("wgAction") === "view" && (mw.config.get("wgPageName") === "Commons:Quality_images_candidates/candidate_list" || mw.config.get("wgPageName") === "Commons:Quality_images_candidates"))) return;
mw.loader.load('/w/index.php?title=MediaWiki:Gadget-QICvote.css&action=raw&ctype=text/css','text/css');
const 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'>Hide descriptions</label> ",
"<input type='checkbox' id='toggleLi' onclick=\"$('div.qi').find('ul li:first').toggle();\"> ",
"<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>"
].join('');
const 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>"
].join('');
const 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 '
].join('');
let USERNAME = mw.config.get("wgUserName");
let TITLE = mw.config.get("wgPageName") === "Commons:Quality_images_candidates" ? "Commons:Quality_images_candidates/candidate_list" : mw.config.get("wgPageName");
const NOMINATION_CONTAINER = "div.qi";
const REVIEW_MESSAGE = "Dear " + USERNAME + ", please enter your review comment";
const api = new mw.Api();
const 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"}
};
let votes = {}, reviewCount = 0;
function _loading(show) {
$("#loading_").text(show ? "Sending reviews..." : "");
$("#confirm_").text("");
}
function _confirmMessage() {
if (reviewCount > 0) $("#confirm_").text(reviewCount + " pending review confirmations");
}
function getButtonBg(val) {
return val === "Decline" ? "red" : val === "Promotion" ? "green" : val === "Discuss" ? "yellow" : null;
}
function setComboClass(cmb) {
let $cmb = $(cmb), btnClass = getButtonBg($cmb.val());
$cmb.removeClass("ui-button-green ui-button-red ui-button-yellow");
if (btnClass) $cmb.addClass("ui-button-" + btnClass);
}
async function getSectionContent(sectionNumber) {
_loading(true);
let data = await api.get({
action: "query",
prop: "revisions",
rvprop: ["content", "timestamp"],
titles: [TITLE],
formatversion: "2",
curtimestamp: true
});
_loading(false);
return data.query.pages[0].revisions[0].content;
}
async function editSection(sectionNumber, content) {
$(".loading_overly").toggle(500);
try {
let data = await $.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,
token: mw.user.tokens.get("csrfToken")
},
dataType: "json",
type: "POST"
});
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 {
mw.notify(data && data.error ? "Error: " + data.error.code + " : " + data.error.info : "Error: Unknown API result.");
}
} catch (e) {
alert("Error: Request failed.");
}
}
function getSectionId(element) {
let url = $(element).closest("h2").find("span a:last").attr("href");
let results = /[\?&]section=([^&#]*)/.exec(url);
return (results && results[1]) || 0;
}
function _getImageName(aElement) {
let parts = $(aElement).parent().find("a.image, a.mw-file-description").last().attr("href").split(":");
return parts.length && parts[1] && parts[1].indexOf(".") !== -1 ? parts[1] : null;
}
function _remplaceLast(nomination, vote) {
let newNom = getNominationType(nomination, vote.type);
let newWord = getEOL(newNom) + getVoteTemplate(vote.type) + vote.comment + _getSignature() + "}}";
let n = newNom.lastIndexOf("}}");
return newNom.slice(0, n) + newNom.slice(n).replace("}}", newWord);
}
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function getEOL(nom) {
return nom.slice(-3) === "|}}" ? "" : "<br />";
}
function getVoteTemplate(type) {
return type === "Promotion" ? "{{s}} " : type === "Decline" ? "{{o}} " : "";
}
function _getSignature() {
let now = new Date(),
month = ["January","February","March","April","May","June","July","August","September","October","November","December"],
minute = now.getUTCMinutes() < 10 ? "0" + now.getUTCMinutes() : now.getUTCMinutes(),
hour = now.getUTCHours() < 10 ? "0" + now.getUTCHours() : now.getUTCHours();
return " --[[User:" + USERNAME + "|" + USERNAME + "]] " + hour + ":" + minute + ", " + now.getUTCDate() + " " + month[now.getUTCMonth()] + " " + now.getUTCFullYear() + " (UTC)";
}
function _addVote(content, vote) {
let reg = escapeRegExp(vote.image) + "(.*)\\n",
match = content.match(new RegExp(reg, "g"));
if (!match) {
reg = reg.replace(/_/g, ' ');
match = content.match(new RegExp(reg, "g"));
}
if (!match) {
console.log('Failed to find: ', vote.image);
return;
}
let nomination = match[0];
let newNom = _remplaceLast(nomination, vote);
return content.replace(nomination, newNom);
}
function scapeFilenames(filesname) {
return filesname.replace(/File:(.*?)\|\{\{+/g, m => m.replace(/\ /g, "_"));
}
function isDiscuss(oldType, newType) {
return (oldType === "Promotion" && newType === "Decline") || (newType === "Promotion" && oldType === "Decline") || oldType === "Discuss";
}
function getNominationType(nom, newType) {
function setType(oldType) {
oldType = oldType.replace("{{/", "").replace("|", "");
if (isDiscuss(oldType, newType)) newType = "Discuss";
return "{{/" + newType + "|";
}
return nom.replace(/\{\{\/(Nomination|Promotion|Decline|Discuss)\|/g, setType);
}
async function addVotes() {
for (let section in votes) {
if (votes[section] && votes[section].length > 0) {
let content = await getSectionContent(section - 1);
content = scapeFilenames(content);
for (let vote of votes[section]) content = _addVote(content, vote);
await editSection(section, content);
}
}
}
function _getNominationContainer(el) {
return $(el).parent().find(NOMINATION_CONTAINER);
}
function _nullVote(cmb) {
if ($(cmb).val() === "") {
_getNominationContainer(cmb).find("ul li i").text("Review needed");
_getNominationContainer(cmb).css("border-color", "#0000FF");
$(cmb).val("");
return true;
}
return false;
}
function _getSelection(cmb) {
let oldType = $(cmb).parent().find("div.qi ul li:nth-child(2)").children(":first").text(),
newType = $(cmb).val();
if (isDiscuss(oldType, newType)) newType = "Discuss";
return opt[newType];
}
function _setBoderColorVote(cmb, color) {
_getNominationContainer(cmb).css("border-color", color);
}
function _setVoteTitle(cmb, title) {
_getNominationContainer(cmb).find("ul li:nth-child(2)").children(":first").text(title);
}
function _filterNominations(cmb) {
let filter = $(cmb).val();
$(".gallerybox").each(function() {
let $box = $(this);
$box.show();
if (filter) {
let $nom = $box.find(NOMINATION_CONTAINER).find("ul:first li:last"),
reviewText = $nom.find("i").text().trim();
if (reviewText === "Review needed") {
if (filter !== "ToReview") $box.hide();
} else if ($nom.find("b:first").text() !== filter) {
$box.hide();
}
}
});
}
function parseMediaWiki($cmb, wikiMarkup, callback) {
api.get({
action: 'parse',
format: 'json',
origin: '*',
text: wikiMarkup + _getSignature()
}).done(function(response) {
let html = response.parse && response.parse.text ? response.parse.text["*"] : "";
callback($cmb, html);
}).fail(() => callback(null));
}
async function onChangeVote(cmb, section) {
if (_nullVote(cmb)) return;
let $cmb = $(cmb),
selection = _getSelection(cmb),
textOpts = selection.type === "Promotion" ? { value: selection.message } : { placeholder: selection.message },
result = await OO.ui.prompt(REVIEW_MESSAGE, { textInput: textOpts, size: 'medium', escapable: true }).catch(() => null);
if (!result || $.trim(result) === "") {
$cmb.val("");
return;
}
_setVoteTitle(cmb, selection.type);
_setBoderColorVote(cmb, selection.color);
setComboClass(cmb);
parseMediaWiki($cmb, result, function($cmb, html) {
$cmb.parent().find(NOMINATION_CONTAINER).find("ul").append(html);
});
let imageName = _getImageName(cmb);
votes[section] = votes[section] || [];
votes[section].push({ image: decodeURIComponent(imageName), comment: result, type: $cmb.val() });
reviewCount++;
_confirmMessage();
}
if (TITLE === "Commons:Quality_images_candidates/candidate_list") {
$("#content").append(CONFIRM_BAR);
$("#confirm_review").on("click", async function() { await addVotes(); });
$(".gallerybox").each(function() {
let $cmb = $(COMBO_REVIEW).val("");
$(this).append($cmb);
let section = getSectionId(this);
$cmb.on("change", function() { onChangeVote(this, section); });
});
$("#filterNominations").change(function() {
_filterNominations(this);
setComboClass(this);
});
}
});