User:WhitePhosphorus/js/active sysops.js
< User:WhitePhosphorus | js
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.
/*
* active_sysops.js (beta test)
* @author [[User:WhitePhosphorus]], based on [[User:Hoo man]]'s script under the same name
* For more info: https://meta.wikimedia.org/wiki/User:WhitePhosphorus/active_sysops
*
* Show the number of active local admins and mark global sysop wikis.
* Still under active development and everything is subject to change.
*/
$.when( mw.loader.using( ['mediawiki.api', 'oojs-ui-core', 'oojs-ui-widgets'] ), $.ready ).then( function () {
var api = new mw.Api(),
metaApi = mw.config.get('wgDBname') === 'metawiki' ? api : new mw.ForeignApi('https://meta.wikimedia.org/w/api.php'),
wikidataApi = mw.config.get('wgDBname') === 'wikidatawiki' ? api : new mw.ForeignApi('https://www.wikidata.org/w/api.php'),
isGSWiki = null,
sysopCount = null,
sysopTotal = null,
sysopList = null,
lastAction = 7*86400,
GSUnknownBGColor = 'rgba(255, 204, 0, 0.8)',
GSBGColor = 'rgba(0, 170, 102, 0.8)',
NonGSBGColor = 'rgba(255, 204, 187, 0.8)',
popupButton = null,
sysopLimit = 10;
function displayCORSWarning() {
// Display the warning only once for the same site
if (!mw.user.options.get('userjs-p4-cors-warning-displayed')) {
// Notify user that we cannot access foreign api
mw.notify($('<p>Some features of active_sysops.js are not available on this wiki. (<a target="_blank" href="https://meta.wikimedia.org/wiki/User:WhitePhosphorus/active_sysops#CORS_Error">Why?</a>)</p>'), {
type: 'warn',
autoHideSeconds: 'long',
tag: 'p4-warning-cors',
});
api.saveOption('userjs-p4-cors-warning-displayed', '1');
}
}
// From https://stackoverflow.com/a/16448981, edited for mobile support
// Usage: $('#element').bind('mousedown touchstart', handle_mousedown);
function handle_mousedown(e) {
var popup = $('.p4-active-sysops > div')[0];
if (e.target === popup || $.contains(popup, e.target)) {
// Do not make the popup window draggable, to allow text selection etc.
return;
}
var pageX0 = e.type.toLowerCase() === 'mousedown' ? e.pageX : e.originalEvent.touches[0].pageX;
var pageY0 = e.type.toLowerCase() === 'mousedown' ? e.pageY : e.originalEvent.touches[0].pageY;
window.my_dragging = {};
my_dragging.pageX0 = pageX0;
my_dragging.pageY0 = pageY0;
my_dragging.elem = this;
my_dragging.offset0 = $(this).offset();
function handle_dragging(e) {
var pageX = e.type.toLowerCase() === 'mousemove' ? e.pageX : e.originalEvent.touches[0].pageX;
var pageY = e.type.toLowerCase() === 'mousemove' ? e.pageY : e.originalEvent.touches[0].pageY;
var left = my_dragging.offset0.left + (pageX - my_dragging.pageX0);
var top = my_dragging.offset0.top + (pageY - my_dragging.pageY0);
$(my_dragging.elem)
.offset({top: top, left: left});
}
function handle_mouseup(e) {
$('body')
.off('mousemove touchmove', handle_dragging)
.off('mouseup touchend', handle_mouseup);
}
$('body')
.on('mouseup touchend', handle_mouseup)
.on('mousemove touchmove', handle_dragging);
}
// From https://stackoverflow.com/a/53800501
// in miliseconds
var units = {
year : 24 * 60 * 60 * 1000 * 365,
month : 24 * 60 * 60 * 1000 * 365/12,
day : 24 * 60 * 60 * 1000,
hour : 60 * 60 * 1000,
minute: 60 * 1000,
second: 1000
};
var rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
var getRelativeTime = function (d1, d2 = new Date()) {
var elapsed = d1 - d2;
// "Math.abs" accounts for both "past" & "future" scenarios
for (var u in units)
if (Math.abs(elapsed) > units[u] || u == 'second')
return rtf.format(Math.round(elapsed/units[u]), u);
};
function displaySiteInfo() {
var gsWikiText = 'GS Loading';
var gsWikiBGColor = GSUnknownBGColor;
if (isGSWiki === true) {
gsWikiText = 'GS Wiki';
gsWikiBGColor = GSBGColor;
} else if (isGSWiki === false) {
gsWikiText = 'NOT GS Wiki';
gsWikiBGColor = NonGSBGColor;
}
var sysopCountText = '?';
if (sysopCount !== null) {
sysopCountText = sysopCount.toString();
}
if (sysopTotal !== null) {
sysopCountText += ('/' + sysopTotal);
}
popupButton.setLabel(
'{0} ({1})'
.replace('{0}', gsWikiText)
.replace('{1}', sysopCountText)
);
$('.p4-active-sysops > .oo-ui-buttonElement-button').css({'background-color': gsWikiBGColor});
if (sysopCount === null) return;
$('.p4-active-sysops-loading').hide();
$('#p4-active-sysops-list').show().empty();
var sysop = null;
for (var i = 0; i < sysopList.length; ++i) {
sysop = sysopList[i];
$('<li />', {
html: '<a target="_blank" href="/wiki/User:{user}">{user}</a> <sup><a target="_blank" href="/wiki/Special:Contribs/{user}">C</a> · <a target="_blank" href="/wiki/Special:Logs/{user}">L</a></sup> (<a target="_blank" href="/wiki/User_talk:{user}">talk</a><sup><a target="_blank" href="/w/index.php?title=User_talk:{user}&action=edit§ion=new">+</a></sup>) - active {lastActive}'
.replace(/{user}/g, sysop.username)
.replace(/{lastActive}/g, getRelativeTime(new Date(sysop.last_action)))
}).appendTo('#p4-active-sysops-list');
}
if (sysopList.length < sysopCount) {
// Show a notice for exceeding `sysopLimit`
$('<li />', {
html: '<i>... and {0} more active sysop(s)</i>'
.replace('{0}', sysopCount - sysopList.length)
}).appendTo('#p4-active-sysops-list');
}
}
function createInterface() {
var fieldset = new OO.ui.FieldsetLayout({
label: 'Config & Details'
});
var lastActionSelect = new OO.ui.ButtonSelectWidget({
align: 'top',
items: [
new OO.ui.ButtonOptionWidget({
data: 3*86400,
label: '3 days'
}),
new OO.ui.ButtonOptionWidget({
data: 7*86400,
label: '7 days'
}),
new OO.ui.ButtonOptionWidget({
data: 60*86400,
label: '60 days'
}),
]
});
// Default is 7 days (handled afterwards)
//lastActionSelect.selectItem(lastActionSelect.getItemFromLabel('7 days'));
var lastActionOptionKey = 'userjs-p4-activeSysops-lastAction';
lastActionSelect.on('choose', function (item, selected) {
if (item.data !== lastAction) {
metaApi.saveOption(lastActionOptionKey, item.data.toString()).fail(displayCORSWarning);
lastAction = item.data;
}
// Reset sysop count
$('.p4-active-sysops-loading').show();
$('#p4-active-sysops-list').hide();
sysopCount = null;
displaySiteInfo();
// Then check again without cache
checkActiveSysops(false);
displaySiteInfo();
});
fieldset.addItems([
new OO.ui.FieldLayout(
lastActionSelect, {
align: 'top',
helpInline: true,
label: 'Count how many local sysops have made a logged action within ...',
help: 'Select again to purge cache.'
}
),
new OO.ui.FieldLayout( new OO.ui.LabelWidget( {
label: $('<a target="_blank" id="p4-active-sysops-extlink" href="#">stewardry</a><span id="p4-active-sysops-an"> · <a target="_blank" href="#">AN</a> (<a target="_blank" href="#">edit</a>)</span><span id="p4-active-sysops-raa"> · <a target="_blank" href="#">RAA</a> (<a target="_blank" href="#">edit</a>)</span> <ul id="p4-active-sysops-list"></ul><div class="p4-active-sysops-loading"></div>')
} ), {
label: 'Active sysops on this wiki:',
align: 'top'
} ),
]);
popupButton = new OO.ui.PopupButtonWidget({
label: 'Loading...',
classes: ['p4-active-sysops'],
popup: {
$content: fieldset.$element,
padded: true,
align: 'forwards',
width: 400,
}
});
$(document.body).append(popupButton.$element);
// External link
$('#p4-active-sysops-extlink').attr('href', 'https://meta.toolforge.org/stewardry/{0}?sysop=1'.replace('{0}', mw.config.get('wgDBname')));
$('#p4-active-sysops-an').hide();
$('#p4-active-sysops-raa').hide();
// Get local admin noticeboard page from cookie first
var ANTitleStorageKey = 'p4-activeSysops-ANTitle',
ANTitleCached = mw.cookie.get(ANTitleStorageKey),
RAATitleStorageKey = 'p4-activeSysops-RAATitle',
RAATitleCached = mw.cookie.get(RAATitleStorageKey);
// null -> we haven't checked yet; '' -> page does not exist
if (ANTitleCached === null || RAATitleCached === null) {
// If not found, fetch the info from wikidata
wikidataApi.get({action: 'wbgetentities', ids: 'Q4580256|Q3907246', props: 'sitelinks', sitefilter: mw.config.get('wgDBname')}).done(function (data) {
if (!data.success) return;
if (data.entities['Q4580256'].sitelinks[mw.config.get('wgDBname')]) {
var ANTitle = data.entities['Q4580256'].sitelinks[mw.config.get('wgDBname')].title;
$('#p4-active-sysops-an > a')[0].href = '/wiki/' + ANTitle; // View link
$('#p4-active-sysops-an > a')[1].href = '/w/index.php?action=edit&title=' + ANTitle; // Edit link
$('#p4-active-sysops-an').show();
}
if (data.entities['Q3907246'].sitelinks[mw.config.get('wgDBname')]) {
var RAATitle = data.entities['Q3907246'].sitelinks[mw.config.get('wgDBname')].title;
$('#p4-active-sysops-raa > a')[0].href = '/wiki/' + RAATitle; // View link
$('#p4-active-sysops-raa > a')[1].href = '/w/index.php?action=edit&title=' + RAATitle; // Edit link
$('#p4-active-sysops-raa').show();
}
mw.cookie.set(ANTitleStorageKey, ANTitle || '', 25*3600);
mw.cookie.set(RAATitleStorageKey, RAATitle || '', 25*3600); // Both expire after 25h
}).fail(displayCORSWarning);
} else {
if (ANTitleCached) {
$('#p4-active-sysops-an > a')[0].href = '/wiki/' + ANTitleCached; // View link
$('#p4-active-sysops-an > a')[1].href = '/w/index.php?action=edit&title=' + ANTitleCached; // Edit link
$('#p4-active-sysops-an').show();
}
if (RAATitleCached) {
$('#p4-active-sysops-raa > a')[0].href = '/wiki/' + RAATitleCached; // View link
$('#p4-active-sysops-raa > a')[1].href = '/w/index.php?action=edit&title=' + RAATitleCached; // Edit link
$('#p4-active-sysops-raa').show();
}
}
$('.p4-active-sysops').hide();
// Global CSS styles
$('.p4-active-sysops').css({
'z-index': 101, // To show above .mw-portlet with z-index 100
});
// Loading gif CSS styles
$('.p4-active-sysops-loading').css({
background: "url('//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif') no-repeat center center",
width: '32px',
height: '32px',
left: '50%',
position: 'absolute',
transform: 'translate(-50%, 0)',
});
// Popup Button CSS styles
$('.p4-active-sysops > .oo-ui-buttonElement-button').css({
'background-color': GSUnknownBGColor,
//border: 'none',
'border-radius': '6px',
//color: 'black',
display: 'inline-block',
//'font-size': '12px',
'text-align': 'center',
'text-decoration': 'none',
//height: '80px',
//width: '80px',
});
// Popup window CSS styles
$('.p4-active-sysops .oo-ui-popupWidget-popup').css({
overflow: 'auto',
});
// Active sysop list CSS styles
$('#p4-active-sysops-list').css({
'font-size': 'smaller',
});
// Load global options from metawiki
var posOptionKey = 'userjs-p4-activeSysops-buttonPos',
posOption;
metaApi.get({action: 'query', meta: 'userinfo', uiprop: 'options'}).done(function (data) {
posOption = data.query.userinfo.options[posOptionKey] || null;
if (posOption !== null) {
var posArr = posOption.split('|'),
posLeft = parseFloat(posArr[0]),
posTop = parseFloat(posArr[1]);
$('.p4-active-sysops').css({top: posTop, left: posLeft, position: 'fixed'});
} else {
$('.p4-active-sysops').css({bottom: '10px', right: '10px', position: 'fixed'});
}
lastAction = data.query.userinfo.options[lastActionOptionKey] || 7*86400;
lastAction = parseInt(lastAction);
lastActionSelect.selectItem(lastActionSelect.findItemFromData(lastAction) || lastActionSelect.getItemFromLabel('7 days'));
// Display after the position is decided
$('.p4-active-sysops').show();
checkActiveSysops();
}).fail(function() {
displayCORSWarning();
// Use default configurations
lastAction = 7*86400;
lastActionSelect.selectItem(lastActionSelect.getItemFromLabel('7 days'));
$('.p4-active-sysops').css({bottom: '10px', right: '10px', position: 'fixed'});
$('.p4-active-sysops').show();
checkActiveSysops();
});
// Make the button draggable
$('.p4-active-sysops').bind('mousedown touchstart', handle_mousedown);
$('.p4-active-sysops').bind('mouseup touchend', function (e) {
// Save button position on metawiki so it works globally
var posDict = $(this).position();
metaApi.saveOption(posOptionKey, posDict.left+'|'+posDict.top).fail(displayCORSWarning);
});
}
function checkGSWiki(useCache=true) {
var storageKey = 'p4-activeSysops-isGSWiki',
cached = mw.cookie.get(storageKey);
if (useCache && cached !== null) {
isGSWiki = (cached === 'true');
displaySiteInfo();
return;
}
api.get({
action: 'query',
list: 'wikisets',
wsfrom: 'Opted-out of global sysop wikis',
wsprop: 'wikisincluded',
wslimit: 1,
}).done(function(data) {
var wikisincluded = data.query.wikisets[0].wikisincluded;
isGSWiki = Object.values(wikisincluded).includes(mw.config.get('wgDBname'));
mw.cookie.set(storageKey, isGSWiki, 25*3600); // Expire after 25h
displaySiteInfo();
});
}
function checkActiveSysops(useCache=true) {
var sysopListStorageKey = 'p4-activeSysops-sysopList-' + lastAction,
sysopListCached = localStorage.getItem(sysopListStorageKey),
sysopCountStorageKey = 'p4-activeSysops-sysopCount',
sysopCountCached = mw.cookie.get(sysopCountStorageKey),
sysopCountArr;
if (useCache && sysopListCached !== null && sysopCountCached !== null) {
sysopList = JSON.parse(sysopListCached);
sysopCountArr = sysopCountCached.split('|');
sysopCount = parseInt(sysopCountArr[0]);
sysopTotal = parseInt(sysopCountArr[1]);
displaySiteInfo();
return;
}
$.ajax({
url: '//globalcsd.toolforge.org/active-sysops/',
data: {
site: mw.config.get('wgDBname'),
last_action: lastAction,
limit: sysopLimit,
},
dataType: 'jsonp',
}).done(function(data) {
if (!data.success) {
return;
}
sysopCount = data.count;
sysopTotal = data.total;
sysopList = data.sysops;
//mw.cookie.set(sysopListStorageKey, JSON.stringify(sysopList), 25*3600); // Expire after 25h
try {
localStorage.setItem(sysopListStorageKey, JSON.stringify(sysopList));
} catch (e) {}
mw.cookie.set(sysopCountStorageKey, sysopCount+'|'+sysopTotal, 25*3600); // Expire after 25h
displaySiteInfo();
});
}
createInterface();
checkGSWiki();
//checkActiveSysops(); // lastAction must be determined to execute this function
} );