// ==UserScript==
// @name Stack Exchange Global Flag Summary
// @namespace http://floern.com/
// @version 1.2.3
// @description Stack Exchange networkwide flag summary available in your network profile
// @author Floern
// @include *://stackexchange.com/users/*/*
// @match *://*.stackexchange.com/users/flag-summary/*
// @match *://*.stackoverflow.com/users/flag-summary/*
// @match *://*.superuser.com/users/flag-summary/*
// @match *://*.serverfault.com/users/flag-summary/*
// @match *://*.askubuntu.com/users/flag-summary/*
// @match *://*.stackapps.com/users/flag-summary/*
// @match *://*.mathoverflow.net/users/flag-summary/*
// @connect stackexchange.com
// @connect stackoverflow.com
// @connect superuser.com
// @connect serverfault.com
// @connect askubuntu.com
// @connect stackapps.com
// @connect mathoverflow.net
// @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @grant GM.xmlHttpRequest
// @grant GM_xmlhttpRequest
// @grant GM.addStyle
// @grant GM_addStyle
// @run-at document-end
// @updateURL https://raw.githubusercontent.com/Floern/stackoverflow/master/userscripts/SE_global_flag_summary.meta.js
// @downloadURL https://raw.githubusercontent.com/Floern/stackoverflow/master/userscripts/SE_global_flag_summary.user.js
// ==/UserScript==
let flagSummaryTable, flagSummaryTableBody, errorView;
let sortedColIndex = 2;
let sortedColAsc = false;
let flagGlobalSummaryStats = {
sumFlagsTotal: 0,
sumFlagsDeclined: 0,
sumFlagsDisputed: 0,
sumFlagsRetracted: 0,
sumFlagsExpired: 0,
sumFlagsPending: 0,
sumFlagsHelpful: 0
};
let rateLimited = false;
let reloadIcon = `iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADrUlEQVRYR+1WS2gTURRN/OFfURER/10IUtqmS
WYmEQz+EFEXohVxIaLgStHMZJLUFmapuBBRKlbnk4+/xi5c+NkIipui1i4FUXShLiqiiPihfuK5yTOdycwkqdaK4IFDMvPuuXPfffe9
+zwjgYCohzklO5U9jjw42TjFi+oDIXp2Bnv1e4goxvhwLFUXkPRgsDW7WpD1Zk46s9i/t3MsM7EAAeghScsLktYXkjKz2euhQYh2TRB
i2k5e0m7A0Xs4/E5OB6l+FyT1rSDp3Vxc2xpRbo1hUg8vG7mfdoKo3fMoyig2VB0tudxopG8XhC+tH6xM2D8RYvoO+pgQ1W4OvldfRR
SlFFxFNCrGdDi5bnY8VCJb3chMT+m51gC4eHZeSNQemp0NB2sKoEHKTMJ69zo5ICK93zCzO3xMOxmUU+18TD8Kx9fx7oOTvZk1BYBCO
uUkBgdQhCf8ifQCZmqBP9E5DUG1IeXvHLQFVg2Ai54RaIZ2sfqak9WVzKwETtJXCbHU5kAivZuLGwfxAQX2hl1fZNUAkMZrDsKPCGAF
M7EAdZKkLeigcSQC6G9pyY1mciv4A8Yip9ljSVqZiR35vBfj+xC4Q9bsxKH0mCntgJNouQAB9VNRMhNXIP17sPZfCxoEg1r5gi08gJr
4SCwcUkg/L+s9TGIHKv+qLQCsJxuuirCszqUs+sXzs5YpuclEv9I5kYj/42jtXdNPoJly+7NTzaTDiA3/x78HXETm86K+tqH9gq9RyT
URG1ov1bNhO5rbUnXU34nNyXMbfYcym4Lx1DYUIip8kMtdTsJyYAd1OBT1E9d2jL59ulzgwmdU7UzmiAh2AG29ci0CcN9VQwiAzoeng
aS2hElt4CW93UkXko3VzMQOdDbVUeRCzOZ5MJmeyeQl4DxZg7EBB/u+irchrHemXFRgsf0eD8Qz4QblUn1j4uKin+cEzmIvkxfAx9Qt
0NjacuGUjOvrmJkzXAMAyQHSmg7I2aXM3IS8N5RI+3D0XnZrTOgX1U/USgGYiMun9gi/50OiquIy0oXnZ2U2FmL8Ti39xHJ7HS7Sx6k
XsE9UhuX2KmmfOFk/TL9mh7WSlgzF2FHzxwnWAIo3l4Co+rCut83Oa+BdQdYizG3tCEnGdsz4MzmxXp3y3mDMaMKMjqCY7sPmDfV6si
v0fZGetV7UwzFeSvmLml8EJxkb4JguEi9c9qyXejv1emRmYVi+MoWe2djwgI+n1uPq1Mce/w4wqzns7x+Ex/MDD9PvSmMer2UAAAAAS
UVORK5CYII=`;
// init
(function () {
if (window.location.href.match(/\/users\/flag-summary\/\d+/i)) {
showGlobalFlagSummaryLink();
return;
}
if (!window.location.href.match(/:\/\/stackexchange\.com\/users\/\d+/i)) {
return;
}
let navigation = document.querySelector('#content .contentWrapper .subheader');
if (!navigation) {
return;
}
let tabbar = navigation.querySelector('.tabs');
// verify that we are in the profile of the logged in user
let tabs = tabbar.getElementsByTagName('a');
let loggedIn = false;
for (let i = 0; i < tabs.length; i++) {
if (tabs[i].textContent.trim().toLowerCase() == 'inbox') {
loggedIn = true;
break;
}
}
if (!loggedIn) {
return;
}
// add navigation tab for flags
let flagTab = document.createElement('a');
flagTab.setAttribute('href', '?tab=flags');
flagTab.textContent = 'flags';
tabs[4].parentNode.insertBefore(flagTab, tabs[4]);
if (!window.location.href.match(/:\/\/stackexchange\.com\/users\/\d+\/.+?\?tab=flags/i)) {
return;
}
// unselect default tab
let selectedTab = navigation.querySelector('.youarehere');
selectedTab.className = '';
// set selected tab to flags
flagTab.className = 'youarehere';
// remove default content
while (navigation.nextSibling) {
navigation.parentNode.removeChild(navigation.nextSibling);
}
document.querySelector('title').textContent = 'Flag Summary - Stack Exchange';
let container = document.createElement('div');
navigation.parentNode.appendChild(container);
// setup summary table
flagSummaryTable = document.createElement('table');
flagSummaryTable.id = 'flag-summary-table';
flagSummaryTable.style.width = '100%';
flagSummaryTable.style.textAlign = 'right';
flagSummaryTable.style.borderCollapse = 'separate';
flagSummaryTable.style.borderSpacing = '0 5px';
flagSummaryTable.innerHTML = `
Site
helpful
declined
disputed
expired
retracted
pending
total
helpful %
last flag

';
loadSiteFlagSummary(siteName, siteFlagSummaryUrl, function() {
// remove old row
flagSummaryTableBody.removeChild(siteFlagSummaryTr);
flagGlobalSummaryStats.sumFlagsTotal -= sumFlagsTotal;
flagGlobalSummaryStats.sumFlagsDeclined -= sumFlagsDeclined;
flagGlobalSummaryStats.sumFlagsDisputed -= sumFlagsDisputed;
flagGlobalSummaryStats.sumFlagsRetracted -= sumFlagsRetracted;
flagGlobalSummaryStats.sumFlagsExpired -= sumFlagsExpired;
flagGlobalSummaryStats.sumFlagsPending -= sumFlagsPending;
flagGlobalSummaryStats.sumFlagsHelpful -= sumFlagsHelpful;
updateGlobalFlagStats();
});
};
let anchorBottom = window.pageYOffset > 9 && (window.innerHeight + window.pageYOffset) >= document.body.offsetHeight - 2;
flagSummaryTableBody.appendChild(siteFlagSummaryTr);
// keep order
sortTable(sortedColIndex, sortedColAsc);
if (anchorBottom) {
// keep scroll position at bottom
window.scrollTo(window.pageXOffset, document.body.scrollHeight);
}
}
/**
* Format flag count, empty if 0.
*/
function formatFlagCount(flagCount) {
if (flagCount == 0)
return '';
else
return flagCount;
}
/**
* Format helpful flag percentage.
*/
function formatFlagPercentage(fraction) {
return (fraction * 100).toFixed(2) + '%';
}
/**
* Format relative time.
*/
function formatTimeRelative(e) {
if (null != e && 20 == e.length) {
e = e.substr(0, 10) + "T" + e.substr(11, 10);
let date = new Date(e),
dsecs = Math.floor((Date.now() - date.getTime()) / 1e3),
ddays = Math.floor(dsecs / 86400);
if (0 <= ddays && ddays < 7 || ddays == 42) {
if (dsecs < 2) return 'just now';
if (dsecs < 60) return dsecs + ' secs ago';
if (dsecs < 120) return '1 min ago';
let dmins = Math.floor(dsecs / 60);
if (dmins < 60) return dmins + ' mins ago';
if (dmins < 120) return '1 hour ago';
let dhrs = Math.floor(dmins / 60);
if (dhrs < 18) return dhrs + ' hours ago';
if (dhrs < 48) return 'yesterday';
else return ddays + ' days ago';
}
else {
let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
return months[date.getMonth()] + ' ' + date.getDate() +
(date.getFullYear() != new Date().getFullYear() ? " '" + date.getFullYear() % 100 : '');
}
}
else {
return e;
}
}
/**
* Show an error.
*/
function showLoadingError(url, statuscode, siteName) {
let errorMsg = document.createElement('div');
errorMsg.style.paddingTop = '4px';
let errorHtml = 'Failed to load ' + (siteName || url) + ' ';
if (statuscode <= 0 && siteName) errorHtml += '(retry)';
else if (statuscode == 429) errorHtml += '(rate limited)';
else errorHtml += 'with status ' + statuscode;
errorMsg.innerHTML = errorHtml;
let retrybtn = errorMsg.getElementsByClassName('segfs-retry');
if (retrybtn.length > 0) {
retrybtn[0].onclick = function(e) {
loadSiteFlagSummary(siteName, url, function(){});
errorMsg.outerHTML = '';
return false;
};
}
errorView.appendChild(errorMsg);
}
/**
* Sort the table by column index `col` and bool `asc`.
*/
function sortTable(col, asc) {
sortedColIndex = col;
let trs = [].slice.call(flagSummaryTableBody.rows, 0);
asc = -((+asc) || -1);
if (col == 1) {
// site name
trs = trs.sort(function (a, b) {
return asc * (a.cells[col].textContent.trim().localeCompare(b.cells[col].textContent.trim()));
});
}
else if (col == 10) {
// date
trs = trs.sort(function (a, b) {
return asc * (b.cells[col].title.localeCompare(a.cells[col].title));
});
}
else {
trs = trs.sort(function (a, b) {
let va = parseInt(a.cells[col].textContent.replace(/\D/g, '')) || 0;
let vb = parseInt(b.cells[col].textContent.replace(/\D/g, '')) || 0;
if (va != vb) // primary order
return asc * (vb - va);
else // secondary order
return a.cells[1].textContent.trim().localeCompare(b.cells[1].textContent.trim());
});
}
for (let i = 0; i < trs.length; ++i) flagSummaryTableBody.appendChild(trs[i]);
}