MediaWiki:Gadget-PatrollingEnhancements.js: Difference between revisions
Jump to navigation
Jump to search
Chrysophylax (talk | contribs) Created page with "var GPE = {}; →</pre> ==Configuration options== <pre>: // The initial value to put in the "deletion reason" text-field; you can // override this in your common.js (or ve..." |
m 1 revision imported |
||
| (2 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
// {{documentation}} | |||
/* | // ******** imported from [[MediaWiki:Gadget-LegacyScriptsNewNode.js]] ******** | ||
== | var newNode = window.newNode = function newNode(tagname) { | ||
var node = document.createElement(tagname); | |||
for (var i = 1; i < arguments.length; i++) { | |||
var argument = arguments[i]; | |||
if (typeof argument == 'string') { //Text | |||
node.appendChild(document.createTextNode(argument)); | |||
} else if (typeof argument == 'object') { | |||
if (argument instanceof Node) { // If it is a DOM Node | |||
node.appendChild(argument); | |||
} else { // Attributes (hopefully) | |||
for (var j in argument) { | |||
if (j === 'class') { // Classname different because... | |||
node.className = argument[j]; | |||
} else if (j == 'style') { // Style is special | |||
node.style.cssText = argument[j]; | |||
} else if (typeof argument[j] == 'function') { // Basic event handlers | |||
node.addEventListener(j, argument[j], false); | |||
} else { | |||
node.setAttribute(j, argument[j]); //Normal attributes | |||
} | |||
} | |||
} | |||
} | |||
} | |||
return node; | |||
{ | |||
{ | |||
/ | |||
{ | |||
}; | }; | ||
// **************************************************************************** | |||
(function PatrollingEnhancements_IIFE() { | |||
{ | window.GPE = typeof window.GPE == 'object' ? window.GPE : {}; | ||
const GPE = window.GPE; | |||
/* </pre> | |||
==Configuration options== | |||
<pre> */ | |||
// The initial value to put in the "deletion reason" text-field; you can | |||
// override this in your common.js (or vector.js or whatnot). | |||
GPE.initialDeleteReason = GPE.initialDeleteReason == undefined ? '' : GPE.initialDeleteReason; | |||
// The value to use as a deletion reason if you leave the text-field blank; you | |||
// can override it in your common.js (or vector.js or whatnot). If you *don't* | |||
// override this, then MediaWiki will generate an automatic deletion reason that | |||
// indicates the entry's last editor and the beginning of its content. | |||
GPE.deleteReasonIfBlank = GPE.deleteReasonIfBlank == undefined ? '' : GPE.deleteReasonIfBlank; | |||
// By DCDuring's request. If you set this to true, then Special:Watchlist will | |||
// show the deletion-reason text-input, but *not* the deletion-reason dropdown, | |||
// when there's an unpatrolled new-page-creation. | |||
GPE.hideDeleteReasonDropdownOnWatchlist = GPE.hideDeleteReasonDropdownOnWatchlist == undefined ? false : GPE.hideDeleteReasonDropdownOnWatchlist; | |||
/* </pre> | |||
==Automated patrolling (whitelisting)== | |||
<pre> */ | |||
// set GPE.currMonth and GPE.lastMonth (in the form of, e.g., '2013/February') | |||
( | |||
function () | |||
{ | |||
var monthNames = [ 'January', 'February', 'March', 'April', 'May', 'June', | |||
'July', 'August', 'September', 'October', 'November', | |||
'December' ]; | |||
var now = new Date(); | |||
var currYear = now.getFullYear(); | |||
var currMonthNum = now.getMonth(); | |||
GPE.currMonth = currYear + '/' + monthNames[currMonthNum]; | |||
var lastYear = (currMonthNum == 0 ? currYear - 1 : currYear); | |||
var lastMonthNum = (currMonthNum == 0 ? 11 : currMonthNum - 1); | |||
GPE.lastMonth = lastYear + '/' + monthNames[lastMonthNum]; | |||
} | |||
)(); | |||
GPE.individualWhiteListedPages = | |||
{ | |||
"Wiktionary:Requests for cleanup": true, | |||
"Wiktionary:Requests for verification": true, | |||
}; | "Wiktionary:Requests for deletion": true, | ||
"Wiktionary:Requests for deletion/Others": true, | |||
"Wiktionary:Requests for moves, mergers and splits": true, | |||
"Wiktionary:Information desk": true, | |||
"Wiktionary:Tea room": true, | |||
"Wiktionary:Etymology scriptorium": true, | |||
"Wiktionary:Requested entries (English)": true, | |||
"Wiktionary:Requested entries (Spanish)": true, | |||
"Wiktionary:List of protologisms": true, | |||
"Wiktionary:Translation requests": true, | |||
"Wiktionary:Feedback": true, | |||
"Wiktionary:Sandbox": true, | |||
"Wiktionary talk:Sandbox": true, | |||
"Wiktionary:Tutorial (Editing)/sandbox": true, | |||
"Wiktionary:Featured word candidates": true, | |||
"Wiktionary:Word of the day/Nominations": true | |||
}; | |||
GPE.individualWhiteListedPages["Wiktionary:Beer parlour/" + GPE.currMonth] = | |||
GPE.individualWhiteListedPages["Wiktionary:Beer parlour/" + GPE.lastMonth] = | |||
GPE.individualWhiteListedPages["Wiktionary:Grease pit/" + GPE.currMonth] = | |||
GPE.individualWhiteListedPages["Wiktionary:Grease pit/" + GPE.lastMonth] = | |||
true; | |||
// per-user white-listed sub-pages (for example, edits by user Foo | |||
// to User:Foo/vector.js should be autopatrolled): | |||
GPE.perUserWhiteListedSubPages = | |||
{ | |||
"/Sandbox": true, | |||
"/sandbox": true, | |||
"/chick.js": true, | |||
"/chick.css": true, | |||
"/standard.js": true, | |||
"/standard.css": true, | |||
"/cologneblue.js": true, | |||
"/cologneblue.css": true, | |||
"/modern.js": true, | |||
"/modern.css": true, | |||
"/myskin.js": true, | |||
"/myskin.css": true, | |||
"/nostalgia.js": true, | |||
"/nostalgia.css": true, | |||
"/simple.js": true, | |||
"/simple.css": true, | |||
"/vector.js": true, | |||
"/vector.css": true, | |||
"/common.js": true, | |||
"/common.css": true | |||
}; | |||
GPE.individualWhiteListedContributors = | |||
/ | { | ||
}; | |||
GPE.shouldAutoPatrol = function(link) | |||
{ | |||
var pagename = link.title; | |||
if(pagename.indexOf('User talk:') == 0) | |||
return true; | |||
if(pagename in GPE.individualWhiteListedPages) | |||
return true; | |||
var contributor; | |||
if(mediaWiki.config.get('wgCanonicalSpecialPageName') === 'Contributions') | |||
{ | |||
contributor = | |||
document.getElementById('t-contributions') | |||
.getElementsByTagName('a')[0].href.replace(/.*\//, ''); | |||
} | |||
else | |||
{ | |||
var li = link.parentNode; | |||
if(li.tagName.toUpperCase() == 'SPAN') | |||
li = li.parentNode; | |||
var links = li.getElementsByTagName('a'); | |||
for(var i = 0; i < links.length; ++i) | |||
if(links[i].title.indexOf('Special:Contributions/') == 0) | |||
{ | |||
contributor = links[i].title.substr('Special:Contributions/'.length); | |||
break; | |||
} | |||
} | |||
if(pagename.indexOf('User:' + contributor + '/') == 0) | |||
if(pagename.substr(contributor.length + 5) in GPE.perUserWhiteListedSubPages) | |||
return true; | |||
if(contributor in GPE.individualWhiteListedContributors) | |||
return true; | |||
return false; | |||
}; | |||
/* </pre> | |||
==Utility functions== | |||
<pre> */ | |||
GPE.newButton = function(text, color, hoverText) | |||
{ | |||
var button = newNode('button', text); | |||
button.style.background = color; | |||
button.style.color = '#FFF'; | |||
button.style.border = '0'; | |||
button.style.padding = '0'; | |||
button.style.cursor = 'pointer'; | |||
button.title = hoverText; | |||
return button; | |||
}; | |||
GPE.disableButton = function(button, text, hoverText) | |||
{ | |||
button.onclick = null; | |||
button.title = (hoverText || ''); | |||
button.innerHTML = text; | |||
// clear out explicit styling and disable, so we can get appropriate | |||
// disabled-button styles: | |||
button.style.background = ''; | |||
button.style.color = ''; | |||
button.style.cursor = ''; | |||
button.disabled = 'disabled'; | |||
}; | |||
/* </pre> | |||
==Individual patrol-buttons== | |||
<pre> */ | |||
GPE.addPatrolButton = function(link, rcid) | |||
{ | |||
if(link.className.search(/(?:^|\s)gpe-hasPatrolButton(?:\s|$)/) > -1) | |||
return; | |||
link.className = (link.className + ' gpe-hasPatrolButton').trim(); | |||
var button = GPE.newButton('M', '#009', 'click to mark as patrolled'); | |||
link.parentNode.insertBefore(button, link.nextSibling); | |||
link.parentNode.insertBefore(document.createTextNode(' · '), button); | |||
button.onclick = | |||
function () | |||
{ | |||
var token = mediaWiki.user.tokens.get('patrolToken'); | |||
$.post | |||
( | |||
'/w/api.php?format=json&action=patrol&assert=user', | |||
{ token: token, rcid: rcid }, | |||
function (data) | |||
{ | |||
if(data.patrol) | |||
GPE.disableButton(button, 'm', 'marked as patrolled'); | |||
else if(data.error) | |||
{ | |||
var msg = data.error.code + ': ' + data.error.info; | |||
if(data.error.code == 'badtoken') | |||
msg += ': "' + token + '"'; | |||
alert(msg); | |||
} | |||
}, | |||
'json' | |||
); | |||
}; | |||
if(GPE.shouldAutoPatrol(link)) | |||
button.click(); | |||
// remove the exclamation point: | |||
var tmp = link; | |||
while(tmp && tmp.nodeName.toUpperCase() !== 'LI') | |||
tmp = tmp.parentNode; | |||
if(tmp) | |||
tmp = tmp.getElementsByClassName('unpatrolled')[0]; | |||
if(tmp) | |||
tmp.parentNode.removeChild(tmp); | |||
== | }; | ||
/* </pre> | |||
==Individual delete-buttons== | |||
<pre> */ | |||
GPE.addDeleteButton = function(link, title) | |||
{ | |||
if(link.className.search(/(?:^|\s)gpe-hasDeleteButton(?:\s|$)/) > -1) | |||
return; | |||
link.className = (link.className + ' gpe-hasDeleteButton').trim(); | |||
var button = GPE.newButton('D', '#900', 'click to delete'); | |||
link.parentNode.insertBefore(button, link.nextSibling); | |||
link.parentNode.insertBefore(document.createTextNode(' · '), button); | |||
button.onclick = | |||
function () | |||
{ | |||
var dropdownReason = | |||
document.getElementById('deleteReasonsDropdown') | |||
? document.getElementById('deleteReasonsDropdown').value | |||
: ''; | |||
if(dropdownReason == 'other') | |||
dropdownReason = ''; | |||
var textInputReason = | |||
document.getElementById('deleteReasonTextInput').value; | |||
var reason; | |||
if(dropdownReason.length && textInputReason.length) | |||
reason = dropdownReason + ': ' + textInputReason; | |||
GPE. | else if(dropdownReason.length || textInputReason.length) | ||
reason = dropdownReason + textInputReason; | |||
else | |||
reason = GPE.deleteReasonIfBlank; | |||
var token = mediaWiki.user.tokens.get('deleteToken'); | |||
$.post | |||
( | |||
'/w/api.php?format=json&action=delete&assert=user', | |||
{ title: title, token: token, reason: reason }, | |||
function (data) | |||
{ | |||
if(data['delete']) | |||
GPE.disableButton(button, 'd', 'deleted'); | |||
else if(data.error) | |||
{ | |||
var msg = data.error.code + ': ' + data.error.info; | |||
/ | if(data.error.code == 'badtoken') | ||
== | msg += ': "' + token + '"'; | ||
alert(msg); | |||
} | |||
}, | |||
{ | 'json' | ||
); | |||
}; | |||
}; | |||
/* </pre> | |||
==Delete-reasons== | |||
<pre> */ | |||
}; | GPE.addDeleteReasonInput = function () | ||
{ var deleteReasonDiv = | |||
( newNode | |||
{ | ( 'div', | ||
{ style: | |||
'background:#900; color:#FFF; ' + | |||
'position:fixed; bottom:0; right:0; margin-bottom:0' | |||
}, | |||
'\u00A0Deletion reason:\u00A0' | |||
) | |||
); | |||
deleteReasonDiv.title = | |||
'the deletion reason (message/summary) to use when you click "D"'; | |||
var deleteReasonTextInput = | |||
( newNode | |||
( 'input', | |||
{ type: 'text', | |||
size: 80, | |||
id: 'deleteReasonTextInput', | |||
value: GPE.initialDeleteReason, | |||
style: 'position:fixed; right:0; margin-bottom:0' | |||
} | |||
) | |||
); | |||
deleteReasonDiv.appendChild(deleteReasonTextInput); | |||
document.getElementById('bodyContent').appendChild(deleteReasonDiv); | |||
if(GPE.hideDeleteReasonDropdownOnWatchlist) | |||
if(mediaWiki.config.get('wgPageName') == 'Special:Watchlist') | |||
return; | |||
// get canned messages from [[MediaWiki:Deletereason-dropdown]]: | |||
$.getJSON | |||
( '/w/api.php?format=json&action=query&meta=allmessages&ammessages=Deletereason-dropdown', | |||
function (data) | |||
{ var rawDeleteReasons = data.query.allmessages[0]['*']; | |||
var deleteReasonsDropdown = | |||
newNode('select', | |||
{ id: 'deleteReasonsDropdown', style: 'vertical-align: bottom' }); | |||
deleteReasonsDropdown.appendChild | |||
(newNode('option', { value: 'other' }, 'Other reason')); | |||
var optGroup = deleteReasonsDropdown; | |||
rawDeleteReasons.replace | |||
( /^(\*\*?) *(.+)$/gm, | |||
function (s, asterisks, text) | |||
{ if(asterisks == '*') | |||
deleteReasonsDropdown.appendChild | |||
(optGroup = newNode('optgroup', { label: text })); | |||
else // '**' | |||
optGroup.appendChild(newNode('option', { value: text }, text)); | |||
} | |||
); | |||
deleteReasonDiv.insertBefore(deleteReasonsDropdown, deleteReasonTextInput); | |||
deleteReasonDiv.insertBefore(newNode('br'), deleteReasonTextInput); | |||
deleteReasonDiv.insertBefore(document.createTextNode('\u00A0'), deleteReasonTextInput); | |||
} | |||
); | |||
}; | |||
/* </pre> | |||
==Namespaces== | |||
<pre> */ | |||
GPE.computeNamespaces = function | |||
(selected, includeAssociated, invertSelection) | |||
{ | |||
GPE. | var associated = Number(selected) + (selected % 2 === 0 ? 1 : -1); | ||
{ | if(invertSelection) | ||
{ | |||
var selector = document.getElementById('namespace'); | |||
if(! selector) | |||
return []; | |||
var ret = []; | |||
for(var option = selector.firstChild; option; option = option.nextSibling) | |||
if(option.nodeName.toUpperCase() === 'OPTION' && option.value) | |||
if(option.value != selected) | |||
if(! includeAssociated || option.value != associated) | |||
ret.push(option.value); | |||
return ret; | |||
} | |||
else | |||
{ | |||
if(includeAssociated) | |||
return [selected, associated]; | |||
else | |||
return [selected]; | |||
} | |||
}; | |||
GPE.generateRcnamespace = function () | |||
{ | |||
var currUrl = document.location.href; | |||
if(! /[?&]namespace=\d+(?:&|$)/.test(currUrl)) | |||
return; | |||
var selected = /[?&]namespace=(\d+)(?:&|$)/.exec(currUrl)[1]; | |||
}; | var includeAssociated = | ||
mediaWiki.config.get('wgPageName') !== 'Special:NewPages' | |||
/* </pre> | && /[?&]associated=(?!0?&|0?$)/.test(currUrl); | ||
== | var invertSelection = /[?&]invert=(?!0?&|0?$)/.test(currUrl); | ||
<pre> */ | var namespaces = | ||
GPE.computeNamespaces(selected, includeAssociated, invertSelection); | |||
if(namespaces.length > 0) | |||
return namespaces.join('|'); | |||
}; | |||
/* </pre> | |||
==Find and handle links== | |||
<pre> */ | |||
GPE.handleUnpatrolledEdits = function (rcidsByRevid) | |||
{ | |||
var links = | |||
document.getElementById('bodyContent').getElementsByTagName('a'); | |||
for(var i = links.length - 1; i >= 0; --i) | |||
{ | |||
var mapKey = /&diff=(prev&oldid=)?(\d+)(&|$)/.exec(links[i].href); | |||
if(mapKey && rcidsByRevid.hasOwnProperty(mapKey[2])) | |||
GPE.addPatrolButton(links[i], rcidsByRevid[mapKey[2]]); | |||
} | |||
}; | |||
GPE.findLinksToUnpatrolledNewPages = function (rcidsByTitle) | |||
{ | |||
if(mediaWiki.config.get('wgPageName') === 'Special:NewPages') | |||
{ | |||
var ret = []; | |||
$('li.not-patrolled a.mw-newpages-pagename').each(function () { | |||
if (this.title && rcidsByTitle.hasOwnProperty(this.title)) | |||
ret.push(this); | |||
}); | |||
return ret; | |||
} | |||
else | |||
{ | |||
var ret = []; | |||
var abbrs = | |||
document.getElementById('bodyContent').getElementsByTagName('abbr'); | |||
for(var i = abbrs.length - 1; i >= 0; --i) | |||
{ | |||
if(abbrs[i].className != 'newpage') | |||
continue; | |||
var link = abbrs[i]; | |||
while(link && link.nodeName.toUpperCase() != 'A') | |||
if(link.nodeName.toUpperCase() === 'SPAN' && link.className === 'mw-title') | |||
link = link.firstChild; | |||
else | |||
link = link.nextSibling; | |||
if(link && link.title && rcidsByTitle.hasOwnProperty(link.title)) | |||
); | ret.push(link); | ||
} | |||
return ret; | |||
} | |||
}; | |||
GPE.handleUnpatrolledNewPages = function (rcidsByTitle) | |||
{ | |||
var userIsSysop = | |||
mediaWiki.config.get('wgUserGroups').indexOf('sysop') > -1; | |||
var links = GPE.findLinksToUnpatrolledNewPages(rcidsByTitle); | |||
for(var i = links.length - 1; i >= 0; --i) | |||
{ | |||
var link = links[i]; | |||
// 2016-04: Equinox removing red D delete button because it hasn't worked for a year. | |||
// if(userIsSysop) | |||
// GPE.addDeleteButton(link, link.title); | |||
GPE.addPatrolButton(link, rcidsByTitle[link.title]); | |||
} | |||
// Remove the rest of the delete features as above | |||
// if(userIsSysop && links.length > 0) | |||
// { | |||
// GPE.getAndStoreDeleteToken(); | |||
// GPE.addDeleteReasonInput(); | |||
// } | |||
}; | |||
GPE.getAndStoreDeleteToken = function () | |||
{ | |||
$.getJSON | |||
( | |||
'/w/api.php?format=json&action=tokens&type=delete', | |||
function (data) | |||
{ | |||
var token = data.tokens.deletetoken; | |||
if(! token || token.search(/^[0-9a-f]{32}\+\\$/) != 0) | |||
return; | |||
mediaWiki.user.tokens.set('deleteToken', token); | |||
} | |||
); | |||
}; | |||
GPE.main = function (params) | |||
{ | |||
var url = | |||
'/w/api.php?format=json&action=query&list=recentchanges' + | |||
'&rcprop=ids|title' + | |||
'&rcshow=!patrolled' + (params.hasOwnProperty('rcshow') ? '|' + params.rcshow : '') + | |||
'&rclimit=' + (params.hasOwnProperty('rclimit') ? params.rclimit : 500) + | |||
'&rctype=' + (params.hasOwnProperty('rctype') ? params.rctype : 'edit|new') + | |||
(params.hasOwnProperty('rcdir') ? '&rcdir=' + params.rcdir : '') + | |||
(params.hasOwnProperty('rcstart') ? '&rcstart=' + params.rcstart : '') + | |||
(params.hasOwnProperty('rcnamespace') ? '&rcnamespace=' + params.rcnamespace : '') + | |||
(params.hasOwnProperty('rcuser') ? '&rcuser=' + params.rcuser : ''); | |||
$.getJSON | |||
( | |||
url, | |||
function (data) | |||
{ | |||
data = data.query.recentchanges; | |||
var rcidsByRevid = {}; // for unpatrolled edits | |||
var rcidsByTitle = {}; // for unpatrolled new pages | |||
for(var i = 0; i < data.length; ++i) | |||
if(data[i].type == 'edit') | |||
rcidsByRevid[data[i].revid] = data[i].rcid; | |||
else | |||
rcidsByTitle[data[i].title] = data[i].rcid; | |||
GPE.handleUnpatrolledEdits(rcidsByRevid); | |||
GPE.handleUnpatrolledNewPages(rcidsByTitle); | |||
} | |||
); | |||
}; | |||
/* </pre> | |||
==Onload-hooks== | |||
<pre> */ | |||
$( document ).ready | |||
( function () | |||
{ | |||
if(mediaWiki.config.get('wgPageName') === 'Special:RecentChanges') { | |||
var currUrl = document.location.href; | |||
var params = {}; | |||
var rcshow = []; | |||
if(currUrl.search(/[?&]hideliu=(?!0?$|0?&)/) > -1) | |||
rcshow.push('anon'); | |||
else if(currUrl.search(/[?&]hideanons=(?!0?$|0?&)/) > -1) | |||
rcshow.push('!anon'); | |||
if(document.getElementsByClassName('minoredit').length === 0) | |||
rcshow.push('!minor'); | |||
if(rcshow.length > 0) | |||
params.rcshow = rcshow.join('|'); | |||
var rcnamespace = GPE.generateRcnamespace(); | |||
if(rcnamespace) | |||
params.rcnamespace = rcnamespace; | |||
GPE.main(params); | |||
} else if(mediaWiki.config.get('wgPageName') === 'Special:NewPages') { | |||
var currUrl = document.location.href; | |||
var params = { rctype: 'new' }; | |||
var rcshow = []; | |||
if(currUrl.search(/[?&]hideliu=(?!0?$|0?&)/) > -1) | |||
rcshow.push('anon'); | |||
if(currUrl.search(/[?&]hideredirs=0?(?:$|&)/) === -1) | |||
rcshow.push('!redirect'); | |||
if(rcshow.length > 0) | |||
params.rcshow = rcshow.join('|'); | |||
if(currUrl.search(/[?&]dir=prev(?=$|&)/) > -1) { | |||
params.rcdir = 'newer'; | |||
if(currUrl.search(/[?&]offset=\d+(?=$|&)/) > -1) | |||
params.rcstart = currUrl.match(/[?&]offset=(\d+)(?=$|&)/)[1]; | |||
} | |||
var rcnamespace = GPE.generateRcnamespace(); | |||
if(rcnamespace) | |||
params.rcnamespace = rcnamespace; | |||
GPE.main(params); | |||
} else if(mediaWiki.config.get('wgPageName') === 'Special:Watchlist') { | |||
var params = {}; | |||
var rcnamespace = GPE.generateRcnamespace(); | |||
if(rcnamespace) | |||
params.rcnamespace = rcnamespace; | |||
// TODO is this the best way to find what we need for the watchlist? | |||
GPE.main(params); | |||
} else if(mediaWiki.config.get('wgPageName').search(/^Special:Contributions(\/|$)/) === 0) | |||
GPE.main({ rcuser: document.getElementById('t-contributions').firstChild.href.replace(/^.*?\/Special:Contributions\//, '') }); | |||
else if(mediaWiki.config.get('wgAction') === 'markpatrolled' | |||
|| mediaWiki.config.get('wgAction') === 'delete' | |||
|| mediaWiki.config.get('wgAction') === 'rollback') | |||
GPE.main({ rclimit: 15 }); | |||
} | |||
); | |||
})(); | |||
Latest revision as of 17:52, 4 November 2025
// {{documentation}}
// ******** imported from [[MediaWiki:Gadget-LegacyScriptsNewNode.js]] ********
var newNode = window.newNode = function newNode(tagname) {
var node = document.createElement(tagname);
for (var i = 1; i < arguments.length; i++) {
var argument = arguments[i];
if (typeof argument == 'string') { //Text
node.appendChild(document.createTextNode(argument));
} else if (typeof argument == 'object') {
if (argument instanceof Node) { // If it is a DOM Node
node.appendChild(argument);
} else { // Attributes (hopefully)
for (var j in argument) {
if (j === 'class') { // Classname different because...
node.className = argument[j];
} else if (j == 'style') { // Style is special
node.style.cssText = argument[j];
} else if (typeof argument[j] == 'function') { // Basic event handlers
node.addEventListener(j, argument[j], false);
} else {
node.setAttribute(j, argument[j]); //Normal attributes
}
}
}
}
}
return node;
};
// ****************************************************************************
(function PatrollingEnhancements_IIFE() {
window.GPE = typeof window.GPE == 'object' ? window.GPE : {};
const GPE = window.GPE;
/* </pre>
==Configuration options==
<pre> */
// The initial value to put in the "deletion reason" text-field; you can
// override this in your common.js (or vector.js or whatnot).
GPE.initialDeleteReason = GPE.initialDeleteReason == undefined ? '' : GPE.initialDeleteReason;
// The value to use as a deletion reason if you leave the text-field blank; you
// can override it in your common.js (or vector.js or whatnot). If you *don't*
// override this, then MediaWiki will generate an automatic deletion reason that
// indicates the entry's last editor and the beginning of its content.
GPE.deleteReasonIfBlank = GPE.deleteReasonIfBlank == undefined ? '' : GPE.deleteReasonIfBlank;
// By DCDuring's request. If you set this to true, then Special:Watchlist will
// show the deletion-reason text-input, but *not* the deletion-reason dropdown,
// when there's an unpatrolled new-page-creation.
GPE.hideDeleteReasonDropdownOnWatchlist = GPE.hideDeleteReasonDropdownOnWatchlist == undefined ? false : GPE.hideDeleteReasonDropdownOnWatchlist;
/* </pre>
==Automated patrolling (whitelisting)==
<pre> */
// set GPE.currMonth and GPE.lastMonth (in the form of, e.g., '2013/February')
(
function ()
{
var monthNames = [ 'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November',
'December' ];
var now = new Date();
var currYear = now.getFullYear();
var currMonthNum = now.getMonth();
GPE.currMonth = currYear + '/' + monthNames[currMonthNum];
var lastYear = (currMonthNum == 0 ? currYear - 1 : currYear);
var lastMonthNum = (currMonthNum == 0 ? 11 : currMonthNum - 1);
GPE.lastMonth = lastYear + '/' + monthNames[lastMonthNum];
}
)();
GPE.individualWhiteListedPages =
{
"Wiktionary:Requests for cleanup": true,
"Wiktionary:Requests for verification": true,
"Wiktionary:Requests for deletion": true,
"Wiktionary:Requests for deletion/Others": true,
"Wiktionary:Requests for moves, mergers and splits": true,
"Wiktionary:Information desk": true,
"Wiktionary:Tea room": true,
"Wiktionary:Etymology scriptorium": true,
"Wiktionary:Requested entries (English)": true,
"Wiktionary:Requested entries (Spanish)": true,
"Wiktionary:List of protologisms": true,
"Wiktionary:Translation requests": true,
"Wiktionary:Feedback": true,
"Wiktionary:Sandbox": true,
"Wiktionary talk:Sandbox": true,
"Wiktionary:Tutorial (Editing)/sandbox": true,
"Wiktionary:Featured word candidates": true,
"Wiktionary:Word of the day/Nominations": true
};
GPE.individualWhiteListedPages["Wiktionary:Beer parlour/" + GPE.currMonth] =
GPE.individualWhiteListedPages["Wiktionary:Beer parlour/" + GPE.lastMonth] =
GPE.individualWhiteListedPages["Wiktionary:Grease pit/" + GPE.currMonth] =
GPE.individualWhiteListedPages["Wiktionary:Grease pit/" + GPE.lastMonth] =
true;
// per-user white-listed sub-pages (for example, edits by user Foo
// to User:Foo/vector.js should be autopatrolled):
GPE.perUserWhiteListedSubPages =
{
"/Sandbox": true,
"/sandbox": true,
"/chick.js": true,
"/chick.css": true,
"/standard.js": true,
"/standard.css": true,
"/cologneblue.js": true,
"/cologneblue.css": true,
"/modern.js": true,
"/modern.css": true,
"/myskin.js": true,
"/myskin.css": true,
"/nostalgia.js": true,
"/nostalgia.css": true,
"/simple.js": true,
"/simple.css": true,
"/vector.js": true,
"/vector.css": true,
"/common.js": true,
"/common.css": true
};
GPE.individualWhiteListedContributors =
{
};
GPE.shouldAutoPatrol = function(link)
{
var pagename = link.title;
if(pagename.indexOf('User talk:') == 0)
return true;
if(pagename in GPE.individualWhiteListedPages)
return true;
var contributor;
if(mediaWiki.config.get('wgCanonicalSpecialPageName') === 'Contributions')
{
contributor =
document.getElementById('t-contributions')
.getElementsByTagName('a')[0].href.replace(/.*\//, '');
}
else
{
var li = link.parentNode;
if(li.tagName.toUpperCase() == 'SPAN')
li = li.parentNode;
var links = li.getElementsByTagName('a');
for(var i = 0; i < links.length; ++i)
if(links[i].title.indexOf('Special:Contributions/') == 0)
{
contributor = links[i].title.substr('Special:Contributions/'.length);
break;
}
}
if(pagename.indexOf('User:' + contributor + '/') == 0)
if(pagename.substr(contributor.length + 5) in GPE.perUserWhiteListedSubPages)
return true;
if(contributor in GPE.individualWhiteListedContributors)
return true;
return false;
};
/* </pre>
==Utility functions==
<pre> */
GPE.newButton = function(text, color, hoverText)
{
var button = newNode('button', text);
button.style.background = color;
button.style.color = '#FFF';
button.style.border = '0';
button.style.padding = '0';
button.style.cursor = 'pointer';
button.title = hoverText;
return button;
};
GPE.disableButton = function(button, text, hoverText)
{
button.onclick = null;
button.title = (hoverText || '');
button.innerHTML = text;
// clear out explicit styling and disable, so we can get appropriate
// disabled-button styles:
button.style.background = '';
button.style.color = '';
button.style.cursor = '';
button.disabled = 'disabled';
};
/* </pre>
==Individual patrol-buttons==
<pre> */
GPE.addPatrolButton = function(link, rcid)
{
if(link.className.search(/(?:^|\s)gpe-hasPatrolButton(?:\s|$)/) > -1)
return;
link.className = (link.className + ' gpe-hasPatrolButton').trim();
var button = GPE.newButton('M', '#009', 'click to mark as patrolled');
link.parentNode.insertBefore(button, link.nextSibling);
link.parentNode.insertBefore(document.createTextNode(' · '), button);
button.onclick =
function ()
{
var token = mediaWiki.user.tokens.get('patrolToken');
$.post
(
'/w/api.php?format=json&action=patrol&assert=user',
{ token: token, rcid: rcid },
function (data)
{
if(data.patrol)
GPE.disableButton(button, 'm', 'marked as patrolled');
else if(data.error)
{
var msg = data.error.code + ': ' + data.error.info;
if(data.error.code == 'badtoken')
msg += ': "' + token + '"';
alert(msg);
}
},
'json'
);
};
if(GPE.shouldAutoPatrol(link))
button.click();
// remove the exclamation point:
var tmp = link;
while(tmp && tmp.nodeName.toUpperCase() !== 'LI')
tmp = tmp.parentNode;
if(tmp)
tmp = tmp.getElementsByClassName('unpatrolled')[0];
if(tmp)
tmp.parentNode.removeChild(tmp);
};
/* </pre>
==Individual delete-buttons==
<pre> */
GPE.addDeleteButton = function(link, title)
{
if(link.className.search(/(?:^|\s)gpe-hasDeleteButton(?:\s|$)/) > -1)
return;
link.className = (link.className + ' gpe-hasDeleteButton').trim();
var button = GPE.newButton('D', '#900', 'click to delete');
link.parentNode.insertBefore(button, link.nextSibling);
link.parentNode.insertBefore(document.createTextNode(' · '), button);
button.onclick =
function ()
{
var dropdownReason =
document.getElementById('deleteReasonsDropdown')
? document.getElementById('deleteReasonsDropdown').value
: '';
if(dropdownReason == 'other')
dropdownReason = '';
var textInputReason =
document.getElementById('deleteReasonTextInput').value;
var reason;
if(dropdownReason.length && textInputReason.length)
reason = dropdownReason + ': ' + textInputReason;
else if(dropdownReason.length || textInputReason.length)
reason = dropdownReason + textInputReason;
else
reason = GPE.deleteReasonIfBlank;
var token = mediaWiki.user.tokens.get('deleteToken');
$.post
(
'/w/api.php?format=json&action=delete&assert=user',
{ title: title, token: token, reason: reason },
function (data)
{
if(data['delete'])
GPE.disableButton(button, 'd', 'deleted');
else if(data.error)
{
var msg = data.error.code + ': ' + data.error.info;
if(data.error.code == 'badtoken')
msg += ': "' + token + '"';
alert(msg);
}
},
'json'
);
};
};
/* </pre>
==Delete-reasons==
<pre> */
GPE.addDeleteReasonInput = function ()
{ var deleteReasonDiv =
( newNode
( 'div',
{ style:
'background:#900; color:#FFF; ' +
'position:fixed; bottom:0; right:0; margin-bottom:0'
},
'\u00A0Deletion reason:\u00A0'
)
);
deleteReasonDiv.title =
'the deletion reason (message/summary) to use when you click "D"';
var deleteReasonTextInput =
( newNode
( 'input',
{ type: 'text',
size: 80,
id: 'deleteReasonTextInput',
value: GPE.initialDeleteReason,
style: 'position:fixed; right:0; margin-bottom:0'
}
)
);
deleteReasonDiv.appendChild(deleteReasonTextInput);
document.getElementById('bodyContent').appendChild(deleteReasonDiv);
if(GPE.hideDeleteReasonDropdownOnWatchlist)
if(mediaWiki.config.get('wgPageName') == 'Special:Watchlist')
return;
// get canned messages from [[MediaWiki:Deletereason-dropdown]]:
$.getJSON
( '/w/api.php?format=json&action=query&meta=allmessages&ammessages=Deletereason-dropdown',
function (data)
{ var rawDeleteReasons = data.query.allmessages[0]['*'];
var deleteReasonsDropdown =
newNode('select',
{ id: 'deleteReasonsDropdown', style: 'vertical-align: bottom' });
deleteReasonsDropdown.appendChild
(newNode('option', { value: 'other' }, 'Other reason'));
var optGroup = deleteReasonsDropdown;
rawDeleteReasons.replace
( /^(\*\*?) *(.+)$/gm,
function (s, asterisks, text)
{ if(asterisks == '*')
deleteReasonsDropdown.appendChild
(optGroup = newNode('optgroup', { label: text }));
else // '**'
optGroup.appendChild(newNode('option', { value: text }, text));
}
);
deleteReasonDiv.insertBefore(deleteReasonsDropdown, deleteReasonTextInput);
deleteReasonDiv.insertBefore(newNode('br'), deleteReasonTextInput);
deleteReasonDiv.insertBefore(document.createTextNode('\u00A0'), deleteReasonTextInput);
}
);
};
/* </pre>
==Namespaces==
<pre> */
GPE.computeNamespaces = function
(selected, includeAssociated, invertSelection)
{
var associated = Number(selected) + (selected % 2 === 0 ? 1 : -1);
if(invertSelection)
{
var selector = document.getElementById('namespace');
if(! selector)
return [];
var ret = [];
for(var option = selector.firstChild; option; option = option.nextSibling)
if(option.nodeName.toUpperCase() === 'OPTION' && option.value)
if(option.value != selected)
if(! includeAssociated || option.value != associated)
ret.push(option.value);
return ret;
}
else
{
if(includeAssociated)
return [selected, associated];
else
return [selected];
}
};
GPE.generateRcnamespace = function ()
{
var currUrl = document.location.href;
if(! /[?&]namespace=\d+(?:&|$)/.test(currUrl))
return;
var selected = /[?&]namespace=(\d+)(?:&|$)/.exec(currUrl)[1];
var includeAssociated =
mediaWiki.config.get('wgPageName') !== 'Special:NewPages'
&& /[?&]associated=(?!0?&|0?$)/.test(currUrl);
var invertSelection = /[?&]invert=(?!0?&|0?$)/.test(currUrl);
var namespaces =
GPE.computeNamespaces(selected, includeAssociated, invertSelection);
if(namespaces.length > 0)
return namespaces.join('|');
};
/* </pre>
==Find and handle links==
<pre> */
GPE.handleUnpatrolledEdits = function (rcidsByRevid)
{
var links =
document.getElementById('bodyContent').getElementsByTagName('a');
for(var i = links.length - 1; i >= 0; --i)
{
var mapKey = /&diff=(prev&oldid=)?(\d+)(&|$)/.exec(links[i].href);
if(mapKey && rcidsByRevid.hasOwnProperty(mapKey[2]))
GPE.addPatrolButton(links[i], rcidsByRevid[mapKey[2]]);
}
};
GPE.findLinksToUnpatrolledNewPages = function (rcidsByTitle)
{
if(mediaWiki.config.get('wgPageName') === 'Special:NewPages')
{
var ret = [];
$('li.not-patrolled a.mw-newpages-pagename').each(function () {
if (this.title && rcidsByTitle.hasOwnProperty(this.title))
ret.push(this);
});
return ret;
}
else
{
var ret = [];
var abbrs =
document.getElementById('bodyContent').getElementsByTagName('abbr');
for(var i = abbrs.length - 1; i >= 0; --i)
{
if(abbrs[i].className != 'newpage')
continue;
var link = abbrs[i];
while(link && link.nodeName.toUpperCase() != 'A')
if(link.nodeName.toUpperCase() === 'SPAN' && link.className === 'mw-title')
link = link.firstChild;
else
link = link.nextSibling;
if(link && link.title && rcidsByTitle.hasOwnProperty(link.title))
ret.push(link);
}
return ret;
}
};
GPE.handleUnpatrolledNewPages = function (rcidsByTitle)
{
var userIsSysop =
mediaWiki.config.get('wgUserGroups').indexOf('sysop') > -1;
var links = GPE.findLinksToUnpatrolledNewPages(rcidsByTitle);
for(var i = links.length - 1; i >= 0; --i)
{
var link = links[i];
// 2016-04: Equinox removing red D delete button because it hasn't worked for a year.
// if(userIsSysop)
// GPE.addDeleteButton(link, link.title);
GPE.addPatrolButton(link, rcidsByTitle[link.title]);
}
// Remove the rest of the delete features as above
// if(userIsSysop && links.length > 0)
// {
// GPE.getAndStoreDeleteToken();
// GPE.addDeleteReasonInput();
// }
};
GPE.getAndStoreDeleteToken = function ()
{
$.getJSON
(
'/w/api.php?format=json&action=tokens&type=delete',
function (data)
{
var token = data.tokens.deletetoken;
if(! token || token.search(/^[0-9a-f]{32}\+\\$/) != 0)
return;
mediaWiki.user.tokens.set('deleteToken', token);
}
);
};
GPE.main = function (params)
{
var url =
'/w/api.php?format=json&action=query&list=recentchanges' +
'&rcprop=ids|title' +
'&rcshow=!patrolled' + (params.hasOwnProperty('rcshow') ? '|' + params.rcshow : '') +
'&rclimit=' + (params.hasOwnProperty('rclimit') ? params.rclimit : 500) +
'&rctype=' + (params.hasOwnProperty('rctype') ? params.rctype : 'edit|new') +
(params.hasOwnProperty('rcdir') ? '&rcdir=' + params.rcdir : '') +
(params.hasOwnProperty('rcstart') ? '&rcstart=' + params.rcstart : '') +
(params.hasOwnProperty('rcnamespace') ? '&rcnamespace=' + params.rcnamespace : '') +
(params.hasOwnProperty('rcuser') ? '&rcuser=' + params.rcuser : '');
$.getJSON
(
url,
function (data)
{
data = data.query.recentchanges;
var rcidsByRevid = {}; // for unpatrolled edits
var rcidsByTitle = {}; // for unpatrolled new pages
for(var i = 0; i < data.length; ++i)
if(data[i].type == 'edit')
rcidsByRevid[data[i].revid] = data[i].rcid;
else
rcidsByTitle[data[i].title] = data[i].rcid;
GPE.handleUnpatrolledEdits(rcidsByRevid);
GPE.handleUnpatrolledNewPages(rcidsByTitle);
}
);
};
/* </pre>
==Onload-hooks==
<pre> */
$( document ).ready
( function ()
{
if(mediaWiki.config.get('wgPageName') === 'Special:RecentChanges') {
var currUrl = document.location.href;
var params = {};
var rcshow = [];
if(currUrl.search(/[?&]hideliu=(?!0?$|0?&)/) > -1)
rcshow.push('anon');
else if(currUrl.search(/[?&]hideanons=(?!0?$|0?&)/) > -1)
rcshow.push('!anon');
if(document.getElementsByClassName('minoredit').length === 0)
rcshow.push('!minor');
if(rcshow.length > 0)
params.rcshow = rcshow.join('|');
var rcnamespace = GPE.generateRcnamespace();
if(rcnamespace)
params.rcnamespace = rcnamespace;
GPE.main(params);
} else if(mediaWiki.config.get('wgPageName') === 'Special:NewPages') {
var currUrl = document.location.href;
var params = { rctype: 'new' };
var rcshow = [];
if(currUrl.search(/[?&]hideliu=(?!0?$|0?&)/) > -1)
rcshow.push('anon');
if(currUrl.search(/[?&]hideredirs=0?(?:$|&)/) === -1)
rcshow.push('!redirect');
if(rcshow.length > 0)
params.rcshow = rcshow.join('|');
if(currUrl.search(/[?&]dir=prev(?=$|&)/) > -1) {
params.rcdir = 'newer';
if(currUrl.search(/[?&]offset=\d+(?=$|&)/) > -1)
params.rcstart = currUrl.match(/[?&]offset=(\d+)(?=$|&)/)[1];
}
var rcnamespace = GPE.generateRcnamespace();
if(rcnamespace)
params.rcnamespace = rcnamespace;
GPE.main(params);
} else if(mediaWiki.config.get('wgPageName') === 'Special:Watchlist') {
var params = {};
var rcnamespace = GPE.generateRcnamespace();
if(rcnamespace)
params.rcnamespace = rcnamespace;
// TODO is this the best way to find what we need for the watchlist?
GPE.main(params);
} else if(mediaWiki.config.get('wgPageName').search(/^Special:Contributions(\/|$)/) === 0)
GPE.main({ rcuser: document.getElementById('t-contributions').firstChild.href.replace(/^.*?\/Special:Contributions\//, '') });
else if(mediaWiki.config.get('wgAction') === 'markpatrolled'
|| mediaWiki.config.get('wgAction') === 'delete'
|| mediaWiki.config.get('wgAction') === 'rollback')
GPE.main({ rclimit: 15 });
}
);
})();