User:DodoMan/revertdiff.js
From Test Wiki
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
mw.loader.using(['mediawiki.util', 'mediawiki.api'], function () {
if (typeof window.RevertDiff === 'undefined' && location.href.match(/diff=/)) { //We're not already launched AND we're on a diff
window.RevertDiff = true;//We're alive !
//Some params.
var RevertDiffParams = {
/**
* Defines the available warns, in the template Averto on the french Vikidia (https://fr.vikidia.org/wiki/Template:Averto).
* The type is the template's parameter type, the title is the title of the section, the MaxLevel is the maximum level ("niveau=") available for that type.
* The Page is the template's Page. Automatically put, it is set to true if available for all levels or set to the max level where it is allowed.
* UserTalkPage tells that we need to return in parameter Page, if we're on a talk page, the page owner's username.
*/
AvailableWarns: {
Global: {Type: "global", Title: "Ta modification a été annulée", MaxLevel: 5, Page: 1},
Copyvio: {Type: "copyvio", Title: "Violation de copyright", MaxLevel: 2, Page: 1},
Encyclo: {Type: "encyclo", Title: "Vikidia est une encyclopédie", MaxLevel: 3},
Polite: {Type: "politesse", Title: "Politesse", MaxLevel: 3, UserTalkPage: true},
Preview: {Type: "prévisualisation", Title: "Merci de [[Aide:Prévisualisation|prévisualiser]]", MaxLevel: 1, Page: true},
Deleted: {Type: "SI", Title: "Article supprimé", MaxLevel: 1, Page: true},
Spam: {Type: "spam", Title: "Spam", MaxLevel: 2, Page: 1},
ShockVandalism: {Type: "vandalisme choquant", Title: "[[Vikidia:Vandalisme|Vandalisme]] choquant", MaxLevel: 3, Page: 2}
},
BlockFrom: 2, //From which level (NON included) the warn template alerts the user that he will be blocked. Will require block permissions to apply it.
GroupBlock: "sysop", //Who can block ? (put the minimum group, if there is more, you may need to edit userIs to allow arrays)
GroupNoLimitMsg: "sysop", //Who doesn't have any limit in posting messages ?
MaxMsgNonInGroup: 2, //How many msgs a user not in GroupNoLimitMsg can post
MsgPosted: 0, //How many messages were posted. Logically we start at 0.
/**
* The predevined warns available. Name will be displayed in the button, Type represents the index in AvailableWarns, Level the level of the warn used.
* If you wish to add a predefined warn which is uses a level superior than the block level, plese check whether the user can block (userIs(GroupBlock)) before generating the buttons.
*/
PredefinedWarns: [
{Name: "Averto 0", Type: "Global", Level: 0},
{Name: "Averto 1", Type: "Global", Level: 1},
{Name: "Averto 2", Type: "Global", Level: 2},
{Name: "Copyvio 0", Type: "Copyvio", Level: 0},
{Name: "Politesse 0", Type: "Polite", Level: 0},
{Name: "Spam 0", Type: "Spam", Level: 1},
{Name: "Spam 1", Type: "Spam", Level: 2}
],
Texts: {
//Restore
NoRestore: "Tu ne peux pas restaurer.",
Restore: "Restaurer",
RestoreConfirm: 'Restaurer les modifications de $0 ?',
RestoreGGBtn: "C'est fait !",
RestoreGGNotif: "L'ancienne modification a été restaurée avec succès !",
RestoreNoChange: "Aucun changement effectué.",
RestorePutReason: "Raison",
RestoreReasonPrompt: 'Indique ici la raison :',
RestoreSummary: 'Restauration (retour à la version de [[Special:Contributions/$0|$0]]).',
Restoring: "Restauration...",
//Patrol
Patrolling: "Marquage de la révision comme relue…",
//Warn/Msg
BlockReminder: "Pense à bloquer l'utilisateur ;-)",
MaxWarnTrig: "Tu as déjà déposé le nombre maximum de messages autorisés.",
MsgAlreadyPost: "Attention, tu as déjà déposé $0 message(s) à $1. En déposer un autre ?",
MsgGG: "Message déposé !",
SendingMsg: "Envoi du message...",
Warn: "Déposer un avertissement",
WarnConfirm: "Déposer un message à l'utilisateur $0 ?",
WarnPredefined: "Déposer un avertissement prédéfini : ",
WarnSubmit: "Avertir",
Welcome: "Bienvenuter",
//Misc
ItsYou: "C'est toi !",
OuchError: "Aïe aïe aïe… ",
//Errors
ErrorInternal: "erreur interne.",
ErrorEditFail: "échec de la modification.",
//EditFail
EditFailErrCode: "Code de l'erreur : ",
'EditFail-abusefilter-disallowed': "Un filtre anti-abus a empêché la modification.",
'EditFail-abusefilter-warning': "Un filtre anti-abus demande à ce que tu confirmes ton action en la répétant.",
'EditFail-default': "La page n'est peut-être pas modifiable pour toi.",
'EditFail-protectedpage': "La page est protégée.",
'EditFail-undofailure': "Conflit d'édition."
},
//To put a welcome message. Title is section's, MsgUser/MsgIp msg for respectively a registred user and an IP.
Welcome: {
Title: "Bienvenue !",
MsgUser: '{{subst:Bienvenue|' + mw.config.get('wgUserName') + '}} [[User:Rafdodo|Rafdodo]] ([[User talk:Rafdodo|talk]]) 08:54, 17 March 2024 (UTC)',
MsgIP: '{{subst:Bienvenue IP|' + mw.config.get('wgUserName') + '}} [[User:Rafdodo|Rafdodo]] ([[User talk:Rafdodo|talk]]) 08:54, 17 March 2024 (UTC)'
}
};
//Return a text to be shown, with the id and the vals (values to replace the $.. in txt). Vals are given in the order it appears in the text ($0, $1...)
function RDtxt(id, vals) {
var Txt = RevertDiffParams.Texts[id];
if (Txt) {
if (Array.isArray(vals))
for (var i = 0, l = vals.length; i < l; i++)
Txt = Txt.split("$" + i.toString()).join(vals[i]); //replaceAll isn't currently supported enough
return Txt;
}
}
//Is the user sysop, bureaucrat... uses wgUserGroups to konw it.
function userIs(group) {
return (mw.config.get("wgUserGroups").indexOf(group) !== -1);
}
//Simple function so as not to repeat a mw.notify().
function Notify(text, type) {
mw.notify(text, {title: "RevertDiff", type: (type === undefined ? "info" : type)});
}
function NotifyFail(text) {//Adds a txt before the actual error message.
mw.notify(RDtxt('OuchError') + text, {title: "RevertDiff", type: "error"});
}
//Will update the max level available, depending on the type and if the user can block.
function updateLevelList() {
var TypeSelected = $("#sel-RD-type").val(), //Which Type of warn is selected
$LevelNum = $("#num-RD-level")[0];
if (TypeSelected.length > 0) {//We selected a warn
var WarnMaxLvl = RevertDiffParams.AvailableWarns[TypeSelected].MaxLevel, //First what's the max lvl of the warn ?
MaxLevel = userIs(RevertDiffParams.GroupBlock) ? WarnMaxLvl : (Math.min(WarnMaxLvl, RevertDiffParams.BlockFrom)); //Then we lower the max lvl to a non blocking lvl if the user can't block.
$LevelNum.max = MaxLevel;
}
$LevelNum.disabled = TypeSelected.length === 0; //Enable the field only if we've selected a type of warning.
}
//Function that edits the page with title title using params, and call successCaalback on success (passing response in arg).
//If there is a fail, call editFailed with the code for parameter.
//Do not mess this with mw.Api.edit, as this last returns the current revisions to make edits on it, here we just send parameters.
//And moreover Vikidia does not support mw.Api.edit (and not even mw.Api.newSection).
function editPage(title, params, successCallback) {
var Api = new mw.Api(),
Settings = {
action: "edit",
format: "json",
title: title
};
Object.assign(Settings, params);
Api.postWithToken('csrf', Settings).then(function (response) {//Let's go !
//We're sure of a success only if this is like this. Sometimes it fails (for example with AbuseFilter) but it doesn't send an "error" object.
if (response.edit.result === "Success") {
successCallback(response);
} else {
editFailed(response.edit.code);//If no success then fail.
}
}).fail(function (code) {
editFailed(code);//Same here
});
}
//Restores, but ask and check a given reason.
function restoreReason(oldId, user1) {
var Reason = prompt(RDtxt("RestoreReasonPrompt"));
if (Reason) //putting a summary is mandatory (if you wanted to put a custom one)
restore(oldId, user1, Reason); // We then just call the main function.
}
//Restores an older edit (oldId, made by user1) overriding all edits above, perhaps using a customReason.
function restore(oldId, user1, customReason) {
//If customReason is defined, then the user already "confirmed" by putting a summary.
if (customReason === undefined && !confirm(RDtxt('RestoreConfirm', [user1])))
return;
Notify(RDtxt('Restoring'), "info");//So the users doesn't feel like waiting for nothing
var Summary = (customReason ? customReason + ' - ' : "") + RDtxt('RestoreSummary', [user1]); //We always put the RestoreSummary but we may add a customReason.
editPage(mw.config.get('wgPageName'), {
undo: mw.config.get('wgCurRevisionId'), // Until the last revision
undoafter: oldId, // Revert from oldId
summary: Summary
}, function (response) {
if (response.edit.nochange !== undefined)//Not very useful, just to be more precise.
Notify(RDtxt('RestoreNoChange'), "info");
else
Notify(RDtxt('RestoreGGNotif'), 'success');
$("#span-RD-restore").text(RDtxt('RestoreGGBtn')); //No need of it anymore
var PatrolLinkElt = $("#mw-diff-ntitle4 > .patrollink > a"); //Can we patrol = is the button-link "Mark as patrolled" present ?
if (PatrolLinkElt.length === 1) {
Notify(RDtxt('Patrolling'));
PatrolLinkElt[0].click();//The link already shows an animation and a notif when clicked.
}
});
}
//Warns user, with a warn of type type, at a level of level, and has been triggered by the element triggerer.
function warnUser(user, type, level, triggerer) {
var WarnObj = RevertDiffParams.AvailableWarns[type];//First let's fetch our warn.
if (!WarnObj) {//Uh oh
NotifyFail(RDtxt("ErrorInternal"));
return;
}
var Msg = '{{subst:Averto|type=';//The template starts by this, always. There are a type and a "niveau" parameter.
Msg += WarnObj.Type +
'|niveau=' +
level; //The mandatory is down
if (WarnObj.Page === true || WarnObj.Page <= level) {//Do we want a Page argument ?
Msg += '|page=' + mw.config.get('wgPageName');
} else if ((WarnObj.UserTalkPage === true || WarnObj.UserTalkPage <= level) && //or a Page arguments that refers to a user talk page ?
mw.config.get("wgNamespaceNumber") === 3) {//But are we on a User talk: namespace ?
Msg += '|page=' + mw.config.get('wgRelevantUserName');//Using the name of the user who owns the talk page.
}
Msg += '}} [[User:Rafdodo|Rafdodo]] ([[User talk:Rafdodo|talk]]) 08:54, 17 March 2024 (UTC)'; //We always finish by this.
//If we need to block, we remind the user by adding an additionnal sucess msg.
postMessage(Msg, WarnObj.Title, user, (level > RevertDiffParams.BlockFrom ? RDtxt('BlockReminder') : ''), triggerer);
}
//Welcomes user, using a message for IPs if the user isIP, and has been triggered by the elt $triggerer.
function welcomeUser(user, isIP, triggerer) {
var Welcome = RevertDiffParams.Welcome;
postMessage((isIP ? Welcome.MsgIP : Welcome.MsgUser), RevertDiffParams.Welcome.Title, user, null, triggerer);
}
//Protection against accidental usages of posting system. A non sysop user cannot post more than MaxMsgNonInGroup message(s).
function confirmPost(user) {
var UserCanPost = userIs(RevertDiffParams.GroupNoLimitMsg) || (RevertDiffParams.MsgPosted < RevertDiffParams.MaxMsgNonInGroup);
if (UserCanPost) {
var ConfirmMsg = "";
if (RevertDiffParams.MsgPosted > 0) {//Already posted a message = confirmation's mandatory
ConfirmMsg = RDtxt("MsgAlreadyPost", [RevertDiffParams.MsgPosted, user]);
} else { // Otherwise: basic confirmation message
ConfirmMsg = RDtxt('WarnConfirm', [user]);
}
return confirm(ConfirmMsg); //Asks for confirmation
}
alert(RDtxt('MaxWarnTrig'));
return false; //No way: the user can't post anymore
}
//Posts to user a message, that contains msg and has for title title. Will add additionnalSuccessMsg to the success notif if it's set.
//Triggered by elt $triggerer.
function postMessage(msg, title, user, additionnalSuccessMsg, triggerer) {
if (!confirmPost(user))//Can the user post ?
return;
Notify(RDtxt('SendingMsg'), "info"); //Wait little user, be patient !
editPage("User talk:" + user, {
section: "new",
sectiontitle: title,
text: msg
}, function () {
switch (triggerer.nodeName) {//The action we do on the elt depends of what is it
case "BUTTON": //We disable and change the txt
triggerer.disabled = true;
triggerer.textContent = RDtxt('MsgGG');
break;
case "FORM": //We reset it
triggerer.reset();
break;
}
Notify(RDtxt('MsgGG') + (additionnalSuccessMsg ? ' ' + additionnalSuccessMsg : ''), 'success');
RevertDiffParams.MsgPosted++;//To remind the user he already posted one !
});
}
//Function to tell that an edit failed and gives a clue about why.
function editFailed(code) {
var Msg = "";
switch (code) {
case "abusefilter-disallowed":
case "abusefilter-warning":
case "undofailure":
case "protectedpage":
Msg = RDtxt("EditFail-" + code);
break;
default:
Msg = RDtxt("EditFail-default") + (code ? (" " + RDtxt("EditFailErrCode") + code) : "");//If the code is defined then let's put it anyway.
}
NotifyFail(RDtxt("ErrorEditFail") + " " + Msg);
}
$(document).ready(function () { //Aight, let's init
// Get username of submitter
var User1TD = $('td.diff-otitle');
var User2TD = $('td.diff-ntitle');
if (!User2TD.length) {//Whoops
NotifyFail(RDtxt("ErrorInternal"));
return;
} else if (!User1TD.length) {//"Fake" diffs (only one version)
return;
}
// Fetching the oldid
var OldId = mw.util.getParamValue("oldid", User1TD.find('span.mw-diff-edit a').attr('href')); //This is the link to edit the old version that we use ("(edit)" is displayed on the UI).
var User1A = User1TD.find('a.mw-userlink'), //This time the link to the user page...
User2A = User2TD.find('a.mw-userlink'),
User1Name = User1A.text(), // Finnally the text
User2Name = User2A.text(),
RestoreHTML, MessagesHTML;//And let's init.
//Can we edit the curr page ? This variable may do false-positive (that will be catched anyway by a nice failure notification) but no false negative.
if (mw.config.get("wgIsProbablyEditable")) {
RestoreHTML = '(<span id=span-RD-restore><button id=btn-RD-restore>' + RDtxt('Restore') + '</button>'//The span will permit the override of the button when we will finish the restoration.
+ '-'
+ '<button id=btn-RD-restore-sum>' + RDtxt('RestorePutReason') + '</button></span>)';//We can choose a custom reason or not.
} else {
RestoreHTML = "(" + RDtxt("NoRestore") + ")";//If we can't it's a little easier
}
//We're not going to warn ourselves !
if (mw.config.get("wgUserName") !== User2Name) {
var WarnsHTML = '(' + RDtxt('Warn') + ' : ';//First warning the user
//Showing a form to select any kind of warn we want that is usable in the template.
//We've a "no selection" option to prevent miss-clicks (amongst with the "required" option on the select).
WarnsHTML += '<form id=form-RD-warn><select id=sel-RD-type name=sel-RD-type required><option value="">---</option>';//Forms
for (var Warn in RevertDiffParams.AvailableWarns)
WarnsHTML += '<option value="' + Warn + '">' + RevertDiffParams.AvailableWarns[Warn].Type + '</option>';//Each of types available
WarnsHTML += '</select> <input id=num-RD-level name=num-RD-level min=0 value=0 step=1 type=number disabled required /> ' + //the level selector (num)
'<input type=submit id=sub-RD-warn value="' + RDtxt("WarnSubmit") + '" /></form>) ';//The submit btn
WarnsHTML += '(' + RDtxt("WarnPredefined");//Now the predefined warns :
for (var id = 0, l = RevertDiffParams.PredefinedWarns.length; id < l; id++) {
if (id !== 0)
WarnsHTML += '-';//If we aren't at the end of the list add a separator.
WarnsHTML += "<button id=btn-RD-predef-warn-" + id + ">" + RevertDiffParams.PredefinedWarns[id].Name + '</button>';//A nice btn.
}
WarnsHTML += ')';//Finished with warns.
//Welcoming the user.
var WelcomeTxt = '(<button id=btn-RD-welcome>' + RDtxt('Welcome') + '</button>)';
MessagesHTML = WarnsHTML + " " + WelcomeTxt;//Now we have our html for the messages.
} else {
MessagesHTML = '(' + RDtxt('ItsYou') + ')';//Not gonna warn myself.
}
$("#contentSub").append("<div id=div-RD-main>" + RestoreHTML + " " + MessagesHTML + "</div>"); //Let's add ourself just below the title.
//Now let's bind some events.
if (mw.config.get("wgIsProbablyEditable")) //There is no btn if we can't edit.
$("button[id^=btn-RD-restore]").on("click", function (e) {
// If the id is btn-RD-restore-sum, we want to set a custom summarry.
var Func = (e.target.id === "btn-RD-restore-sum" ? restoreReason : restore);//Which fn to use ?
Func(OldId, User1Name);
});
if (mw.config.get("wgUserName") !== User2Name) { //Same but it is because we don't warn ourselves.
$("button[id^=btn-RD-predef-warn-]").on("click", function (e) { //Predefined warns
var PredefinedWarn = RevertDiffParams.PredefinedWarns[e.target.id.slice(19, 21)];
warnUser(User2Name, PredefinedWarn.Type, PredefinedWarn.Level, e.target);
});
$("#btn-RD-welcome").on("click", function (e) { //Welcome
var IsIP = User2A.hasClass("mw-anonuserlink"); //Is the link for an IP ?
welcomeUser(User2Name, IsIP, e.target);
});
$("#sel-RD-type").on("change", updateLevelList); //Change warn type = update the max lvl available
$("#form-RD-warn").on("submit", function (e) {
warnUser(User2Name, $("#sel-RD-type").val(), $("#num-RD-level").val(), e.target);
e.returnValue = false;//We don't reload the page here !
e.preventDefault();
});
}
});
}//End of testing if we can use RevertDiff
});//End of closure
//</nowiki>