MediaWiki:Gadget-PatrollingEnhancements.js

//

(function PatrollingEnhancements_IIFE {	window.GPE = typeof window.GPE == 'object' ? window.GPE : {};	const GPE = window.GPE;	/* 	==Configuration options==	 */	// 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; /* 	==Automated patrolling (whitelisting)== */	// 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; };	/* 	==Utility functions== */	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'; };	/* 	==Individual patrol-buttons== */	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); };	/* 	==Individual delete-buttons== */	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'	      ); };	};	/* 	==Delete-reasons== */	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);	    }	  ); };	/* 	==Namespaces== */	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('|'); };	/* 	==Find and handle links== */	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);	    }	  ); };	/* 	==Onload-hooks== */	$( 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 }); }	); });